diff options
Diffstat (limited to 'test')
28 files changed, 887 insertions, 766 deletions
diff --git a/test/python/delayed/wsgi.py b/test/python/delayed/wsgi.py index d25e2765..3eb5a6f8 100644 --- a/test/python/delayed/wsgi.py +++ b/test/python/delayed/wsgi.py @@ -11,6 +11,7 @@ def application(environ, start_response): write = start_response('200', [('Content-Length', str(len(body)))]) if not body: + time.sleep(delay) return [] step = int(len(body) / parts) diff --git a/test/python/log_body/wsgi.py b/test/python/log_body/wsgi.py index 9dcb1b0c..0ec07a68 100644 --- a/test/python/log_body/wsgi.py +++ b/test/python/log_body/wsgi.py @@ -2,7 +2,7 @@ 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'].write(body.decode()) environ['wsgi.errors'].flush() start_response('200', [('Content-Length', '0')]) diff --git a/test/python/upstreams/0/wsgi.py b/test/python/upstreams/0/wsgi.py deleted file mode 100644 index 2c88979b..00000000 --- a/test/python/upstreams/0/wsgi.py +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 5077bdb1..00000000 --- a/test/python/upstreams/1/wsgi.py +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index bb0ce797..00000000 --- a/test/python/upstreams/2/wsgi.py +++ /dev/null @@ -1,8 +0,0 @@ -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/test_configuration.py b/test/test_configuration.py index 186e037d..daba874b 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -83,6 +83,86 @@ class TestConfiguration(TestControl): 'unicode number', ) + def test_json_utf8_bom(self): + self.assertIn( + 'success', + self.conf( + b"""\xEF\xBB\xBF + { + "app": { + "type": "python", + "processes": {"spare": 0}, + "path": "/app", + "module": "wsgi" + } + } + """, + 'applications', + ), + 'UTF-8 BOM', + ) + + def test_json_comment_single_line(self): + self.assertIn( + 'success', + self.conf( + b""" + // this is bridge + { + "//app": { + "type": "python", // end line + "processes": {"spare": 0}, + // inside of block + "path": "/app", + "module": "wsgi" + } + // double // + } + // end of json \xEF\t + """, + 'applications', + ), + 'single line comments', + ) + + def test_json_comment_multi_line(self): + self.assertIn( + 'success', + self.conf( + b""" + /* this is bridge */ + { + "/*app": { + /** + * multiple lines + **/ + "type": "python", + "processes": /* inline */ {"spare": 0}, + "path": "/app", + "module": "wsgi" + /* + // end of block */ + } + /* blah * / blah /* blah */ + } + /* end of json \xEF\t\b */ + """, + 'applications', + ), + 'multi line comments', + ) + + def test_json_comment_invalid(self): + self.assertIn('error', self.conf(b'/{}', 'applications'), 'slash') + self.assertIn('error', self.conf(b'//{}', 'applications'), 'comment') + self.assertIn('error', self.conf(b'{} /', 'applications'), 'slash end') + self.assertIn( + 'error', self.conf(b'/*{}', 'applications'), 'slash star' + ) + self.assertIn( + 'error', self.conf(b'{} /*', 'applications'), 'slash star end' + ) + def test_applications_open_brace(self): self.assertIn('error', self.conf('{', 'applications'), 'open brace') diff --git a/test/test_go_application.py b/test/test_go_application.py index 42429be7..c9d4ba77 100644 --- a/test/test_go_application.py +++ b/test/test_go_application.py @@ -89,6 +89,7 @@ class TestGoApplication(TestApplicationGo): self.assertEqual(self.get()['status'], 200, 'init') + body = '0123456789' * 500 (resp, sock) = self.post( headers={ 'Host': 'localhost', @@ -96,12 +97,13 @@ class TestGoApplication(TestApplicationGo): 'Content-Type': 'text/html', }, start=True, - body='0123456789' * 500, + body=body, read_timeout=1, ) - self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') + self.assertEqual(resp['body'], body, 'keep-alive 1') + body = '0123456789' resp = self.post( headers={ 'Host': 'localhost', @@ -109,10 +111,10 @@ class TestGoApplication(TestApplicationGo): 'Connection': 'close', }, sock=sock, - body='0123456789', + body=body, ) - self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') + self.assertEqual(resp['body'], body, 'keep-alive 2') def test_go_application_cookies(self): self.load('cookies') diff --git a/test/test_java_application.py b/test/test_java_application.py index 9d873d6b..7bd351a4 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -1085,6 +1085,7 @@ class TestJavaApplication(TestApplicationJava): self.assertEqual(self.post()['status'], 200, 'init') + body = '0123456789' * 500 (resp, sock) = self.post( headers={ 'Connection': 'keep-alive', @@ -1092,12 +1093,13 @@ class TestJavaApplication(TestApplicationJava): 'Host': 'localhost', }, start=True, - body='0123456789' * 500, + body=body, read_timeout=1, ) - self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') + self.assertEqual(resp['body'], body, 'keep-alive 1') + body = '0123456789' resp = self.post( headers={ 'Connection': 'close', @@ -1105,10 +1107,10 @@ class TestJavaApplication(TestApplicationJava): 'Host': 'localhost', }, sock=sock, - body='0123456789', + body=body, ) - self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') + self.assertEqual(resp['body'], body, 'keep-alive 2') def test_java_application_http_10(self): self.load('empty') diff --git a/test/test_java_websockets.py b/test/test_java_websockets.py index d75ee3a6..7ea04620 100644 --- a/test/test_java_websockets.py +++ b/test/test_java_websockets.py @@ -22,11 +22,11 @@ class TestJavaWebsockets(TestApplicationJava): ) self.skip_alerts.extend( - [r'last message send failed', r'socket close\(\d+\) failed'] + [r'socket close\(\d+\) failed'] ) def close_connection(self, sock): - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty soc') self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) @@ -441,12 +441,12 @@ class TestJavaWebsockets(TestApplicationJava): _, sock, _ = self.ws.upgrade() self.ws.frame_write(sock, self.ws.OP_PONG, '') - self.assertEqual(self.recvall(sock, read_timeout=1), b'', '2_7') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', '2_7') # 2_8 self.ws.frame_write(sock, self.ws.OP_PONG, 'unsolicited pong payload') - self.assertEqual(self.recvall(sock, read_timeout=1), b'', '2_8') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', '2_8') # 2_9 @@ -512,7 +512,7 @@ class TestJavaWebsockets(TestApplicationJava): self.check_close(sock, 1002, no_close=True) - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_2') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty 3_2') sock.close() # 3_3 @@ -530,7 +530,7 @@ class TestJavaWebsockets(TestApplicationJava): self.check_close(sock, 1002, no_close=True) - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_3') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty 3_3') sock.close() # 3_4 @@ -548,7 +548,7 @@ class TestJavaWebsockets(TestApplicationJava): self.check_close(sock, 1002, no_close=True) - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_4') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty 3_4') sock.close() # 3_5 @@ -734,7 +734,7 @@ class TestJavaWebsockets(TestApplicationJava): # 5_4 self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) - self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_4') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', '5_4') self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) frame = self.ws.frame_read(sock) @@ -771,7 +771,7 @@ class TestJavaWebsockets(TestApplicationJava): ping_payload = 'ping payload' self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) - self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_7') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', '5_7') self.ws.frame_write(sock, self.ws.OP_PING, ping_payload) @@ -955,7 +955,7 @@ class TestJavaWebsockets(TestApplicationJava): frame = self.ws.frame_read(sock) self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 2!') - self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_20') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', '5_20') self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment5') self.check_frame( @@ -1088,7 +1088,7 @@ class TestJavaWebsockets(TestApplicationJava): self.check_close(sock, no_close=True) self.ws.frame_write(sock, self.ws.OP_PING, '') - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty soc') sock.close() @@ -1100,7 +1100,7 @@ class TestJavaWebsockets(TestApplicationJava): self.check_close(sock, no_close=True) self.ws.frame_write(sock, self.ws.OP_TEXT, payload) - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty soc') sock.close() @@ -1113,7 +1113,7 @@ class TestJavaWebsockets(TestApplicationJava): self.check_close(sock, no_close=True) self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2') - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty soc') sock.close() @@ -1128,7 +1128,7 @@ class TestJavaWebsockets(TestApplicationJava): self.recvall(sock, read_timeout=1) self.ws.frame_write(sock, self.ws.OP_PING, '') - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty soc') sock.close() diff --git a/test/test_node_application.py b/test/test_node_application.py index b80d17d3..174af15d 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -112,6 +112,7 @@ class TestNodeApplication(TestApplicationNode): self.assertEqual(self.get()['status'], 200, 'init') + body = '0123456789' * 500 (resp, sock) = self.post( headers={ 'Host': 'localhost', @@ -119,12 +120,13 @@ class TestNodeApplication(TestApplicationNode): 'Content-Type': 'text/html', }, start=True, - body='0123456789' * 500, + body=body, read_timeout=1, ) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') + body = '0123456789' resp = self.post( headers={ 'Host': 'localhost', @@ -132,10 +134,10 @@ class TestNodeApplication(TestApplicationNode): 'Content-Type': 'text/html', }, sock=sock, - body='0123456789', + body=body, ) - self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') + self.assertEqual(resp['body'], body, 'keep-alive 2') def test_node_application_write_buffer(self): self.load('write_buffer') diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py index bb189552..4ce727db 100644 --- a/test/test_node_websockets.py +++ b/test/test_node_websockets.py @@ -22,11 +22,11 @@ class TestNodeWebsockets(TestApplicationNode): ) self.skip_alerts.extend( - [r'last message send failed', r'socket close\(\d+\) failed'] + [r'socket close\(\d+\) failed'] ) def close_connection(self, sock): - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty soc') self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) @@ -460,12 +460,12 @@ class TestNodeWebsockets(TestApplicationNode): _, sock, _ = self.ws.upgrade() self.ws.frame_write(sock, self.ws.OP_PONG, '') - self.assertEqual(self.recvall(sock, read_timeout=1), b'', '2_7') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', '2_7') # 2_8 self.ws.frame_write(sock, self.ws.OP_PONG, 'unsolicited pong payload') - self.assertEqual(self.recvall(sock, read_timeout=1), b'', '2_8') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', '2_8') # 2_9 @@ -531,7 +531,7 @@ class TestNodeWebsockets(TestApplicationNode): self.check_close(sock, 1002, no_close=True) - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_2') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty 3_2') sock.close() # 3_3 @@ -549,7 +549,7 @@ class TestNodeWebsockets(TestApplicationNode): self.check_close(sock, 1002, no_close=True) - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_3') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty 3_3') sock.close() # 3_4 @@ -567,7 +567,7 @@ class TestNodeWebsockets(TestApplicationNode): self.check_close(sock, 1002, no_close=True) - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_4') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty 3_4') sock.close() # 3_5 @@ -753,7 +753,7 @@ class TestNodeWebsockets(TestApplicationNode): # 5_4 self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) - self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_4') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', '5_4') self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) frame = self.ws.frame_read(sock) @@ -790,7 +790,7 @@ class TestNodeWebsockets(TestApplicationNode): ping_payload = 'ping payload' self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) - self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_7') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', '5_7') self.ws.frame_write(sock, self.ws.OP_PING, ping_payload) @@ -974,7 +974,7 @@ class TestNodeWebsockets(TestApplicationNode): frame = self.ws.frame_read(sock) self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 2!') - self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_20') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', '5_20') self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment5') self.check_frame( @@ -1107,7 +1107,7 @@ class TestNodeWebsockets(TestApplicationNode): self.check_close(sock, no_close=True) self.ws.frame_write(sock, self.ws.OP_PING, '') - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty soc') sock.close() @@ -1119,7 +1119,7 @@ class TestNodeWebsockets(TestApplicationNode): self.check_close(sock, no_close=True) self.ws.frame_write(sock, self.ws.OP_TEXT, payload) - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty soc') sock.close() @@ -1132,7 +1132,7 @@ class TestNodeWebsockets(TestApplicationNode): self.check_close(sock, no_close=True) self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2') - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty soc') sock.close() @@ -1147,7 +1147,7 @@ class TestNodeWebsockets(TestApplicationNode): self.recvall(sock, read_timeout=1) self.ws.frame_write(sock, self.ws.OP_PING, '') - self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + self.assertEqual(self.recvall(sock, read_timeout=0.1), b'', 'empty soc') sock.close() diff --git a/test/test_perl_application.py b/test/test_perl_application.py index a4bac623..cc4eb915 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -197,6 +197,7 @@ class TestPerlApplication(TestApplicationPerl): self.assertEqual(self.get()['status'], 200, 'init') + body = '0123456789' * 500 (resp, sock) = self.post( headers={ 'Host': 'localhost', @@ -204,12 +205,13 @@ class TestPerlApplication(TestApplicationPerl): 'Content-Type': 'text/html', }, start=True, - body='0123456789' * 500, + body=body, read_timeout=1, ) - self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') + self.assertEqual(resp['body'], body, 'keep-alive 1') + body = '0123456789' resp = self.post( headers={ 'Host': 'localhost', @@ -217,10 +219,10 @@ class TestPerlApplication(TestApplicationPerl): 'Content-Type': 'text/html', }, sock=sock, - body='0123456789', + body=body, ) - self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') + self.assertEqual(resp['body'], body, 'keep-alive 2') def test_perl_body_io_fake(self): self.load('body_io_fake') diff --git a/test/test_php_application.py b/test/test_php_application.py index c3645a99..48e1e815 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -183,6 +183,7 @@ class TestPHPApplication(TestApplicationPHP): self.assertEqual(self.get()['status'], 200, 'init') + body = '0123456789' * 500 (resp, sock) = self.post( headers={ 'Host': 'localhost', @@ -190,12 +191,13 @@ class TestPHPApplication(TestApplicationPHP): 'Content-Type': 'text/html', }, start=True, - body='0123456789' * 500, + body=body, read_timeout=1, ) - self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') + self.assertEqual(resp['body'], body, 'keep-alive 1') + body = '0123456789' resp = self.post( headers={ 'Host': 'localhost', @@ -203,10 +205,10 @@ class TestPHPApplication(TestApplicationPHP): 'Content-Type': 'text/html', }, sock=sock, - body='0123456789', + body=body, ) - self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') + self.assertEqual(resp['body'], body, 'keep-alive 2') def test_php_application_conditional(self): self.load('conditional') diff --git a/test/test_php_basic.py b/test/test_php_basic.py index 7ecff1b2..5fde3e00 100644 --- a/test/test_php_basic.py +++ b/test/test_php_basic.py @@ -37,9 +37,6 @@ class TestPHPBasic(TestControl): 'applications', ) - def test_php_get_applications_prefix(self): - self.conf(self.conf_app, 'applications') - self.assertEqual( self.conf_get('applications'), { @@ -53,9 +50,6 @@ class TestPHPBasic(TestControl): 'applications prefix', ) - def test_php_get_applications_prefix_2(self): - self.conf(self.conf_app, 'applications') - self.assertEqual( self.conf_get('applications/app'), { @@ -67,9 +61,6 @@ class TestPHPBasic(TestControl): 'applications prefix 2', ) - def test_php_get_applications_prefix_3(self): - self.conf(self.conf_app, 'applications') - self.assertEqual(self.conf_get('applications/app/type'), 'php', 'type') self.assertEqual( self.conf_get('applications/app/processes/spare'), @@ -86,18 +77,12 @@ class TestPHPBasic(TestControl): 'listeners', ) - def test_php_get_listeners_prefix(self): - self.conf(self.conf_basic) - self.assertEqual( self.conf_get('listeners'), {"*:7080": {"pass": "applications/app"}}, 'listeners prefix', ) - def test_php_get_listeners_prefix_2(self): - self.conf(self.conf_basic) - self.assertEqual( self.conf_get('listeners/*:7080'), {"pass": "applications/app"}, @@ -147,49 +132,24 @@ class TestPHPBasic(TestControl): def test_php_delete(self): self.conf(self.conf_basic) - self.assertIn( - 'error', - self.conf_delete('applications/app'), - 'delete app before listener', - ) - self.assertIn( - 'success', self.conf_delete('listeners/*:7080'), 'delete listener' - ) - self.assertIn( - 'success', - self.conf_delete('applications/app'), - 'delete app after listener', - ) - self.assertIn( - 'error', self.conf_delete('applications/app'), 'delete app again' - ) + self.assertIn('error', self.conf_delete('applications/app')) + self.assertIn('success', self.conf_delete('listeners/*:7080')) + self.assertIn('success', self.conf_delete('applications/app')) + self.assertIn('error', self.conf_delete('applications/app')) def test_php_delete_blocks(self): self.conf(self.conf_basic) - self.assertIn( - 'success', - self.conf_delete('listeners'), - 'listeners delete', - ) - - self.assertIn( - 'success', - self.conf_delete('applications'), - 'applications delete', - ) - - self.assertIn( - 'success', - self.conf(self.conf_app, 'applications'), - 'listeners restore', - ) + self.assertIn('success', self.conf_delete('listeners')) + self.assertIn('success', self.conf_delete('applications')) + self.assertIn('success', self.conf(self.conf_app, 'applications')) self.assertIn( 'success', self.conf({"*:7081": {"pass": "applications/app"}}, 'listeners'), 'applications restore', ) + if __name__ == '__main__': TestPHPBasic.main() diff --git a/test/test_python_application.py b/test/test_python_application.py index 460cc804..8d435b48 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -187,6 +187,7 @@ class TestPythonApplication(TestApplicationPython): self.assertEqual(self.get()['status'], 200, 'init') + body = '0123456789' * 500 (resp, sock) = self.post( headers={ 'Host': 'localhost', @@ -194,12 +195,13 @@ class TestPythonApplication(TestApplicationPython): 'Content-Type': 'text/html', }, start=True, - body='0123456789' * 500, + body=body, read_timeout=1, ) - self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') + self.assertEqual(resp['body'], body, 'keep-alive 1') + body = '0123456789' resp = self.post( headers={ 'Host': 'localhost', @@ -207,10 +209,10 @@ class TestPythonApplication(TestApplicationPython): 'Content-Type': 'text/html', }, sock=sock, - body='0123456789', + body=body, ) - self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') + self.assertEqual(resp['body'], body, 'keep-alive 2') def test_python_keepalive_reconfigure(self): self.skip_alerts.extend( @@ -340,14 +342,16 @@ class TestPythonApplication(TestApplicationPython): self.assertEqual(self.get()['status'], 200, 'init') - (resp, sock) = self.http( + (_, sock) = self.http( b"""GET / HTTP/1.1 """, start=True, raw=True, - read_timeout=5, + no_recv=True, ) + self.assertEqual(self.get()['status'], 200) + self.assertIn( 'success', self.conf({"listeners": {}, "applications": {}}), @@ -378,6 +382,38 @@ Connection: close self.wait_for_record(r'At exit called\.'), 'atexit' ) + def test_python_process_switch(self): + self.load('delayed') + + self.assertIn( + 'success', + self.conf('2', 'applications/delayed/processes'), + 'configure 2 processes', + ) + + self.get(headers={ + 'Host': 'localhost', + 'Content-Length': '0', + 'X-Delay': '5', + 'Connection': 'close', + }, no_recv=True) + + headers_delay_1 = { + 'Connection': 'close', + 'Host': 'localhost', + 'Content-Length': '0', + 'X-Delay': '1', + } + + self.get(headers=headers_delay_1, no_recv=True) + + time.sleep(0.5) + + for _ in range(10): + self.get(headers=headers_delay_1, no_recv=True) + + self.get(headers=headers_delay_1) + @unittest.skip('not yet') def test_python_application_start_response_exit(self): self.load('start_response_exit') diff --git a/test/test_python_basic.py b/test/test_python_basic.py index 67a5f548..3233fca2 100644 --- a/test/test_python_basic.py +++ b/test/test_python_basic.py @@ -19,17 +19,9 @@ class TestPythonBasic(TestControl): } def test_python_get_empty(self): - self.assertEqual( - self.conf_get(), {'listeners': {}, 'applications': {}}, 'empty' - ) - - def test_python_get_prefix_listeners(self): - self.assertEqual(self.conf_get('listeners'), {}, 'listeners prefix') - - def test_python_get_prefix_applications(self): - self.assertEqual( - self.conf_get('applications'), {}, 'applications prefix' - ) + self.assertEqual(self.conf_get(), {'listeners': {}, 'applications': {}}) + self.assertEqual(self.conf_get('listeners'), {}) + self.assertEqual(self.conf_get('applications'), {}) def test_python_get_applications(self): self.conf(self.conf_app, 'applications') @@ -50,9 +42,6 @@ class TestPythonBasic(TestControl): 'applications', ) - def test_python_get_applications_prefix(self): - self.conf(self.conf_app, 'applications') - self.assertEqual( self.conf_get('applications'), { @@ -66,9 +55,6 @@ class TestPythonBasic(TestControl): 'applications prefix', ) - def test_python_get_applications_prefix_2(self): - self.conf(self.conf_app, 'applications') - self.assertEqual( self.conf_get('applications/app'), { @@ -80,9 +66,6 @@ class TestPythonBasic(TestControl): 'applications prefix 2', ) - def test_python_get_applications_prefix_3(self): - self.conf(self.conf_app, 'applications') - self.assertEqual( self.conf_get('applications/app/type'), 'python', 'type' ) @@ -99,18 +82,12 @@ class TestPythonBasic(TestControl): 'listeners', ) - def test_python_get_listeners_prefix(self): - self.conf(self.conf_basic) - self.assertEqual( self.conf_get('listeners'), {"*:7080": {"pass": "applications/app"}}, 'listeners prefix', ) - def test_python_get_listeners_prefix_2(self): - self.conf(self.conf_basic) - self.assertEqual( self.conf_get('listeners/*:7080'), {"pass": "applications/app"}, @@ -160,44 +137,18 @@ class TestPythonBasic(TestControl): def test_python_delete(self): self.conf(self.conf_basic) - self.assertIn( - 'error', - self.conf_delete('applications/app'), - 'delete app before listener', - ) - self.assertIn( - 'success', self.conf_delete('listeners/*:7080'), 'delete listener' - ) - self.assertIn( - 'success', - self.conf_delete('applications/app'), - 'delete app after listener', - ) - self.assertIn( - 'error', self.conf_delete('applications/app'), 'delete app again' - ) + self.assertIn('error', self.conf_delete('applications/app')) + self.assertIn('success', self.conf_delete('listeners/*:7080')) + self.assertIn('success', self.conf_delete('applications/app')) + self.assertIn('error', self.conf_delete('applications/app')) def test_python_delete_blocks(self): self.conf(self.conf_basic) - self.assertIn( - 'success', - self.conf_delete('listeners'), - 'listeners delete', - ) - - self.assertIn( - 'success', - self.conf_delete('applications'), - 'applications delete', - ) - - self.assertIn( - 'success', - self.conf(self.conf_app, 'applications'), - 'listeners restore', - ) + self.assertIn('success', self.conf_delete('listeners')) + self.assertIn('success', self.conf_delete('applications')) + self.assertIn('success', self.conf(self.conf_app, 'applications')) self.assertIn( 'success', self.conf({"*:7081": {"pass": "applications/app"}}, 'listeners'), diff --git a/test/test_python_procman.py b/test/test_python_procman.py index 52d8cacb..a2e6126c 100644 --- a/test/test_python_procman.py +++ b/test/test_python_procman.py @@ -8,6 +8,13 @@ from unit.applications.lang.python import TestApplicationPython class TestPythonProcman(TestApplicationPython): prerequisites = {'modules': ['python']} + def setUp(self): + super().setUp() + + self.app_name = "app-" + self.testdir.split('/')[-1] + self.app_proc = 'applications/' + self.app_name + '/processes' + self.load('empty', self.app_name) + def pids_for_process(self): time.sleep(0.2) @@ -19,103 +26,20 @@ class TestPythonProcman(TestApplicationPython): return pids - def setUp(self): - super().setUp() - - self.app_name = "app-" + self.testdir.split('/')[-1] - self.load('empty', self.app_name) - - def test_python_processes_access(self): - self.conf('1', 'applications/' + self.app_name + '/processes') - - self.assertIn( - 'error', - self.conf_get('/applications/' + self.app_name + '/processes/max'), - 'max no access', - ) - self.assertIn( - 'error', - self.conf_get( - '/applications/' + self.app_name + '/processes/spare' - ), - 'spare no access', - ) - self.assertIn( - 'error', - self.conf_get( - '/applications/' + self.app_name + '/processes/idle_timeout' - ), - 'idle_timeout no access', - ) - - def test_python_processes_spare_negative(self): - self.assertIn( - 'error', - self.conf( - {"spare": -1}, 'applications/' + self.app_name + '/processes' - ), - 'negative spare', - ) - - def test_python_processes_max_negative(self): - self.assertIn( - 'error', - self.conf( - {"max": -1}, 'applications/' + self.app_name + '/processes' - ), - 'negative max', - ) - - def test_python_processes_idle_timeout_negative(self): - self.assertIn( - 'error', - self.conf( - {"idle_timeout": -1}, - 'applications/' + self.app_name + '/processes', - ), - 'negative idle_timeout', - ) - - def test_python_processes_spare_gt_max_default(self): - self.assertIn( - 'error', - self.conf( - {"spare": 2}, 'applications/' + self.app_name + '/processes' - ), - 'spare greater than max default', - ) - - def test_python_processes_spare_gt_max(self): - self.assertIn( - 'error', - self.conf( - {"spare": 2, "max": 1, "idle_timeout": 1}, - '/applications/' + self.app_name + '/processes', - ), - 'spare greater than max', - ) + def conf_proc(self, conf, path=None): + if path is None: + path = self.app_proc - def test_python_processes_max_zero(self): - self.assertIn( - 'error', - self.conf( - {"spare": 0, "max": 0, "idle_timeout": 1}, - 'applications/' + self.app_name + '/processes', - ), - 'max 0', - ) + self.assertIn('success', self.conf(conf, path), 'configure processes') def test_python_processes_idle_timeout_zero(self): - self.conf( - {"spare": 0, "max": 2, "idle_timeout": 0}, - 'applications/' + self.app_name + '/processes', - ) + self.conf_proc({"spare": 0, "max": 2, "idle_timeout": 0}) self.get() self.assertEqual(len(self.pids_for_process()), 0, 'idle timeout 0') def test_python_prefork(self): - self.conf('2', 'applications/' + self.app_name + '/processes') + self.conf_proc('2') pids = self.pids_for_process() self.assertEqual(len(pids), 2, 'prefork 2') @@ -123,7 +47,7 @@ class TestPythonProcman(TestApplicationPython): self.get() self.assertSetEqual(self.pids_for_process(), pids, 'prefork still 2') - self.conf('4', 'applications/' + self.app_name + '/processes') + self.conf_proc('4') pids = self.pids_for_process() self.assertEqual(len(pids), 4, 'prefork 4') @@ -135,21 +59,16 @@ class TestPythonProcman(TestApplicationPython): @unittest.skip('not yet') def test_python_prefork_same_processes(self): - self.conf('2', 'applications/' + self.app_name + '/processes') - + self.conf_proc('2') pids = self.pids_for_process() - self.conf('4', 'applications/' + self.app_name + '/processes') - + self.conf_proc('4') pids_new = self.pids_for_process() self.assertTrue(pids.issubset(pids_new), 'prefork same processes') def test_python_ondemand(self): - self.conf( - {"spare": 0, "max": 8, "idle_timeout": 1}, - 'applications/' + self.app_name + '/processes', - ) + self.conf_proc({"spare": 0, "max": 8, "idle_timeout": 1}) self.assertEqual(len(self.pids_for_process()), 0, 'on-demand 0') @@ -169,10 +88,7 @@ class TestPythonProcman(TestApplicationPython): self.stop_all() def test_python_scale_updown(self): - self.conf( - {"spare": 2, "max": 8, "idle_timeout": 1}, - 'applications/' + self.app_name + '/processes', - ) + self.conf_proc({"spare": 2, "max": 8, "idle_timeout": 1}) pids = self.pids_for_process() self.assertEqual(len(pids), 2, 'updown 2') @@ -200,10 +116,7 @@ class TestPythonProcman(TestApplicationPython): self.stop_all() def test_python_reconfigure(self): - self.conf( - {"spare": 2, "max": 6, "idle_timeout": 1}, - 'applications/' + self.app_name + '/processes', - ) + self.conf_proc({"spare": 2, "max": 6, "idle_timeout": 1}) pids = self.pids_for_process() self.assertEqual(len(pids), 2, 'reconf 2') @@ -213,7 +126,7 @@ class TestPythonProcman(TestApplicationPython): self.assertEqual(len(pids_new), 3, 'reconf 3') self.assertTrue(pids.issubset(pids_new), 'reconf 3 only 1 new') - self.conf('6', 'applications/' + self.app_name + '/processes/spare') + self.conf_proc('6', self.app_proc + '/spare') pids = self.pids_for_process() self.assertEqual(len(pids), 6, 'reconf 6') @@ -224,10 +137,7 @@ class TestPythonProcman(TestApplicationPython): self.stop_all() def test_python_idle_timeout(self): - self.conf( - {"spare": 0, "max": 6, "idle_timeout": 2}, - 'applications/' + self.app_name + '/processes', - ) + self.conf_proc({"spare": 0, "max": 6, "idle_timeout": 2}) self.get() pids = self.pids_for_process() @@ -250,10 +160,7 @@ class TestPythonProcman(TestApplicationPython): self.assertEqual(len(self.pids_for_process()), 0, 'idle timed out') def test_python_processes_connection_keepalive(self): - self.conf( - {"spare": 0, "max": 6, "idle_timeout": 2}, - 'applications/' + self.app_name + '/processes', - ) + self.conf_proc({"spare": 0, "max": 6, "idle_timeout": 2}) (resp, sock) = self.get( headers={'Host': 'localhost', 'Connection': 'keep-alive'}, @@ -272,6 +179,42 @@ class TestPythonProcman(TestApplicationPython): sock.close() + def test_python_processes_access(self): + self.conf_proc('1') + + path = '/' + self.app_proc + self.assertIn('error', self.conf_get(path + '/max')) + self.assertIn('error', self.conf_get(path + '/spare')) + self.assertIn('error', self.conf_get(path + '/idle_timeout')) + + def test_python_processes_invalid(self): + self.assertIn( + 'error', self.conf({"spare": -1}, self.app_proc), 'negative spare', + ) + self.assertIn( + 'error', self.conf({"max": -1}, self.app_proc), 'negative max', + ) + self.assertIn( + 'error', + self.conf({"idle_timeout": -1}, self.app_proc), + 'negative idle_timeout', + ) + self.assertIn( + 'error', + self.conf({"spare": 2}, self.app_proc), + 'spare gt max default', + ) + self.assertIn( + 'error', + self.conf({"spare": 2, "max": 1}, self.app_proc), + 'spare gt max', + ) + self.assertIn( + 'error', + self.conf({"spare": 0, "max": 0}, self.app_proc), + 'max zero', + ) + def stop_all(self): self.conf({"listeners": {}, "applications": {}}) diff --git a/test/test_return.py b/test/test_return.py new file mode 100644 index 00000000..fcb51745 --- /dev/null +++ b/test/test_return.py @@ -0,0 +1,198 @@ +import re +import unittest +from unit.applications.proto import TestApplicationProto + + +class TestReturn(TestApplicationProto): + prerequisites = {} + + def setUp(self): + super().setUp() + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [{"action": {"return": 200}}], + "applications": {}, + } + ) + + def get_resps_sc(self, req=10): + to_send = b"""GET / HTTP/1.1 +Host: localhost + +""" * ( + req - 1 + ) + + to_send += b"""GET / HTTP/1.1 +Host: localhost +Connection: close + +""" + + return self.http(to_send, raw_resp=True, raw=True) + + def test_return(self): + resp = self.get() + self.assertEqual(resp['status'], 200) + self.assertIn('Server', resp['headers']) + self.assertIn('Date', resp['headers']) + self.assertEqual(resp['headers']['Content-Length'], '0') + self.assertEqual(resp['headers']['Connection'], 'close') + self.assertEqual(resp['body'], '', 'body') + + resp = self.post(body='blah') + self.assertEqual(resp['status'], 200) + self.assertEqual(resp['body'], '', 'body') + + resp = self.get_resps_sc() + self.assertEqual(len(re.findall('200 OK', resp)), 10) + self.assertEqual(len(re.findall('Connection:', resp)), 1) + self.assertEqual(len(re.findall('Connection: close', resp)), 1) + + resp = self.get(http_10=True) + self.assertEqual(resp['status'], 200) + self.assertIn('Server', resp['headers']) + self.assertIn('Date', resp['headers']) + self.assertEqual(resp['headers']['Content-Length'], '0') + self.assertNotIn('Connection', resp['headers']) + self.assertEqual(resp['body'], '', 'body') + + def test_return_update(self): + self.assertIn('success', self.conf('0', 'routes/0/action/return')) + + resp = self.get() + self.assertEqual(resp['status'], 0) + self.assertEqual(resp['body'], '') + + self.assertIn('success', self.conf('404', 'routes/0/action/return')) + + resp = self.get() + self.assertEqual(resp['status'], 404) + self.assertNotEqual(resp['body'], '') + + self.assertIn('success', self.conf('598', 'routes/0/action/return')) + + resp = self.get() + self.assertEqual(resp['status'], 598) + self.assertNotEqual(resp['body'], '') + + self.assertIn('success', self.conf('999', 'routes/0/action/return')) + + resp = self.get() + self.assertEqual(resp['status'], 999) + self.assertEqual(resp['body'], '') + + def test_return_location(self): + reserved = ":/?#[]@!$&'()*+,;=" + unreserved = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~") + unsafe = " \"%<>\\^`{|}" + unsafe_enc = "%20%22%25%3C%3E%5C%5E%60%7B%7C%7D" + + def check_location(location, expect=None): + if expect is None: + expect = location + + self.assertIn( + 'success', + self.conf( + {"return": 301, "location": location}, 'routes/0/action' + ), + 'configure location' + ) + + self.assertEqual(self.get()['headers']['Location'], expect) + + # FAIL: can't specify empty header value. + # check_location("") + + check_location(reserved) + + # After first "?" all other "?" encoded. + check_location("/?" + reserved, "/?:/%3F#[]@!$&'()*+,;=") + check_location("???", "?%3F%3F") + + # After first "#" all other "?" or "#" encoded. + check_location("/#" + reserved, "/#:/%3F%23[]@!$&'()*+,;=") + check_location("##?#?", "#%23%3F%23%3F") + + # After first "?" next "#" not encoded. + check_location("/?#" + reserved, "/?#:/%3F%23[]@!$&'()*+,;=") + check_location("??##", "?%3F#%23") + check_location("/?##?", "/?#%23%3F") + + # Unreserved never encoded. + check_location(unreserved) + check_location("/" + unreserved + "?" + unreserved + "#" + unreserved) + + # Unsafe always encoded. + check_location(unsafe, unsafe_enc) + check_location("?" + unsafe, "?" + unsafe_enc) + check_location("#" + unsafe, "#" + unsafe_enc) + + # %00-%20 and %7F-%FF always encoded. + check_location(u"\u0000\u0018\u001F\u0020\u0021", "%00%18%1F%20!") + check_location(u"\u007F\u0080н\u20BD", "%7F%C2%80%D0%BD%E2%82%BD") + + # Encoded string detection. If at least one char need to be encoded + # then whole string will be encoded. + check_location("%20") + check_location("/%20?%20#%20") + check_location(" %20", "%20%2520") + check_location("%20 ", "%2520%20") + check_location("/%20?%20#%20 ", "/%2520?%2520#%2520%20") + + def test_return_location_edit(self): + self.assertIn( + 'success', + self.conf( + {"return": 302, "location": "blah"}, 'routes/0/action' + ), + 'configure init location' + ) + self.assertEqual(self.get()['headers']['Location'], 'blah') + + self.assertIn( + 'success', + self.conf_delete('routes/0/action/location'), + 'location delete' + ) + self.assertNotIn('Location', self.get()['headers']) + + self.assertIn( + 'success', + self.conf('"blah"', 'routes/0/action/location'), + 'location restore' + ) + self.assertEqual(self.get()['headers']['Location'], 'blah') + + self.assertIn( + 'error', + self.conf_post('"blah"', 'routes/0/action/location'), + 'location method not allowed' + ) + self.assertEqual(self.get()['headers']['Location'], 'blah') + + def test_return_invalid(self): + def check_error(conf): + self.assertIn('error', self.conf(conf, 'routes/0/action')) + + check_error({"return": "200"}) + check_error({"return": []}) + check_error({"return": 80.1}) + check_error({"return": 1000}) + check_error({"return": -1}) + check_error({"return": 200, "share": "/blah"}) + + self.assertIn( + 'error', self.conf('001', 'routes/0/action/return'), 'leading zero' + ) + + check_error({"return": 301, "location": 0}) + check_error({"return": 301, "location": []}) + + +if __name__ == '__main__': + TestReturn.main() diff --git a/test/test_routing.py b/test/test_routing.py index 950923d6..ad793662 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -16,27 +16,10 @@ class TestRouting(TestApplicationProto): "routes": [ { "match": {"method": "GET"}, - "action": {"pass": "applications/empty"}, + "action": {"return": 200}, } ], - "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", - }, - }, + "applications": {}, } ), 'routing configure', @@ -48,18 +31,14 @@ class TestRouting(TestApplicationProto): def route_match(self, match): self.assertIn( 'success', - self.route( - {"match": match, "action": {"pass": "applications/empty"}} - ), + self.route({"match": match, "action": {"return": 200}}), 'route match configure', ) def route_match_invalid(self, match): self.assertIn( 'error', - self.route( - {"match": match, "action": {"pass": "applications/empty"}} - ), + self.route({"match": match, "action": {"return": 200}}), 'route match configure invalid', ) @@ -233,24 +212,7 @@ class TestRouting(TestApplicationProto): { "listeners": {"*:7080": {"pass": "routes/main"}}, "routes": {"main": []}, - "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", - }, - }, + "applications": {}, } ), 'route empty configure', @@ -272,7 +234,7 @@ class TestRouting(TestApplicationProto): def test_routes_route_match_absent(self): self.assertIn( 'success', - self.conf([{"action": {"pass": "applications/empty"}}], 'routes'), + self.conf([{"action": {"return": 200}}], 'routes'), 'route match absent configure', ) @@ -349,14 +311,8 @@ class TestRouting(TestApplicationProto): 'success', self.conf( [ - { - "match": {"method": "GET"}, - "action": {"pass": "applications/empty"}, - }, - { - "match": {"method": "POST"}, - "action": {"pass": "applications/mirror"}, - }, + {"match": {"method": "GET"}, "action": {"return": 200}}, + {"match": {"method": "POST"}, "action": {"return": 201}}, ], 'routes', ), @@ -364,18 +320,7 @@ class TestRouting(TestApplicationProto): ) self.assertEqual(self.get()['status'], 200, 'rules two match first') - self.assertEqual( - self.post( - headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'Connection': 'close', - }, - body='X', - )['status'], - 200, - 'rules two match second', - ) + self.assertEqual(self.post()['status'], 201, 'rules two match second') def test_routes_two(self): self.assertIn( @@ -393,20 +338,11 @@ class TestRouting(TestApplicationProto): "second": [ { "match": {"host": "localhost"}, - "action": {"pass": "applications/empty"}, + "action": {"return": 200}, } ], }, - "applications": { - "empty": { - "type": "python", - "processes": {"spare": 0}, - "path": self.current_dir + '/python/empty', - "working_directory": self.current_dir - + '/python/empty', - "module": "wsgi", - } - }, + "applications": {}, } ), 'routes two configure', @@ -556,7 +492,7 @@ class TestRouting(TestApplicationProto): self.assertIn( 'success', - self.conf([{"action": {"pass": "applications/empty"}}], 'routes'), + self.conf([{"action": {"return": 200}}], 'routes'), 'redefine 2', ) self.assertEqual(self.get()['status'], 200, 'redefine request 2') @@ -569,19 +505,8 @@ class TestRouting(TestApplicationProto): self.conf( { "listeners": {"*:7080": {"pass": "routes/main"}}, - "routes": { - "main": [{"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", - } - }, + "routes": {"main": [{"action": {"return": 200}}]}, + "applications": {}, } ), 'redefine 4', @@ -595,25 +520,19 @@ class TestRouting(TestApplicationProto): self.assertIn( 'success', - self.conf_post( - {"action": {"pass": "applications/empty"}}, 'routes/main' - ), + self.conf_post({"action": {"return": 200}}, 'routes/main'), 'redefine 6', ) self.assertEqual(self.get()['status'], 200, 'redefine request 6') self.assertIn( 'error', - self.conf( - {"action": {"pass": "applications/empty"}}, 'routes/main/2' - ), + self.conf({"action": {"return": 200}}, 'routes/main/2'), 'redefine 7', ) self.assertIn( 'success', - self.conf( - {"action": {"pass": "applications/empty"}}, 'routes/main/1' - ), + self.conf({"action": {"return": 201}}, 'routes/main/1'), 'redefine 8', ) @@ -631,10 +550,7 @@ class TestRouting(TestApplicationProto): self.assertIn( 'success', self.conf_post( - { - "match": {"method": "POST"}, - "action": {"pass": "applications/empty"}, - }, + {"match": {"method": "POST"}, "action": {"return": 200}}, 'routes', ), 'routes edit configure 2', @@ -654,9 +570,7 @@ class TestRouting(TestApplicationProto): self.assertEqual(self.post()['status'], 200, 'routes edit POST 2') self.assertIn( - 'success', - self.conf_delete('routes/0'), - 'routes edit configure 3', + 'success', self.conf_delete('routes/0'), 'routes edit configure 3', ) self.assertEqual(self.get()['status'], 404, 'routes edit GET 3') @@ -682,9 +596,7 @@ class TestRouting(TestApplicationProto): self.assertEqual(self.post()['status'], 200, 'routes edit POST 4') self.assertIn( - 'success', - self.conf_delete('routes/0'), - 'routes edit configure 5', + 'success', self.conf_delete('routes/0'), 'routes edit configure 5', ) self.assertEqual(self.get()['status'], 404, 'routes edit GET 5') @@ -693,10 +605,7 @@ class TestRouting(TestApplicationProto): self.assertIn( 'success', self.conf_post( - { - "match": {"method": "POST"}, - "action": {"pass": "applications/empty"}, - }, + {"match": {"method": "POST"}, "action": {"return": 200}}, 'routes', ), 'routes edit configure 6', @@ -710,19 +619,8 @@ class TestRouting(TestApplicationProto): self.conf( { "listeners": {"*:7080": {"pass": "routes/main"}}, - "routes": { - "main": [{"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", - } - }, + "routes": {"main": [{"action": {"return": 200}}]}, + "applications": {}, } ), 'route edit configure 7', @@ -1838,20 +1736,11 @@ class TestRouting(TestApplicationProto): "second": [ { "match": {"destination": ["127.0.0.1:7081"]}, - "action": {"pass": "applications/empty"}, + "action": {"return": 200}, } ], }, - "applications": { - "empty": { - "type": "python", - "processes": {"spare": 0}, - "path": self.current_dir + "/python/empty", - "working_directory": self.current_dir - + "/python/empty", - "module": "wsgi", - } - }, + "applications": {}, } ), 'proxy configure', diff --git a/test/test_routing_tls.py b/test/test_routing_tls.py index c6648095..36bd9057 100644 --- a/test/test_routing_tls.py +++ b/test/test_routing_tls.py @@ -2,9 +2,9 @@ from unit.applications.tls import TestApplicationTLS class TestRoutingTLS(TestApplicationTLS): - prerequisites = {'modules': ['python', 'openssl']} + prerequisites = {'modules': ['openssl']} - def test_routes_match_scheme(self): + def test_routes_match_scheme_tls(self): self.certificate() self.assertIn( @@ -21,35 +21,21 @@ class TestRoutingTLS(TestApplicationTLS): "routes": [ { "match": {"scheme": "http"}, - "action": {"pass": "applications/empty"}, + "action": {"return": 200}, }, { "match": {"scheme": "https"}, - "action": {"pass": "applications/204_no_content"}, + "action": {"return": 201}, }, ], - "applications": { - "empty": { - "type": "python", - "processes": {"spare": 0}, - "path": self.current_dir + "/python/empty", - "module": "wsgi", - }, - "204_no_content": { - "type": "python", - "processes": {"spare": 0}, - "path": self.current_dir - + "/python/204_no_content", - "module": "wsgi", - }, - }, + "applications": {}, } ), 'scheme configure', ) self.assertEqual(self.get()['status'], 200, 'http') - self.assertEqual(self.get_ssl(port=7081)['status'], 204, 'https') + self.assertEqual(self.get_ssl(port=7081)['status'], 201, 'https') if __name__ == '__main__': diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index 83a71f96..bdaabe51 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -322,6 +322,7 @@ class TestRubyApplication(TestApplicationRuby): self.assertEqual(self.get()['status'], 200, 'init') + body = '0123456789' * 500 (resp, sock) = self.post( headers={ 'Host': 'localhost', @@ -329,12 +330,13 @@ class TestRubyApplication(TestApplicationRuby): 'Content-Type': 'text/html', }, start=True, - body='0123456789' * 500, + body=body, read_timeout=1, ) - self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') + self.assertEqual(resp['body'], body, 'keep-alive 1') + body = '0123456789' resp = self.post( headers={ 'Host': 'localhost', @@ -342,10 +344,10 @@ class TestRubyApplication(TestApplicationRuby): 'Content-Type': 'text/html', }, sock=sock, - body='0123456789', + body=body, ) - self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') + self.assertEqual(resp['body'], body, 'keep-alive 2') def test_ruby_application_constants(self): self.load('constants') diff --git a/test/test_share_fallback.py b/test/test_share_fallback.py index 8c45793e..c51e43ee 100644 --- a/test/test_share_fallback.py +++ b/test/test_share_fallback.py @@ -20,19 +20,10 @@ class TestStatic(TestApplicationProto): { "listeners": { "*:7080": {"pass": "routes"}, - "*:7081": {"pass": "applications/empty"}, + "*:7081": {"pass": "routes"}, }, "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", - } - }, + "applications": {}, } ) @@ -41,37 +32,22 @@ class TestStatic(TestApplicationProto): super().tearDown() + def action_update(self, conf): + self.assertIn('success', self.conf(conf, 'routes/0/action')) + def test_fallback(self): - self.assertIn( - 'success', - self.conf({"share": "/blah"}, 'routes/0/action'), - 'configure bad path no fallback', - ) + self.action_update({"share": "/blah"}) 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', - ) + self.action_update({"share": "/blah", "fallback": {"return": 200}}) + 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', + self.action_update( + {"share": self.testdir + "/assets", "fallback": {"return": 200}} ) resp = self.get() self.assertEqual(resp['status'], 200, 'fallback status') @@ -90,36 +66,28 @@ class TestStatic(TestApplicationProto): ) def test_fallback_nested(self): - self.assertIn( - 'success', - self.conf( - { - "share": "/blah", - "fallback": { - "share": "/blah/blah", - "fallback": {"pass": "applications/empty"}, - }, + self.action_update( + { + "share": "/blah", + "fallback": { + "share": "/blah/blah", + "fallback": {"return": 200}, }, - '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', + self.action_update( + { + "share": "/blah", + "fallback": {"share": self.testdir + "/assets"}, + } ) + resp = self.get() self.assertEqual(resp['status'], 200, 'fallback share status') self.assertEqual(resp['body'], '0123456789', 'fallback share') @@ -136,76 +104,51 @@ class TestStatic(TestApplicationProto): self.assertIn( 'success', self.conf( - { - "share": "/blah", - "fallback": {"proxy": "http://127.0.0.1:7081"}, - }, - 'routes/0/action', + [ + { + "match": {"destination": "*:7081"}, + "action": {"return": 200}, + }, + { + "action": { + "share": "/blah", + "fallback": {"proxy": "http://127.0.0.1:7081"}, + } + }, + ], + 'routes', ), - 'configure fallback proxy', + 'configure fallback proxy route', ) + 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.action_update( + { + "share": "/blah", + "fallback": {"proxy": "http://127.0.0.1:7080"}, + } ) self.assertNotEqual(self.get()['status'], 200, 'fallback cycle') - self.assertIn( - 'success', self.conf_delete('listeners/*:7081'), 'delete listener' - ) + self.assertIn('success', self.conf_delete('listeners/*:7081')) 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', + def check_error(conf): + self.assertIn('error', self.conf(conf, 'routes/0/action')) + + check_error({"share": "/blah", "fallback": {}}) + check_error({"share": "/blah", "fallback": ""}) + check_error({"return": 200, "fallback": {"share": "/blah"}}) + check_error( + {"proxy": "http://127.0.0.1:7081", "fallback": {"share": "/blah"}} ) + check_error({"fallback": {"share": "/blah"}}) if __name__ == '__main__': diff --git a/test/test_tls.py b/test/test_tls.py index 475e9919..d9dcf237 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -521,7 +521,6 @@ basicConstraints = critical,CA:TRUE""" ) def test_tls_application_respawn(self): - self.skip_alerts.append(r'process \d+ exited on signal 9') self.load('mirror') self.certificate() @@ -530,7 +529,7 @@ basicConstraints = critical,CA:TRUE""" self.add_tls(application='mirror') - (resp, sock) = self.post_ssl( + (_, sock) = self.post_ssl( headers={ 'Host': 'localhost', 'Connection': 'keep-alive', @@ -545,6 +544,8 @@ basicConstraints = critical,CA:TRUE""" subprocess.call(['kill', '-9', app_id]) + self.skip_alerts.append(r'process %s exited on signal 9' % app_id) + self.wait_for_record( re.compile( ' (?!' + app_id + '#)(\d+)#\d+ "mirror" application started' diff --git a/test/test_upstreams_rr.py b/test/test_upstreams_rr.py index 2bc2d90a..7045318a 100644 --- a/test/test_upstreams_rr.py +++ b/test/test_upstreams_rr.py @@ -16,10 +16,10 @@ class TestUpstreamsRR(TestApplicationPython): { "listeners": { "*:7080": {"pass": "upstreams/one"}, - "*:7081": {"pass": "applications/ups_0"}, - "*:7082": {"pass": "applications/ups_1"}, - "*:7083": {"pass": "applications/ups_2"}, "*:7090": {"pass": "upstreams/two"}, + "*:7081": {"pass": "routes/one"}, + "*:7082": {"pass": "routes/two"}, + "*:7083": {"pass": "routes/three"}, }, "upstreams": { "one": { @@ -35,32 +35,12 @@ class TestUpstreamsRR(TestApplicationPython): }, }, }, - "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", - }, + "routes": { + "one": [{"action": {"return": 200}}], + "two": [{"action": {"return": 201}}], + "three": [{"action": {"return": 202}}], }, + "applications": {}, }, ), 'upstreams initial configuration', @@ -70,15 +50,17 @@ class TestUpstreamsRR(TestApplicationPython): 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']) + status = self.get(port=port)['status'] + if 200 > status or status > 209: + continue - if ups > len(resps) - 1: - resps.extend([0] * (ups - len(resps) + 1)) + ups = status % 10 + if ups > len(resps) - 1: + resps.extend([0] * (ups - len(resps) + 1)) - resps[ups] += 1 + resps[ups] += 1 return resps @@ -97,16 +79,19 @@ 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) + status = re.findall(r'HTTP\/\d\.\d\s(\d\d\d)', resp) + status = list(filter(lambda x: x[:2] == '20', status)) + ups = list(map(lambda x: int(x[-1]), status)) + resps = [0] * (max(ups) + 1) for i in range(len(ups)): - resps[int(ups[i])] += 1 + resps[ups[i]] += 1 return resps def test_upstreams_rr_no_weight(self): resps = self.get_resps() + self.assertEqual(sum(resps), 100, 'no weight sum') self.assertLessEqual( abs(resps[0] - resps[1]), self.cpu_count, 'no weight' ) @@ -127,6 +112,7 @@ Connection: close ) resps = self.get_resps() + self.assertEqual(sum(resps), 100, 'no weight 3 sum') self.assertLessEqual( abs(resps[0] - resps[1]), self.cpu_count, 'no weight 3' ) @@ -138,6 +124,7 @@ Connection: close ) resps = self.get_resps() + self.assertEqual(sum(resps), 100, 'no weight 4 sum') self.assertLessEqual( max(resps) - min(resps), self.cpu_count, 'no weight 4' ) @@ -193,6 +180,67 @@ Connection: close self.assertEqual(resps[0], 60, 'weight 2 0') self.assertEqual(resps[2], 40, 'weight 2 1') + def test_upstreams_rr_weight_rational(self): + def set_weights(w1, w2): + self.assertIn( + 'success', + self.conf( + { + "127.0.0.1:7081": {"weight": w1}, + "127.0.0.1:7082": {"weight": w2}, + }, + 'upstreams/one/servers', + ), + 'configure weights', + ) + + def check_reqs(w1, w2, reqs=10): + resps = self.get_resps_sc(req=reqs) + self.assertEqual(resps[0], reqs * w1 / (w1 + w2), 'weight 1') + self.assertEqual(resps[1], reqs * w2 / (w1 + w2), 'weight 2') + + def check_weights(w1, w2): + set_weights(w1, w2) + check_reqs(w1, w2) + + check_weights(0, 1) + check_weights(0, 999999.0123456) + check_weights(1, 9) + check_weights(100000, 900000) + check_weights(1, .25) + check_weights(1, 0.25) + check_weights(0.2, .8) + check_weights(1, 1.5) + check_weights(1e-3, 1E-3) + check_weights(1e-20, 1e-20) + check_weights(1e4, 1e4) + check_weights(1000000, 1000000) + + set_weights(0.25, 0.25) + self.assertIn( + 'success', + self.conf_delete('upstreams/one/servers/127.0.0.1:7081/weight'), + 'delete weight', + ) + check_reqs(1, 0.25) + + self.assertIn( + 'success', + self.conf( + { + "127.0.0.1:7081": {"weight": 0.1}, + "127.0.0.1:7082": {"weight": 1}, + "127.0.0.1:7083": {"weight": 0.9}, + }, + 'upstreams/one/servers', + ), + 'configure weights', + ) + resps = self.get_resps_sc(req=20) + self.assertEqual(resps[0], 1, 'weight 3 1') + self.assertEqual(resps[1], 10, 'weight 3 2') + self.assertEqual(resps[2], 9, 'weight 3 3') + def test_upstreams_rr_independent(self): def sum_resps(*args): sum = [0] * len(args[0]) @@ -234,33 +282,71 @@ Connection: close 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.assertEqual(sum(r_one), 100, 'dep one mix sum') self.assertLessEqual( abs(r_one[0] - r_one[1]), self.cpu_count, 'dep one mix' ) + self.assertEqual(sum(r_two), 100, 'dep two mix sum') 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', - } + self.assertIn( + 'success', + self.conf( + { + "listeners": { + "*:7080": {"pass": "upstreams/one"}, + "*:7081": {"pass": "routes"}, + "*:7082": {"pass": "routes"}, + }, + "upstreams": { + "one": { + "servers": { + "127.0.0.1:7081": {}, + "127.0.0.1:7082": {}, + }, + }, + }, + "routes": [ + { + "match": {"destination": "*:7081"}, + "action": {"pass": "applications/delayed"}, + }, + { + "match": {"destination": "*:7082"}, + "action": {"return": 201}, + }, + ], + "applications": { + "delayed": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + "/python/delayed", + "working_directory": self.current_dir + + "/python/delayed", + "module": "wsgi", + } + }, + }, + ), + 'upstreams initial configuration', + ) req = 50 socks = [] for i in range(req): - headers = headers_delay_1 if i % 5 == 0 else headers_no_delay + delay = 1 if i % 5 == 0 else 0 _, sock = self.get( - headers=headers, + headers={ + 'Host': 'localhost', + 'Content-Length': '0', + 'X-Delay': str(delay), + 'Connection': 'close', + }, start=True, no_recv=True, ) @@ -271,12 +357,12 @@ Connection: close resp = self.recvall(socks[i]).decode() socks[i].close() - m = re.search('X-Upstream: (\d+)', resp) + m = re.search('HTTP/1.1 20(\d)', resp) + self.assertIsNotNone(m, 'status') resps[int(m.group(1))] += 1 - self.assertLessEqual( - abs(resps[0] - resps[1]), self.cpu_count, 'dep two mix' - ) + self.assertEqual(sum(resps), req, 'delay sum') + self.assertLessEqual(abs(resps[0] - resps[1]), self.cpu_count, 'delay') def test_upstreams_rr_active_req(self): conns = 5 @@ -303,7 +389,7 @@ Connection: close # 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.assertEqual(self.get()['body'], '') self.assertIn( 'success', @@ -327,13 +413,17 @@ Connection: close ) for i in range(conns): - resp = self.recvall(socks[i]).decode() - socks[i].close() - - self.assertRegex(resp, r'X-Upstream', 'active req GET') + self.assertEqual( + self.http(b'', sock=socks[i], raw=True)['body'], + '', + 'active req GET', + ) - resp = self.http(b"""0123456789""", sock=socks2[i], raw=True) - self.assertEqual(resp['status'], 200, 'active req POST') + self.assertEqual( + self.http(b"""0123456789""", sock=socks2[i], raw=True)['body'], + '', + 'active req POST', + ) def test_upstreams_rr_bad_server(self): self.assertIn( @@ -356,14 +446,11 @@ Connection: close 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 + resps[self.get()['status'] % 10] += 1 + resps[self.post(body='0123456789')['status'] % 10] += 1 - self.assertLessEqual( - abs(resps[0] - resps[1]), self.cpu_count, 'post' - ) + self.assertEqual(sum(resps), 100, 'post sum') + self.assertLessEqual(abs(resps[0] - resps[1]), self.cpu_count, 'post') def test_upstreams_rr_unix(self): addr_0 = self.testdir + '/sock_0' @@ -374,8 +461,8 @@ Connection: close self.conf( { "*:7080": {"pass": "upstreams/one"}, - "unix:" + addr_0: {"pass": "applications/ups_0"}, - "unix:" + addr_1: {"pass": "applications/ups_1"}, + "unix:" + addr_0: {"pass": "routes/one"}, + "unix:" + addr_1: {"pass": "routes/two"}, }, 'listeners', ), @@ -385,7 +472,7 @@ Connection: close self.assertIn( 'success', self.conf( - {"unix:" + addr_0: {}, "unix:" + addr_1: {},}, + {"unix:" + addr_0: {}, "unix:" + addr_1: {}}, 'upstreams/one/servers', ), 'configure servers unix', @@ -402,8 +489,8 @@ Connection: close self.conf( { "*:7080": {"pass": "upstreams/one"}, - "[::1]:7081": {"pass": "applications/ups_0"}, - "[::1]:7082": {"pass": "applications/ups_1"}, + "[::1]:7081": {"pass": "routes/one"}, + "[::1]:7082": {"pass": "routes/two"}, }, 'listeners', ), @@ -413,7 +500,7 @@ Connection: close self.assertIn( 'success', self.conf( - {"[::1]:7081": {}, "[::1]:7082": {},}, 'upstreams/one/servers' + {"[::1]:7081": {}, "[::1]:7082": {}}, 'upstreams/one/servers' ), 'configure servers ipv6', ) @@ -429,9 +516,29 @@ Connection: close self.conf({}, 'upstreams/one/servers'), 'configure servers empty', ) - self.assertEqual(self.get()['status'], 502, 'servers empty') + self.assertIn( + 'success', + self.conf( + {"127.0.0.1:7081": {"weight": 0}}, 'upstreams/one/servers' + ), + 'configure servers empty one', + ) + self.assertEqual(self.get()['status'], 502, 'servers empty one') + self.assertIn( + 'success', + self.conf( + { + "127.0.0.1:7081": {"weight": 0}, + "127.0.0.1:7082": {"weight": 0}, + }, + 'upstreams/one/servers', + ), + 'configure servers empty two', + ) + self.assertEqual(self.get()['status'], 502, 'servers empty two') + def test_upstreams_rr_invalid(self): self.assertIn( 'error', self.conf({}, 'upstreams'), 'upstreams empty', @@ -449,16 +556,21 @@ Connection: close 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', - ) + + def check_weight(w): + self.assertIn( + 'error', + self.conf(w, 'upstreams/one/servers/127.0.0.1:7081/weight'), + 'invalid weight option', + ) + check_weight({}) + check_weight('-1') + check_weight('1.') + check_weight('1.1.') + check_weight('.') + check_weight('.01234567890123') + check_weight('1000001') + check_weight('2e6') if __name__ == '__main__': diff --git a/test/test_usr1.py b/test/test_usr1.py index 2b4f394b..155303ea 100644 --- a/test/test_usr1.py +++ b/test/test_usr1.py @@ -51,12 +51,11 @@ class TestUSR1(TestApplicationPython): 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_new = 'new.log' - log_path = self.testdir + '/' + 'unit.log' + log_path = self.testdir + '/unit.log' log_path_new = self.testdir + '/' + log_new os.rename(log_path, log_path_new) diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py index ef16f433..fc15e8e4 100644 --- a/test/unit/applications/websockets.py +++ b/test/unit/applications/websockets.py @@ -52,7 +52,11 @@ class TestApplicationWebsocket(TestApplicationProto): ) resp = '' - while select.select([sock], [], [], 30)[0]: + while True: + rlist = select.select([sock], [], [], 60)[0] + if not rlist: + self.fail('Can\'t read response from server.') + resp += sock.recv(4096).decode() if ( @@ -70,10 +74,18 @@ class TestApplicationWebsocket(TestApplicationProto): def serialize_close(self, code=1000, reason=''): return struct.pack('!H', code) + reason.encode('utf-8') - def frame_read(self, sock, read_timeout=30): + def frame_read(self, sock, read_timeout=60): def recv_bytes(sock, bytes): data = b'' - while select.select([sock], [], [], read_timeout)[0]: + while True: + rlist = select.select([sock], [], [], read_timeout)[0] + if not rlist: + # For all current cases if the "read_timeout" was changed + # than test do not expect to get a response from server. + if read_timeout == 60: + self.fail('Can\'t read response from server.') + break + data += sock.recv(bytes - len(data)) if len(data) == bytes: @@ -221,7 +233,7 @@ class TestApplicationWebsocket(TestApplicationProto): op_code = self.OP_CONT pos = end - def message_read(self, sock, read_timeout=10): + def message_read(self, sock, read_timeout=60): frame = self.frame_read(sock, read_timeout=read_timeout) while not frame['fin']: diff --git a/test/unit/http.py b/test/unit/http.py index 47fb48f1..13384dc8 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -17,11 +17,6 @@ class TestHTTP(TestUnit): port = 7080 if 'port' not in kwargs else kwargs['port'] url = '/' if 'url' not in kwargs else kwargs['url'] http = 'HTTP/1.0' if 'http_10' in kwargs else 'HTTP/1.1' - read_buffer_size = ( - 4096 - if 'read_buffer_size' not in kwargs - else kwargs['read_buffer_size'] - ) headers = ( {'Host': 'localhost', 'Connection': 'close'} @@ -60,7 +55,7 @@ class TestHTTP(TestUnit): sock.connect(connect_args) except ConnectionRefusedError: sock.close() - return None + self.fail('Client can\'t connect to the server.') else: sock = kwargs['sock'] @@ -101,12 +96,15 @@ class TestHTTP(TestUnit): resp = '' if 'no_recv' not in kwargs: - read_timeout = ( - 30 if 'read_timeout' not in kwargs else kwargs['read_timeout'] - ) - resp = self.recvall( - sock, read_timeout=read_timeout, buff_size=read_buffer_size - ).decode(encoding) + recvall_kwargs = {} + + if 'read_timeout' in kwargs: + recvall_kwargs['read_timeout'] = kwargs['read_timeout'] + + if 'read_buffer_size' in kwargs: + recvall_kwargs['buff_size'] = kwargs['read_buffer_size'] + + resp = self.recvall(sock, **recvall_kwargs).decode(encoding) self.log_in(resp) @@ -174,9 +172,26 @@ class TestHTTP(TestUnit): def put(self, **kwargs): return self.http('PUT', **kwargs) - def recvall(self, sock, read_timeout=30, buff_size=4096): + def recvall(self, sock, **kwargs): + timeout_default = 60 + + timeout = ( + timeout_default + if 'read_timeout' not in kwargs + else kwargs['read_timeout'] + ) + buff_size = 4096 if 'buff_size' not in kwargs else kwargs['buff_size'] + data = b'' - while select.select([sock], [], [], read_timeout)[0]: + while True: + rlist = select.select([sock], [], [], timeout)[0] + if not rlist: + # For all current cases if the "read_timeout" was changed + # than test do not expect to get a response from server. + if timeout == timeout_default: + self.fail('Can\'t read response from server.') + break + try: part = sock.recv(buff_size) except: @@ -264,12 +279,8 @@ class TestHTTP(TestUnit): def _parse_json(self, resp): headers = resp['headers'] - self.assertIn('Content-Type', headers, 'Content-Type header set') - self.assertEqual( - headers['Content-Type'], - 'application/json', - 'Content-Type header is application/json', - ) + self.assertIn('Content-Type', headers) + self.assertEqual(headers['Content-Type'], 'application/json') resp['body'] = json.loads(resp['body']) diff --git a/test/unit/main.py b/test/unit/main.py index 69234dcc..4507f71a 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -4,6 +4,7 @@ import sys import stat import time import fcntl +import atexit import shutil import signal import argparse @@ -151,48 +152,6 @@ class TestUnit(unittest.TestCase): def setUp(self): self._run() - def tearDown(self): - self.stop() - - # detect errors and failures for current test - - def list2reason(exc_list): - if exc_list and exc_list[-1][0] is self: - return exc_list[-1][1] - - if hasattr(self, '_outcome'): - result = self.defaultTestResult() - self._feedErrorsToResult(result, self._outcome.errors) - else: - result = getattr( - self, '_outcomeForDoCleanups', self._resultForDoCleanups - ) - - success = not list2reason(result.errors) and not list2reason( - result.failures - ) - - # check unit.log for alerts - - unit_log = self.testdir + '/unit.log' - - with open(unit_log, 'r', encoding='utf-8', errors='ignore') as f: - self._check_alerts(f.read()) - - # remove unit.log - - if not TestUnit.save_log and success: - shutil.rmtree(self.testdir) - - else: - self._print_log() - - def stop(self): - if self._started: - self._stop() - - self.stop_processes() - def _run(self): build_dir = self.pardir + '/build' self.unitd = build_dir + '/unitd' @@ -224,62 +183,81 @@ class TestUnit(unittest.TestCase): stderr=log, ) + atexit.register(self.stop) + 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 self._p as p: - p.send_signal(signal.SIGQUIT) + def tearDown(self): + stop_errs = self.stop() - 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() + # detect errors and failures for current test - self._started = False + def list2reason(exc_list): + if exc_list and exc_list[-1][0] is self: + return exc_list[-1][1] - def _check_alerts(self, log): - found = False + if hasattr(self, '_outcome'): + result = self.defaultTestResult() + self._feedErrorsToResult(result, self._outcome.errors) + else: + result = getattr( + self, '_outcomeForDoCleanups', self._resultForDoCleanups + ) - alerts = re.findall('.+\[alert\].+', log) + success = not list2reason(result.errors) and not list2reason( + result.failures + ) - if alerts: - print('All alerts/sanitizer errors found in log:') - [print(alert) for alert in alerts] - found = True + # check unit.log for alerts - if self.skip_alerts: - for skip in self.skip_alerts: - alerts = [al for al in alerts if re.search(skip, al) is None] + unit_log = self.testdir + '/unit.log' - if alerts: - self._print_log(log) - self.assertFalse(alerts, 'alert(s)') + with open(unit_log, 'r', encoding='utf-8', errors='ignore') as f: + self._check_alerts(f.read()) - if not self.skip_sanitizer: - sanitizer_errors = re.findall('.+Sanitizer.+', log) + # remove unit.log - if sanitizer_errors: - self._print_log(log) - self.assertFalse(sanitizer_errors, 'sanitizer error(s)') + if not TestUnit.save_log and success: + shutil.rmtree(self.testdir) - if found: - print('skipped.') + else: + self._print_log() + + self.assertListEqual(stop_errs, [None, None], 'stop errors') + + def stop(self): + errors = [] + + errors.append(self._stop()) + + errors.append(self.stop_processes()) + + atexit.unregister(self.stop) + + return errors + + def _stop(self): + if self._p.poll() is not None: + return + + with self._p as p: + p.send_signal(signal.SIGQUIT) + + try: + retcode = p.wait(15) + if retcode: + return 'Child process terminated with code ' + str(retcode) + except: + p.kill() + return 'Could not terminate unit' def run_process(self, target, *args): if not hasattr(self, '_processes'): @@ -294,12 +272,17 @@ class TestUnit(unittest.TestCase): if not hasattr(self, '_processes'): return + fail = False for process in self._processes: - process.terminate() - process.join(timeout=5) - if process.is_alive(): - self.fail('Fail to stop process') + process.terminate() + process.join(timeout=15) + + if process.is_alive(): + fail = True + + if fail: + return 'Fail to stop process' def waitforfiles(self, *files): for i in range(50): @@ -329,6 +312,34 @@ class TestUnit(unittest.TestCase): for f in files: os.chmod(os.path.join(root, f), 0o777) + def _check_alerts(self, log): + found = False + + alerts = re.findall('.+\[alert\].+', log) + + if alerts: + print('All alerts/sanitizer errors found in log:') + [print(alert) for alert in alerts] + found = True + + if self.skip_alerts: + for skip in self.skip_alerts: + alerts = [al for al in alerts if re.search(skip, al) is None] + + if alerts: + 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_log(log) + self.assertFalse(sanitizer_errors, 'sanitizer error(s)') + + if found: + print('skipped.') + @staticmethod def _parse_args(): parser = argparse.ArgumentParser(add_help=False) |