summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
authorKonstantin Pavlov <thresh@nginx.com>2022-09-13 13:17:16 +0400
committerKonstantin Pavlov <thresh@nginx.com>2022-09-13 13:17:16 +0400
commitce964aa30b163e2b3263c5af57c1a6dae7d0cebb (patch)
tree26bf70c1a5991f6471fc4caed8628e068fdc0b7b /test
parenteba4c3c98fa1bf275d94df8c727f70692ae7eae1 (diff)
parent38bd7e76a134084ab95a4ee3125af1ccd7b35864 (diff)
downloadunit-1.28.0-1.tar.gz
unit-1.28.0-1.tar.bz2
Merged with the default branch.1.28.0-1
Diffstat (limited to 'test')
-rw-r--r--test/conftest.py5
-rw-r--r--test/python/ctx_iter_atexit/wsgi.py1
-rw-r--r--test/python/forwarded_header/wsgi.py10
-rw-r--r--test/test_access_log.py82
-rw-r--r--test/test_asgi_application.py55
-rw-r--r--test/test_asgi_application_unix_abstract.py23
-rw-r--r--test/test_client_ip.py82
-rw-r--r--test/test_configuration.py10
-rw-r--r--test/test_forwarded_header.py266
-rw-r--r--test/test_go_application.py12
-rw-r--r--test/test_node_application.py11
-rw-r--r--test/test_php_application.py11
-rw-r--r--test/test_proxy.py4
-rw-r--r--test/test_python_application.py44
-rw-r--r--test/test_routing.py14
-rw-r--r--test/test_ruby_application.py13
-rw-r--r--test/test_static_chroot.py126
-rw-r--r--test/test_status.py223
-rw-r--r--test/test_status_tls.py30
-rw-r--r--test/test_tls.py26
-rw-r--r--test/test_tls_sni.py2
-rw-r--r--test/test_unix_abstract.py109
-rw-r--r--test/test_variables.py154
-rw-r--r--test/unit/applications/lang/go.py5
-rw-r--r--test/unit/applications/proto.py14
-rw-r--r--test/unit/check/unix_abstract.py25
-rw-r--r--test/unit/http.py6
-rw-r--r--test/unit/status.py45
28 files changed, 1121 insertions, 287 deletions
diff --git a/test/conftest.py b/test/conftest.py
index 904abc32..18851baa 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -20,9 +20,11 @@ from unit.check.isolation import check_isolation
from unit.check.node import check_node
from unit.check.regex import check_regex
from unit.check.tls import check_openssl
+from unit.check.unix_abstract import check_unix_abstract
from unit.http import TestHTTP
from unit.log import Log
from unit.option import option
+from unit.status import Status
from unit.utils import public_dir
from unit.utils import waitforfiles
@@ -213,6 +215,7 @@ def pytest_sessionstart(session):
check_chroot()
check_isolation()
+ check_unix_abstract()
_clear_conf(unit['temp_dir'] + '/control.unit.sock')
@@ -427,6 +430,8 @@ def unit_run(state_dir=None):
controller['pid'] = pid_by_name(controller['name'])
controller['fds'] = _count_fds(controller['pid'])
+ Status._check_zeros()
+
return unit_instance
diff --git a/test/python/ctx_iter_atexit/wsgi.py b/test/python/ctx_iter_atexit/wsgi.py
index b2f12c35..75d40895 100644
--- a/test/python/ctx_iter_atexit/wsgi.py
+++ b/test/python/ctx_iter_atexit/wsgi.py
@@ -15,7 +15,6 @@ class application:
self.start(
'200',
[
- ('Content-Type', self.environ.get('CONTENT_TYPE')),
('Content-Length', str(len(body))),
],
)
diff --git a/test/python/forwarded_header/wsgi.py b/test/python/forwarded_header/wsgi.py
new file mode 100644
index 00000000..44d370ab
--- /dev/null
+++ b/test/python/forwarded_header/wsgi.py
@@ -0,0 +1,10 @@
+def application(env, start_response):
+ start_response(
+ '200',
+ [
+ ('Content-Length', '0'),
+ ('Remote-Addr', env.get('REMOTE_ADDR')),
+ ('Url-Scheme', env.get('wsgi.url_scheme')),
+ ],
+ )
+ return []
diff --git a/test/test_access_log.py b/test/test_access_log.py
index 5d242a1a..b1d89343 100644
--- a/test/test_access_log.py
+++ b/test/test_access_log.py
@@ -15,6 +15,15 @@ class TestAccessLog(TestApplicationPython):
'"' + option.temp_dir + '/access.log"', 'access_log'
), 'access_log configure'
+ def set_format(self, format):
+ assert 'success' in self.conf(
+ {
+ 'path': option.temp_dir + '/access.log',
+ 'format': format,
+ },
+ 'access_log',
+ ), 'access_log format'
+
def wait_for_record(self, pattern, name='access.log'):
return super().wait_for_record(pattern, name)
@@ -27,7 +36,6 @@ class TestAccessLog(TestApplicationPython):
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
body='01234',
@@ -38,15 +46,7 @@ class TestAccessLog(TestApplicationPython):
self.wait_for_record(r'"POST / HTTP/1.1" 200 5') is not None
), 'keepalive 1'
- resp = self.post(
- headers={
- 'Host': 'localhost',
- 'Connection': 'close',
- 'Content-Type': 'text/html',
- },
- sock=sock,
- body='0123456789',
- )
+ resp = self.post(sock=sock, body='0123456789')
assert (
self.wait_for_record(r'"POST / HTTP/1.1" 200 10') is not None
@@ -263,3 +263,65 @@ Connection: close
self.wait_for_record(r'"GET / HTTP/1.1" 200 0 "-" "-"', 'new.log')
is not None
), 'change'
+
+ def test_access_log_format(self):
+ self.load('empty')
+
+ def check_format(format, expect, url='/'):
+ self.set_format(format)
+
+ assert self.get(url=url)['status'] == 200
+ assert self.wait_for_record(expect) is not None, 'found'
+
+ format = 'BLAH\t0123456789'
+ check_format(format, format)
+ check_format('$uri $status $uri $status', '/ 200 / 200')
+
+ def test_access_log_variables(self):
+ self.load('mirror')
+
+ # $time_local
+
+ self.set_format('$uri $time_local $uri')
+ assert self.get(url='/time_local')['status'] == 200
+ assert self.wait_for_record('/time_local') is not None, 'time log'
+ date = self.search_in_log(
+ r'^\/time_local (.*) \/time_local$', 'access.log'
+ )[1]
+ assert (
+ abs(
+ self.date_to_sec_epoch(date, '%d/%b/%Y:%X %z')
+ - time.mktime(time.localtime())
+ )
+ < 5
+ ), '$time_local'
+
+ # $request_line
+
+ self.set_format('$request_line')
+ assert self.get(url='/r_line')['status'] == 200
+ assert self.wait_for_record(r'^GET \/r_line HTTP\/1\.1$') is not None
+
+ # $body_bytes_sent
+
+ self.set_format('$uri $body_bytes_sent')
+ body = '0123456789' * 50
+ self.post(url='/bbs', body=body, read_timeout=1)
+ assert (
+ self.wait_for_record(r'^\/bbs ' + str(len(body)) + r'$') is not None
+ ), '$body_bytes_sent'
+
+ def test_access_log_incorrect(self, skip_alert):
+ skip_alert(r'failed to apply new conf')
+
+ assert 'error' in self.conf(
+ option.temp_dir + '/blah/access.log' 'access_log/path',
+ ), 'access_log path incorrect'
+
+ assert 'error' in self.conf(
+ {
+ 'path': option.temp_dir + '/access.log',
+ 'format': '$remote_add',
+ },
+ 'access_log',
+ ), 'access_log format incorrect'
diff --git a/test/test_asgi_application.py b/test/test_asgi_application.py
index 60fcffc1..34dfe18e 100644
--- a/test/test_asgi_application.py
+++ b/test/test_asgi_application.py
@@ -60,6 +60,16 @@ custom-header: BLAH
}, 'headers'
assert resp['body'] == body, 'body'
+ def test_asgi_application_unix(self, temp_dir):
+ self.load('empty')
+
+ addr = temp_dir + '/sock'
+ assert 'success' in self.conf(
+ {"unix:" + addr: {"pass": "applications/empty"}}, 'listeners'
+ )
+
+ assert self.get(sock_type='unix', addr=addr)['status'] == 200
+
def test_asgi_application_query_string(self):
self.load('query_string')
@@ -150,15 +160,7 @@ custom-header: BLAH
assert self.get()['status'] == 200, 'init'
body = '0123456789AB' * 1024 * 1024 # 12 Mb
- resp = self.post(
- headers={
- 'Host': 'localhost',
- 'Connection': 'close',
- 'Content-Type': 'text/html',
- },
- body=body,
- read_buffer_size=1024 * 1024,
- )
+ resp = self.post(body=body, read_buffer_size=1024 * 1024)
assert resp['body'] == body, 'keep-alive 1'
@@ -172,7 +174,6 @@ custom-header: BLAH
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
body=body,
@@ -182,15 +183,7 @@ custom-header: BLAH
assert resp['body'] == body, 'keep-alive 1'
body = '0123456789'
- resp = self.post(
- headers={
- 'Host': 'localhost',
- 'Connection': 'close',
- 'Content-Type': 'text/html',
- },
- sock=sock,
- body=body,
- )
+ resp = self.post(sock=sock, body=body)
assert resp['body'] == body, 'keep-alive 2'
@@ -208,7 +201,6 @@ custom-header: BLAH
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
body=body,
@@ -226,7 +218,6 @@ custom-header: BLAH
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
sock=socks[i],
@@ -239,15 +230,7 @@ custom-header: BLAH
self.load('mirror', processes=i + 1)
for i in range(conns):
- resp = self.post(
- headers={
- 'Host': 'localhost',
- 'Connection': 'close',
- 'Content-Type': 'text/html',
- },
- sock=socks[i],
- body=body,
- )
+ resp = self.post(sock=socks[i], body=body)
assert resp['body'] == body, 'keep-alive close'
@@ -264,7 +247,6 @@ custom-header: BLAH
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
body=body,
@@ -277,16 +259,7 @@ custom-header: BLAH
assert self.get()['status'] == 200, 'init'
- (resp, sock) = self.post(
- headers={
- 'Host': 'localhost',
- 'Connection': 'close',
- 'Content-Type': 'text/html',
- },
- start=True,
- sock=sock,
- body=body,
- )
+ (resp, sock) = self.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'
diff --git a/test/test_asgi_application_unix_abstract.py b/test/test_asgi_application_unix_abstract.py
new file mode 100644
index 00000000..c4ec812f
--- /dev/null
+++ b/test/test_asgi_application_unix_abstract.py
@@ -0,0 +1,23 @@
+from packaging import version
+from unit.applications.lang.python import TestApplicationPython
+
+
+class TestASGIApplicationUnixAbstract(TestApplicationPython):
+ prerequisites = {
+ 'modules': {
+ 'python': lambda v: version.parse(v) >= version.parse('3.5')
+ },
+ 'features': ['unix_abstract'],
+ }
+ load_module = 'asgi'
+
+ def test_asgi_application_unix_abstract(self):
+ self.load('empty')
+
+ addr = '\0sock'
+ assert 'success' in self.conf(
+ {"unix:@" + addr[1:]: {"pass": "applications/empty"}},
+ 'listeners',
+ )
+
+ assert self.get(sock_type='unix', addr=addr)['status'] == 200
diff --git a/test/test_client_ip.py b/test/test_client_ip.py
index 53e52201..50aa6afc 100644
--- a/test/test_client_ip.py
+++ b/test/test_client_ip.py
@@ -1,4 +1,5 @@
from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
class TestClientIP(TestApplicationPython):
@@ -15,15 +16,27 @@ class TestClientIP(TestApplicationPython):
"client_ip": options,
"pass": "applications/client_ip",
},
+ "unix:"
+ + option.temp_dir
+ + "/sock": {
+ "client_ip": options,
+ "pass": "applications/client_ip",
+ },
},
'listeners',
), 'listeners configure'
def get_xff(self, xff, sock_type='ipv4'):
- port = 7081 if sock_type == 'ipv4' else 7082
+ address = {
+ 'ipv4': ('127.0.0.1', 7081),
+ 'ipv6': ('::1', 7082),
+ 'unix': (option.temp_dir + '/sock', None),
+ }
+ (addr, port) = address[sock_type]
return self.get(
sock_type=sock_type,
+ addr=addr,
port=port,
headers={'Connection': 'close', 'X-Forwarded-For': xff},
)['body']
@@ -31,7 +44,7 @@ class TestClientIP(TestApplicationPython):
def setup_method(self):
self.load('client_ip')
- def test_settings_client_ip_single_ip(self):
+ def test_client_ip_single_ip(self):
self.client_ip(
{'header': 'X-Forwarded-For', 'source': '123.123.123.123'}
)
@@ -59,7 +72,7 @@ class TestClientIP(TestApplicationPython):
assert self.get_xff('1.1.1.1') == '127.0.0.1', 'bad source 3'
assert self.get_xff('1.1.1.1', 'ipv6') == '1.1.1.1', 'replace 2'
- def test_settings_client_ip_ipv4(self):
+ def test_client_ip_ipv4(self):
self.client_ip({'header': 'X-Forwarded-For', 'source': '127.0.0.1'})
assert (
@@ -72,7 +85,7 @@ class TestClientIP(TestApplicationPython):
self.get_xff(['8.8.8.8', '127.0.0.1, 10.0.1.1']) == '10.0.1.1'
), 'xff replace multi'
- def test_settings_client_ip_ipv6(self):
+ def test_client_ip_ipv6(self):
self.client_ip({'header': 'X-Forwarded-For', 'source': '::1'})
assert self.get_xff('1.1.1.1') == '127.0.0.1', 'bad source ipv4'
@@ -85,7 +98,19 @@ class TestClientIP(TestApplicationPython):
]:
assert self.get_xff(ip, 'ipv6') == ip, 'replace'
- def test_settings_client_ip_recursive(self):
+ def test_client_ip_unix(self, temp_dir):
+ self.client_ip({'header': 'X-Forwarded-For', 'source': 'unix'})
+
+ assert self.get_xff('1.1.1.1') == '127.0.0.1', 'bad source ipv4'
+ assert self.get_xff('1.1.1.1', 'ipv6') == '::1', 'bad source ipv6'
+
+ for ip in [
+ '1.1.1.1',
+ '::11.22.33.44',
+ ]:
+ assert self.get_xff(ip, 'unix') == ip, 'replace'
+
+ def test_client_ip_recursive(self):
self.client_ip(
{
'header': 'X-Forwarded-For',
@@ -118,20 +143,41 @@ class TestClientIP(TestApplicationPython):
== '2001:db8:3c4d:15::1a2f:1a2b'
), 'xff chain ipv6'
- def test_settings_client_ip_invalid(self):
- assert 'error' in self.conf(
- {
- "http": {
- "client_ip": {'header': 'X-Forwarded-For', 'source': []}
- }
- },
- 'settings',
- ), 'empty array source'
+ def test_client_ip_case_insensitive(self):
+ self.client_ip({'header': 'x-forwarded-for', 'source': '127.0.0.1'})
+
+ assert self.get_xff('1.1.1.1') == '1.1.1.1', 'case insensitive'
+
+ def test_client_ip_empty_source(self):
+ self.client_ip({'header': 'X-Forwarded-For', 'source': []})
+
+ assert self.get_xff('1.1.1.1') == '127.0.0.1', 'empty source'
+
+ def test_client_ip_invalid(self):
assert 'error' in self.conf(
{
- "http": {
- "client_ip": {'header': 'X-Forwarded-For', 'source': 'a'}
+ "127.0.0.1:7081": {
+ "client_ip": {"source": '127.0.0.1'},
+ "pass": "applications/client_ip",
}
},
- 'settings',
- ), 'empty source invalid'
+ 'listeners',
+ ), 'invalid header'
+
+ def check_invalid_source(source):
+ assert 'error' in self.conf(
+ {
+ "127.0.0.1:7081": {
+ "client_ip": {
+ "header": "X-Forwarded-For",
+ "source": source,
+ },
+ "pass": "applications/client_ip",
+ }
+ },
+ 'listeners',
+ ), 'invalid source'
+
+ check_invalid_source(None)
+ check_invalid_source('a')
+ check_invalid_source(['a'])
diff --git a/test/test_configuration.py b/test/test_configuration.py
index 4a9d9840..7c612db0 100644
--- a/test/test_configuration.py
+++ b/test/test_configuration.py
@@ -2,6 +2,7 @@ import socket
import pytest
from unit.control import TestControl
+from unit.option import option
class TestConfiguration(TestControl):
@@ -226,6 +227,15 @@ class TestConfiguration(TestControl):
{"*:7080": {"pass": "applications/app"}}, 'listeners'
), 'listeners no app'
+ def test_listeners_unix_abstract(self):
+ if option.system != 'Linux':
+ assert 'error' in self.try_addr("unix:@sock"), 'abstract at'
+
+ pytest.skip('not yet')
+
+ assert 'error' in self.try_addr("unix:\0soc"), 'abstract \0'
+ assert 'error' in self.try_addr("unix:\u0000soc"), 'abstract \0 unicode'
+
def test_listeners_addr(self):
assert 'success' in self.try_addr("*:7080"), 'wildcard'
assert 'success' in self.try_addr("127.0.0.1:7081"), 'explicit'
diff --git a/test/test_forwarded_header.py b/test/test_forwarded_header.py
new file mode 100644
index 00000000..eb2f25f8
--- /dev/null
+++ b/test/test_forwarded_header.py
@@ -0,0 +1,266 @@
+from unit.applications.lang.python import TestApplicationPython
+
+
+class TestForwardedHeader(TestApplicationPython):
+ prerequisites = {'modules': {'python': 'any'}}
+
+ def forwarded_header(self, forwarded):
+ assert 'success' in self.conf(
+ {
+ "127.0.0.1:7081": {
+ "forwarded": forwarded,
+ "pass": "applications/forwarded_header",
+ },
+ "[::1]:7082": {
+ "forwarded": forwarded,
+ "pass": "applications/forwarded_header",
+ },
+ },
+ 'listeners',
+ ), 'listeners configure'
+
+ def get_fwd(self, sock_type='ipv4', xff=None, xfp=None):
+ port = 7081 if sock_type == 'ipv4' else 7082
+
+ headers = {'Connection': 'close'}
+
+ if xff is not None:
+ headers['X-Forwarded-For'] = xff
+
+ if xfp is not None:
+ headers['X-Forwarded-Proto'] = xfp
+
+ return self.get(sock_type=sock_type, port=port, headers=headers)[
+ 'headers'
+ ]
+
+ def get_addr(self, *args, **kwargs):
+ return self.get_fwd(*args, **kwargs)['Remote-Addr']
+
+ def get_scheme(self, *args, **kwargs):
+ return self.get_fwd(*args, **kwargs)['Url-Scheme']
+
+ def setup_method(self):
+ self.load('forwarded_header')
+
+ def test_forwarded_header_single_ip(self):
+ self.forwarded_header(
+ {
+ 'client_ip': 'X-Forwarded-For',
+ 'protocol': 'X-Forwarded-Proto',
+ 'source': '123.123.123.123',
+ }
+ )
+
+ resp = self.get_fwd(xff='1.1.1.1', xfp='https')
+ assert resp['Remote-Addr'] == '127.0.0.1', 'both headers addr'
+ assert resp['Url-Scheme'] == 'http', 'both headers proto'
+
+ assert self.get_addr() == '127.0.0.1', 'ipv4 default addr'
+ assert self.get_addr('ipv6') == '::1', 'ipv6 default addr'
+ assert self.get_addr(xff='1.1.1.1') == '127.0.0.1', 'bad source'
+ assert self.get_addr(xff='blah') == '127.0.0.1', 'bad xff'
+ assert self.get_addr('ipv6', '1.1.1.1') == '::1', 'bad source ipv6'
+
+ assert self.get_scheme() == 'http', 'ipv4 default proto'
+ assert self.get_scheme('ipv6') == 'http', 'ipv6 default proto'
+ assert self.get_scheme(xfp='https') == 'http', 'bad proto'
+ assert self.get_scheme(xfp='blah') == 'http', 'bad xfp'
+ assert self.get_scheme('ipv6', xfp='https') == 'http', 'bad proto ipv6'
+
+ self.forwarded_header(
+ {
+ 'client_ip': 'X-Forwarded-For',
+ 'protocol': 'X-Forwarded-Proto',
+ 'source': '127.0.0.1',
+ }
+ )
+
+ resp = self.get_fwd(xff='1.1.1.1', xfp='https')
+ assert resp['Remote-Addr'] == '1.1.1.1', 'both headers addr 2'
+ assert resp['Url-Scheme'] == 'https', 'both headers proto 2'
+
+ assert self.get_addr() == '127.0.0.1', 'ipv4 default addr 2'
+ assert self.get_addr('ipv6') == '::1', 'ipv6 default addr 2'
+ assert self.get_addr(xff='1.1.1.1') == '1.1.1.1', 'xff replace'
+ assert self.get_addr('ipv6', '1.1.1.1') == '::1', 'bad source ipv6 2'
+
+ assert self.get_scheme() == 'http', 'ipv4 default proto 2'
+ assert self.get_scheme('ipv6') == 'http', 'ipv6 default proto 2'
+ assert self.get_scheme(xfp='https') == 'https', 'xfp replace'
+ assert self.get_scheme(xfp='on') == 'https', 'xfp replace 2'
+ assert (
+ self.get_scheme('ipv6', xfp='https') == 'http'
+ ), 'bad proto ipv6 2'
+
+ self.forwarded_header(
+ {
+ 'client_ip': 'X-Forwarded-For',
+ 'protocol': 'X-Forwarded-Proto',
+ 'source': '!127.0.0.1',
+ }
+ )
+
+ assert self.get_addr(xff='1.1.1.1') == '127.0.0.1', 'bad source 3'
+ assert self.get_addr('ipv6', '1.1.1.1') == '1.1.1.1', 'xff replace 2'
+ assert self.get_scheme(xfp='https') == 'http', 'bad proto 2'
+ assert self.get_scheme('ipv6', xfp='https') == 'https', 'xfp replace 3'
+
+ def test_forwarded_header_ipv4(self):
+ self.forwarded_header(
+ {
+ 'client_ip': 'X-Forwarded-For',
+ 'protocol': 'X-Forwarded-Proto',
+ 'source': '127.0.0.1',
+ }
+ )
+
+ assert (
+ self.get_addr(xff='8.8.8.8, 84.23.23.11') == '84.23.23.11'
+ ), 'xff replace'
+ assert (
+ self.get_addr(xff='8.8.8.8, 84.23.23.11, 127.0.0.1') == '127.0.0.1'
+ ), 'xff replace 2'
+ assert (
+ self.get_addr(xff=['8.8.8.8', '127.0.0.1, 10.0.1.1']) == '10.0.1.1'
+ ), 'xff replace multi'
+
+ assert self.get_scheme(xfp='http, https') == 'http', 'xfp replace'
+ assert (
+ self.get_scheme(xfp='http, https, http') == 'http'
+ ), 'xfp replace 2'
+ assert (
+ self.get_scheme(xfp=['http, https', 'http', 'https']) == 'http'
+ ), 'xfp replace multi'
+
+ def test_forwarded_header_ipv6(self):
+ self.forwarded_header(
+ {
+ 'client_ip': 'X-Forwarded-For',
+ 'protocol': 'X-Forwarded-Proto',
+ 'source': '::1',
+ }
+ )
+
+ assert self.get_addr(xff='1.1.1.1') == '127.0.0.1', 'bad source ipv4'
+
+ for ip in [
+ 'f607:7403:1e4b:6c66:33b2:843f:2517:da27',
+ '2001:db8:3c4d:15::1a2f:1a2b',
+ '2001::3c4d:15:1a2f:1a2b',
+ '::11.22.33.44',
+ ]:
+ assert self.get_addr('ipv6', ip) == ip, 'replace'
+
+ assert self.get_scheme(xfp='https') == 'http', 'bad source ipv4'
+
+ for proto in ['http', 'https']:
+ assert self.get_scheme('ipv6', xfp=proto) == proto, 'replace'
+
+ def test_forwarded_header_recursive(self):
+ self.forwarded_header(
+ {
+ 'client_ip': 'X-Forwarded-For',
+ 'recursive': True,
+ 'source': ['127.0.0.1', '10.50.0.17', '10.5.2.1'],
+ }
+ )
+
+ assert self.get_addr(xff='1.1.1.1') == '1.1.1.1', 'xff chain'
+ assert (
+ self.get_addr(xff='1.1.1.1, 10.5.2.1') == '1.1.1.1'
+ ), 'xff chain 2'
+ assert (
+ self.get_addr(xff='8.8.8.8, 1.1.1.1, 10.5.2.1') == '1.1.1.1'
+ ), 'xff chain 3'
+ assert (
+ self.get_addr(xff='10.50.0.17, 10.5.2.1, 10.5.2.1') == '10.50.0.17'
+ ), 'xff chain 4'
+ assert (
+ self.get_addr(xff=['8.8.8.8', '1.1.1.1, 127.0.0.1']) == '1.1.1.1'
+ ), 'xff replace multi'
+ assert (
+ self.get_addr(xff=['8.8.8.8', '1.1.1.1, 127.0.0.1', '10.5.2.1'])
+ == '1.1.1.1'
+ ), 'xff replace multi 2'
+ assert (
+ self.get_addr(xff=['10.5.2.1', '10.50.0.17, 1.1.1.1', '10.5.2.1'])
+ == '1.1.1.1'
+ ), 'xff replace multi 3'
+ assert (
+ self.get_addr(
+ xff='8.8.8.8, 2001:db8:3c4d:15::1a2f:1a2b, 127.0.0.1'
+ )
+ == '2001:db8:3c4d:15::1a2f:1a2b'
+ ), 'xff chain ipv6'
+
+ def test_forwarded_header_case_insensitive(self):
+ self.forwarded_header(
+ {
+ 'client_ip': 'x-forwarded-for',
+ 'protocol': 'x-forwarded-proto',
+ 'source': '127.0.0.1',
+ }
+ )
+
+ assert self.get_addr() == '127.0.0.1', 'ipv4 default addr'
+ assert self.get_addr('ipv6') == '::1', 'ipv6 default addr'
+ assert self.get_addr(xff='1.1.1.1') == '1.1.1.1', 'replace'
+
+ assert self.get_scheme() == 'http', 'ipv4 default proto'
+ assert self.get_scheme('ipv6') == 'http', 'ipv6 default proto'
+ assert self.get_scheme(xfp='https') == 'https', 'replace 1'
+ assert self.get_scheme(xfp='oN') == 'https', 'replace 2'
+
+ def test_forwarded_header_source_empty(self):
+ self.forwarded_header(
+ {
+ 'client_ip': 'X-Forwarded-For',
+ 'protocol': 'X-Forwarded-Proto',
+ 'source': [],
+ }
+ )
+
+ assert self.get_addr(xff='1.1.1.1') == '127.0.0.1', 'empty source xff'
+ assert self.get_scheme(xfp='https') == 'http', 'empty source xfp'
+
+ def test_forwarded_header_source_range(self):
+ self.forwarded_header(
+ {
+ 'client_ip': 'X-Forwarded-For',
+ 'protocol': 'X-Forwarded-Proto',
+ 'source': '127.0.0.0-127.0.0.1',
+ }
+ )
+
+ assert self.get_addr(xff='1.1.1.1') == '1.1.1.1', 'source range'
+ assert self.get_addr('ipv6', '1.1.1.1') == '::1', 'source range 2'
+
+ def test_forwarded_header_invalid(self):
+ assert 'error' in self.conf(
+ {
+ "127.0.0.1:7081": {
+ "forwarded": {"source": '127.0.0.1'},
+ "pass": "applications/forwarded_header",
+ }
+ },
+ 'listeners',
+ ), 'invalid forward'
+
+ def check_invalid_source(source):
+ assert 'error' in self.conf(
+ {
+ "127.0.0.1:7081": {
+ "forwarded": {
+ "client_ip": "X-Forwarded-For",
+ "source": source,
+ },
+ "pass": "applications/forwarded_header",
+ }
+ },
+ 'listeners',
+ ), 'invalid source'
+
+ check_invalid_source(None)
+ check_invalid_source('a')
+ check_invalid_source(['a'])
diff --git a/test/test_go_application.py b/test/test_go_application.py
index c8cf3e53..a746c6f4 100644
--- a/test/test_go_application.py
+++ b/test/test_go_application.py
@@ -95,7 +95,6 @@ class TestGoApplication(TestApplicationGo):
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
body=body,
@@ -105,16 +104,7 @@ class TestGoApplication(TestApplicationGo):
assert resp['body'] == body, 'keep-alive 1'
body = '0123456789'
- resp = self.post(
- headers={
- 'Host': 'localhost',
- 'Content-Type': 'text/html',
- 'Connection': 'close',
- },
- sock=sock,
- body=body,
- )
-
+ resp = self.post(sock=sock, body=body)
assert resp['body'] == body, 'keep-alive 2'
def test_go_application_cookies(self):
diff --git a/test/test_node_application.py b/test/test_node_application.py
index fc722582..c26c72d0 100644
--- a/test/test_node_application.py
+++ b/test/test_node_application.py
@@ -124,7 +124,6 @@ class TestNodeApplication(TestApplicationNode):
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
body=body,
@@ -134,15 +133,7 @@ class TestNodeApplication(TestApplicationNode):
assert resp['body'] == '0123456789' * 500, 'keep-alive 1'
body = '0123456789'
- resp = self.post(
- headers={
- 'Host': 'localhost',
- 'Connection': 'close',
- 'Content-Type': 'text/html',
- },
- sock=sock,
- body=body,
- )
+ resp = self.post(sock=sock, body=body)
assert resp['body'] == body, 'keep-alive 2'
diff --git a/test/test_php_application.py b/test/test_php_application.py
index 606ac723..f1dcc995 100644
--- a/test/test_php_application.py
+++ b/test/test_php_application.py
@@ -226,7 +226,6 @@ opcache.preload_user = %(user)s
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
body=body,
@@ -236,15 +235,7 @@ opcache.preload_user = %(user)s
assert resp['body'] == body, 'keep-alive 1'
body = '0123456789'
- resp = self.post(
- headers={
- 'Host': 'localhost',
- 'Connection': 'close',
- 'Content-Type': 'text/html',
- },
- sock=sock,
- body=body,
- )
+ resp = self.post(sock=sock, body=body)
assert resp['body'] == body, 'keep-alive 2'
diff --git a/test/test_proxy.py b/test/test_proxy.py
index 68ae2394..b0d471e4 100644
--- a/test/test_proxy.py
+++ b/test/test_proxy.py
@@ -370,7 +370,6 @@ Content-Length: 10
resp = self.post_http10(
headers={
'Host': 'localhost',
- 'Content-Type': 'text/html',
'Content-Length': str(len(body)),
'X-Parts': '2',
'X-Delay': '1',
@@ -384,7 +383,6 @@ Content-Length: 10
resp = self.post_http10(
headers={
'Host': 'localhost',
- 'Content-Type': 'text/html',
'Content-Length': str(len(body)),
'X-Parts': '2',
'X-Delay': '1',
@@ -403,7 +401,6 @@ Content-Length: 10
_, sock = self.post_http10(
headers={
'Host': 'localhost',
- 'Content-Type': 'text/html',
'Content-Length': '10000',
'X-Parts': '3',
'X-Delay': '1',
@@ -419,7 +416,6 @@ Content-Length: 10
_, sock = self.post_http10(
headers={
'Host': 'localhost',
- 'Content-Type': 'text/html',
'Content-Length': '10000',
'X-Parts': '3',
'X-Delay': '1',
diff --git a/test/test_python_application.py b/test/test_python_application.py
index befbd4d8..2ea9a22e 100644
--- a/test/test_python_application.py
+++ b/test/test_python_application.py
@@ -138,14 +138,7 @@ custom-header: BLAH
def test_python_application_ctx_iter_atexit(self):
self.load('ctx_iter_atexit')
- resp = self.post(
- headers={
- 'Host': 'localhost',
- 'Connection': 'close',
- 'Content-Type': 'text/html',
- },
- body='0123456789',
- )
+ resp = self.post(body='0123456789')
assert resp['status'] == 200, 'ctx iter status'
assert resp['body'] == '0123456789', 'ctx iter body'
@@ -166,7 +159,6 @@ custom-header: BLAH
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
body=body,
@@ -176,15 +168,7 @@ custom-header: BLAH
assert resp['body'] == body, 'keep-alive 1'
body = '0123456789'
- resp = self.post(
- headers={
- 'Host': 'localhost',
- 'Connection': 'close',
- 'Content-Type': 'text/html',
- },
- sock=sock,
- body=body,
- )
+ resp = self.post(sock=sock, body=body)
assert resp['body'] == body, 'keep-alive 2'
@@ -202,7 +186,6 @@ custom-header: BLAH
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
body=body,
@@ -220,7 +203,6 @@ custom-header: BLAH
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
sock=socks[i],
@@ -233,15 +215,7 @@ custom-header: BLAH
self.load('mirror', processes=i + 1)
for i in range(conns):
- resp = self.post(
- headers={
- 'Host': 'localhost',
- 'Connection': 'close',
- 'Content-Type': 'text/html',
- },
- sock=socks[i],
- body=body,
- )
+ resp = self.post(sock=socks[i], body=body)
assert resp['body'] == body, 'keep-alive close'
@@ -258,7 +232,6 @@ custom-header: BLAH
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
body=body,
@@ -271,16 +244,7 @@ custom-header: BLAH
assert self.get()['status'] == 200, 'init'
- (resp, sock) = self.post(
- headers={
- 'Host': 'localhost',
- 'Connection': 'close',
- 'Content-Type': 'text/html',
- },
- start=True,
- sock=sock,
- body=body,
- )
+ (resp, sock) = self.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'
diff --git a/test/test_routing.py b/test/test_routing.py
index fda429a4..3649b37c 100644
--- a/test/test_routing.py
+++ b/test/test_routing.py
@@ -1723,18 +1723,26 @@ class TestRouting(TestApplicationPython):
addr = temp_dir + '/sock'
assert 'success' in self.conf(
- {"unix:" + addr: {"pass": "routes"}}, 'listeners'
+ {
+ "127.0.0.1:7081": {"pass": "routes"},
+ "unix:" + addr: {"pass": "routes"},
+ },
+ 'listeners',
), 'source listeners configure'
self.route_match({"source": "!0.0.0.0/0"})
assert (
self.get(sock_type='unix', addr=addr)['status'] == 200
- ), 'unix ipv4'
+ ), 'unix ipv4 neg'
self.route_match({"source": "!::/0"})
assert (
self.get(sock_type='unix', addr=addr)['status'] == 200
- ), 'unix ipv6'
+ ), 'unix ipv6 neg'
+
+ self.route_match({"source": "unix"})
+ assert self.get(port=7081)['status'] == 404, 'unix ipv4'
+ assert self.get(sock_type='unix', addr=addr)['status'] == 200, 'unix'
def test_routes_match_source(self):
self.route_match({"source": "::"})
diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py
index 95c75d47..83af39be 100644
--- a/test/test_ruby_application.py
+++ b/test/test_ruby_application.py
@@ -44,7 +44,7 @@ class TestRubyApplication(TestApplicationRuby):
'Request-Method': 'POST',
'Request-Uri': '/',
'Http-Host': 'localhost',
- 'Script-Name': 'config.ru',
+ 'Script-Name': '',
'Server-Protocol': 'HTTP/1.1',
'Custom-Header': 'blah',
'Rack-Version': '13',
@@ -347,7 +347,6 @@ class TestRubyApplication(TestApplicationRuby):
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
body=body,
@@ -357,15 +356,7 @@ class TestRubyApplication(TestApplicationRuby):
assert resp['body'] == body, 'keep-alive 1'
body = '0123456789'
- resp = self.post(
- headers={
- 'Host': 'localhost',
- 'Connection': 'close',
- 'Content-Type': 'text/html',
- },
- sock=sock,
- body=body,
- )
+ resp = self.post(sock=sock, body=body)
assert resp['body'] == body, 'keep-alive 2'
diff --git a/test/test_static_chroot.py b/test/test_static_chroot.py
index b896a9b9..e33a181c 100644
--- a/test/test_static_chroot.py
+++ b/test/test_static_chroot.py
@@ -14,8 +14,7 @@ class TestStaticChroot(TestApplicationProto):
Path(temp_dir + '/assets/index.html').write_text('0123456789')
Path(temp_dir + '/assets/dir/file').write_text('blah')
- test = Path(__file__)
- self.test_path = '/' + test.parent.name + '/' + test.name
+ self.test_path = '/' + os.path.relpath(Path(__file__))
self._load_conf(
{
@@ -39,26 +38,18 @@ class TestStaticChroot(TestApplicationProto):
assert self.get(url='/dir/file')['status'] == 200, 'default chroot'
assert self.get(url='/index.html')['status'] == 200, 'default chroot 2'
- assert 'success' in self.conf(
- {
- "share": temp_dir + "/assets$uri",
- "chroot": temp_dir + "/assets/dir",
- },
- 'routes/0/action',
- ), 'configure chroot'
+ assert 'success' in self.update_action(
+ temp_dir + "/assets$uri", temp_dir + "/assets/dir"
+ )
assert self.get(url='/dir/file')['status'] == 200, 'chroot'
assert self.get(url='/index.html')['status'] == 403, 'chroot 403 2'
assert self.get(url='/file')['status'] == 403, 'chroot 403'
def test_share_chroot_array(self, temp_dir):
- assert 'success' in self.conf(
- {
- "share": ["/blah", temp_dir + "/assets$uri"],
- "chroot": temp_dir + "/assets/dir",
- },
- 'routes/0/action',
- ), 'configure share array'
+ assert 'success' in self.update_action(
+ ["/blah", temp_dir + "/assets$uri"], temp_dir + "/assets/dir"
+ )
assert self.get(url='/dir/file')['status'] == 200, 'share array'
assert 'success' in self.update_action(
@@ -66,13 +57,9 @@ class TestStaticChroot(TestApplicationProto):
)
assert self.get_custom('/dir/file', 'dir') == 200, 'array variable'
- assert 'success' in self.conf(
- {
- "share": ["/blah", "/blah2"],
- "chroot": temp_dir + "/assets/dir",
- },
- 'routes/0/action',
- ), 'configure share array bad'
+ assert 'success' in self.update_action(
+ ["/blah", "/blah2"], temp_dir + "/assets/dir"
+ )
assert self.get()['status'] != 200, 'share array bad'
def test_static_chroot_permission(self, is_su, temp_dir):
@@ -81,58 +68,30 @@ class TestStaticChroot(TestApplicationProto):
os.chmod(temp_dir + '/assets/dir', 0o100)
- assert 'success' in self.conf(
- {
- "share": temp_dir + "/assets$uri",
- "chroot": temp_dir + "/assets/dir",
- },
- 'routes/0/action',
+ assert 'success' in self.update_action(
+ temp_dir + "/assets$uri", temp_dir + "/assets/dir"
), 'configure chroot'
assert self.get(url='/dir/file')['status'] == 200, 'chroot'
def test_static_chroot_empty(self, temp_dir):
- assert 'success' in self.conf(
- {"share": temp_dir + "/assets$uri", "chroot": ""},
- 'routes/0/action',
- ), 'configure chroot empty absolute'
-
- assert (
- self.get(url='/dir/file')['status'] == 200
- ), 'chroot empty absolute'
-
- assert 'success' in self.conf(
- {"share": ".$uri", "chroot": ""},
- 'routes/0/action',
- ), 'configure chroot empty relative'
+ assert 'success' in self.update_action(temp_dir + "/assets$uri", "")
+ assert self.get(url='/dir/file')['status'] == 200, 'empty absolute'
- assert (
- self.get(url=self.test_path)['status'] == 200
- ), 'chroot empty relative'
+ assert 'success' in self.update_action(".$uri", "")
+ assert self.get(url=self.test_path)['status'] == 200, 'empty relative'
def test_static_chroot_relative(self, is_su, temp_dir):
if is_su:
pytest.skip('does\'t work under root')
- assert 'success' in self.conf(
- {"share": temp_dir + "/assets$uri", "chroot": "."},
- 'routes/0/action',
- ), 'configure relative chroot'
-
+ assert 'success' in self.update_action(temp_dir + "/assets$uri", ".")
assert self.get(url='/dir/file')['status'] == 403, 'relative chroot'
- assert 'success' in self.conf(
- {"share": ".$uri"},
- 'routes/0/action',
- ), 'configure relative share'
-
+ assert 'success' in self.conf({"share": ".$uri"}, 'routes/0/action')
assert self.get(url=self.test_path)['status'] == 200, 'relative share'
- assert 'success' in self.conf(
- {"share": ".$uri", "chroot": "."},
- 'routes/0/action',
- ), 'configure relative'
-
+ assert 'success' in self.update_action(".$uri", ".")
assert self.get(url=self.test_path)['status'] == 200, 'relative'
def test_static_chroot_variables(self, temp_dir):
@@ -150,64 +109,41 @@ class TestStaticChroot(TestApplicationProto):
assert 'success' in self.update_action(
temp_dir + '/assets/dir/$host', '$uri/assets/dir'
)
-
assert self.get_custom(temp_dir, 'file') == 200
def test_static_chroot_variables_buildin_mid(self, temp_dir):
assert 'success' in self.update_action(
temp_dir + '/assets$uri', temp_dir + '/$host/dir'
)
-
assert self.get_custom('/dir/file', 'assets') == 200
def test_static_chroot_variables_buildin_end(self, temp_dir):
assert 'success' in self.update_action(
temp_dir + '/assets$uri', temp_dir + '/assets/$host'
)
-
assert self.get_custom('/dir/file', 'dir') == 200
def test_static_chroot_slash(self, temp_dir):
- assert 'success' in self.conf(
- {
- "share": temp_dir + "/assets$uri",
- "chroot": temp_dir + "/assets/dir/",
- },
- 'routes/0/action',
- ), 'configure chroot slash end'
-
+ assert 'success' in self.update_action(
+ temp_dir + "/assets$uri", temp_dir + "/assets/dir/"
+ )
assert self.get(url='/dir/file')['status'] == 200, 'slash end'
assert self.get(url='/dirxfile')['status'] == 403, 'slash end bad'
- assert 'success' in self.conf(
- {
- "share": temp_dir + "/assets$uri",
- "chroot": temp_dir + "/assets/dir",
- },
- 'routes/0/action',
- ), 'configure chroot no slash end'
-
+ assert 'success' in self.update_action(
+ temp_dir + "/assets$uri", temp_dir + "/assets/dir"
+ )
assert self.get(url='/dir/file')['status'] == 200, 'no slash end'
- assert 'success' in self.conf(
- {
- "share": temp_dir + "/assets$uri",
- "chroot": temp_dir + "/assets/dir/",
- },
- 'routes/0/action',
- ), 'configure chroot slash end 2'
-
+ assert 'success' in self.update_action(
+ temp_dir + "/assets$uri", temp_dir + "/assets/dir/"
+ )
assert self.get(url='/dir/file')['status'] == 200, 'slash end 2'
assert self.get(url='/dirxfile')['status'] == 403, 'slash end 2 bad'
- assert 'success' in self.conf(
- {
- "share": temp_dir + "///assets/////$uri",
- "chroot": temp_dir + "//assets////dir///",
- },
- 'routes/0/action',
- ), 'configure chroot multiple slashes'
-
+ assert 'success' in self.update_action(
+ temp_dir + "///assets/////$uri", temp_dir + "//assets////dir///"
+ )
assert self.get(url='/dir/file')['status'] == 200, 'multiple slashes'
def test_static_chroot_invalid(self, temp_dir):
diff --git a/test/test_status.py b/test/test_status.py
new file mode 100644
index 00000000..214072d4
--- /dev/null
+++ b/test/test_status.py
@@ -0,0 +1,223 @@
+import time
+
+import pytest
+from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
+from unit.status import Status
+
+
+class TestStatus(TestApplicationPython):
+ prerequisites = {'modules': {'python': 'any'}}
+
+ def test_status(self):
+ assert 'error' in self.conf_delete('/status'), 'DELETE method'
+
+ def test_status_requests(self, skip_alert):
+ skip_alert(r'Python failed to import module "blah"')
+
+ assert 'success' in self.conf(
+ {
+ "listeners": {
+ "*:7080": {"pass": "routes"},
+ "*:7081": {"pass": "applications/empty"},
+ "*:7082": {"pass": "applications/blah"},
+ },
+ "routes": [{"action": {"return": 200}}],
+ "applications": {
+ "empty": {
+ "type": self.get_application_type(),
+ "processes": {"spare": 0},
+ "path": option.test_dir + '/python/empty',
+ "working_directory": option.test_dir + '/python/empty',
+ "module": "wsgi",
+ },
+ "blah": {
+ "type": self.get_application_type(),
+ "processes": {"spare": 0},
+ "module": "blah",
+ },
+ },
+ },
+ )
+
+ Status.init()
+
+ assert self.get()['status'] == 200
+ assert Status.get('/requests/total') == 1, '2xx'
+
+ assert self.get(port=7081)['status'] == 200
+ assert Status.get('/requests/total') == 2, '2xx app'
+
+ assert (
+ self.get(headers={'Host': '/', 'Connection': 'close'})['status']
+ == 400
+ )
+ assert Status.get('/requests/total') == 3, '4xx'
+
+ assert self.get(port=7082)['status'] == 503
+ assert Status.get('/requests/total') == 4, '5xx'
+
+ self.http(
+ b"""GET / HTTP/1.1
+Host: localhost
+
+GET / HTTP/1.1
+Host: localhost
+Connection: close
+
+""",
+ raw=True,
+ )
+ assert Status.get('/requests/total') == 6, 'pipeline'
+
+ (_, sock) = self.get(port=7081, no_recv=True, start=True)
+
+ time.sleep(1)
+
+ assert Status.get('/requests/total') == 7, 'no receive'
+
+ sock.close()
+
+ def test_status_connections(self):
+ def check_connections(accepted, active, idle, closed):
+ Status.get('/connections') == {
+ 'accepted': accepted,
+ 'active': active,
+ 'idle': idle,
+ 'closed': closed,
+ }
+
+ assert 'success' in self.conf(
+ {
+ "listeners": {
+ "*:7080": {"pass": "routes"},
+ "*:7081": {"pass": "applications/delayed"},
+ },
+ "routes": [{"action": {"return": 200}}],
+ "applications": {
+ "delayed": {
+ "type": self.get_application_type(),
+ "processes": {"spare": 0},
+ "path": option.test_dir + "/python/delayed",
+ "working_directory": option.test_dir
+ + "/python/delayed",
+ "module": "wsgi",
+ },
+ },
+ },
+ )
+
+ Status.init()
+
+ # accepted, closed
+
+ assert self.get()['status'] == 200
+ check_connections(1, 0, 0, 1)
+
+ # idle
+
+ _, sock = self.http(b'', start=True, raw=True, no_recv=True)
+ check_connections(2, 0, 1, 1)
+
+ self.get(sock=sock)
+ check_connections(2, 0, 0, 2)
+
+ # active
+
+ (_, sock) = self.get(
+ headers={
+ 'Host': 'localhost',
+ 'X-Delay': '2',
+ 'Connection': 'close',
+ },
+ port=7081,
+ start=True,
+ read_timeout=1,
+ )
+ check_connections(3, 1, 0, 2)
+
+ self.get(sock=sock)
+ check_connections(3, 0, 0, 3)
+
+ def test_status_applications(self):
+ def check_applications(expert):
+ apps = list(self.conf_get('/status/applications').keys()).sort()
+ assert apps == expert.sort()
+
+ def check_application(name, running, starting, idle, active):
+ Status.get('/applications/' + name) == {
+ 'processes': {
+ 'running': running,
+ 'starting': starting,
+ 'idle': idle,
+ },
+ 'requests': {'active': active},
+ }
+
+ self.load('delayed')
+ Status.init()
+
+ check_applications(['delayed'])
+ check_application('delayed', 0, 0, 0, 0)
+
+ # idle
+
+ assert self.get()['status'] == 200
+ check_application('delayed', 1, 0, 1, 0)
+
+ assert 'success' in self.conf('4', 'applications/delayed/processes')
+ check_application('delayed', 4, 0, 4, 0)
+
+ # active
+
+ (_, sock) = self.get(
+ headers={
+ 'Host': 'localhost',
+ 'X-Delay': '2',
+ 'Connection': 'close',
+ },
+ start=True,
+ read_timeout=1,
+ )
+ check_application('delayed', 4, 0, 3, 1)
+ sock.close()
+
+ # starting
+
+ assert 'success' in self.conf(
+ {
+ "listeners": {
+ "*:7080": {"pass": "applications/restart"},
+ "*:7081": {"pass": "applications/delayed"},
+ },
+ "routes": [],
+ "applications": {
+ "restart": {
+ "type": self.get_application_type(),
+ "processes": {"spare": 0},
+ "path": option.test_dir + "/python/restart",
+ "working_directory": option.test_dir
+ + "/python/restart",
+ "module": "longstart",
+ },
+ "delayed": {
+ "type": self.get_application_type(),
+ "processes": {"spare": 0},
+ "path": option.test_dir + "/python/delayed",
+ "working_directory": option.test_dir
+ + "/python/delayed",
+ "module": "wsgi",
+ },
+ },
+ },
+ )
+ Status.init()
+
+ check_applications(['delayed', 'restart'])
+ check_application('restart', 0, 0, 0, 0)
+ check_application('delayed', 0, 0, 0, 0)
+
+ self.get(read_timeout=1)
+
+ check_application('restart', 0, 1, 0, 1)
+ check_application('delayed', 0, 0, 0, 0)
diff --git a/test/test_status_tls.py b/test/test_status_tls.py
new file mode 100644
index 00000000..dc3d68da
--- /dev/null
+++ b/test/test_status_tls.py
@@ -0,0 +1,30 @@
+from unit.applications.tls import TestApplicationTLS
+from unit.status import Status
+
+
+class TestStatusTLS(TestApplicationTLS):
+ prerequisites = {'modules': {'openssl': 'any'}}
+
+ def test_status_tls_requests(self):
+ self.certificate()
+
+ assert 'success' in self.conf(
+ {
+ "listeners": {
+ "*:7080": {"pass": "routes"},
+ "*:7081": {
+ "pass": "routes",
+ "tls": {"certificate": "default"},
+ },
+ },
+ "routes": [{"action": {"return": 200}}],
+ "applications": {},
+ }
+ )
+
+ Status.init()
+
+ assert self.get()['status'] == 200
+ assert self.get_ssl(port=7081)['status'] == 200
+
+ assert Status.get('/requests/total') == 2
diff --git a/test/test_tls.py b/test/test_tls.py
index 56ee8298..d4edcbd3 100644
--- a/test/test_tls.py
+++ b/test/test_tls.py
@@ -1,5 +1,4 @@
import io
-import re
import ssl
import subprocess
import time
@@ -13,7 +12,7 @@ class TestTLS(TestApplicationTLS):
prerequisites = {'modules': {'python': 'any', 'openssl': 'any'}}
def openssl_date_to_sec_epoch(self, date):
- return self.date_to_sec_epoch(date, '%b %d %H:%M:%S %Y %Z')
+ return self.date_to_sec_epoch(date, '%b %d %X %Y %Z')
def add_tls(self, application='empty', cert='default', port=7080):
assert 'success' in self.conf(
@@ -506,7 +505,6 @@ basicConstraints = critical,CA:TRUE"""
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
body='0123456789',
@@ -519,7 +517,6 @@ basicConstraints = critical,CA:TRUE"""
headers={
'Host': 'localhost',
'Connection': 'close',
- 'Content-Type': 'text/html',
},
sock=sock,
body='0123456789',
@@ -571,9 +568,7 @@ basicConstraints = critical,CA:TRUE"""
assert 'success' in self.conf_delete('/certificates/default')
try:
- resp = self.get_ssl(
- headers={'Host': 'localhost', 'Connection': 'close'}, sock=sock
- )
+ resp = self.get_ssl(sock=sock)
except KeyboardInterrupt:
raise
@@ -606,7 +601,6 @@ basicConstraints = critical,CA:TRUE"""
headers={
'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
},
start=True,
body='0123456789',
@@ -617,23 +611,13 @@ basicConstraints = critical,CA:TRUE"""
subprocess.check_output(['kill', '-9', app_id])
- skip_alert(r'process .* %s.* exited on signal 9' % app_id)
+ skip_alert(r'process %s exited on signal 9' % app_id)
self.wait_for_record(
- re.compile(
- r' (?!' + app_id + r'#)(\d+)#\d+ "mirror" application started'
- )
+ r' (?!' + app_id + r'#)(\d+)#\d+ "mirror" application started'
)
- resp = self.post_ssl(
- headers={
- 'Host': 'localhost',
- 'Connection': 'close',
- 'Content-Type': 'text/html',
- },
- sock=sock,
- body='0123456789',
- )
+ resp = self.post_ssl(sock=sock, body='0123456789')
assert resp['status'] == 200, 'application respawn status'
assert resp['body'] == '0123456789', 'application respawn body'
diff --git a/test/test_tls_sni.py b/test/test_tls_sni.py
index dbd5d900..44cc21e1 100644
--- a/test/test_tls_sni.py
+++ b/test/test_tls_sni.py
@@ -18,7 +18,7 @@ class TestTLSSNI(TestApplicationTLS):
)
def openssl_date_to_sec_epoch(self, date):
- return self.date_to_sec_epoch(date, '%b %d %H:%M:%S %Y %Z')
+ return self.date_to_sec_epoch(date, '%b %d %X %Y %Z')
def add_tls(self, cert='default'):
assert 'success' in self.conf(
diff --git a/test/test_unix_abstract.py b/test/test_unix_abstract.py
new file mode 100644
index 00000000..195b0aa7
--- /dev/null
+++ b/test/test_unix_abstract.py
@@ -0,0 +1,109 @@
+from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
+
+
+class TestUnixAbstract(TestApplicationPython):
+ prerequisites = {
+ 'modules': {'python': 'any'},
+ 'features': ['unix_abstract'],
+ }
+
+ def test_unix_abstract_source(self):
+ addr = '\0sock'
+
+ def source(source):
+ assert 'success' in self.conf(
+ '"' + source + '"', 'routes/0/match/source'
+ )
+
+ assert 'success' in self.conf(
+ {
+ "listeners": {
+ "127.0.0.1:7080": {"pass": "routes"},
+ "unix:@" + addr[1:]: {"pass": "routes"},
+ },
+ "routes": [
+ {
+ "match": {"source": "!0.0.0.0/0"},
+ "action": {"return": 200},
+ }
+ ],
+ "applications": {},
+ }
+ )
+
+ assert (
+ self.get(sock_type='unix', addr=addr)['status'] == 200
+ ), 'neg ipv4'
+
+ source("!::/0")
+ assert (
+ self.get(sock_type='unix', addr=addr)['status'] == 200
+ ), 'neg ipv6'
+
+ source("unix")
+ assert self.get()['status'] == 404, 'ipv4'
+ assert self.get(sock_type='unix', addr=addr)['status'] == 200, 'unix'
+
+ def test_unix_abstract_client_ip(self):
+ def get_xff(xff, sock_type='ipv4'):
+ address = {
+ 'ipv4': ('127.0.0.1', 7080),
+ 'ipv6': ('::1', 7081),
+ 'unix': ('\0sock', None),
+ }
+ (addr, port) = address[sock_type]
+
+ return self.get(
+ sock_type=sock_type,
+ addr=addr,
+ port=port,
+ headers={'Connection': 'close', 'X-Forwarded-For': xff},
+ )['body']
+
+ assert 'success' in self.conf(
+ {
+ "listeners": {
+ "127.0.0.1:7080": {
+ "client_ip": {
+ "header": "X-Forwarded-For",
+ "source": "unix",
+ },
+ "pass": "applications/client_ip",
+ },
+ "[::1]:7081": {
+ "client_ip": {
+ "header": "X-Forwarded-For",
+ "source": "unix",
+ },
+ "pass": "applications/client_ip",
+ },
+ "unix:@sock": {
+ "client_ip": {
+ "header": "X-Forwarded-For",
+ "source": "unix",
+ },
+ "pass": "applications/client_ip",
+ },
+ },
+ "applications": {
+ "client_ip": {
+ "type": self.get_application_type(),
+ "processes": {"spare": 0},
+ "path": option.test_dir + "/python/client_ip",
+ "working_directory": option.test_dir
+ + "/python/client_ip",
+ "module": "wsgi",
+ }
+ },
+ }
+ )
+
+ assert get_xff('1.1.1.1') == '127.0.0.1', 'bad source ipv4'
+ assert get_xff('1.1.1.1', 'ipv6') == '::1', 'bad source ipv6'
+
+ for ip in [
+ '1.1.1.1',
+ '::11.22.33.44',
+ ]:
+ assert get_xff(ip, 'unix') == ip, 'replace'
diff --git a/test/test_variables.py b/test/test_variables.py
index 71553685..2ddfdc0a 100644
--- a/test/test_variables.py
+++ b/test/test_variables.py
@@ -18,6 +18,11 @@ class TestVariables(TestApplicationProto):
"GETGET": [{"action": {"return": 207}}],
"localhost": [{"action": {"return": 208}}],
"9?q#a": [{"action": {"return": 209}}],
+ "blah": [{"action": {"return": 210}}],
+ "127.0.0.1": [{"action": {"return": 211}}],
+ "::1": [{"action": {"return": 212}}],
+ "referer-value": [{"action": {"return": 213}}],
+ "MSIE": [{"action": {"return": 214}}],
},
},
), 'configure routes'
@@ -62,6 +67,74 @@ class TestVariables(TestApplicationProto):
check_host('www.localhost', 404)
check_host('localhost1', 404)
+ def test_variables_remote_addr(self):
+ self.conf_routes("\"routes/$remote_addr\"")
+ assert self.get()['status'] == 211
+
+ assert 'success' in self.conf(
+ {"[::1]:7080": {"pass": "routes/$remote_addr"}}, 'listeners'
+ )
+ assert self.get(sock_type='ipv6')['status'] == 212
+
+ def test_variables_header_referer(self):
+ self.conf_routes("\"routes/$header_referer\"")
+
+ def check_referer(referer, status=213):
+ assert (
+ self.get(
+ headers={
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ 'Referer': referer,
+ }
+ )['status']
+ == status
+ )
+
+ check_referer('referer-value')
+ check_referer('', 404)
+ check_referer('no', 404)
+
+ def test_variables_header_user_agent(self):
+ self.conf_routes("\"routes/$header_user_agent\"")
+
+ def check_user_agent(user_agent, status=214):
+ assert (
+ self.get(
+ headers={
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ 'User-Agent': user_agent,
+ }
+ )['status']
+ == status
+ )
+
+ check_user_agent('MSIE')
+ check_user_agent('', 404)
+ check_user_agent('no', 404)
+
+ def test_variables_dollar(self):
+ assert 'success' in self.conf(
+ {
+ "listeners": {"*:7080": {"pass": "routes"}},
+ "routes": [{"action": {"return": 301}}],
+ }
+ )
+
+ def check_dollar(location, expect):
+ assert 'success' in self.conf(
+ '"' + location + '"',
+ 'routes/0/action/location',
+ )
+ assert self.get()['headers']['Location'] == expect
+
+ check_dollar(
+ 'https://${host}${uri}path${dollar}dollar',
+ 'https://localhost/path$dollar',
+ )
+ check_dollar('path$dollar${dollar}', 'path$$')
+
def test_variables_many(self):
self.conf_routes("\"routes$uri$method\"")
assert self.get(url='/5')['status'] == 206, 'many'
@@ -124,6 +197,80 @@ class TestVariables(TestApplicationProto):
update_pass("applications")
assert self.get(url='/3')['status'] == 404
+ def test_variables_dynamic(self):
+ self.conf_routes("\"routes/$header_foo$arg_foo$cookie_foo\"")
+
+ self.get(
+ url='/?foo=h',
+ headers={'Foo': 'b', 'Cookie': 'foo=la', 'Connection': 'close'},
+ )['status'] = 210
+
+ def test_variables_dynamic_headers(self):
+ def check_header(header, status=210):
+ assert (
+ self.get(headers={header: "blah", 'Connection': 'close'})[
+ 'status'
+ ]
+ == status
+ )
+
+ self.conf_routes("\"routes/$header_foo_bar\"")
+ check_header('foo-bar')
+ check_header('Foo-Bar')
+ check_header('foo_bar', 404)
+ check_header('Foo', 404)
+ check_header('Bar', 404)
+ check_header('foobar', 404)
+
+ self.conf_routes("\"routes/$header_Foo_Bar\"")
+ check_header('Foo-Bar')
+ check_header('foo-bar')
+ check_header('foo_bar', 404)
+ check_header('foobar', 404)
+
+ self.conf_routes("\"routes/$header_foo-bar\"")
+ check_header('foo_bar', 404)
+
+ def test_variables_dynamic_arguments(self):
+ self.conf_routes("\"routes/$arg_foo_bar\"")
+ assert self.get(url='/?foo_bar=blah')['status'] == 210
+ assert self.get(url='/?foo_b%61r=blah')['status'] == 210
+ assert self.get(url='/?bar&foo_bar=blah&foo')['status'] == 210
+ assert self.get(url='/?Foo_bar=blah')['status'] == 404
+ assert self.get(url='/?foo-bar=blah')['status'] == 404
+ assert self.get()['status'] == 404
+ assert self.get(url='/?foo_bar=')['status'] == 404
+ assert self.get(url='/?foo_bar=l&foo_bar=blah')['status'] == 210
+ assert self.get(url='/?foo_bar=blah&foo_bar=l')['status'] == 404
+
+ self.conf_routes("\"routes/$arg_foo_b%61r\"")
+ assert self.get(url='/?foo_b=blah')['status'] == 404
+ assert self.get(url='/?foo_bar=blah')['status'] == 404
+
+ self.conf_routes("\"routes/$arg_f!~\"")
+ assert self.get(url='/?f=blah')['status'] == 404
+ assert self.get(url='/?f!~=blah')['status'] == 404
+
+ def test_variables_dynamic_cookies(self):
+ def check_cookie(cookie, status=210):
+ assert (
+ self.get(
+ headers={
+ 'Host': 'localhost',
+ 'Cookie': cookie,
+ 'Connection': 'close',
+ },
+ )['status']
+ == status
+ ), 'match cookie'
+
+ self.conf_routes("\"routes/$cookie_foo_bar\"")
+ check_cookie('foo_bar=blah', 210)
+ check_cookie('fOo_bar=blah', 404)
+ assert self.get()['status'] == 404
+ check_cookie('foo_bar', 404)
+ check_cookie('foo_bar=', 404)
+
def test_variables_invalid(self):
def check_variables(routes):
assert 'error' in self.conf(
@@ -137,3 +284,10 @@ class TestVariables(TestApplicationProto):
check_variables("\"routes$uriblah\"")
check_variables("\"routes${uri\"")
check_variables("\"routes${{uri}\"")
+ check_variables("\"routes$ar\"")
+ check_variables("\"routes$arg\"")
+ check_variables("\"routes$arg_\"")
+ check_variables("\"routes$cookie\"")
+ check_variables("\"routes$cookie_\"")
+ check_variables("\"routes$header\"")
+ check_variables("\"routes$header_\"")
diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py
index 04af26e1..3db955f3 100644
--- a/test/unit/applications/lang/go.py
+++ b/test/unit/applications/lang/go.py
@@ -9,6 +9,11 @@ from unit.option import option
class TestApplicationGo(TestApplicationProto):
@staticmethod
def prepare_env(script, name='app', static=False):
+ try:
+ subprocess.check_output(['which', 'go'])
+ except subprocess.CalledProcessError:
+ return None
+
temp_dir = option.temp_dir + '/go/'
if not os.path.exists(temp_dir):
diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py
index cd8672ba..f04ee408 100644
--- a/test/unit/applications/proto.py
+++ b/test/unit/applications/proto.py
@@ -13,21 +13,21 @@ class TestApplicationProto(TestControl):
def sec_epoch(self):
return time.mktime(time.gmtime())
- def date_to_sec_epoch(self, date, template='%a, %d %b %Y %H:%M:%S %Z'):
+ def date_to_sec_epoch(self, date, template='%a, %d %b %Y %X %Z'):
return time.mktime(time.strptime(date, template))
- def findall(self, pattern, name='unit.log'):
+ def findall(self, pattern, name='unit.log', flags=re.M):
with Log.open(name) as f:
- return re.findall(pattern, f.read())
+ return re.findall(pattern, f.read(), flags)
- def search_in_log(self, pattern, name='unit.log'):
+ def search_in_log(self, pattern, name='unit.log', flags=re.M):
with Log.open(name) as f:
- return re.search(pattern, f.read())
+ return re.search(pattern, f.read(), flags)
- def wait_for_record(self, pattern, name='unit.log', wait=150):
+ def wait_for_record(self, pattern, name='unit.log', wait=150, flags=re.M):
with Log.open(name) as f:
for i in range(wait):
- found = re.search(pattern, f.read())
+ found = re.search(pattern, f.read(), flags)
if found is not None:
break
diff --git a/test/unit/check/unix_abstract.py b/test/unit/check/unix_abstract.py
new file mode 100644
index 00000000..5d1f629e
--- /dev/null
+++ b/test/unit/check/unix_abstract.py
@@ -0,0 +1,25 @@
+import json
+
+from unit.http import TestHTTP
+from unit.option import option
+
+http = TestHTTP()
+
+
+def check_unix_abstract():
+ available = option.available
+
+ resp = http.put(
+ url='/config',
+ sock_type='unix',
+ addr=option.temp_dir + '/control.unit.sock',
+ body=json.dumps(
+ {
+ "listeners": {"unix:@sock": {"pass": "routes"}},
+ "routes": [],
+ }
+ ),
+ )
+
+ if 'success' in resp['body']:
+ available['features']['unix_abstract'] = True
diff --git a/test/unit/http.py b/test/unit/http.py
index b4a1a17b..b29667c9 100644
--- a/test/unit/http.py
+++ b/test/unit/http.py
@@ -51,7 +51,7 @@ class TestHTTP:
connect_args = addr if sock_type == 'unix' else (addr, port)
try:
sock.connect(connect_args)
- except ConnectionRefusedError:
+ except (ConnectionRefusedError, FileNotFoundError):
sock.close()
pytest.fail('Client can\'t connect to the server.')
@@ -209,9 +209,7 @@ class TestHTTP:
return {}
headers_text, body = m.group(1), m.group(2)
-
- p = re.compile('(.*?)\x0d\x0a?', re.M | re.S)
- headers_lines = p.findall(headers_text)
+ headers_lines = re.findall('(.*?)\x0d\x0a?', headers_text, re.M | re.S)
status = re.search(
r'^HTTP\/\d\.\d\s(\d+)|$', headers_lines.pop(0)
diff --git a/test/unit/status.py b/test/unit/status.py
new file mode 100644
index 00000000..17416f17
--- /dev/null
+++ b/test/unit/status.py
@@ -0,0 +1,45 @@
+from unit.control import TestControl
+
+
+class Status:
+ _status = None
+ control = TestControl()
+
+ def _check_zeros():
+ assert Status.control.conf_get('/status') == {
+ 'connections': {
+ 'accepted': 0,
+ 'active': 0,
+ 'idle': 0,
+ 'closed': 0,
+ },
+ 'requests': {'total': 0},
+ 'applications': {},
+ }
+
+ def init(status=None):
+ Status._status = (
+ status if status is not None else Status.control.conf_get('/status')
+ )
+
+ def diff():
+ def find_diffs(d1, d2):
+ if isinstance(d1, dict) and isinstance(d2, dict):
+ return {
+ k: find_diffs(d1.get(k, 0), d2.get(k, 0))
+ for k in d1
+ if k in d2
+ }
+ else:
+ return d1 - d2
+
+ return find_diffs(Status.control.conf_get('/status'), Status._status)
+
+ def get(path='/'):
+ path = path.split('/')[1:]
+ diff = Status.diff()
+
+ for p in path:
+ diff = diff[p]
+
+ return diff