diff options
Diffstat (limited to 'test')
52 files changed, 1135 insertions, 226 deletions
diff --git a/test/go/ns_inspect/app.go b/test/go/ns_inspect/app.go index d9b561c9..4d19a796 100644 --- a/test/go/ns_inspect/app.go +++ b/test/go/ns_inspect/app.go @@ -21,10 +21,11 @@ type ( } Output struct { - PID int - UID int - GID int - NS NS + PID int + UID int + GID int + NS NS + FileExists bool } ) @@ -64,6 +65,18 @@ func handler(w http.ResponseWriter, r *http.Request) { CGROUP: getns("cgroup"), }, } + + err := r.ParseForm() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + if fname := r.Form.Get("file"); fname != "" { + _, err = os.Stat(fname); + out.FileExists = err == nil + } + data, err := json.Marshal(out) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/test/php/targets/1.php b/test/php/targets/1.php new file mode 100644 index 00000000..09f7ae2c --- /dev/null +++ b/test/php/targets/1.php @@ -0,0 +1,4 @@ +<?php +header('Content-Length: 1'); +echo '1'; +?> diff --git a/test/php/targets/2/2.php b/test/php/targets/2/2.php new file mode 100644 index 00000000..0c5d27c6 --- /dev/null +++ b/test/php/targets/2/2.php @@ -0,0 +1,4 @@ +<?php +header('Content-Length: 1'); +echo '2'; +?> diff --git a/test/php/targets/index.php b/test/php/targets/index.php new file mode 100644 index 00000000..88239d5f --- /dev/null +++ b/test/php/targets/index.php @@ -0,0 +1,4 @@ +<?php +header('Content-Length: 5'); +echo 'index'; +?> diff --git a/test/python/ns_inspect/wsgi.py b/test/python/ns_inspect/wsgi.py new file mode 100644 index 00000000..fa1222e4 --- /dev/null +++ b/test/python/ns_inspect/wsgi.py @@ -0,0 +1,31 @@ +import json +import os + +try: + # Python 3 + from urllib.parse import parse_qs +except ImportError: + # Python 2 + from urlparse import parse_qs + + +def application(environ, start_response): + ret = { + 'FileExists': False, + } + + d = parse_qs(environ['QUERY_STRING']) + + ret['FileExists'] = os.path.exists(d.get('path')[0]) + + out = json.dumps(ret) + + start_response( + '200', + [ + ('Content-Type', 'application/json'), + ('Content-Length', str(len(out))), + ], + ) + + return out.encode('utf-8') diff --git a/test/run.py b/test/run.py index 59e06bcb..384663f9 100755 --- a/test/run.py +++ b/test/run.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 - -import unittest -import sys import os +import sys +import unittest if __name__ == '__main__': loader = unittest.TestLoader() diff --git a/test/test_access_log.py b/test/test_access_log.py index 898d8b24..3ef8f7a0 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -1,12 +1,11 @@ -import os -import re import time import unittest + from unit.applications.lang.python import TestApplicationPython class TestAccessLog(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def load(self, script): super().load(script) diff --git a/test/test_configuration.py b/test/test_configuration.py index daba874b..0329ef5e 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -1,9 +1,10 @@ import unittest + from unit.control import TestControl class TestConfiguration(TestControl): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def test_json_empty(self): self.assertIn('error', self.conf(''), 'empty') diff --git a/test/test_go_application.py b/test/test_go_application.py index c9d4ba77..b9b78e2b 100644 --- a/test/test_go_application.py +++ b/test/test_go_application.py @@ -2,7 +2,7 @@ from unit.applications.lang.go import TestApplicationGo class TestGoApplication(TestApplicationGo): - prerequisites = {'modules': ['go']} + prerequisites = {'modules': {'go': 'all'}} def test_go_application_variables(self): self.load('variables') diff --git a/test/test_go_isolation.py b/test/test_go_isolation.py index 7884274d..61d39617 100644 --- a/test/test_go_isolation.py +++ b/test/test_go_isolation.py @@ -1,13 +1,13 @@ -import pwd import grp -import json +import pwd import unittest + from unit.applications.lang.go import TestApplicationGo from unit.feature.isolation import TestFeatureIsolation class TestGoIsolation(TestApplicationGo): - prerequisites = {'modules': ['go'], 'features': ['isolation']} + prerequisites = {'modules': {'go': 'any'}, 'features': ['isolation']} isolation = TestFeatureIsolation() @@ -281,6 +281,52 @@ class TestGoIsolation(TestApplicationGo): '%s match' % ns, ) + def test_go_isolation_rootfs_container(self): + if not self.isolation_key('unprivileged_userns_clone'): + print('unprivileged clone is not available') + raise unittest.SkipTest() + + if not self.isolation_key('mnt'): + print('mnt namespace is not supported') + raise unittest.SkipTest() + + isolation = { + 'namespaces': {'mount': True, 'credential': True}, + 'rootfs': self.testdir, + } + + self.load('ns_inspect', isolation=isolation) + + obj = self.getjson(url='/?file=/go/app')['body'] + + self.assertEqual(obj['FileExists'], True, 'app relative to rootfs') + + obj = self.getjson(url='/?file=/bin/sh')['body'] + self.assertEqual(obj['FileExists'], False, 'file should not exists') + + def test_go_isolation_rootfs_container_priv(self): + if not self.is_su: + print("requires root") + raise unittest.SkipTest() + + if not self.isolation_key('mnt'): + print('mnt namespace is not supported') + raise unittest.SkipTest() + + isolation = { + 'namespaces': {'mount': True}, + 'rootfs': self.testdir, + } + + self.load('ns_inspect', isolation=isolation) + + obj = self.getjson(url='/?file=/go/app')['body'] + + self.assertEqual(obj['FileExists'], True, 'app relative to rootfs') + + obj = self.getjson(url='/?file=/bin/sh')['body'] + self.assertEqual(obj['FileExists'], False, 'file should not exists') + if __name__ == '__main__': TestGoIsolation.main() diff --git a/test/test_go_isolation_rootfs.py b/test/test_go_isolation_rootfs.py new file mode 100644 index 00000000..0039ff87 --- /dev/null +++ b/test/test_go_isolation_rootfs.py @@ -0,0 +1,34 @@ +import os +import unittest + +from unit.applications.lang.go import TestApplicationGo + + +class TestGoIsolationRootfs(TestApplicationGo): + prerequisites = {'modules': {'go': 'all'}} + + def test_go_isolation_rootfs_chroot(self): + if not self.is_su: + print("requires root") + raise unittest.SkipTest() + + if os.uname().sysname == 'Darwin': + print('chroot tests not supported on OSX') + raise unittest.SkipTest() + + isolation = { + 'rootfs': self.testdir, + } + + self.load('ns_inspect', isolation=isolation) + + obj = self.getjson(url='/?file=/go/app')['body'] + + self.assertEqual(obj['FileExists'], True, 'app relative to rootfs') + + obj = self.getjson(url='/?file=/bin/sh')['body'] + self.assertEqual(obj['FileExists'], False, 'file should not exists') + + +if __name__ == '__main__': + TestGoIsolationRootfs.main() diff --git a/test/test_http_header.py b/test/test_http_header.py index b773bd68..ea4520c1 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -1,9 +1,10 @@ import unittest + from unit.applications.lang.python import TestApplicationPython class TestHTTPHeader(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} 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 7bd351a4..0cb18c25 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -1,18 +1,19 @@ import io import os import time -import unittest + from unit.applications.lang.java import TestApplicationJava class TestJavaApplication(TestApplicationJava): - prerequisites = {'modules': ['java']} + prerequisites = {'modules': {'java': 'all'}} def test_java_conf_error(self): self.skip_alerts.extend( [ r'realpath.*failed', r'failed to apply new conf', + r'application setup failed', ] ) self.assertIn( diff --git a/test/test_java_isolation_rootfs.py b/test/test_java_isolation_rootfs.py new file mode 100644 index 00000000..4d39bdc3 --- /dev/null +++ b/test/test_java_isolation_rootfs.py @@ -0,0 +1,85 @@ +import os +import subprocess +import unittest + +from unit.applications.lang.java import TestApplicationJava + + +class TestJavaIsolationRootfs(TestApplicationJava): + prerequisites = {'modules': {'java': 'all'}} + + def setUp(self): + if not self.is_su: + return + + super().setUp() + + os.makedirs(self.testdir + '/jars') + os.makedirs(self.testdir + '/tmp') + os.chmod(self.testdir + '/tmp', 0o777) + + try: + process = subprocess.Popen( + [ + "mount", + "--bind", + self.pardir + "/build", + self.testdir + "/jars", + ], + stderr=subprocess.STDOUT, + ) + + process.communicate() + + except: + self.fail('Cann\'t run mount process.') + + def tearDown(self): + if not self.is_su: + return + + try: + process = subprocess.Popen( + ["umount", "--lazy", self.testdir + "/jars"], + stderr=subprocess.STDOUT, + ) + + process.communicate() + + except: + self.fail('Cann\'t run mount process.') + + # super teardown must happen after unmount to avoid deletion of /build + super().tearDown() + + def test_java_isolation_rootfs_chroot_war(self): + if not self.is_su: + print('require root') + raise unittest.SkipTest() + + isolation = { + 'rootfs': self.testdir, + } + + self.load('empty_war', isolation=isolation) + + self.assertIn( + 'success', + self.conf( + '"/"', '/config/applications/empty_war/working_directory', + ), + ) + + self.assertIn( + 'success', self.conf('"/jars"', 'applications/empty_war/unit_jars') + ) + self.assertIn( + 'success', + self.conf('"/java/empty.war"', 'applications/empty_war/webapp'), + ) + + self.assertEqual(self.get()['status'], 200, 'war') + + +if __name__ == '__main__': + TestJavaIsolationRootfs.main() diff --git a/test/test_java_websockets.py b/test/test_java_websockets.py index 7ea04620..d78f7263 100644 --- a/test/test_java_websockets.py +++ b/test/test_java_websockets.py @@ -1,12 +1,13 @@ -import time import struct +import time import unittest + from unit.applications.lang.java import TestApplicationJava from unit.applications.websockets import TestApplicationWebsocket class TestJavaWebsockets(TestApplicationJava): - prerequisites = {'modules': ['java']} + prerequisites = {'modules': {'java': 'any'}} ws = TestApplicationWebsocket() diff --git a/test/test_node_application.py b/test/test_node_application.py index 174af15d..e46cc6a1 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -1,9 +1,10 @@ import unittest + from unit.applications.lang.node import TestApplicationNode class TestNodeApplication(TestApplicationNode): - prerequisites = {'modules': ['node']} + prerequisites = {'modules': {'node': 'all'}} def test_node_application_basic(self): self.load('basic') diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py index 4ce727db..1928d8c9 100644 --- a/test/test_node_websockets.py +++ b/test/test_node_websockets.py @@ -1,12 +1,13 @@ -import time import struct +import time import unittest + from unit.applications.lang.node import TestApplicationNode from unit.applications.websockets import TestApplicationWebsocket class TestNodeWebsockets(TestApplicationNode): - prerequisites = {'modules': ['node']} + prerequisites = {'modules': {'node': 'any'}} ws = TestApplicationWebsocket() diff --git a/test/test_perl_application.py b/test/test_perl_application.py index cc4eb915..dbf6abf7 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -1,9 +1,10 @@ import unittest + from unit.applications.lang.perl import TestApplicationPerl class TestPerlApplication(TestApplicationPerl): - prerequisites = {'modules': ['perl']} + prerequisites = {'modules': {'perl': 'all'}} def test_perl_application(self): self.load('variables') diff --git a/test/test_php_application.py b/test/test_php_application.py index 48e1e815..1259d22d 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -1,11 +1,11 @@ import os -import re import shutil import unittest + from unit.applications.lang.php import TestApplicationPHP class TestPHPApplication(TestApplicationPHP): - prerequisites = {'modules': ['php']} + prerequisites = {'modules': {'php': 'all'}} def before_disable_functions(self): body = self.get()['body'] diff --git a/test/test_php_basic.py b/test/test_php_basic.py index 5fde3e00..16483c4a 100644 --- a/test/test_php_basic.py +++ b/test/test_php_basic.py @@ -2,7 +2,7 @@ from unit.control import TestControl class TestPHPBasic(TestControl): - prerequisites = {'modules': ['php']} + prerequisites = {'modules': {'php': 'any'}} conf_app = { "app": { diff --git a/test/test_php_isolation.py b/test/test_php_isolation.py new file mode 100644 index 00000000..1b70ef02 --- /dev/null +++ b/test/test_php_isolation.py @@ -0,0 +1,57 @@ +import unittest + +from unit.applications.lang.php import TestApplicationPHP +from unit.feature.isolation import TestFeatureIsolation + + +class TestPHPIsolation(TestApplicationPHP): + prerequisites = {'modules': {'php': 'any'}, 'features': ['isolation']} + + isolation = TestFeatureIsolation() + + @classmethod + def setUpClass(cls, complete_check=True): + unit = super().setUpClass(complete_check=False) + + TestFeatureIsolation().check(cls.available, unit.testdir) + + return unit if not complete_check else unit.complete() + + def test_php_isolation_rootfs(self): + isolation_features = self.available['features']['isolation'].keys() + + if 'mnt' not in isolation_features: + print('requires mnt ns') + raise unittest.SkipTest() + + if not self.is_su: + if 'user' not in isolation_features: + print('requires unprivileged userns or root') + raise unittest.SkipTest() + + if not 'unprivileged_userns_clone' in isolation_features: + print('requires unprivileged userns or root') + raise unittest.SkipTest() + + isolation = { + 'namespaces': {'credential': not self.is_su, 'mount': True}, + 'rootfs': self.current_dir, + } + + self.load('phpinfo', isolation=isolation) + + self.assertIn( + 'success', self.conf('"/php/phpinfo"', 'applications/phpinfo/root') + ) + self.assertIn( + 'success', + self.conf( + '"/php/phpinfo"', 'applications/phpinfo/working_directory' + ), + ) + + self.assertEqual(self.get()['status'], 200, 'empty rootfs') + + +if __name__ == '__main__': + TestPHPIsolation.main() diff --git a/test/test_php_targets.py b/test/test_php_targets.py new file mode 100644 index 00000000..9c1ba2a6 --- /dev/null +++ b/test/test_php_targets.py @@ -0,0 +1,129 @@ +import unittest +from unit.applications.lang.php import TestApplicationPHP + +class TestPHPTargets(TestApplicationPHP): + prerequisites = {'modules': {'php': 'any'}} + + def test_php_application_targets(self): + self.assertIn( + 'success', + self.conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [ + { + "match": {"uri": "/1"}, + "action": {"pass": "applications/targets/1"}, + }, + { + "match": {"uri": "/2"}, + "action": {"pass": "applications/targets/2"}, + }, + {"action": {"pass": "applications/targets/default"}}, + ], + "applications": { + "targets": { + "type": "php", + "processes": {"spare": 0}, + "targets": { + "1": { + "script": "1.php", + "root": self.current_dir + "/php/targets", + }, + "2": { + "script": "2.php", + "root": self.current_dir + + "/php/targets/2", + }, + "default": { + "index": "index.php", + "root": self.current_dir + "/php/targets", + }, + }, + } + }, + } + ), + ) + + self.assertEqual(self.get(url='/1')['body'], '1') + self.assertEqual(self.get(url='/2')['body'], '2') + self.assertEqual(self.get(url='/blah')['status'], 503) # TODO 404 + self.assertEqual(self.get(url='/')['body'], 'index') + + self.assertIn( + 'success', + self.conf( + "\"1.php\"", 'applications/targets/targets/default/index' + ), + 'change targets index', + ) + self.assertEqual(self.get(url='/')['body'], '1') + + self.assertIn( + 'success', + self.conf_delete('applications/targets/targets/default/index'), + 'remove targets index', + ) + self.assertEqual(self.get(url='/')['body'], 'index') + + def test_php_application_targets_error(self): + self.assertIn( + 'success', + self.conf( + { + "listeners": { + "*:7080": {"pass": "applications/targets/default"} + }, + "applications": { + "targets": { + "type": "php", + "processes": {"spare": 0}, + "targets": { + "default": { + "index": "index.php", + "root": self.current_dir + "/php/targets", + }, + }, + } + }, + } + ), + 'initial configuration', + ) + self.assertEqual(self.get()['status'], 200) + + self.assertIn( + 'error', + self.conf( + {"pass": "applications/targets/blah"}, 'listeners/*:7080' + ), + 'invalid targets pass', + ) + self.assertIn( + 'error', + self.conf( + '"' + self.current_dir + '/php/targets\"', + 'applications/targets/root', + ), + 'invalid root', + ) + self.assertIn( + 'error', + self.conf('"index.php"', 'applications/targets/index'), + 'invalid index', + ) + self.assertIn( + 'error', + self.conf('"index.php"', 'applications/targets/script'), + 'invalid script', + ) + self.assertIn( + 'error', + self.conf_delete('applications/targets/default/root'), + 'root remove', + ) + + +if __name__ == '__main__': + TestPHPTargets.main() diff --git a/test/test_proxy.py b/test/test_proxy.py index 74bd0873..feec1ac4 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -1,12 +1,13 @@ import re -import time import socket +import time import unittest + from unit.applications.lang.python import TestApplicationPython class TestProxy(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} SERVER_PORT = 7999 @@ -521,88 +522,35 @@ Content-Length: 10 self.assertEqual(len(resp['body']), 10, 'body gt Content-Length 15') def test_proxy_invalid(self): - self.assertIn( - 'error', - self.conf([{"action": {"proxy": 'blah'}}], 'routes'), - 'proxy invalid', - ) - self.assertIn( - 'error', - self.conf([{"action": {"proxy": '/blah'}}], 'routes'), - 'proxy invalid 2', - ) - self.assertIn( - 'error', - self.conf([{"action": {"proxy": 'unix:/blah'}}], 'routes'), - 'proxy unix invalid 2', - ) - self.assertIn( - 'error', - self.conf([{"action": {"proxy": 'http://blah'}}], 'routes'), - 'proxy unix invalid 3', - ) - self.assertIn( - 'error', - self.conf([{"action": {"proxy": 'http://127.0.0.1'}}], 'routes'), - 'proxy ipv4 invalid', - ) - self.assertIn( - 'error', - self.conf([{"action": {"proxy": 'http://127.0.0.1:'}}], 'routes'), - 'proxy ipv4 invalid 2', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://127.0.0.1:blah'}}], 'routes' - ), - 'proxy ipv4 invalid 3', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://127.0.0.1:-1'}}], 'routes' - ), - 'proxy ipv4 invalid 4', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://127.0.0.1:7080b'}}], 'routes' - ), - 'proxy ipv4 invalid 5', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://[]'}}], 'routes' - ), - 'proxy ipv6 invalid', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://[]:7080'}}], 'routes' - ), - 'proxy ipv6 invalid 2', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://[:]:7080'}}], 'routes' - ), - 'proxy ipv6 invalid 3', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://[::7080'}}], 'routes' - ), - 'proxy ipv6 invalid 4', - ) + def check_proxy(proxy): + self.assertIn( + 'error', + self.conf([{"action": {"proxy": proxy}}], 'routes'), + 'proxy invalid', + ) + + check_proxy('blah') + check_proxy('/blah') + check_proxy('unix:/blah') + check_proxy('http://blah') + check_proxy('http://127.0.0.1') + check_proxy('http://127.0.0.1:') + check_proxy('http://127.0.0.1:blah') + check_proxy('http://127.0.0.1:-1') + check_proxy('http://127.0.0.1:7080b') + check_proxy('http://[]') + check_proxy('http://[]:7080') + check_proxy('http://[:]:7080') + check_proxy('http://[::7080') - @unittest.skip('not yet') def test_proxy_loop(self): + self.skip_alerts.extend( + [ + r'socket.*failed', + r'accept.*failed', + r'new connections are not accepted', + ] + ) self.conf( { "listeners": { @@ -625,6 +573,7 @@ Content-Length: 10 ) self.get_http10(no_recv=True) + self.get_http10(read_timeout=1) if __name__ == '__main__': TestProxy.main() diff --git a/test/test_python_application.py b/test/test_python_application.py index 8d435b48..8bd3f750 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -1,14 +1,14 @@ -import re -import os import grp import pwd +import re import time import unittest + from unit.applications.lang.python import TestApplicationPython class TestPythonApplication(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'all'}} def findall(self, pattern): with open(self.testdir + '/unit.log', 'r', errors='ignore') as f: diff --git a/test/test_python_basic.py b/test/test_python_basic.py index 3233fca2..d6445ac2 100644 --- a/test/test_python_basic.py +++ b/test/test_python_basic.py @@ -2,7 +2,7 @@ from unit.control import TestControl class TestPythonBasic(TestControl): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} conf_app = { "app": { diff --git a/test/test_python_environment.py b/test/test_python_environment.py index f808f795..a03b96e6 100644 --- a/test/test_python_environment.py +++ b/test/test_python_environment.py @@ -2,7 +2,7 @@ from unit.applications.lang.python import TestApplicationPython class TestPythonEnvironment(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def test_python_environment_name_null(self): self.load('environment') diff --git a/test/test_python_isolation.py b/test/test_python_isolation.py new file mode 100644 index 00000000..1bed64ba --- /dev/null +++ b/test/test_python_isolation.py @@ -0,0 +1,79 @@ +import unittest + +from unit.applications.lang.python import TestApplicationPython +from unit.feature.isolation import TestFeatureIsolation + + +class TestPythonIsolation(TestApplicationPython): + prerequisites = {'modules': {'python': 'any'}, 'features': ['isolation']} + + isolation = TestFeatureIsolation() + + @classmethod + def setUpClass(cls, complete_check=True): + unit = super().setUpClass(complete_check=False) + + TestFeatureIsolation().check(cls.available, unit.testdir) + + return unit if not complete_check else unit.complete() + + def test_python_isolation_rootfs(self): + isolation_features = self.available['features']['isolation'].keys() + + if 'mnt' not in isolation_features: + print('requires mnt ns') + raise unittest.SkipTest() + + if not self.is_su: + if 'user' not in isolation_features: + print('requires unprivileged userns or root') + raise unittest.SkipTest() + + if not 'unprivileged_userns_clone' in isolation_features: + print('requires unprivileged userns or root') + raise unittest.SkipTest() + + isolation = { + 'namespaces': {'credential': not self.is_su, 'mount': True}, + 'rootfs': self.testdir, + } + + self.load('empty', isolation=isolation) + + self.assertEqual(self.get()['status'], 200, 'python rootfs') + + self.load('ns_inspect', isolation=isolation) + + self.assertEqual( + self.getjson(url='/?path=' + self.testdir)['body']['FileExists'], + False, + 'testdir does not exists in rootfs', + ) + + self.assertEqual( + self.getjson(url='/?path=/proc/self')['body']['FileExists'], + False, + 'no /proc/self', + ) + + self.assertEqual( + self.getjson(url='/?path=/dev/pts')['body']['FileExists'], + False, + 'no /dev/pts', + ) + + self.assertEqual( + self.getjson(url='/?path=/sys/kernel')['body']['FileExists'], + False, + 'no /sys/kernel', + ) + + ret = self.getjson(url='/?path=/app/python/ns_inspect') + + self.assertEqual( + ret['body']['FileExists'], True, 'application exists in rootfs', + ) + + +if __name__ == '__main__': + TestPythonIsolation.main() diff --git a/test/test_python_isolation_chroot.py b/test/test_python_isolation_chroot.py new file mode 100644 index 00000000..7761128e --- /dev/null +++ b/test/test_python_isolation_chroot.py @@ -0,0 +1,57 @@ +import unittest + +from unit.applications.lang.python import TestApplicationPython +from unit.feature.isolation import TestFeatureIsolation + + +class TestPythonIsolation(TestApplicationPython): + prerequisites = {'modules': {'python': 'any'}} + + def test_python_isolation_chroot(self): + if not self.is_su: + print('requires root') + raise unittest.SkipTest() + + isolation = { + 'rootfs': self.testdir, + } + + self.load('empty', isolation=isolation) + + self.assertEqual(self.get()['status'], 200, 'python chroot') + + self.load('ns_inspect', isolation=isolation) + + self.assertEqual( + self.getjson(url='/?path=' + self.testdir)['body']['FileExists'], + False, + 'testdir does not exists in rootfs', + ) + + self.assertEqual( + self.getjson(url='/?path=/proc/self')['body']['FileExists'], + False, + 'no /proc/self', + ) + + self.assertEqual( + self.getjson(url='/?path=/dev/pts')['body']['FileExists'], + False, + 'no /dev/pts', + ) + + self.assertEqual( + self.getjson(url='/?path=/sys/kernel')['body']['FileExists'], + False, + 'no /sys/kernel', + ) + + ret = self.getjson(url='/?path=/app/python/ns_inspect') + + self.assertEqual( + ret['body']['FileExists'], True, 'application exists in rootfs', + ) + + +if __name__ == '__main__': + TestPythonIsolation.main() diff --git a/test/test_python_procman.py b/test/test_python_procman.py index a2e6126c..8613f58e 100644 --- a/test/test_python_procman.py +++ b/test/test_python_procman.py @@ -1,12 +1,13 @@ import re -import time import subprocess +import time import unittest + from unit.applications.lang.python import TestApplicationPython class TestPythonProcman(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def setUp(self): super().setUp() diff --git a/test/test_respawn.py b/test/test_respawn.py new file mode 100644 index 00000000..f1c71a20 --- /dev/null +++ b/test/test_respawn.py @@ -0,0 +1,95 @@ +import re +import subprocess +import time + +from unit.applications.lang.python import TestApplicationPython + + +class TestRespawn(TestApplicationPython): + prerequisites = {'modules': {'python': 'any'}} + + PATTERN_ROUTER = 'unit: router' + PATTERN_CONTROLLER = 'unit: controller' + + def setUp(self): + super().setUp() + + self.app_name = "app-" + self.testdir.split('/')[-1] + + self.load('empty', self.app_name) + + self.assertIn( + 'success', + self.conf('1', 'applications/' + self.app_name + '/processes') + ) + + def pid_by_name(self, name): + output = subprocess.check_output(['ps', 'ax']).decode() + m = re.search('\s*(\d+).*' + name, output) + return m 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): + for i in range(50): + found = self.pid_by_name(process) + + if found is not None: + break + + time.sleep(0.1) + + return found + + def smoke_test(self): + for _ in range(5): + self.assertIn( + 'success', + self.conf('1', 'applications/' + self.app_name + '/processes') + ) + self.assertEqual(self.get()['status'], 200) + + # Check if the only one router, controller, + # and application processes running. + + output = subprocess.check_output(['ps', 'ax']).decode() + self.assertEqual(len(re.findall(self.PATTERN_ROUTER, output)), 1) + self.assertEqual(len(re.findall(self.PATTERN_CONTROLLER, output)), 1) + self.assertEqual(len(re.findall(self.app_name, output)), 1) + + def test_respawn_router(self): + pid = self.pid_by_name(self.PATTERN_ROUTER) + + self.kill_pids(pid) + self.skip_alerts.append(r'process %s exited on signal 9' % pid) + + self.assertIsNotNone(self.wait_for_process(self.PATTERN_ROUTER)) + + self.smoke_test() + + def test_respawn_controller(self): + pid = self.pid_by_name(self.PATTERN_CONTROLLER) + + self.kill_pids(pid) + self.skip_alerts.append(r'process %s exited on signal 9' % pid) + + self.assertIsNotNone(self.wait_for_process(self.PATTERN_CONTROLLER)) + + self.assertEqual(self.get()['status'], 200) + + self.smoke_test() + + def test_respawn_application(self): + pid = self.pid_by_name(self.app_name) + + self.kill_pids(pid) + self.skip_alerts.append(r'process %s exited on signal 9' % pid) + + self.assertIsNotNone(self.wait_for_process(self.app_name)) + + self.smoke_test() + + +if __name__ == '__main__': + TestRespawn.main() diff --git a/test/test_return.py b/test/test_return.py index fcb51745..a89d97e6 100644 --- a/test/test_return.py +++ b/test/test_return.py @@ -1,5 +1,5 @@ import re -import unittest + from unit.applications.proto import TestApplicationProto diff --git a/test/test_routing.py b/test/test_routing.py index ad793662..3cf4009c 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -1,9 +1,11 @@ +# -*- coding: utf-8 -*- import unittest + from unit.applications.proto import TestApplicationProto class TestRouting(TestApplicationProto): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def setUp(self): super().setUp() @@ -179,6 +181,61 @@ class TestRouting(TestApplicationProto): self.assertEqual(self.get(url='/blah')['status'], 200, '/blah') self.assertEqual(self.get(url='/BLAH')['status'], 404, '/BLAH') + def test_routes_pass_encode(self): + def check_pass(path, name): + self.assertIn( + 'success', + self.conf( + { + "listeners": { + "*:7080": {"pass": "applications/" + path} + }, + "applications": { + name: { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + + '/python/empty', + "module": "wsgi", + } + }, + } + ), + ) + + self.assertEqual(self.get()['status'], 200) + + check_pass("%25", "%") + check_pass("blah%2Fblah", "blah/blah") + check_pass("%2Fblah%2F%2Fblah%2F", "/blah//blah/") + check_pass("%20blah%252Fblah%7E", " blah%2Fblah~") + + def check_pass_error(path, name): + self.assertIn( + 'error', + self.conf( + { + "listeners": { + "*:7080": {"pass": "applications/" + path} + }, + "applications": { + name: { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + + '/python/empty', + "module": "wsgi", + } + }, + } + ), + ) + + check_pass_error("%", "%") + check_pass_error("%1", "%1") + def test_routes_absent(self): self.conf( { @@ -1069,6 +1126,33 @@ class TestRouting(TestApplicationProto): self.assertEqual(self.get(url='/?Foo=bar')['status'], 404, 'case') self.assertEqual(self.get(url='/?foo=Bar')['status'], 404, 'case 2') + def test_routes_match_arguments_chars(self): + chars = ( + " !\"%23$%25%26'()*%2B,-./0123456789:;<%3D>?@" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + ) + + chars_enc = "" + for h1 in ["2", "3", "4", "5", "6", "7"]: + for h2 in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", + "B", "C", "D", "E", "F", + ]: + chars_enc += "%" + h1 + h2 + chars_enc = chars_enc[:-3] + + def check_args(args, query): + self.route_match({"arguments": args}) + self.assertEqual(self.get(url='/?' + query)['status'], 200) + + check_args({chars: chars}, chars + '=' + chars) + check_args({chars: chars}, chars + '=' + chars_enc) + check_args({chars: chars}, chars_enc + '=' + chars) + check_args({chars: chars}, chars_enc + '=' + chars_enc) + check_args({chars_enc: chars_enc}, chars + '=' + chars) + check_args({chars_enc: chars_enc}, chars + '=' + chars_enc) + check_args({chars_enc: chars_enc}, chars_enc + '=' + chars) + check_args({chars_enc: chars_enc}, chars_enc + '=' + chars_enc) + def test_routes_match_arguments_empty(self): self.route_match({"arguments": {}}) self.assertEqual(self.get()['status'], 200, 'arguments empty') @@ -1076,43 +1160,113 @@ class TestRouting(TestApplicationProto): self.route_match({"arguments": []}) self.assertEqual(self.get()['status'], 200, 'arguments empty 2') - def test_routes_match_arguments_invalid(self): - self.route_match_invalid({"arguments": ["var"]}) - self.route_match_invalid({"arguments": [{"var1": {}}]}) - self.route_match_invalid({"arguments": {"": "bar"}}) - - @unittest.skip('not yet') def test_routes_match_arguments_space(self): - self.route_match({"arguments": {"foo": "bar "}}) - - self.assertEqual(self.get(url='/?foo=bar &')['status'], 200, 'sp') - # FAIL - self.assertEqual(self.get(url='/?foo=bar+&')['status'], 200, 'sp 2') - # FAIL - self.assertEqual(self.get(url='/?foo=bar%20&')['status'], 200, 'sp 3') - - @unittest.skip('not yet') - def test_routes_match_arguments_plus(self): - self.route_match({"arguments": [{"foo": "bar+"}]}) - - self.assertEqual(self.get(url='/?foo=bar+&')['status'], 200, 'plus') - # FAIL + self.route_match({"arguments": {"+fo o%20": "%20b+a r"}}) + self.assertEqual(self.get(url='/? fo o = b a r&')['status'], 200) + self.assertEqual(self.get(url='/?+fo+o+=+b+a+r&')['status'], 200) self.assertEqual( - self.get(url='/?foo=bar%2B&')['status'], 200, 'plus 2' - ) - - @unittest.skip('not yet') - def test_routes_match_arguments_hex(self): - self.route_match({"arguments": [{"foo": "bar"}]}) - - self.assertEqual( - self.get(url='/?%66%6F%6f=%62%61%72&')['status'], 200, 'hex' - ) - - def test_routes_match_arguments_chars(self): - self.route_match({"arguments": {"foo": "-._()[],;"}}) - - self.assertEqual(self.get(url='/?foo=-._()[],;')['status'], 200, 'chs') + self.get(url='/?%20fo%20o%20=%20b%20a%20r&')['status'], 200 + ) + + self.route_match({"arguments": {"%20foo": " bar"}}) + self.assertEqual(self.get(url='/? foo= bar')['status'], 200) + self.assertEqual(self.get(url='/?+foo=+bar')['status'], 200) + self.assertEqual(self.get(url='/?%20foo=%20bar')['status'], 200) + self.assertEqual(self.get(url='/?+foo= bar')['status'], 200) + self.assertEqual(self.get(url='/?%20foo=+bar')['status'], 200) + + def test_routes_match_arguments_equal(self): + self.route_match({"arguments": {"=": "="}}) + self.assertEqual(self.get(url='/?%3D=%3D')['status'], 200) + self.assertEqual(self.get(url='/?%3D==')['status'], 200) + self.assertEqual(self.get(url='/?===')['status'], 404) + self.assertEqual(self.get(url='/?%3D%3D%3D')['status'], 404) + self.assertEqual(self.get(url='/?==%3D')['status'], 404) + + def test_routes_match_arguments_enc(self): + self.route_match({"arguments": {"Ю": "н"}}) + self.assertEqual(self.get(url='/?%D0%AE=%D0%BD')['status'], 200) + self.assertEqual(self.get(url='/?%d0%ae=%d0%Bd')['status'], 200) + + def test_routes_match_arguments_hash(self): + self.route_match({"arguments": {"#": "#"}}) + self.assertEqual(self.get(url='/?%23=%23')['status'], 200) + self.assertEqual(self.get(url='/?%23=%23#')['status'], 200) + self.assertEqual(self.get(url='/?#=#')['status'], 404) + self.assertEqual(self.get(url='/?%23=#')['status'], 404) + + def test_routes_match_arguments_wildcard(self): + self.route_match({"arguments": {"foo": "*"}}) + self.assertEqual(self.get(url='/?foo')['status'], 200) + self.assertEqual(self.get(url='/?foo=')['status'], 200) + self.assertEqual(self.get(url='/?foo=blah')['status'], 200) + self.assertEqual(self.get(url='/?blah=foo')['status'], 404) + + self.route_match({"arguments": {"foo": "%25*"}}) + self.assertEqual(self.get(url='/?foo=%xx')['status'], 200) + + self.route_match({"arguments": {"foo": "%2A*"}}) + self.assertEqual(self.get(url='/?foo=*xx')['status'], 200) + self.assertEqual(self.get(url='/?foo=xx')['status'], 404) + + self.route_match({"arguments": {"foo": "*%2A"}}) + self.assertEqual(self.get(url='/?foo=xx*')['status'], 200) + self.assertEqual(self.get(url='/?foo=xx*x')['status'], 404) + + self.route_match({"arguments": {"foo": "1*2"}}) + self.assertEqual(self.get(url='/?foo=12')['status'], 200) + self.assertEqual(self.get(url='/?foo=1blah2')['status'], 200) + self.assertEqual(self.get(url='/?foo=1%2A2')['status'], 200) + self.assertEqual(self.get(url='/?foo=x12')['status'], 404) + + self.route_match({"arguments": {"foo": "bar*", "%25": "%25"}}) + self.assertEqual(self.get(url='/?foo=barxx&%=%')['status'], 200) + self.assertEqual(self.get(url='/?foo=barxx&x%=%')['status'], 404) + + def test_routes_match_arguments_negative(self): + self.route_match({"arguments": {"foo": "!%25"}}) + self.assertEqual(self.get(url='/?foo=blah')['status'], 200) + self.assertEqual(self.get(url='/?foo=%')['status'], 404) + + self.route_match({"arguments": {"foo": "%21blah"}}) + self.assertEqual(self.get(url='/?foo=%21blah')['status'], 200) + self.assertEqual(self.get(url='/?foo=!blah')['status'], 200) + self.assertEqual(self.get(url='/?foo=bar')['status'], 404) + + self.route_match({"arguments": {"foo": "!!%21*a"}}) + self.assertEqual(self.get(url='/?foo=blah')['status'], 200) + self.assertEqual(self.get(url='/?foo=!blah')['status'], 200) + self.assertEqual(self.get(url='/?foo=!!a')['status'], 404) + self.assertEqual(self.get(url='/?foo=!!bla')['status'], 404) + + def test_routes_match_arguments_percent(self): + self.route_match({"arguments": {"%25": "%25"}}) + self.assertEqual(self.get(url='/?%=%')['status'], 200) + self.assertEqual(self.get(url='/?%25=%25')['status'], 200) + self.assertEqual(self.get(url='/?%25=%')['status'], 200) + + self.route_match({"arguments": {"%251": "%252"}}) + self.assertEqual(self.get(url='/?%1=%2')['status'], 200) + self.assertEqual(self.get(url='/?%251=%252')['status'], 200) + self.assertEqual(self.get(url='/?%251=%2')['status'], 200) + + self.route_match({"arguments": {"%25%21%251": "%25%24%252"}}) + self.assertEqual(self.get(url='/?%!%1=%$%2')['status'], 200) + self.assertEqual(self.get(url='/?%25!%251=%25$%252')['status'], 200) + self.assertEqual(self.get(url='/?%25!%1=%$%2')['status'], 200) + + def test_routes_match_arguments_ampersand(self): + self.route_match({"arguments": {"foo": "&"}}) + self.assertEqual(self.get(url='/?foo=%26')['status'], 200) + self.assertEqual(self.get(url='/?foo=%26&')['status'], 200) + self.assertEqual(self.get(url='/?foo=%26%26')['status'], 404) + self.assertEqual(self.get(url='/?foo=&')['status'], 404) + + self.route_match({"arguments": {"&": ""}}) + self.assertEqual(self.get(url='/?%26=')['status'], 200) + self.assertEqual(self.get(url='/?%26=&')['status'], 200) + self.assertEqual(self.get(url='/?%26=%26')['status'], 404) + self.assertEqual(self.get(url='/?&=')['status'], 404) def test_routes_match_arguments_complex(self): self.route_match({"arguments": {"foo": ""}}) @@ -1147,6 +1301,14 @@ class TestRouting(TestApplicationProto): self.assertEqual( self.get(url='/?foo=bar&blah')['status'], 404, 'multiple 3' ) + self.assertEqual( + self.get(url='/?foo=bar&blah=tes')['status'], 404, 'multiple 4' + ) + self.assertEqual( + self.get(url='/?foo=b%61r&bl%61h=t%65st')['status'], + 200, + 'multiple 5', + ) def test_routes_match_arguments_multiple_rules(self): self.route_match({"arguments": {"foo": ["bar", "blah"]}}) @@ -1193,6 +1355,22 @@ class TestRouting(TestApplicationProto): self.assertEqual(self.get(url='/?var2=val2')['status'], 404, 'arr 7') self.assertEqual(self.get(url='/?var3=foo')['status'], 200, 'arr 8') + def test_routes_match_arguments_invalid(self): + # TODO remove it after controller fixed + self.skip_alerts.append(r'failed to apply new conf') + + self.route_match_invalid({"arguments": ["var"]}) + self.route_match_invalid({"arguments": [{"var1": {}}]}) + self.route_match_invalid({"arguments": {"": "bar"}}) + self.route_match_invalid({"arguments": {"foo": "*ba*r"}}) + self.route_match_invalid({"arguments": {"foo": "%"}}) + self.route_match_invalid({"arguments": {"foo": "%1G"}}) + self.route_match_invalid({"arguments": {"%": "bar"}}) + self.route_match_invalid({"arguments": {"foo": "%0"}}) + self.route_match_invalid({"arguments": {"foo": "%%1F"}}) + self.route_match_invalid({"arguments": {"%%1F": ""}}) + self.route_match_invalid({"arguments": {"%7%F": ""}}) + def test_routes_match_cookies(self): self.route_match({"cookies": {"foO": "bar"}}) @@ -1748,5 +1926,6 @@ class TestRouting(TestApplicationProto): self.assertEqual(self.get()['status'], 200, 'proxy') + if __name__ == '__main__': TestRouting.main() diff --git a/test/test_routing_tls.py b/test/test_routing_tls.py index 36bd9057..a9b8f88d 100644 --- a/test/test_routing_tls.py +++ b/test/test_routing_tls.py @@ -2,7 +2,7 @@ from unit.applications.tls import TestApplicationTLS class TestRoutingTLS(TestApplicationTLS): - prerequisites = {'modules': ['openssl']} + prerequisites = {'modules': {'openssl': 'any'}} def test_routes_match_scheme_tls(self): self.certificate() diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index bdaabe51..4709df6c 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -1,9 +1,10 @@ import unittest + from unit.applications.lang.ruby import TestApplicationRuby class TestRubyApplication(TestApplicationRuby): - prerequisites = {'modules': ['ruby']} + prerequisites = {'modules': {'ruby': 'all'}} def test_ruby_application(self): self.load('variables') diff --git a/test/test_ruby_isolation.py b/test/test_ruby_isolation.py new file mode 100644 index 00000000..9bac162e --- /dev/null +++ b/test/test_ruby_isolation.py @@ -0,0 +1,71 @@ +import os +import shutil +import unittest + +from unit.applications.lang.ruby import TestApplicationRuby +from unit.feature.isolation import TestFeatureIsolation + + +class TestRubyIsolation(TestApplicationRuby): + prerequisites = {'modules': {'ruby': 'any'}, 'features': ['isolation']} + + isolation = TestFeatureIsolation() + + @classmethod + def setUpClass(cls, complete_check=True): + unit = super().setUpClass(complete_check=False) + + TestFeatureIsolation().check(cls.available, unit.testdir) + + return unit if not complete_check else unit.complete() + + def test_ruby_isolation_rootfs(self): + isolation_features = self.available['features']['isolation'].keys() + + if 'mnt' not in isolation_features: + print('requires mnt ns') + raise unittest.SkipTest() + + if not self.is_su: + if 'user' not in isolation_features: + print('requires unprivileged userns or root') + raise unittest.SkipTest() + + if not 'unprivileged_userns_clone' in isolation_features: + print('requires unprivileged userns or root') + raise unittest.SkipTest() + + os.mkdir(self.testdir + '/ruby') + + shutil.copytree( + self.current_dir + '/ruby/status_int', + self.testdir + '/ruby/status_int', + ) + isolation = { + 'namespaces': {'credential': not self.is_su, 'mount': True}, + 'rootfs': self.testdir, + } + + self.load('status_int', isolation=isolation) + + self.assertIn( + 'success', + self.conf( + '"/ruby/status_int/config.ru"', + 'applications/status_int/script', + ), + ) + + self.assertIn( + 'success', + self.conf( + '"/ruby/status_int"', + 'applications/status_int/working_directory', + ), + ) + + self.assertEqual(self.get()['status'], 200, 'status int') + + +if __name__ == '__main__': + TestRubyIsolation.main() diff --git a/test/test_settings.py b/test/test_settings.py index 9de3a928..6600358d 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -1,11 +1,12 @@ -import time import socket +import time import unittest + from unit.applications.lang.python import TestApplicationPython class TestSettings(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def test_settings_header_read_timeout(self): self.load('empty') diff --git a/test/test_share_fallback.py b/test/test_share_fallback.py index c51e43ee..ca5e2678 100644 --- a/test/test_share_fallback.py +++ b/test/test_share_fallback.py @@ -1,5 +1,5 @@ import os -import unittest + from unit.applications.proto import TestApplicationProto @@ -125,18 +125,23 @@ class TestStatic(TestApplicationProto): self.assertEqual(resp['status'], 200, 'fallback proxy status') self.assertEqual(resp['body'], '', 'fallback proxy') - @unittest.skip('not yet') - def test_fallback_proxy_cycle(self): + def test_fallback_proxy_loop(self): + self.skip_alerts.extend( + [ + r'open.*/blah/index.html.*failed', + r'accept.*failed', + r'socket.*failed', + r'new connections are not accepted', + ] + ) + self.action_update( - { - "share": "/blah", - "fallback": {"proxy": "http://127.0.0.1:7080"}, - } + {"share": "/blah", "fallback": {"proxy": "http://127.0.0.1:7080"}} ) - self.assertNotEqual(self.get()['status'], 200, 'fallback cycle') + self.get(no_recv=True) self.assertIn('success', self.conf_delete('listeners/*:7081')) - self.assertNotEqual(self.get()['status'], 200, 'fallback cycle 2') + self.get(read_timeout=1) def test_fallback_invalid(self): def check_error(conf): diff --git a/test/test_static.py b/test/test_static.py index b2489aa0..bee5db28 100644 --- a/test/test_static.py +++ b/test/test_static.py @@ -1,6 +1,7 @@ import os import socket import unittest + from unit.applications.proto import TestApplicationProto diff --git a/test/test_tls.py b/test/test_tls.py index d9dcf237..a0434174 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -1,14 +1,14 @@ import io -import os import re import ssl import subprocess import unittest + from unit.applications.tls import TestApplicationTLS class TestTLS(TestApplicationTLS): - prerequisites = {'modules': ['python', 'openssl']} + prerequisites = {'modules': {'python': 'any', 'openssl': 'any'}} def findall(self, pattern): with open(self.testdir + '/unit.log', 'r', errors='ignore') as f: diff --git a/test/test_upstreams_rr.py b/test/test_upstreams_rr.py index 7045318a..2f74fbde 100644 --- a/test/test_upstreams_rr.py +++ b/test/test_upstreams_rr.py @@ -1,11 +1,11 @@ import os import re -import unittest + from unit.applications.lang.python import TestApplicationPython class TestUpstreamsRR(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def setUp(self): super().setUp() diff --git a/test/test_usr1.py b/test/test_usr1.py index 155303ea..d1db652f 100644 --- a/test/test_usr1.py +++ b/test/test_usr1.py @@ -1,11 +1,11 @@ import os -import unittest from subprocess import call + from unit.applications.lang.python import TestApplicationPython class TestUSR1(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def test_usr1_access_log(self): self.load('empty') diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py index e0f83c0a..83bde4d8 100644 --- a/test/unit/applications/lang/go.py +++ b/test/unit/applications/lang/go.py @@ -1,5 +1,6 @@ import os import subprocess + from unit.applications.proto import TestApplicationProto @@ -18,26 +19,36 @@ class TestApplicationGo(TestApplicationProto): return unit if not complete_check else unit.complete() - def prepare_env(self, script, name): + def prepare_env(self, script, name, static=False): if not os.path.exists(self.testdir + '/go'): os.mkdir(self.testdir + '/go') env = os.environ.copy() env['GOPATH'] = self.pardir + '/build/go' - try: - process = subprocess.Popen( - [ - 'go', - 'build', - '-o', - self.testdir + '/go/' + name, - self.current_dir + '/go/' + script + '/' + name + '.go', - ], - env=env, - stderr=subprocess.STDOUT, - ) + if static: + args = [ + 'go', + 'build', + '-tags', + 'netgo', + '-ldflags', + '-extldflags "-static"', + '-o', + self.testdir + '/go/' + name, + self.current_dir + '/go/' + script + '/' + name + '.go', + ] + else: + args = [ + 'go', + 'build', + '-o', + self.testdir + '/go/' + name, + self.current_dir + '/go/' + script + '/' + name + '.go', + ] + try: + process = subprocess.Popen(args, env=env) process.communicate() except: @@ -46,21 +57,28 @@ class TestApplicationGo(TestApplicationProto): return process def load(self, script, name='app', **kwargs): - self.prepare_env(script, name) - - self._load_conf( - { - "listeners": {"*:7080": {"pass": "applications/" + script}}, - "applications": { - script: { - "type": "external", - "processes": {"spare": 0}, - "working_directory": self.current_dir - + "/go/" - + script, - "executable": self.testdir + "/go/" + name, - } + static_build = False + + wdir = self.current_dir + "/go/" + script + executable = self.testdir + "/go/" + name + + if 'isolation' in kwargs and 'rootfs' in kwargs['isolation']: + wdir = "/go/" + executable = "/go/" + name + static_build = True + + self.prepare_env(script, name, static=static_build) + + conf = { + "listeners": {"*:7080": {"pass": "applications/" + script}}, + "applications": { + script: { + "type": "external", + "processes": {"spare": 0}, + "working_directory": wdir, + "executable": executable, }, }, - **kwargs - ) + } + + self._load_conf(conf, **kwargs) diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index a8a09ce5..c2c6dc51 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -1,7 +1,8 @@ -import os import glob +import os import shutil import subprocess + from unit.applications.proto import TestApplicationProto diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py index d818298f..cf2a99f6 100644 --- a/test/unit/applications/lang/node.py +++ b/test/unit/applications/lang/node.py @@ -1,5 +1,7 @@ import os import shutil +from urllib.parse import quote + from unit.applications.proto import TestApplicationProto @@ -33,7 +35,9 @@ class TestApplicationNode(TestApplicationProto): self._load_conf( { - "listeners": {"*:7080": {"pass": "applications/" + script}}, + "listeners": { + "*:7080": {"pass": "applications/" + quote(script, '')} + }, "applications": { script: { "type": "external", diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py index fdda024a..31a04107 100644 --- a/test/unit/applications/lang/python.py +++ b/test/unit/applications/lang/python.py @@ -1,3 +1,6 @@ +import shutil +import os + from unit.applications.proto import TestApplicationProto @@ -8,7 +11,21 @@ class TestApplicationPython(TestApplicationProto): if name is None: name = script - script_path = self.current_dir + '/python/' + script + if script[0] == '/': + script_path = script + else: + script_path = self.current_dir + '/python/' + script + + if kwargs.get('isolation') and kwargs['isolation'].get('rootfs'): + rootfs = kwargs['isolation']['rootfs'] + + if not os.path.exists(rootfs + '/app/python/'): + os.makedirs(rootfs + '/app/python/') + + if not os.path.exists(rootfs + '/app/python/' + name): + shutil.copytree(script_path, rootfs + '/app/python/' + name) + + script_path = '/app/python/' + name self._load_conf( { diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py index ae1af354..244cb5be 100644 --- a/test/unit/applications/proto.py +++ b/test/unit/applications/proto.py @@ -1,5 +1,6 @@ import re import time + from unit.control import TestControl diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index 9213974a..e6a846b2 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -2,6 +2,7 @@ import os import re import ssl import subprocess + from unit.applications.proto import TestApplicationProto diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py index fc15e8e4..e0dd2c0d 100644 --- a/test/unit/applications/websockets.py +++ b/test/unit/applications/websockets.py @@ -1,10 +1,11 @@ -import re -import random import base64 -import struct -import select import hashlib import itertools +import random +import re +import select +import struct + from unit.applications.proto import TestApplicationProto GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" diff --git a/test/unit/control.py b/test/unit/control.py index 0b344ed5..029072b5 100644 --- a/test/unit/control.py +++ b/test/unit/control.py @@ -1,4 +1,5 @@ import json + from unit.http import TestHTTP diff --git a/test/unit/feature/isolation.py b/test/unit/feature/isolation.py index 3f474993..4f33d04a 100644 --- a/test/unit/feature/isolation.py +++ b/test/unit/feature/isolation.py @@ -1,6 +1,5 @@ import os -import json -from unit.applications.proto import TestApplicationProto + from unit.applications.lang.go import TestApplicationGo from unit.applications.lang.java import TestApplicationJava from unit.applications.lang.node import TestApplicationNode @@ -8,6 +7,7 @@ from unit.applications.lang.perl import TestApplicationPerl from unit.applications.lang.php import TestApplicationPHP from unit.applications.lang.python import TestApplicationPython from unit.applications.lang.ruby import TestApplicationRuby +from unit.applications.proto import TestApplicationProto class TestFeatureIsolation(TestApplicationProto): diff --git a/test/unit/http.py b/test/unit/http.py index 13384dc8..de3bb2a4 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -1,11 +1,12 @@ import binascii import io +import json import os import re -import time -import json -import socket import select +import socket +import time + from unit.main import TestUnit diff --git a/test/unit/main.py b/test/unit/main.py index 4507f71a..408cf31c 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -1,17 +1,17 @@ +import argparse +import atexit +import fcntl import os +import platform import re -import sys -import stat -import time -import fcntl -import atexit import shutil import signal -import argparse -import platform +import stat +import subprocess +import sys import tempfile +import time import unittest -import subprocess from multiprocessing import Process @@ -52,9 +52,22 @@ class TestUnit(unittest.TestCase): type = self.application_type for module in self.prerequisites['modules']: if module in self.available['modules']: - for version in self.available['modules'][module]: - self.application_type = type + ' ' + version + prereq_version = self.prerequisites['modules'][module] + available_versions = self.available['modules'][module] + + if prereq_version == 'all': + for version in available_versions: + self.application_type = type + ' ' + version + self.application_version = version + super().run(result) + elif prereq_version == 'any': + self.application_type = type + ' ' + available_versions[0] super().run(result) + else: + for version in available_versions: + if version.startswith(prereq_version): + self.application_type = type + ' ' + version + super().run(result) @classmethod def main(cls): @@ -90,7 +103,7 @@ class TestUnit(unittest.TestCase): break if m is None: - unit.stop() + unit._print_log() exit("Unit is writing log too long") # discover available modules from unit.log @@ -153,7 +166,7 @@ class TestUnit(unittest.TestCase): self._run() def _run(self): - build_dir = self.pardir + '/build' + build_dir = os.path.join(self.pardir, 'build') self.unitd = build_dir + '/unitd' if not os.path.isfile(self.unitd): @@ -186,6 +199,7 @@ class TestUnit(unittest.TestCase): atexit.register(self.stop) if not self.waitforfiles(self.testdir + '/control.unit.sock'): + self._print_log() exit("Could not start unit") self.skip_alerts = [ @@ -398,4 +412,3 @@ class TestUnit(unittest.TestCase): data = f.read() print(data) - |