diff options
Diffstat (limited to 'test')
29 files changed, 1306 insertions, 105 deletions
diff --git a/test/php/cwd/index.php b/test/php/cwd/index.php new file mode 100644 index 00000000..24ae3a21 --- /dev/null +++ b/test/php/cwd/index.php @@ -0,0 +1,19 @@ +<?php + +if (isset($_GET['chdir']) && $_GET['chdir'] != "") { + if (!chdir($_GET['chdir'])) { + echo "failure to chdir(" . $_GET['chdir'] . ")\n"; + exit; + } +} + +$opcache = -1; + +if (function_exists('opcache_get_status')) { + $opcache = opcache_get_status()->opcache_enabled; +} + +header('X-OPcache: ' . $opcache); + +print(getcwd()); +?> diff --git a/test/php/cwd/subdir/index.php b/test/php/cwd/subdir/index.php new file mode 100644 index 00000000..597bcac4 --- /dev/null +++ b/test/php/cwd/subdir/index.php @@ -0,0 +1 @@ +<?php print(getcwd()); ?> diff --git a/test/php/open/index.php b/test/php/open/index.php new file mode 100644 index 00000000..a5bebc54 --- /dev/null +++ b/test/php/open/index.php @@ -0,0 +1,7 @@ +<?php +if (isset($_GET['chdir'])) { + chdir($_GET['chdir']); +} + +echo file_get_contents('test.txt'); +?> diff --git a/test/php/open/test.txt b/test/php/open/test.txt new file mode 100644 index 00000000..30d74d25 --- /dev/null +++ b/test/php/open/test.txt @@ -0,0 +1 @@ +test
\ No newline at end of file diff --git a/test/python/input_iter/wsgi.py b/test/python/input_iter/wsgi.py index d3bf437f..04a6a06c 100644 --- a/test/python/input_iter/wsgi.py +++ b/test/python/input_iter/wsgi.py @@ -1,5 +1,16 @@ def application(environ, start_response): - body = bytes(environ['wsgi.input'].__iter__()) + body = [] + content_length = 0 - start_response('200', [('Content-Length', str(len(body)))]) - return [body] + for l in environ['wsgi.input'].__iter__(): + body.append(l) + content_length += len(l) + + start_response( + '200', + [ + ('Content-Length', str(content_length)), + ('X-Lines-Count', str(len(body))), + ], + ) + return body diff --git a/test/python/input_readline/wsgi.py b/test/python/input_readline/wsgi.py new file mode 100644 index 00000000..ec42e0c8 --- /dev/null +++ b/test/python/input_readline/wsgi.py @@ -0,0 +1,20 @@ +def application(environ, start_response): + body = [] + content_length = 0 + + while True: + l = environ['wsgi.input'].readline() + if not l: + break + + body.append(l) + content_length += len(l) + + start_response( + '200', + [ + ('Content-Length', str(content_length)), + ('X-Lines-Count', str(len(body))), + ], + ) + return body diff --git a/test/python/input_readline_size/wsgi.py b/test/python/input_readline_size/wsgi.py new file mode 100644 index 00000000..36cf07b0 --- /dev/null +++ b/test/python/input_readline_size/wsgi.py @@ -0,0 +1,16 @@ +def application(environ, start_response): + body = [] + + while True: + l = environ['wsgi.input'].readline(9) + if not l: + break + + body.append(l) + + if len(l) > 9: + body.append(b'len(l) > 9: ' + l) + break + + start_response('200', [('X-Lines-Count', str(len(body)))]) + return body diff --git a/test/python/input_readlines/wsgi.py b/test/python/input_readlines/wsgi.py new file mode 100644 index 00000000..64b03d79 --- /dev/null +++ b/test/python/input_readlines/wsgi.py @@ -0,0 +1,5 @@ +def application(environ, start_response): + body = environ['wsgi.input'].readlines() + + start_response('200', [('X-Lines-Count', str(len(body)))]) + return body diff --git a/test/python/upstreams/0/wsgi.py b/test/python/upstreams/0/wsgi.py new file mode 100644 index 00000000..2c88979b --- /dev/null +++ b/test/python/upstreams/0/wsgi.py @@ -0,0 +1,8 @@ +import time + +def application(env, start_response): + delay = int(env.get('HTTP_X_DELAY', 0)) + + start_response('200', [('Content-Length', '0'), ('X-Upstream', '0')]) + time.sleep(delay) + return [] diff --git a/test/python/upstreams/1/wsgi.py b/test/python/upstreams/1/wsgi.py new file mode 100644 index 00000000..5077bdb1 --- /dev/null +++ b/test/python/upstreams/1/wsgi.py @@ -0,0 +1,8 @@ +import time + +def application(env, start_response): + delay = int(env.get('HTTP_X_DELAY', 0)) + + start_response('200', [('Content-Length', '0'), ('X-Upstream', '1')]) + time.sleep(delay) + return [] diff --git a/test/python/upstreams/2/wsgi.py b/test/python/upstreams/2/wsgi.py new file mode 100644 index 00000000..bb0ce797 --- /dev/null +++ b/test/python/upstreams/2/wsgi.py @@ -0,0 +1,8 @@ +import time + +def application(env, start_response): + delay = int(env.get('HTTP_X_DELAY', 0)) + + start_response('200', [('Content-Length', '0'), ('X-Upstream', '2')]) + time.sleep(delay) + return [] diff --git a/test/run.py b/test/run.py index b79d0484..59e06bcb 100755 --- a/test/run.py +++ b/test/run.py @@ -12,7 +12,7 @@ if __name__ == '__main__': tests = loader.discover(start_dir=this_dir) suite.addTests(tests) - runner = unittest.TextTestRunner(verbosity=3) + runner = unittest.TextTestRunner(stream=sys.stdout, verbosity=3) result = runner.run(suite) ret = not (len(result.failures) == len(result.errors) == 0) diff --git a/test/test_access_log.py b/test/test_access_log.py index 94f6e7bf..898d8b24 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -2,7 +2,6 @@ import os import re import time import unittest -from subprocess import call from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_php_application.py b/test/test_php_application.py index 837181e6..c3645a99 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -1,4 +1,6 @@ +import os import re +import shutil import unittest from unit.applications.lang.php import TestApplicationPHP @@ -11,6 +13,28 @@ class TestPHPApplication(TestApplicationPHP): self.assertRegex(body, r'time: \d+', 'disable_functions before time') self.assertRegex(body, r'exec: \/\w+', 'disable_functions before exec') + def set_opcache(self, app, val): + self.assertIn( + 'success', + self.conf( + { + "admin": { + "opcache.enable": val, + "opcache.enable_cli": val, + }, + }, + 'applications/' + app + '/options', + ), + ) + + opcache = self.get()['headers']['X-OPcache'] + + if not opcache or opcache == '-1': + print('opcache is not supported') + raise unittest.SkipTest() + + self.assertEqual(opcache, val, 'opcache value') + def test_php_application_variables(self): self.load('variables') @@ -464,7 +488,8 @@ class TestPHPApplication(TestApplicationPHP): def test_php_application_script(self): self.assertIn( - 'success', self.conf( + 'success', + self.conf( { "listeners": {"*:7080": {"pass": "applications/script"}}, "applications": { @@ -476,7 +501,8 @@ class TestPHPApplication(TestApplicationPHP): } }, } - ), 'configure script' + ), + 'configure script', ) resp = self.get() @@ -486,7 +512,8 @@ class TestPHPApplication(TestApplicationPHP): def test_php_application_index_default(self): self.assertIn( - 'success', self.conf( + 'success', + self.conf( { "listeners": {"*:7080": {"pass": "applications/phpinfo"}}, "applications": { @@ -497,7 +524,8 @@ class TestPHPApplication(TestApplicationPHP): } }, } - ), 'configure index default' + ), + 'configure index default', ) resp = self.get() @@ -512,5 +540,134 @@ class TestPHPApplication(TestApplicationPHP): self.get(url='/index.wrong')['status'], 200, 'status' ) + new_root = self.testdir + "/php" + os.mkdir(new_root) + shutil.copy(self.current_dir + '/php/phpinfo/index.wrong', new_root) + + self.assertIn( + 'success', + self.conf( + { + "listeners": {"*:7080": {"pass": "applications/phpinfo"}}, + "applications": { + "phpinfo": { + "type": "php", + "processes": {"spare": 0}, + "root": new_root, + "working_directory": new_root, + } + }, + } + ), + 'configure new root', + ) + + resp = self.get() + self.assertNotEqual( + str(resp['status']) + resp['body'], '200', 'status new root' + ) + + def run_php_application_cwd_root_tests(self): + self.assertIn( + 'success', self.conf_delete('applications/cwd/working_directory') + ) + + script_cwd = self.current_dir + '/php/cwd' + + resp = self.get() + self.assertEqual(resp['status'], 200, 'status ok') + self.assertEqual(resp['body'], script_cwd, 'default cwd') + + self.assertIn( + 'success', + self.conf( + '"' + self.current_dir + '"', + 'applications/cwd/working_directory', + ), + ) + + resp = self.get() + self.assertEqual(resp['status'], 200, 'status ok') + self.assertEqual(resp['body'], script_cwd, 'wdir cwd') + + resp = self.get(url='/?chdir=/') + self.assertEqual(resp['status'], 200, 'status ok') + self.assertEqual(resp['body'], '/', 'cwd after chdir') + + # cwd must be restored + + resp = self.get() + self.assertEqual(resp['status'], 200, 'status ok') + self.assertEqual(resp['body'], script_cwd, 'cwd restored') + + resp = self.get(url='/subdir/') + self.assertEqual( + resp['body'], script_cwd + '/subdir', 'cwd subdir', + ) + + def test_php_application_cwd_root(self): + self.load('cwd') + self.run_php_application_cwd_root_tests() + + def test_php_application_cwd_opcache_disabled(self): + self.load('cwd') + self.set_opcache('cwd', '0') + self.run_php_application_cwd_root_tests() + + def test_php_application_cwd_opcache_enabled(self): + self.load('cwd') + self.set_opcache('cwd', '1') + self.run_php_application_cwd_root_tests() + + def run_php_application_cwd_script_tests(self): + self.load('cwd') + + script_cwd = self.current_dir + '/php/cwd' + + self.assertIn( + 'success', self.conf_delete('applications/cwd/working_directory') + ) + + self.assertIn( + 'success', self.conf('"index.php"', 'applications/cwd/script') + ) + + self.assertEqual( + self.get()['body'], script_cwd, 'default cwd', + ) + + self.assertEqual( + self.get(url='/?chdir=/')['body'], '/', 'cwd after chdir', + ) + + # cwd must be restored + self.assertEqual(self.get()['body'], script_cwd, 'cwd restored') + + def test_php_application_cwd_script(self): + self.load('cwd') + self.run_php_application_cwd_script_tests() + + def test_php_application_cwd_script_opcache_disabled(self): + self.load('cwd') + self.set_opcache('cwd', '0') + self.run_php_application_cwd_script_tests() + + def test_php_application_cwd_script_opcache_enabled(self): + self.load('cwd') + self.set_opcache('cwd', '1') + self.run_php_application_cwd_script_tests() + + def test_php_application_path_relative(self): + self.load('open') + + self.assertEqual(self.get()['body'], 'test', 'relative path') + + self.assertNotEqual( + self.get(url='/?chdir=/')['body'], 'test', 'relative path w/ chdir' + ) + + self.assertEqual(self.get()['body'], 'test', 'relative path 2') + + if __name__ == '__main__': TestPHPApplication.main() diff --git a/test/test_proxy.py b/test/test_proxy.py index 5d158285..74bd0873 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -188,6 +188,13 @@ Content-Length: 10 self.assertEqual(resp['status'], 200, 'status') self.assertEqual(resp['body'], payload, 'body') + self.conf({'http': {'max_body_size': 32 * 1024 * 1024}}, 'settings') + + payload = '0123456789abcdef' * 32 * 64 * 1024 + resp = self.post_http10(body=payload, read_buffer_size=1024 * 1024) + self.assertEqual(resp['status'], 200, 'status') + self.assertEqual(resp['body'], payload, 'body') + def test_proxy_parallel(self): payload = 'X' * 4096 * 257 buff_size = 4096 * 258 diff --git a/test/test_python_application.py b/test/test_python_application.py index 818816d0..460cc804 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -384,13 +384,80 @@ Connection: close self.assertEqual(self.get()['status'], 500, 'start response exit') - @unittest.skip('not yet') def test_python_application_input_iter(self): self.load('input_iter') - body = '0123456789' + body = '''0123456789 +next line + +last line''' + + resp = self.post(body=body) + self.assertEqual(resp['body'], body, 'input iter') + self.assertEqual( + resp['headers']['X-Lines-Count'], '4', 'input iter lines' + ) + + def test_python_application_input_readline(self): + self.load('input_readline') + + body = '''0123456789 +next line + +last line''' + + resp = self.post(body=body) + self.assertEqual(resp['body'], body, 'input readline') + self.assertEqual( + resp['headers']['X-Lines-Count'], '4', 'input readline lines' + ) + + def test_python_application_input_readline_size(self): + self.load('input_readline_size') + + body = '''0123456789 +next line + +last line''' + + self.assertEqual( + self.post(body=body)['body'], body, 'input readline size' + ) + self.assertEqual( + self.post(body='0123')['body'], '0123', 'input readline size less' + ) + + def test_python_application_input_readlines(self): + self.load('input_readlines') + + body = '''0123456789 +next line + +last line''' + + resp = self.post(body=body) + self.assertEqual(resp['body'], body, 'input readlines') + self.assertEqual( + resp['headers']['X-Lines-Count'], '4', 'input readlines lines' + ) + + def test_python_application_input_readlines_huge(self): + self.load('input_readlines') + + body = ( + '''0123456789 abcdefghi +next line: 0123456789 abcdefghi - self.assertEqual(self.post(body=body)['body'], body, 'input iter') +last line: 987654321 +''' + * 512 + ) + + self.assertEqual( + self.post(body=body, read_buffer_size=16384)['body'], + body, + 'input readlines huge', + ) def test_python_application_input_read_length(self): self.load('input_read_length') diff --git a/test/test_routing.py b/test/test_routing.py index eb7b2fd8..950923d6 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -288,14 +288,62 @@ class TestRouting(TestApplicationProto): ) def test_routes_route_pass_absent(self): - self.skip_alerts.append(r'failed to apply new conf') - self.assertIn( 'error', self.conf([{"match": {"method": "GET"}, "action": {}}], 'routes'), 'route pass absent configure', ) + def test_routes_action_unique(self): + self.assertIn( + 'success', + self.conf( + { + "listeners": { + "*:7080": {"pass": "routes"}, + "*:7081": {"pass": "applications/app"}, + }, + "routes": [{"action": {"proxy": "http://127.0.0.1:7081"}}], + "applications": { + "app": { + "type": "python", + "processes": {"spare": 0}, + "path": "/app", + "module": "wsgi", + } + }, + } + ), + ) + + self.assertIn( + 'error', + self.conf( + {"proxy": "http://127.0.0.1:7081", "share": self.testdir}, + 'routes/0/action', + ), + 'proxy share', + ) + self.assertIn( + 'error', + self.conf( + { + "proxy": "http://127.0.0.1:7081", + "pass": "applications/app", + }, + 'routes/0/action', + ), + 'proxy pass', + ) + self.assertIn( + 'error', + self.conf( + {"share": self.testdir, "pass": "applications/app"}, + 'routes/0/action', + ), + 'share pass', + ) + def test_routes_rules_two(self): self.assertIn( 'success', @@ -1364,6 +1412,13 @@ class TestRouting(TestApplicationProto): sock, port = sock_port() sock2, port2 = sock_port() + self.route_match({"source": ["*:" + str(port), "!127.0.0.1"]}) + self.assertEqual(self.get(sock=sock)['status'], 404, 'negative 3') + self.assertEqual(self.get(sock=sock2)['status'], 404, 'negative 4') + + sock, port = sock_port() + sock2, port2 = sock_port() + self.route_match( {"source": "127.0.0.1:" + str(port) + "-" + str(port)} ) @@ -1678,6 +1733,7 @@ class TestRouting(TestApplicationProto): self.route_match_invalid({"source": "127"}) self.route_match_invalid({"source": "256.0.0.1"}) self.route_match_invalid({"source": "127.0.0."}) + self.route_match_invalid({"source": " 127.0.0.1"}) self.route_match_invalid({"source": "127.0.0.1:"}) self.route_match_invalid({"source": "127.0.0.1/"}) self.route_match_invalid({"source": "11.0.0.0/33"}) @@ -1690,6 +1746,7 @@ class TestRouting(TestApplicationProto): self.route_match_invalid({"source": "2001::/129"}) self.route_match_invalid({"source": "::FFFFF"}) self.route_match_invalid({"source": "[::1]:"}) + self.route_match_invalid({"source": "[:::]:7080"}) self.route_match_invalid({"source": "*:"}) self.route_match_invalid({"source": "*:1-a"}) self.route_match_invalid({"source": "*:65536"}) @@ -1716,6 +1773,55 @@ class TestRouting(TestApplicationProto): self.assertEqual(self.get()['status'], 404, 'dest neg') self.assertEqual(self.get(port=7081)['status'], 200, 'dest neg 2') + self.route_match({"destination": ['!*:7080', '!*:7081']}) + self.assertEqual(self.get()['status'], 404, 'dest neg 3') + self.assertEqual(self.get(port=7081)['status'], 404, 'dest neg 4') + + self.route_match({"destination": ['!*:7081', '!*:7082']}) + self.assertEqual(self.get()['status'], 200, 'dest neg 5') + + self.route_match({"destination": ['*:7080', '!*:7080']}) + self.assertEqual(self.get()['status'], 404, 'dest neg 6') + + self.route_match( + {"destination": ['127.0.0.1:7080', '*:7081', '!*:7080']} + ) + self.assertEqual(self.get()['status'], 404, 'dest neg 7') + self.assertEqual(self.get(port=7081)['status'], 200, 'dest neg 8') + + self.route_match({"destination": ['!*:7081', '!*:7082', '*:7083']}) + self.assertEqual(self.get()['status'], 404, 'dest neg 9') + + self.route_match( + {"destination": ['*:7081', '!127.0.0.1:7080', '*:7080']} + ) + self.assertEqual(self.get()['status'], 404, 'dest neg 10') + self.assertEqual(self.get(port=7081)['status'], 200, 'dest neg 11') + + self.assertIn( + 'success', + self.conf_delete('routes/0/match/destination/0'), + 'remove destination rule', + ) + self.assertEqual(self.get()['status'], 404, 'dest neg 12') + self.assertEqual(self.get(port=7081)['status'], 404, 'dest neg 13') + + self.assertIn( + 'success', + self.conf_delete('routes/0/match/destination/0'), + 'remove destination rule 2', + ) + self.assertEqual(self.get()['status'], 200, 'dest neg 14') + self.assertEqual(self.get(port=7081)['status'], 404, 'dest neg 15') + + self.assertIn( + 'success', + self.conf_post("\"!127.0.0.1\"", 'routes/0/match/destination'), + 'add destination rule', + ) + self.assertEqual(self.get()['status'], 404, 'dest neg 16') + self.assertEqual(self.get(port=7081)['status'], 404, 'dest neg 17') + def test_routes_match_destination_proxy(self): self.assertIn( 'success', diff --git a/test/test_settings.py b/test/test_settings.py index 6b849558..9de3a928 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -215,6 +215,31 @@ Connection: close self.post(body='012345')['status'], 413, 'status size max' ) + def test_settings_max_body_size_large(self): + self.load('mirror') + + self.conf({'http': {'max_body_size': 32 * 1024 * 1024}}, 'settings') + + body = '0123456789abcdef' * 4 * 64 * 1024 + resp = self.post(body=body, read_buffer_size=1024 * 1024) + self.assertEqual(resp['status'], 200, 'status size 4') + self.assertEqual(resp['body'], body, 'status body 4') + + body = '0123456789abcdef' * 8 * 64 * 1024 + resp = self.post(body=body, read_buffer_size=1024 * 1024) + self.assertEqual(resp['status'], 200, 'status size 8') + self.assertEqual(resp['body'], body, 'status body 8') + + body = '0123456789abcdef' * 16 * 64 * 1024 + resp = self.post(body=body, read_buffer_size=1024 * 1024) + self.assertEqual(resp['status'], 200, 'status size 16') + self.assertEqual(resp['body'], body, 'status body 16') + + body = '0123456789abcdef' * 32 * 64 * 1024 + resp = self.post(body=body, read_buffer_size=1024 * 1024) + self.assertEqual(resp['status'], 200, 'status size 32') + self.assertEqual(resp['body'], body, 'status body 32') + @unittest.skip('not yet') def test_settings_negative_value(self): self.assertIn( diff --git a/test/test_share_fallback.py b/test/test_share_fallback.py new file mode 100644 index 00000000..8c45793e --- /dev/null +++ b/test/test_share_fallback.py @@ -0,0 +1,212 @@ +import os +import unittest +from unit.applications.proto import TestApplicationProto + + +class TestStatic(TestApplicationProto): + prerequisites = {} + + def setUp(self): + super().setUp() + + os.makedirs(self.testdir + '/assets/dir') + with open(self.testdir + '/assets/index.html', 'w') as index: + index.write('0123456789') + + os.makedirs(self.testdir + '/assets/403') + os.chmod(self.testdir + '/assets/403', 0o000) + + self._load_conf( + { + "listeners": { + "*:7080": {"pass": "routes"}, + "*:7081": {"pass": "applications/empty"}, + }, + "routes": [{"action": {"share": self.testdir + "/assets"}}], + "applications": { + "empty": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + "/python/empty", + "working_directory": self.current_dir + + "/python/empty", + "module": "wsgi", + } + }, + } + ) + + def tearDown(self): + os.chmod(self.testdir + '/assets/403', 0o777) + + super().tearDown() + + def test_fallback(self): + self.assertIn( + 'success', + self.conf({"share": "/blah"}, 'routes/0/action'), + 'configure bad path no fallback', + ) + self.assertEqual(self.get()['status'], 404, 'bad path no fallback') + + self.assertIn( + 'success', + self.conf( + {"share": "/blah", "fallback": {"pass": "applications/empty"}}, + 'routes/0/action', + ), + 'configure bad path fallback', + ) + resp = self.get() + self.assertEqual(resp['status'], 200, 'bad path fallback status') + self.assertEqual(resp['body'], '', 'bad path fallback') + + def test_fallback_valid_path(self): + self.assertIn( + 'success', + self.conf( + { + "share": self.testdir + "/assets", + "fallback": {"pass": "applications/empty"}, + }, + 'routes/0/action', + ), + 'configure fallback', + ) + resp = self.get() + self.assertEqual(resp['status'], 200, 'fallback status') + self.assertEqual(resp['body'], '0123456789', 'fallback') + + resp = self.get(url='/403/') + self.assertEqual(resp['status'], 200, 'fallback status 403') + self.assertEqual(resp['body'], '', 'fallback 403') + + resp = self.post() + self.assertEqual(resp['status'], 200, 'fallback status 405') + self.assertEqual(resp['body'], '', 'fallback 405') + + self.assertEqual( + self.get(url='/dir')['status'], 301, 'fallback status 301' + ) + + def test_fallback_nested(self): + self.assertIn( + 'success', + self.conf( + { + "share": "/blah", + "fallback": { + "share": "/blah/blah", + "fallback": {"pass": "applications/empty"}, + }, + }, + 'routes/0/action', + ), + 'configure fallback nested', + ) + resp = self.get() + self.assertEqual(resp['status'], 200, 'fallback nested status') + self.assertEqual(resp['body'], '', 'fallback nested') + + def test_fallback_share(self): + self.assertIn( + 'success', + self.conf( + { + "share": "/blah", + "fallback": {"share": self.testdir + "/assets"}, + }, + 'routes/0/action', + ), + 'configure fallback share', + ) + resp = self.get() + self.assertEqual(resp['status'], 200, 'fallback share status') + self.assertEqual(resp['body'], '0123456789', 'fallback share') + + resp = self.head() + self.assertEqual(resp['status'], 200, 'fallback share status HEAD') + self.assertEqual(resp['body'], '', 'fallback share HEAD') + + self.assertEqual( + self.get(url='/dir')['status'], 301, 'fallback share status 301' + ) + + def test_fallback_proxy(self): + self.assertIn( + 'success', + self.conf( + { + "share": "/blah", + "fallback": {"proxy": "http://127.0.0.1:7081"}, + }, + 'routes/0/action', + ), + 'configure fallback proxy', + ) + resp = self.get() + self.assertEqual(resp['status'], 200, 'fallback proxy status') + self.assertEqual(resp['body'], '', 'fallback proxy') + + @unittest.skip('not yet') + def test_fallback_proxy_cycle(self): + self.assertIn( + 'success', + self.conf( + { + "share": "/blah", + "fallback": {"proxy": "http://127.0.0.1:7080"}, + }, + 'routes/0/action', + ), + 'configure fallback cycle', + ) + self.assertNotEqual(self.get()['status'], 200, 'fallback cycle') + + self.assertIn( + 'success', self.conf_delete('listeners/*:7081'), 'delete listener' + ) + self.assertNotEqual(self.get()['status'], 200, 'fallback cycle 2') + + def test_fallback_invalid(self): + self.assertIn( + 'error', + self.conf({"share": "/blah", "fallback": {}}, 'routes/0/action'), + 'configure fallback empty', + ) + self.assertIn( + 'error', + self.conf({"share": "/blah", "fallback": ""}, 'routes/0/action'), + 'configure fallback not object', + ) + self.assertIn( + 'error', + self.conf( + { + "proxy": "http://127.0.0.1:7081", + "fallback": {"share": "/blah"}, + }, + 'routes/0/action', + ), + 'configure fallback proxy invalid', + ) + self.assertIn( + 'error', + self.conf( + { + "pass": "applications/empty", + "fallback": {"share": "/blah"}, + }, + 'routes/0/action', + ), + 'configure fallback pass invalid', + ) + self.assertIn( + 'error', + self.conf({"fallback": {"share": "/blah"}}, 'routes/0/action'), + 'configure fallback only', + ) + + +if __name__ == '__main__': + TestStatic.main() diff --git a/test/test_static.py b/test/test_static.py index f9dcb7dd..b2489aa0 100644 --- a/test/test_static.py +++ b/test/test_static.py @@ -39,6 +39,9 @@ class TestStatic(TestApplicationProto): self.get(url='/index.html')['body'], '0123456789', 'index' ) self.assertEqual(self.get(url='/')['body'], '0123456789', 'index 2') + self.assertEqual(self.get(url='//')['body'], '0123456789', 'index 3') + self.assertEqual(self.get(url='/.')['body'], '0123456789', 'index 4') + self.assertEqual(self.get(url='/./')['body'], '0123456789', 'index 5') self.assertEqual( self.get(url='/?blah')['body'], '0123456789', 'index vars' ) @@ -199,10 +202,29 @@ class TestStatic(TestApplicationProto): self.get(url='/link/file')['status'], 200, 'symlink file' ) - def test_static_head(self): - resp = self.head(url='/') - self.assertEqual(resp['status'], 200, 'status') - self.assertEqual(resp['body'], '', 'empty body') + def test_static_method(self): + resp = self.head() + self.assertEqual(resp['status'], 200, 'HEAD status') + self.assertEqual(resp['body'], '', 'HEAD empty body') + + self.assertEqual(self.delete()['status'], 405, 'DELETE') + self.assertEqual(self.post()['status'], 405, 'POST') + self.assertEqual(self.put()['status'], 405, 'PUT') + + def test_static_path(self): + self.assertEqual( + self.get(url='/dir/../dir/file')['status'], 200, 'relative' + ) + + self.assertEqual(self.get(url='./')['status'], 400, 'path invalid') + self.assertEqual(self.get(url='../')['status'], 400, 'path invalid 2') + self.assertEqual(self.get(url='/..')['status'], 400, 'path invalid 3') + self.assertEqual( + self.get(url='../assets/')['status'], 400, 'path invalid 4' + ) + self.assertEqual( + self.get(url='/../assets/')['status'], 400, 'path invalid 5' + ) def test_static_two_clients(self): _, sock = self.get(url='/', start=True, no_recv=True) diff --git a/test/test_tls.py b/test/test_tls.py index 1ead111c..475e9919 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -157,7 +157,8 @@ class TestTLS(TestApplicationTLS): '-genkey', '-out', self.testdir + '/ec.key', '-name', 'prime256v1', - ] + ], + stderr=subprocess.STDOUT, ) subprocess.call( @@ -170,7 +171,8 @@ class TestTLS(TestApplicationTLS): '-config', self.testdir + '/openssl.conf', '-key', self.testdir + '/ec.key', '-out', self.testdir + '/ec.crt', - ] + ], + stderr=subprocess.STDOUT, ) self.certificate_load('ec') @@ -230,7 +232,8 @@ class TestTLS(TestApplicationTLS): '-config', self.testdir + '/openssl.conf', '-out', self.testdir + '/int.csr', '-keyout', self.testdir + '/int.key', - ] + ], + stderr=subprocess.STDOUT, ) subprocess.call( @@ -242,7 +245,8 @@ class TestTLS(TestApplicationTLS): '-config', self.testdir + '/openssl.conf', '-out', self.testdir + '/end.csr', '-keyout', self.testdir + '/end.key', - ] + ], + stderr=subprocess.STDOUT, ) with open(self.testdir + '/ca.conf', 'w') as f: @@ -288,7 +292,8 @@ basicConstraints = critical,CA:TRUE""" '-cert', self.testdir + '/root.crt', '-in', self.testdir + '/int.csr', '-out', self.testdir + '/int.crt', - ] + ], + stderr=subprocess.STDOUT, ) subprocess.call( @@ -302,7 +307,8 @@ basicConstraints = critical,CA:TRUE""" '-cert', self.testdir + '/int.crt', '-in', self.testdir + '/end.csr', '-out', self.testdir + '/end.crt', - ] + ], + stderr=subprocess.STDOUT, ) crt_path = self.testdir + '/end-int.crt' diff --git a/test/test_upstreams_rr.py b/test/test_upstreams_rr.py new file mode 100644 index 00000000..2bc2d90a --- /dev/null +++ b/test/test_upstreams_rr.py @@ -0,0 +1,465 @@ +import os +import re +import unittest +from unit.applications.lang.python import TestApplicationPython + + +class TestUpstreamsRR(TestApplicationPython): + prerequisites = {'modules': ['python']} + + def setUp(self): + super().setUp() + + self.assertIn( + 'success', + self.conf( + { + "listeners": { + "*:7080": {"pass": "upstreams/one"}, + "*:7081": {"pass": "applications/ups_0"}, + "*:7082": {"pass": "applications/ups_1"}, + "*:7083": {"pass": "applications/ups_2"}, + "*:7090": {"pass": "upstreams/two"}, + }, + "upstreams": { + "one": { + "servers": { + "127.0.0.1:7081": {}, + "127.0.0.1:7082": {}, + }, + }, + "two": { + "servers": { + "127.0.0.1:7081": {}, + "127.0.0.1:7082": {}, + }, + }, + }, + "applications": { + "ups_0": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + "/python/upstreams/0", + "working_directory": self.current_dir + + "/python/upstreams/0", + "module": "wsgi", + }, + "ups_1": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + "/python/upstreams/1", + "working_directory": self.current_dir + + "/python/upstreams/1", + "module": "wsgi", + }, + "ups_2": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + "/python/upstreams/2", + "working_directory": self.current_dir + + "/python/upstreams/2", + "module": "wsgi", + }, + }, + }, + ), + 'upstreams initial configuration', + ) + + self.cpu_count = os.cpu_count() + + def get_resps(self, req=100, port=7080): + resps = [0] + for _ in range(req): + headers = self.get(port=port)['headers'] + if 'X-Upstream' in headers: + ups = int(headers['X-Upstream']) + + if ups > len(resps) - 1: + resps.extend([0] * (ups - len(resps) + 1)) + + resps[ups] += 1 + + return resps + + def get_resps_sc(self, req=100, port=7080): + to_send = b"""GET / HTTP/1.1 +Host: localhost + +""" * ( + req - 1 + ) + + to_send += b"""GET / HTTP/1.1 +Host: localhost +Connection: close + +""" + + resp = self.http(to_send, raw_resp=True, raw=True, port=port) + ups = re.findall('X-Upstream: (\d+)', resp) + resps = [0] * (int(max(ups)) + 1) + + for i in range(len(ups)): + resps[int(ups[i])] += 1 + + return resps + + def test_upstreams_rr_no_weight(self): + resps = self.get_resps() + self.assertLessEqual( + abs(resps[0] - resps[1]), self.cpu_count, 'no weight' + ) + + self.assertIn( + 'success', + self.conf_delete('upstreams/one/servers/127.0.0.1:7081'), + 'no weight server remove', + ) + + resps = self.get_resps(req=50) + self.assertEqual(resps[1], 50, 'no weight 2') + + self.assertIn( + 'success', + self.conf({}, 'upstreams/one/servers/127.0.0.1:7081'), + 'no weight server revert', + ) + + resps = self.get_resps() + self.assertLessEqual( + abs(resps[0] - resps[1]), self.cpu_count, 'no weight 3' + ) + + self.assertIn( + 'success', + self.conf({}, 'upstreams/one/servers/127.0.0.1:7083'), + 'no weight server new', + ) + + resps = self.get_resps() + self.assertLessEqual( + max(resps) - min(resps), self.cpu_count, 'no weight 4' + ) + + resps = self.get_resps_sc(req=30) + self.assertEqual(resps[0], 10, 'no weight 4 0') + self.assertEqual(resps[1], 10, 'no weight 4 1') + self.assertEqual(resps[2], 10, 'no weight 4 2') + + def test_upstreams_rr_weight(self): + self.assertIn( + 'success', + self.conf({"weight": 3}, 'upstreams/one/servers/127.0.0.1:7081'), + 'configure weight', + ) + + resps = self.get_resps_sc() + self.assertEqual(resps[0], 75, 'weight 3 0') + self.assertEqual(resps[1], 25, 'weight 3 1') + + self.assertIn( + 'success', + self.conf_delete('upstreams/one/servers/127.0.0.1:7081/weight'), + 'configure weight remove', + ) + resps = self.get_resps_sc(req=10) + self.assertEqual(resps[0], 5, 'weight 0 0') + self.assertEqual(resps[1], 5, 'weight 0 1') + + self.assertIn( + 'success', + self.conf('1', 'upstreams/one/servers/127.0.0.1:7081/weight'), + 'configure weight 1', + ) + + resps = self.get_resps_sc() + self.assertEqual(resps[0], 50, 'weight 1 0') + self.assertEqual(resps[1], 50, 'weight 1 1') + + self.assertIn( + 'success', + self.conf( + { + "127.0.0.1:7081": {"weight": 3}, + "127.0.0.1:7083": {"weight": 2}, + }, + 'upstreams/one/servers', + ), + 'configure weight 2', + ) + + resps = self.get_resps_sc() + self.assertEqual(resps[0], 60, 'weight 2 0') + self.assertEqual(resps[2], 40, 'weight 2 1') + + def test_upstreams_rr_independent(self): + def sum_resps(*args): + sum = [0] * len(args[0]) + for arg in args: + sum = [x + y for x, y in zip(sum, arg)] + + return sum + + resps = self.get_resps_sc(req=30, port=7090) + self.assertEqual(resps[0], 15, 'dep two before 0') + self.assertEqual(resps[1], 15, 'dep two before 1') + + resps = self.get_resps_sc(req=30) + self.assertEqual(resps[0], 15, 'dep one before 0') + self.assertEqual(resps[1], 15, 'dep one before 1') + + self.assertIn( + 'success', + self.conf('2', 'upstreams/two/servers/127.0.0.1:7081/weight'), + 'configure dep weight', + ) + + resps = self.get_resps_sc(req=30, port=7090) + self.assertEqual(resps[0], 20, 'dep two 0') + self.assertEqual(resps[1], 10, 'dep two 1') + + resps = self.get_resps_sc(req=30) + self.assertEqual(resps[0], 15, 'dep one 0') + self.assertEqual(resps[1], 15, 'dep one 1') + + self.assertIn( + 'success', + self.conf('1', 'upstreams/two/servers/127.0.0.1:7081/weight'), + 'configure dep weight 1', + ) + + r_one, r_two = [0, 0], [0, 0] + for _ in range(10): + r_one = sum_resps(r_one, self.get_resps(req=10)) + r_two = sum_resps(r_two, self.get_resps(req=10, port=7090)) + + self.assertLessEqual( + abs(r_one[0] - r_one[1]), self.cpu_count, 'dep one mix' + ) + self.assertLessEqual( + abs(r_two[0] - r_two[1]), self.cpu_count, 'dep two mix' + ) + + def test_upstreams_rr_delay(self): + headers_delay_1 = { + 'Connection': 'close', + 'Host': 'localhost', + 'Content-Length': '0', + 'X-Delay': '1', + } + headers_no_delay = { + 'Connection': 'close', + 'Host': 'localhost', + 'Content-Length': '0', + } + + req = 50 + + socks = [] + for i in range(req): + headers = headers_delay_1 if i % 5 == 0 else headers_no_delay + _, sock = self.get( + headers=headers, + start=True, + no_recv=True, + ) + socks.append(sock) + + resps = [0, 0] + for i in range(req): + resp = self.recvall(socks[i]).decode() + socks[i].close() + + m = re.search('X-Upstream: (\d+)', resp) + resps[int(m.group(1))] += 1 + + self.assertLessEqual( + abs(resps[0] - resps[1]), self.cpu_count, 'dep two mix' + ) + + def test_upstreams_rr_active_req(self): + conns = 5 + socks = [] + socks2 = [] + + for _ in range(conns): + _, sock = self.get(start=True, no_recv=True) + socks.append(sock) + + _, sock2 = self.http( + b"""POST / HTTP/1.1 +Host: localhost +Content-Length: 10 +Connection: close + +""", + start=True, + no_recv=True, + raw=True, + ) + socks2.append(sock2) + + # Send one more request and read response to make sure that previous + # requests had enough time to reach server. + + self.assertEqual(self.get()['status'], 200) + + self.assertIn( + 'success', + self.conf( + {"127.0.0.1:7083": {"weight": 2}}, 'upstreams/one/servers', + ), + 'active req new server', + ) + self.assertIn( + 'success', + self.conf_delete('upstreams/one/servers/127.0.0.1:7083'), + 'active req server remove', + ) + self.assertIn( + 'success', self.conf_delete('listeners/*:7080'), 'delete listener' + ) + self.assertIn( + 'success', + self.conf_delete('upstreams/one'), + 'active req upstream remove', + ) + + for i in range(conns): + resp = self.recvall(socks[i]).decode() + socks[i].close() + + self.assertRegex(resp, r'X-Upstream', 'active req GET') + + resp = self.http(b"""0123456789""", sock=socks2[i], raw=True) + self.assertEqual(resp['status'], 200, 'active req POST') + + def test_upstreams_rr_bad_server(self): + self.assertIn( + 'success', + self.conf({"weight": 1}, 'upstreams/one/servers/127.0.0.1:7084'), + 'configure bad server', + ) + + resps = self.get_resps_sc(req=30) + self.assertEqual(resps[0], 10, 'bad server 0') + self.assertEqual(resps[1], 10, 'bad server 1') + self.assertEqual(sum(resps), 20, 'bad server sum') + + def test_upstreams_rr_pipeline(self): + resps = self.get_resps_sc() + + self.assertEqual(resps[0], 50, 'pipeline 0') + self.assertEqual(resps[1], 50, 'pipeline 1') + + def test_upstreams_rr_post(self): + resps = [0, 0] + for _ in range(50): + resps[ + int(self.post(body='0123456789')['headers']['X-Upstream']) + ] += 1 + resps[int(self.get()['headers']['X-Upstream'])] += 1 + + self.assertLessEqual( + abs(resps[0] - resps[1]), self.cpu_count, 'post' + ) + + def test_upstreams_rr_unix(self): + addr_0 = self.testdir + '/sock_0' + addr_1 = self.testdir + '/sock_1' + + self.assertIn( + 'success', + self.conf( + { + "*:7080": {"pass": "upstreams/one"}, + "unix:" + addr_0: {"pass": "applications/ups_0"}, + "unix:" + addr_1: {"pass": "applications/ups_1"}, + }, + 'listeners', + ), + 'configure listeners unix', + ) + + self.assertIn( + 'success', + self.conf( + {"unix:" + addr_0: {}, "unix:" + addr_1: {},}, + 'upstreams/one/servers', + ), + 'configure servers unix', + ) + + resps = self.get_resps_sc() + + self.assertEqual(resps[0], 50, 'unix 0') + self.assertEqual(resps[1], 50, 'unix 1') + + def test_upstreams_rr_ipv6(self): + self.assertIn( + 'success', + self.conf( + { + "*:7080": {"pass": "upstreams/one"}, + "[::1]:7081": {"pass": "applications/ups_0"}, + "[::1]:7082": {"pass": "applications/ups_1"}, + }, + 'listeners', + ), + 'configure listeners ipv6', + ) + + self.assertIn( + 'success', + self.conf( + {"[::1]:7081": {}, "[::1]:7082": {},}, 'upstreams/one/servers' + ), + 'configure servers ipv6', + ) + + resps = self.get_resps_sc() + + self.assertEqual(resps[0], 50, 'ipv6 0') + self.assertEqual(resps[1], 50, 'ipv6 1') + + def test_upstreams_rr_servers_empty(self): + self.assertIn( + 'success', + self.conf({}, 'upstreams/one/servers'), + 'configure servers empty', + ) + + self.assertEqual(self.get()['status'], 502, 'servers empty') + + def test_upstreams_rr_invalid(self): + self.assertIn( + 'error', self.conf({}, 'upstreams'), 'upstreams empty', + ) + self.assertIn( + 'error', self.conf({}, 'upstreams/one'), 'named upstreams empty', + ) + self.assertIn( + 'error', + self.conf({}, 'upstreams/one/servers/127.0.0.1'), + 'invalid address', + ) + self.assertIn( + 'error', + self.conf({}, 'upstreams/one/servers/127.0.0.1:7081/blah'), + 'invalid server option', + ) + self.assertIn( + 'error', + self.conf({}, 'upstreams/one/servers/127.0.0.1:7081/weight'), + 'invalid weight option', + ) + self.assertIn( + 'error', + self.conf('-1', 'upstreams/one/servers/127.0.0.1:7081/weight'), + 'invalid negative weight', + ) + + +if __name__ == '__main__': + TestUpstreamsRR.main() diff --git a/test/test_usr1.py b/test/test_usr1.py index dd9292c7..2b4f394b 100644 --- a/test/test_usr1.py +++ b/test/test_usr1.py @@ -10,7 +10,9 @@ class TestUSR1(TestApplicationPython): def test_usr1_access_log(self): self.load('empty') - log_path = self.testdir + '/access.log' + log = 'access.log' + log_new = 'new.log' + log_path = self.testdir + '/' + log self.assertIn( 'success', @@ -20,14 +22,12 @@ class TestUSR1(TestApplicationPython): self.assertTrue(self.waitforfiles(log_path), 'open') - log_path_new = self.testdir + '/new.log' + os.rename(log_path, self.testdir + '/' + log_new) - os.rename(log_path, log_path_new) - - self.get() + self.assertEqual(self.get()['status'], 200) self.assertIsNotNone( - self.wait_for_record(r'"GET / HTTP/1.1" 200 0 "-" "-"', 'new.log'), + self.wait_for_record(r'"GET / HTTP/1.1" 200 0 "-" "-"', log_new), 'rename new', ) self.assertFalse(os.path.isfile(log_path), 'rename old') @@ -39,32 +39,33 @@ class TestUSR1(TestApplicationPython): self.assertTrue(self.waitforfiles(log_path), 'reopen') - self.get(url='/usr1') + self.assertEqual(self.get(url='/usr1')['status'], 200) + + self.stop() self.assertIsNotNone( - self.wait_for_record( - r'"GET /usr1 HTTP/1.1" 200 0 "-" "-"', 'access.log' - ), + self.wait_for_record(r'"GET /usr1 HTTP/1.1" 200 0 "-" "-"', log), 'reopen 2', ) self.assertIsNone( - self.search_in_log(r'/usr1', 'new.log'), 'rename new 2' + self.search_in_log(r'/usr1', log_new), 'rename new 2' ) @unittest.skip('not yet') def test_usr1_unit_log(self): self.load('log_body') - log_path = self.testdir + '/unit.log' - log_path_new = self.testdir + '/new.log' + log_new = 'new.log' + log_path = self.testdir + '/' + 'unit.log' + log_path_new = self.testdir + '/' + log_new os.rename(log_path, log_path_new) body = 'body_for_a_log_new' - self.post(body=body) + self.assertEqual(self.post(body=body)['status'], 200) self.assertIsNotNone( - self.wait_for_record(body, 'new.log'), 'rename new' + self.wait_for_record(body, log_new), 'rename new' ) self.assertFalse(os.path.isfile(log_path), 'rename old') @@ -76,16 +77,18 @@ class TestUSR1(TestApplicationPython): self.assertTrue(self.waitforfiles(log_path), 'reopen') body = 'body_for_a_log_unit' - self.post(body=body) + self.assertEqual(self.post(body=body)['status'], 200) + + self.stop() self.assertIsNotNone(self.wait_for_record(body), 'rename new') - self.assertIsNone(self.search_in_log(body, 'new.log'), 'rename new 2') + self.assertIsNone(self.search_in_log(body, log_new), 'rename new 2') # merge two log files into unit.log to check alerts with open(log_path, 'w') as unit_log, \ - open(log_path_new, 'r') as new_log: - unit_log.write(new_log.read()) + open(log_path_new, 'r') as unit_log_new: + unit_log.write(unit_log_new.read()) if __name__ == '__main__': diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py index 7212a95c..e0f83c0a 100644 --- a/test/unit/applications/lang/go.py +++ b/test/unit/applications/lang/go.py @@ -1,5 +1,5 @@ import os -from subprocess import Popen +import subprocess from unit.applications.proto import TestApplicationProto @@ -26,7 +26,7 @@ class TestApplicationGo(TestApplicationProto): env['GOPATH'] = self.pardir + '/build/go' try: - process = Popen( + process = subprocess.Popen( [ 'go', 'build', @@ -35,6 +35,7 @@ class TestApplicationGo(TestApplicationProto): self.current_dir + '/go/' + script + '/' + name + '.go', ], env=env, + stderr=subprocess.STDOUT, ) process.communicate() diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index a370d96b..a8a09ce5 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -1,7 +1,7 @@ import os import glob import shutil -from subprocess import Popen +import subprocess from unit.applications.proto import TestApplicationProto @@ -64,7 +64,7 @@ class TestApplicationJava(TestApplicationProto): javac.extend(src) try: - process = Popen(javac) + process = subprocess.Popen(javac, stderr=subprocess.STDOUT) process.communicate() except: diff --git a/test/unit/applications/lang/php.py b/test/unit/applications/lang/php.py index 6b1677e6..e8c70c62 100644 --- a/test/unit/applications/lang/php.py +++ b/test/unit/applications/lang/php.py @@ -4,7 +4,7 @@ from unit.applications.proto import TestApplicationProto class TestApplicationPHP(TestApplicationProto): application_type = "php" - def load(self, script, name='index.php', **kwargs): + def load(self, script, index='index.php', **kwargs): script_path = self.current_dir + '/php/' + script self._load_conf( @@ -16,7 +16,7 @@ class TestApplicationPHP(TestApplicationProto): "processes": {"spare": 0}, "root": script_path, "working_directory": script_path, - "index": name, + "index": index, } }, }, diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index 1290279d..9213974a 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -47,7 +47,8 @@ class TestApplicationTLS(TestApplicationProto): '-config', self.testdir + '/openssl.conf', '-out', self.testdir + '/' + name + '.crt', '-keyout', self.testdir + '/' + name + '.key', - ] + ], + stderr=subprocess.STDOUT, ) if load: diff --git a/test/unit/http.py b/test/unit/http.py index c71e8f7e..47fb48f1 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -96,12 +96,7 @@ class TestHTTP(TestUnit): encoding = 'utf-8' if 'encoding' not in kwargs else kwargs['encoding'] - if TestUnit.detailed: - print('>>>') - try: - print(req.decode(encoding, 'ignore')) - except UnicodeEncodeError: - print(req) + self.log_out(req, encoding) resp = '' @@ -113,12 +108,7 @@ class TestHTTP(TestUnit): sock, read_timeout=read_timeout, buff_size=read_buffer_size ).decode(encoding) - if TestUnit.detailed: - print('<<<') - try: - print(resp) - except UnicodeEncodeError: - print(resp.encode()) + self.log_in(resp) if 'raw_resp' not in kwargs: resp = self._resp_to_dict(resp) @@ -138,6 +128,37 @@ class TestHTTP(TestUnit): return (resp, sock) + def log_out(self, log, encoding): + if TestUnit.detailed: + print('>>>') + log = self.log_truncate(log) + try: + print(log.decode(encoding, 'ignore')) + except UnicodeEncodeError: + print(log) + + def log_in(self, log): + if TestUnit.detailed: + print('<<<') + log = self.log_truncate(log) + try: + print(log) + except UnicodeEncodeError: + print(log.encode()) + + def log_truncate(self, log, limit=1024): + len_log = len(log) + if len_log > limit: + log = log[:limit] + appendix = '(...logged %s of %s bytes)' % (limit, len_log) + + if isinstance(log, bytes): + appendix = appendix.encode() + + log = log + appendix + + return log + def delete(self, **kwargs): return self.http('DELETE', **kwargs) diff --git a/test/unit/main.py b/test/unit/main.py index 37d01d3b..69234dcc 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -5,6 +5,7 @@ import stat import time import fcntl import shutil +import signal import argparse import platform import tempfile @@ -30,6 +31,7 @@ class TestUnit(unittest.TestCase): detailed = False save_log = False + print_log = False unsafe = False def __init__(self, methodName='runTest'): @@ -183,7 +185,7 @@ class TestUnit(unittest.TestCase): shutil.rmtree(self.testdir) else: - self._print_path_to_log() + self._print_log() def stop(self): if self._started: @@ -207,9 +209,9 @@ class TestUnit(unittest.TestCase): os.mkdir(self.testdir + '/state') - print() - - self._p = Process(target=subprocess.call, args=[ [ + with open(self.testdir + '/unit.log', 'w') as log: + self._p = subprocess.Popen( + [ self.unitd, '--no-daemon', '--modules', self.pardir + '/build', @@ -217,55 +219,40 @@ class TestUnit(unittest.TestCase): '--pid', self.testdir + '/unit.pid', '--log', self.testdir + '/unit.log', '--control', 'unix:' + self.testdir + '/control.unit.sock', - ] ]) - self._p.start() - - if not self.waitforfiles( - self.testdir + '/unit.pid', - self.testdir + '/unit.log', - self.testdir + '/control.unit.sock', - ): + '--tmp', self.testdir, + ], + stderr=log, + ) + + if not self.waitforfiles(self.testdir + '/control.unit.sock'): exit("Could not start unit") self._started = True self.skip_alerts = [ r'read signalfd\(4\) failed', + r'last message send failed', r'sendmsg.+failed', r'recvmsg.+failed', ] self.skip_sanitizer = False def _stop(self): - with open(self.testdir + '/unit.pid', 'r') as f: - pid = f.read().rstrip() - - subprocess.call(['kill', '-s', 'QUIT', pid]) - - for i in range(150): - if not os.path.exists(self.testdir + '/unit.pid'): - break - time.sleep(0.1) - - self._p.join(timeout=5) - - if self._p.is_alive(): - self._p.terminate() - self._p.join(timeout=5) - - if self._p.is_alive(): - self.fail("Could not terminate process " + str(self._p.pid)) - - if os.path.exists(self.testdir + '/unit.pid'): - self.fail("Could not terminate unit") + with self._p as p: + p.send_signal(signal.SIGQUIT) + + try: + retcode = p.wait(15) + if retcode: + self.fail( + "Child process terminated with code " + str(retcode) + ) + except: + self.fail("Could not terminate unit") + p.kill() self._started = False - if self._p.exitcode: - self.fail( - "Child process terminated with code " + str(self._p.exitcode) - ) - def _check_alerts(self, log): found = False @@ -281,14 +268,14 @@ class TestUnit(unittest.TestCase): alerts = [al for al in alerts if re.search(skip, al) is None] if alerts: - self._print_path_to_log() + self._print_log(log) self.assertFalse(alerts, 'alert(s)') if not self.skip_sanitizer: sanitizer_errors = re.findall('.+Sanitizer.+', log) if sanitizer_errors: - self._print_path_to_log() + self._print_log(log) self.assertFalse(sanitizer_errors, 'sanitizer error(s)') if found: @@ -361,6 +348,13 @@ class TestUnit(unittest.TestCase): help='Save unit.log after the test execution', ) parser.add_argument( + '-r', + '--reprint_log', + dest='print_log', + action='store_true', + help='Print unit.log to stdout in case of errors', + ) + parser.add_argument( '-u', '--unsafe', dest='unsafe', @@ -374,12 +368,23 @@ class TestUnit(unittest.TestCase): def _set_args(args): TestUnit.detailed = args.detailed TestUnit.save_log = args.save_log + TestUnit.print_log = args.print_log TestUnit.unsafe = args.unsafe # set stdout to non-blocking - if TestUnit.detailed: + if TestUnit.detailed or TestUnit.print_log: fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, 0) - def _print_path_to_log(self): - print('Path to unit.log:\n' + self.testdir + '/unit.log') + def _print_log(self, data=None): + path = self.testdir + '/unit.log' + + print('Path to unit.log:\n' + path + '\n') + + if TestUnit.print_log: + if data is None: + with open(path, 'r', encoding='utf-8', errors='ignore') as f: + data = f.read() + + print(data) + |