summaryrefslogtreecommitdiffhomepage
path: root/test/unit
diff options
context:
space:
mode:
authorAndrei Zeliankou <zelenkov@nginx.com>2023-06-12 14:16:59 +0100
committerAndrei Zeliankou <zelenkov@nginx.com>2023-06-12 14:16:59 +0100
commitce2405ec3dd97e8bdf8f63312e3c6ce59ef562d4 (patch)
tree818e60eb10d7f2be90f25003b3a2b347314e966f /test/unit
parenta3b9b49cfb091410ca8f3c8d9df24d1fe184f8e0 (diff)
downloadunit-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.
Diffstat (limited to 'test/unit')
-rw-r--r--test/unit/applications/proto.py3
-rw-r--r--test/unit/check/check_prerequisites.py63
-rw-r--r--test/unit/check/chroot.py40
-rw-r--r--test/unit/check/discover_available.py47
-rw-r--r--test/unit/check/go.py3
-rw-r--r--test/unit/check/isolation.py16
-rw-r--r--test/unit/check/njs.py3
-rw-r--r--test/unit/check/node.py10
-rw-r--r--test/unit/check/regex.py5
-rw-r--r--test/unit/check/tls.py5
-rw-r--r--test/unit/check/unix_abstract.py30
-rw-r--r--test/unit/log.py24
-rw-r--r--test/unit/option.py1
13 files changed, 185 insertions, 65 deletions
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,