summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/conftest.py170
-rw-r--r--test/php/auth/index.php7
-rw-r--r--test/pytest.ini2
-rw-r--r--test/python/path/wsgi.py8
-rw-r--r--test/test_access_log.py46
-rw-r--r--test/test_asgi_application.py6
-rw-r--r--test/test_asgi_lifespan.py4
-rw-r--r--test/test_asgi_websockets.py31
-rw-r--r--test/test_configuration.py4
-rw-r--r--test/test_go_application.py8
-rw-r--r--test/test_go_isolation.py42
-rw-r--r--test/test_go_isolation_rootfs.py1
-rw-r--r--test/test_http_header.py1
-rw-r--r--test/test_java_application.py7
-rw-r--r--test/test_java_isolation_rootfs.py3
-rw-r--r--test/test_java_websockets.py7
-rw-r--r--test/test_node_application.py48
-rw-r--r--test/test_node_websockets.py7
-rw-r--r--test/test_perl_application.py7
-rw-r--r--test/test_php_application.py107
-rw-r--r--test/test_php_basic.py26
-rw-r--r--test/test_php_isolation.py22
-rw-r--r--test/test_php_targets.py2
-rw-r--r--test/test_proxy.py14
-rw-r--r--test/test_proxy_chunked.py4
-rw-r--r--test/test_python_application.py64
-rw-r--r--test/test_python_basic.py24
-rw-r--r--test/test_python_isolation.py76
-rw-r--r--test/test_python_isolation_chroot.py2
-rw-r--r--test/test_python_procman.py5
-rw-r--r--test/test_respawn.py54
-rw-r--r--test/test_routing.py12
-rw-r--r--test/test_ruby_application.py18
-rw-r--r--test/test_ruby_isolation.py47
-rw-r--r--test/test_settings.py2
-rw-r--r--test/test_share_fallback.py5
-rw-r--r--test/test_static.py5
-rw-r--r--test/test_tls.py18
-rw-r--r--test/test_upstreams_rr.py2
-rw-r--r--test/test_usr1.py7
-rw-r--r--test/unit/applications/lang/go.py3
-rw-r--r--test/unit/applications/lang/java.py2
-rw-r--r--test/unit/applications/lang/node.py4
-rw-r--r--test/unit/applications/lang/perl.py2
-rw-r--r--test/unit/applications/lang/php.py2
-rw-r--r--test/unit/applications/lang/python.py2
-rw-r--r--test/unit/applications/lang/ruby.py2
-rw-r--r--test/unit/applications/proto.py2
-rw-r--r--test/unit/applications/tls.py2
-rw-r--r--test/unit/check/isolation.py158
-rw-r--r--test/unit/control.py2
-rw-r--r--test/unit/feature/isolation.py160
-rw-r--r--test/unit/http.py5
-rw-r--r--test/unit/main.py42
-rw-r--r--test/unit/option.py16
-rw-r--r--test/unit/utils.py94
56 files changed, 724 insertions, 699 deletions
diff --git a/test/conftest.py b/test/conftest.py
index 3edc471d..b36aabad 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -4,7 +4,6 @@ import platform
import re
import shutil
import signal
-import socket
import stat
import subprocess
import sys
@@ -13,10 +12,13 @@ import time
from multiprocessing import Process
import pytest
-
from unit.check.go import check_go
+from unit.check.isolation import check_isolation
from unit.check.node import check_node
from unit.check.tls import check_openssl
+from unit.option import option
+from unit.utils import public_dir
+from unit.utils import waitforfiles
def pytest_addoption(parser):
@@ -27,13 +29,13 @@ def pytest_addoption(parser):
help="Detailed output for tests",
)
parser.addoption(
- "--print_log",
+ "--print-log",
default=False,
action="store_true",
help="Print unit.log to stdout in case of errors",
)
parser.addoption(
- "--save_log",
+ "--save-log",
default=False,
action="store_true",
help="Save unit.log after the test execution",
@@ -44,16 +46,24 @@ def pytest_addoption(parser):
action="store_true",
help="Run unsafe tests",
)
+ parser.addoption(
+ "--user",
+ type=str,
+ help="Default user for non-privileged processes of unitd",
+ )
unit_instance = {}
_processes = []
-option = None
-
def pytest_configure(config):
- global option
- option = config.option
+ option.config = config.option
+
+ option.detailed = config.option.detailed
+ option.print_log = config.option.print_log
+ option.save_log = config.option.save_log
+ option.unsafe = config.option.unsafe
+ option.user = config.option.user
option.generated_tests = {}
option.current_dir = os.path.abspath(
@@ -63,16 +73,15 @@ def pytest_configure(config):
option.architecture = platform.architecture()[0]
option.system = platform.system()
+ option.cache_dir = tempfile.mkdtemp(prefix='unit-test-cache-')
+ public_dir(option.cache_dir)
+
# set stdout to non-blocking
if option.detailed or option.print_log:
fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, 0)
-def skip_alert(*alerts):
- option.skip_alerts.extend(alerts)
-
-
def pytest_generate_tests(metafunc):
cls = metafunc.cls
if (not hasattr(cls, 'application_type')
@@ -122,6 +131,7 @@ def pytest_sessionstart(session):
option.available = {'modules': {}, 'features': {}}
unit = unit_run()
+ option.temp_dir = unit['temp_dir']
# read unit.log
@@ -160,8 +170,12 @@ def pytest_sessionstart(session):
k: v for k, v in option.available['modules'].items() if v is not None
}
+ check_isolation()
+
unit_stop()
+ _check_alerts()
+
shutil.rmtree(unit_instance['temp_dir'])
@@ -177,6 +191,40 @@ def pytest_runtest_makereport(item, call):
setattr(item, "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('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(', '.join(missed) + ' feature(s) not supported')
+
+
@pytest.fixture(autouse=True)
def run(request):
unit = unit_run()
@@ -239,26 +287,28 @@ def unit_run():
os.mkdir(temp_dir + '/state')
+ unitd_args = [
+ unitd,
+ '--no-daemon',
+ '--modules',
+ build_dir,
+ '--state',
+ temp_dir + '/state',
+ '--pid',
+ temp_dir + '/unit.pid',
+ '--log',
+ temp_dir + '/unit.log',
+ '--control',
+ 'unix:' + temp_dir + '/control.unit.sock',
+ '--tmp',
+ temp_dir,
+ ]
+
+ if option.user:
+ unitd_args.extend(['--user', option.user])
+
with open(temp_dir + '/unit.log', 'w') as log:
- unit_instance['process'] = subprocess.Popen(
- [
- unitd,
- '--no-daemon',
- '--modules',
- build_dir,
- '--state',
- temp_dir + '/state',
- '--pid',
- temp_dir + '/unit.pid',
- '--log',
- temp_dir + '/unit.log',
- '--control',
- 'unix:' + temp_dir + '/control.unit.sock',
- '--tmp',
- temp_dir,
- ],
- stderr=log,
- )
+ unit_instance['process'] = subprocess.Popen(unitd_args, stderr=log)
if not waitforfiles(temp_dir + '/control.unit.sock'):
_print_log()
@@ -293,34 +343,6 @@ def unit_stop():
p.kill()
return 'Could not terminate unit'
-def public_dir(path):
- os.chmod(path, 0o777)
-
- for root, dirs, files in os.walk(path):
- for d in dirs:
- os.chmod(os.path.join(root, d), 0o777)
- for f in files:
- os.chmod(os.path.join(root, f), 0o777)
-
-def waitforfiles(*files):
- for i in range(50):
- wait = False
- ret = False
-
- for f in files:
- if not os.path.exists(f):
- wait = True
- break
-
- if wait:
- time.sleep(0.1)
-
- else:
- ret = True
- break
-
- return ret
-
def _check_alerts(path=None):
@@ -335,7 +357,7 @@ def _check_alerts(path=None):
alerts = re.findall(r'.+\[alert\].+', log)
if alerts:
- print('All alerts/sanitizer errors found in log:')
+ print('\nAll alerts/sanitizer errors found in log:')
[print(alert) for alert in alerts]
found = True
@@ -399,26 +421,13 @@ def stop_processes():
return 'Fail to stop process(es)'
-def waitforsocket(port):
- ret = False
-
- for i in range(50):
- try:
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.connect(('127.0.0.1', port))
- ret = True
- break
-
- except KeyboardInterrupt:
- raise
-
- except:
- sock.close()
- time.sleep(0.1)
+@pytest.fixture()
+def skip_alert():
+ def _skip(*alerts):
+ option.skip_alerts.extend(alerts)
- sock.close()
+ return _skip
- assert ret, 'socket connected'
@pytest.fixture
def temp_dir(request):
@@ -432,5 +441,10 @@ def is_unsafe(request):
def is_su(request):
return os.geteuid() == 0
+@pytest.fixture
+def unit_pid(request):
+ return unit_instance['process'].pid
+
def pytest_sessionfinish(session):
unit_stop()
+ shutil.rmtree(option.cache_dir)
diff --git a/test/php/auth/index.php b/test/php/auth/index.php
new file mode 100644
index 00000000..d77076d8
--- /dev/null
+++ b/test/php/auth/index.php
@@ -0,0 +1,7 @@
+<?php
+
+header('X-Digest: ' . (isset($_SERVER['PHP_AUTH_DIGEST']) ? $_SERVER['PHP_AUTH_DIGEST'] : 'not set'));
+header('X-User: ' . (isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : 'not set'));
+header('X-Password: ' . (isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : 'not set'));
+
+?>
diff --git a/test/pytest.ini b/test/pytest.ini
index fe86cef2..8952e8c6 100644
--- a/test/pytest.ini
+++ b/test/pytest.ini
@@ -1,3 +1,3 @@
[pytest]
-addopts = -vvv -s --print_log
+addopts = -vvv -s --print-log
python_functions = test_*
diff --git a/test/python/path/wsgi.py b/test/python/path/wsgi.py
new file mode 100644
index 00000000..2807f6ef
--- /dev/null
+++ b/test/python/path/wsgi.py
@@ -0,0 +1,8 @@
+import os
+import sys
+
+def application(environ, start_response):
+ body = os.pathsep.join(sys.path).encode()
+
+ start_response('200', [('Content-Length', str(len(body)))])
+ return [body]
diff --git a/test/test_access_log.py b/test/test_access_log.py
index 511ce6c5..65d5e50a 100644
--- a/test/test_access_log.py
+++ b/test/test_access_log.py
@@ -1,10 +1,8 @@
import time
import pytest
-
-from conftest import option
-from conftest import unit_stop
from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
class TestAccessLog(TestApplicationPython):
@@ -50,8 +48,6 @@ class TestAccessLog(TestApplicationPython):
body='0123456789',
)
- unit_stop()
-
assert (
self.wait_for_record(r'"POST / HTTP/1.1" 200 10') is not None
), 'keepalive 2'
@@ -78,8 +74,6 @@ Connection: close
raw=True,
)
- unit_stop()
-
assert (
self.wait_for_record(r'"GET / HTTP/1.1" 200 0 "Referer-1" "-"')
is not None
@@ -96,12 +90,12 @@ Connection: close
def test_access_log_ipv6(self):
self.load('empty')
- self.conf({"[::1]:7080": {"pass": "applications/empty"}}, 'listeners')
+ assert 'success' in self.conf(
+ {"[::1]:7080": {"pass": "applications/empty"}}, 'listeners'
+ )
self.get(sock_type='ipv6')
- unit_stop()
-
assert (
self.wait_for_record(
r'::1 - - \[.+\] "GET / HTTP/1.1" 200 0 "-" "-"'
@@ -114,14 +108,12 @@ Connection: close
addr = option.temp_dir + '/sock'
- self.conf(
+ assert 'success' in self.conf(
{"unix:" + addr: {"pass": "applications/empty"}}, 'listeners'
)
self.get(sock_type='unix', addr=addr)
- unit_stop()
-
assert (
self.wait_for_record(
r'unix: - - \[.+\] "GET / HTTP/1.1" 200 0 "-" "-"'
@@ -140,8 +132,6 @@ Connection: close
}
)
- unit_stop()
-
assert (
self.wait_for_record(r'"GET / HTTP/1.1" 200 0 "referer-value" "-"')
is not None
@@ -158,8 +148,6 @@ Connection: close
}
)
- unit_stop()
-
assert (
self.wait_for_record(
r'"GET / HTTP/1.1" 200 0 "-" "user-agent-value"'
@@ -172,8 +160,6 @@ Connection: close
self.get(http_10=True)
- unit_stop()
-
assert (
self.wait_for_record(r'"GET / HTTP/1.0" 200 0 "-" "-"') is not None
), 'http 1.0'
@@ -187,8 +173,6 @@ Connection: close
time.sleep(1)
- unit_stop()
-
assert (
self.wait_for_record(r'"GE" 400 0 "-" "-"') is not None
), 'partial'
@@ -200,8 +184,6 @@ Connection: close
self.http(b"""GET /\n""", raw=True)
- unit_stop()
-
assert (
self.wait_for_record(r'"GET /" 400 \d+ "-" "-"') is not None
), 'partial 2'
@@ -215,8 +197,6 @@ Connection: close
time.sleep(1)
- unit_stop()
-
assert (
self.wait_for_record(r'"GET /" 400 0 "-" "-"') is not None
), 'partial 3'
@@ -230,8 +210,6 @@ Connection: close
time.sleep(1)
- unit_stop()
-
assert (
self.wait_for_record(r'"GET / HTTP/1.1" 400 0 "-" "-"') is not None
), 'partial 4'
@@ -244,8 +222,6 @@ Connection: close
self.get(headers={'Connection': 'close'})
- unit_stop()
-
assert (
self.wait_for_record(r'"GET / HTTP/1.1" 400 \d+ "-" "-"')
is not None
@@ -256,8 +232,6 @@ Connection: close
self.get(url='/?blah&var=val')
- unit_stop()
-
assert (
self.wait_for_record(
r'"GET /\?blah&var=val HTTP/1.1" 200 0 "-" "-"'
@@ -268,12 +242,10 @@ Connection: close
def test_access_log_delete(self):
self.load('empty')
- self.conf_delete('access_log')
+ assert 'success' in self.conf_delete('access_log')
self.get(url='/delete')
- unit_stop()
-
assert self.search_in_log(r'/delete', 'access.log') is None, 'delete'
def test_access_log_change(self, temp_dir):
@@ -281,12 +253,12 @@ Connection: close
self.get()
- self.conf('"' + option.temp_dir + '/new.log"', 'access_log')
+ assert 'success' in self.conf(
+ '"' + option.temp_dir + '/new.log"', 'access_log'
+ )
self.get()
- unit_stop()
-
assert (
self.wait_for_record(r'"GET / HTTP/1.1" 200 0 "-" "-"', 'new.log')
is not None
diff --git a/test/test_asgi_application.py b/test/test_asgi_application.py
index e90d78bc..5770265d 100644
--- a/test/test_asgi_application.py
+++ b/test/test_asgi_application.py
@@ -3,10 +3,8 @@ import time
from distutils.version import LooseVersion
import pytest
-
-from conftest import option
-from conftest import skip_alert
from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
class TestASGIApplication(TestApplicationPython):
@@ -361,7 +359,7 @@ Connection: close
self.get(headers=headers_delay_1)
- def test_asgi_application_loading_error(self):
+ def test_asgi_application_loading_error(self, skip_alert):
skip_alert(r'Python failed to import module "blah"')
self.load('empty', module="blah")
diff --git a/test/test_asgi_lifespan.py b/test/test_asgi_lifespan.py
index 3f29c7e7..3ecece43 100644
--- a/test/test_asgi_lifespan.py
+++ b/test/test_asgi_lifespan.py
@@ -2,11 +2,9 @@ import os
from distutils.version import LooseVersion
import pytest
-
-from conftest import option
-from conftest import public_dir
from conftest import unit_stop
from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
class TestASGILifespan(TestApplicationPython):
diff --git a/test/test_asgi_websockets.py b/test/test_asgi_websockets.py
index 54984526..6121fcc5 100644
--- a/test/test_asgi_websockets.py
+++ b/test/test_asgi_websockets.py
@@ -3,11 +3,9 @@ import time
from distutils.version import LooseVersion
import pytest
-
-from conftest import option
-from conftest import skip_alert
from unit.applications.lang.python import TestApplicationPython
from unit.applications.websockets import TestApplicationWebsocket
+from unit.option import option
class TestASGIWebsockets(TestApplicationPython):
@@ -17,7 +15,8 @@ class TestASGIWebsockets(TestApplicationPython):
ws = TestApplicationWebsocket()
- def setup_method(self):
+ @pytest.fixture(autouse=True)
+ def setup_method_fixture(self, request, skip_alert):
assert 'success' in self.conf(
{'http': {'websocket': {'keepalive_interval': 0}}}, 'settings'
), 'clear keepalive_interval'
@@ -64,6 +63,9 @@ class TestASGIWebsockets(TestApplicationPython):
key
), 'key'
+ # remove "mirror" application
+ self.load('websockets/subprotocol')
+
def test_asgi_websockets_subprotocol(self):
self.load('websockets/subprotocol')
@@ -93,6 +95,27 @@ class TestASGIWebsockets(TestApplicationPython):
sock.close()
+ def test_asgi_websockets_mirror_app_change(self):
+ self.load('websockets/mirror')
+
+ message = 'blah'
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, message)
+ frame = self.ws.frame_read(sock)
+
+ assert message == frame['data'].decode('utf-8'), 'mirror'
+
+ self.load('websockets/subprotocol')
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, message)
+ frame = self.ws.frame_read(sock)
+
+ assert message == frame['data'].decode('utf-8'), 'mirror 2'
+
+ sock.close()
+
def test_asgi_websockets_no_mask(self):
self.load('websockets/mirror')
diff --git a/test/test_configuration.py b/test/test_configuration.py
index d1e6f000..b7417264 100644
--- a/test/test_configuration.py
+++ b/test/test_configuration.py
@@ -1,6 +1,4 @@
import pytest
-
-from conftest import skip_alert
from unit.control import TestControl
@@ -337,7 +335,7 @@ class TestConfiguration(TestControl):
assert 'success' in self.conf(conf)
- def test_unprivileged_user_error(self, is_su):
+ def test_unprivileged_user_error(self, is_su, skip_alert):
skip_alert(r'cannot set user "root"', r'failed to apply new conf')
if is_su:
pytest.skip('unprivileged tests')
diff --git a/test/test_go_application.py b/test/test_go_application.py
index 8c77dfc5..e833d190 100644
--- a/test/test_go_application.py
+++ b/test/test_go_application.py
@@ -149,7 +149,7 @@ class TestGoApplication(TestApplicationGo):
arg2 = '--cc-opt=\'-O0 -DNXT_DEBUG_MEMORY=1 -fsanitize=address\''
arg3 = '--debug'
- self.conf(
+ assert 'success' in self.conf(
'["' + arg1 + '", "' + arg2 + '", "' + arg3 + '"]',
'applications/command_line_arguments/arguments',
)
@@ -163,15 +163,15 @@ class TestGoApplication(TestApplicationGo):
args_path = 'applications/command_line_arguments/arguments'
- self.conf('["0", "a", "$", ""]', args_path)
+ assert 'success' in self.conf('["0", "a", "$", ""]', args_path)
assert self.get()['body'] == '0,a,$,', 'arguments'
- self.conf('["-1", "b", "%"]', args_path)
+ assert 'success' in self.conf('["-1", "b", "%"]', args_path)
assert self.get()['body'] == '-1,b,%', 'arguments change'
- self.conf('[]', args_path)
+ assert 'success' in self.conf('[]', args_path)
assert (
self.get()['headers']['Content-Length'] == '0'
diff --git a/test/test_go_isolation.py b/test/test_go_isolation.py
index 8c4a6b9c..e8fa26c6 100644
--- a/test/test_go_isolation.py
+++ b/test/test_go_isolation.py
@@ -1,35 +1,15 @@
import grp
import os
import pwd
-import shutil
import pytest
-
-from conftest import option
-from conftest import unit_run
-from conftest import unit_stop
from unit.applications.lang.go import TestApplicationGo
-from unit.feature.isolation import TestFeatureIsolation
+from unit.option import option
+from unit.utils import getns
class TestGoIsolation(TestApplicationGo):
prerequisites = {'modules': {'go': 'any'}, 'features': ['isolation']}
- isolation = TestFeatureIsolation()
-
- @classmethod
- def setup_class(cls, complete_check=True):
- check = super().setup_class(complete_check=False)
-
- unit = unit_run()
- option.temp_dir = unit['temp_dir']
-
- TestFeatureIsolation().check(option.available, unit['temp_dir'])
-
- assert unit_stop() is None
- shutil.rmtree(unit['temp_dir'])
-
- return check if not complete_check else check()
-
def unpriv_creds(self):
nobody_uid = pwd.getpwnam('nobody').pw_uid
@@ -219,8 +199,8 @@ class TestGoIsolation(TestApplicationGo):
== option.available['features']['isolation'][ns]
), ('%s match' % ns)
- assert obj['NS']['MNT'] != self.isolation.getns('mnt'), 'mnt set'
- assert obj['NS']['USER'] != self.isolation.getns('user'), 'user set'
+ 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'):
@@ -360,16 +340,20 @@ class TestGoIsolation(TestApplicationGo):
'pid': True
}
+ isolation['automount'] = {
+ 'tmpfs': False
+ }
+
self.load('ns_inspect', isolation=isolation)
obj = self.getjson(url='/?mounts=true')['body']
assert (
- "/ /tmp" in obj['Mounts'] and "tmpfs" in obj['Mounts']
- ), 'app has /tmp mounted on /'
+ "/ /tmp" not in obj['Mounts'] and "tmpfs" not in obj['Mounts']
+ ), 'app has no /tmp mounted'
isolation['automount'] = {
- 'tmpfs': False
+ 'tmpfs': True
}
self.load('ns_inspect', isolation=isolation)
@@ -377,5 +361,5 @@ class TestGoIsolation(TestApplicationGo):
obj = self.getjson(url='/?mounts=true')['body']
assert (
- "/ /tmp" not in obj['Mounts'] and "tmpfs" not in obj['Mounts']
- ), 'app has no /tmp mounted'
+ "/ /tmp" in obj['Mounts'] and "tmpfs" in obj['Mounts']
+ ), 'app has /tmp mounted on /'
diff --git a/test/test_go_isolation_rootfs.py b/test/test_go_isolation_rootfs.py
index 1cc59c67..2bded5ec 100644
--- a/test/test_go_isolation_rootfs.py
+++ b/test/test_go_isolation_rootfs.py
@@ -1,7 +1,6 @@
import os
import pytest
-
from unit.applications.lang.go import TestApplicationGo
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_application.py b/test/test_java_application.py
index 41345e87..4a67f291 100644
--- a/test/test_java_application.py
+++ b/test/test_java_application.py
@@ -3,15 +3,14 @@ import os
import re
import time
-from conftest import option
-from conftest import public_dir
-from conftest import skip_alert
from unit.applications.lang.java import TestApplicationJava
+from unit.option import option
+from unit.utils import public_dir
class TestJavaApplication(TestApplicationJava):
prerequisites = {'modules': {'java': 'all'}}
- def test_java_conf_error(self, temp_dir):
+ def test_java_conf_error(self, temp_dir, skip_alert):
skip_alert(
r'realpath.*failed',
r'failed to apply new conf',
diff --git a/test/test_java_isolation_rootfs.py b/test/test_java_isolation_rootfs.py
index 02d35a62..91773981 100644
--- a/test/test_java_isolation_rootfs.py
+++ b/test/test_java_isolation_rootfs.py
@@ -2,9 +2,8 @@ import os
import subprocess
import pytest
-
-from conftest import option
from unit.applications.lang.java import TestApplicationJava
+from unit.option import option
class TestJavaIsolationRootfs(TestApplicationJava):
diff --git a/test/test_java_websockets.py b/test/test_java_websockets.py
index 7586d4aa..315c496d 100644
--- a/test/test_java_websockets.py
+++ b/test/test_java_websockets.py
@@ -2,11 +2,9 @@ import struct
import time
import pytest
-
-from conftest import option
-from conftest import skip_alert
from unit.applications.lang.java import TestApplicationJava
from unit.applications.websockets import TestApplicationWebsocket
+from unit.option import option
class TestJavaWebsockets(TestApplicationJava):
@@ -14,7 +12,8 @@ class TestJavaWebsockets(TestApplicationJava):
ws = TestApplicationWebsocket()
- def setup_method(self):
+ @pytest.fixture(autouse=True)
+ def setup_method_fixture(self, request, skip_alert):
assert 'success' in self.conf(
{'http': {'websocket': {'keepalive_interval': 0}}}, 'settings'
), 'clear keepalive_interval'
diff --git a/test/test_node_application.py b/test/test_node_application.py
index c8c3a444..b277dc3a 100644
--- a/test/test_node_application.py
+++ b/test/test_node_application.py
@@ -1,9 +1,8 @@
import re
import pytest
-
-from conftest import waitforfiles
from unit.applications.lang.node import TestApplicationNode
+from unit.utils import waitforfiles
class TestNodeApplication(TestApplicationNode):
@@ -222,22 +221,6 @@ class TestNodeApplication(TestApplicationNode):
assert 'X-Header' not in headers, 'insensitive'
assert 'X-header' not in headers, 'insensitive 2'
- def test_node_application_promise_handler(self, temp_dir):
- self.load('promise_handler')
-
- assert (
- self.post(
- headers={
- 'Host': 'localhost',
- 'Content-Type': 'text/html',
- 'Connection': 'close',
- },
- body='callback',
- )['status']
- == 200
- ), 'promise handler request'
- assert waitforfiles(temp_dir + '/node/callback'), 'promise handler'
-
def test_node_application_promise_handler_write_after_end(self):
self.load('promise_handler')
@@ -270,35 +253,6 @@ class TestNodeApplication(TestApplicationNode):
), 'promise end request'
assert waitforfiles(temp_dir + '/node/callback'), 'promise end'
- def test_node_application_promise_multiple_calls(self, temp_dir):
- self.load('promise_handler')
-
- self.post(
- headers={
- 'Host': 'localhost',
- 'Content-Type': 'text/html',
- 'Connection': 'close',
- },
- body='callback1',
- )
-
- assert waitforfiles(
- temp_dir + '/node/callback1'
- ), 'promise first call'
-
- self.post(
- headers={
- 'Host': 'localhost',
- 'Content-Type': 'text/html',
- 'Connection': 'close',
- },
- body='callback2',
- )
-
- assert waitforfiles(
- temp_dir + '/node/callback2'
- ), 'promise second call'
-
@pytest.mark.skip('not yet')
def test_node_application_header_name_valid(self):
self.load('header_name_valid')
diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py
index 7b65b5c1..4f1e5906 100644
--- a/test/test_node_websockets.py
+++ b/test/test_node_websockets.py
@@ -2,11 +2,9 @@ import struct
import time
import pytest
-
-from conftest import option
-from conftest import skip_alert
from unit.applications.lang.node import TestApplicationNode
from unit.applications.websockets import TestApplicationWebsocket
+from unit.option import option
class TestNodeWebsockets(TestApplicationNode):
@@ -14,7 +12,8 @@ class TestNodeWebsockets(TestApplicationNode):
ws = TestApplicationWebsocket()
- def setup_method(self):
+ @pytest.fixture(autouse=True)
+ def setup_method_fixture(self, request, skip_alert):
assert 'success' in self.conf(
{'http': {'websocket': {'keepalive_interval': 0}}}, 'settings'
), 'clear keepalive_interval'
diff --git a/test/test_perl_application.py b/test/test_perl_application.py
index 78f2dd90..dfd8be6c 100644
--- a/test/test_perl_application.py
+++ b/test/test_perl_application.py
@@ -1,9 +1,6 @@
import re
import pytest
-
-from conftest import skip_alert
-from conftest import unit_stop
from unit.applications.lang.perl import TestApplicationPerl
@@ -120,8 +117,6 @@ class TestPerlApplication(TestApplicationPerl):
assert self.get()['body'] == '1', 'errors result'
- unit_stop()
-
assert (
self.wait_for_record(r'\[error\].+Error in application')
is not None
@@ -170,7 +165,7 @@ class TestPerlApplication(TestApplicationPerl):
assert self.get()['body'] == 'body\n', 'body io file'
@pytest.mark.skip('not yet')
- def test_perl_application_syntax_error(self):
+ def test_perl_application_syntax_error(self, skip_alert):
skip_alert(r'PSGI: Failed to parse script')
self.load('syntax_error')
diff --git a/test/test_php_application.py b/test/test_php_application.py
index 578de0b7..e73c67ba 100644
--- a/test/test_php_application.py
+++ b/test/test_php_application.py
@@ -2,12 +2,11 @@ import os
import re
import shutil
import time
+from subprocess import call
import pytest
-
-from conftest import option
-from conftest import unit_stop
from unit.applications.lang.php import TestApplicationPHP
+from unit.option import option
class TestPHPApplication(TestApplicationPHP):
prerequisites = {'modules': {'php': 'all'}}
@@ -20,7 +19,7 @@ class TestPHPApplication(TestApplicationPHP):
def set_opcache(self, app, val):
assert 'success' in self.conf(
- {"admin": {"opcache.enable": val, "opcache.enable_cli": val,},},
+ {"admin": {"opcache.enable": val, "opcache.enable_cli": val}},
'applications/' + app + '/options',
)
@@ -99,7 +98,10 @@ class TestPHPApplication(TestApplicationPHP):
assert self.get()['body'] == '0123'
- unit_stop()
+ with open(temp_dir + '/unit.pid', 'r') as f:
+ pid = f.read().rstrip()
+
+ call(['kill', '-s', 'USR1', pid])
with open(temp_dir + '/unit.log', 'r', errors='ignore') as f:
errs = re.findall(r'Error in fastcgi_finish_request', f.read())
@@ -113,7 +115,10 @@ class TestPHPApplication(TestApplicationPHP):
assert resp['status'] == 200
assert resp['body'] == ''
- unit_stop()
+ with open(temp_dir + '/unit.pid', 'r') as f:
+ pid = f.read().rstrip()
+
+ call(['kill', '-s', 'USR1', pid])
with open(temp_dir + '/unit.log', 'r', errors='ignore') as f:
errs = re.findall(r'Error in fastcgi_finish_request', f.read())
@@ -263,7 +268,7 @@ class TestPHPApplication(TestApplicationPHP):
assert self.get()['headers']['X-Precision'] != '4', 'ini value default'
- self.conf(
+ assert 'success' in self.conf(
{"file": "ini/php.ini"}, 'applications/ini_precision/options'
)
@@ -285,7 +290,7 @@ class TestPHPApplication(TestApplicationPHP):
def test_php_application_ini_admin(self):
self.load('ini_precision')
- self.conf(
+ assert 'success' in self.conf(
{"file": "php.ini", "admin": {"precision": "5"}},
'applications/ini_precision/options',
)
@@ -295,7 +300,7 @@ class TestPHPApplication(TestApplicationPHP):
def test_php_application_ini_user(self):
self.load('ini_precision')
- self.conf(
+ assert 'success' in self.conf(
{"file": "php.ini", "user": {"precision": "5"}},
'applications/ini_precision/options',
)
@@ -305,13 +310,13 @@ class TestPHPApplication(TestApplicationPHP):
def test_php_application_ini_user_2(self):
self.load('ini_precision')
- self.conf(
+ assert 'success' in self.conf(
{"file": "ini/php.ini"}, 'applications/ini_precision/options'
)
assert self.get()['headers']['X-Precision'] == '4', 'ini user file'
- self.conf(
+ assert 'success' in self.conf(
{"precision": "5"}, 'applications/ini_precision/options/user'
)
@@ -320,7 +325,7 @@ class TestPHPApplication(TestApplicationPHP):
def test_php_application_ini_set_admin(self):
self.load('ini_precision')
- self.conf(
+ assert 'success' in self.conf(
{"admin": {"precision": "5"}}, 'applications/ini_precision/options'
)
@@ -331,7 +336,7 @@ class TestPHPApplication(TestApplicationPHP):
def test_php_application_ini_set_user(self):
self.load('ini_precision')
- self.conf(
+ assert 'success' in self.conf(
{"user": {"precision": "5"}}, 'applications/ini_precision/options'
)
@@ -342,7 +347,7 @@ class TestPHPApplication(TestApplicationPHP):
def test_php_application_ini_repeat(self):
self.load('ini_precision')
- self.conf(
+ assert 'success' in self.conf(
{"user": {"precision": "5"}}, 'applications/ini_precision/options'
)
@@ -355,7 +360,7 @@ class TestPHPApplication(TestApplicationPHP):
self.before_disable_functions()
- self.conf(
+ assert 'success' in self.conf(
{"admin": {"disable_functions": "exec"}},
'applications/time_exec/options',
)
@@ -370,7 +375,7 @@ class TestPHPApplication(TestApplicationPHP):
self.before_disable_functions()
- self.conf(
+ assert 'success' in self.conf(
{"admin": {"disable_functions": "exec,time"}},
'applications/time_exec/options',
)
@@ -384,12 +389,70 @@ class TestPHPApplication(TestApplicationPHP):
r'exec: \/\w+', body
), 'disable_functions comma exec'
+ def test_php_application_auth(self):
+ self.load('auth')
+
+ resp = self.get()
+ assert resp['status'] == 200, 'status'
+ assert resp['headers']['X-Digest'] == 'not set', 'digest'
+ assert resp['headers']['X-User'] == 'not set', 'user'
+ assert resp['headers']['X-Password'] == 'not set', 'password'
+
+ resp = self.get(
+ headers={
+ 'Host': 'localhost',
+ 'Authorization': 'Basic dXNlcjpwYXNzd29yZA==',
+ 'Connection': 'close',
+ }
+ )
+ assert resp['status'] == 200, 'basic status'
+ assert resp['headers']['X-Digest'] == 'not set', 'basic digest'
+ assert resp['headers']['X-User'] == 'user', 'basic user'
+ assert resp['headers']['X-Password'] == 'password', 'basic password'
+
+ resp = self.get(
+ headers={
+ 'Host': 'localhost',
+ 'Authorization': 'Digest username="blah", realm="", uri="/"',
+ 'Connection': 'close',
+ }
+ )
+ assert resp['status'] == 200, 'digest status'
+ assert (
+ resp['headers']['X-Digest'] == 'username="blah", realm="", uri="/"'
+ ), 'digest digest'
+ assert resp['headers']['X-User'] == 'not set', 'digest user'
+ assert resp['headers']['X-Password'] == 'not set', 'digest password'
+
+ def test_php_application_auth_invalid(self):
+ self.load('auth')
+
+ def check_auth(auth):
+ resp = self.get(headers={
+ 'Host': 'localhost',
+ 'Authorization': auth,
+ 'Connection': 'close',
+ })
+
+ assert resp['status'] == 200, 'status'
+ assert resp['headers']['X-Digest'] == 'not set', 'Digest'
+ assert resp['headers']['X-User'] == 'not set', 'User'
+ assert resp['headers']['X-Password'] == 'not set', 'Password'
+
+ check_auth('Basic dXN%cjpwYXNzd29yZA==')
+ check_auth('Basic XNlcjpwYXNzd29yZA==')
+ check_auth('Basic DdXNlcjpwYXNzd29yZA==')
+ check_auth('Basic blah')
+ check_auth('Basic')
+ check_auth('Digest')
+ check_auth('blah')
+
def test_php_application_disable_functions_space(self):
self.load('time_exec')
self.before_disable_functions()
- self.conf(
+ assert 'success' in self.conf(
{"admin": {"disable_functions": "exec time"}},
'applications/time_exec/options',
)
@@ -408,7 +471,7 @@ class TestPHPApplication(TestApplicationPHP):
self.before_disable_functions()
- self.conf(
+ assert 'success' in self.conf(
{"user": {"disable_functions": "exec"}},
'applications/time_exec/options',
)
@@ -425,7 +488,7 @@ class TestPHPApplication(TestApplicationPHP):
self.before_disable_functions()
- self.conf(
+ assert 'success' in self.conf(
{"admin": {"disable_functions": "blah"}},
'applications/time_exec/options',
)
@@ -446,7 +509,7 @@ class TestPHPApplication(TestApplicationPHP):
r'012345', self.get()['body']
), 'disable_classes before'
- self.conf(
+ assert 'success' in self.conf(
{"admin": {"disable_classes": "DateTime"}},
'applications/date_time/options',
)
@@ -462,7 +525,7 @@ class TestPHPApplication(TestApplicationPHP):
r'012345', self.get()['body']
), 'disable_classes before'
- self.conf(
+ assert 'success' in self.conf(
{"user": {"disable_classes": "DateTime"}},
'applications/date_time/options',
)
@@ -480,8 +543,6 @@ class TestPHPApplication(TestApplicationPHP):
assert self.get()['status'] == 200, 'status 2'
- unit_stop()
-
pattern = r'\d{4}\/\d\d\/\d\d\s\d\d:.+\[notice\].+Error in application'
assert self.wait_for_record(pattern) is not None, 'errors print'
diff --git a/test/test_php_basic.py b/test/test_php_basic.py
index 1420ec21..bcd66173 100644
--- a/test/test_php_basic.py
+++ b/test/test_php_basic.py
@@ -19,7 +19,7 @@ class TestPHPBasic(TestControl):
}
def test_php_get_applications(self):
- self.conf(self.conf_app, 'applications')
+ assert 'success' in self.conf(self.conf_app, 'applications')
conf = self.conf_get()
@@ -55,7 +55,7 @@ class TestPHPBasic(TestControl):
), 'spare processes'
def test_php_get_listeners(self):
- self.conf(self.conf_basic)
+ assert 'success' in self.conf(self.conf_basic)
assert self.conf_get()['listeners'] == {
"*:7080": {"pass": "applications/app"}
@@ -70,16 +70,20 @@ class TestPHPBasic(TestControl):
}, 'listeners prefix 2'
def test_php_change_listener(self):
- self.conf(self.conf_basic)
- self.conf({"*:7081": {"pass": "applications/app"}}, 'listeners')
+ assert 'success' in self.conf(self.conf_basic)
+ assert 'success' in self.conf(
+ {"*:7081": {"pass": "applications/app"}}, 'listeners'
+ )
assert self.conf_get('listeners') == {
"*:7081": {"pass": "applications/app"}
}, 'change listener'
def test_php_add_listener(self):
- self.conf(self.conf_basic)
- self.conf({"pass": "applications/app"}, 'listeners/*:7082')
+ assert 'success' in self.conf(self.conf_basic)
+ assert 'success' in self.conf(
+ {"pass": "applications/app"}, 'listeners/*:7082'
+ )
assert self.conf_get('listeners') == {
"*:7080": {"pass": "applications/app"},
@@ -87,20 +91,20 @@ class TestPHPBasic(TestControl):
}, 'add listener'
def test_php_change_application(self):
- self.conf(self.conf_basic)
+ assert 'success' in self.conf(self.conf_basic)
- self.conf('30', 'applications/app/processes/max')
+ assert 'success' in self.conf('30', 'applications/app/processes/max')
assert (
self.conf_get('applications/app/processes/max') == 30
), 'change application max'
- self.conf('"/www"', 'applications/app/root')
+ assert 'success' in self.conf('"/www"', 'applications/app/root')
assert (
self.conf_get('applications/app/root') == '/www'
), 'change application root'
def test_php_delete(self):
- self.conf(self.conf_basic)
+ assert 'success' in self.conf(self.conf_basic)
assert 'error' in self.conf_delete('applications/app')
assert 'success' in self.conf_delete('listeners/*:7080')
@@ -108,7 +112,7 @@ class TestPHPBasic(TestControl):
assert 'error' in self.conf_delete('applications/app')
def test_php_delete_blocks(self):
- self.conf(self.conf_basic)
+ assert 'success' in self.conf(self.conf_basic)
assert 'success' in self.conf_delete('listeners')
assert 'success' in self.conf_delete('applications')
diff --git a/test/test_php_isolation.py b/test/test_php_isolation.py
index cc660e04..2e458023 100644
--- a/test/test_php_isolation.py
+++ b/test/test_php_isolation.py
@@ -1,31 +1,11 @@
-import shutil
-
import pytest
-
-from conftest import option
-from conftest import unit_run
-from conftest import unit_stop
from unit.applications.lang.php import TestApplicationPHP
-from unit.feature.isolation import TestFeatureIsolation
+from unit.option import option
class TestPHPIsolation(TestApplicationPHP):
prerequisites = {'modules': {'php': 'any'}, 'features': ['isolation']}
- @classmethod
- def setup_class(cls, complete_check=True):
- check = super().setup_class(complete_check=False)
-
- unit = unit_run()
- option.temp_dir = unit['temp_dir']
-
- TestFeatureIsolation().check(option.available, unit['temp_dir'])
-
- assert unit_stop() is None
- shutil.rmtree(unit['temp_dir'])
-
- return check if not complete_check else check()
-
def test_php_isolation_rootfs(self, is_su, temp_dir):
isolation_features = option.available['features']['isolation'].keys()
diff --git a/test/test_php_targets.py b/test/test_php_targets.py
index e64cd6b6..76326131 100644
--- a/test/test_php_targets.py
+++ b/test/test_php_targets.py
@@ -1,5 +1,5 @@
-from conftest import option
from unit.applications.lang.php import TestApplicationPHP
+from unit.option import option
class TestPHPTargets(TestApplicationPHP):
diff --git a/test/test_proxy.py b/test/test_proxy.py
index be3e93fd..2d305e98 100644
--- a/test/test_proxy.py
+++ b/test/test_proxy.py
@@ -3,12 +3,10 @@ import socket
import time
import pytest
-
-from conftest import option
from conftest import run_process
-from conftest import skip_alert
-from conftest import waitforsocket
from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
+from unit.utils import waitforsocket
class TestProxy(TestApplicationPython):
@@ -174,7 +172,9 @@ Content-Length: 10
assert resp['status'] == 200, 'status'
assert resp['body'] == payload, 'body'
- self.conf({'http': {'max_body_size': 32 * 1024 * 1024}}, 'settings')
+ assert 'success' in self.conf(
+ {'http': {'max_body_size': 32 * 1024 * 1024}}, 'settings'
+ )
payload = '0123456789abcdef' * 32 * 64 * 1024
resp = self.post_http10(body=payload, read_buffer_size=1024 * 1024)
@@ -482,13 +482,13 @@ Content-Length: 10
check_proxy('http://[:]:7080')
check_proxy('http://[::7080')
- def test_proxy_loop(self):
+ def test_proxy_loop(self, skip_alert):
skip_alert(
r'socket.*failed',
r'accept.*failed',
r'new connections are not accepted',
)
- self.conf(
+ assert 'success' in self.conf(
{
"listeners": {
"*:7080": {"pass": "routes"},
diff --git a/test/test_proxy_chunked.py b/test/test_proxy_chunked.py
index ae2228fa..73d94332 100644
--- a/test/test_proxy_chunked.py
+++ b/test/test_proxy_chunked.py
@@ -3,10 +3,10 @@ import select
import socket
import time
-from conftest import option
from conftest import run_process
-from conftest import waitforsocket
from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
+from unit.utils import waitforsocket
class TestProxyChunked(TestApplicationPython):
diff --git a/test/test_python_application.py b/test/test_python_application.py
index 83b0c8f4..709df3ff 100644
--- a/test/test_python_application.py
+++ b/test/test_python_application.py
@@ -1,14 +1,12 @@
import grp
+import os
import pwd
import re
import time
import pytest
-
-from conftest import option
-from conftest import skip_alert
-from conftest import unit_stop
from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
class TestPythonApplication(TestApplicationPython):
@@ -156,9 +154,7 @@ custom-header: BLAH
assert resp['status'] == 200, 'ctx iter status'
assert resp['body'] == '0123456789', 'ctx iter body'
- self.conf({"listeners": {}, "applications": {}})
-
- unit_stop()
+ assert 'success' in self.conf({"listeners": {}, "applications": {}})
assert (
self.wait_for_record(r'RuntimeError') is not None
@@ -336,9 +332,7 @@ Connection: close
self.get()
- self.conf({"listeners": {}, "applications": {}})
-
- unit_stop()
+ assert 'success' in self.conf({"listeners": {}, "applications": {}})
assert self.wait_for_record(r'At exit called\.') is not None, 'atexit'
@@ -497,8 +491,6 @@ last line: 987654321
self.get()
- unit_stop()
-
assert (
self.wait_for_record(r'\[error\].+Error in application\.')
is not None
@@ -520,13 +512,13 @@ last line: 987654321
assert self.get()['body'] == 'body\n', 'body io file'
@pytest.mark.skip('not yet')
- def test_python_application_syntax_error(self):
+ def test_python_application_syntax_error(self, skip_alert):
skip_alert(r'Python failed to import module "wsgi"')
self.load('syntax_error')
assert self.get()['status'] == 500, 'syntax error'
- def test_python_application_loading_error(self):
+ def test_python_application_loading_error(self, skip_alert):
skip_alert(r'Python failed to import module "blah"')
self.load('empty', module="blah")
@@ -538,8 +530,6 @@ last line: 987654321
self.get()
- unit_stop()
-
assert self.wait_for_record(r'Close called\.') is not None, 'close'
def test_python_application_close_error(self):
@@ -547,8 +537,6 @@ last line: 987654321
self.get()
- unit_stop()
-
assert (
self.wait_for_record(r'Close called\.') is not None
), 'close error'
@@ -558,8 +546,6 @@ last line: 987654321
self.get()
- unit_stop()
-
assert (
self.wait_for_record(
r'\[error\].+the application returned not an iterable object'
@@ -791,7 +777,7 @@ last line: 987654321
assert obj['UID'] == nobody_uid, 'root uid group=root'
assert obj['GID'] == 0, 'root gid group=root'
- def test_python_application_callable(self):
+ def test_python_application_callable(self, skip_alert):
skip_alert(r'Python failed to get "blah" from module')
self.load('callable')
@@ -805,6 +791,42 @@ last line: 987654321
assert self.get()['status'] not in [200, 204], 'callable response inv'
+ def test_python_application_path(self):
+ self.load('path')
+
+ def set_path(path):
+ assert 'success' in self.conf(path, 'applications/path/path')
+
+ def get_path():
+ return self.get()['body'].split(os.pathsep)
+
+ default_path = self.conf_get('/config/applications/path/path')
+ assert 'success' in self.conf(
+ {"PYTHONPATH": default_path},
+ '/config/applications/path/environment',
+ )
+
+ self.conf_delete('/config/applications/path/path')
+ sys_path = get_path()
+
+ set_path('"/blah"')
+ assert ['/blah', *sys_path] == get_path(), 'check path'
+
+ set_path('"/new"')
+ assert ['/new', *sys_path] == get_path(), 'check path update'
+
+ set_path('["/blah1", "/blah2"]')
+ assert ['/blah1', '/blah2', *sys_path] == get_path(), 'check path array'
+
+ def test_python_application_path_invalid(self):
+ self.load('path')
+
+ def check_path(path):
+ assert 'error' in self.conf(path, 'applications/path/path')
+
+ check_path('{}')
+ check_path('["/blah", []]')
+
def test_python_application_threads(self):
self.load('threads', threads=4)
diff --git a/test/test_python_basic.py b/test/test_python_basic.py
index 0cc70e51..e661a89c 100644
--- a/test/test_python_basic.py
+++ b/test/test_python_basic.py
@@ -58,7 +58,7 @@ class TestPythonBasic(TestControl):
assert self.conf_get('applications/app/processes/spare') == 0, 'spare'
def test_python_get_listeners(self):
- self.conf(self.conf_basic)
+ assert 'success' in self.conf(self.conf_basic)
assert self.conf_get()['listeners'] == {
"*:7080": {"pass": "applications/app"}
@@ -73,16 +73,20 @@ class TestPythonBasic(TestControl):
}, 'listeners prefix 2'
def test_python_change_listener(self):
- self.conf(self.conf_basic)
- self.conf({"*:7081": {"pass": "applications/app"}}, 'listeners')
+ assert 'success' in self.conf(self.conf_basic)
+ assert 'success' in self.conf(
+ {"*:7081": {"pass": "applications/app"}}, 'listeners'
+ )
assert self.conf_get('listeners') == {
"*:7081": {"pass": "applications/app"}
}, 'change listener'
def test_python_add_listener(self):
- self.conf(self.conf_basic)
- self.conf({"pass": "applications/app"}, 'listeners/*:7082')
+ assert 'success' in self.conf(self.conf_basic)
+ assert 'success' in self.conf(
+ {"pass": "applications/app"}, 'listeners/*:7082'
+ )
assert self.conf_get('listeners') == {
"*:7080": {"pass": "applications/app"},
@@ -90,20 +94,20 @@ class TestPythonBasic(TestControl):
}, 'add listener'
def test_python_change_application(self):
- self.conf(self.conf_basic)
+ assert 'success' in self.conf(self.conf_basic)
- self.conf('30', 'applications/app/processes/max')
+ assert 'success' in self.conf('30', 'applications/app/processes/max')
assert (
self.conf_get('applications/app/processes/max') == 30
), 'change application max'
- self.conf('"/www"', 'applications/app/path')
+ assert 'success' in self.conf('"/www"', 'applications/app/path')
assert (
self.conf_get('applications/app/path') == '/www'
), 'change application path'
def test_python_delete(self):
- self.conf(self.conf_basic)
+ assert 'success' in self.conf(self.conf_basic)
assert 'error' in self.conf_delete('applications/app')
assert 'success' in self.conf_delete('listeners/*:7080')
@@ -111,7 +115,7 @@ class TestPythonBasic(TestControl):
assert 'error' in self.conf_delete('applications/app')
def test_python_delete_blocks(self):
- self.conf(self.conf_basic)
+ assert 'success' in self.conf(self.conf_basic)
assert 'success' in self.conf_delete('listeners')
assert 'success' in self.conf_delete('applications')
diff --git a/test/test_python_isolation.py b/test/test_python_isolation.py
index 1a157528..680f2c03 100644
--- a/test/test_python_isolation.py
+++ b/test/test_python_isolation.py
@@ -1,31 +1,15 @@
-import shutil
import pytest
-
-from conftest import option
-from conftest import unit_run
-from conftest import unit_stop
from unit.applications.lang.python import TestApplicationPython
-from unit.feature.isolation import TestFeatureIsolation
+from unit.option import option
+from unit.utils import findmnt
+from unit.utils import waitformount
+from unit.utils import waitforunmount
class TestPythonIsolation(TestApplicationPython):
prerequisites = {'modules': {'python': 'any'}, 'features': ['isolation']}
- @classmethod
- def setup_class(cls, complete_check=True):
- check = super().setup_class(complete_check=False)
-
- unit = unit_run()
- option.temp_dir = unit['temp_dir']
-
- TestFeatureIsolation().check(option.available, unit['temp_dir'])
-
- assert unit_stop() is None
- shutil.rmtree(unit['temp_dir'])
-
- return check if not complete_check else check()
-
def test_python_isolation_rootfs(self, is_su, temp_dir):
isolation_features = option.available['features']['isolation'].keys()
@@ -79,39 +63,51 @@ class TestPythonIsolation(TestApplicationPython):
), 'application exists in rootfs'
def test_python_isolation_rootfs_no_language_deps(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')
+ pytest.skip('requires root')
isolation = {
'rootfs': temp_dir,
'automount': {'language_deps': False}
}
- if not is_su:
- isolation['namespaces'] = {
- 'mount': True,
- 'credential': True,
- 'pid': True
- }
-
self.load('empty', isolation=isolation)
+ assert findmnt().find(temp_dir) == -1
assert (self.get()['status'] != 200), 'disabled language_deps'
+ assert findmnt().find(temp_dir) == -1
isolation['automount']['language_deps'] = True
self.load('empty', isolation=isolation)
+ assert findmnt().find(temp_dir) == -1
assert (self.get()['status'] == 200), 'enabled language_deps'
+ assert waitformount(temp_dir), 'language_deps mount'
+
+ self.conf({"listeners": {}, "applications": {}})
+
+ assert waitforunmount(temp_dir), 'language_deps unmount'
+
+ def test_python_isolation_procfs(self, is_su, temp_dir):
+ isolation_features = option.available['features']['isolation'].keys()
+
+ if not is_su:
+ pytest.skip('requires root')
+
+ isolation = {'rootfs': temp_dir, 'automount': {'procfs': False}}
+
+ self.load('ns_inspect', isolation=isolation)
+
+ assert (
+ self.getjson(url='/?path=/proc/self')['body']['FileExists']
+ == False
+ ), 'no /proc/self'
+
+ isolation['automount']['procfs'] = True
+
+ self.load('ns_inspect', isolation=isolation)
+
+ assert (
+ self.getjson(url='/?path=/proc/self')['body']['FileExists'] == True
+ ), '/proc/self'
diff --git a/test/test_python_isolation_chroot.py b/test/test_python_isolation_chroot.py
index 8018d5b9..7281f5f6 100644
--- a/test/test_python_isolation_chroot.py
+++ b/test/test_python_isolation_chroot.py
@@ -1,7 +1,5 @@
import pytest
-
from unit.applications.lang.python import TestApplicationPython
-from unit.feature.isolation import TestFeatureIsolation
class TestPythonIsolation(TestApplicationPython):
diff --git a/test/test_python_procman.py b/test/test_python_procman.py
index ff914fc8..ac403ce4 100644
--- a/test/test_python_procman.py
+++ b/test/test_python_procman.py
@@ -3,9 +3,8 @@ import subprocess
import time
import pytest
-
-from conftest import option
from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
class TestPythonProcman(TestApplicationPython):
@@ -198,6 +197,6 @@ class TestPythonProcman(TestApplicationPython):
), 'max zero'
def stop_all(self):
- self.conf({"listeners": {}, "applications": {}})
+ assert 'success' in self.conf({"listeners": {}, "applications": {}})
assert len(self.pids_for_process()) == 0, 'stop all'
diff --git a/test/test_respawn.py b/test/test_respawn.py
index 09a806d4..ed85ee95 100644
--- a/test/test_respawn.py
+++ b/test/test_respawn.py
@@ -2,9 +2,8 @@ import re
import subprocess
import time
-from conftest import option
-from conftest import skip_alert
from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
class TestRespawn(TestApplicationPython):
@@ -22,17 +21,17 @@ class TestRespawn(TestApplicationPython):
'1', 'applications/' + self.app_name + '/processes'
)
- def pid_by_name(self, name):
- output = subprocess.check_output(['ps', 'ax']).decode()
- m = re.search(r'\s*(\d+).*' + name, output)
- return m if m is None else m.group(1)
+ def pid_by_name(self, name, ppid):
+ output = subprocess.check_output(['ps', 'ax', '-O', 'ppid']).decode()
+ m = re.search(r'\s*(\d+)\s*' + str(ppid) + r'.*' + name, output)
+ return None if m is None else m.group(1)
def kill_pids(self, *pids):
subprocess.call(['kill', '-9'] + list(pids))
- def wait_for_process(self, process):
+ def wait_for_process(self, process, unit_pid):
for i in range(50):
- found = self.pid_by_name(process)
+ found = self.pid_by_name(process, unit_pid)
if found is not None:
break
@@ -41,7 +40,10 @@ class TestRespawn(TestApplicationPython):
return found
- def smoke_test(self):
+ def find_proc(self, name, ppid, ps_output):
+ return re.findall(str(ppid) + r'.*' + name, ps_output)
+
+ def smoke_test(self, unit_pid):
for _ in range(5):
assert 'success' in self.conf(
'1', 'applications/' + self.app_name + '/processes'
@@ -51,39 +53,41 @@ class TestRespawn(TestApplicationPython):
# Check if the only one router, controller,
# and application processes running.
- output = subprocess.check_output(['ps', 'ax']).decode()
- assert len(re.findall(self.PATTERN_ROUTER, output)) == 1
- assert len(re.findall(self.PATTERN_CONTROLLER, output)) == 1
- assert len(re.findall(self.app_name, output)) == 1
+ out = subprocess.check_output(['ps', 'ax', '-O', 'ppid']).decode()
+ assert len(self.find_proc(self.PATTERN_ROUTER, unit_pid, out)) == 1
+ assert len(self.find_proc(self.PATTERN_CONTROLLER, unit_pid, out)) == 1
+ assert len(self.find_proc(self.app_name, unit_pid, out)) == 1
- def test_respawn_router(self):
- pid = self.pid_by_name(self.PATTERN_ROUTER)
+ def test_respawn_router(self, skip_alert, unit_pid):
+ pid = self.pid_by_name(self.PATTERN_ROUTER, unit_pid)
self.kill_pids(pid)
skip_alert(r'process %s exited on signal 9' % pid)
- assert self.wait_for_process(self.PATTERN_ROUTER) is not None
+ assert self.wait_for_process(self.PATTERN_ROUTER, unit_pid) is not None
- self.smoke_test()
+ self.smoke_test(unit_pid)
- def test_respawn_controller(self):
- pid = self.pid_by_name(self.PATTERN_CONTROLLER)
+ def test_respawn_controller(self, skip_alert, unit_pid):
+ pid = self.pid_by_name(self.PATTERN_CONTROLLER, unit_pid)
self.kill_pids(pid)
skip_alert(r'process %s exited on signal 9' % pid)
- assert self.wait_for_process(self.PATTERN_CONTROLLER) is not None
+ assert self.wait_for_process(
+ self.PATTERN_CONTROLLER, unit_pid
+ ) is not None
assert self.get()['status'] == 200
- self.smoke_test()
+ self.smoke_test(unit_pid)
- def test_respawn_application(self):
- pid = self.pid_by_name(self.app_name)
+ def test_respawn_application(self, skip_alert, unit_pid):
+ pid = self.pid_by_name(self.app_name, unit_pid)
self.kill_pids(pid)
skip_alert(r'process %s exited on signal 9' % pid)
- assert self.wait_for_process(self.app_name) is not None
+ assert self.wait_for_process(self.app_name, unit_pid) is not None
- self.smoke_test()
+ self.smoke_test(unit_pid)
diff --git a/test/test_routing.py b/test/test_routing.py
index 83852273..4d27cb61 100644
--- a/test/test_routing.py
+++ b/test/test_routing.py
@@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
import pytest
-
-from conftest import option
-from conftest import skip_alert
from unit.applications.proto import TestApplicationProto
+from unit.option import option
class TestRouting(TestApplicationProto):
@@ -318,7 +316,7 @@ class TestRouting(TestApplicationProto):
check_pass_error("%1", "%1")
def test_routes_absent(self):
- self.conf(
+ assert 'success' in self.conf(
{
"listeners": {"*:7081": {"pass": "applications/empty"}},
"applications": {
@@ -366,7 +364,7 @@ class TestRouting(TestApplicationProto):
assert self.get()['status'] == 200, 'route match absent'
- def test_routes_route_action_absent(self):
+ def test_routes_route_action_absent(self, skip_alert):
skip_alert(r'failed to apply new conf')
assert 'error' in self.conf(
@@ -755,7 +753,7 @@ class TestRouting(TestApplicationProto):
'routes/main'
), 'route edit configure 9'
- def test_match_edit(self):
+ def test_match_edit(self, skip_alert):
skip_alert(r'failed to apply new conf')
self.route_match({"method": ["GET", "POST"]})
@@ -1352,7 +1350,7 @@ class TestRouting(TestApplicationProto):
assert self.get(url='/?var2=val2')['status'] == 404, 'arr 7'
assert self.get(url='/?var3=foo')['status'] == 200, 'arr 8'
- def test_routes_match_arguments_invalid(self):
+ def test_routes_match_arguments_invalid(self, skip_alert):
# TODO remove it after controller fixed
skip_alert(r'failed to apply new conf')
diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py
index e42fb97f..b18a6cee 100644
--- a/test/test_ruby_application.py
+++ b/test/test_ruby_application.py
@@ -1,9 +1,6 @@
import re
import pytest
-
-from conftest import skip_alert
-from conftest import unit_stop
from unit.applications.lang.ruby import TestApplicationRuby
@@ -160,7 +157,7 @@ class TestRubyApplication(TestApplicationRuby):
assert self.post(body=body)['body'] == body, 'input rewind'
@pytest.mark.skip('not yet')
- def test_ruby_application_syntax_error(self):
+ def test_ruby_application_syntax_error(self, skip_alert):
skip_alert(
r'Failed to parse rack script',
r'syntax error',
@@ -176,8 +173,6 @@ class TestRubyApplication(TestApplicationRuby):
self.get()
- unit_stop()
-
assert (
self.wait_for_record(r'\[error\].+Error in application')
is not None
@@ -188,8 +183,6 @@ class TestRubyApplication(TestApplicationRuby):
self.get()
- unit_stop()
-
assert (
self.wait_for_record(r'\[error\].+1234567890') is not None
), 'errors puts int'
@@ -199,8 +192,6 @@ class TestRubyApplication(TestApplicationRuby):
self.get()
- unit_stop()
-
assert (
self.wait_for_record(r'\[error\].+Error in application')
is not None
@@ -216,7 +207,6 @@ class TestRubyApplication(TestApplicationRuby):
self.get()
- unit_stop()
assert (
self.wait_for_record(r'\[error\].+1234567890') is not None
@@ -227,9 +217,7 @@ class TestRubyApplication(TestApplicationRuby):
self.get()
- self.conf({"listeners": {}, "applications": {}})
-
- unit_stop()
+ assert 'success' in self.conf({"listeners": {}, "applications": {}})
assert (
self.wait_for_record(r'\[error\].+At exit called\.') is not None
@@ -290,8 +278,6 @@ class TestRubyApplication(TestApplicationRuby):
assert self.get()['status'] == 500, 'body each error status'
- unit_stop()
-
assert (
self.wait_for_record(r'\[error\].+Failed to run ruby script')
is not None
diff --git a/test/test_ruby_isolation.py b/test/test_ruby_isolation.py
index 69e25de9..c49b6732 100644
--- a/test/test_ruby_isolation.py
+++ b/test/test_ruby_isolation.py
@@ -1,32 +1,15 @@
+import os
import shutil
import pytest
-
-from conftest import option
-from conftest import unit_run
-from conftest import unit_stop
from unit.applications.lang.ruby import TestApplicationRuby
-from unit.feature.isolation import TestFeatureIsolation
+from unit.option import option
class TestRubyIsolation(TestApplicationRuby):
prerequisites = {'modules': {'ruby': 'any'}, 'features': ['isolation']}
- @classmethod
- def setup_class(cls, complete_check=True):
- check = super().setup_class(complete_check=False)
-
- unit = unit_run()
- option.temp_dir = unit['temp_dir']
-
- TestFeatureIsolation().check(option.available, unit['temp_dir'])
-
- assert unit_stop() is None
- shutil.rmtree(unit['temp_dir'])
-
- return check if not complete_check else check()
-
- def test_ruby_isolation_rootfs_mount_namespace(self, is_su):
+ def test_ruby_isolation_rootfs(self, is_su):
isolation_features = option.available['features']['isolation'].keys()
if not is_su:
@@ -42,34 +25,22 @@ class TestRubyIsolation(TestApplicationRuby):
if 'pid' not in isolation_features:
pytest.skip('pid namespace is not supported')
- isolation = {'rootfs': option.test_dir}
+ isolation = {'rootfs': option.temp_dir}
if not is_su:
isolation['namespaces'] = {
'mount': True,
'credential': True,
- 'pid': True
+ 'pid': True,
}
- self.load('status_int', isolation=isolation)
-
- assert 'success' in self.conf(
- '"/ruby/status_int/config.ru"', 'applications/status_int/script',
- )
+ os.mkdir(option.temp_dir + '/ruby')
- assert 'success' in self.conf(
- '"/ruby/status_int"', 'applications/status_int/working_directory',
+ shutil.copytree(
+ option.test_dir + '/ruby/status_int',
+ option.temp_dir + '/ruby/status_int',
)
- assert self.get()['status'] == 200, 'status int'
-
- def test_ruby_isolation_rootfs(self, is_su):
- if not is_su:
- pytest.skip('requires root')
- return
-
- isolation = {'rootfs': option.test_dir}
-
self.load('status_int', isolation=isolation)
assert 'success' in self.conf(
diff --git a/test/test_settings.py b/test/test_settings.py
index 22830a3b..c59ca8b9 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
@@ -297,4 +296,3 @@ Connection: close
assert bool(resp), 'response from application 4'
assert resp['status'] == 200, 'status 4'
assert resp['body'] == body, 'body 4'
-
diff --git a/test/test_share_fallback.py b/test/test_share_fallback.py
index 462da9de..a02cb1a3 100644
--- a/test/test_share_fallback.py
+++ b/test/test_share_fallback.py
@@ -1,8 +1,7 @@
import os
-from conftest import option
-from conftest import skip_alert
from unit.applications.proto import TestApplicationProto
+from unit.option import option
class TestStatic(TestApplicationProto):
@@ -117,7 +116,7 @@ class TestStatic(TestApplicationProto):
assert resp['status'] == 200, 'fallback proxy status'
assert resp['body'] == '', 'fallback proxy'
- def test_fallback_proxy_loop(self):
+ def test_fallback_proxy_loop(self, skip_alert):
skip_alert(
r'open.*/blah/index.html.*failed',
r'accept.*failed',
diff --git a/test/test_static.py b/test/test_static.py
index a65928ca..3e85b435 100644
--- a/test/test_static.py
+++ b/test/test_static.py
@@ -2,10 +2,9 @@ import os
import socket
import pytest
-
-from conftest import option
-from conftest import waitforfiles
from unit.applications.proto import TestApplicationProto
+from unit.option import option
+from unit.utils import waitforfiles
class TestStatic(TestApplicationProto):
diff --git a/test/test_tls.py b/test/test_tls.py
index 4cf8d22c..89c57d07 100644
--- a/test/test_tls.py
+++ b/test/test_tls.py
@@ -4,10 +4,8 @@ import ssl
import subprocess
import pytest
-
-from conftest import option
-from conftest import skip_alert
from unit.applications.tls import TestApplicationTLS
+from unit.option import option
class TestTLS(TestApplicationTLS):
@@ -21,7 +19,7 @@ class TestTLS(TestApplicationTLS):
return self.date_to_sec_epoch(date, '%b %d %H:%M:%S %Y %Z')
def add_tls(self, application='empty', cert='default', port=7080):
- self.conf(
+ assert 'success' in self.conf(
{
"pass": "applications/" + application,
"tls": {"certificate": cert}
@@ -30,7 +28,7 @@ class TestTLS(TestApplicationTLS):
)
def remove_tls(self, application='empty', port=7080):
- self.conf(
+ assert 'success' in self.conf(
{"pass": "applications/" + application}, 'listeners/*:' + str(port)
)
@@ -479,8 +477,10 @@ basicConstraints = critical,CA:TRUE"""
read_timeout=1,
)
- self.conf({"pass": "applications/empty"}, 'listeners/*:7080')
- self.conf_delete('/certificates/default')
+ assert 'success' in self.conf(
+ {"pass": "applications/empty"}, 'listeners/*:7080'
+ )
+ assert 'success' in self.conf_delete('/certificates/default')
try:
resp = self.get_ssl(
@@ -505,12 +505,12 @@ basicConstraints = critical,CA:TRUE"""
'/certificates'
), 'remove all certificates'
- def test_tls_application_respawn(self):
+ def test_tls_application_respawn(self, skip_alert):
self.load('mirror')
self.certificate()
- self.conf('1', 'applications/mirror/processes')
+ assert 'success' in self.conf('1', 'applications/mirror/processes')
self.add_tls(application='mirror')
diff --git a/test/test_upstreams_rr.py b/test/test_upstreams_rr.py
index c20d6054..163eb646 100644
--- a/test/test_upstreams_rr.py
+++ b/test/test_upstreams_rr.py
@@ -1,8 +1,8 @@
import os
import re
-from conftest import option
from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
class TestUpstreamsRR(TestApplicationPython):
diff --git a/test/test_usr1.py b/test/test_usr1.py
index 3e44e4c5..dbb5265c 100644
--- a/test/test_usr1.py
+++ b/test/test_usr1.py
@@ -1,9 +1,8 @@
import os
from subprocess import call
-from conftest import unit_stop
-from conftest import waitforfiles
from unit.applications.lang.python import TestApplicationPython
+from unit.utils import waitforfiles
class TestUSR1(TestApplicationPython):
@@ -41,8 +40,6 @@ class TestUSR1(TestApplicationPython):
assert self.get(url='/usr1')['status'] == 200
- unit_stop()
-
assert (
self.wait_for_record(r'"GET /usr1 HTTP/1.1" 200 0 "-" "-"', log)
is not None
@@ -74,8 +71,6 @@ class TestUSR1(TestApplicationPython):
body = 'body_for_a_log_unit'
assert self.post(body=body)['status'] == 200
- unit_stop()
-
assert self.wait_for_record(body) is not None, 'rename new'
assert self.search_in_log(body, log_new) is None, 'rename new 2'
diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py
index 866dec47..a17b1af4 100644
--- a/test/unit/applications/lang/go.py
+++ b/test/unit/applications/lang/go.py
@@ -1,8 +1,8 @@
import os
import subprocess
-from conftest import option
from unit.applications.proto import TestApplicationProto
+from unit.option import option
class TestApplicationGo(TestApplicationProto):
@@ -12,6 +12,7 @@ class TestApplicationGo(TestApplicationProto):
env = os.environ.copy()
env['GOPATH'] = option.current_dir + '/build/go'
+ env['GOCACHE'] = option.cache_dir + '/go'
if static:
args = [
diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py
index 0ff85187..b2e17f23 100644
--- a/test/unit/applications/lang/java.py
+++ b/test/unit/applications/lang/java.py
@@ -4,8 +4,8 @@ import shutil
import subprocess
import pytest
-from conftest import option
from unit.applications.proto import TestApplicationProto
+from unit.option import option
class TestApplicationJava(TestApplicationProto):
diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py
index 98fd9ffc..cc6d06ef 100644
--- a/test/unit/applications/lang/node.py
+++ b/test/unit/applications/lang/node.py
@@ -1,9 +1,9 @@
import shutil
from urllib.parse import quote
-from conftest import option
-from conftest import public_dir
from unit.applications.proto import TestApplicationProto
+from unit.option import option
+from unit.utils import public_dir
class TestApplicationNode(TestApplicationProto):
diff --git a/test/unit/applications/lang/perl.py b/test/unit/applications/lang/perl.py
index 9dc24ace..58b867f0 100644
--- a/test/unit/applications/lang/perl.py
+++ b/test/unit/applications/lang/perl.py
@@ -1,5 +1,5 @@
-from conftest import option
from unit.applications.proto import TestApplicationProto
+from unit.option import option
class TestApplicationPerl(TestApplicationProto):
diff --git a/test/unit/applications/lang/php.py b/test/unit/applications/lang/php.py
index 3dbb32f5..90c0078c 100644
--- a/test/unit/applications/lang/php.py
+++ b/test/unit/applications/lang/php.py
@@ -1,8 +1,8 @@
-from conftest import option
import os
import shutil
from unit.applications.proto import TestApplicationProto
+from unit.option import option
class TestApplicationPHP(TestApplicationProto):
diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py
index 792a86fa..287d23f0 100644
--- a/test/unit/applications/lang/python.py
+++ b/test/unit/applications/lang/python.py
@@ -3,8 +3,8 @@ import shutil
from urllib.parse import quote
import pytest
-from conftest import option
from unit.applications.proto import TestApplicationProto
+from unit.option import option
class TestApplicationPython(TestApplicationProto):
diff --git a/test/unit/applications/lang/ruby.py b/test/unit/applications/lang/ruby.py
index 82d66e65..02644584 100644
--- a/test/unit/applications/lang/ruby.py
+++ b/test/unit/applications/lang/ruby.py
@@ -1,5 +1,5 @@
-from conftest import option
from unit.applications.proto import TestApplicationProto
+from unit.option import option
class TestApplicationRuby(TestApplicationProto):
diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py
index 6e760c70..af05d071 100644
--- a/test/unit/applications/proto.py
+++ b/test/unit/applications/proto.py
@@ -2,8 +2,8 @@ import os
import re
import time
-from conftest import option
from unit.control import TestControl
+from unit.option import option
class TestApplicationProto(TestControl):
diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py
index fb1b112c..b0cd5abb 100644
--- a/test/unit/applications/tls.py
+++ b/test/unit/applications/tls.py
@@ -2,8 +2,8 @@ import os
import ssl
import subprocess
-from conftest import option
from unit.applications.proto import TestApplicationProto
+from unit.option import option
class TestApplicationTLS(TestApplicationProto):
diff --git a/test/unit/check/isolation.py b/test/unit/check/isolation.py
new file mode 100644
index 00000000..fe5a41f8
--- /dev/null
+++ b/test/unit/check/isolation.py
@@ -0,0 +1,158 @@
+import json
+import os
+
+from unit.applications.lang.go import TestApplicationGo
+from unit.applications.lang.java import TestApplicationJava
+from unit.applications.lang.node import TestApplicationNode
+from unit.applications.proto import TestApplicationProto
+from unit.http import TestHTTP
+from unit.option import option
+from unit.utils import getns
+
+allns = ['pid', 'mnt', 'ipc', 'uts', 'cgroup', 'net']
+http = TestHTTP()
+
+def check_isolation():
+ test_conf = {"namespaces": {"credential": True}}
+ available = option.available
+
+ conf = ''
+ if 'go' in available['modules']:
+ TestApplicationGo().prepare_env('empty', 'app')
+
+ conf = {
+ "listeners": {"*:7080": {"pass": "applications/empty"}},
+ "applications": {
+ "empty": {
+ "type": "external",
+ "processes": {"spare": 0},
+ "working_directory": option.test_dir + "/go/empty",
+ "executable": option.temp_dir + "/go/app",
+ "isolation": {"namespaces": {"credential": True}},
+ },
+ },
+ }
+
+ elif 'python' in available['modules']:
+ conf = {
+ "listeners": {"*:7080": {"pass": "applications/empty"}},
+ "applications": {
+ "empty": {
+ "type": "python",
+ "processes": {"spare": 0},
+ "path": option.test_dir + "/python/empty",
+ "working_directory": option.test_dir + "/python/empty",
+ "module": "wsgi",
+ "isolation": {"namespaces": {"credential": True}},
+ }
+ },
+ }
+
+ elif 'php' in available['modules']:
+ conf = {
+ "listeners": {"*:7080": {"pass": "applications/phpinfo"}},
+ "applications": {
+ "phpinfo": {
+ "type": "php",
+ "processes": {"spare": 0},
+ "root": option.test_dir + "/php/phpinfo",
+ "working_directory": option.test_dir + "/php/phpinfo",
+ "index": "index.php",
+ "isolation": {"namespaces": {"credential": True}},
+ }
+ },
+ }
+
+ elif 'ruby' in available['modules']:
+ conf = {
+ "listeners": {"*:7080": {"pass": "applications/empty"}},
+ "applications": {
+ "empty": {
+ "type": "ruby",
+ "processes": {"spare": 0},
+ "working_directory": option.test_dir + "/ruby/empty",
+ "script": option.test_dir + "/ruby/empty/config.ru",
+ "isolation": {"namespaces": {"credential": True}},
+ }
+ },
+ }
+
+ elif 'java' in available['modules']:
+ TestApplicationJava().prepare_env('empty')
+
+ conf = {
+ "listeners": {"*:7080": {"pass": "applications/empty"}},
+ "applications": {
+ "empty": {
+ "unit_jars": option.current_dir + "/build",
+ "type": "java",
+ "processes": {"spare": 0},
+ "working_directory": option.test_dir + "/java/empty/",
+ "webapp": option.temp_dir + "/java",
+ "isolation": {"namespaces": {"credential": True}},
+ }
+ },
+ }
+
+ elif 'node' in available['modules']:
+ TestApplicationNode().prepare_env('basic')
+
+ conf = {
+ "listeners": {"*:7080": {"pass": "applications/basic"}},
+ "applications": {
+ "basic": {
+ "type": "external",
+ "processes": {"spare": 0},
+ "working_directory": option.temp_dir + "/node",
+ "executable": "app.js",
+ "isolation": {"namespaces": {"credential": True}},
+ }
+ },
+ }
+
+ elif 'perl' in available['modules']:
+ conf = {
+ "listeners": {"*:7080": {"pass": "applications/body_empty"}},
+ "applications": {
+ "body_empty": {
+ "type": "perl",
+ "processes": {"spare": 0},
+ "working_directory": option.test_dir
+ + "/perl/body_empty",
+ "script": option.test_dir + "/perl/body_empty/psgi.pl",
+ "isolation": {"namespaces": {"credential": True}},
+ }
+ },
+ }
+
+ else:
+ return
+
+ resp = http.put(
+ url='/config',
+ sock_type='unix',
+ addr=option.temp_dir + '/control.unit.sock',
+ body=json.dumps(conf),
+ )
+
+ if 'success' not in resp['body']:
+ return
+
+ userns = getns('user')
+ if not userns:
+ return
+
+ available['features']['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
+
+ for ns in allns:
+ ns_value = getns(ns)
+ if ns_value:
+ available['features']['isolation'][ns] = ns_value
diff --git a/test/unit/control.py b/test/unit/control.py
index f05aa827..3008a64b 100644
--- a/test/unit/control.py
+++ b/test/unit/control.py
@@ -1,7 +1,7 @@
import json
-from conftest import option
from unit.http import TestHTTP
+from unit.option import option
def args_handler(conf_func):
diff --git a/test/unit/feature/isolation.py b/test/unit/feature/isolation.py
deleted file mode 100644
index 7877c03a..00000000
--- a/test/unit/feature/isolation.py
+++ /dev/null
@@ -1,160 +0,0 @@
-import os
-
-from unit.applications.lang.go import TestApplicationGo
-from unit.applications.lang.java import TestApplicationJava
-from unit.applications.lang.node import TestApplicationNode
-from unit.applications.proto import TestApplicationProto
-from conftest import option
-
-
-class TestFeatureIsolation(TestApplicationProto):
- allns = ['pid', 'mnt', 'ipc', 'uts', 'cgroup', 'net']
-
- def check(self, available, temp_dir):
- test_conf = {"namespaces": {"credential": True}}
-
- conf = ''
- if 'go' in available['modules']:
- TestApplicationGo().prepare_env('empty', 'app')
-
- conf = {
- "listeners": {"*:7080": {"pass": "applications/empty"}},
- "applications": {
- "empty": {
- "type": "external",
- "processes": {"spare": 0},
- "working_directory": option.test_dir + "/go/empty",
- "executable": option.temp_dir + "/go/app",
- "isolation": {"namespaces": {"credential": True}},
- },
- },
- }
-
- elif 'python' in available['modules']:
- conf = {
- "listeners": {"*:7080": {"pass": "applications/empty"}},
- "applications": {
- "empty": {
- "type": "python",
- "processes": {"spare": 0},
- "path": option.test_dir + "/python/empty",
- "working_directory": option.test_dir + "/python/empty",
- "module": "wsgi",
- "isolation": {"namespaces": {"credential": True}},
- }
- },
- }
-
- elif 'php' in available['modules']:
- conf = {
- "listeners": {"*:7080": {"pass": "applications/phpinfo"}},
- "applications": {
- "phpinfo": {
- "type": "php",
- "processes": {"spare": 0},
- "root": option.test_dir + "/php/phpinfo",
- "working_directory": option.test_dir + "/php/phpinfo",
- "index": "index.php",
- "isolation": {"namespaces": {"credential": True}},
- }
- },
- }
-
- elif 'ruby' in available['modules']:
- conf = {
- "listeners": {"*:7080": {"pass": "applications/empty"}},
- "applications": {
- "empty": {
- "type": "ruby",
- "processes": {"spare": 0},
- "working_directory": option.test_dir + "/ruby/empty",
- "script": option.test_dir + "/ruby/empty/config.ru",
- "isolation": {"namespaces": {"credential": True}},
- }
- },
- }
-
- elif 'java' in available['modules']:
- TestApplicationJava().prepare_env('empty')
-
- conf = {
- "listeners": {"*:7080": {"pass": "applications/empty"}},
- "applications": {
- "empty": {
- "unit_jars": option.current_dir + "/build",
- "type": "java",
- "processes": {"spare": 0},
- "working_directory": option.test_dir + "/java/empty/",
- "webapp": option.temp_dir + "/java",
- "isolation": {"namespaces": {"credential": True}},
- }
- },
- }
-
- elif 'node' in available['modules']:
- TestApplicationNode().prepare_env('basic')
-
- conf = {
- "listeners": {"*:7080": {"pass": "applications/basic"}},
- "applications": {
- "basic": {
- "type": "external",
- "processes": {"spare": 0},
- "working_directory": option.temp_dir + "/node",
- "executable": "app.js",
- "isolation": {"namespaces": {"credential": True}},
- }
- },
- }
-
- elif 'perl' in available['modules']:
- conf = {
- "listeners": {"*:7080": {"pass": "applications/body_empty"}},
- "applications": {
- "body_empty": {
- "type": "perl",
- "processes": {"spare": 0},
- "working_directory": option.test_dir
- + "/perl/body_empty",
- "script": option.test_dir + "/perl/body_empty/psgi.pl",
- "isolation": {"namespaces": {"credential": True}},
- }
- },
- }
-
- else:
- return
-
- if 'success' not in self.conf(conf):
- return
-
- userns = self.getns('user')
- if not userns:
- return
-
- available['features']['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
-
- for ns in self.allns:
- ns_value = self.getns(ns)
- if ns_value:
- available['features']['isolation'][ns] = ns_value
-
- def getns(self, nstype):
- # read namespace id from symlink file:
- # it points to: '<nstype>:[<ns id>]'
- # # eg.: 'pid:[4026531836]'
- nspath = '/proc/self/ns/' + nstype
- data = None
-
- if os.path.exists(nspath):
- data = int(os.readlink(nspath)[len(nstype) + 2 : -1])
-
- return data
diff --git a/test/unit/http.py b/test/unit/http.py
index 8d964978..57e6ed3a 100644
--- a/test/unit/http.py
+++ b/test/unit/http.py
@@ -7,11 +7,10 @@ import select
import socket
import pytest
-from conftest import option
-from unit.main import TestUnit
+from unit.option import option
-class TestHTTP(TestUnit):
+class TestHTTP():
def http(self, start_str, **kwargs):
sock_type = kwargs.get('sock_type', 'ipv4')
port = kwargs.get('port', 7080)
diff --git a/test/unit/main.py b/test/unit/main.py
deleted file mode 100644
index 488b3f4d..00000000
--- a/test/unit/main.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import pytest
-from conftest import option
-
-
-class TestUnit():
- @classmethod
- def setup_class(cls, complete_check=True):
- def check():
- 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('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(', '.join(missed) + ' feature(s) not supported')
-
- if complete_check:
- check()
- else:
- return check
diff --git a/test/unit/option.py b/test/unit/option.py
new file mode 100644
index 00000000..677d806e
--- /dev/null
+++ b/test/unit/option.py
@@ -0,0 +1,16 @@
+class Options():
+ _options = {
+ 'skip_alerts': [],
+ 'skip_sanitizer': False,
+ }
+
+ def __setattr__(self, name, value):
+ Options._options[name] = value
+
+ def __getattr__(self, name):
+ if name in Options._options:
+ return Options._options[name]
+
+ raise AttributeError
+
+option = Options()
diff --git a/test/unit/utils.py b/test/unit/utils.py
new file mode 100644
index 00000000..7a0a3fe5
--- /dev/null
+++ b/test/unit/utils.py
@@ -0,0 +1,94 @@
+import os
+import socket
+import subprocess
+import time
+
+import pytest
+
+
+def public_dir(path):
+ os.chmod(path, 0o777)
+
+ for root, dirs, files in os.walk(path):
+ for d in dirs:
+ os.chmod(os.path.join(root, d), 0o777)
+ for f in files:
+ os.chmod(os.path.join(root, f), 0o777)
+
+
+def waitforfiles(*files):
+ for i in range(50):
+ wait = False
+
+ for f in files:
+ if not os.path.exists(f):
+ wait = True
+ break
+
+ if not wait:
+ return True
+
+ time.sleep(0.1)
+
+ return False
+
+
+def waitforsocket(port):
+ for i in range(50):
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+ try:
+ sock.settimeout(5)
+ sock.connect(('127.0.0.1', port))
+ return
+
+ except ConnectionRefusedError:
+ time.sleep(0.1)
+
+ except KeyboardInterrupt:
+ raise
+
+ pytest.fail('Can\'t connect to the 127.0.0.1:' + port)
+
+
+def findmnt():
+ try:
+ out = subprocess.check_output(
+ ['findmnt', '--raw'], stderr=subprocess.STDOUT
+ ).decode()
+ except FileNotFoundError:
+ pytest.skip('requires findmnt')
+
+ return out
+
+
+def waitformount(template, wait=50):
+ for i in range(wait):
+ if findmnt().find(template) != -1:
+ return True
+
+ time.sleep(0.1)
+
+ return False
+
+
+def waitforunmount(template, wait=50):
+ for i in range(wait):
+ if findmnt().find(template) == -1:
+ return True
+
+ time.sleep(0.1)
+
+ return False
+
+
+def getns(nstype):
+ # read namespace id from symlink file:
+ # it points to: '<nstype>:[<ns id>]'
+ # # eg.: 'pid:[4026531836]'
+ nspath = '/proc/self/ns/' + nstype
+ data = None
+
+ if os.path.exists(nspath):
+ data = int(os.readlink(nspath)[len(nstype) + 2 : -1])
+
+ return data