diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/php/cwd/index.php | 3 | ||||
-rw-r--r-- | test/php/error_log/index.php | 3 | ||||
-rw-r--r-- | test/test_configuration.py | 31 | ||||
-rw-r--r-- | test/test_php_application.py | 26 | ||||
-rw-r--r-- | test/test_php_targets.py | 1 | ||||
-rw-r--r-- | test/test_proxy_chunked.py | 249 | ||||
-rw-r--r-- | test/test_python_application.py | 11 | ||||
-rw-r--r-- | test/test_python_procman.py | 1 | ||||
-rw-r--r-- | test/test_routing.py | 70 | ||||
-rw-r--r-- | test/test_variables.py | 94 | ||||
-rw-r--r-- | test/unit/applications/lang/python.py | 2 | ||||
-rw-r--r-- | test/unit/main.py | 29 |
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) |