summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/python/delayed/wsgi.py25
-rw-r--r--test/python/errors_write/wsgi.py1
-rw-r--r--test/python/iter_exception/wsgi.py45
-rw-r--r--test/python/log_body/wsgi.py9
-rw-r--r--test/python/threading/wsgi.py33
-rw-r--r--test/ruby/constants/config.ru15
-rw-r--r--test/test_access_log.py47
-rw-r--r--test/test_configuration.py2
-rw-r--r--test/test_go_isolation.py32
-rw-r--r--test/test_java_websockets.py63
-rw-r--r--test/test_node_websockets.py43
-rw-r--r--test/test_proxy.py622
-rw-r--r--test/test_python_application.py182
-rw-r--r--test/test_routing.py118
-rw-r--r--test/test_ruby_application.py23
-rw-r--r--test/test_static.py6
-rw-r--r--test/test_usr1.py92
-rw-r--r--test/unit/applications/websockets.py31
-rw-r--r--test/unit/http.py20
-rw-r--r--test/unit/main.py48
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