diff options
author | Andrei Zeliankou <zelenkov@nginx.com> | 2023-06-12 14:16:59 +0100 |
---|---|---|
committer | Andrei Zeliankou <zelenkov@nginx.com> | 2023-06-12 14:16:59 +0100 |
commit | ce2405ec3dd97e8bdf8f63312e3c6ce59ef562d4 (patch) | |
tree | 818e60eb10d7f2be90f25003b3a2b347314e966f | |
parent | a3b9b49cfb091410ca8f3c8d9df24d1fe184f8e0 (diff) | |
download | unit-ce2405ec3dd97e8bdf8f63312e3c6ce59ef562d4.tar.gz unit-ce2405ec3dd97e8bdf8f63312e3c6ce59ef562d4.tar.bz2 |
Tests: prerequisites checking reworked.
Prerequisites check moved to the module level to simplify class structure.
Discovery and prerequisites checks functions moved to the separate files.
Introduced "require" fixture to provide per-test requirements check.
79 files changed, 524 insertions, 548 deletions
diff --git a/test/conftest.py b/test/conftest.py index 84902dc2..4487f059 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -13,14 +13,8 @@ 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 -from unit.check.njs import check_njs -from unit.check.node import check_node -from unit.check.regex import check_regex -from unit.check.tls import check_openssl -from unit.check.unix_abstract import check_unix_abstract +from unit.check.discover_available import discover_available +from unit.check.check_prerequisites import check_prerequisites from unit.http import TestHTTP from unit.log import Log from unit.log import print_log_on_assert @@ -130,6 +124,9 @@ def pytest_generate_tests(metafunc): type = cls.application_type def generate_tests(versions): + if not versions: + pytest.skip('no available module versions') + metafunc.fixturenames.append('tmp_ct') metafunc.parametrize('tmp_ct', versions) @@ -140,75 +137,43 @@ def pytest_generate_tests(metafunc): # take available module from option and generate tests for each version - for module, prereq_version in cls.prerequisites['modules'].items(): - if module in option.available['modules']: - available_versions = option.available['modules'][module] + available_modules = option.available['modules'] + + for module, version in metafunc.module.prerequisites['modules'].items(): + if module in available_modules and available_modules[module]: + available_versions = available_modules[module] - if prereq_version == 'all': + if version == 'all': generate_tests(available_versions) - elif prereq_version == 'any': + elif version == 'any': option.generated_tests[ metafunc.function.__name__ ] = f'{type} {available_versions[0]}' - elif callable(prereq_version): - generate_tests(list(filter(prereq_version, available_versions))) + elif callable(version): + generate_tests(list(filter(version, available_versions))) else: raise ValueError( f''' -Unexpected prerequisite version "{prereq_version}" for module "{module}" in -{cls}. 'all', 'any' or callable expected.''' +Unexpected prerequisite version "{version}" for module "{module}". +'all', 'any' or callable expected.''' ) def pytest_sessionstart(): - option.available = {'modules': {}, 'features': {}} - unit = unit_run() - output_version = subprocess.check_output( - [unit['unitd'], '--version'], stderr=subprocess.STDOUT - ).decode() - - if not _wait_for_record(r'controller started'): - Log.print_log() - exit("Unit is writing log too long") - - # discover available modules from unit.log - - for module in re.findall( - r'module: ([a-zA-Z]+) (.*) ".*"$', Log.read(), re.M - ): - versions = option.available['modules'].setdefault(module[0], []) - if module[1] not in versions: - versions.append(module[1]) - - # discover modules from check - option.available['modules']['go'] = check_go() - option.available['modules']['njs'] = check_njs(output_version) - option.available['modules']['node'] = check_node(option.current_dir) - option.available['modules']['openssl'] = check_openssl(output_version) - option.available['modules']['regex'] = check_regex(output_version) + discover_available(unit) - # remove None values - - option.available['modules'] = { - k: v for k, v in option.available['modules'].items() if v is not None - } - - check_chroot() - check_isolation() - check_unix_abstract() - - _clear_conf(f'{unit["temp_dir"]}/control.unit.sock') + _clear_conf() unit_stop() Log.check_alerts() if option.restart: - shutil.rmtree(unit_instance['temp_dir']) + shutil.rmtree(unit['temp_dir']) else: _clear_temp_dir() @@ -225,38 +190,10 @@ def pytest_runtest_makereport(item): setattr(item, f'rep_{rep.when}', rep) -@pytest.fixture(scope='class', autouse=True) -def check_prerequisites(request): - cls = request.cls - missed = [] - - # check modules - - if 'modules' in cls.prerequisites: - available_modules = list(option.available['modules'].keys()) - - for module in cls.prerequisites['modules']: - if module in available_modules: - continue - - missed.append(module) - - if missed: - pytest.skip(f'Unit has no {", ".join(missed)} module(s)') - - # check features - - if 'features' in cls.prerequisites: - available_features = list(option.available['features'].keys()) - - for feature in cls.prerequisites['features']: - if feature in available_features: - continue - - missed.append(feature) - - if missed: - pytest.skip(f'{", ".join(missed)} feature(s) not supported') +@pytest.fixture(scope='module', autouse=True) +def check_prerequisites_module(request): + if hasattr(request.module, 'prerequisites'): + check_prerequisites(request.module.prerequisites) @pytest.fixture(autouse=True) @@ -283,7 +220,7 @@ def run(request): # prepare log - with Log.open(encoding='utf-8') as f: + with Log.open() as f: log = f.read() Log.set_pos(f.tell()) @@ -294,7 +231,7 @@ def run(request): # clean temp_dir before the next test if not option.restart: - _clear_conf(f'{unit["temp_dir"]}/control.unit.sock', log=log) + _clear_conf(log=log) _clear_temp_dir() # check descriptors @@ -384,7 +321,7 @@ def unit_run(state_dir=None): unit_instance['pid'] = f.read().rstrip() if state_dir is None: - _clear_conf(control_sock) + _clear_conf() _fds_info['main']['fds'] = _count_fds(unit_instance['pid']) @@ -440,7 +377,9 @@ def unit_stop(): @print_log_on_assert -def _clear_conf(sock, *, log=None): +def _clear_conf(*, log=None): + sock = unit_instance['control_sock'] + resp = http.put( url='/config', sock_type='unix', @@ -456,7 +395,10 @@ def _clear_conf(sock, *, log=None): def delete(url): return http.delete(url=url, sock_type='unix', addr=sock)['body'] - if 'openssl' in option.available['modules']: + if ( + 'openssl' in option.available['modules'] + and option.available['modules']['openssl'] + ): try: certs = json.loads(get('/certificates')).keys() @@ -466,7 +408,10 @@ def _clear_conf(sock, *, log=None): for cert in certs: assert 'success' in delete(f'/certificates/{cert}'), 'delete cert' - if 'njs' in option.available['modules']: + if ( + 'njs' in option.available['modules'] + and option.available['modules']['njs'] + ): try: scripts = json.loads(get('/js_modules')).keys() @@ -621,19 +566,6 @@ def _count_fds(pid): return 0 -def _wait_for_record(pattern, name='unit.log', wait=150, flags=re.M): - with Log.open(name) as file: - for _ in range(wait): - found = re.search(pattern, file.read(), flags) - - if found is not None: - break - - time.sleep(0.1) - - return found - - def run_process(target, *args): global _processes @@ -696,8 +628,8 @@ def date_to_sec_epoch(): @pytest.fixture def findall(): - def _findall(pattern, name='unit.log', flags=re.M): - return re.findall(pattern, Log.read(name), flags) + def _findall(*args, **kwargs): + return Log.findall(*args, **kwargs) return _findall @@ -713,6 +645,11 @@ def is_unsafe(request): @pytest.fixture +def require(): + return check_prerequisites + + +@pytest.fixture def search_in_file(): def _search_in_file(pattern, name='unit.log', flags=re.M): return re.search(pattern, Log.read(name), flags) @@ -760,4 +697,7 @@ def unit_pid(): @pytest.fixture def wait_for_record(): + def _wait_for_record(*args, **kwargs): + return Log.wait_for_record(*args, **kwargs) + return _wait_for_record diff --git a/test/test_access_log.py b/test/test_access_log.py index dd20d828..184a5fdf 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -4,10 +4,10 @@ import pytest from unit.applications.lang.python import TestApplicationPython from unit.option import option +prerequisites = {'modules': {'python': 'any'}} -class TestAccessLog(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestAccessLog(TestApplicationPython): def load(self, script): super().load(script) diff --git a/test/test_asgi_application.py b/test/test_asgi_application.py index e2f74dd9..1f98b170 100644 --- a/test/test_asgi_application.py +++ b/test/test_asgi_application.py @@ -5,13 +5,12 @@ import pytest from packaging import version from unit.applications.lang.python import TestApplicationPython +prerequisites = { + 'modules': {'python': lambda v: version.parse(v) >= version.parse('3.5')} +} + class TestASGIApplication(TestApplicationPython): - prerequisites = { - 'modules': { - 'python': lambda v: version.parse(v) >= version.parse('3.5') - } - } load_module = 'asgi' def test_asgi_application_variables(self, date_to_sec_epoch, sec_epoch): diff --git a/test/test_asgi_application_unix_abstract.py b/test/test_asgi_application_unix_abstract.py index 2ca7839f..e473154a 100644 --- a/test/test_asgi_application_unix_abstract.py +++ b/test/test_asgi_application_unix_abstract.py @@ -1,14 +1,13 @@ from packaging import version from unit.applications.lang.python import TestApplicationPython +prerequisites = { + 'modules': {'python': lambda v: version.parse(v) >= version.parse('3.5')}, + 'features': {'unix_abstract': True}, +} + class TestASGIApplicationUnixAbstract(TestApplicationPython): - prerequisites = { - 'modules': { - 'python': lambda v: version.parse(v) >= version.parse('3.5') - }, - 'features': ['unix_abstract'], - } load_module = 'asgi' def test_asgi_application_unix_abstract(self): diff --git a/test/test_asgi_lifespan.py b/test/test_asgi_lifespan.py index d3ac1721..0d0cb162 100644 --- a/test/test_asgi_lifespan.py +++ b/test/test_asgi_lifespan.py @@ -5,13 +5,12 @@ from packaging import version from unit.applications.lang.python import TestApplicationPython from unit.option import option +prerequisites = { + 'modules': {'python': lambda v: version.parse(v) >= version.parse('3.5')} +} + class TestASGILifespan(TestApplicationPython): - prerequisites = { - 'modules': { - 'python': lambda v: version.parse(v) >= version.parse('3.5') - } - } load_module = 'asgi' def setup_cookies(self, prefix): diff --git a/test/test_asgi_targets.py b/test/test_asgi_targets.py index 5afc7079..0c9d9ad0 100644 --- a/test/test_asgi_targets.py +++ b/test/test_asgi_targets.py @@ -3,13 +3,12 @@ from packaging import version from unit.applications.lang.python import TestApplicationPython from unit.option import option +prerequisites = { + 'modules': {'python': lambda v: version.parse(v) >= version.parse('3.5')} +} + class TestASGITargets(TestApplicationPython): - prerequisites = { - 'modules': { - 'python': lambda v: version.parse(v) >= version.parse('3.5') - } - } load_module = 'asgi' @pytest.fixture(autouse=True) diff --git a/test/test_asgi_websockets.py b/test/test_asgi_websockets.py index 3fab318c..fa71274b 100644 --- a/test/test_asgi_websockets.py +++ b/test/test_asgi_websockets.py @@ -6,13 +6,12 @@ from packaging import version from unit.applications.lang.python import TestApplicationPython from unit.applications.websockets import TestApplicationWebsocket +prerequisites = { + 'modules': {'python': lambda v: version.parse(v) >= version.parse('3.5')} +} + class TestASGIWebsockets(TestApplicationPython): - prerequisites = { - 'modules': { - 'python': lambda v: version.parse(v) >= version.parse('3.5') - } - } load_module = 'asgi' ws = TestApplicationWebsocket() diff --git a/test/test_client_ip.py b/test/test_client_ip.py index 59e170f1..347083bc 100644 --- a/test/test_client_ip.py +++ b/test/test_client_ip.py @@ -2,10 +2,10 @@ import pytest from unit.applications.lang.python import TestApplicationPython from unit.option import option +prerequisites = {'modules': {'python': 'any'}} -class TestClientIP(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestClientIP(TestApplicationPython): @pytest.fixture(autouse=True) def setup_method_fixture(self): self.load('client_ip') diff --git a/test/test_configuration.py b/test/test_configuration.py index e78a2957..0c48ad57 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -3,10 +3,10 @@ import socket import pytest from unit.control import TestControl +prerequisites = {'modules': {'python': 'any'}} -class TestConfiguration(TestControl): - prerequisites = {'modules': {'python': 'any'}} +class TestConfiguration(TestControl): def try_addr(self, addr): return self.conf( { @@ -420,10 +420,10 @@ class TestConfiguration(TestControl): assert 'success' in self.conf(conf) - def test_unprivileged_user_error(self, is_su, skip_alert): + def test_unprivileged_user_error(self, require, skip_alert): + require({'privileged_user': False}) + skip_alert(r'cannot set user "root"', r'failed to apply new conf') - if is_su: - pytest.skip('unprivileged tests') assert 'error' in self.conf( { diff --git a/test/test_forwarded_header.py b/test/test_forwarded_header.py index 9c6e0395..6fe61d23 100644 --- a/test/test_forwarded_header.py +++ b/test/test_forwarded_header.py @@ -1,10 +1,10 @@ import pytest from unit.applications.lang.python import TestApplicationPython +prerequisites = {'modules': {'python': 'any'}} -class TestForwardedHeader(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestForwardedHeader(TestApplicationPython): @pytest.fixture(autouse=True) def setup_method_fixture(self): self.load('forwarded_header') @@ -190,9 +190,7 @@ class TestForwardedHeader(TestApplicationPython): == '1.1.1.1' ), 'xff replace multi 3' assert ( - self.get_addr( - xff='8.8.8.8, 2001:db8:3c4d:15::1a2f:1a2b, 127.0.0.1' - ) + self.get_addr(xff='8.8.8.8, 2001:db8:3c4d:15::1a2f:1a2b, 127.0.0.1') == '2001:db8:3c4d:15::1a2f:1a2b' ), 'xff chain ipv6' diff --git a/test/test_go_application.py b/test/test_go_application.py index 062d0cb5..36ddbc3a 100644 --- a/test/test_go_application.py +++ b/test/test_go_application.py @@ -3,10 +3,10 @@ import re import pytest from unit.applications.lang.go import TestApplicationGo +prerequisites = {'modules': {'go': 'all'}} -class TestGoApplication(TestApplicationGo): - prerequisites = {'modules': {'go': 'all'}} +class TestGoApplication(TestApplicationGo): @pytest.fixture(autouse=True) def setup_method_fixture(self, skip_alert): skip_alert(r'\[unit\] close\(\d+\) failed: Bad file descriptor') diff --git a/test/test_go_isolation.py b/test/test_go_isolation.py index 8216a6fe..ba997fd3 100644 --- a/test/test_go_isolation.py +++ b/test/test_go_isolation.py @@ -7,10 +7,10 @@ from unit.applications.lang.go import TestApplicationGo from unit.option import option from unit.utils import getns +prerequisites = {'modules': {'go': 'any'}, 'features': {'isolation': True}} -class TestGoIsolation(TestApplicationGo): - prerequisites = {'modules': {'go': 'any'}, 'features': ['isolation']} +class TestGoIsolation(TestApplicationGo): @pytest.fixture(autouse=True) def setup_method_fixture(self, skip_alert): skip_alert(r'\[unit\] close\(\d+\) failed: Bad file descriptor') @@ -27,9 +27,6 @@ class TestGoIsolation(TestApplicationGo): return (nobody_uid, nogroup_gid, nogroup) - def isolation_key(self, key): - return key in option.available['features']['isolation'].keys() - def test_isolation_values(self): self.load('ns_inspect') @@ -39,12 +36,13 @@ class TestGoIsolation(TestApplicationGo): if ns.upper() in obj['NS']: assert obj['NS'][ns.upper()] == ns_value, f'{ns} match' - def test_isolation_unpriv_user(self, is_su): - if not self.isolation_key('unprivileged_userns_clone'): - pytest.skip('unprivileged clone is not available') - - if is_su: - pytest.skip('privileged tests, skip this') + def test_isolation_unpriv_user(self, require): + require( + { + 'privileged_user': False, + 'features': {'isolation': ['unprivileged_userns_clone']}, + } + ) self.load('ns_inspect') obj = self.getjson()['body'] @@ -101,9 +99,8 @@ class TestGoIsolation(TestApplicationGo): assert obj['UID'] == 0, 'uid match uidmap' assert obj['GID'] == 0, 'gid match gidmap' - def test_isolation_priv_user(self, is_su): - if not is_su: - pytest.skip('unprivileged tests, skip this') + def test_isolation_priv_user(self, require): + require({'privileged_user': True}) self.load('ns_inspect') @@ -176,12 +173,12 @@ class TestGoIsolation(TestApplicationGo): assert obj['UID'] == nobody_uid, 'uid match uidmap user=nobody' assert obj['GID'] == nogroup_gid, 'gid match uidmap user=nobody' - def test_isolation_mnt(self): - if not self.isolation_key('mnt'): - pytest.skip('mnt namespace is not supported') - - if not self.isolation_key('unprivileged_userns_clone'): - pytest.skip('unprivileged clone is not available') + def test_isolation_mnt(self, require): + require( + { + 'features': {'isolation': ['unprivileged_userns_clone', 'mnt']}, + } + ) self.load( 'ns_inspect', @@ -205,19 +202,21 @@ class TestGoIsolation(TestApplicationGo): assert obj['NS']['MNT'] != getns('mnt'), 'mnt set' assert obj['NS']['USER'] != getns('user'), 'user set' - def test_isolation_pid(self, is_su): - if not self.isolation_key('pid'): - pytest.skip('pid namespace is not supported') + def test_isolation_pid(self, is_su, require): + require({'features': {'isolation': ['pid']}}) if not is_su: - if not self.isolation_key('unprivileged_userns_clone'): - pytest.skip('unprivileged clone is not available') - - if not self.isolation_key('user'): - pytest.skip('user namespace is not supported') - - if not self.isolation_key('mnt'): - pytest.skip('mnt namespace is not supported') + require( + { + 'features': { + 'isolation': [ + 'unprivileged_userns_clone', + 'user', + 'mnt', + ] + } + } + ) isolation = {'namespaces': {'pid': True}} @@ -262,19 +261,20 @@ class TestGoIsolation(TestApplicationGo): == option.available['features']['isolation'][ns] ), f'{ns} match' - def test_go_isolation_rootfs_container(self, is_su, temp_dir): + def test_go_isolation_rootfs_container(self, is_su, require, temp_dir): if not is_su: - if not self.isolation_key('unprivileged_userns_clone'): - pytest.skip('unprivileged clone is not available') - - if not self.isolation_key('user'): - pytest.skip('user namespace is not supported') - - if not self.isolation_key('mnt'): - pytest.skip('mnt namespace is not supported') - - if not self.isolation_key('pid'): - pytest.skip('pid namespace is not supported') + require( + { + 'features': { + 'isolation': [ + 'unprivileged_userns_clone', + 'user', + 'mnt', + 'pid', + ] + } + } + ) isolation = {'rootfs': temp_dir} @@ -294,12 +294,8 @@ class TestGoIsolation(TestApplicationGo): obj = self.getjson(url='/?file=/bin/sh')['body'] assert not obj['FileExists'], 'file should not exists' - def test_go_isolation_rootfs_container_priv(self, is_su, temp_dir): - if not is_su: - pytest.skip('requires root') - - if not self.isolation_key('mnt'): - pytest.skip('mnt namespace is not supported') + def test_go_isolation_rootfs_container_priv(self, require, temp_dir): + require({'privileged_user': True, 'features': {'isolation': ['mnt']}}) isolation = { 'namespaces': {'mount': True}, @@ -315,24 +311,27 @@ class TestGoIsolation(TestApplicationGo): obj = self.getjson(url='/?file=/bin/sh')['body'] assert not obj['FileExists'], 'file should not exists' - def test_go_isolation_rootfs_automount_tmpfs(self, is_su, temp_dir): + def test_go_isolation_rootfs_automount_tmpfs( + self, is_su, require, temp_dir + ): try: open("/proc/self/mountinfo") except: pytest.skip('The system lacks /proc/self/mountinfo file') if not is_su: - if not self.isolation_key('unprivileged_userns_clone'): - pytest.skip('unprivileged clone is not available') - - if not self.isolation_key('user'): - pytest.skip('user namespace is not supported') - - if not self.isolation_key('mnt'): - pytest.skip('mnt namespace is not supported') - - if not self.isolation_key('pid'): - pytest.skip('pid namespace is not supported') + require( + { + 'features': { + 'isolation': [ + 'unprivileged_userns_clone', + 'user', + 'mnt', + 'pid', + ] + } + } + ) isolation = {'rootfs': temp_dir} diff --git a/test/test_go_isolation_rootfs.py b/test/test_go_isolation_rootfs.py index b4d73ec8..aa07b80d 100644 --- a/test/test_go_isolation_rootfs.py +++ b/test/test_go_isolation_rootfs.py @@ -1,26 +1,20 @@ -import os - import pytest from unit.applications.lang.go import TestApplicationGo +prerequisites = { + 'modules': {'go': 'all'}, + 'features': {'isolation': True}, + 'privileged_user': True, +} -class TestGoIsolationRootfs(TestApplicationGo): - prerequisites = {'modules': {'go': 'all'}} +class TestGoIsolationRootfs(TestApplicationGo): @pytest.fixture(autouse=True) def setup_method_fixture(self, 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') - - if os.uname().sysname == 'Darwin': - pytest.skip('chroot tests not supported on OSX') - - isolation = { - 'rootfs': temp_dir, - } + def test_go_isolation_rootfs_chroot(self, temp_dir): + isolation = {'rootfs': temp_dir} self.load('ns_inspect', isolation=isolation) diff --git a/test/test_http_header.py b/test/test_http_header.py index cae5e9b8..3202ae9c 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -1,10 +1,10 @@ import pytest from unit.applications.lang.python import TestApplicationPython +prerequisites = {'modules': {'python': 'any'}} -class TestHTTPHeader(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestHTTPHeader(TestApplicationPython): def test_http_header_value_leading_sp(self): self.load('custom_header') diff --git a/test/test_java_application.py b/test/test_java_application.py index 85cf71a1..4296b1e5 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -7,10 +7,10 @@ from unit.applications.lang.java import TestApplicationJava from unit.option import option from unit.utils import public_dir +prerequisites = {'modules': {'java': 'all'}} -class TestJavaApplication(TestApplicationJava): - prerequisites = {'modules': {'java': 'all'}} +class TestJavaApplication(TestApplicationJava): def test_java_conf_error(self, temp_dir, skip_alert): skip_alert( r'realpath.*failed', diff --git a/test/test_java_isolation_rootfs.py b/test/test_java_isolation_rootfs.py index 28bc4a0d..bbd2c915 100644 --- a/test/test_java_isolation_rootfs.py +++ b/test/test_java_isolation_rootfs.py @@ -5,15 +5,12 @@ import pytest from unit.applications.lang.java import TestApplicationJava from unit.option import option +prerequisites = {'modules': {'java': 'all'}, 'privileged_user': True} -class TestJavaIsolationRootfs(TestApplicationJava): - prerequisites = {'modules': {'java': 'all'}} +class TestJavaIsolationRootfs(TestApplicationJava): @pytest.fixture(autouse=True) - def setup_method_fixture(self, is_su, temp_dir): - if not is_su: - pytest.skip('require root') - + def setup_method_fixture(self, temp_dir): os.makedirs(f'{temp_dir}/jars') os.makedirs(f'{temp_dir}/tmp') os.chmod(f'{temp_dir}/tmp', 0o777) @@ -35,10 +32,7 @@ class TestJavaIsolationRootfs(TestApplicationJava): except subprocess.CalledProcessError: pytest.fail("Can't run mount process.") - def teardown_method(self, is_su): - if not is_su: - return - + def teardown_method(self): try: subprocess.run( ["umount", "--lazy", f"{option.temp_dir}/jars"], @@ -51,13 +45,8 @@ class TestJavaIsolationRootfs(TestApplicationJava): 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: - pytest.skip('require root') - - isolation = { - 'rootfs': temp_dir, - } + def test_java_isolation_rootfs_chroot_war(self, temp_dir): + isolation = {'rootfs': temp_dir} self.load('empty_war', isolation=isolation) diff --git a/test/test_java_websockets.py b/test/test_java_websockets.py index ce8f6e83..573e3525 100644 --- a/test/test_java_websockets.py +++ b/test/test_java_websockets.py @@ -5,10 +5,10 @@ import pytest from unit.applications.lang.java import TestApplicationJava from unit.applications.websockets import TestApplicationWebsocket +prerequisites = {'modules': {'java': 'any'}} -class TestJavaWebsockets(TestApplicationJava): - prerequisites = {'modules': {'java': 'any'}} +class TestJavaWebsockets(TestApplicationJava): ws = TestApplicationWebsocket() @pytest.fixture(autouse=True) diff --git a/test/test_njs.py b/test/test_njs.py index 86a8a4ba..dc0ff921 100644 --- a/test/test_njs.py +++ b/test/test_njs.py @@ -5,18 +5,16 @@ from unit.applications.proto import TestApplicationProto from unit.option import option from unit.utils import waitforfiles +prerequisites = {'modules': {'njs': 'any'}} -class TestNJS(TestApplicationProto): - prerequisites = {'modules': {'njs': 'any'}} +class TestNJS(TestApplicationProto): @pytest.fixture(autouse=True) def setup_method_fixture(self, temp_dir): assert 'success' in self.conf( { "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [ - {"action": {"share": f"{temp_dir}/assets$uri"}} - ], + "routes": [{"action": {"share": f"{temp_dir}/assets$uri"}}], } ) diff --git a/test/test_njs_modules.py b/test/test_njs_modules.py index ce592fe4..10ea03a7 100644 --- a/test/test_njs_modules.py +++ b/test/test_njs_modules.py @@ -1,10 +1,10 @@ from unit.applications.proto import TestApplicationProto from unit.option import option +prerequisites = {'modules': {'njs': 'any'}} -class TestNJSModules(TestApplicationProto): - prerequisites = {'modules': {'njs': 'any'}} +class TestNJSModules(TestApplicationProto): def njs_script_load(self, module, name=None, expect='success'): if name is None: name = module diff --git a/test/test_node_application.py b/test/test_node_application.py index 321e7aa9..8b0b90ea 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -4,10 +4,10 @@ import pytest from unit.applications.lang.node import TestApplicationNode from unit.utils import waitforfiles +prerequisites = {'modules': {'node': 'all'}} -class TestNodeApplication(TestApplicationNode): - prerequisites = {'modules': {'node': 'all'}} +class TestNodeApplication(TestApplicationNode): def assert_basic_application(self): resp = self.get() assert resp['headers']['Content-Type'] == 'text/plain', 'basic header' diff --git a/test/test_node_es_modules.py b/test/test_node_es_modules.py index 8a9cb181..10f9eab0 100644 --- a/test/test_node_es_modules.py +++ b/test/test_node_es_modules.py @@ -2,14 +2,12 @@ from packaging import version from unit.applications.lang.node import TestApplicationNode from unit.applications.websockets import TestApplicationWebsocket +prerequisites = { + 'modules': {'node': lambda v: version.parse(v) >= version.parse('14.16.0')} +} -class TestNodeESModules(TestApplicationNode): - prerequisites = { - 'modules': { - 'node': lambda v: version.parse(v) >= version.parse('14.16.0') - } - } +class TestNodeESModules(TestApplicationNode): es_modules = True ws = TestApplicationWebsocket() diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py index fc263d63..d59bc2ef 100644 --- a/test/test_node_websockets.py +++ b/test/test_node_websockets.py @@ -5,10 +5,10 @@ import pytest from unit.applications.lang.node import TestApplicationNode from unit.applications.websockets import TestApplicationWebsocket +prerequisites = {'modules': {'node': 'any'}} -class TestNodeWebsockets(TestApplicationNode): - prerequisites = {'modules': {'node': 'any'}} +class TestNodeWebsockets(TestApplicationNode): ws = TestApplicationWebsocket() @pytest.fixture(autouse=True) diff --git a/test/test_perl_application.py b/test/test_perl_application.py index 17bd0fea..91737843 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -3,10 +3,10 @@ import re import pytest from unit.applications.lang.perl import TestApplicationPerl +prerequisites = {'modules': {'perl': 'all'}} -class TestPerlApplication(TestApplicationPerl): - prerequisites = {'modules': {'perl': 'all'}} +class TestPerlApplication(TestApplicationPerl): def test_perl_application(self, date_to_sec_epoch, sec_epoch): self.load('variables') diff --git a/test/test_php_application.py b/test/test_php_application.py index faf1d18b..548c0c9d 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -10,10 +10,10 @@ import pytest from unit.applications.lang.php import TestApplicationPHP from unit.option import option +prerequisites = {'modules': {'php': 'all'}} -class TestPHPApplication(TestApplicationPHP): - prerequisites = {'modules': {'php': 'all'}} +class TestPHPApplication(TestApplicationPHP): def before_disable_functions(self): body = self.get()['body'] diff --git a/test/test_php_basic.py b/test/test_php_basic.py index bcd66173..1a2a00e6 100644 --- a/test/test_php_basic.py +++ b/test/test_php_basic.py @@ -1,9 +1,9 @@ from unit.control import TestControl +prerequisites = {'modules': {'php': 'any'}} -class TestPHPBasic(TestControl): - prerequisites = {'modules': {'php': 'any'}} +class TestPHPBasic(TestControl): conf_app = { "app": { "type": "php", diff --git a/test/test_php_isolation.py b/test/test_php_isolation.py index aebeefa6..e8471015 100644 --- a/test/test_php_isolation.py +++ b/test/test_php_isolation.py @@ -1,30 +1,26 @@ -import pytest from unit.applications.lang.php import TestApplicationPHP -from unit.option import option +prerequisites = {'modules': {'php': 'any'}, 'features': {'isolation': True}} -class TestPHPIsolation(TestApplicationPHP): - prerequisites = {'modules': {'php': 'any'}, 'features': ['isolation']} - - def test_php_isolation_rootfs(self, is_su, temp_dir): - isolation_features = option.available['features']['isolation'].keys() - - if not is_su: - if not 'unprivileged_userns_clone' in isolation_features: - pytest.skip('requires unprivileged userns or root') - - if 'user' not in isolation_features: - pytest.skip('user namespace is not supported') - - if 'mnt' not in isolation_features: - pytest.skip('mnt namespace is not supported') - - if 'pid' not in isolation_features: - pytest.skip('pid namespace is not supported') +class TestPHPIsolation(TestApplicationPHP): + def test_php_isolation_rootfs(self, is_su, require, temp_dir): isolation = {'rootfs': temp_dir} if not is_su: + require( + { + 'features': { + 'isolation': [ + 'unprivileged_userns_clone', + 'user', + 'mnt', + 'pid', + ] + } + } + ) + isolation['namespaces'] = { 'mount': True, 'credential': True, @@ -42,25 +38,23 @@ class TestPHPIsolation(TestApplicationPHP): assert self.get()['status'] == 200, 'empty rootfs' - def test_php_isolation_rootfs_extensions(self, is_su, temp_dir): - isolation_features = option.available['features']['isolation'].keys() - - if not is_su: - if not 'unprivileged_userns_clone' in isolation_features: - pytest.skip('requires unprivileged userns or root') - - if 'user' not in isolation_features: - pytest.skip('user namespace is not supported') - - if 'mnt' not in isolation_features: - pytest.skip('mnt namespace is not supported') - - if 'pid' not in isolation_features: - pytest.skip('pid namespace is not supported') - + def test_php_isolation_rootfs_extensions(self, is_su, require, temp_dir): isolation = {'rootfs': temp_dir} if not is_su: + require( + { + 'features': { + 'isolation': [ + 'unprivileged_userns_clone', + 'user', + 'mnt', + 'pid', + ] + } + } + ) + isolation['namespaces'] = { 'mount': True, 'credential': True, diff --git a/test/test_php_targets.py b/test/test_php_targets.py index e74f2ec6..db9b56ce 100644 --- a/test/test_php_targets.py +++ b/test/test_php_targets.py @@ -1,10 +1,10 @@ from unit.applications.lang.php import TestApplicationPHP from unit.option import option +prerequisites = {'modules': {'php': 'any'}} -class TestPHPTargets(TestApplicationPHP): - prerequisites = {'modules': {'php': 'any'}} +class TestPHPTargets(TestApplicationPHP): def test_php_application_targets(self): targets_dir = f"{option.test_dir}/php/targets" assert 'success' in self.conf( diff --git a/test/test_proxy.py b/test/test_proxy.py index 23b4a6a4..ad14c23d 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -8,10 +8,10 @@ from unit.applications.lang.python import TestApplicationPython from unit.option import option from unit.utils import waitforsocket +prerequisites = {'modules': {'python': 'any'}} -class TestProxy(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestProxy(TestApplicationPython): SERVER_PORT = 7999 @pytest.fixture(autouse=True) diff --git a/test/test_proxy_chunked.py b/test/test_proxy_chunked.py index b5198e9c..75203073 100644 --- a/test/test_proxy_chunked.py +++ b/test/test_proxy_chunked.py @@ -8,10 +8,10 @@ from conftest import run_process from unit.applications.lang.python import TestApplicationPython from unit.utils import waitforsocket +prerequisites = {'modules': {'python': 'any'}} -class TestProxyChunked(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestProxyChunked(TestApplicationPython): SERVER_PORT = 7999 @pytest.fixture(autouse=True) diff --git a/test/test_python_application.py b/test/test_python_application.py index f67dc24f..d846c1e3 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -10,10 +10,10 @@ import pytest from packaging import version from unit.applications.lang.python import TestApplicationPython +prerequisites = {'modules': {'python': 'all'}} -class TestPythonApplication(TestApplicationPython): - prerequisites = {'modules': {'python': 'all'}} +class TestPythonApplication(TestApplicationPython): def test_python_application_variables(self, date_to_sec_epoch, sec_epoch): self.load('variables') @@ -740,9 +740,8 @@ last line: 987654321 ), 'exception raise close' assert len(findall(r'Traceback')) == 8, 'traceback count 8' - def test_python_user_group(self, is_su): - if not is_su: - pytest.skip('requires root') + def test_python_user_group(self, require): + require({'privileged_user': True}) nobody_uid = pwd.getpwnam('nobody').pw_uid diff --git a/test/test_python_basic.py b/test/test_python_basic.py index e661a89c..5783e78d 100644 --- a/test/test_python_basic.py +++ b/test/test_python_basic.py @@ -1,8 +1,9 @@ from unit.control import TestControl +prerequisites = {'modules': {'python': 'any'}} + class TestPythonBasic(TestControl): - prerequisites = {'modules': {'python': 'any'}} conf_app = { "app": { diff --git a/test/test_python_environment.py b/test/test_python_environment.py index bce72c4d..a57e3760 100644 --- a/test/test_python_environment.py +++ b/test/test_python_environment.py @@ -1,9 +1,9 @@ from unit.applications.lang.python import TestApplicationPython +prerequisites = {'modules': {'python': 'any'}} -class TestPythonEnvironment(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestPythonEnvironment(TestApplicationPython): def test_python_environment_name_null(self): self.load('environment') diff --git a/test/test_python_isolation.py b/test/test_python_isolation.py index 506d20b7..a9ed2900 100644 --- a/test/test_python_isolation.py +++ b/test/test_python_isolation.py @@ -10,10 +10,10 @@ from unit.utils import findmnt from unit.utils import waitformount from unit.utils import waitforunmount +prerequisites = {'modules': {'python': 'any'}, 'features': {'isolation': True}} -class TestPythonIsolation(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}, 'features': ['isolation']} +class TestPythonIsolation(TestApplicationPython): def get_cgroup(self, app_name): output = subprocess.check_output( ['ps', 'ax', '-o', 'pid', '-o', 'cmd'] @@ -31,25 +31,23 @@ class TestPythonIsolation(TestApplicationPython): with open(cgroup, 'r') as f: return f.read().rstrip() - def test_python_isolation_rootfs(self, is_su, temp_dir): - isolation_features = option.available['features']['isolation'].keys() - - if not is_su: - if not 'unprivileged_userns_clone' in isolation_features: - pytest.skip('requires unprivileged userns or root') - - if 'user' not in isolation_features: - pytest.skip('user namespace is not supported') - - if 'mnt' not in isolation_features: - pytest.skip('mnt namespace is not supported') - - if 'pid' not in isolation_features: - pytest.skip('pid namespace is not supported') - + def test_python_isolation_rootfs(self, is_su, require, temp_dir): isolation = {'rootfs': temp_dir} if not is_su: + require( + { + 'features': { + 'isolation': [ + 'unprivileged_userns_clone', + 'user', + 'mnt', + 'pid', + ] + } + } + ) + isolation['namespaces'] = { 'mount': True, 'credential': True, @@ -78,9 +76,8 @@ class TestPythonIsolation(TestApplicationPython): assert ret['body']['FileExists'], 'application exists in rootfs' - def test_python_isolation_rootfs_no_language_deps(self, is_su, temp_dir): - if not is_su: - pytest.skip('requires root') + def test_python_isolation_rootfs_no_language_deps(self, require, temp_dir): + require({'privileged_user': True}) isolation = {'rootfs': temp_dir, 'automount': {'language_deps': False}} self.load('empty', isolation=isolation) @@ -103,9 +100,8 @@ class TestPythonIsolation(TestApplicationPython): assert waitforunmount(python_path), 'language_deps unmount' - def test_python_isolation_procfs(self, is_su, temp_dir): - if not is_su: - pytest.skip('requires root') + def test_python_isolation_procfs(self, require, temp_dir): + require({'privileged_user': True}) isolation = {'rootfs': temp_dir, 'automount': {'procfs': False}} @@ -123,12 +119,10 @@ class TestPythonIsolation(TestApplicationPython): 'FileExists' ], '/proc/self' - def test_python_isolation_cgroup(self, is_su): - if not is_su: - pytest.skip('requires root') - - if not 'cgroup' in option.available['features']['isolation']: - pytest.skip('cgroup is not supported') + def test_python_isolation_cgroup(self, require): + require( + {'privileged_user': True, 'features': {'isolation': ['cgroup']}} + ) def set_cgroup_path(path): isolation = {'cgroup': {'path': path}} @@ -146,12 +140,10 @@ class TestPythonIsolation(TestApplicationPython): assert len(cgroup_rel.parts) >= len(cgroup_abs.parts) - def test_python_isolation_cgroup_two(self, is_su): - if not is_su: - pytest.skip('requires root') - - if not 'cgroup' in option.available['features']['isolation']: - pytest.skip('cgroup is not supported') + def test_python_isolation_cgroup_two(self, require): + require( + {'privileged_user': True, 'features': {'isolation': ['cgroup']}} + ) def set_two_cgroup_path(path, path2): script_path = f'{option.test_dir}/python/empty' @@ -193,12 +185,10 @@ class TestPythonIsolation(TestApplicationPython): set_two_cgroup_path('/scope/python', '/scope2/python') assert self.get_cgroup('one') != self.get_cgroup('two') - def test_python_isolation_cgroup_invalid(self, is_su): - if not is_su: - pytest.skip('requires root') - - if not 'cgroup' in option.available['features']['isolation']: - pytest.skip('cgroup is not supported') + def test_python_isolation_cgroup_invalid(self, require): + require( + {'privileged_user': True, 'features': {'isolation': ['cgroup']}} + ) def check_invalid(path): script_path = f'{option.test_dir}/python/empty' diff --git a/test/test_python_isolation_chroot.py b/test/test_python_isolation_chroot.py index dd0dede0..fd16c1fb 100644 --- a/test/test_python_isolation_chroot.py +++ b/test/test_python_isolation_chroot.py @@ -1,17 +1,11 @@ -import pytest from unit.applications.lang.python import TestApplicationPython +prerequisites = {'modules': {'python': 'any'}, 'privileged_user': True} -class TestPythonIsolation(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} - - def test_python_isolation_chroot(self, is_su, temp_dir): - if not is_su: - pytest.skip('requires root') - isolation = { - 'rootfs': temp_dir, - } +class TestPythonIsolation(TestApplicationPython): + def test_python_isolation_chroot(self, temp_dir): + isolation = {'rootfs': temp_dir} self.load('ns_inspect', isolation=isolation) diff --git a/test/test_python_procman.py b/test/test_python_procman.py index 82766d65..86f70ba9 100644 --- a/test/test_python_procman.py +++ b/test/test_python_procman.py @@ -7,10 +7,10 @@ import pytest from unit.applications.lang.python import TestApplicationPython from unit.option import option +prerequisites = {'modules': {'python': 'any'}} -class TestPythonProcman(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestPythonProcman(TestApplicationPython): @pytest.fixture(autouse=True) def setup_method_fixture(self, temp_dir): self.app_name = f'app-{temp_dir.split("/")[-1]}' diff --git a/test/test_python_targets.py b/test/test_python_targets.py index f55609ba..dc0dc529 100644 --- a/test/test_python_targets.py +++ b/test/test_python_targets.py @@ -1,10 +1,10 @@ from unit.applications.lang.python import TestApplicationPython from unit.option import option +prerequisites = {'modules': {'python': 'all'}} -class TestPythonTargets(TestApplicationPython): - prerequisites = {'modules': {'python': 'all'}} +class TestPythonTargets(TestApplicationPython): def test_python_targets(self): python_dir = f'{option.test_dir}/python' diff --git a/test/test_reconfigure.py b/test/test_reconfigure.py index feb027aa..ae19db9d 100644 --- a/test/test_reconfigure.py +++ b/test/test_reconfigure.py @@ -5,8 +5,6 @@ from unit.applications.proto import TestApplicationProto class TestReconfigure(TestApplicationProto): - prerequisites = {} - @pytest.fixture(autouse=True) def setup_method_fixture(self): assert 'success' in self.conf( diff --git a/test/test_reconfigure_tls.py b/test/test_reconfigure_tls.py index 0f92a419..310f800a 100644 --- a/test/test_reconfigure_tls.py +++ b/test/test_reconfigure_tls.py @@ -5,10 +5,10 @@ import time import pytest from unit.applications.tls import TestApplicationTLS +prerequisites = {'modules': {'openssl': 'any'}} -class TestReconfigureTLS(TestApplicationTLS): - prerequisites = {'modules': {'openssl': 'any'}} +class TestReconfigureTLS(TestApplicationTLS): @pytest.fixture(autouse=True) def setup_method_fixture(self): if 'HAS_TLSv1_2' not in dir(ssl) or not ssl.HAS_TLSv1_2: diff --git a/test/test_respawn.py b/test/test_respawn.py index 2d01cf3b..84bddbaa 100644 --- a/test/test_respawn.py +++ b/test/test_respawn.py @@ -5,10 +5,10 @@ import time import pytest from unit.applications.lang.python import TestApplicationPython +prerequisites = {'modules': {'python': 'any'}} -class TestRespawn(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestRespawn(TestApplicationPython): PATTERN_ROUTER = 'unit: router' PATTERN_CONTROLLER = 'unit: controller' diff --git a/test/test_return.py b/test/test_return.py index 71b1242c..478f8393 100644 --- a/test/test_return.py +++ b/test/test_return.py @@ -5,8 +5,6 @@ from unit.applications.proto import TestApplicationProto class TestReturn(TestApplicationProto): - prerequisites = {} - @pytest.fixture(autouse=True) def setup_method_fixture(self): self._load_conf( diff --git a/test/test_rewrite.py b/test/test_rewrite.py index a7b8e975..c1844165 100644 --- a/test/test_rewrite.py +++ b/test/test_rewrite.py @@ -2,12 +2,9 @@ import os import pytest from unit.applications.proto import TestApplicationProto -from unit.option import option class TestRewrite(TestApplicationProto): - prerequisites = {} - @pytest.fixture(autouse=True) def setup_method_fixture(self): assert 'success' in self.conf( @@ -97,9 +94,8 @@ class TestRewrite(TestApplicationProto): ) assert self.get(url='/foo?arg=val')['status'] == 200 - def test_rewrite_njs(self): - if 'njs' not in option.available['modules'].keys(): - pytest.skip('NJS is not available') + def test_rewrite_njs(self, require): + require({'modules': {'njs': 'any'}}) self.set_rewrite("`/${host}`", "/localhost") assert self.get()['status'] == 200 diff --git a/test/test_routing.py b/test/test_routing.py index 41de6f51..715033dd 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -3,10 +3,10 @@ import pytest from unit.applications.lang.python import TestApplicationPython from unit.option import option +prerequisites = {'modules': {'python': 'any'}} -class TestRouting(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestRouting(TestApplicationPython): @pytest.fixture(autouse=True) def setup_method_fixture(self): assert 'success' in self.conf( @@ -232,9 +232,8 @@ class TestRouting(TestApplicationPython): assert self.get(url='/aBCaBbc')['status'] == 200 assert self.get(url='/ABc')['status'] == 404 - def test_routes_empty_regex(self): - if not option.available['modules']['regex']: - pytest.skip('requires regex') + def test_routes_empty_regex(self, require): + require({'modules': {'regex': True}}) self.route_match({"uri": "~"}) assert self.get(url='/')['status'] == 200, 'empty regexp' @@ -244,9 +243,8 @@ class TestRouting(TestApplicationPython): assert self.get(url='/')['status'] == 404, 'empty regexp 2' assert self.get(url='/nothing')['status'] == 404, '/nothing' - def test_routes_bad_regex(self): - if not option.available['modules']['regex']: - pytest.skip('requires regex') + def test_routes_bad_regex(self, require): + require({'modules': {'regex': True}}) assert 'error' in self.route( {"match": {"uri": "~/bl[ah"}, "action": {"return": 200}} @@ -264,9 +262,8 @@ class TestRouting(TestApplicationPython): if 'error' not in status: assert self.get(url='/nothing_z')['status'] == 500, '/nothing_z' - def test_routes_match_regex_case_sensitive(self): - if not option.available['modules']['regex']: - pytest.skip('requires regex') + def test_routes_match_regex_case_sensitive(self, require): + require({'modules': {'regex': True}}) self.route_match({"uri": "~/bl[ah]"}) @@ -275,9 +272,8 @@ class TestRouting(TestApplicationPython): assert self.get(url='/blh')['status'] == 200, '/blh' assert self.get(url='/BLAH')['status'] == 404, '/BLAH' - def test_routes_match_regex_negative_case_sensitive(self): - if not option.available['modules']['regex']: - pytest.skip('requires regex') + def test_routes_match_regex_negative_case_sensitive(self, require): + require({'modules': {'regex': True}}) self.route_match({"uri": "!~/bl[ah]"}) diff --git a/test/test_routing_tls.py b/test/test_routing_tls.py index 76cfb485..1ba79986 100644 --- a/test/test_routing_tls.py +++ b/test/test_routing_tls.py @@ -1,9 +1,9 @@ from unit.applications.tls import TestApplicationTLS +prerequisites = {'modules': {'openssl': 'any'}} -class TestRoutingTLS(TestApplicationTLS): - prerequisites = {'modules': {'openssl': 'any'}} +class TestRoutingTLS(TestApplicationTLS): def test_routes_match_scheme_tls(self): self.certificate() diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index 166ca1ed..424d6764 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -4,10 +4,10 @@ import subprocess import pytest from unit.applications.lang.ruby import TestApplicationRuby +prerequisites = {'modules': {'ruby': 'all'}} -class TestRubyApplication(TestApplicationRuby): - prerequisites = {'modules': {'ruby': 'all'}} +class TestRubyApplication(TestApplicationRuby): def test_ruby_application(self, date_to_sec_epoch, sec_epoch): self.load('variables') diff --git a/test/test_ruby_hooks.py b/test/test_ruby_hooks.py index 078e5723..6bd4c808 100644 --- a/test/test_ruby_hooks.py +++ b/test/test_ruby_hooks.py @@ -2,10 +2,10 @@ from unit.applications.lang.ruby import TestApplicationRuby from unit.option import option from unit.utils import waitforglob +prerequisites = {'modules': {'ruby': 'all'}} -class TestRubyHooks(TestApplicationRuby): - prerequisites = {'modules': {'ruby': 'all'}} +class TestRubyHooks(TestApplicationRuby): def _wait_cookie(self, pattern, count): return waitforglob( f'{option.temp_dir}/ruby/hooks/cookie_{pattern}', count diff --git a/test/test_ruby_isolation.py b/test/test_ruby_isolation.py index 3ec06d86..82a12dff 100644 --- a/test/test_ruby_isolation.py +++ b/test/test_ruby_isolation.py @@ -1,30 +1,26 @@ -import pytest from unit.applications.lang.ruby import TestApplicationRuby -from unit.option import option +prerequisites = {'modules': {'ruby': 'any'}, 'features': {'isolation': True}} -class TestRubyIsolation(TestApplicationRuby): - prerequisites = {'modules': {'ruby': 'any'}, 'features': ['isolation']} - - def test_ruby_isolation_rootfs(self, is_su, temp_dir): - isolation_features = option.available['features']['isolation'].keys() - - if not is_su: - if not 'unprivileged_userns_clone' in isolation_features: - pytest.skip('requires unprivileged userns or root') - - if 'user' not in isolation_features: - pytest.skip('user namespace is not supported') - - if 'mnt' not in isolation_features: - pytest.skip('mnt namespace is not supported') - - if 'pid' not in isolation_features: - pytest.skip('pid namespace is not supported') +class TestRubyIsolation(TestApplicationRuby): + def test_ruby_isolation_rootfs(self, is_su, require, temp_dir): isolation = {'rootfs': temp_dir} if not is_su: + require( + { + 'features': { + 'isolation': [ + 'unprivileged_userns_clone', + 'user', + 'mnt', + 'pid', + ] + } + } + ) + isolation['namespaces'] = { 'mount': True, 'credential': True, diff --git a/test/test_settings.py b/test/test_settings.py index 4b139069..0a48da5e 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -6,10 +6,10 @@ import time import pytest from unit.applications.lang.python import TestApplicationPython +prerequisites = {'modules': {'python': 'any'}} -class TestSettings(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestSettings(TestApplicationPython): def sysctl(self): try: out = subprocess.check_output( diff --git a/test/test_static.py b/test/test_static.py index 48004661..60a066a0 100644 --- a/test/test_static.py +++ b/test/test_static.py @@ -7,8 +7,6 @@ from unit.utils import waitforfiles class TestStatic(TestApplicationProto): - prerequisites = {} - @pytest.fixture(autouse=True) def setup_method_fixture(self, temp_dir): os.makedirs(f'{temp_dir}/assets/dir') diff --git a/test/test_static_chroot.py b/test/test_static_chroot.py index 812e14ea..21a47a78 100644 --- a/test/test_static_chroot.py +++ b/test/test_static_chroot.py @@ -5,10 +5,10 @@ import pytest from unit.applications.proto import TestApplicationProto from unit.option import option +prerequisites = {'features': {'chroot': True}} -class TestStaticChroot(TestApplicationProto): - prerequisites = {'features': ['chroot']} +class TestStaticChroot(TestApplicationProto): @pytest.fixture(autouse=True) def setup_method_fixture(self, temp_dir): os.makedirs(f'{temp_dir}/assets/dir') @@ -62,9 +62,8 @@ class TestStaticChroot(TestApplicationProto): ) 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") + def test_static_chroot_permission(self, require, temp_dir): + require({'privileged_user': False}) os.chmod(f'{temp_dir}/assets/dir', 0o100) @@ -81,9 +80,8 @@ class TestStaticChroot(TestApplicationProto): assert 'success' in self.update_action("", ".$uri") assert self.get(url=self.test_path)['status'] == 200, 'empty relative' - def test_static_chroot_relative(self, is_su): - if is_su: - pytest.skip("Does't work under root.") + def test_static_chroot_relative(self, require): + require({'privileged_user': False}) assert 'success' in self.update_action('.') assert self.get(url='/dir/file')['status'] == 403, 'relative chroot' diff --git a/test/test_static_fallback.py b/test/test_static_fallback.py index 75012bbb..5c3ec7d3 100644 --- a/test/test_static_fallback.py +++ b/test/test_static_fallback.py @@ -6,8 +6,6 @@ from unit.applications.proto import TestApplicationProto class TestStaticFallback(TestApplicationProto): - prerequisites = {} - @pytest.fixture(autouse=True) def setup_method_fixture(self, temp_dir): assets_dir = f'{temp_dir}/assets' diff --git a/test/test_static_mount.py b/test/test_static_mount.py index 406922b1..45ae8fa7 100644 --- a/test/test_static_mount.py +++ b/test/test_static_mount.py @@ -5,15 +5,12 @@ from pathlib import Path import pytest from unit.applications.proto import TestApplicationProto +prerequisites = {'features': {'chroot': True}, 'privileged_user': True} -class TestStaticMount(TestApplicationProto): - prerequisites = {'features': ['chroot']} +class TestStaticMount(TestApplicationProto): @pytest.fixture(autouse=True) - def setup_method_fixture(self, is_su, temp_dir): - if not is_su: - pytest.skip('requires root') - + def setup_method_fixture(self, temp_dir): os.makedirs(f'{temp_dir}/assets/dir/mount') os.makedirs(f'{temp_dir}/assets/dir/dir') os.makedirs(f'{temp_dir}/assets/mount') diff --git a/test/test_static_share.py b/test/test_static_share.py index 0166f1f0..2eb63659 100644 --- a/test/test_static_share.py +++ b/test/test_static_share.py @@ -6,8 +6,6 @@ from unit.applications.proto import TestApplicationProto class TestStaticShare(TestApplicationProto): - prerequisites = {} - @pytest.fixture(autouse=True) def setup_method_fixture(self, temp_dir): os.makedirs(f'{temp_dir}/assets/dir') diff --git a/test/test_static_symlink.py b/test/test_static_symlink.py index 13d67bc7..de65b85b 100644 --- a/test/test_static_symlink.py +++ b/test/test_static_symlink.py @@ -4,10 +4,10 @@ from pathlib import Path import pytest from unit.applications.proto import TestApplicationProto +prerequisites = {'features': {'chroot': True}} -class TestStaticSymlink(TestApplicationProto): - prerequisites = {'features': ['chroot']} +class TestStaticSymlink(TestApplicationProto): @pytest.fixture(autouse=True) def setup_method_fixture(self, temp_dir): os.makedirs(f'{temp_dir}/assets/dir/dir') diff --git a/test/test_static_types.py b/test/test_static_types.py index 28ab28e6..125da181 100644 --- a/test/test_static_types.py +++ b/test/test_static_types.py @@ -5,8 +5,6 @@ from unit.applications.proto import TestApplicationProto class TestStaticTypes(TestApplicationProto): - prerequisites = {} - @pytest.fixture(autouse=True) def setup_method_fixture(self, temp_dir): Path(f'{temp_dir}/assets').mkdir() diff --git a/test/test_static_variables.py b/test/test_static_variables.py index 370c3e6f..f69a936e 100644 --- a/test/test_static_variables.py +++ b/test/test_static_variables.py @@ -6,8 +6,6 @@ from unit.applications.proto import TestApplicationProto class TestStaticVariables(TestApplicationProto): - prerequisites = {} - @pytest.fixture(autouse=True) def setup_method_fixture(self, temp_dir): os.makedirs(f'{temp_dir}/assets/dir') diff --git a/test/test_status.py b/test/test_status.py index f8885dff..2fc235d4 100644 --- a/test/test_status.py +++ b/test/test_status.py @@ -4,10 +4,10 @@ from unit.applications.lang.python import TestApplicationPython from unit.option import option from unit.status import Status +prerequisites = {'modules': {'python': 'any'}} -class TestStatus(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestStatus(TestApplicationPython): def check_connections(self, accepted, active, idle, closed): assert Status.get('/connections') == { 'accepted': accepted, diff --git a/test/test_status_tls.py b/test/test_status_tls.py index dc3d68da..e38a2f43 100644 --- a/test/test_status_tls.py +++ b/test/test_status_tls.py @@ -1,10 +1,10 @@ from unit.applications.tls import TestApplicationTLS from unit.status import Status +prerequisites = {'modules': {'openssl': 'any'}} -class TestStatusTLS(TestApplicationTLS): - prerequisites = {'modules': {'openssl': 'any'}} +class TestStatusTLS(TestApplicationTLS): def test_status_tls_requests(self): self.certificate() diff --git a/test/test_tls.py b/test/test_tls.py index 5c7b31c1..ca9d5b07 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -7,10 +7,10 @@ import pytest from unit.applications.tls import TestApplicationTLS from unit.option import option +prerequisites = {'modules': {'python': 'any', 'openssl': 'any'}} -class TestTLS(TestApplicationTLS): - prerequisites = {'modules': {'python': 'any', 'openssl': 'any'}} +class TestTLS(TestApplicationTLS): def add_tls(self, application='empty', cert='default', port=7080): assert 'success' in self.conf( { diff --git a/test/test_tls_conf_command.py b/test/test_tls_conf_command.py index f2238574..a7500551 100644 --- a/test/test_tls_conf_command.py +++ b/test/test_tls_conf_command.py @@ -3,10 +3,10 @@ import ssl import pytest from unit.applications.tls import TestApplicationTLS +prerequisites = {'modules': {'openssl': 'any'}} -class TestTLSConfCommand(TestApplicationTLS): - prerequisites = {'modules': {'openssl': 'any'}} +class TestTLSConfCommand(TestApplicationTLS): @pytest.fixture(autouse=True) def setup_method_fixture(self): self.certificate() diff --git a/test/test_tls_session.py b/test/test_tls_session.py index 84637dea..997b7148 100644 --- a/test/test_tls_session.py +++ b/test/test_tls_session.py @@ -14,10 +14,10 @@ from OpenSSL.SSL import ( ) from unit.applications.tls import TestApplicationTLS +prerequisites = {'modules': {'openssl': 'any'}} -class TestTLSSession(TestApplicationTLS): - prerequisites = {'modules': {'openssl': 'any'}} +class TestTLSSession(TestApplicationTLS): @pytest.fixture(autouse=True) def setup_method_fixture(self): self.certificate() diff --git a/test/test_tls_sni.py b/test/test_tls_sni.py index 09e212bd..1c3afbea 100644 --- a/test/test_tls_sni.py +++ b/test/test_tls_sni.py @@ -5,10 +5,10 @@ import pytest from unit.applications.tls import TestApplicationTLS from unit.option import option +prerequisites = {'modules': {'openssl': 'any'}} -class TestTLSSNI(TestApplicationTLS): - prerequisites = {'modules': {'openssl': 'any'}} +class TestTLSSNI(TestApplicationTLS): @pytest.fixture(autouse=True) def setup_method_fixture(self): self._load_conf( diff --git a/test/test_tls_tickets.py b/test/test_tls_tickets.py index 3962316e..acb03428 100644 --- a/test/test_tls_tickets.py +++ b/test/test_tls_tickets.py @@ -11,10 +11,10 @@ from OpenSSL.SSL import ( ) from unit.applications.tls import TestApplicationTLS +prerequisites = {'modules': {'openssl': 'any'}} -class TestTLSTicket(TestApplicationTLS): - prerequisites = {'modules': {'openssl': 'any'}} +class TestTLSTicket(TestApplicationTLS): ticket = 'U1oDTh11mMxODuw12gS0EXX1E/PkZG13cJNQ6m5+6BGlfPTjNlIEw7PSVU3X1gTE' ticket2 = '5AV0DSYIYbZWZQB7fCnTHZmMxtotb/aXjam+n2XS79lTvX3Tq9xGqpC8XKNEF2lt' ticket80 = '6Pfil8lv/k8zf8MndPpfXaO5EAV6dhME6zs6CfUyq2yziynQwSywtKQMqHGnJ2HR\ diff --git a/test/test_unix_abstract.py b/test/test_unix_abstract.py index c562487b..a6b7cd99 100644 --- a/test/test_unix_abstract.py +++ b/test/test_unix_abstract.py @@ -1,13 +1,13 @@ from unit.applications.lang.python import TestApplicationPython from unit.option import option +prerequisites = { + 'modules': {'python': 'any'}, + 'features': {'unix_abstract': True}, +} -class TestUnixAbstract(TestApplicationPython): - prerequisites = { - 'modules': {'python': 'any'}, - 'features': ['unix_abstract'], - } +class TestUnixAbstract(TestApplicationPython): def test_unix_abstract_source(self): addr = '\0sock' diff --git a/test/test_upstreams_rr.py b/test/test_upstreams_rr.py index 2bb23be0..cf7aa67d 100644 --- a/test/test_upstreams_rr.py +++ b/test/test_upstreams_rr.py @@ -5,10 +5,10 @@ import pytest from unit.applications.lang.python import TestApplicationPython from unit.option import option +prerequisites = {'modules': {'python': 'any'}} -class TestUpstreamsRR(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestUpstreamsRR(TestApplicationPython): @pytest.fixture(autouse=True) def setup_method_fixture(self): assert 'success' in self.conf( diff --git a/test/test_usr1.py b/test/test_usr1.py index 30160a88..160cef77 100644 --- a/test/test_usr1.py +++ b/test/test_usr1.py @@ -5,10 +5,10 @@ from unit.applications.lang.python import TestApplicationPython from unit.log import Log from unit.utils import waitforfiles +prerequisites = {'modules': {'python': 'any'}} -class TestUSR1(TestApplicationPython): - prerequisites = {'modules': {'python': 'any'}} +class TestUSR1(TestApplicationPython): def test_usr1_access_log( self, search_in_file, temp_dir, unit_pid, wait_for_record ): diff --git a/test/test_variables.py b/test/test_variables.py index 45e193cc..b93f8b36 100644 --- a/test/test_variables.py +++ b/test/test_variables.py @@ -7,8 +7,6 @@ from unit.option import option class TestVariables(TestApplicationProto): - prerequisites = {} - @pytest.fixture(autouse=True) def setup_method_fixture(self): assert 'success' in self.conf( diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py index 00ea44b2..354c31af 100644 --- a/test/unit/applications/proto.py +++ b/test/unit/applications/proto.py @@ -1,9 +1,6 @@ import os -import re -import time from unit.control import TestControl -from unit.log import Log from unit.option import option diff --git a/test/unit/check/check_prerequisites.py b/test/unit/check/check_prerequisites.py new file mode 100644 index 00000000..44c3f10f --- /dev/null +++ b/test/unit/check/check_prerequisites.py @@ -0,0 +1,63 @@ +import pytest +from unit.option import option + + +def check_prerequisites(prerequisites): + if 'privileged_user' in prerequisites: + if prerequisites['privileged_user'] and not option.is_privileged: + pytest.skip( + 'privileged user required', + allow_module_level=True, + ) + elif not prerequisites['privileged_user'] and option.is_privileged: + pytest.skip( + 'unprivileged user required', + allow_module_level=True, + ) + + missed = [] + + # check modules + + if 'modules' in prerequisites: + available = option.available['modules'] + + for module in prerequisites['modules']: + if module in available and available[module]: + continue + + missed.append(module) + + if missed: + pytest.skip( + f'Unit has no {", ".join(missed)} module(s)', + allow_module_level=True, + ) + + # check features + + if 'features' in prerequisites: + available = option.available['features'] + require = prerequisites['features'] + + for feature in require: + avail_feature = available[feature] + + if feature in available and avail_feature: + if isinstance(require[feature], list) and isinstance( + avail_feature, dict + ): + avail_keys = avail_feature.keys() + + for key in require[feature]: + if key not in avail_keys: + missed.append(f'{feature}/{key}') + continue + + missed.append(feature) + + if missed: + pytest.skip( + f'{", ".join(missed)} feature(s) not supported', + allow_module_level=True, + ) diff --git a/test/unit/check/chroot.py b/test/unit/check/chroot.py index 1b7aae90..ac5a91d0 100644 --- a/test/unit/check/chroot.py +++ b/test/unit/check/chroot.py @@ -7,26 +7,24 @@ http = TestHTTP() def check_chroot(): - available = option.available - - resp = http.put( - url='/config', - sock_type='unix', - addr=f'{option.temp_dir}/control.unit.sock', - body=json.dumps( - { - "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [ - { - "action": { - "share": option.temp_dir, - "chroot": option.temp_dir, + return ( + 'success' + in http.put( + url='/config', + sock_type='unix', + addr=f'{option.temp_dir}/control.unit.sock', + body=json.dumps( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [ + { + "action": { + "share": option.temp_dir, + "chroot": option.temp_dir, + } } - } - ], - } - ), + ], + } + ), + )['body'] ) - - if 'success' in resp['body']: - available['features']['chroot'] = True diff --git a/test/unit/check/discover_available.py b/test/unit/check/discover_available.py new file mode 100644 index 00000000..0942581b --- /dev/null +++ b/test/unit/check/discover_available.py @@ -0,0 +1,47 @@ +import subprocess +import sys + +from unit.check.chroot import check_chroot +from unit.check.go import check_go +from unit.check.isolation import check_isolation +from unit.check.njs import check_njs +from unit.check.node import check_node +from unit.check.regex import check_regex +from unit.check.tls import check_openssl +from unit.check.unix_abstract import check_unix_abstract +from unit.log import Log +from unit.option import option + + +def discover_available(unit): + output_version = subprocess.check_output( + [unit['unitd'], '--version'], stderr=subprocess.STDOUT + ).decode() + + # wait for controller start + + if Log.wait_for_record(r'controller started') is None: + Log.print_log() + sys.exit("controller didn't start") + + # discover modules from log file + + for module in Log.findall(r'module: ([a-zA-Z]+) (.*) ".*"$'): + versions = option.available['modules'].setdefault(module[0], []) + if module[1] not in versions: + versions.append(module[1]) + + # discover modules using check + + option.available['modules']['go'] = check_go() + option.available['modules']['njs'] = check_njs(output_version) + option.available['modules']['node'] = check_node() + option.available['modules']['openssl'] = check_openssl(output_version) + option.available['modules']['regex'] = check_regex(output_version) + + # Discover features using check. Features should be discovered after + # modules since some features can require modules. + + option.available['features']['chroot'] = check_chroot() + option.available['features']['isolation'] = check_isolation() + option.available['features']['unix_abstract'] = check_unix_abstract() diff --git a/test/unit/check/go.py b/test/unit/check/go.py index 09ae641d..469bef1d 100644 --- a/test/unit/check/go.py +++ b/test/unit/check/go.py @@ -2,5 +2,4 @@ from unit.applications.lang.go import TestApplicationGo def check_go(): - if TestApplicationGo.prepare_env('empty') is not None: - return True + return TestApplicationGo.prepare_env('empty') is not None diff --git a/test/unit/check/isolation.py b/test/unit/check/isolation.py index 829d956b..aaa9b166 100644 --- a/test/unit/check/isolation.py +++ b/test/unit/check/isolation.py @@ -127,7 +127,7 @@ def check_isolation(): } else: - return + return False resp = http.put( url='/config', @@ -137,23 +137,23 @@ def check_isolation(): ) if 'success' not in resp['body']: - return + return False userns = getns('user') if not userns: - return + return False - available['features']['isolation'] = {'user': userns} + isolation = {'user': userns} unp_clone_path = '/proc/sys/kernel/unprivileged_userns_clone' if os.path.exists(unp_clone_path): with open(unp_clone_path, 'r') as f: if str(f.read()).rstrip() == '1': - available['features']['isolation'][ - 'unprivileged_userns_clone' - ] = True + isolation['unprivileged_userns_clone'] = True for ns in allns: ns_value = getns(ns) if ns_value: - available['features']['isolation'][ns] = ns_value + isolation[ns] = ns_value + + return isolation diff --git a/test/unit/check/njs.py b/test/unit/check/njs.py index 433473a1..363a1b62 100644 --- a/test/unit/check/njs.py +++ b/test/unit/check/njs.py @@ -2,5 +2,4 @@ import re def check_njs(output_version): - if re.search('--njs', output_version): - return True + return re.search('--njs', output_version) diff --git a/test/unit/check/node.py b/test/unit/check/node.py index dd59e7a4..6a3d581f 100644 --- a/test/unit/check/node.py +++ b/test/unit/check/node.py @@ -1,10 +1,12 @@ import os import subprocess +from unit.option import option -def check_node(current_dir): - if not os.path.exists(f'{current_dir}/node/node_modules'): - return None + +def check_node(): + if not os.path.exists(f'{option.current_dir}/node/node_modules'): + return False try: v_bytes = subprocess.check_output(['/usr/bin/env', 'node', '-v']) @@ -12,4 +14,4 @@ def check_node(current_dir): return [str(v_bytes, 'utf-8').lstrip('v').rstrip()] except subprocess.CalledProcessError: - return None + return False diff --git a/test/unit/check/regex.py b/test/unit/check/regex.py index 51cf966b..83e93f2d 100644 --- a/test/unit/check/regex.py +++ b/test/unit/check/regex.py @@ -2,7 +2,4 @@ import re def check_regex(output_version): - if re.search('--no-regex', output_version): - return False - - return True + return not re.search('--no-regex', output_version) diff --git a/test/unit/check/tls.py b/test/unit/check/tls.py index 53ce5ffc..9cc2a5f9 100644 --- a/test/unit/check/tls.py +++ b/test/unit/check/tls.py @@ -6,7 +6,6 @@ def check_openssl(output_version): try: subprocess.check_output(['which', 'openssl']) except subprocess.CalledProcessError: - return None + return False - if re.search('--openssl', output_version): - return True + return re.search('--openssl', output_version) diff --git a/test/unit/check/unix_abstract.py b/test/unit/check/unix_abstract.py index aadde43a..780e0212 100644 --- a/test/unit/check/unix_abstract.py +++ b/test/unit/check/unix_abstract.py @@ -7,19 +7,19 @@ http = TestHTTP() def check_unix_abstract(): - available = option.available - - resp = http.put( - url='/config', - sock_type='unix', - addr=f'{option.temp_dir}/control.unit.sock', - body=json.dumps( - { - "listeners": {"unix:@sock": {"pass": "routes"}}, - "routes": [], - } - ), + return ( + 'success' + in http.put( + url='/config', + sock_type='unix', + addr=f'{option.temp_dir}/control.unit.sock', + body=json.dumps( + { + "listeners": { + f'unix:@{option.temp_dir}/sock': {"pass": "routes"} + }, + "routes": [], + } + ), + )['body'] ) - - if 'success' in resp['body']: - available['features']['unix_abstract'] = True diff --git a/test/unit/log.py b/test/unit/log.py index c98054d5..7d7e355a 100644 --- a/test/unit/log.py +++ b/test/unit/log.py @@ -1,6 +1,7 @@ import os import re import sys +import time from unit.option import option @@ -25,7 +26,7 @@ class Log: @print_log_on_assert def check_alerts(log=None): if log is None: - log = Log.read(encoding='utf-8') + log = Log.read() found = False alerts = re.findall(r'.+\[alert\].+', log) @@ -52,11 +53,15 @@ class Log: print('skipped.') @staticmethod + def findall(pattern, name=UNIT_LOG, flags=re.M): + return re.findall(pattern, Log.read(name), flags) + + @staticmethod def get_path(name=UNIT_LOG): return f'{option.temp_dir}/{name}' @staticmethod - def open(name=UNIT_LOG, encoding=None): + def open(name=UNIT_LOG, encoding='utf-8'): file = open(Log.get_path(name), 'r', encoding=encoding, errors='ignore') file.seek(Log.pos.get(name, 0)) @@ -71,7 +76,7 @@ class Log: sys.stdout.flush() if log is None: - log = Log.read(encoding='utf-8') + log = Log.read() sys.stdout.write(log) @@ -93,3 +98,16 @@ class Log: pos = Log.pos.get(UNIT_LOG, 0) Log.pos[UNIT_LOG] = Log.pos.get(name, 0) Log.pos[name] = pos + + @staticmethod + def wait_for_record(pattern, name=UNIT_LOG, wait=150, flags=re.M): + with Log.open(name) as file: + for _ in range(wait): + found = re.search(pattern, file.read(), flags) + + if found is not None: + break + + time.sleep(0.1) + + return found diff --git a/test/unit/option.py b/test/unit/option.py index e00a043a..6bd0c836 100644 --- a/test/unit/option.py +++ b/test/unit/option.py @@ -4,6 +4,7 @@ import platform class Options: _options = { 'architecture': platform.architecture()[0], + 'available': {'modules': {}, 'features': {}}, 'is_privileged': os.geteuid() == 0, 'skip_alerts': [], 'skip_sanitizer': False, |