summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/php/cwd/index.php3
-rw-r--r--test/php/error_log/index.php3
-rw-r--r--test/test_configuration.py31
-rw-r--r--test/test_php_application.py26
-rw-r--r--test/test_php_targets.py1
-rw-r--r--test/test_proxy_chunked.py249
-rw-r--r--test/test_python_application.py11
-rw-r--r--test/test_python_procman.py1
-rw-r--r--test/test_routing.py70
-rw-r--r--test/test_variables.py94
-rw-r--r--test/unit/applications/lang/python.py2
-rw-r--r--test/unit/main.py29
12 files changed, 500 insertions, 20 deletions
diff --git a/test/php/cwd/index.php b/test/php/cwd/index.php
index 24ae3a21..de3797e4 100644
--- a/test/php/cwd/index.php
+++ b/test/php/cwd/index.php
@@ -10,7 +10,8 @@ if (isset($_GET['chdir']) && $_GET['chdir'] != "") {
$opcache = -1;
if (function_exists('opcache_get_status')) {
- $opcache = opcache_get_status()->opcache_enabled;
+ $status = opcache_get_status();
+ $opcache = $status['opcache_enabled'];
}
header('X-OPcache: ' . $opcache);
diff --git a/test/php/error_log/index.php b/test/php/error_log/index.php
new file mode 100644
index 00000000..fd90adfe
--- /dev/null
+++ b/test/php/error_log/index.php
@@ -0,0 +1,3 @@
+<?php
+error_log("Error in application");
+?>
diff --git a/test/test_configuration.py b/test/test_configuration.py
index 0329ef5e..0b0c9c78 100644
--- a/test/test_configuration.py
+++ b/test/test_configuration.py
@@ -400,13 +400,42 @@ class TestConfiguration(TestControl):
"path": "/app",
"module": "wsgi",
}
- for a in range(999)
+ # Larger number of applications can cause test fail with default
+ # open files limit due to the lack of file descriptors.
+ for a in range(100)
},
"listeners": {"*:7080": {"pass": "applications/app-1"}},
}
self.assertIn('success', self.conf(conf))
+ def test_unprivileged_user_error(self):
+ self.skip_alerts.extend(
+ [
+ r'cannot set user "root"',
+ r'failed to apply new conf',
+ ]
+ )
+ if self.is_su:
+ print('unprivileged tests, skip this')
+ raise unittest.SkipTest()
+
+ self.assertIn(
+ 'error',
+ self.conf(
+ {
+ "app": {
+ "type": "external",
+ "processes": 1,
+ "executable": "/app",
+ "user": "root",
+ }
+ },
+ 'applications',
+ ),
+ 'setting user',
+ )
+
if __name__ == '__main__':
TestConfiguration.main()
diff --git a/test/test_php_application.py b/test/test_php_application.py
index 1259d22d..d8bfade2 100644
--- a/test/test_php_application.py
+++ b/test/test_php_application.py
@@ -1,5 +1,7 @@
import os
+import re
import shutil
+import time
import unittest
from unit.applications.lang.php import TestApplicationPHP
@@ -488,6 +490,30 @@ class TestPHPApplication(TestApplicationPHP):
self.get()['body'], r'012345', 'disable_classes before'
)
+ def test_php_application_error_log(self):
+ self.load('error_log')
+
+ self.assertEqual(self.get()['status'], 200, 'status')
+
+ time.sleep(1)
+
+ self.assertEqual(self.get()['status'], 200, 'status 2')
+
+ self.stop()
+
+ pattern = r'\d{4}\/\d\d\/\d\d\s\d\d:.+\[notice\].+Error in application'
+
+ self.assertIsNotNone(self.wait_for_record(pattern), 'errors print')
+
+ with open(self.testdir + '/unit.log', 'r', errors='ignore') as f:
+ errs = re.findall(pattern, f.read())
+
+ self.assertEqual(len(errs), 2, 'error_log count')
+
+ date = errs[0].split('[')[0]
+ date2 = errs[1].split('[')[0]
+ self.assertNotEqual(date, date2, 'date diff')
+
def test_php_application_script(self):
self.assertIn(
'success',
diff --git a/test/test_php_targets.py b/test/test_php_targets.py
index 9c1ba2a6..0657554a 100644
--- a/test/test_php_targets.py
+++ b/test/test_php_targets.py
@@ -1,4 +1,3 @@
-import unittest
from unit.applications.lang.php import TestApplicationPHP
class TestPHPTargets(TestApplicationPHP):
diff --git a/test/test_proxy_chunked.py b/test/test_proxy_chunked.py
new file mode 100644
index 00000000..f344b69a
--- /dev/null
+++ b/test/test_proxy_chunked.py
@@ -0,0 +1,249 @@
+import re
+import select
+import socket
+import time
+
+from unit.applications.lang.python import TestApplicationPython
+
+
+class TestProxyChunked(TestApplicationPython):
+ prerequisites = {'modules': {'python': 'any'}}
+
+ SERVER_PORT = 7999
+
+ @staticmethod
+ def run_server(server_port, testdir):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+
+ server_address = ('127.0.0.1', server_port)
+ sock.bind(server_address)
+ sock.listen(10)
+
+ def recvall(sock):
+ buff_size = 4096 * 4096
+ data = b''
+ while True:
+ rlist = select.select([sock], [], [], 0.1)
+
+ if not rlist[0]:
+ break
+
+ part = sock.recv(buff_size)
+ data += part
+
+ if not len(part):
+ break
+
+ return data
+
+ while True:
+ connection, client_address = sock.accept()
+
+ req = """HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked"""
+
+ data = recvall(connection).decode()
+
+ m = re.search('\x0d\x0a\x0d\x0a(.*)', data, re.M | re.S)
+ if m is not None:
+ body = m.group(1)
+
+ for line in re.split('\r\n', body):
+ add = ''
+ m1 = re.search('(.*)\sX\s(\d+)', line)
+
+ if m1 is not None:
+ add = m1.group(1) * int(m1.group(2))
+ else:
+ add = line
+
+ req = req + add + '\r\n'
+
+ for chunk in re.split(r'([@#])', req):
+ if chunk == '@' or chunk == '#':
+ if chunk == '#':
+ time.sleep(0.1)
+ continue
+
+ connection.sendall(chunk.encode())
+
+ connection.close()
+
+ def chunks(self, chunks):
+ body = '\r\n\r\n'
+
+ for l, c in chunks:
+ body = body + l + '\r\n' + c + '\r\n'
+
+ return body + '0\r\n\r\n'
+
+ def get_http10(self, *args, **kwargs):
+ return self.get(*args, http_10=True, **kwargs)
+
+ def setUp(self):
+ super().setUp()
+
+ self.run_process(self.run_server, self.SERVER_PORT, self.testdir)
+ self.waitforsocket(self.SERVER_PORT)
+
+ self.assertIn(
+ 'success',
+ self.conf(
+ {
+ "listeners": {"*:7080": {"pass": "routes"},},
+ "routes": [
+ {
+ "action": {
+ "proxy": "http://127.0.0.1:"
+ + str(self.SERVER_PORT)
+ }
+ }
+ ],
+ }
+ ),
+ 'proxy initial configuration',
+ )
+
+ def test_proxy_chunked(self):
+ for _ in range(10):
+ self.assertEqual(
+ self.get_http10(body='\r\n\r\n0\r\n\r\n')['status'], 200
+ )
+
+ def test_proxy_chunked_body(self):
+ part = '0123456789abcdef'
+
+ self.assertEqual(
+ self.get_http10(body=self.chunks([('1000', part + ' X 256')]))[
+ 'body'
+ ],
+ part * 256,
+ )
+ self.assertEqual(
+ self.get_http10(body=self.chunks([('100000', part + ' X 65536')]))[
+ 'body'
+ ],
+ part * 65536,
+ )
+ self.assertEqual(
+ self.get_http10(
+ body=self.chunks([('1000000', part + ' X 1048576')]),
+ read_buffer_size=4096 * 4096,
+ )['body'],
+ part * 1048576,
+ )
+
+ self.assertEqual(
+ self.get_http10(
+ body=self.chunks(
+ [('1000', part + ' X 256'), ('1000', part + ' X 256')]
+ )
+ )['body'],
+ part * 256 * 2,
+ )
+ self.assertEqual(
+ self.get_http10(
+ body=self.chunks(
+ [
+ ('100000', part + ' X 65536'),
+ ('100000', part + ' X 65536'),
+ ]
+ )
+ )['body'],
+ part * 65536 * 2,
+ )
+ self.assertEqual(
+ self.get_http10(
+ body=self.chunks(
+ [
+ ('1000000', part + ' X 1048576'),
+ ('1000000', part + ' X 1048576'),
+ ]
+ ),
+ read_buffer_size=4096 * 4096,
+ )['body'],
+ part * 1048576 * 2,
+ )
+
+ def test_proxy_chunked_fragmented(self):
+ part = '0123456789abcdef'
+
+ self.assertEqual(
+ self.get_http10(
+ body=self.chunks(
+ [('1', hex(i % 16)[2:]) for i in range(4096)]
+ ),
+ )['body'],
+ part * 256,
+ )
+
+ def test_proxy_chunked_send(self):
+ self.assertEqual(
+ self.get_http10(body='\r\n\r\n@0@\r\n\r\n')['status'], 200
+ )
+ self.assertEqual(
+ self.get_http10(
+ body='\r@\n\r\n2\r@\na@b\r\n2\r\ncd@\r\n0\r@\n\r\n'
+ )['body'],
+ 'abcd',
+ )
+ self.assertEqual(
+ self.get_http10(
+ body='\r\n\r\n2\r#\na#b\r\n##2\r\n#cd\r\n0\r\n#\r#\n'
+ )['body'],
+ 'abcd',
+ )
+
+ def test_proxy_chunked_invalid(self):
+ def check_invalid(body):
+ self.assertNotEqual(self.get_http10(body=body)['status'], 200)
+
+ check_invalid('\r\n\r0')
+ check_invalid('\r\n\r\n\r0')
+ check_invalid('\r\n\r\n\r\n0')
+ check_invalid('\r\nContent-Length: 5\r\n\r\n0\r\n\r\n')
+ check_invalid('\r\n\r\n1\r\nXX\r\n0\r\n\r\n')
+ check_invalid('\r\n\r\n2\r\nX\r\n0\r\n\r\n')
+ check_invalid('\r\n\r\nH\r\nXX\r\n0\r\n\r\n')
+ check_invalid('\r\n\r\n0\r\nX')
+
+ resp = self.get_http10(body='\r\n\r\n65#\r\nA X 100')
+ self.assertEqual(resp['status'], 200, 'incomplete chunk status')
+ self.assertNotEqual(resp['body'][-5:], '0\r\n\r\n', 'incomplete chunk')
+
+ resp = self.get_http10(body='\r\n\r\n64#\r\nA X 100')
+ self.assertEqual(resp['status'], 200, 'no zero chunk status')
+ self.assertNotEqual(resp['body'][-5:], '0\r\n\r\n', 'no zero chunk')
+
+ self.assertEqual(
+ self.get_http10(body='\r\n\r\n80000000\r\nA X 100')['status'], 200,
+ )
+ self.assertEqual(
+ self.get_http10(body='\r\n\r\n10000000000000000\r\nA X 100')[
+ 'status'
+ ],
+ 502,
+ )
+ self.assertGreaterEqual(
+ len(
+ self.get_http10(
+ body='\r\n\r\n1000000\r\nA X 1048576\r\n1000000\r\nA X 100',
+ read_buffer_size=4096 * 4096,
+ )['body']
+ ),
+ 1048576,
+ )
+ self.assertGreaterEqual(
+ len(
+ self.get_http10(
+ body='\r\n\r\n1000000\r\nA X 1048576\r\nXXX\r\nA X 100',
+ read_buffer_size=4096 * 4096,
+ )['body']
+ ),
+ 1048576,
+ )
+
+
+if __name__ == '__main__':
+ TestProxyChunked.main()
diff --git a/test/test_python_application.py b/test/test_python_application.py
index 8bd3f750..4b8983ff 100644
--- a/test/test_python_application.py
+++ b/test/test_python_application.py
@@ -579,6 +579,17 @@ last line: 987654321
self.assertEqual(self.get()['status'], 500, 'syntax error')
+ def test_python_application_loading_error(self):
+ self.skip_alerts.append(r'Python failed to import module "blah"')
+
+ self.load('empty')
+
+ self.assertIn(
+ 'success', self.conf('"blah"', 'applications/empty/module'),
+ )
+
+ self.assertEqual(self.get()['status'], 503, 'loading error')
+
def test_python_application_close(self):
self.load('close')
diff --git a/test/test_python_procman.py b/test/test_python_procman.py
index 8613f58e..c327ab14 100644
--- a/test/test_python_procman.py
+++ b/test/test_python_procman.py
@@ -33,6 +33,7 @@ class TestPythonProcman(TestApplicationPython):
self.assertIn('success', self.conf(conf, path), 'configure processes')
+ @unittest.skip('not yet')
def test_python_processes_idle_timeout_zero(self):
self.conf_proc({"spare": 0, "max": 2, "idle_timeout": 0})
diff --git a/test/test_routing.py b/test/test_routing.py
index 3cf4009c..269e8efc 100644
--- a/test/test_routing.py
+++ b/test/test_routing.py
@@ -115,10 +115,41 @@ class TestRouting(TestApplicationProto):
def test_routes_match_invalid(self):
self.route_match_invalid({"method": "**"})
- self.route_match_invalid({"method": "blah**"})
- self.route_match_invalid({"host": "*blah*blah"})
- self.route_match_invalid({"host": "blah*blah*blah"})
- self.route_match_invalid({"host": "blah*blah*"})
+
+ def test_routes_match_valid(self):
+ self.route_match({"method": "blah*"})
+ self.route_match({"host": "*blah*blah"})
+ self.route_match({"host": "blah*blah*blah"})
+ self.route_match({"host": "blah*blah*"})
+
+ def test_routes_match_empty_exact(self):
+ self.route_match({"uri": ""})
+ self.assertEqual(self.get()['status'], 404)
+
+ self.route_match({"uri": "/"})
+ self.assertEqual(self.get()['status'], 200)
+ self.assertEqual(self.get(url='/blah')['status'], 404)
+
+ def test_routes_match_negative(self):
+ self.route_match({"uri": "!"})
+ self.assertEqual(self.get()['status'], 404)
+
+ self.route_match({"uri": "!/"})
+ self.assertEqual(self.get()['status'], 404)
+ self.assertEqual(self.get(url='/blah')['status'], 200)
+
+ self.route_match({"uri": "!*blah"})
+ self.assertEqual(self.get()['status'], 200)
+ self.assertEqual(self.get(url='/bla')['status'], 200)
+ self.assertEqual(self.get(url='/blah')['status'], 404)
+ self.assertEqual(self.get(url='/blah1')['status'], 200)
+
+ self.route_match({"uri": "!/blah*1*"})
+ self.assertEqual(self.get()['status'], 200)
+ self.assertEqual(self.get(url='/blah')['status'], 200)
+ self.assertEqual(self.get(url='/blah1')['status'], 404)
+ self.assertEqual(self.get(url='/blah12')['status'], 404)
+ self.assertEqual(self.get(url='/blah2')['status'], 200)
def test_routes_match_wildcard_middle(self):
self.route_match({"host": "ex*le"})
@@ -169,6 +200,27 @@ class TestRouting(TestApplicationProto):
self.assertEqual(self.get(url='/blah')['status'], 200, '/blah')
self.assertEqual(self.get(url='/BLAH')['status'], 404, '/BLAH')
+ def test_route_match_wildcards_ordered(self):
+ self.route_match({"uri": "/a*x*y*"})
+
+ self.assertEqual(self.get(url='/axy')['status'], 200, '/axy')
+ self.assertEqual(self.get(url='/ayx')['status'], 404, '/ayx')
+
+ def test_route_match_wildcards_adjust_start(self):
+ self.route_match({"uri": "/bla*bla*"})
+
+ self.assertEqual(self.get(url='/bla_foo')['status'], 404, '/bla_foo')
+
+ def test_route_match_wildcards_adjust_start_substr(self):
+ self.route_match({"uri": "*bla*bla*"})
+
+ self.assertEqual(self.get(url='/bla_foo')['status'], 404, '/bla_foo')
+
+ def test_route_match_wildcards_adjust_end(self):
+ self.route_match({"uri": "/bla*bla"})
+
+ self.assertEqual(self.get(url='/foo_bla')['status'], 404, '/foo_bla')
+
def test_routes_match_wildcard_right_case_sensitive(self):
self.route_match({"uri": "/bla*"})
@@ -181,6 +233,15 @@ class TestRouting(TestApplicationProto):
self.assertEqual(self.get(url='/blah')['status'], 200, '/blah')
self.assertEqual(self.get(url='/BLAH')['status'], 404, '/BLAH')
+ def test_routes_match_many_wildcard_substrings_case_sensitive(self):
+ self.route_match({"uri": "*a*B*c*"})
+
+ self.assertEqual(self.get(url='/blah-a-B-c-blah')['status'], 200)
+ self.assertEqual(self.get(url='/a-B-c')['status'], 200)
+ self.assertEqual(self.get(url='/aBc')['status'], 200)
+ self.assertEqual(self.get(url='/aBCaBbc')['status'], 200)
+ self.assertEqual(self.get(url='/ABc')['status'], 404)
+
def test_routes_pass_encode(self):
def check_pass(path, name):
self.assertIn(
@@ -1362,7 +1423,6 @@ class TestRouting(TestApplicationProto):
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"}})
diff --git a/test/test_variables.py b/test/test_variables.py
new file mode 100644
index 00000000..805c5144
--- /dev/null
+++ b/test/test_variables.py
@@ -0,0 +1,94 @@
+from unit.applications.proto import TestApplicationProto
+
+
+class TestVariables(TestApplicationProto):
+ prerequisites = {}
+
+ def setUp(self):
+ super().setUp()
+
+ self.assertIn(
+ 'success',
+ self.conf(
+ {
+ "listeners": {"*:7080": {"pass": "routes/$method"}},
+ "routes": {
+ "GET": [{"action": {"return": 201}}],
+ "POST": [{"action": {"return": 202}}],
+ "3": [{"action": {"return": 203}}],
+ "4": [{"action": {"return": 204}}],
+ "blahGET}": [{"action": {"return": 205}}],
+ "5GET": [{"action": {"return": 206}}],
+ "GETGET": [{"action": {"return": 207}}],
+ },
+ },
+ ),
+ 'configure routes',
+ )
+
+ def conf_routes(self, routes):
+ self.assertIn(
+ 'success',
+ self.conf(routes, 'listeners/*:7080/pass')
+ )
+
+ def test_variables_method(self):
+ self.assertEqual(self.get()['status'], 201, 'method GET')
+ self.assertEqual(self.post()['status'], 202, 'method POST')
+
+ def test_variables_uri(self):
+ self.conf_routes("\"routes$uri\"")
+
+ self.assertEqual(self.get(url='/3')['status'], 203, 'uri')
+ self.assertEqual(self.get(url='/4')['status'], 204, 'uri 2')
+
+ def test_variables_many(self):
+ self.conf_routes("\"routes$uri$method\"")
+ self.assertEqual(self.get(url='/5')['status'], 206, 'many')
+
+ self.conf_routes("\"routes${uri}${method}\"")
+ self.assertEqual(self.get(url='/5')['status'], 206, 'many 2')
+
+ self.conf_routes("\"routes${uri}$method\"")
+ self.assertEqual(self.get(url='/5')['status'], 206, 'many 3')
+
+ self.conf_routes("\"routes/$method$method\"")
+ self.assertEqual(self.get()['status'], 207, 'many 4')
+
+ self.conf_routes("\"routes/$method$uri\"")
+ self.assertEqual(self.get()['status'], 404, 'no route')
+ self.assertEqual(self.get(url='/blah')['status'], 404, 'no route 2')
+
+ def test_variables_replace(self):
+ self.assertEqual(self.get()['status'], 201)
+
+ self.conf_routes("\"routes$uri\"")
+ self.assertEqual(self.get(url='/3')['status'], 203)
+
+ self.conf_routes("\"routes/${method}\"")
+ self.assertEqual(self.post()['status'], 202)
+
+ self.conf_routes("\"routes${uri}\"")
+ self.assertEqual(self.get(url='/4')['status'], 204)
+
+ self.conf_routes("\"routes/blah$method}\"")
+ self.assertEqual(self.get()['status'], 205)
+
+ def test_variables_invalid(self):
+ def check_variables(routes):
+ self.assertIn(
+ 'error',
+ self.conf(routes, 'listeners/*:7080/pass'),
+ 'invalid variables',
+ )
+
+ check_variables("\"routes$\"")
+ check_variables("\"routes${\"")
+ check_variables("\"routes${}\"")
+ check_variables("\"routes$ur\"")
+ check_variables("\"routes$uriblah\"")
+ check_variables("\"routes${uri\"")
+ check_variables("\"routes${{uri}\"")
+
+if __name__ == '__main__':
+ TestVariables.main()
diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py
index 31a04107..91559f4b 100644
--- a/test/unit/applications/lang/python.py
+++ b/test/unit/applications/lang/python.py
@@ -1,5 +1,5 @@
-import shutil
import os
+import shutil
from unit.applications.proto import TestApplicationProto
diff --git a/test/unit/main.py b/test/unit/main.py
index 408cf31c..83aa9139 100644
--- a/test/unit/main.py
+++ b/test/unit/main.py
@@ -58,7 +58,6 @@ class TestUnit(unittest.TestCase):
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]
@@ -166,7 +165,7 @@ class TestUnit(unittest.TestCase):
self._run()
def _run(self):
- build_dir = os.path.join(self.pardir, 'build')
+ build_dir = self.pardir + '/build'
self.unitd = build_dir + '/unitd'
if not os.path.isfile(self.unitd):
@@ -202,6 +201,8 @@ class TestUnit(unittest.TestCase):
self._print_log()
exit("Could not start unit")
+ self._started = True
+
self.skip_alerts = [
r'read signalfd\(4\) failed',
r'sendmsg.+failed',
@@ -210,7 +211,7 @@ class TestUnit(unittest.TestCase):
self.skip_sanitizer = False
def tearDown(self):
- stop_errs = self.stop()
+ self.stop()
# detect errors and failures for current test
@@ -245,18 +246,21 @@ class TestUnit(unittest.TestCase):
else:
self._print_log()
- self.assertListEqual(stop_errs, [None, None], 'stop errors')
+ self.assertListEqual(self.stop_errors, [None, None], 'stop errors')
def stop(self):
- errors = []
+ if not self._started:
+ return
+
+ self.stop_errors = []
- errors.append(self._stop())
+ self.stop_errors.append(self._stop())
- errors.append(self.stop_processes())
+ self.stop_errors.append(self.stop_processes())
atexit.unregister(self.stop)
- return errors
+ self._started = False
def _stop(self):
if self._p.poll() is not None:
@@ -407,8 +411,11 @@ class TestUnit(unittest.TestCase):
print('Path to unit.log:\n' + path + '\n')
if TestUnit.print_log:
+ os.set_blocking(sys.stdout.fileno(), True)
+ sys.stdout.flush()
+
if data is None:
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
- data = f.read()
-
- print(data)
+ shutil.copyfileobj(f, sys.stdout)
+ else:
+ sys.stdout.write(data)