c183bd8749
Class usage came from the unittest framework and it was always redundant after migration to the pytest. This commit removes classes from files containing tests to make them more readable and understandable.
476 lines
11 KiB
Python
476 lines
11 KiB
Python
import re
|
|
import time
|
|
|
|
import pytest
|
|
from packaging import version
|
|
from unit.applications.lang.python import ApplicationPython
|
|
|
|
prerequisites = {
|
|
'modules': {'python': lambda v: version.parse(v) >= version.parse('3.5')}
|
|
}
|
|
|
|
client = ApplicationPython(load_module='asgi')
|
|
|
|
|
|
def test_asgi_application_variables(date_to_sec_epoch, sec_epoch):
|
|
client.load('variables')
|
|
|
|
body = 'Test body string.'
|
|
|
|
resp = client.http(
|
|
f"""POST / HTTP/1.1
|
|
Host: localhost
|
|
Content-Length: {len(body)}
|
|
Custom-Header: blah
|
|
Custom-hEader: Blah
|
|
Content-Type: text/html
|
|
Connection: close
|
|
custom-header: BLAH
|
|
|
|
{body}""".encode(),
|
|
raw=True,
|
|
)
|
|
|
|
assert resp['status'] == 200, 'status'
|
|
headers = resp['headers']
|
|
header_server = headers.pop('Server')
|
|
assert re.search(r'Unit/[\d\.]+', header_server), 'server header'
|
|
|
|
date = headers.pop('Date')
|
|
assert date[-4:] == ' GMT', 'date header timezone'
|
|
assert abs(date_to_sec_epoch(date) - sec_epoch) < 5, 'date header'
|
|
|
|
assert headers == {
|
|
'Connection': 'close',
|
|
'content-length': str(len(body)),
|
|
'content-type': 'text/html',
|
|
'request-method': 'POST',
|
|
'request-uri': '/',
|
|
'http-host': 'localhost',
|
|
'http-version': '1.1',
|
|
'custom-header': 'blah, Blah, BLAH',
|
|
'asgi-version': '3.0',
|
|
'asgi-spec-version': '2.1',
|
|
'scheme': 'http',
|
|
}, 'headers'
|
|
assert resp['body'] == body, 'body'
|
|
|
|
|
|
def test_asgi_application_ipv6():
|
|
client.load('empty')
|
|
|
|
assert 'success' in client.conf(
|
|
{"[::1]:7080": {"pass": "applications/empty"}}, 'listeners'
|
|
)
|
|
|
|
assert client.get(sock_type='ipv6')['status'] == 200
|
|
|
|
|
|
def test_asgi_application_unix(temp_dir):
|
|
client.load('empty')
|
|
|
|
addr = f'{temp_dir}/sock'
|
|
assert 'success' in client.conf(
|
|
{f"unix:{addr}": {"pass": "applications/empty"}}, 'listeners'
|
|
)
|
|
|
|
assert client.get(sock_type='unix', addr=addr)['status'] == 200
|
|
|
|
|
|
def test_asgi_application_query_string():
|
|
client.load('query_string')
|
|
|
|
resp = client.get(url='/?var1=val1&var2=val2')
|
|
|
|
assert (
|
|
resp['headers']['query-string'] == 'var1=val1&var2=val2'
|
|
), 'query-string header'
|
|
|
|
|
|
def test_asgi_application_prefix():
|
|
client.load('prefix', prefix='/api/rest')
|
|
|
|
def set_prefix(prefix):
|
|
client.conf(f'"{prefix}"', 'applications/prefix/prefix')
|
|
|
|
def check_prefix(url, prefix):
|
|
resp = client.get(url=url)
|
|
assert resp['status'] == 200
|
|
assert resp['headers']['prefix'] == prefix
|
|
|
|
check_prefix('/ap', 'NULL')
|
|
check_prefix('/api', 'NULL')
|
|
check_prefix('/api/', 'NULL')
|
|
check_prefix('/api/res', 'NULL')
|
|
check_prefix('/api/restful', 'NULL')
|
|
check_prefix('/api/rest', '/api/rest')
|
|
check_prefix('/api/rest/', '/api/rest')
|
|
check_prefix('/api/rest/get', '/api/rest')
|
|
check_prefix('/api/rest/get/blah', '/api/rest')
|
|
|
|
set_prefix('/api/rest/')
|
|
check_prefix('/api/rest', '/api/rest')
|
|
check_prefix('/api/restful', 'NULL')
|
|
check_prefix('/api/rest/', '/api/rest')
|
|
check_prefix('/api/rest/blah', '/api/rest')
|
|
|
|
set_prefix('/app')
|
|
check_prefix('/ap', 'NULL')
|
|
check_prefix('/app', '/app')
|
|
check_prefix('/app/', '/app')
|
|
check_prefix('/application/', 'NULL')
|
|
|
|
set_prefix('/')
|
|
check_prefix('/', 'NULL')
|
|
check_prefix('/app', 'NULL')
|
|
|
|
|
|
def test_asgi_application_query_string_space():
|
|
client.load('query_string')
|
|
|
|
resp = client.get(url='/ ?var1=val1&var2=val2')
|
|
assert (
|
|
resp['headers']['query-string'] == 'var1=val1&var2=val2'
|
|
), 'query-string space'
|
|
|
|
resp = client.get(url='/ %20?var1=val1&var2=val2')
|
|
assert (
|
|
resp['headers']['query-string'] == 'var1=val1&var2=val2'
|
|
), 'query-string space 2'
|
|
|
|
resp = client.get(url='/ %20 ?var1=val1&var2=val2')
|
|
assert (
|
|
resp['headers']['query-string'] == 'var1=val1&var2=val2'
|
|
), 'query-string space 3'
|
|
|
|
resp = client.get(url='/blah %20 blah? var1= val1 & var2=val2')
|
|
assert (
|
|
resp['headers']['query-string'] == ' var1= val1 & var2=val2'
|
|
), 'query-string space 4'
|
|
|
|
|
|
def test_asgi_application_query_string_empty():
|
|
client.load('query_string')
|
|
|
|
resp = client.get(url='/?')
|
|
|
|
assert resp['status'] == 200, 'query string empty status'
|
|
assert resp['headers']['query-string'] == '', 'query string empty'
|
|
|
|
|
|
def test_asgi_application_query_string_absent():
|
|
client.load('query_string')
|
|
|
|
resp = client.get()
|
|
|
|
assert resp['status'] == 200, 'query string absent status'
|
|
assert resp['headers']['query-string'] == '', 'query string absent'
|
|
|
|
|
|
@pytest.mark.skip('not yet')
|
|
def test_asgi_application_server_port():
|
|
client.load('server_port')
|
|
|
|
assert (
|
|
client.get()['headers']['Server-Port'] == '7080'
|
|
), 'Server-Port header'
|
|
|
|
|
|
@pytest.mark.skip('not yet')
|
|
def test_asgi_application_working_directory_invalid():
|
|
client.load('empty')
|
|
|
|
assert 'success' in client.conf(
|
|
'"/blah"', 'applications/empty/working_directory'
|
|
), 'configure invalid working_directory'
|
|
|
|
assert client.get()['status'] == 500, 'status'
|
|
|
|
|
|
def test_asgi_application_204_transfer_encoding():
|
|
client.load('204_no_content')
|
|
|
|
assert (
|
|
'Transfer-Encoding' not in client.get()['headers']
|
|
), '204 header transfer encoding'
|
|
|
|
|
|
def test_asgi_application_shm_ack_handle():
|
|
# Minimum possible limit
|
|
shm_limit = 10 * 1024 * 1024
|
|
|
|
client.load('mirror', limits={"shm": shm_limit})
|
|
|
|
# Should exceed shm_limit
|
|
max_body_size = 12 * 1024 * 1024
|
|
|
|
assert 'success' in client.conf(
|
|
f'{{"http":{{"max_body_size": {max_body_size} }}}}',
|
|
'settings',
|
|
)
|
|
|
|
assert client.get()['status'] == 200, 'init'
|
|
|
|
body = '0123456789AB' * 1024 * 1024 # 12 Mb
|
|
resp = client.post(body=body, read_buffer_size=1024 * 1024)
|
|
|
|
assert resp['body'] == body, 'keep-alive 1'
|
|
|
|
|
|
def test_asgi_keepalive_body():
|
|
client.load('mirror')
|
|
|
|
assert client.get()['status'] == 200, 'init'
|
|
|
|
body = '0123456789' * 500
|
|
(resp, sock) = client.post(
|
|
headers={
|
|
'Host': 'localhost',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
start=True,
|
|
body=body,
|
|
read_timeout=1,
|
|
)
|
|
|
|
assert resp['body'] == body, 'keep-alive 1'
|
|
|
|
body = '0123456789'
|
|
resp = client.post(sock=sock, body=body)
|
|
|
|
assert resp['body'] == body, 'keep-alive 2'
|
|
|
|
|
|
def test_asgi_keepalive_reconfigure():
|
|
client.load('mirror')
|
|
|
|
assert client.get()['status'] == 200, 'init'
|
|
|
|
body = '0123456789'
|
|
conns = 3
|
|
socks = []
|
|
|
|
for i in range(conns):
|
|
(resp, sock) = client.post(
|
|
headers={
|
|
'Host': 'localhost',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
start=True,
|
|
body=body,
|
|
read_timeout=1,
|
|
)
|
|
|
|
assert resp['body'] == body, 'keep-alive open'
|
|
|
|
client.load('mirror', processes=i + 1)
|
|
|
|
socks.append(sock)
|
|
|
|
for i in range(conns):
|
|
(resp, sock) = client.post(
|
|
headers={
|
|
'Host': 'localhost',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
start=True,
|
|
sock=socks[i],
|
|
body=body,
|
|
read_timeout=1,
|
|
)
|
|
|
|
assert resp['body'] == body, 'keep-alive request'
|
|
|
|
client.load('mirror', processes=i + 1)
|
|
|
|
for i in range(conns):
|
|
resp = client.post(sock=socks[i], body=body)
|
|
|
|
assert resp['body'] == body, 'keep-alive close'
|
|
|
|
client.load('mirror', processes=i + 1)
|
|
|
|
|
|
def test_asgi_keepalive_reconfigure_2():
|
|
client.load('mirror')
|
|
|
|
assert client.get()['status'] == 200, 'init'
|
|
|
|
body = '0123456789'
|
|
|
|
(resp, sock) = client.post(
|
|
headers={
|
|
'Host': 'localhost',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
start=True,
|
|
body=body,
|
|
read_timeout=1,
|
|
)
|
|
|
|
assert resp['body'] == body, 'reconfigure 2 keep-alive 1'
|
|
|
|
client.load('empty')
|
|
|
|
assert client.get()['status'] == 200, 'init'
|
|
|
|
(resp, sock) = client.post(start=True, sock=sock, body=body)
|
|
|
|
assert resp['status'] == 200, 'reconfigure 2 keep-alive 2'
|
|
assert resp['body'] == '', 'reconfigure 2 keep-alive 2 body'
|
|
|
|
assert 'success' in client.conf(
|
|
{"listeners": {}, "applications": {}}
|
|
), 'reconfigure 2 clear configuration'
|
|
|
|
resp = client.get(sock=sock)
|
|
|
|
assert resp == {}, 'reconfigure 2 keep-alive 3'
|
|
|
|
|
|
def test_asgi_keepalive_reconfigure_3():
|
|
client.load('empty')
|
|
|
|
assert client.get()['status'] == 200, 'init'
|
|
|
|
sock = client.http(
|
|
b"""GET / HTTP/1.1
|
|
""",
|
|
raw=True,
|
|
no_recv=True,
|
|
)
|
|
|
|
assert client.get()['status'] == 200
|
|
|
|
assert 'success' in client.conf(
|
|
{"listeners": {}, "applications": {}}
|
|
), 'reconfigure 3 clear configuration'
|
|
|
|
resp = client.http(
|
|
b"""Host: localhost
|
|
Connection: close
|
|
|
|
""",
|
|
sock=sock,
|
|
raw=True,
|
|
)
|
|
|
|
assert resp['status'] == 200, 'reconfigure 3'
|
|
|
|
|
|
def test_asgi_process_switch():
|
|
client.load('delayed', processes=2)
|
|
|
|
client.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',
|
|
}
|
|
|
|
client.get(headers=headers_delay_1, no_recv=True)
|
|
|
|
time.sleep(0.5)
|
|
|
|
for _ in range(10):
|
|
client.get(headers=headers_delay_1, no_recv=True)
|
|
|
|
client.get(headers=headers_delay_1)
|
|
|
|
|
|
def test_asgi_application_loading_error(skip_alert):
|
|
skip_alert(r'Python failed to import module "blah"')
|
|
|
|
client.load('empty', module="blah")
|
|
|
|
assert client.get()['status'] == 503, 'loading error'
|
|
|
|
|
|
def test_asgi_application_threading(wait_for_record):
|
|
"""wait_for_record() timeouts after 5s while every thread works at
|
|
least 3s. So without releasing GIL test should fail.
|
|
"""
|
|
|
|
client.load('threading')
|
|
|
|
for _ in range(10):
|
|
client.get(no_recv=True)
|
|
|
|
assert (
|
|
wait_for_record(r'\(5\) Thread: 100', wait=50) is not None
|
|
), 'last thread finished'
|
|
|
|
|
|
def test_asgi_application_threads():
|
|
client.load('threads', threads=2)
|
|
|
|
socks = []
|
|
|
|
for _ in range(2):
|
|
sock = client.get(
|
|
headers={
|
|
'Host': 'localhost',
|
|
'X-Delay': '3',
|
|
'Connection': 'close',
|
|
},
|
|
no_recv=True,
|
|
)
|
|
|
|
socks.append(sock)
|
|
|
|
time.sleep(1.0) # required to avoid greedy request reading
|
|
|
|
threads = set()
|
|
|
|
for sock in socks:
|
|
resp = client.recvall(sock).decode('utf-8')
|
|
|
|
client.log_in(resp)
|
|
|
|
resp = client._resp_to_dict(resp)
|
|
|
|
assert resp['status'] == 200, 'status'
|
|
|
|
threads.add(resp['headers']['x-thread'])
|
|
|
|
sock.close()
|
|
|
|
assert len(socks) == len(threads), 'threads differs'
|
|
|
|
|
|
def test_asgi_application_legacy():
|
|
client.load('legacy')
|
|
|
|
resp = client.get(
|
|
headers={
|
|
'Host': 'localhost',
|
|
'Content-Length': '0',
|
|
'Connection': 'close',
|
|
},
|
|
)
|
|
|
|
assert resp['status'] == 200, 'status'
|
|
|
|
|
|
def test_asgi_application_legacy_force():
|
|
client.load('legacy_force', protocol='asgi')
|
|
|
|
resp = client.get(
|
|
headers={
|
|
'Host': 'localhost',
|
|
'Content-Length': '0',
|
|
'Connection': 'close',
|
|
},
|
|
)
|
|
|
|
assert resp['status'] == 200, 'status'
|