diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/python/delayed/wsgi.py | 25 | ||||
-rw-r--r-- | test/python/errors_write/wsgi.py | 1 | ||||
-rw-r--r-- | test/python/iter_exception/wsgi.py | 45 | ||||
-rw-r--r-- | test/python/log_body/wsgi.py | 9 | ||||
-rw-r--r-- | test/python/threading/wsgi.py | 33 | ||||
-rw-r--r-- | test/ruby/constants/config.ru | 15 | ||||
-rw-r--r-- | test/test_access_log.py | 47 | ||||
-rw-r--r-- | test/test_configuration.py | 2 | ||||
-rw-r--r-- | test/test_go_isolation.py | 32 | ||||
-rw-r--r-- | test/test_java_websockets.py | 63 | ||||
-rw-r--r-- | test/test_node_websockets.py | 43 | ||||
-rw-r--r-- | test/test_proxy.py | 622 | ||||
-rw-r--r-- | test/test_python_application.py | 182 | ||||
-rw-r--r-- | test/test_routing.py | 118 | ||||
-rw-r--r-- | test/test_ruby_application.py | 23 | ||||
-rw-r--r-- | test/test_static.py | 6 | ||||
-rw-r--r-- | test/test_usr1.py | 92 | ||||
-rw-r--r-- | test/unit/applications/websockets.py | 31 | ||||
-rw-r--r-- | test/unit/http.py | 20 | ||||
-rw-r--r-- | test/unit/main.py | 48 |
20 files changed, 1282 insertions, 175 deletions
diff --git a/test/python/delayed/wsgi.py b/test/python/delayed/wsgi.py new file mode 100644 index 00000000..d25e2765 --- /dev/null +++ b/test/python/delayed/wsgi.py @@ -0,0 +1,25 @@ +import time + + +def application(environ, start_response): + parts = int(environ.get('HTTP_X_PARTS', 1)) + delay = int(environ.get('HTTP_X_DELAY', 0)) + + content_length = int(environ.get('CONTENT_LENGTH', 0)) + body = bytes(environ['wsgi.input'].read(content_length)) + + write = start_response('200', [('Content-Length', str(len(body)))]) + + if not body: + return [] + + step = int(len(body) / parts) + for i in range(0, len(body), step): + try: + write(body[i : i + step]) + except: + break + + time.sleep(delay) + + return [] diff --git a/test/python/errors_write/wsgi.py b/test/python/errors_write/wsgi.py index b1a9d2ee..148bce9e 100644 --- a/test/python/errors_write/wsgi.py +++ b/test/python/errors_write/wsgi.py @@ -1,5 +1,6 @@ def application(environ, start_response): environ['wsgi.errors'].write('Error in application.') + environ['wsgi.errors'].flush() start_response('200', [('Content-Length', '0')]) return [] diff --git a/test/python/iter_exception/wsgi.py b/test/python/iter_exception/wsgi.py new file mode 100644 index 00000000..66a09af7 --- /dev/null +++ b/test/python/iter_exception/wsgi.py @@ -0,0 +1,45 @@ +class application: + def __init__(self, environ, start_response): + self.environ = environ + self.start = start_response + + self.next = self.__next__ + + def __iter__(self): + self.__i = 0 + self._skip_level = int(self.environ.get('HTTP_X_SKIP', 0)) + self._not_skip_close = int(self.environ.get('HTTP_X_NOT_SKIP_CLOSE', 0)) + self._is_chunked = self.environ.get('HTTP_X_CHUNKED') + + headers = [(('Content-Length', '10'))] + if self._is_chunked is not None: + headers = [] + + if self._skip_level < 1: + raise Exception('first exception') + + write = self.start('200', headers) + + if self._skip_level < 2: + raise Exception('second exception') + + write(b'XXXXX') + + if self._skip_level < 3: + raise Exception('third exception') + + return self + + def __next__(self): + if self._skip_level < 4: + raise Exception('next exception') + + self.__i += 1 + if self.__i > 2: + raise StopIteration + + return b'X' + + def close(self): + if self._not_skip_close == 1: + raise Exception('close exception') diff --git a/test/python/log_body/wsgi.py b/test/python/log_body/wsgi.py new file mode 100644 index 00000000..9dcb1b0c --- /dev/null +++ b/test/python/log_body/wsgi.py @@ -0,0 +1,9 @@ +def application(environ, start_response): + content_length = int(environ.get('CONTENT_LENGTH', 0)) + body = bytes(environ['wsgi.input'].read(content_length)) + + environ['wsgi.errors'].write(body) + environ['wsgi.errors'].flush() + + start_response('200', [('Content-Length', '0')]) + return [] diff --git a/test/python/threading/wsgi.py b/test/python/threading/wsgi.py new file mode 100644 index 00000000..adaa2a37 --- /dev/null +++ b/test/python/threading/wsgi.py @@ -0,0 +1,33 @@ +import sys +import time +import threading + + +class Foo(threading.Thread): + num = 10 + + def __init__(self, x): + self.__x = x + threading.Thread.__init__(self) + + def log_index(self, index): + sys.stderr.write( + "(" + str(index) + ") Thread: " + str(self.__x) + "\n" + ) + sys.stderr.flush() + + def run(self): + i = 0 + for _ in range(3): + self.log_index(i) + i += 1 + time.sleep(1) + self.log_index(i) + i += 1 + + +def application(environ, start_response): + Foo(Foo.num).start() + Foo.num += 10 + start_response('200 OK', [('Content-Length', '0')]) + return [] diff --git a/test/ruby/constants/config.ru b/test/ruby/constants/config.ru new file mode 100644 index 00000000..e0951bf4 --- /dev/null +++ b/test/ruby/constants/config.ru @@ -0,0 +1,15 @@ +app = Proc.new do |env| + ['200', { + 'X-Copyright' => RUBY_COPYRIGHT, + 'X-Description' => RUBY_DESCRIPTION, + 'X-Engine' => RUBY_ENGINE, + 'X-Engine-Version' => RUBY_ENGINE_VERSION, + 'X-Patchlevel' => RUBY_PATCHLEVEL.to_s, + 'X-Platform' => RUBY_PLATFORM, + 'X-Release-Date' => RUBY_RELEASE_DATE, + 'X-Revision' => RUBY_REVISION.to_s, + 'X-Version' => RUBY_VERSION, + }, []] +end + +run app diff --git a/test/test_access_log.py b/test/test_access_log.py index 8dc87524..94f6e7bf 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -12,7 +12,11 @@ class TestAccessLog(TestApplicationPython): def load(self, script): super().load(script) - self.conf('"' + self.testdir + '/access.log"', 'access_log') + self.assertIn( + 'success', + self.conf('"' + self.testdir + '/access.log"', 'access_log'), + 'access_log configure', + ) def wait_for_record(self, pattern, name='access.log'): return super().wait_for_record(pattern, name) @@ -111,7 +115,9 @@ Connection: close addr = self.testdir + '/sock' - self.conf({"unix:" + addr: {"pass": "applications/empty"}}, 'listeners') + self.conf( + {"unix:" + addr: {"pass": "applications/empty"}}, 'listeners' + ) self.get(sock_type='unix', addr=addr) @@ -292,42 +298,5 @@ Connection: close 'change', ) - def test_access_log_reopen(self): - self.load('empty') - - log_path = self.testdir + '/access.log' - - self.assertTrue(self.waitforfiles(log_path), 'open') - - log_path_new = self.testdir + '/new.log' - - os.rename(log_path, log_path_new) - - self.get() - - self.assertIsNotNone( - self.wait_for_record(r'"GET / HTTP/1.1" 200 0 "-" "-"', 'new.log'), - 'rename new', - ) - self.assertFalse(os.path.isfile(log_path), 'rename old') - - with open(self.testdir + '/unit.pid', 'r') as f: - pid = f.read().rstrip() - - call(['kill', '-s', 'USR1', pid]) - - self.assertTrue(self.waitforfiles(log_path), 'reopen') - - self.get(url='/usr1') - - self.assertIsNotNone( - self.wait_for_record(r'"GET /usr1 HTTP/1.1" 200 0 "-" "-"'), - 'reopen 2', - ) - self.assertIsNone( - self.search_in_log(r'/usr1', 'new.log'), 'rename new 2' - ) - - if __name__ == '__main__': TestAccessLog.main() diff --git a/test/test_configuration.py b/test/test_configuration.py index 69647858..186e037d 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -321,7 +321,7 @@ class TestConfiguration(TestControl): } for a in range(999) }, - "listeners": {"*:7001": {"pass": "applications/app-1"}}, + "listeners": {"*:7080": {"pass": "applications/app-1"}}, } self.assertIn('success', self.conf(conf)) diff --git a/test/test_go_isolation.py b/test/test_go_isolation.py index 780c2b03..ee5ddf47 100644 --- a/test/test_go_isolation.py +++ b/test/test_go_isolation.py @@ -130,6 +130,38 @@ class TestGoIsolation(TestApplicationGo): self.assertEqual(obj['PID'], 1, 'pid of container is 1') + def test_isolation_namespace_false(self): + self.load('ns_inspect') + allns = list(self.available['features']['isolation'].keys()) + + remove_list = ['unprivileged_userns_clone', 'ipc', 'cgroup'] + allns = [ns for ns in allns if ns not in remove_list] + + namespaces = {} + for ns in allns: + if ns == 'user': + namespaces['credential'] = False + elif ns == 'mnt': + namespaces['mount'] = False + elif ns == 'net': + namespaces['network'] = False + elif ns == 'uts': + namespaces['uname'] = False + else: + namespaces[ns] = False + + self.conf_isolation({"namespaces": namespaces}) + + obj = self.isolation.parsejson(self.get()['body']) + + for ns in allns: + if ns.upper() in obj['NS']: + self.assertEqual( + obj['NS'][ns.upper()], + self.available['features']['isolation'][ns], + '%s match' % ns, + ) + if __name__ == '__main__': TestGoIsolation.main() diff --git a/test/test_java_websockets.py b/test/test_java_websockets.py index 3f2c0a8a..d75ee3a6 100644 --- a/test/test_java_websockets.py +++ b/test/test_java_websockets.py @@ -8,7 +8,7 @@ from unit.applications.websockets import TestApplicationWebsocket class TestJavaWebsockets(TestApplicationJava): prerequisites = {'modules': ['java']} - ws = TestApplicationWebsocket(True) + ws = TestApplicationWebsocket() def setUp(self): super().setUp() @@ -179,18 +179,14 @@ class TestJavaWebsockets(TestApplicationJava): ): # FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1 self.load('websockets_mirror') - self.get() - - key = self.ws.key() resp = self.get( headers={ 'Host': 'localhost', 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, - read_timeout=1, ) self.assertEqual(resp['status'], 400, 'upgrade absent') @@ -198,20 +194,17 @@ class TestJavaWebsockets(TestApplicationJava): def test_java_websockets_handshake_case_insensitive(self): self.load('websockets_mirror') - self.get() - - key = self.ws.key() - resp = self.get( + resp, sock, _ = self.ws.upgrade( headers={ 'Host': 'localhost', 'Upgrade': 'WEBSOCKET', 'Connection': 'UPGRADE', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, - }, - read_timeout=1, + } ) + sock.close() self.assertEqual(resp['status'], 101, 'status') @@ -219,18 +212,14 @@ class TestJavaWebsockets(TestApplicationJava): def test_java_websockets_handshake_connection_absent(self): # FAIL self.load('websockets_mirror') - self.get() - - key = self.ws.key() resp = self.get( headers={ 'Host': 'localhost', 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, - read_timeout=1, ) self.assertEqual(resp['status'], 400, 'status') @@ -238,18 +227,14 @@ class TestJavaWebsockets(TestApplicationJava): def test_java_websockets_handshake_version_absent(self): self.load('websockets_mirror') - self.get() - - key = self.ws.key() resp = self.get( headers={ 'Host': 'localhost', 'Upgrade': 'websocket', 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', }, - read_timeout=1, ) self.assertEqual(resp['status'], 426, 'status') @@ -258,8 +243,6 @@ class TestJavaWebsockets(TestApplicationJava): def test_java_websockets_handshake_key_invalid(self): self.load('websockets_mirror') - self.get() - resp = self.get( headers={ 'Host': 'localhost', @@ -269,7 +252,6 @@ class TestJavaWebsockets(TestApplicationJava): 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, - read_timeout=1, ) self.assertEqual(resp['status'], 400, 'key length') @@ -284,7 +266,6 @@ class TestJavaWebsockets(TestApplicationJava): 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, - read_timeout=1, ) self.assertEqual( @@ -294,19 +275,15 @@ class TestJavaWebsockets(TestApplicationJava): def test_java_websockets_handshake_method_invalid(self): self.load('websockets_mirror') - self.get() - - key = self.ws.key() resp = self.post( headers={ 'Host': 'localhost', 'Upgrade': 'websocket', 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, - read_timeout=1, ) self.assertEqual(resp['status'], 400, 'status') @@ -314,20 +291,16 @@ class TestJavaWebsockets(TestApplicationJava): def test_java_websockets_handshake_http_10(self): self.load('websockets_mirror') - self.get() - - key = self.ws.key() resp = self.get( headers={ 'Host': 'localhost', 'Upgrade': 'websocket', 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, http_10=True, - read_timeout=1, ) self.assertEqual(resp['status'], 400, 'status') @@ -335,20 +308,16 @@ class TestJavaWebsockets(TestApplicationJava): def test_java_websockets_handshake_uri_invalid(self): self.load('websockets_mirror') - self.get() - - key = self.ws.key() resp = self.get( headers={ 'Host': 'localhost', 'Upgrade': 'websocket', 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, url='!', - read_timeout=1, ) self.assertEqual(resp['status'], 400, 'status') @@ -356,19 +325,17 @@ class TestJavaWebsockets(TestApplicationJava): def test_java_websockets_protocol_absent(self): self.load('websockets_mirror') - self.get() - key = self.ws.key() - resp = self.get( + resp, sock, _ = self.ws.upgrade( headers={ 'Host': 'localhost', 'Upgrade': 'websocket', 'Connection': 'Upgrade', 'Sec-WebSocket-Key': key, 'Sec-WebSocket-Version': 13, - }, - read_timeout=1, + } ) + sock.close() self.assertEqual(resp['status'], 101, 'status') self.assertEqual(resp['headers']['Upgrade'], 'websocket', 'upgrade') @@ -1165,7 +1132,7 @@ class TestJavaWebsockets(TestApplicationJava): sock.close() - # 7_3_1 # FAIL + # 7_3_1 _, sock, _ = self.ws.upgrade() diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py index b24bee75..bb189552 100644 --- a/test/test_node_websockets.py +++ b/test/test_node_websockets.py @@ -198,16 +198,14 @@ class TestNodeWebsockets(TestApplicationNode): ): # FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1 self.load('websockets/mirror') - key = self.ws.key() resp = self.get( headers={ 'Host': 'localhost', 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, - read_timeout=1, ) self.assertEqual(resp['status'], 400, 'upgrade absent') @@ -215,18 +213,17 @@ class TestNodeWebsockets(TestApplicationNode): def test_node_websockets_handshake_case_insensitive(self): self.load('websockets/mirror') - key = self.ws.key() - resp = self.get( + resp, sock, _ = self.ws.upgrade( headers={ 'Host': 'localhost', 'Upgrade': 'WEBSOCKET', 'Connection': 'UPGRADE', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, - }, - read_timeout=1, + } ) + sock.close() self.assertEqual(resp['status'], 101, 'status') @@ -234,16 +231,14 @@ class TestNodeWebsockets(TestApplicationNode): def test_node_websockets_handshake_connection_absent(self): # FAIL self.load('websockets/mirror') - key = self.ws.key() resp = self.get( headers={ 'Host': 'localhost', 'Upgrade': 'websocket', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, - read_timeout=1, ) self.assertEqual(resp['status'], 400, 'status') @@ -251,16 +246,14 @@ class TestNodeWebsockets(TestApplicationNode): def test_node_websockets_handshake_version_absent(self): self.load('websockets/mirror') - key = self.ws.key() resp = self.get( headers={ 'Host': 'localhost', 'Upgrade': 'websocket', 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', }, - read_timeout=1, ) self.assertEqual(resp['status'], 426, 'status') @@ -278,7 +271,6 @@ class TestNodeWebsockets(TestApplicationNode): 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, - read_timeout=1, ) self.assertEqual(resp['status'], 400, 'key length') @@ -293,7 +285,6 @@ class TestNodeWebsockets(TestApplicationNode): 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, - read_timeout=1, ) self.assertEqual( @@ -303,17 +294,15 @@ class TestNodeWebsockets(TestApplicationNode): def test_node_websockets_handshake_method_invalid(self): self.load('websockets/mirror') - key = self.ws.key() resp = self.post( headers={ 'Host': 'localhost', 'Upgrade': 'websocket', 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, - read_timeout=1, ) self.assertEqual(resp['status'], 400, 'status') @@ -321,18 +310,16 @@ class TestNodeWebsockets(TestApplicationNode): def test_node_websockets_handshake_http_10(self): self.load('websockets/mirror') - key = self.ws.key() resp = self.get( headers={ 'Host': 'localhost', 'Upgrade': 'websocket', 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, http_10=True, - read_timeout=1, ) self.assertEqual(resp['status'], 400, 'status') @@ -340,18 +327,16 @@ class TestNodeWebsockets(TestApplicationNode): def test_node_websockets_handshake_uri_invalid(self): self.load('websockets/mirror') - key = self.ws.key() resp = self.get( headers={ 'Host': 'localhost', 'Upgrade': 'websocket', 'Connection': 'Upgrade', - 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Key': self.ws.key(), 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, }, url='!', - read_timeout=1, ) self.assertEqual(resp['status'], 400, 'status') @@ -360,16 +345,16 @@ class TestNodeWebsockets(TestApplicationNode): self.load('websockets/mirror') key = self.ws.key() - resp = self.get( + resp, sock, _ = self.ws.upgrade( headers={ 'Host': 'localhost', 'Upgrade': 'websocket', 'Connection': 'Upgrade', 'Sec-WebSocket-Key': key, 'Sec-WebSocket-Version': 13, - }, - read_timeout=1, + } ) + sock.close() self.assertEqual(resp['status'], 101, 'status') self.assertEqual(resp['headers']['Upgrade'], 'websocket', 'upgrade') @@ -1166,7 +1151,7 @@ class TestNodeWebsockets(TestApplicationNode): sock.close() - # 7_3_1 # FAIL + # 7_3_1 _, sock, _ = self.ws.upgrade() diff --git a/test/test_proxy.py b/test/test_proxy.py new file mode 100644 index 00000000..4697b88f --- /dev/null +++ b/test/test_proxy.py @@ -0,0 +1,622 @@ +import re +import time +import socket +import unittest +from unit.applications.lang.python import TestApplicationPython + + +class TestProxy(TestApplicationPython): + prerequisites = {'modules': ['python']} + + SERVER_PORT = 7999 + + def run_server(self): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + server_address = ('', self.SERVER_PORT) + sock.bind(server_address) + sock.listen(5) + + def recvall(sock): + buff_size = 4096 + data = b'' + while True: + part = sock.recv(buff_size) + data += part + if len(part) < buff_size: + break + return data + + req = b"""HTTP/1.1 200 OK +Content-Length: 10 + +""" + + while True: + connection, client_address = sock.accept() + + data = recvall(connection).decode() + + to_send = req + + m = re.search('X-Len: (\d+)', data) + if m: + to_send += b'X' * int(m.group(1)) + + connection.sendall(to_send) + + connection.close() + + def get_http10(self, *args, **kwargs): + return self.get(*args, http_10=True, **kwargs) + + def post_http10(self, *args, **kwargs): + return self.post(*args, http_10=True, **kwargs) + + def setUp(self): + super().setUp() + + self.run_process(self.run_server) + self.waitforsocket(self.SERVER_PORT) + + self.assertIn( + 'success', + self.conf( + { + "listeners": { + "*:7080": {"pass": "routes"}, + "*:7081": {"pass": "applications/mirror"}, + }, + "routes": [{"action": {"proxy": "http://127.0.0.1:7081"}}], + "applications": { + "mirror": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + "/python/mirror", + "working_directory": self.current_dir + + "/python/mirror", + "module": "wsgi", + }, + "custom_header": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + "/python/custom_header", + "working_directory": self.current_dir + + "/python/custom_header", + "module": "wsgi", + }, + "delayed": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + "/python/delayed", + "working_directory": self.current_dir + + "/python/delayed", + "module": "wsgi", + }, + }, + } + ), + 'proxy initial configuration', + ) + + def test_proxy_http10(self): + for _ in range(10): + self.assertEqual(self.get_http10()['status'], 200, 'status') + + def test_proxy_chain(self): + self.assertIn( + 'success', + self.conf( + { + "listeners": { + "*:7080": {"pass": "routes/first"}, + "*:7081": {"pass": "routes/second"}, + "*:7082": {"pass": "routes/third"}, + "*:7083": {"pass": "routes/fourth"}, + "*:7084": {"pass": "routes/fifth"}, + "*:7085": {"pass": "applications/mirror"}, + }, + "routes": { + "first": [ + {"action": {"proxy": "http://127.0.0.1:7081"}} + ], + "second": [ + {"action": {"proxy": "http://127.0.0.1:7082"}} + ], + "third": [ + {"action": {"proxy": "http://127.0.0.1:7083"}} + ], + "fourth": [ + {"action": {"proxy": "http://127.0.0.1:7084"}} + ], + "fifth": [ + {"action": {"proxy": "http://127.0.0.1:7085"}} + ], + }, + "applications": { + "mirror": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + "/python/mirror", + "working_directory": self.current_dir + + "/python/mirror", + "module": "wsgi", + } + }, + } + ), + 'proxy chain configuration', + ) + + self.assertEqual(self.get_http10()['status'], 200, 'status') + + def test_proxy_body(self): + payload = '0123456789' + for _ in range(10): + resp = self.post_http10(body=payload) + + self.assertEqual(resp['status'], 200, 'status') + self.assertEqual(resp['body'], payload, 'body') + + payload = 'X' * 4096 + for _ in range(10): + resp = self.post_http10(body=payload) + + self.assertEqual(resp['status'], 200, 'status') + self.assertEqual(resp['body'], payload, 'body') + + payload = 'X' * 4097 + for _ in range(10): + resp = self.post_http10(body=payload) + + self.assertEqual(resp['status'], 200, 'status') + self.assertEqual(resp['body'], payload, 'body') + + payload = 'X' * 4096 * 256 + for _ in range(10): + resp = self.post_http10(body=payload, read_buffer_size=4096 * 128) + + self.assertEqual(resp['status'], 200, 'status') + self.assertEqual(resp['body'], payload, 'body') + + payload = 'X' * 4096 * 257 + for _ in range(10): + resp = self.post_http10(body=payload, read_buffer_size=4096 * 128) + + 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 + + socks = [] + for i in range(10): + _, sock = self.post_http10( + body=payload + str(i), + start=True, + no_recv=True, + read_buffer_size=buff_size, + ) + socks.append(sock) + + for i in range(10): + resp = self.recvall(socks[i], buff_size=buff_size).decode() + socks[i].close() + + resp = self._resp_to_dict(resp) + + self.assertEqual(resp['status'], 200, 'status') + self.assertEqual(resp['body'], payload + str(i), 'body') + + def test_proxy_header(self): + self.assertIn( + 'success', + self.conf( + {"pass": "applications/custom_header"}, 'listeners/*:7081' + ), + 'custom_header configure', + ) + + header_value = 'blah' + self.assertEqual( + self.get_http10( + headers={'Host': 'localhost', 'Custom-Header': header_value} + )['headers']['Custom-Header'], + header_value, + 'custom header', + ) + + header_value = '(),/:;<=>?@[\]{}\t !#$%&\'*+-.^_`|~' + self.assertEqual( + self.get_http10( + headers={'Host': 'localhost', 'Custom-Header': header_value} + )['headers']['Custom-Header'], + header_value, + 'custom header 2', + ) + + header_value = 'X' * 4096 + self.assertEqual( + self.get_http10( + headers={'Host': 'localhost', 'Custom-Header': header_value} + )['headers']['Custom-Header'], + header_value, + 'custom header 3', + ) + + header_value = 'X' * 8191 + self.assertEqual( + self.get_http10( + headers={'Host': 'localhost', 'Custom-Header': header_value} + )['headers']['Custom-Header'], + header_value, + 'custom header 4', + ) + + header_value = 'X' * 8192 + self.assertEqual( + self.get_http10( + headers={'Host': 'localhost', 'Custom-Header': header_value} + )['status'], + 431, + 'custom header 5', + ) + + def test_proxy_fragmented(self): + _, sock = self.http( + b"""GET / HTT""", raw=True, start=True, no_recv=True + ) + + time.sleep(1) + + sock.sendall("P/1.0\r\nHost: localhos".encode()) + + time.sleep(1) + + sock.sendall("t\r\n\r\n".encode()) + + self.assertRegex( + self.recvall(sock).decode(), '200 OK', 'fragmented send' + ) + sock.close() + + def test_proxy_fragmented_close(self): + _, sock = self.http( + b"""GET / HTT""", raw=True, start=True, no_recv=True + ) + + time.sleep(1) + + sock.sendall("P/1.0\r\nHo".encode()) + + sock.close() + + def test_proxy_fragmented_body(self): + _, sock = self.http( + b"""GET / HTT""", raw=True, start=True, no_recv=True + ) + + time.sleep(1) + + sock.sendall("P/1.0\r\nHost: localhost\r\n".encode()) + sock.sendall("Content-Length: 30000\r\n".encode()) + + time.sleep(1) + + sock.sendall("\r\n".encode()) + sock.sendall(("X" * 10000).encode()) + + time.sleep(1) + + sock.sendall(("X" * 10000).encode()) + + time.sleep(1) + + sock.sendall(("X" * 10000).encode()) + + resp = self._resp_to_dict(self.recvall(sock).decode()) + sock.close() + + self.assertEqual(resp['status'], 200, 'status') + self.assertEqual(resp['body'], "X" * 30000, 'body') + + def test_proxy_fragmented_body_close(self): + _, sock = self.http( + b"""GET / HTT""", raw=True, start=True, no_recv=True + ) + + time.sleep(1) + + sock.sendall("P/1.0\r\nHost: localhost\r\n".encode()) + sock.sendall("Content-Length: 30000\r\n".encode()) + + time.sleep(1) + + sock.sendall("\r\n".encode()) + sock.sendall(("X" * 10000).encode()) + + sock.close() + + def test_proxy_nowhere(self): + self.assertIn( + 'success', + self.conf( + [{"action": {"proxy": "http://127.0.0.1:7082"}}], 'routes' + ), + 'proxy path changed', + ) + + self.assertEqual(self.get_http10()['status'], 502, 'status') + + def test_proxy_ipv6(self): + self.assertIn( + 'success', + self.conf( + { + "*:7080": {"pass": "routes"}, + "[::1]:7081": {'application': 'mirror'}, + }, + 'listeners', + ), + 'add ipv6 listener configure', + ) + + self.assertIn( + 'success', + self.conf([{"action": {"proxy": "http://[::1]:7081"}}], 'routes'), + 'proxy ipv6 configure', + ) + + self.assertEqual(self.get_http10()['status'], 200, 'status') + + def test_proxy_unix(self): + addr = self.testdir + '/sock' + + self.assertIn( + 'success', + self.conf( + { + "*:7080": {"pass": "routes"}, + "unix:" + addr: {'application': 'mirror'}, + }, + 'listeners', + ), + 'add unix listener configure', + ) + + self.assertIn( + 'success', + self.conf( + [{"action": {"proxy": 'http://unix:' + addr}}], 'routes' + ), + 'proxy unix configure', + ) + + self.assertEqual(self.get_http10()['status'], 200, 'status') + + def test_proxy_delayed(self): + self.assertIn( + 'success', + self.conf( + {"pass": "applications/delayed"}, 'listeners/*:7081' + ), + 'delayed configure', + ) + + body = '0123456789' * 1000 + resp = self.post_http10( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Content-Length': str(len(body)), + 'X-Parts': '2', + 'X-Delay': '1', + }, + body=body, + ) + + self.assertEqual(resp['status'], 200, 'status') + self.assertEqual(resp['body'], body, 'body') + + resp = self.post_http10( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Content-Length': str(len(body)), + 'X-Parts': '2', + 'X-Delay': '1', + }, + body=body, + ) + + self.assertEqual(resp['status'], 200, 'status') + self.assertEqual(resp['body'], body, 'body') + + def test_proxy_delayed_close(self): + self.assertIn( + 'success', + self.conf( + {"pass": "applications/delayed"}, 'listeners/*:7081' + ), + 'delayed configure', + ) + + _, sock = self.post_http10( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Content-Length': '10000', + 'X-Parts': '3', + 'X-Delay': '1', + }, + body='0123456789' * 1000, + start=True, + no_recv=True, + ) + + self.assertRegex( + sock.recv(100).decode(), '200 OK', 'first' + ) + sock.close() + + _, sock = self.post_http10( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Content-Length': '10000', + 'X-Parts': '3', + 'X-Delay': '1', + }, + body='0123456789' * 1000, + start=True, + no_recv=True, + ) + + self.assertRegex( + sock.recv(100).decode(), '200 OK', 'second' + ) + sock.close() + + @unittest.skip('not yet') + def test_proxy_content_length(self): + self.assertIn( + 'success', + self.conf( + [ + { + "action": { + "proxy": "http://127.0.0.1:" + + str(self.SERVER_PORT) + } + } + ], + 'routes', + ), + 'proxy backend configure', + ) + + resp = self.get_http10() + self.assertEqual(len(resp['body']), 0, 'body lt Content-Length 0') + + resp = self.get_http10(headers={'Host': 'localhost', 'X-Len': '5'}) + self.assertEqual(len(resp['body']), 5, 'body lt Content-Length 5') + + resp = self.get_http10(headers={'Host': 'localhost', 'X-Len': '9'}) + self.assertEqual(len(resp['body']), 9, 'body lt Content-Length 9') + + resp = self.get_http10(headers={'Host': 'localhost', 'X-Len': '11'}) + self.assertEqual(len(resp['body']), 10, 'body gt Content-Length 11') + + resp = self.get_http10(headers={'Host': 'localhost', 'X-Len': '15'}) + self.assertEqual(len(resp['body']), 10, 'body gt Content-Length 15') + + def test_proxy_invalid(self): + self.assertIn( + 'error', + self.conf([{"action": {"proxy": 'blah'}}], 'routes'), + 'proxy invalid', + ) + self.assertIn( + 'error', + self.conf([{"action": {"proxy": '/blah'}}], 'routes'), + 'proxy invalid 2', + ) + self.assertIn( + 'error', + self.conf([{"action": {"proxy": 'unix:/blah'}}], 'routes'), + 'proxy unix invalid 2', + ) + self.assertIn( + 'error', + self.conf([{"action": {"proxy": 'http://blah'}}], 'routes'), + 'proxy unix invalid 3', + ) + self.assertIn( + 'error', + self.conf([{"action": {"proxy": 'http://127.0.0.1'}}], 'routes'), + 'proxy ipv4 invalid', + ) + self.assertIn( + 'error', + self.conf([{"action": {"proxy": 'http://127.0.0.1:'}}], 'routes'), + 'proxy ipv4 invalid 2', + ) + self.assertIn( + 'error', + self.conf( + [{"action": {"proxy": 'http://127.0.0.1:blah'}}], 'routes' + ), + 'proxy ipv4 invalid 3', + ) + self.assertIn( + 'error', + self.conf( + [{"action": {"proxy": 'http://127.0.0.1:-1'}}], 'routes' + ), + 'proxy ipv4 invalid 4', + ) + self.assertIn( + 'error', + self.conf( + [{"action": {"proxy": 'http://127.0.0.1:7080b'}}], 'routes' + ), + 'proxy ipv4 invalid 5', + ) + self.assertIn( + 'error', + self.conf( + [{"action": {"proxy": 'http://[]'}}], 'routes' + ), + 'proxy ipv6 invalid', + ) + self.assertIn( + 'error', + self.conf( + [{"action": {"proxy": 'http://[]:7080'}}], 'routes' + ), + 'proxy ipv6 invalid 2', + ) + self.assertIn( + 'error', + self.conf( + [{"action": {"proxy": 'http://[:]:7080'}}], 'routes' + ), + 'proxy ipv6 invalid 3', + ) + self.assertIn( + 'error', + self.conf( + [{"action": {"proxy": 'http://[::7080'}}], 'routes' + ), + 'proxy ipv6 invalid 4', + ) + + @unittest.skip('not yet') + def test_proxy_loop(self): + self.conf( + { + "listeners": { + "*:7080": {"pass": "routes"}, + "*:7081": {"pass": "applications/mirror"}, + "*:7082": {"pass": "routes"}, + }, + "routes": [{"action": {"proxy": "http://127.0.0.1:7082"}}], + "applications": { + "mirror": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + "/python/mirror", + "working_directory": self.current_dir + + "/python/mirror", + "module": "wsgi", + }, + }, + } + ) + + self.get_http10(no_recv=True) + +if __name__ == '__main__': + TestProxy.main() diff --git a/test/test_python_application.py b/test/test_python_application.py index 5b6e2089..ae8f01ca 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -1,3 +1,4 @@ +import re import time import unittest from unit.applications.lang.python import TestApplicationPython @@ -6,6 +7,10 @@ from unit.applications.lang.python import TestApplicationPython class TestPythonApplication(TestApplicationPython): prerequisites = {'modules': ['python']} + def findall(self, pattern): + with open(self.testdir + '/unit.log', 'r', errors='ignore') as f: + return re.findall(pattern, f.read()) + def test_python_application_variables(self): self.load('variables') @@ -130,6 +135,18 @@ class TestPythonApplication(TestApplicationPython): self.get()['headers']['Server-Port'], '7080', 'Server-Port header' ) + @unittest.skip('not yet') + def test_python_application_working_directory_invalid(self): + self.load('empty') + + self.assertIn( + 'success', + self.conf('"/blah"', 'applications/empty/working_directory'), + 'configure invalid working_directory', + ) + + self.assertEqual(self.get()['status'], 500, 'status') + def test_python_application_204_transfer_encoding(self): self.load('204_no_content') @@ -495,6 +512,171 @@ Connection: close self.assertEqual(self.get()['body'], '0123456789', 'write') + def test_python_application_threading(self): + """wait_for_record() timeouts after 5s while every thread works at + least 3s. So without releasing GIL test should fail. + """ + + self.load('threading') + + for _ in range(10): + self.get(no_recv=True) + + self.assertIsNotNone( + self.wait_for_record(r'\(5\) Thread: 100'), 'last thread finished' + ) + + def test_python_application_iter_exception(self): + self.load('iter_exception') + + # Default request doesn't lead to the exception. + + resp = self.get( + headers={ + 'Host': 'localhost', + 'X-Skip': '9', + 'X-Chunked': '1', + 'Connection': 'close', + } + ) + self.assertEqual(resp['status'], 200, 'status') + self.assertEqual(resp['body'][-5:], '0\r\n\r\n', 'body') + + # Exception before start_response(). + + self.assertEqual(self.get()['status'], 503, 'error') + + self.assertIsNotNone(self.wait_for_record(r'Traceback'), 'traceback') + self.assertIsNotNone( + self.wait_for_record(r'raise Exception\(\'first exception\'\)'), + 'first exception raise', + ) + self.assertEqual( + len(self.findall(r'Traceback')), 1, 'traceback count 1' + ) + + # Exception after start_response(), before first write(). + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'X-Skip': '1', + 'Connection': 'close', + } + )['status'], + 503, + 'error 2', + ) + + self.assertIsNotNone( + self.wait_for_record(r'raise Exception\(\'second exception\'\)'), + 'exception raise second', + ) + self.assertEqual( + len(self.findall(r'Traceback')), 2, 'traceback count 2' + ) + + # Exception after first write(), before first __next__(). + + _, sock = self.get( + headers={ + 'Host': 'localhost', + 'X-Skip': '2', + 'Connection': 'keep-alive', + }, + start=True, + ) + + self.assertIsNotNone( + self.wait_for_record(r'raise Exception\(\'third exception\'\)'), + 'exception raise third', + ) + self.assertEqual( + len(self.findall(r'Traceback')), 3, 'traceback count 3' + ) + + self.assertDictEqual(self.get(sock=sock), {}, 'closed connection') + + # Exception after first write(), before first __next__(), + # chunked (incomplete body). + + resp = self.get( + headers={ + 'Host': 'localhost', + 'X-Skip': '2', + 'X-Chunked': '1', + 'Connection': 'close', + } + ) + if 'body' in resp: + self.assertNotEqual( + resp['body'][-5:], '0\r\n\r\n', 'incomplete body' + ) + self.assertEqual( + len(self.findall(r'Traceback')), 4, 'traceback count 4' + ) + + # Exception in __next__(). + + _, sock = self.get( + headers={ + 'Host': 'localhost', + 'X-Skip': '3', + 'Connection': 'keep-alive', + }, + start=True, + ) + + self.assertIsNotNone( + self.wait_for_record(r'raise Exception\(\'next exception\'\)'), + 'exception raise next', + ) + self.assertEqual( + len(self.findall(r'Traceback')), 5, 'traceback count 5' + ) + + self.assertDictEqual(self.get(sock=sock), {}, 'closed connection 2') + + # Exception in __next__(), chunked (incomplete body). + + resp = self.get( + headers={ + 'Host': 'localhost', + 'X-Skip': '3', + 'X-Chunked': '1', + 'Connection': 'close', + } + ) + if 'body' in resp: + self.assertNotEqual( + resp['body'][-5:], '0\r\n\r\n', 'incomplete body 2' + ) + self.assertEqual( + len(self.findall(r'Traceback')), 6, 'traceback count 6' + ) + + # Exception before start_response() and in close(). + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'X-Not-Skip-Close': '1', + 'Connection': 'close', + } + )['status'], + 503, + 'error', + ) + + self.assertIsNotNone( + self.wait_for_record(r'raise Exception\(\'close exception\'\)'), + 'exception raise close', + ) + self.assertEqual( + len(self.findall(r'Traceback')), 8, 'traceback count 8' + ) if __name__ == '__main__': TestPythonApplication.main() diff --git a/test/test_routing.py b/test/test_routing.py index 20e3a1c4..2960f978 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -8,34 +8,38 @@ class TestRouting(TestApplicationProto): def setUp(self): super().setUp() - self.conf( - { - "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [ - { - "match": {"method": "GET"}, - "action": {"pass": "applications/empty"}, - } - ], - "applications": { - "empty": { - "type": "python", - "processes": {"spare": 0}, - "path": self.current_dir + '/python/empty', - "working_directory": self.current_dir - + '/python/empty', - "module": "wsgi", - }, - "mirror": { - "type": "python", - "processes": {"spare": 0}, - "path": self.current_dir + '/python/mirror', - "working_directory": self.current_dir - + '/python/mirror', - "module": "wsgi", + self.assertIn( + 'success', + self.conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [ + { + "match": {"method": "GET"}, + "action": {"pass": "applications/empty"}, + } + ], + "applications": { + "empty": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + + '/python/empty', + "module": "wsgi", + }, + "mirror": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/mirror', + "working_directory": self.current_dir + + '/python/mirror', + "module": "wsgi", + }, }, - }, - } + } + ), + 'routing configure', ) def route(self, route): @@ -897,31 +901,75 @@ class TestRouting(TestApplicationProto): 'success', self.route( { - "match": {"uri": "/"}, + "match": {"uri": ["/blah", "/slash/"]}, "action": {"pass": "applications/empty"}, } ), 'match uri positive configure', ) - self.assertEqual(self.get()['status'], 200, 'match uri positive') + self.assertEqual(self.get()['status'], 404, 'match uri positive') + self.assertEqual( + self.get(url='/blah')['status'], 200, 'match uri positive blah' + ) + self.assertEqual( + self.get(url='/blah#foo')['status'], + 200, + 'match uri positive #foo', + ) + self.assertEqual( + self.get(url='/blah?var')['status'], 200, 'match uri args' + ) + self.assertEqual( + self.get(url='//blah')['status'], 200, 'match uri adjacent slashes' + ) self.assertEqual( - self.get(url='/blah')['status'], 404, 'match uri positive blah' + self.get(url='/slash/foo/../')['status'], + 200, + 'match uri relative path', + ) + self.assertEqual( + self.get(url='/slash/./')['status'], + 200, + 'match uri relative path 2', + ) + self.assertEqual( + self.get(url='/slash//.//')['status'], + 200, + 'match uri adjacent slashes 2', + ) + self.assertEqual( + self.get(url='/%')['status'], 400, 'match uri percent' ) self.assertEqual( - self.get(url='/#blah')['status'], 200, 'match uri positive #blah' + self.get(url='/%1')['status'], 400, 'match uri percent digit' ) self.assertEqual( - self.get(url='/?var')['status'], 200, 'match uri params' + self.get(url='/%A')['status'], 400, 'match uri percent letter' ) self.assertEqual( - self.get(url='//')['status'], 200, 'match uri adjacent slashes' + self.get(url='/slash/.?args')['status'], 200, 'match uri dot args' ) self.assertEqual( - self.get(url='/blah/../')['status'], 200, 'match uri relative path' + self.get(url='/slash/.#frag')['status'], 200, 'match uri dot frag' ) self.assertEqual( - self.get(url='/./')['status'], 200, 'match uri relative path' + self.get(url='/slash/foo/..?args')['status'], + 200, + 'match uri dot dot args', + ) + self.assertEqual( + self.get(url='/slash/foo/..#frag')['status'], + 200, + 'match uri dot dot frag', + ) + self.assertEqual( + self.get(url='/slash/.')['status'], 200, 'match uri trailing dot' + ) + self.assertEqual( + self.get(url='/slash/foo/..')['status'], + 200, + 'match uri trailing dot dot', ) def test_routes_match_uri_case_sensitive(self): diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index 6f82ae81..bbb252d7 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -347,6 +347,29 @@ class TestRubyApplication(TestApplicationRuby): self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') + def test_ruby_application_constants(self): + self.load('constants') + + resp = self.get() + + self.assertEqual(resp['status'], 200, 'status') + + headers = resp['headers'] + self.assertGreater(len(headers['X-Copyright']), 0, 'RUBY_COPYRIGHT') + self.assertGreater( + len(headers['X-Description']), 0, 'RUBY_DESCRIPTION' + ) + self.assertGreater(len(headers['X-Engine']), 0, 'RUBY_ENGINE') + self.assertGreater( + len(headers['X-Engine-Version']), 0, 'RUBY_ENGINE_VERSION' + ) + self.assertGreater(len(headers['X-Patchlevel']), 0, 'RUBY_PATCHLEVEL') + self.assertGreater(len(headers['X-Platform']), 0, 'RUBY_PLATFORM') + self.assertGreater( + len(headers['X-Release-Date']), 0, 'RUBY_RELEASE_DATE' + ) + self.assertGreater(len(headers['X-Revision']), 0, 'RUBY_REVISION') + self.assertGreater(len(headers['X-Version']), 0, 'RUBY_VERSION') if __name__ == '__main__': TestRubyApplication.main() diff --git a/test/test_static.py b/test/test_static.py index 4bdd83ed..f9dcb7dd 100644 --- a/test/test_static.py +++ b/test/test_static.py @@ -40,6 +40,12 @@ class TestStatic(TestApplicationProto): ) self.assertEqual(self.get(url='/')['body'], '0123456789', 'index 2') self.assertEqual( + self.get(url='/?blah')['body'], '0123456789', 'index vars' + ) + self.assertEqual( + self.get(url='/#blah')['body'], '0123456789', 'index anchor' + ) + self.assertEqual( self.get(url='/dir/')['status'], 404, 'index not found' ) diff --git a/test/test_usr1.py b/test/test_usr1.py new file mode 100644 index 00000000..dd9292c7 --- /dev/null +++ b/test/test_usr1.py @@ -0,0 +1,92 @@ +import os +import unittest +from subprocess import call +from unit.applications.lang.python import TestApplicationPython + + +class TestUSR1(TestApplicationPython): + prerequisites = {'modules': ['python']} + + def test_usr1_access_log(self): + self.load('empty') + + log_path = self.testdir + '/access.log' + + self.assertIn( + 'success', + self.conf('"' + log_path + '"', 'access_log'), + 'access log configure', + ) + + self.assertTrue(self.waitforfiles(log_path), 'open') + + log_path_new = self.testdir + '/new.log' + + os.rename(log_path, log_path_new) + + self.get() + + self.assertIsNotNone( + self.wait_for_record(r'"GET / HTTP/1.1" 200 0 "-" "-"', 'new.log'), + 'rename new', + ) + self.assertFalse(os.path.isfile(log_path), 'rename old') + + with open(self.testdir + '/unit.pid', 'r') as f: + pid = f.read().rstrip() + + call(['kill', '-s', 'USR1', pid]) + + self.assertTrue(self.waitforfiles(log_path), 'reopen') + + self.get(url='/usr1') + + self.assertIsNotNone( + self.wait_for_record( + r'"GET /usr1 HTTP/1.1" 200 0 "-" "-"', 'access.log' + ), + 'reopen 2', + ) + self.assertIsNone( + self.search_in_log(r'/usr1', 'new.log'), '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' + + os.rename(log_path, log_path_new) + + body = 'body_for_a_log_new' + self.post(body=body) + + self.assertIsNotNone( + self.wait_for_record(body, 'new.log'), 'rename new' + ) + self.assertFalse(os.path.isfile(log_path), 'rename old') + + with open(self.testdir + '/unit.pid', 'r') as f: + pid = f.read().rstrip() + + call(['kill', '-s', 'USR1', pid]) + + self.assertTrue(self.waitforfiles(log_path), 'reopen') + + body = 'body_for_a_log_unit' + self.post(body=body) + + self.assertIsNotNone(self.wait_for_record(body), 'rename new') + self.assertIsNone(self.search_in_log(body, 'new.log'), '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()) + + +if __name__ == '__main__': + TestUSR1.main() diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py index 50ff2797..ef16f433 100644 --- a/test/unit/applications/websockets.py +++ b/test/unit/applications/websockets.py @@ -1,3 +1,4 @@ +import re import random import base64 import struct @@ -30,25 +31,37 @@ class TestApplicationWebsocket(TestApplicationProto): sha1 = hashlib.sha1((key + GUID).encode()).digest() return base64.b64encode(sha1).decode() - def upgrade(self): - key = self.key() + def upgrade(self, headers=None): + key = None - if self.preinit: - self.get() - - resp, sock = self.get( - headers={ + if headers is None: + key = self.key() + headers = { 'Host': 'localhost', 'Upgrade': 'websocket', 'Connection': 'Upgrade', 'Sec-WebSocket-Key': key, 'Sec-WebSocket-Protocol': 'chat', 'Sec-WebSocket-Version': 13, - }, - read_timeout=1, + } + + _, sock = self.get( + headers=headers, + no_recv=True, start=True, ) + resp = '' + while select.select([sock], [], [], 30)[0]: + resp += sock.recv(4096).decode() + + if ( + re.search('101 Switching Protocols', resp) + and resp[-4:] == '\r\n\r\n' + ): + resp = self._resp_to_dict(resp) + break + return (resp, sock, key) def apply_mask(self, data, mask): diff --git a/test/unit/http.py b/test/unit/http.py index 82a6bd6a..c7e3e36d 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -1,4 +1,5 @@ import re +import time import socket import select from unit.main import TestUnit @@ -63,7 +64,7 @@ class TestHTTP(TestUnit): if 'raw' not in kwargs: req = ' '.join([start_str, url, http]) + crlf - if body is not b'': + if body != b'': if isinstance(body, str): body = body.encode() @@ -178,3 +179,20 @@ class TestHTTP(TestUnit): headers[m.group(1)] = [headers[m.group(1)], m.group(2)] return {'status': int(status), 'headers': headers, 'body': body} + + def waitforsocket(self, port): + ret = False + + for i in range(50): + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('127.0.0.1', port)) + ret = True + break + except: + sock.close() + time.sleep(0.1) + + sock.close() + + self.assertTrue(ret, 'socket connected') diff --git a/test/unit/main.py b/test/unit/main.py index 873f1815..094fdb0e 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -185,6 +185,8 @@ class TestUnit(unittest.TestCase): if self._started: self._stop() + self.stop_processes() + def _run(self): self.unitd = self.pardir + '/build/unitd' @@ -240,24 +242,24 @@ class TestUnit(unittest.TestCase): break time.sleep(0.1) - if os.path.exists(self.testdir + '/unit.pid'): - exit("Could not terminate unit") + self._p.join(timeout=5) - self._started = False + if self._p.is_alive(): + self._p.terminate() + self._p.join(timeout=5) - self._p.join(timeout=1) - self._terminate_process(self._p) + if self._p.is_alive(): + self.fail("Could not terminate process " + str(self._p.pid)) - def _terminate_process(self, process): - if process.is_alive(): - process.terminate() - process.join(timeout=5) + if os.path.exists(self.testdir + '/unit.pid'): + self.fail("Could not terminate unit") - if process.is_alive(): - exit("Could not terminate process " + process.pid) + self._started = False - if process.exitcode: - exit("Child process terminated with code " + str(process.exitcode)) + if self._p.exitcode: + self.fail( + "Child process terminated with code " + str(self._p.exitcode) + ) def _check_alerts(self, log): found = False @@ -287,6 +289,26 @@ class TestUnit(unittest.TestCase): if found: print('skipped.') + def run_process(self, target): + if not hasattr(self, '_processes'): + self._processes = [] + + process = Process(target=target) + process.start() + + self._processes.append(process) + + def stop_processes(self): + if not hasattr(self, '_processes'): + return + + for process in self._processes: + process.terminate() + process.join(timeout=5) + + if process.is_alive(): + self.fail('Fail to stop process') + def waitforfiles(self, *files): for i in range(50): wait = False |