From e8577afc2126001db03d4b8ac1dd8670a2504322 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Fri, 26 Mar 2021 21:06:23 +0000 Subject: Tests: SNI. --- test/unit/applications/tls.py | 21 ++++++++++++++++++--- test/unit/http.py | 3 ++- 2 files changed, 20 insertions(+), 4 deletions(-) (limited to 'test/unit') diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index b0cd5abb..490ae916 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -63,19 +63,34 @@ class TestApplicationTLS(TestApplicationProto): return ssl.get_server_certificate(addr, ssl_version=ssl_version) - def openssl_conf(self): + def openssl_conf(self, rewrite=False, alt_names=[]): conf_path = option.temp_dir + '/openssl.conf' - if os.path.exists(conf_path): + if not rewrite and os.path.exists(conf_path): return + # Generates alt_names section with dns names + a_names = "[alt_names]\n" + for i, k in enumerate(alt_names, 1): + a_names += "DNS.%d = %s\n" % (i, k) + + # Generates section for sign request extension + a_sec = """req_extensions = myca_req_extensions + +[ myca_req_extensions ] +subjectAltName = @alt_names + +{a_names}""".format(a_names=a_names) + with open(conf_path, 'w') as f: f.write( """[ req ] default_bits = 2048 encrypt_key = no distinguished_name = req_distinguished_name -[ req_distinguished_name ]""" + +{a_sec} +[ req_distinguished_name ]""".format(a_sec=a_sec if alt_names else "") ) def load(self, script, name=None): diff --git a/test/unit/http.py b/test/unit/http.py index 57e6ed3a..7706fe05 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -44,7 +44,8 @@ class TestHTTP(): sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) if 'wrapper' in kwargs: - sock = kwargs['wrapper'](sock) + server_hostname = headers.get('Host', 'localhost') + sock = kwargs['wrapper'](sock, server_hostname=server_hostname) connect_args = addr if sock_type == 'unix' else (addr, port) try: -- cgit From 6c97a1a069f0ae8cf683c8364fb7f9dabc5e89cb Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 5 Apr 2021 14:03:05 +0100 Subject: Tests: style. --- test/unit/applications/tls.py | 22 +++++++++++++++------- test/unit/applications/websockets.py | 18 +++++++----------- test/unit/check/isolation.py | 4 ++-- test/unit/http.py | 23 ++++++++++++++--------- test/unit/option.py | 3 ++- 5 files changed, 40 insertions(+), 30 deletions(-) (limited to 'test/unit') diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index 490ae916..95eeac55 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -21,10 +21,14 @@ class TestApplicationTLS(TestApplicationProto): 'req', '-x509', '-new', - '-subj', '/CN=' + name + '/', - '-config', option.temp_dir + '/openssl.conf', - '-out', option.temp_dir + '/' + name + '.crt', - '-keyout', option.temp_dir + '/' + name + '.key', + '-subj', + '/CN=' + name + '/', + '-config', + option.temp_dir + '/openssl.conf', + '-out', + option.temp_dir + '/' + name + '.crt', + '-keyout', + option.temp_dir + '/' + name + '.key', ], stderr=subprocess.STDOUT, ) @@ -75,12 +79,14 @@ class TestApplicationTLS(TestApplicationProto): a_names += "DNS.%d = %s\n" % (i, k) # Generates section for sign request extension - a_sec = """req_extensions = myca_req_extensions + a_sec = """req_extensions = myca_req_extensions [ myca_req_extensions ] subjectAltName = @alt_names -{a_names}""".format(a_names=a_names) +{a_names}""".format( + a_names=a_names + ) with open(conf_path, 'w') as f: f.write( @@ -90,7 +96,9 @@ encrypt_key = no distinguished_name = req_distinguished_name {a_sec} -[ req_distinguished_name ]""".format(a_sec=a_sec if alt_names else "") +[ req_distinguished_name ]""".format( + a_sec=a_sec if alt_names else "" + ) ) def load(self, script, name=None): diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py index cc720a98..aa83339c 100644 --- a/test/unit/applications/websockets.py +++ b/test/unit/applications/websockets.py @@ -43,11 +43,7 @@ class TestApplicationWebsocket(TestApplicationProto): 'Sec-WebSocket-Version': 13, } - _, sock = self.get( - headers=headers, - no_recv=True, - start=True, - ) + _, sock = self.get(headers=headers, no_recv=True, start=True,) resp = '' while True: @@ -57,7 +53,7 @@ class TestApplicationWebsocket(TestApplicationProto): resp += sock.recv(4096).decode() - if (resp.startswith('HTTP/') and '\r\n\r\n' in resp): + if resp.startswith('HTTP/') and '\r\n\r\n' in resp: resp = self._resp_to_dict(resp) break @@ -90,8 +86,8 @@ class TestApplicationWebsocket(TestApplicationProto): frame = {} - head1, = struct.unpack('!B', recv_bytes(sock, 1)) - head2, = struct.unpack('!B', recv_bytes(sock, 1)) + (head1,) = struct.unpack('!B', recv_bytes(sock, 1)) + (head2,) = struct.unpack('!B', recv_bytes(sock, 1)) frame['fin'] = bool(head1 & 0b10000000) frame['rsv1'] = bool(head1 & 0b01000000) @@ -103,10 +99,10 @@ class TestApplicationWebsocket(TestApplicationProto): length = head2 & 0b01111111 if length == 126: data = recv_bytes(sock, 2) - length, = struct.unpack('!H', data) + (length,) = struct.unpack('!H', data) elif length == 127: data = recv_bytes(sock, 8) - length, = struct.unpack('!Q', data) + (length,) = struct.unpack('!Q', data) if frame['mask']: mask_bits = recv_bytes(sock, 4) @@ -121,7 +117,7 @@ class TestApplicationWebsocket(TestApplicationProto): if frame['opcode'] == self.OP_CLOSE: if length >= 2: - code, = struct.unpack('!H', data[:2]) + (code,) = struct.unpack('!H', data[:2]) reason = data[2:].decode('utf-8') if not (code in self.CLOSE_CODES or 3000 <= code < 5000): pytest.fail('Invalid status code') diff --git a/test/unit/check/isolation.py b/test/unit/check/isolation.py index fe5a41f8..7c83ae35 100644 --- a/test/unit/check/isolation.py +++ b/test/unit/check/isolation.py @@ -12,6 +12,7 @@ from unit.utils import getns allns = ['pid', 'mnt', 'ipc', 'uts', 'cgroup', 'net'] http = TestHTTP() + def check_isolation(): test_conf = {"namespaces": {"credential": True}} available = option.available @@ -117,8 +118,7 @@ def check_isolation(): "body_empty": { "type": "perl", "processes": {"spare": 0}, - "working_directory": option.test_dir - + "/perl/body_empty", + "working_directory": option.test_dir + "/perl/body_empty", "script": option.test_dir + "/perl/body_empty/psgi.pl", "isolation": {"namespaces": {"credential": True}}, } diff --git a/test/unit/http.py b/test/unit/http.py index 7706fe05..797b7681 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -10,15 +10,16 @@ import pytest from unit.option import option -class TestHTTP(): +class TestHTTP: def http(self, start_str, **kwargs): sock_type = kwargs.get('sock_type', 'ipv4') port = kwargs.get('port', 7080) url = kwargs.get('url', '/') http = 'HTTP/1.0' if 'http_10' in kwargs else 'HTTP/1.1' - headers = kwargs.get('headers', - {'Host': 'localhost', 'Connection': 'close'}) + headers = kwargs.get( + 'headers', {'Host': 'localhost', 'Connection': 'close'} + ) body = kwargs.get('body', b'') crlf = '\r\n' @@ -305,8 +306,9 @@ class TestHTTP(): return body, content_type def form_url_encode(self, fields): - data = "&".join("%s=%s" % (name, value) - for name, value in fields.items()).encode() + data = "&".join( + "%s=%s" % (name, value) for name, value in fields.items() + ).encode() return data, 'application/x-www-form-urlencoded' def multipart_encode(self, fields): @@ -326,7 +328,9 @@ class TestHTTP(): datatype = value['type'] if not isinstance(value['data'], io.IOBase): - pytest.fail('multipart encoding of file requires a stream.') + pytest.fail( + 'multipart encoding of file requires a stream.' + ) data = value['data'].read() @@ -336,9 +340,10 @@ class TestHTTP(): else: pytest.fail('multipart requires a string or stream data') - body += ( - "--%s\r\nContent-Disposition: form-data; name=\"%s\"" - ) % (boundary, field) + body += ("--%s\r\nContent-Disposition: form-data; name=\"%s\"") % ( + boundary, + field, + ) if filename != '': body += "; filename=\"%s\"" % filename diff --git a/test/unit/option.py b/test/unit/option.py index 677d806e..cb3803dc 100644 --- a/test/unit/option.py +++ b/test/unit/option.py @@ -1,4 +1,4 @@ -class Options(): +class Options: _options = { 'skip_alerts': [], 'skip_sanitizer': False, @@ -13,4 +13,5 @@ class Options(): raise AttributeError + option = Options() -- cgit From 74b1b1fc17726d805b00dee6b5547254f5cf230c Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 8 Apr 2021 19:11:11 +0300 Subject: Tests: preserving unit.log when run without restart. Introducing "unit.log.Log" class for "unit.log" file management. Moving "findall()" function into TestApplicationProto. Using "os.kill()" to send signals. --- test/unit/applications/proto.py | 18 ++++++++++++------ test/unit/log.py | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 test/unit/log.py (limited to 'test/unit') diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py index 5c400621..92754c03 100644 --- a/test/unit/applications/proto.py +++ b/test/unit/applications/proto.py @@ -4,6 +4,7 @@ import time from unit.control import TestControl from unit.option import option +from unit.log import Log class TestApplicationProto(TestControl): @@ -15,18 +16,23 @@ class TestApplicationProto(TestControl): def date_to_sec_epoch(self, date, template='%a, %d %b %Y %H:%M:%S %Z'): return time.mktime(time.strptime(date, template)) + def findall(self, pattern, name='unit.log'): + with Log.open(name) as f: + return re.findall(pattern, f.read()) + def search_in_log(self, pattern, name='unit.log'): - with open(option.temp_dir + '/' + name, 'r', errors='ignore') as f: + with Log.open(name) as f: return re.search(pattern, f.read()) def wait_for_record(self, pattern, name='unit.log', wait=150): - for i in range(wait): - found = self.search_in_log(pattern, name) + with Log.open(name) as f: + for i in range(wait): + found = re.search(pattern, f.read()) - if found is not None: - break + if found is not None: + break - time.sleep(0.1) + time.sleep(0.1) return found diff --git a/test/unit/log.py b/test/unit/log.py new file mode 100644 index 00000000..7263443d --- /dev/null +++ b/test/unit/log.py @@ -0,0 +1,23 @@ +UNIT_LOG = 'unit.log' + + +class Log: + temp_dir = None + pos = {} + + def open(name=UNIT_LOG, encoding=None): + f = open(Log.get_path(name), 'r', encoding=encoding, errors='ignore') + f.seek(Log.pos.get(name, 0)) + + return f + + def set_pos(pos, name=UNIT_LOG): + Log.pos[name] = pos + + def swap(name): + pos = Log.pos.get(UNIT_LOG, 0) + Log.pos[UNIT_LOG] = Log.pos.get(name, 0) + Log.pos[name] = pos + + def get_path(name=UNIT_LOG): + return Log.temp_dir + '/' + name -- cgit From e0a061955bba69aaf28022d405362c8a5e444ff6 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 5 May 2021 12:36:57 +0100 Subject: Tests: added tests for openat2() features. --- test/unit/check/chroot.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 test/unit/check/chroot.py (limited to 'test/unit') diff --git a/test/unit/check/chroot.py b/test/unit/check/chroot.py new file mode 100644 index 00000000..40b75058 --- /dev/null +++ b/test/unit/check/chroot.py @@ -0,0 +1,32 @@ +import json + +from unit.http import TestHTTP +from unit.option import option + +http = TestHTTP() + + +def check_chroot(): + available = option.available + + resp = http.put( + url='/config', + sock_type='unix', + addr=option.temp_dir + '/control.unit.sock', + body=json.dumps( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [ + { + "action": { + "share": option.temp_dir, + "chroot": option.temp_dir, + } + } + ], + } + ), + ) + + if 'success' in resp['body']: + available['features']['chroot'] = True -- cgit From a0c083af208cd9f676bb56762b4e27a3174a773d Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Wed, 12 May 2021 09:26:55 +0000 Subject: Node.js: a shim for overriding "http" and "websocket" modules. Also added stubs for Server.address() This was done to prevent crashes in some popular frameworks like express Supports both CommonJS and the new ES Modules system syntax e.g: app.js: const http = require('http') app.mjs: import http from "http" Usage on Node 14.16.x and higher: { "type": "external", "processes": {"spare": 0}, "working_directory": '/project', "executable": "/usr/bin/env", "arguments": [ "node", "--loader", "unit-http/require_shim.mjs" "--require", "unit-http/require_shim", "app.js" ] } Usage on Node 14.15.x and lower: { "type": "external", "processes": {"spare": 0}, "working_directory": '/project', "executable": "/usr/bin/env", "arguments": [ "node", "--require", "unit-http/require_shim", "app.js" ] } --- test/unit/applications/lang/node.py | 21 ++++++++++++++++++--- test/unit/check/node.py | 13 +++++++++++-- 2 files changed, 29 insertions(+), 5 deletions(-) (limited to 'test/unit') diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py index cc6d06ef..3254f3d4 100644 --- a/test/unit/applications/lang/node.py +++ b/test/unit/applications/lang/node.py @@ -7,15 +7,16 @@ from unit.utils import public_dir class TestApplicationNode(TestApplicationProto): + application_type = "node" + es_modules = False + def prepare_env(self, script): # copy application - shutil.copytree( option.test_dir + '/node/' + script, option.temp_dir + '/node' ) # copy modules - shutil.copytree( option.current_dir + '/node/node_modules', option.temp_dir + '/node/node_modules', @@ -26,6 +27,19 @@ class TestApplicationNode(TestApplicationProto): def load(self, script, name='app.js', **kwargs): self.prepare_env(script) + if self.es_modules: + arguments = [ + "node", + "--loader", + "unit-http/require_shim.mjs", + "--require", + "unit-http/require_shim", + name, + ] + + else: + arguments = ["node", "--require", "unit-http/require_shim", name] + self._load_conf( { "listeners": { @@ -36,7 +50,8 @@ class TestApplicationNode(TestApplicationProto): "type": "external", "processes": {"spare": 0}, "working_directory": option.temp_dir + '/node', - "executable": name, + "executable": '/usr/bin/env', + "arguments": arguments, } }, }, diff --git a/test/unit/check/node.py b/test/unit/check/node.py index 236ba7b5..e053a749 100644 --- a/test/unit/check/node.py +++ b/test/unit/check/node.py @@ -1,6 +1,15 @@ import os +import subprocess def check_node(current_dir): - if os.path.exists(current_dir + '/node/node_modules'): - return True + if not os.path.exists(current_dir + '/node/node_modules'): + return None + + try: + v_bytes = subprocess.check_output(['/usr/bin/env', 'node', '-v']) + + return [str(v_bytes, 'utf-8').lstrip('v').rstrip()] + + except subprocess.CalledProcessError: + return None -- cgit From 25603eae9f8d3c2a6af3c5efb12b4a826776e300 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 12 May 2021 14:37:25 +0100 Subject: Tests: added test for TLS with IP in SAN. --- test/unit/applications/tls.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'test/unit') diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index 95eeac55..583b618f 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -76,9 +76,14 @@ class TestApplicationTLS(TestApplicationProto): # Generates alt_names section with dns names a_names = "[alt_names]\n" for i, k in enumerate(alt_names, 1): - a_names += "DNS.%d = %s\n" % (i, k) + k = k.split('|') - # Generates section for sign request extension + if k[0] == 'IP': + a_names += "IP.%d = %s\n" % (i, k[1]) + else: + a_names += "DNS.%d = %s\n" % (i, k[0]) + + # Generates section for sign request extension a_sec = """req_extensions = myca_req_extensions [ myca_req_extensions ] -- cgit From e50bb120e2a2dfad55d28724133656264ef13dc8 Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Thu, 20 May 2021 13:03:12 +0000 Subject: Tests: Python targets. --- test/unit/applications/lang/python.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'test/unit') diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py index 287d23f0..b399dffd 100644 --- a/test/unit/applications/lang/python.py +++ b/test/unit/applications/lang/python.py @@ -42,8 +42,15 @@ class TestApplicationPython(TestApplicationProto): "module": module, } - for attr in ('callable', 'home', 'limits', 'path', 'protocol', - 'threads'): + for attr in ( + 'callable', + 'home', + 'limits', + 'path', + 'protocol', + 'targets', + 'threads', + ): if attr in kwargs: app[attr] = kwargs.pop(attr) -- cgit From c160ea11e4ece4db52731ac8b83dd09ca2d1ef11 Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Mon, 24 May 2021 09:01:42 +0000 Subject: Node.js: renamed "require_shim" to "loader". --- test/unit/applications/lang/node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'test/unit') diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py index 3254f3d4..5d05c70c 100644 --- a/test/unit/applications/lang/node.py +++ b/test/unit/applications/lang/node.py @@ -31,14 +31,14 @@ class TestApplicationNode(TestApplicationProto): arguments = [ "node", "--loader", - "unit-http/require_shim.mjs", + "unit-http/loader.mjs", "--require", - "unit-http/require_shim", + "unit-http/loader", name, ] else: - arguments = ["node", "--require", "unit-http/require_shim", name] + arguments = ["node", "--require", "unit-http/loader", name] self._load_conf( { -- cgit From 1154ede862824331d24591c717c270e779a2b08c Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 24 May 2021 05:26:15 +0100 Subject: Tests: test_settings_send_timeout improved. Data length adjusts depending on socket buffer size when it's possible. --- test/unit/utils.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'test/unit') diff --git a/test/unit/utils.py b/test/unit/utils.py index e80fc469..a627e9f5 100644 --- a/test/unit/utils.py +++ b/test/unit/utils.py @@ -61,6 +61,17 @@ def findmnt(): return out +def sysctl(): + try: + out = subprocess.check_output( + ['sysctl', '-a'], stderr=subprocess.STDOUT + ).decode() + except FileNotFoundError: + pytest.skip('requires sysctl') + + return out + + def waitformount(template, wait=50): for i in range(wait): if findmnt().find(template) != -1: -- cgit From 155e22da05f01eb51b9dc082e9c8e8bff9b5ec8d Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Tue, 25 May 2021 18:00:59 +0300 Subject: Go: fixing tests for Go 1.16. In Go 1.16, the module-aware mode is enabled by default; to fall back to previous behavior, the GO111MODULE environment variable should be set to 'auto'. Details: https://golang.org/doc/go1.16 --- test/unit/applications/lang/go.py | 1 + test/unit/check/go.py | 1 + 2 files changed, 2 insertions(+) (limited to 'test/unit') diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py index a17b1af4..6be1667b 100644 --- a/test/unit/applications/lang/go.py +++ b/test/unit/applications/lang/go.py @@ -13,6 +13,7 @@ class TestApplicationGo(TestApplicationProto): env = os.environ.copy() env['GOPATH'] = option.current_dir + '/build/go' env['GOCACHE'] = option.cache_dir + '/go' + env['GO111MODULE'] = 'auto' if static: args = [ diff --git a/test/unit/check/go.py b/test/unit/check/go.py index 35b0c2d5..309091c0 100644 --- a/test/unit/check/go.py +++ b/test/unit/check/go.py @@ -8,6 +8,7 @@ def check_go(current_dir, temp_dir, test_dir): env = os.environ.copy() env['GOPATH'] = current_dir + '/build/go' + env['GO111MODULE'] = 'auto' try: process = subprocess.Popen( -- cgit