From 4404097e05dc71b7ee78ef49c8ba4d0eaf234f85 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 8 Feb 2021 23:32:27 +0000 Subject: Tests: added "--restart" option. Now Unit do not restart after each test by default. --- test/conftest.py | 113 ++++++++++++++++++++++++++++++++++++-------- test/test_proxy.py | 1 + test/test_share_fallback.py | 7 ++- test/test_tls.py | 2 +- test/unit/utils.py | 2 +- 5 files changed, 103 insertions(+), 22 deletions(-) (limited to 'test') diff --git a/test/conftest.py b/test/conftest.py index b36aabad..35249a1f 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,9 +1,12 @@ import fcntl +import inspect +import json import os import platform import re import shutil import signal +import socket import stat import subprocess import sys @@ -16,6 +19,7 @@ from unit.check.go import check_go from unit.check.isolation import check_isolation from unit.check.node import check_node from unit.check.tls import check_openssl +from unit.http import TestHTTP from unit.option import option from unit.utils import public_dir from unit.utils import waitforfiles @@ -51,10 +55,18 @@ def pytest_addoption(parser): type=str, help="Default user for non-privileged processes of unitd", ) + parser.addoption( + "--restart", + default=False, + action="store_true", + help="Force Unit to restart after every test", + ) unit_instance = {} +unit_log_copy = "unit.log.copy" _processes = [] +http = TestHTTP() def pytest_configure(config): option.config = config.option @@ -64,6 +76,7 @@ def pytest_configure(config): option.save_log = config.option.save_log option.unsafe = config.option.unsafe option.user = config.option.user + option.restart = config.option.restart option.generated_tests = {} option.current_dir = os.path.abspath( @@ -172,12 +185,17 @@ def pytest_sessionstart(session): check_isolation() + assert 'success' in _clear_conf(unit['temp_dir'] + '/control.unit.sock') + unit_stop() _check_alerts() - shutil.rmtree(unit_instance['temp_dir']) + if option.restart: + shutil.rmtree(unit_instance['temp_dir']) + elif option.save_log: + open(unit_instance['temp_dir'] + '/' + unit_log_copy, 'w').close() @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): @@ -241,38 +259,74 @@ def run(request): # stop unit - error = unit_stop() + error_stop_unit = unit_stop() + error_stop_processes = stop_processes() - if error: - _print_log() + # prepare log - assert error is None, 'stop unit' + with open( + unit_instance['log'], 'r', encoding='utf-8', errors='ignore' + ) as f: + log = f.read() - # stop all processes + if not option.restart and option.save_log: + with open(unit_instance['temp_dir'] + '/' + unit_log_copy, 'a') as f: + f.write(log) - error = stop_processes() + # remove unit.log - if error: - _print_log() + if not option.save_log and option.restart: + shutil.rmtree(unit['temp_dir']) - assert error is None, 'stop unit' + # clean temp_dir before the next test - # check unit.log for alerts + if not option.restart: + conf_resp = _clear_conf(unit['temp_dir'] + '/control.unit.sock') - _check_alerts() + if 'success' not in conf_resp: + _print_log(log) + assert 'success' in conf_resp + + open(unit['log'], 'w').close() + + for item in os.listdir(unit['temp_dir']): + if item not in [ + 'control.unit.sock', + 'state', + 'unit.pid', + 'unit.log', + unit_log_copy, + ]: + path = os.path.join(unit['temp_dir'], item) + + public_dir(path) + + if os.path.isfile(path) or stat.S_ISSOCK(os.stat(path).st_mode): + os.remove(path) + else: + shutil.rmtree(path) # print unit.log in case of error if hasattr(request.node, 'rep_call') and request.node.rep_call.failed: - _print_log() + _print_log(log) - # remove unit.log + if error_stop_unit or error_stop_processes: + _print_log(log) - if not option.save_log: - shutil.rmtree(unit['temp_dir']) + # check unit.log for errors + + assert error_stop_unit is None, 'stop unit' + assert error_stop_processes is None, 'stop processes' + + _check_alerts(log=log) def unit_run(): global unit_instance + + if not option.restart and 'unitd' in unit_instance: + return unit_instance + build_dir = option.current_dir + '/build' unitd = build_dir + '/unitd' @@ -323,6 +377,12 @@ def unit_run(): def unit_stop(): + if not option.restart: + if inspect.stack()[1].function.startswith('test_'): + pytest.skip('no restart mode') + + return + p = unit_instance['process'] if p.poll() is not None: @@ -345,12 +405,13 @@ def unit_stop(): -def _check_alerts(path=None): +def _check_alerts(path=None, log=None): if path is None: path = unit_instance['log'] - with open(path, 'r', encoding='utf-8', errors='ignore') as f: - log = f.read() + if log is None: + with open(path, 'r', encoding='utf-8', errors='ignore') as f: + log = f.read() found = False @@ -396,6 +457,15 @@ def _print_log(data=None): sys.stdout.write(data) +def _clear_conf(sock): + return http.put( + url='/config', + sock_type='unix', + addr=sock, + body=json.dumps({"listeners": {}, "applications": {}}), + )['body'] + + def run_process(target, *args): global _processes @@ -446,5 +516,10 @@ def unit_pid(request): return unit_instance['process'].pid def pytest_sessionfinish(session): + if not option.restart and option.save_log: + print('Path to unit.log:\n' + unit_instance['log'] + '\n') + + option.restart = True + unit_stop() shutil.rmtree(option.cache_dir) diff --git a/test/test_proxy.py b/test/test_proxy.py index 2d305e98..7e7c7246 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -482,6 +482,7 @@ Content-Length: 10 check_proxy('http://[:]:7080') check_proxy('http://[::7080') + @pytest.mark.skip('not yet') def test_proxy_loop(self, skip_alert): skip_alert( r'socket.*failed', diff --git a/test/test_share_fallback.py b/test/test_share_fallback.py index a02cb1a3..46464670 100644 --- a/test/test_share_fallback.py +++ b/test/test_share_fallback.py @@ -1,5 +1,6 @@ import os +import pytest from unit.applications.proto import TestApplicationProto from unit.option import option @@ -27,7 +28,10 @@ class TestStatic(TestApplicationProto): ) def teardown_method(self): - os.chmod(option.temp_dir + '/assets/403', 0o777) + try: + os.chmod(option.temp_dir + '/assets/403', 0o777) + except FileNotFoundError: + pass def action_update(self, conf): assert 'success' in self.conf(conf, 'routes/0/action') @@ -116,6 +120,7 @@ class TestStatic(TestApplicationProto): assert resp['status'] == 200, 'fallback proxy status' assert resp['body'] == '', 'fallback proxy' + @pytest.mark.skip('not yet') def test_fallback_proxy_loop(self, skip_alert): skip_alert( r'open.*/blah/index.html.*failed', diff --git a/test/test_tls.py b/test/test_tls.py index 89c57d07..206ea828 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -199,7 +199,7 @@ class TestTLS(TestApplicationTLS): self.sec_epoch() - self.openssl_date_to_sec_epoch(cert['validity']['since']) ) - < 5 + < 60 ), 'certificate validity since' assert ( self.openssl_date_to_sec_epoch(cert['validity']['until']) diff --git a/test/unit/utils.py b/test/unit/utils.py index 7a0a3fe5..e80fc469 100644 --- a/test/unit/utils.py +++ b/test/unit/utils.py @@ -47,7 +47,7 @@ def waitforsocket(port): except KeyboardInterrupt: raise - pytest.fail('Can\'t connect to the 127.0.0.1:' + port) + pytest.fail('Can\'t connect to the 127.0.0.1:' + str(port)) def findmnt(): -- cgit From 11f7d833a9bad1fb3f066cee815fe9772bed4875 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 10 Feb 2021 16:34:37 +0000 Subject: Tests: increased timeout in wait_for_record(). --- test/test_asgi_application.py | 2 +- test/test_python_application.py | 2 +- test/unit/applications/proto.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'test') diff --git a/test/test_asgi_application.py b/test/test_asgi_application.py index 5770265d..886a160b 100644 --- a/test/test_asgi_application.py +++ b/test/test_asgi_application.py @@ -377,7 +377,7 @@ Connection: close self.get(no_recv=True) assert ( - self.wait_for_record(r'\(5\) Thread: 100') is not None + self.wait_for_record(r'\(5\) Thread: 100', wait=50) is not None ), 'last thread finished' def test_asgi_application_threads(self): diff --git a/test/test_python_application.py b/test/test_python_application.py index 709df3ff..41f6f538 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -569,7 +569,7 @@ last line: 987654321 self.get(no_recv=True) assert ( - self.wait_for_record(r'\(5\) Thread: 100') is not None + self.wait_for_record(r'\(5\) Thread: 100', wait=50) is not None ), 'last thread finished' def test_python_application_iter_exception(self): diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py index af05d071..5c400621 100644 --- a/test/unit/applications/proto.py +++ b/test/unit/applications/proto.py @@ -19,8 +19,8 @@ class TestApplicationProto(TestControl): with open(option.temp_dir + '/' + name, 'r', errors='ignore') as f: return re.search(pattern, f.read()) - def wait_for_record(self, pattern, name='unit.log'): - for i in range(50): + def wait_for_record(self, pattern, name='unit.log', wait=150): + for i in range(wait): found = self.search_in_log(pattern, name) if found is not None: -- cgit From cf530e19bc5cc0de67bd57a650a0adf3d58e0881 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 15 Feb 2021 03:11:26 +0000 Subject: Tests: clear certificates after each test. --- test/conftest.py | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) (limited to 'test') diff --git a/test/conftest.py b/test/conftest.py index 35249a1f..b49fb377 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -185,7 +185,7 @@ def pytest_sessionstart(session): check_isolation() - assert 'success' in _clear_conf(unit['temp_dir'] + '/control.unit.sock') + _clear_conf(unit['temp_dir'] + '/control.unit.sock') unit_stop() @@ -281,11 +281,7 @@ def run(request): # clean temp_dir before the next test if not option.restart: - conf_resp = _clear_conf(unit['temp_dir'] + '/control.unit.sock') - - if 'success' not in conf_resp: - _print_log(log) - assert 'success' in conf_resp + _clear_conf(unit['temp_dir'] + '/control.unit.sock', log) open(unit['log'], 'w').close() @@ -457,14 +453,39 @@ def _print_log(data=None): sys.stdout.write(data) -def _clear_conf(sock): - return http.put( +def _clear_conf(sock, log=None): + def check_success(resp): + if 'success' not in resp: + _print_log(log) + assert 'success' in resp + + resp = http.put( url='/config', sock_type='unix', addr=sock, body=json.dumps({"listeners": {}, "applications": {}}), )['body'] + check_success(resp) + + try: + certs = json.loads(http.get( + url='/certificates', + sock_type='unix', + addr=sock, + )['body']).keys() + + except json.JSONDecodeError: + pytest.fail('Can\'t parse certificates list.') + + for cert in certs: + resp = http.delete( + url='/certificates/' + cert, + sock_type='unix', + addr=sock, + )['body'] + + check_success(resp) def run_process(target, *args): global _processes -- cgit From af3b6ef37d0d8d4d9e27d77a750419d96e0119bc Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Thu, 18 Feb 2021 21:21:55 +0000 Subject: Tests: added regex check. --- test/conftest.py | 2 ++ test/test_routing.py | 12 ++++++++++++ test/unit/check/regex.py | 13 +++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 test/unit/check/regex.py (limited to 'test') diff --git a/test/conftest.py b/test/conftest.py index b49fb377..e75f9208 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -19,6 +19,7 @@ from unit.check.go import check_go from unit.check.isolation import check_isolation from unit.check.node import check_node from unit.check.tls import check_openssl +from unit.check.regex import check_regex from unit.http import TestHTTP from unit.option import option from unit.utils import public_dir @@ -176,6 +177,7 @@ def pytest_sessionstart(session): option.current_dir, unit['temp_dir'], option.test_dir ) option.available['modules']['node'] = check_node(option.current_dir) + option.available['modules']['regex'] = check_regex(unit['unitd']) # remove None values diff --git a/test/test_routing.py b/test/test_routing.py index 4d27cb61..be9a1faf 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -229,6 +229,9 @@ class TestRouting(TestApplicationProto): assert self.get(url='/ABc')['status'] == 404 def test_routes_empty_regex(self): + if not option.available['modules']['regex']: + pytest.skip('requires regex') + self.route_match({"uri":"~"}) assert self.get(url='/')['status'] == 200, 'empty regexp' assert self.get(url='/anything')['status'] == 200, '/anything' @@ -238,6 +241,9 @@ class TestRouting(TestApplicationProto): assert self.get(url='/nothing')['status'] == 404, '/nothing' def test_routes_bad_regex(self): + if not option.available['modules']['regex']: + pytest.skip('requires regex') + assert 'error' in self.route( {"match": {"uri": "~/bl[ah"}, "action": {"return": 200}} ), 'bad regex' @@ -255,6 +261,9 @@ class TestRouting(TestApplicationProto): assert self.get(url='/nothing_z')['status'] == 500, '/nothing_z' def test_routes_match_regex_case_sensitive(self): + if not option.available['modules']['regex']: + pytest.skip('requires regex') + self.route_match({"uri": "~/bl[ah]"}) assert self.get(url='/rlah')['status'] == 404, '/rlah' @@ -263,6 +272,9 @@ class TestRouting(TestApplicationProto): assert self.get(url='/BLAH')['status'] == 404, '/BLAH' def test_routes_match_regex_negative_case_sensitive(self): + if not option.available['modules']['regex']: + pytest.skip('requires regex') + self.route_match({"uri": "!~/bl[ah]"}) assert self.get(url='/rlah')['status'] == 200, '/rlah' diff --git a/test/unit/check/regex.py b/test/unit/check/regex.py new file mode 100644 index 00000000..734c0150 --- /dev/null +++ b/test/unit/check/regex.py @@ -0,0 +1,13 @@ +import re +import subprocess + + +def check_regex(unitd): + output = subprocess.check_output( + [unitd, '--version'], stderr=subprocess.STDOUT + ) + + if re.search('--no-regex', output.decode()): + return False + + return True -- cgit From d0591f07d7681a66197ee9ea4104177f13ccf8df Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Tue, 23 Feb 2021 22:25:47 +0000 Subject: Tests: fixed tests to work without openssl support. --- test/conftest.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'test') diff --git a/test/conftest.py b/test/conftest.py index e75f9208..20ac6e81 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -470,6 +470,9 @@ def _clear_conf(sock, log=None): check_success(resp) + if 'openssl' not in option.available['modules']: + return + try: certs = json.loads(http.get( url='/certificates', -- cgit From 175ef1c1dbe5a8fb65b1fbb4adb8a56a16b08569 Mon Sep 17 00:00:00 2001 From: "Sergey A. Osokin" Date: Mon, 22 Mar 2021 17:15:12 +0300 Subject: Java: upgrading third-party components. --- test/unit/applications/lang/java.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index b2e17f23..c9c2095e 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -52,7 +52,7 @@ class TestApplicationJava(TestApplicationProto): os.makedirs(classes_path) classpath = ( - option.current_dir + '/build/tomcat-servlet-api-9.0.39.jar' + option.current_dir + '/build/tomcat-servlet-api-9.0.44.jar' ) ws_jars = glob.glob( -- cgit From 178f232b3ad36a763b3b5c2e0ef6f26cc1885229 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Wed, 24 Mar 2021 11:43:41 +0300 Subject: Tests: fixed racing condition in websocket test 5_15. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test case: "send a text message split into two fragments, then a continuation frame with FIN = false where there is nothing to continue, then an unfragmented text message, all sent in one chop". The test case investigates immediate connection closing since there is no message to continue. The mirror server may send a response for the first frame before the test сontinuation frame is received by the router. In this case, the test will receive a text frame before the close frame. --- test/test_asgi_websockets.py | 14 +++++++++++--- test/test_java_websockets.py | 14 +++++++++++--- test/test_node_websockets.py | 14 +++++++++++--- 3 files changed, 33 insertions(+), 9 deletions(-) (limited to 'test') diff --git a/test/test_asgi_websockets.py b/test/test_asgi_websockets.py index 6121fcc5..7218d526 100644 --- a/test/test_asgi_websockets.py +++ b/test/test_asgi_websockets.py @@ -30,8 +30,9 @@ class TestASGIWebsockets(TestApplicationPython): self.check_close(sock) - def check_close(self, sock, code=1000, no_close=False): - frame = self.ws.frame_read(sock) + def check_close(self, sock, code=1000, no_close=False, frame=None): + if frame == None: + frame = self.ws.frame_read(sock) assert frame['fin'] == True, 'close fin' assert frame['opcode'] == self.ws.OP_CLOSE, 'close opcode' @@ -930,7 +931,14 @@ class TestASGIWebsockets(TestApplicationPython): self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=False) self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment4', fin=True) - self.check_close(sock, 1002) + + frame = self.ws.frame_read(sock) + + if frame['opcode'] == self.ws.OP_TEXT: + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + frame = None + + self.check_close(sock, 1002, frame=frame) # 5_16 diff --git a/test/test_java_websockets.py b/test/test_java_websockets.py index 315c496d..df9e0885 100644 --- a/test/test_java_websockets.py +++ b/test/test_java_websockets.py @@ -27,8 +27,9 @@ class TestJavaWebsockets(TestApplicationJava): self.check_close(sock) - def check_close(self, sock, code=1000, no_close=False): - frame = self.ws.frame_read(sock) + def check_close(self, sock, code=1000, no_close=False, frame=None): + if frame == None: + frame = self.ws.frame_read(sock) assert frame['fin'] == True, 'close fin' assert frame['opcode'] == self.ws.OP_CLOSE, 'close opcode' @@ -862,7 +863,14 @@ class TestJavaWebsockets(TestApplicationJava): self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=False) self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment4', fin=True) - self.check_close(sock, 1002) + + frame = self.ws.frame_read(sock) + + if frame['opcode'] == self.ws.OP_TEXT: + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + frame = None + + self.check_close(sock, 1002, frame=frame) # 5_16 diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py index 4f1e5906..d5f79811 100644 --- a/test/test_node_websockets.py +++ b/test/test_node_websockets.py @@ -27,8 +27,9 @@ class TestNodeWebsockets(TestApplicationNode): self.check_close(sock) - def check_close(self, sock, code=1000, no_close=False): - frame = self.ws.frame_read(sock) + def check_close(self, sock, code=1000, no_close=False, frame=None): + if frame == None: + frame = self.ws.frame_read(sock) assert frame['fin'] == True, 'close fin' assert frame['opcode'] == self.ws.OP_CLOSE, 'close opcode' @@ -881,7 +882,14 @@ class TestNodeWebsockets(TestApplicationNode): self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=False) self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment4', fin=True) - self.check_close(sock, 1002) + + frame = self.ws.frame_read(sock) + + if frame['opcode'] == self.ws.OP_TEXT: + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + frame = None + + self.check_close(sock, 1002, frame=frame) # 5_16 -- cgit