diff options
Diffstat (limited to 'test')
63 files changed, 880 insertions, 169 deletions
diff --git a/test/conftest.py b/test/conftest.py index 4d46e2fc..689c857a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -14,7 +14,6 @@ import time from multiprocessing import Process import pytest - from unit.check.chroot import check_chroot from unit.check.go import check_go from unit.check.isolation import check_isolation @@ -357,7 +356,7 @@ def run(request): _check_alerts(log=log) -def unit_run(): +def unit_run(state_dir=None): global unit_instance if not option.restart and 'unitd' in unit_instance: @@ -375,7 +374,9 @@ def unit_run(): if oct(stat.S_IMODE(os.stat(build_dir).st_mode)) != '0o777': public_dir(build_dir) - os.mkdir(temp_dir + '/state') + state = temp_dir + '/state' if state_dir is None else state_dir + if not os.path.isdir(state): + os.mkdir(state) unitd_args = [ unitd, @@ -383,7 +384,7 @@ def unit_run(): '--modules', build_dir, '--state', - temp_dir + '/state', + state, '--pid', temp_dir + '/unit.pid', '--log', @@ -415,7 +416,8 @@ def unit_run(): with open(temp_dir + '/unit.pid', 'r') as f: unit_instance['pid'] = f.read().rstrip() - _clear_conf(unit_instance['temp_dir'] + '/control.unit.sock') + if state_dir is None: + _clear_conf(unit_instance['temp_dir'] + '/control.unit.sock') _fds_info['main']['fds'] = _count_fds(unit_instance['pid']) diff --git a/test/php/opcache/index.php b/test/php/opcache/index.php new file mode 100644 index 00000000..de4002bb --- /dev/null +++ b/test/php/opcache/index.php @@ -0,0 +1,18 @@ +<?php + +$pid = getmypid(); + +header('X-Pid: ' . $pid); + +if (function_exists('opcache_is_script_cached')) { + if (opcache_is_script_cached(__DIR__ . '/test.php')) { + header('X-Cached: 1'); + } else { + header('X-Cached: 0'); + opcache_compile_file(__DIR__ . '/test.php'); + } +} else { + header('X-Cached: -1'); +} + +?> diff --git a/test/php/opcache/test.php b/test/php/opcache/test.php new file mode 100644 index 00000000..147cebcd --- /dev/null +++ b/test/php/opcache/test.php @@ -0,0 +1 @@ +<?php phpinfo(); ?> diff --git a/test/python/restart/v1.py b/test/python/restart/v1.py index 2e45b269..08f7dd64 100644 --- a/test/python/restart/v1.py +++ b/test/python/restart/v1.py @@ -1,5 +1,3 @@ -import os - def application(environ, start_response): body = "v1".encode() diff --git a/test/python/restart/v2.py b/test/python/restart/v2.py index 59e3d30f..163d0d17 100644 --- a/test/python/restart/v2.py +++ b/test/python/restart/v2.py @@ -1,5 +1,3 @@ -import os - def application(environ, start_response): body = "v2".encode() diff --git a/test/python/threading/asgi.py b/test/python/threading/asgi.py index c4169a24..fed6fcce 100644 --- a/test/python/threading/asgi.py +++ b/test/python/threading/asgi.py @@ -1,7 +1,6 @@ -import asyncio import sys -import time import threading +import time class Foo(threading.Thread): diff --git a/test/python/threading/wsgi.py b/test/python/threading/wsgi.py index adaa2a37..48a73afd 100644 --- a/test/python/threading/wsgi.py +++ b/test/python/threading/wsgi.py @@ -1,6 +1,6 @@ import sys -import time import threading +import time class Foo(threading.Thread): diff --git a/test/python/threads/asgi.py b/test/python/threads/asgi.py index ff4e52ad..0537f59b 100644 --- a/test/python/threads/asgi.py +++ b/test/python/threads/asgi.py @@ -1,6 +1,5 @@ -import asyncio -import time import threading +import time async def application(scope, receive, send): diff --git a/test/python/threads/wsgi.py b/test/python/threads/wsgi.py index cc283cfe..35db2d07 100644 --- a/test/python/threads/wsgi.py +++ b/test/python/threads/wsgi.py @@ -1,5 +1,5 @@ -import time import threading +import time def application(environ, start_response): diff --git a/test/python/upload/wsgi.py b/test/python/upload/wsgi.py index 953c5ecc..2c820d06 100644 --- a/test/python/upload/wsgi.py +++ b/test/python/upload/wsgi.py @@ -1,5 +1,5 @@ +import cgi from tempfile import TemporaryFile -import os, cgi def read(environ): diff --git a/test/requirements.txt b/test/requirements.txt new file mode 100644 index 00000000..5f94fad2 --- /dev/null +++ b/test/requirements.txt @@ -0,0 +1,2 @@ +pyOpenSSL>=20.0.1 +pytest>=6.0.1 diff --git a/test/test_access_log.py b/test/test_access_log.py index ba254c5e..5d242a1a 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -1,7 +1,6 @@ import time import pytest - from unit.applications.lang.python import TestApplicationPython from unit.option import option diff --git a/test/test_asgi_application.py b/test/test_asgi_application.py index f503fa82..021aa2b2 100644 --- a/test/test_asgi_application.py +++ b/test/test_asgi_application.py @@ -3,9 +3,7 @@ import time from distutils.version import LooseVersion import pytest - from unit.applications.lang.python import TestApplicationPython -from unit.option import option class TestASGIApplication(TestApplicationPython): diff --git a/test/test_asgi_lifespan.py b/test/test_asgi_lifespan.py index 90866ec3..912d0d85 100644 --- a/test/test_asgi_lifespan.py +++ b/test/test_asgi_lifespan.py @@ -1,8 +1,6 @@ import os from distutils.version import LooseVersion -import pytest - from conftest import unit_stop from unit.applications.lang.python import TestApplicationPython from unit.option import option diff --git a/test/test_asgi_targets.py b/test/test_asgi_targets.py index a0eb1f84..b9489cd3 100644 --- a/test/test_asgi_targets.py +++ b/test/test_asgi_targets.py @@ -1,7 +1,6 @@ from distutils.version import LooseVersion import pytest - from unit.applications.lang.python import TestApplicationPython from unit.option import option diff --git a/test/test_asgi_websockets.py b/test/test_asgi_websockets.py index 140bcb9a..bad54e22 100644 --- a/test/test_asgi_websockets.py +++ b/test/test_asgi_websockets.py @@ -3,7 +3,6 @@ import time from distutils.version import LooseVersion import pytest - from unit.applications.lang.python import TestApplicationPython from unit.applications.websockets import TestApplicationWebsocket from unit.option import option @@ -1481,3 +1480,20 @@ class TestASGIWebsockets(TestApplicationPython): self.check_frame(frame, True, self.ws.OP_PING, '') # PING frame sock.close() + + def test_asgi_websockets_client_locks_app(self): + self.load('websockets/mirror') + + message = 'blah' + + _, sock, _ = self.ws.upgrade() + + assert 'success' in self.conf({}), 'remove app' + + self.ws.frame_write(sock, self.ws.OP_TEXT, message) + + frame = self.ws.frame_read(sock) + + assert message == frame['data'].decode('utf-8'), 'client' + + sock.close() diff --git a/test/test_client_ip.py b/test/test_client_ip.py index 0084574e..4b2b2fa1 100644 --- a/test/test_client_ip.py +++ b/test/test_client_ip.py @@ -1,5 +1,3 @@ -import pytest - from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_configuration.py b/test/test_configuration.py index 8655968f..4a9d9840 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -1,7 +1,6 @@ import socket import pytest - from unit.control import TestControl diff --git a/test/test_go_application.py b/test/test_go_application.py index 438ce2e0..94da1aee 100644 --- a/test/test_go_application.py +++ b/test/test_go_application.py @@ -1,11 +1,17 @@ import re +import pytest + from unit.applications.lang.go import TestApplicationGo class TestGoApplication(TestApplicationGo): prerequisites = {'modules': {'go': 'all'}} + @pytest.fixture(autouse=True) + def setup_method_fixture(self, request, skip_alert): + skip_alert(r'\[unit\] close\(\d+\) failed: Bad file descriptor') + def test_go_application_variables(self): self.load('variables') diff --git a/test/test_go_isolation.py b/test/test_go_isolation.py index e02ef1cf..72988a34 100644 --- a/test/test_go_isolation.py +++ b/test/test_go_isolation.py @@ -3,7 +3,6 @@ import os import pwd import pytest - from unit.applications.lang.go import TestApplicationGo from unit.option import option from unit.utils import getns @@ -12,6 +11,10 @@ from unit.utils import getns class TestGoIsolation(TestApplicationGo): prerequisites = {'modules': {'go': 'any'}, 'features': ['isolation']} + @pytest.fixture(autouse=True) + def setup_method_fixture(self, request, skip_alert): + skip_alert(r'\[unit\] close\(\d+\) failed: Bad file descriptor') + def unpriv_creds(self): nobody_uid = pwd.getpwnam('nobody').pw_uid @@ -228,7 +231,7 @@ class TestGoIsolation(TestApplicationGo): obj = self.getjson()['body'] - assert obj['PID'] == 1, 'pid of container is 1' + assert obj['PID'] == 2, 'pid of container is 2' def test_isolation_namespace_false(self): self.load('ns_inspect') diff --git a/test/test_go_isolation_rootfs.py b/test/test_go_isolation_rootfs.py index 1cc59c67..d246a48d 100644 --- a/test/test_go_isolation_rootfs.py +++ b/test/test_go_isolation_rootfs.py @@ -1,13 +1,16 @@ import os import pytest - from unit.applications.lang.go import TestApplicationGo class TestGoIsolationRootfs(TestApplicationGo): prerequisites = {'modules': {'go': 'all'}} + @pytest.fixture(autouse=True) + def setup_method_fixture(self, request, skip_alert): + skip_alert(r'\[unit\] close\(\d+\) failed: Bad file descriptor') + def test_go_isolation_rootfs_chroot(self, is_su, temp_dir): if not is_su: pytest.skip('requires root') diff --git a/test/test_http_header.py b/test/test_http_header.py index fdb557cf..ca355eb7 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -1,5 +1,4 @@ import pytest - from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_java_isolation_rootfs.py b/test/test_java_isolation_rootfs.py index a401e23b..eac86a0c 100644 --- a/test/test_java_isolation_rootfs.py +++ b/test/test_java_isolation_rootfs.py @@ -2,7 +2,6 @@ import os import subprocess import pytest - from unit.applications.lang.java import TestApplicationJava from unit.option import option @@ -19,7 +18,7 @@ class TestJavaIsolationRootfs(TestApplicationJava): os.chmod(option.temp_dir + '/tmp', 0o777) try: - process = subprocess.Popen( + subprocess.run( [ "mount", "--bind", @@ -29,12 +28,10 @@ class TestJavaIsolationRootfs(TestApplicationJava): stderr=subprocess.STDOUT, ) - process.communicate() - except KeyboardInterrupt: raise - except: + except subprocess.CalledProcessError: pytest.fail('Can\'t run mount process.') def teardown_method(self, is_su): @@ -42,18 +39,16 @@ class TestJavaIsolationRootfs(TestApplicationJava): return try: - process = subprocess.Popen( + subprocess.run( ["umount", "--lazy", option.temp_dir + "/jars"], stderr=subprocess.STDOUT, ) - process.communicate() - except KeyboardInterrupt: raise - except: - pytest.fail('Can\'t run mount process.') + except subprocess.CalledProcessError: + pytest.fail('Can\'t run umount process.') def test_java_isolation_rootfs_chroot_war(self, is_su, temp_dir): if not is_su: diff --git a/test/test_java_websockets.py b/test/test_java_websockets.py index df0f76e8..a80d3bf3 100644 --- a/test/test_java_websockets.py +++ b/test/test_java_websockets.py @@ -2,7 +2,6 @@ import struct import time import pytest - from unit.applications.lang.java import TestApplicationJava from unit.applications.websockets import TestApplicationWebsocket from unit.option import option diff --git a/test/test_node_application.py b/test/test_node_application.py index 48ed8d3d..62a09c43 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -1,7 +1,6 @@ import re import pytest - from unit.applications.lang.node import TestApplicationNode from unit.utils import waitforfiles diff --git a/test/test_node_es_modules.py b/test/test_node_es_modules.py index 5464d4a6..12788fa4 100644 --- a/test/test_node_es_modules.py +++ b/test/test_node_es_modules.py @@ -1,7 +1,5 @@ from distutils.version import LooseVersion -import pytest - from unit.applications.lang.node import TestApplicationNode from unit.applications.websockets import TestApplicationWebsocket diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py index 51515f4e..e4c8a05e 100644 --- a/test/test_node_websockets.py +++ b/test/test_node_websockets.py @@ -2,7 +2,6 @@ import struct import time import pytest - from unit.applications.lang.node import TestApplicationNode from unit.applications.websockets import TestApplicationWebsocket from unit.option import option diff --git a/test/test_perl_application.py b/test/test_perl_application.py index e906aaca..dfd8be6c 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -1,7 +1,6 @@ import re import pytest - from unit.applications.lang.perl import TestApplicationPerl diff --git a/test/test_php_application.py b/test/test_php_application.py index 66e2ef7d..d9c16a6d 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -5,7 +5,6 @@ import signal import time import pytest - from unit.applications.lang.php import TestApplicationPHP from unit.option import option @@ -714,3 +713,20 @@ class TestPHPApplication(TestApplicationPHP): ), 'relative path w/ chdir' assert self.get()['body'] == 'test', 'relative path 2' + + def test_php_application_shared_opcache(self): + self.load('opcache', limits={'requests': 1}) + + r = self.get() + cached = r['headers']['X-Cached'] + if cached == '-1': + pytest.skip('opcache is not supported') + + pid = r['headers']['X-Pid'] + + assert cached == '0', 'not cached' + + r = self.get() + + assert r['headers']['X-Pid'] != pid, 'new instance' + assert r['headers']['X-Cached'] == '1', 'cached' diff --git a/test/test_php_isolation.py b/test/test_php_isolation.py index 8db6b590..aebeefa6 100644 --- a/test/test_php_isolation.py +++ b/test/test_php_isolation.py @@ -1,5 +1,4 @@ import pytest - from unit.applications.lang.php import TestApplicationPHP from unit.option import option diff --git a/test/test_proxy.py b/test/test_proxy.py index 3d59cf24..553cb07c 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -3,7 +3,6 @@ import socket import time import pytest - from conftest import run_process from unit.applications.lang.python import TestApplicationPython from unit.option import option diff --git a/test/test_python_application.py b/test/test_python_application.py index 48c3d603..7bd43664 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -5,7 +5,6 @@ import re import time import pytest - from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_python_isolation.py b/test/test_python_isolation.py index 93f85264..53d28285 100644 --- a/test/test_python_isolation.py +++ b/test/test_python_isolation.py @@ -1,5 +1,4 @@ import pytest - from unit.applications.lang.python import TestApplicationPython from unit.option import option from unit.utils import findmnt diff --git a/test/test_python_isolation_chroot.py b/test/test_python_isolation_chroot.py index 54fbad12..1554fb72 100644 --- a/test/test_python_isolation_chroot.py +++ b/test/test_python_isolation_chroot.py @@ -1,5 +1,4 @@ import pytest - from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_python_procman.py b/test/test_python_procman.py index a95c5680..a25b84ec 100644 --- a/test/test_python_procman.py +++ b/test/test_python_procman.py @@ -4,7 +4,6 @@ import subprocess import time import pytest - from unit.applications.lang.python import TestApplicationPython from unit.option import option @@ -23,7 +22,9 @@ class TestPythonProcman(TestApplicationPython): output = subprocess.check_output(['ps', 'ax']) pids = set() - for m in re.findall('.*' + self.app_name, output.decode()): + for m in re.findall( + '.*unit: "' + self.app_name + '" application', output.decode() + ): pids.add(re.search(r'^\s*(\d+)', m).group(1)) return pids @@ -265,7 +266,8 @@ class TestPythonProcman(TestApplicationPython): assert len(self.pids_for_process()) == 1, 'longstarts == 1' - pid = self.get()['body'] + self.get() + pids = self.pids_for_process() assert len(pids) == 2, 'longstarts == 2' diff --git a/test/test_python_targets.py b/test/test_python_targets.py index ca736c0d..e5dca870 100644 --- a/test/test_python_targets.py +++ b/test/test_python_targets.py @@ -1,5 +1,3 @@ -import pytest - from unit.applications.lang.python import TestApplicationPython from unit.option import option diff --git a/test/test_routing.py b/test/test_routing.py index ef5622c2..167d2640 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- import pytest - from unit.applications.proto import TestApplicationProto from unit.option import option @@ -1321,6 +1320,58 @@ class TestRouting(TestApplicationProto): self.route_match_invalid({"arguments": {"%%1F": ""}}) self.route_match_invalid({"arguments": {"%7%F": ""}}) + def test_routes_match_query(self): + self.route_match({"query": "!"}) + assert self.get(url='/')['status'] == 404 + assert self.get(url='/?')['status'] == 404 + assert self.get(url='/?foo')['status'] == 200 + assert self.get(url='/?foo=')['status'] == 200 + assert self.get(url='/?foo=baz')['status'] == 200 + + self.route_match({"query": "foo=%26"}) + assert self.get(url='/?foo=&')['status'] == 200 + + self.route_match({"query": "a=b&c=d"}) + assert self.get(url='/?a=b&c=d')['status'] == 200 + + self.route_match({"query": "a=b%26c%3Dd"}) + assert self.get(url='/?a=b%26c%3Dd')['status'] == 200 + assert self.get(url='/?a=b&c=d')['status'] == 200 + + self.route_match({"query": "a=b%26c%3Dd+e"}) + assert self.get(url='/?a=b&c=d e')['status'] == 200 + + def test_routes_match_query_array(self): + self.route_match({"query": ["foo", "bar"]}) + + assert self.get()['status'] == 404, 'no args' + assert self.get(url='/?foo')['status'] == 200, 'arg first' + assert self.get(url='/?bar')['status'] == 200, 'arg second' + + assert 'success' in self.conf_delete( + 'routes/0/match/query/1' + ), 'query array remove second' + + assert self.get(url='/?foo')['status'] == 200, 'still arg first' + assert self.get(url='/?bar')['status'] == 404, 'no arg second' + + self.route_match({"query": ["!f", "foo"]}) + + assert self.get(url='/?f')['status'] == 404, 'negative arg' + assert self.get(url='/?fo')['status'] == 404, 'negative arg 2' + assert self.get(url='/?foo')['status'] == 200, 'negative arg 3' + + self.route_match({"query": []}) + assert self.get()['status'] == 200, 'empty array' + + def test_routes_match_query_invalid(self): + self.route_match_invalid({"query": [1]}) + self.route_match_invalid({"query": "%"}) + self.route_match_invalid({"query": "%1G"}) + self.route_match_invalid({"query": "%0"}) + self.route_match_invalid({"query": "%%1F"}) + self.route_match_invalid({"query": ["foo", "%3D", "%%1F"]}) + def test_routes_match_cookies(self): self.route_match({"cookies": {"foO": "bar"}}) diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index ddd31f59..ed0200d9 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -2,7 +2,6 @@ import re import subprocess import pytest - from unit.applications.lang.ruby import TestApplicationRuby diff --git a/test/test_ruby_hooks.py b/test/test_ruby_hooks.py index af8ce337..20980ad7 100644 --- a/test/test_ruby_hooks.py +++ b/test/test_ruby_hooks.py @@ -1,10 +1,3 @@ -import os -import time -from pathlib import Path - -import pytest - -from conftest import unit_stop from unit.applications.lang.ruby import TestApplicationRuby from unit.option import option from unit.utils import waitforglob diff --git a/test/test_ruby_isolation.py b/test/test_ruby_isolation.py index f414d610..940427f1 100644 --- a/test/test_ruby_isolation.py +++ b/test/test_ruby_isolation.py @@ -1,8 +1,4 @@ -import os -import shutil - import pytest - from unit.applications.lang.ruby import TestApplicationRuby from unit.option import option diff --git a/test/test_settings.py b/test/test_settings.py index 49041b62..a16e35e8 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -3,7 +3,6 @@ import socket import time import pytest - from unit.applications.lang.python import TestApplicationPython from unit.utils import sysctl diff --git a/test/test_static.py b/test/test_static.py index 669e265d..80f4c610 100644 --- a/test/test_static.py +++ b/test/test_static.py @@ -1,8 +1,9 @@ import os +import shutil import socket import pytest - +from conftest import unit_run, unit_stop from unit.applications.proto import TestApplicationProto from unit.option import option from unit.utils import waitforfiles @@ -28,7 +29,9 @@ class TestStatic(TestApplicationProto): self._load_conf( { "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": option.temp_dir + "/assets"}}], + "routes": [ + {"action": {"share": option.temp_dir + "/assets$uri"}} + ], "settings": { "http": { "static": { @@ -39,6 +42,49 @@ class TestStatic(TestApplicationProto): } ) + def test_static_migration(self, skip_fds_check, temp_dir): + skip_fds_check(True, True, True) + + def set_conf_version(path, version): + with open(path, 'w+') as f: + f.write(str(version)) + + with open(temp_dir + '/state/version', 'r') as f: + assert int(f.read().rstrip()) > 12500, 'current version' + + assert 'success' in self.conf( + {"share": temp_dir + "/assets"}, 'routes/0/action' + ), 'configure migration 12500' + + shutil.copytree(temp_dir + '/state', temp_dir + '/state_copy_12500') + set_conf_version(temp_dir + '/state_copy_12500/version', 12500) + + assert 'success' in self.conf( + {"share": temp_dir + "/assets$uri"}, 'routes/0/action' + ), 'configure migration 12600' + shutil.copytree(temp_dir + '/state', temp_dir + '/state_copy_12600') + set_conf_version(temp_dir + '/state_copy_12600/version', 12600) + + assert 'success' in self.conf( + {"share": temp_dir + "/assets"}, 'routes/0/action' + ), 'configure migration no version' + shutil.copytree( + temp_dir + '/state', temp_dir + '/state_copy_no_version' + ) + os.remove(temp_dir + '/state_copy_no_version/version') + + unit_stop() + unit_run(temp_dir + '/state_copy_12500') + assert self.get(url='/')['body'] == '0123456789', 'before 1.26.0' + + unit_stop() + unit_run(temp_dir + '/state_copy_12600') + assert self.get(url='/')['body'] == '0123456789', 'after 1.26.0' + + unit_stop() + unit_run(temp_dir + '/state_copy_no_version') + assert self.get(url='/')['body'] == '0123456789', 'before 1.26.0 2' + def test_static_index(self): assert self.get(url='/index.html')['body'] == '0123456789', 'index' assert self.get(url='/')['body'] == '0123456789', 'index 2' diff --git a/test/test_static_chroot.py b/test/test_static_chroot.py index f9bc93a8..62288807 100644 --- a/test/test_static_chroot.py +++ b/test/test_static_chroot.py @@ -2,7 +2,6 @@ import os from pathlib import Path import pytest - from unit.applications.proto import TestApplicationProto @@ -21,17 +20,27 @@ class TestStaticChroot(TestApplicationProto): self._load_conf( { "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": temp_dir + "/assets"}}], + "routes": [{"action": {"share": temp_dir + "/assets$uri"}}], } ) + def update_action(self, share, chroot): + return self.conf( + {"share": share, "chroot": chroot}, 'routes/0/action', + ) + + def get_custom(self, uri, host): + return self.get( + url=uri, headers={'Host': host, 'Connection': 'close'} + )['status'] + def test_static_chroot(self, temp_dir): assert self.get(url='/dir/file')['status'] == 200, 'default chroot' assert self.get(url='/index.html')['status'] == 200, 'default chroot 2' assert 'success' in self.conf( { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "chroot": temp_dir + "/assets/dir", }, 'routes/0/action', @@ -41,6 +50,30 @@ class TestStaticChroot(TestApplicationProto): assert self.get(url='/index.html')['status'] == 403, 'chroot 403 2' assert self.get(url='/file')['status'] == 403, 'chroot 403' + def test_share_chroot_array(self, temp_dir): + assert 'success' in self.conf( + { + "share": ["/blah", temp_dir + "/assets$uri"], + "chroot": temp_dir + "/assets/dir", + }, + 'routes/0/action', + ), 'configure share array' + assert self.get(url='/dir/file')['status'] == 200, 'share array' + + assert 'success' in self.update_action( + ["/blah", temp_dir + '/assets$uri'], temp_dir + '/assets/$host' + ) + assert self.get_custom('/dir/file', 'dir') == 200, 'array variable' + + assert 'success' in self.conf( + { + "share": ["/blah", "/blah2"], + "chroot": temp_dir + "/assets/dir", + }, + 'routes/0/action', + ), 'configure share array bad' + assert self.get()['status'] != 200, 'share array bad' + def test_static_chroot_permission(self, is_su, temp_dir): if is_su: pytest.skip('does\'t work under root') @@ -49,7 +82,7 @@ class TestStaticChroot(TestApplicationProto): assert 'success' in self.conf( { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "chroot": temp_dir + "/assets/dir", }, 'routes/0/action', @@ -59,7 +92,8 @@ class TestStaticChroot(TestApplicationProto): def test_static_chroot_empty(self, temp_dir): assert 'success' in self.conf( - {"share": temp_dir + "/assets", "chroot": ""}, 'routes/0/action', + {"share": temp_dir + "/assets$uri", "chroot": ""}, + 'routes/0/action', ), 'configure chroot empty absolute' assert ( @@ -67,7 +101,7 @@ class TestStaticChroot(TestApplicationProto): ), 'chroot empty absolute' assert 'success' in self.conf( - {"share": ".", "chroot": ""}, 'routes/0/action', + {"share": ".$uri", "chroot": ""}, 'routes/0/action', ), 'configure chroot empty relative' assert ( @@ -79,23 +113,99 @@ class TestStaticChroot(TestApplicationProto): pytest.skip('does\'t work under root') assert 'success' in self.conf( - {"share": temp_dir + "/assets", "chroot": "."}, 'routes/0/action', + {"share": temp_dir + "/assets$uri", "chroot": "."}, + 'routes/0/action', ), 'configure relative chroot' assert self.get(url='/dir/file')['status'] == 403, 'relative chroot' assert 'success' in self.conf( - {"share": "."}, 'routes/0/action', + {"share": ".$uri"}, 'routes/0/action', ), 'configure relative share' assert self.get(url=self.test_path)['status'] == 200, 'relative share' assert 'success' in self.conf( - {"share": ".", "chroot": "."}, 'routes/0/action', + {"share": ".$uri", "chroot": "."}, 'routes/0/action', ), 'configure relative' assert self.get(url=self.test_path)['status'] == 200, 'relative' + def test_static_chroot_variables(self, temp_dir): + assert 'success' in self.update_action( + temp_dir + '/assets$uri', temp_dir + '/assets/$host' + ) + assert self.get_custom('/dir/file', 'dir') == 200 + + assert 'success' in self.update_action( + temp_dir + '/assets$uri', temp_dir + '/assets/${host}' + ) + assert self.get_custom('/dir/file', 'dir') == 200 + + def test_static_chroot_variables_buildin_start(self, temp_dir): + assert 'success' in self.update_action( + temp_dir + '/assets/dir/$host', '$uri/assets/dir' + ) + + assert self.get_custom(temp_dir, 'file') == 200 + + def test_static_chroot_variables_buildin_mid(self, temp_dir): + assert 'success' in self.update_action( + temp_dir + '/assets$uri', temp_dir + '/$host/dir' + ) + + assert self.get_custom('/dir/file', 'assets') == 200 + + def test_static_chroot_variables_buildin_end(self, temp_dir): + assert 'success' in self.update_action( + temp_dir + '/assets$uri', temp_dir + '/assets/$host' + ) + + assert self.get_custom('/dir/file', 'dir') == 200 + + def test_static_chroot_slash(self, temp_dir): + assert 'success' in self.conf( + { + "share": temp_dir + "/assets$uri", + "chroot": temp_dir + "/assets/dir/", + }, + 'routes/0/action', + ), 'configure chroot slash end' + + assert self.get(url='/dir/file')['status'] == 200, 'slash end' + assert self.get(url='/dirxfile')['status'] == 403, 'slash end bad' + + assert 'success' in self.conf( + { + "share": temp_dir + "/assets$uri", + "chroot": temp_dir + "/assets/dir", + }, + 'routes/0/action', + ), 'configure chroot no slash end' + + assert self.get(url='/dir/file')['status'] == 200, 'no slash end' + + assert 'success' in self.conf( + { + "share": temp_dir + "/assets$uri", + "chroot": temp_dir + "/assets/dir/", + }, + 'routes/0/action', + ), 'configure chroot slash end 2' + + assert self.get(url='/dir/file')['status'] == 200, 'slash end 2' + assert self.get(url='/dirxfile')['status'] == 403, 'slash end 2 bad' + + assert 'success' in self.conf( + { + "share": temp_dir + "///assets/////$uri", + "chroot": temp_dir + "//assets////dir///", + }, + 'routes/0/action', + ), 'configure chroot multiple slashes' + + assert self.get(url='/dir/file')['status'] == 200, 'multiple slashes' + def test_static_chroot_invalid(self, temp_dir): assert 'error' in self.conf( {"share": temp_dir, "chroot": True}, 'routes/0/action', @@ -106,3 +216,10 @@ class TestStaticChroot(TestApplicationProto): assert 'error' in self.conf( {"share": temp_dir, "mount": "True"}, 'routes/0/action', ), 'configure mount error' + + assert 'error' in self.update_action( + temp_dir + '/assets$uri', temp_dir + '/assets/d$r$uri' + ) + assert 'error' in self.update_action( + temp_dir + '/assets$uri', temp_dir + '/assets/$$uri' + ) diff --git a/test/test_static_fallback.py b/test/test_static_fallback.py index dc9056b9..71b268c8 100644 --- a/test/test_static_fallback.py +++ b/test/test_static_fallback.py @@ -2,7 +2,6 @@ import os from pathlib import Path import pytest - from unit.applications.proto import TestApplicationProto @@ -23,7 +22,7 @@ class TestStaticFallback(TestApplicationProto): "*:7080": {"pass": "routes"}, "*:7081": {"pass": "routes"}, }, - "routes": [{"action": {"share": temp_dir + "/assets"}}], + "routes": [{"action": {"share": temp_dir + "/assets$uri"}}], "applications": {}, } ) @@ -50,7 +49,7 @@ class TestStaticFallback(TestApplicationProto): def test_static_fallback_valid_path(self, temp_dir): self.action_update( - {"share": temp_dir + "/assets", "fallback": {"return": 200}} + {"share": temp_dir + "/assets$uri", "fallback": {"return": 200}} ) resp = self.get() assert resp['status'] == 200, 'fallback status' @@ -83,7 +82,7 @@ class TestStaticFallback(TestApplicationProto): def test_static_fallback_share(self, temp_dir): self.action_update( - {"share": "/blah", "fallback": {"share": temp_dir + "/assets"},} + {"share": "/blah", "fallback": {"share": temp_dir + "/assets$uri"},} ) resp = self.get() diff --git a/test/test_static_mount.py b/test/test_static_mount.py index 570f6439..82eda956 100644 --- a/test/test_static_mount.py +++ b/test/test_static_mount.py @@ -3,7 +3,6 @@ import subprocess from pathlib import Path import pytest - from unit.applications.proto import TestApplicationProto @@ -23,7 +22,7 @@ class TestStaticMount(TestApplicationProto): Path(temp_dir + '/assets/mount/index.html').write_text('mount') try: - process = subprocess.Popen( + subprocess.check_output( [ "mount", "--bind", @@ -33,35 +32,33 @@ class TestStaticMount(TestApplicationProto): stderr=subprocess.STDOUT, ) - process.communicate() - except KeyboardInterrupt: raise - except: + except subprocess.CalledProcessError: pytest.fail('Can\'t run mount process.') self._load_conf( { "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": temp_dir + "/assets/dir"}}], + "routes": [ + {"action": {"share": temp_dir + "/assets/dir$uri"}} + ], } ) yield try: - process = subprocess.Popen( + subprocess.check_output( ["umount", "--lazy", temp_dir + "/assets/dir/mount"], stderr=subprocess.STDOUT, ) - process.communicate() - except KeyboardInterrupt: raise - except: + except subprocess.CalledProcessError: pytest.fail('Can\'t run umount process.') def test_static_mount(self, temp_dir, skip_alert): @@ -72,14 +69,14 @@ class TestStaticMount(TestApplicationProto): assert resp['body'] == 'mount' assert 'success' in self.conf( - {"share": temp_dir + "/assets/dir", "traverse_mounts": False}, + {"share": temp_dir + "/assets/dir$uri", "traverse_mounts": False}, 'routes/0/action', ), 'configure mount disable' assert self.get(url='/mount/')['status'] == 403 assert 'success' in self.conf( - {"share": temp_dir + "/assets/dir", "traverse_mounts": True}, + {"share": temp_dir + "/assets/dir$uri", "traverse_mounts": True}, 'routes/0/action', ), 'configure mount enable' @@ -97,14 +94,14 @@ class TestStaticMount(TestApplicationProto): { "match": {"method": "HEAD"}, "action": { - "share": temp_dir + "/assets/dir", + "share": temp_dir + "/assets/dir$uri", "traverse_mounts": False, }, }, { "match": {"method": "GET"}, "action": { - "share": temp_dir + "/assets/dir", + "share": temp_dir + "/assets/dir$uri", "traverse_mounts": True, }, }, @@ -120,7 +117,7 @@ class TestStaticMount(TestApplicationProto): assert 'success' in self.conf( { - "share": temp_dir + "/assets/dir", + "share": temp_dir + "/assets/dir$uri", "chroot": temp_dir + "/assets", }, 'routes/0/action', @@ -130,7 +127,7 @@ class TestStaticMount(TestApplicationProto): assert 'success' in self.conf( { - "share": temp_dir + "/assets/dir", + "share": temp_dir + "/assets/dir$uri", "chroot": temp_dir + "/assets", "traverse_mounts": False, }, diff --git a/test/test_static_share.py b/test/test_static_share.py new file mode 100644 index 00000000..5384866e --- /dev/null +++ b/test/test_static_share.py @@ -0,0 +1,72 @@ +import os +from pathlib import Path + +import pytest +from unit.applications.proto import TestApplicationProto + + +class TestStaticShare(TestApplicationProto): + prerequisites = {} + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, temp_dir): + os.makedirs(temp_dir + '/assets/dir') + os.makedirs(temp_dir + '/assets/dir2') + + Path(temp_dir + '/assets/dir/file').write_text('1') + Path(temp_dir + '/assets/dir2/file2').write_text('2') + + assert 'success' in self.conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [{"action": {"share": temp_dir + "/assets$uri"}}], + "applications": {}, + } + ) + + def action_update(self, conf): + assert 'success' in self.conf(conf, 'routes/0/action') + + def test_share_array(self, temp_dir): + assert self.get(url='/dir/file')['body'] == '1' + assert self.get(url='/dir2/file2')['body'] == '2' + + self.action_update({"share": [temp_dir + "/assets/dir$uri"]}) + + assert self.get(url='/file')['body'] == '1' + assert self.get(url='/file2')['status'] == 404 + + self.action_update( + { + "share": [ + temp_dir + "/assets/dir$uri", + temp_dir + "/assets/dir2$uri", + ] + } + ) + + assert self.get(url='/file')['body'] == '1' + assert self.get(url='/file2')['body'] == '2' + + self.action_update( + { + "share": [ + temp_dir + "/assets/dir2$uri", + temp_dir + "/assets/dir3$uri", + ] + } + ) + + assert self.get(url='/file')['status'] == 404 + assert self.get(url='/file2')['body'] == '2' + + def test_share_array_fallback(self): + self.action_update( + {"share": ["/blah", "/blah2"], "fallback": {"return": 201}} + ) + + assert self.get()['status'] == 201 + + def test_share_array_invalid(self): + assert 'error' in self.conf({"share": []}, 'routes/0/action') + assert 'error' in self.conf({"share": {}}, 'routes/0/action') diff --git a/test/test_static_symlink.py b/test/test_static_symlink.py index 35eb402a..24638e20 100644 --- a/test/test_static_symlink.py +++ b/test/test_static_symlink.py @@ -2,7 +2,6 @@ import os from pathlib import Path import pytest - from unit.applications.proto import TestApplicationProto @@ -18,7 +17,7 @@ class TestStaticSymlink(TestApplicationProto): self._load_conf( { "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": temp_dir + "/assets"}}], + "routes": [{"action": {"share": temp_dir + "/assets$uri"}}], } ) @@ -33,14 +32,14 @@ class TestStaticSymlink(TestApplicationProto): assert self.get(url='/link/file')['status'] == 200, 'symlink file' assert 'success' in self.conf( - {"share": temp_dir + "/assets", "follow_symlinks": False}, + {"share": temp_dir + "/assets$uri", "follow_symlinks": False}, 'routes/0/action', ), 'configure symlink disable' assert self.get(url='/link/file')['status'] == 403, 'symlink disabled' assert 'success' in self.conf( - {"share": temp_dir + "/assets", "follow_symlinks": True}, + {"share": temp_dir + "/assets$uri", "follow_symlinks": True}, 'routes/0/action', ), 'configure symlink enable' @@ -56,14 +55,14 @@ class TestStaticSymlink(TestApplicationProto): { "match": {"method": "HEAD"}, "action": { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "follow_symlinks": False, }, }, { "match": {"method": "GET"}, "action": { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "follow_symlinks": True, }, }, @@ -85,7 +84,7 @@ class TestStaticSymlink(TestApplicationProto): assert 'success' in self.conf( { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "chroot": temp_dir + "/assets/dir/dir", }, 'routes/0/action', diff --git a/test/test_static_types.py b/test/test_static_types.py index 20defddf..18564a21 100644 --- a/test/test_static_types.py +++ b/test/test_static_types.py @@ -1,7 +1,6 @@ from pathlib import Path import pytest - from unit.applications.proto import TestApplicationProto @@ -22,7 +21,7 @@ class TestStaticTypes(TestApplicationProto): "*:7080": {"pass": "routes"}, "*:7081": {"pass": "routes"}, }, - "routes": [{"action": {"share": temp_dir + "/assets"}}], + "routes": [{"action": {"share": temp_dir + "/assets$uri"}}], "applications": {}, } ) @@ -36,39 +35,39 @@ class TestStaticTypes(TestApplicationProto): assert resp['body'] == body, 'body' def test_static_types_basic(self, temp_dir): - self.action_update({"share": temp_dir + "/assets"}) + self.action_update({"share": temp_dir + "/assets$uri"}) self.check_body('/index.html', 'index') self.check_body('/file.xml', '.xml') self.action_update( - {"share": temp_dir + "/assets", "types": "application/xml"} + {"share": temp_dir + "/assets$uri", "types": "application/xml"} ) self.check_body('/file.xml', '.xml') self.action_update( - {"share": temp_dir + "/assets", "types": ["application/xml"]} + {"share": temp_dir + "/assets$uri", "types": ["application/xml"]} ) self.check_body('/file.xml', '.xml') - self.action_update({"share": temp_dir + "/assets", "types": [""]}) + self.action_update({"share": temp_dir + "/assets$uri", "types": [""]}) assert self.get(url='/file.xml')['status'] == 403, 'no mtype' def test_static_types_wildcard(self, temp_dir): self.action_update( - {"share": temp_dir + "/assets", "types": ["application/*"]} + {"share": temp_dir + "/assets$uri", "types": ["application/*"]} ) self.check_body('/file.xml', '.xml') assert self.get(url='/file.mp4')['status'] == 403, 'app * mtype mp4' self.action_update( - {"share": temp_dir + "/assets", "types": ["video/*"]} + {"share": temp_dir + "/assets$uri", "types": ["video/*"]} ) assert self.get(url='/file.xml')['status'] == 403, 'video * mtype xml' self.check_body('/file.mp4', '.mp4') def test_static_types_negation(self, temp_dir): self.action_update( - {"share": temp_dir + "/assets", "types": ["!application/xml"]} + {"share": temp_dir + "/assets$uri", "types": ["!application/xml"]} ) assert self.get(url='/file.xml')['status'] == 403, 'forbidden negation' self.check_body('/file.mp4', '.mp4') @@ -76,7 +75,7 @@ class TestStaticTypes(TestApplicationProto): # sorting negation self.action_update( { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "types": ["!video/*", "image/png", "!image/jpg"], } ) @@ -86,7 +85,7 @@ class TestStaticTypes(TestApplicationProto): def test_static_types_regex(self, temp_dir): self.action_update( - {"share": temp_dir + "/assets", "types": ["~text/(html|plain)"]} + {"share": temp_dir + "/assets$uri", "types": ["~text/(html|plain)"]} ) assert self.get(url='/file.php')['status'] == 403, 'regex fail' self.check_body('/file.html', '.html') @@ -94,7 +93,7 @@ class TestStaticTypes(TestApplicationProto): def test_static_types_case(self, temp_dir): self.action_update( - {"share": temp_dir + "/assets", "types": ["!APpliCaTiOn/xMl"]} + {"share": temp_dir + "/assets$uri", "types": ["!APpliCaTiOn/xMl"]} ) self.check_body('/file.mp4', '.mp4') assert ( @@ -102,7 +101,7 @@ class TestStaticTypes(TestApplicationProto): ), 'mixed case xml negation' self.action_update( - {"share": temp_dir + "/assets", "types": ["vIdEo/mp4"]} + {"share": temp_dir + "/assets$uri", "types": ["vIdEo/mp4"]} ) assert self.get(url='/file.mp4')['status'] == 200, 'mixed case' assert ( @@ -110,7 +109,7 @@ class TestStaticTypes(TestApplicationProto): ), 'mixed case video negation' self.action_update( - {"share": temp_dir + "/assets", "types": ["vIdEo/*"]} + {"share": temp_dir + "/assets$uri", "types": ["vIdEo/*"]} ) self.check_body('/file.mp4', '.mp4') assert ( @@ -126,7 +125,7 @@ class TestStaticTypes(TestApplicationProto): }, { "action": { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "types": ["!application/x-httpd-php"], "fallback": {"proxy": "http://127.0.0.1:7081"}, } @@ -140,17 +139,18 @@ class TestStaticTypes(TestApplicationProto): def test_static_types_index(self, temp_dir): self.action_update( - {"share": temp_dir + "/assets", "types": "application/xml"} + {"share": temp_dir + "/assets$uri", "types": "application/xml"} ) self.check_body('/', 'index') self.check_body('/file.xml', '.xml') + assert self.get(url='/index.html')['status'] == 403, 'forbidden mtype' assert self.get(url='/file.mp4')['status'] == 403, 'forbidden mtype' def test_static_types_custom_mime(self, temp_dir): self._load_conf( { "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": temp_dir + "/assets"}}], + "routes": [{"action": {"share": temp_dir + "/assets$uri"}}], "applications": {}, "settings": { "http": { @@ -160,10 +160,10 @@ class TestStaticTypes(TestApplicationProto): } ) - self.action_update({"share": temp_dir + "/assets", "types": [""]}) + self.action_update({"share": temp_dir + "/assets$uri", "types": [""]}) assert self.get(url='/file')['status'] == 403, 'forbidden custom mime' self.action_update( - {"share": temp_dir + "/assets", "types": ["test/mime-type"]} + {"share": temp_dir + "/assets$uri", "types": ["test/mime-type"]} ) self.check_body('/file', '') diff --git a/test/test_static_variables.py b/test/test_static_variables.py new file mode 100644 index 00000000..e7e1629c --- /dev/null +++ b/test/test_static_variables.py @@ -0,0 +1,79 @@ +import os +from pathlib import Path + +import pytest +from unit.applications.proto import TestApplicationProto + + +class TestStaticVariables(TestApplicationProto): + prerequisites = {} + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, temp_dir): + os.makedirs(temp_dir + '/assets/dir') + os.makedirs(temp_dir + '/assets/d$r') + Path(temp_dir + '/assets/index.html').write_text('0123456789') + Path(temp_dir + '/assets/dir/file').write_text('file') + Path(temp_dir + '/assets/d$r/file').write_text('d$r') + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [{"action": {"share": temp_dir + "/assets$uri"}}], + } + ) + + def update_share(self, share): + if isinstance(share, list): + return self.conf(share, 'routes/0/action/share') + + return self.conf('"' + share + '"', 'routes/0/action/share') + + def test_static_variables(self, temp_dir): + assert self.get(url='/index.html')['status'] == 200 + assert self.get(url='/d$r/file')['status'] == 200 + + assert 'success' in self.update_share('$uri') + assert self.get(url=temp_dir + '/assets/index.html')['status'] == 200 + + assert 'success' in self.update_share(temp_dir + '/assets${uri}') + assert self.get(url='/index.html')['status'] == 200 + + def test_static_variables_array(self, temp_dir): + assert 'success' in self.update_share( + [temp_dir + '/assets$uri', '$uri'] + ) + + assert self.get(url='/dir/file')['status'] == 200 + assert self.get(url=temp_dir + '/assets/index.html')['status'] == 200 + assert self.get(url='/blah')['status'] == 404 + + assert 'success' in self.conf( + { + "share": [temp_dir + '/assets$uri', '$uri'], + "fallback": {"return": 201}, + }, + 'routes/0/action', + ) + + assert self.get(url='/dir/file')['status'] == 200 + assert self.get(url=temp_dir + '/assets/index.html')['status'] == 200 + assert self.get(url='/dir/blah')['status'] == 201 + + def test_static_variables_buildin_start(self, temp_dir): + assert 'success' in self.update_share('$uri/assets/index.html') + assert self.get(url=temp_dir)['status'] == 200 + + def test_static_variables_buildin_mid(self, temp_dir): + assert 'success' in self.update_share(temp_dir + '$uri/index.html') + assert self.get(url='/assets')['status'] == 200 + + def test_static_variables_buildin_end(self): + assert self.get(url='/index.html')['status'] == 200 + + def test_static_variables_invalid(self, temp_dir): + assert 'error' in self.update_share(temp_dir + '/assets/d$r$uri') + assert 'error' in self.update_share(temp_dir + '/assets/$$uri') + assert 'error' in self.update_share( + [temp_dir + '/assets$uri', temp_dir + '/assets/dir', '$$uri'] + ) diff --git a/test/test_tls.py b/test/test_tls.py index 546f0f89..01336765 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -5,7 +5,6 @@ import subprocess import time import pytest - from unit.applications.tls import TestApplicationTLS from unit.option import option @@ -33,7 +32,7 @@ class TestTLS(TestApplicationTLS): def req(self, name='localhost', subject=None, x509=False): subj = subject if subject is not None else '/CN=' + name + '/' - subprocess.call( + subprocess.check_output( [ 'openssl', 'req', @@ -88,7 +87,7 @@ basicConstraints = critical,CA:TRUE""" f.write('') def ca(self, cert='root', out='localhost'): - subprocess.call( + subprocess.check_output( [ 'openssl', 'ca', @@ -221,7 +220,7 @@ basicConstraints = critical,CA:TRUE""" self.openssl_conf() - subprocess.call( + subprocess.check_output( [ 'openssl', 'ecparam', @@ -235,7 +234,7 @@ basicConstraints = critical,CA:TRUE""" stderr=subprocess.STDOUT, ) - subprocess.call( + subprocess.check_output( [ 'openssl', 'req', @@ -590,9 +589,9 @@ basicConstraints = critical,CA:TRUE""" app_id = self.findall(r'(\d+)#\d+ "mirror" application started')[0] - subprocess.call(['kill', '-9', app_id]) + subprocess.check_output(['kill', '-9', app_id]) - skip_alert(r'process %s exited on signal 9' % app_id) + skip_alert(r'process .* %s.* exited on signal 9' % app_id) self.wait_for_record( re.compile( @@ -677,4 +676,3 @@ basicConstraints = critical,CA:TRUE""" assert self.get_ssl()['status'] == 200, 'listener #1' assert self.get_ssl(port=7081)['status'] == 200, 'listener #2' - diff --git a/test/test_tls_conf_command.py b/test/test_tls_conf_command.py index ccae09ad..b414b5a0 100644 --- a/test/test_tls_conf_command.py +++ b/test/test_tls_conf_command.py @@ -1,7 +1,6 @@ import ssl import pytest - from unit.applications.tls import TestApplicationTLS diff --git a/test/test_tls_session.py b/test/test_tls_session.py new file mode 100644 index 00000000..58f11f2d --- /dev/null +++ b/test/test_tls_session.py @@ -0,0 +1,126 @@ +import socket +import time + +import pytest + +pytest.importorskip('OpenSSL.SSL') +from OpenSSL.SSL import ( + TLSv1_2_METHOD, + SESS_CACHE_CLIENT, + OP_NO_TICKET, + Context, + Connection, + _lib, +) +from unit.applications.tls import TestApplicationTLS + + +class TestTLSSession(TestApplicationTLS): + prerequisites = {'modules': {'openssl': 'any'}} + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, request): + self.certificate() + + assert 'success' in self.conf( + { + "listeners": { + "*:7080": { + "pass": "routes", + "tls": {"certificate": "default", "session": {}}, + } + }, + "routes": [{"action": {"return": 200}}], + "applications": {}, + } + ), 'load application configuration' + + def add_session(self, cache_size=None, timeout=None): + session = {} + + if cache_size is not None: + session['cache_size'] = cache_size + if timeout is not None: + session['timeout'] = timeout + + return self.conf(session, 'listeners/*:7080/tls/session') + + def connect(self, ctx=None, session=None): + sock = socket.create_connection(('127.0.0.1', 7080)) + + if ctx is None: + ctx = Context(TLSv1_2_METHOD) + ctx.set_session_cache_mode(SESS_CACHE_CLIENT) + ctx.set_options(OP_NO_TICKET) + + client = Connection(ctx, sock) + client.set_connect_state() + + if session is not None: + client.set_session(session) + + client.do_handshake() + client.shutdown() + + return ( + client, + client.get_session(), + ctx, + _lib.SSL_session_reused(client._ssl), + ) + + def test_tls_session(self): + client, sess, ctx, reused = self.connect() + assert not reused, 'new connection' + + client, _, _, reused = self.connect(ctx, sess) + assert not reused, 'no cache' + + assert 'success' in self.add_session(cache_size=2) + + client, sess, ctx, reused = self.connect() + assert not reused, 'new connection cache' + + client, _, _, reused = self.connect(ctx, sess) + assert reused, 'cache' + + client, _, _, reused = self.connect(ctx, sess) + assert reused, 'cache 2' + + # check that at least one session of four is not reused + + clients = [self.connect() for _ in range(4)] + assert True not in [c[-1] for c in clients], 'cache small all new' + + clients_again = [self.connect(c[2], c[1]) for c in clients] + assert False in [c[-1] for c in clients_again], 'cache small no reuse' + + # all four sessions are reused + + assert 'success' in self.add_session(cache_size=8) + + clients = [self.connect() for _ in range(4)] + assert True not in [c[-1] for c in clients], 'cache big all new' + + clients_again = [self.connect(c[2], c[1]) for c in clients] + assert False not in [c[-1] for c in clients_again], 'cache big reuse' + + def test_tls_session_timeout(self): + assert 'success' in self.add_session(cache_size=5, timeout=1) + + client, sess, ctx, reused = self.connect() + assert not reused, 'new connection' + + client, _, _, reused = self.connect(ctx, sess) + assert reused, 'no timeout' + + time.sleep(3) + + client, _, _, reused = self.connect(ctx, sess) + assert not reused, 'timeout' + + def test_tls_session_invalid(self): + assert 'error' in self.add_session(cache_size=-1) + assert 'error' in self.add_session(cache_size={}) + assert 'error' in self.add_session(timeout=-1) + assert 'error' in self.add_session(timeout={}) diff --git a/test/test_tls_sni.py b/test/test_tls_sni.py index eba6140a..d5f205cf 100644 --- a/test/test_tls_sni.py +++ b/test/test_tls_sni.py @@ -1,8 +1,6 @@ import ssl import subprocess -import pytest - from unit.applications.tls import TestApplicationTLS from unit.option import option @@ -76,7 +74,7 @@ basicConstraints = critical,CA:TRUE""" else '/' ) - subprocess.call( + subprocess.check_output( [ 'openssl', 'req', @@ -102,7 +100,7 @@ basicConstraints = critical,CA:TRUE""" else '/' ) - subprocess.call( + subprocess.check_output( [ 'openssl', 'ca', diff --git a/test/test_tls_tickets.py b/test/test_tls_tickets.py new file mode 100644 index 00000000..6899eaa1 --- /dev/null +++ b/test/test_tls_tickets.py @@ -0,0 +1,196 @@ +import socket + +import pytest + +pytest.importorskip('OpenSSL.SSL') +from OpenSSL.SSL import ( + TLSv1_2_METHOD, + Context, + Connection, + Session, + _lib, +) +from unit.applications.tls import TestApplicationTLS + + +class TestTLSTicket(TestApplicationTLS): + prerequisites = {'modules': {'openssl': 'any'}} + + ticket = 'U1oDTh11mMxODuw12gS0EXX1E/PkZG13cJNQ6m5+6BGlfPTjNlIEw7PSVU3X1gTE' + ticket2 = ( + '5AV0DSYIYbZWZQB7fCnTHZmMxtotb/aXjam+n2XS79lTvX3Tq9xGqpC8XKNEF2lt' + ) + ticket80 = '6Pfil8lv/k8zf8MndPpfXaO5EAV6dhME6zs6CfUyq2yziynQwSywtKQMqHGnJ2HR\ +49TZXi/Y4/8RSIO7QPsU51/HLR1gWIMhVM2m9yh93Bw=' + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, request): + self.certificate() + + listener_conf = { + "pass": "routes", + "tls": { + "certificate": "default", + "session": {"cache_size": 0, "tickets": True}, + }, + } + + assert 'success' in self.conf( + { + "listeners": { + "*:7080": listener_conf, + "*:7081": listener_conf, + "*:7082": listener_conf, + }, + "routes": [{"action": {"return": 200}}], + "applications": {}, + } + ), 'load application configuration' + + def set_tickets(self, tickets=True, port=7080): + assert 'success' in self.conf( + {"cache_size": 0, "tickets": tickets}, + 'listeners/*:' + str(port) + '/tls/session', + ) + + def connect(self, ctx=None, session=None, port=7080): + sock = socket.create_connection(('127.0.0.1', port)) + + if ctx is None: + ctx = Context(TLSv1_2_METHOD) + + client = Connection(ctx, sock) + client.set_connect_state() + + if session is not None: + client.set_session(session) + + client.do_handshake() + client.shutdown() + + return ( + client.get_session(), + ctx, + _lib.SSL_session_reused(client._ssl), + ) + + def has_ticket(self, sess): + return _lib.SSL_SESSION_has_ticket(sess._session) + + @pytest.mark.skipif( + not hasattr(_lib, 'SSL_SESSION_has_ticket'), + reason='ticket check is not supported', + ) + def test_tls_ticket(self): + sess, ctx, reused = self.connect() + assert self.has_ticket(sess), 'tickets True' + assert not reused, 'tickets True not reused' + + sess, ctx, reused = self.connect(ctx, sess) + assert self.has_ticket(sess), 'tickets True reconnect' + assert reused, 'tickets True reused' + + self.set_tickets(tickets=False) + + sess, _, _ = self.connect() + assert not self.has_ticket(sess), 'tickets False' + + assert 'success' in self.conf_delete( + 'listeners/*:7080/tls/session/tickets' + ), 'tickets default configure' + + sess, _, _ = self.connect() + assert not self.has_ticket(sess), 'tickets default (false)' + + @pytest.mark.skipif( + not hasattr(_lib, 'SSL_SESSION_has_ticket'), + reason='ticket check is not supported', + ) + def test_tls_ticket_string(self): + self.set_tickets(self.ticket) + sess, ctx, _ = self.connect() + assert self.has_ticket(sess), 'tickets string' + + sess2, _, reused = self.connect(ctx, sess) + assert self.has_ticket(sess2), 'tickets string reconnect' + assert reused, 'tickets string reused' + + sess2, _, reused = self.connect(ctx, sess, port=7081) + assert self.has_ticket(sess2), 'connect True' + assert not reused, 'connect True not reused' + + self.set_tickets(self.ticket2, port=7081) + + sess2, _, reused = self.connect(ctx, sess, port=7081) + assert self.has_ticket(sess2), 'wrong ticket' + assert not reused, 'wrong ticket not reused' + + self.set_tickets(self.ticket80) + + sess, ctx, _ = self.connect() + assert self.has_ticket(sess), 'tickets string 80' + + sess2, _, reused = self.connect(ctx, sess) + assert self.has_ticket(sess2), 'tickets string 80 reconnect' + assert reused, 'tickets string 80 reused' + + sess2, _, reused = self.connect(ctx, sess, port=7081) + assert self.has_ticket(sess2), 'wrong ticket 80' + assert not reused, 'wrong ticket 80 not reused' + + @pytest.mark.skipif( + not hasattr(_lib, 'SSL_SESSION_has_ticket'), + reason='ticket check is not supported', + ) + def test_tls_ticket_array(self): + self.set_tickets([]) + + sess, ctx, _ = self.connect() + assert not self.has_ticket(sess), 'tickets array empty' + + self.set_tickets([self.ticket, self.ticket2]) + self.set_tickets(self.ticket, port=7081) + self.set_tickets(self.ticket2, port=7082) + + sess, ctx, _ = self.connect() + _, _, reused = self.connect(ctx, sess, port=7081) + assert not reused, 'not last ticket' + _, _, reused = self.connect(ctx, sess, port=7082) + assert reused, 'last ticket' + + sess, ctx, _ = self.connect(port=7081) + _, _, reused = self.connect(ctx, sess) + assert reused, 'first ticket' + + sess, ctx, _ = self.connect(port=7082) + _, _, reused = self.connect(ctx, sess) + assert reused, 'second ticket' + + assert 'success' in self.conf_delete( + 'listeners/*:7080/tls/session/tickets/0' + ), 'removed first ticket' + assert 'success' in self.conf_post( + '"' + self.ticket + '"', 'listeners/*:7080/tls/session/tickets' + ), 'add new ticket to the end of array' + + sess, ctx, _ = self.connect() + _, _, reused = self.connect(ctx, sess, port=7082) + assert not reused, 'not last ticket 2' + _, _, reused = self.connect(ctx, sess, port=7081) + assert reused, 'last ticket 2' + + def test_tls_ticket_invalid(self): + def check_tickets(tickets): + assert 'error' in self.conf( + {"tickets": tickets}, 'listeners/*:7080/tls/session', + ) + + check_tickets({}) + check_tickets('!?&^' * 16) + check_tickets(self.ticket[:-2] + '!' + self.ticket[3:]) + check_tickets(self.ticket[:-1]) + check_tickets(self.ticket + 'b') + check_tickets(self.ticket + 'blah') + check_tickets([True, self.ticket, self.ticket2]) + check_tickets([self.ticket, 'blah', self.ticket2]) + check_tickets([self.ticket, self.ticket2, []]) diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py index 6be1667b..367059e6 100644 --- a/test/unit/applications/lang/go.py +++ b/test/unit/applications/lang/go.py @@ -40,13 +40,12 @@ class TestApplicationGo(TestApplicationProto): print("\n$ GOPATH=" + env['GOPATH'] + " " + " ".join(args)) try: - process = subprocess.Popen(args, env=env) - process.communicate() + process = subprocess.run(args, env=env) except KeyboardInterrupt: raise - except: + except subprocess.CalledProcessError: return None return process diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index 53b27b07..50998978 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -64,10 +64,17 @@ class TestApplicationJava(TestApplicationProto): javac = [ 'javac', - '-target', '8', '-source', '8', '-nowarn', - '-encoding', 'utf-8', - '-d', classes_path, - '-classpath', classpath + ':' + ws_jars[0], + '-target', + '8', + '-source', + '8', + '-nowarn', + '-encoding', + 'utf-8', + '-d', + classes_path, + '-classpath', + classpath + ':' + ws_jars[0], ] javac.extend(src) @@ -75,13 +82,12 @@ class TestApplicationJava(TestApplicationProto): print("\n$ " + " ".join(javac)) try: - process = subprocess.Popen(javac, stderr=subprocess.STDOUT) - process.communicate() + subprocess.check_output(javac, stderr=subprocess.STDOUT) except KeyboardInterrupt: raise - except: + except subprocess.CalledProcessError: pytest.fail('Can\'t run javac process.') def load(self, script, **kwargs): diff --git a/test/unit/applications/lang/php.py b/test/unit/applications/lang/php.py index 90c0078c..5319d2ca 100644 --- a/test/unit/applications/lang/php.py +++ b/test/unit/applications/lang/php.py @@ -22,18 +22,27 @@ class TestApplicationPHP(TestApplicationProto): script_path = '/app/php/' + script + app = { + "type": self.get_application_type(), + "processes": kwargs.pop('processes', {"spare": 0}), + "root": script_path, + "working_directory": script_path, + "index": index, + } + + for attr in ( + 'environment', + 'limits', + 'options', + 'targets', + ): + if attr in kwargs: + app[attr] = kwargs.pop(attr) + self._load_conf( { "listeners": {"*:7080": {"pass": "applications/" + script}}, - "applications": { - script: { - "type": self.get_application_type(), - "processes": {"spare": 0}, - "root": script_path, - "working_directory": script_path, - "index": index, - } - }, + "applications": {script: app}, }, **kwargs ) diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py index 215aa332..1e38f3fa 100644 --- a/test/unit/applications/lang/python.py +++ b/test/unit/applications/lang/python.py @@ -2,7 +2,6 @@ import os import shutil from urllib.parse import quote -import pytest from unit.applications.proto import TestApplicationProto from unit.option import option diff --git a/test/unit/applications/lang/ruby.py b/test/unit/applications/lang/ruby.py index 61d50558..824bfe7f 100644 --- a/test/unit/applications/lang/ruby.py +++ b/test/unit/applications/lang/ruby.py @@ -1,4 +1,3 @@ -import os import shutil from unit.applications.proto import TestApplicationProto diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py index e30d21ff..cd8672ba 100644 --- a/test/unit/applications/proto.py +++ b/test/unit/applications/proto.py @@ -3,8 +3,8 @@ import re import time from unit.control import TestControl -from unit.option import option from unit.log import Log +from unit.option import option class TestApplicationProto(TestControl): diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index 583b618f..c7254235 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -15,7 +15,7 @@ class TestApplicationTLS(TestApplicationProto): def certificate(self, name='default', load=True): self.openssl_conf() - subprocess.call( + subprocess.check_output( [ 'openssl', 'req', diff --git a/test/unit/check/go.py b/test/unit/check/go.py index 309091c0..cc17f0fe 100644 --- a/test/unit/check/go.py +++ b/test/unit/check/go.py @@ -11,7 +11,7 @@ def check_go(current_dir, temp_dir, test_dir): env['GO111MODULE'] = 'auto' try: - process = subprocess.Popen( + process = subprocess.run( [ 'go', 'build', @@ -20,8 +20,9 @@ def check_go(current_dir, temp_dir, test_dir): test_dir + '/go/empty/app.go', ], env=env, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, ) - process.communicate() if process.returncode == 0: return True @@ -29,5 +30,5 @@ def check_go(current_dir, temp_dir, test_dir): except KeyboardInterrupt: raise - except: + except subprocess.CalledProcessError: return None diff --git a/test/unit/check/isolation.py b/test/unit/check/isolation.py index 43c8842f..9bd835a3 100644 --- a/test/unit/check/isolation.py +++ b/test/unit/check/isolation.py @@ -3,9 +3,8 @@ import os from unit.applications.lang.go import TestApplicationGo from unit.applications.lang.java import TestApplicationJava -from unit.applications.lang.ruby import TestApplicationRuby from unit.applications.lang.node import TestApplicationNode -from unit.applications.proto import TestApplicationProto +from unit.applications.lang.ruby import TestApplicationRuby from unit.http import TestHTTP from unit.option import option from unit.utils import getns |