summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/go/ns_inspect/app.go21
-rw-r--r--test/php/targets/1.php4
-rw-r--r--test/php/targets/2/2.php4
-rw-r--r--test/php/targets/index.php4
-rw-r--r--test/python/ns_inspect/wsgi.py31
-rwxr-xr-xtest/run.py5
-rw-r--r--test/test_access_log.py5
-rw-r--r--test/test_configuration.py3
-rw-r--r--test/test_go_application.py2
-rw-r--r--test/test_go_isolation.py52
-rw-r--r--test/test_go_isolation_rootfs.py34
-rw-r--r--test/test_http_header.py3
-rw-r--r--test/test_java_application.py5
-rw-r--r--test/test_java_isolation_rootfs.py85
-rw-r--r--test/test_java_websockets.py5
-rw-r--r--test/test_node_application.py3
-rw-r--r--test/test_node_websockets.py5
-rw-r--r--test/test_perl_application.py3
-rw-r--r--test/test_php_application.py4
-rw-r--r--test/test_php_basic.py2
-rw-r--r--test/test_php_isolation.py57
-rw-r--r--test/test_php_targets.py129
-rw-r--r--test/test_proxy.py113
-rw-r--r--test/test_python_application.py6
-rw-r--r--test/test_python_basic.py2
-rw-r--r--test/test_python_environment.py2
-rw-r--r--test/test_python_isolation.py79
-rw-r--r--test/test_python_isolation_chroot.py57
-rw-r--r--test/test_python_procman.py5
-rw-r--r--test/test_respawn.py95
-rw-r--r--test/test_return.py2
-rw-r--r--test/test_routing.py251
-rw-r--r--test/test_routing_tls.py2
-rw-r--r--test/test_ruby_application.py3
-rw-r--r--test/test_ruby_isolation.py71
-rw-r--r--test/test_settings.py5
-rw-r--r--test/test_share_fallback.py23
-rw-r--r--test/test_static.py1
-rw-r--r--test/test_tls.py4
-rw-r--r--test/test_upstreams_rr.py4
-rw-r--r--test/test_usr1.py4
-rw-r--r--test/unit/applications/lang/go.py76
-rw-r--r--test/unit/applications/lang/java.py3
-rw-r--r--test/unit/applications/lang/node.py6
-rw-r--r--test/unit/applications/lang/python.py19
-rw-r--r--test/unit/applications/proto.py1
-rw-r--r--test/unit/applications/tls.py1
-rw-r--r--test/unit/applications/websockets.py9
-rw-r--r--test/unit/control.py1
-rw-r--r--test/unit/feature/isolation.py4
-rw-r--r--test/unit/http.py7
-rw-r--r--test/unit/main.py39
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)
-