diff options
author | Andrei Belov <defan@nginx.com> | 2021-08-19 18:17:12 +0300 |
---|---|---|
committer | Andrei Belov <defan@nginx.com> | 2021-08-19 18:17:12 +0300 |
commit | db442f1be7e713e6a219621ff97a51046590dbd6 (patch) | |
tree | 913734275bc890ec175e51fcb0f36b01a3c52c24 /test | |
parent | a1d2ced6fc2317d36bc917c5d0ac339bc647dc34 (diff) | |
parent | 13c0025dfa6e041563d0ad5dd81679b44522694c (diff) | |
download | unit-1.25.0-1.tar.gz unit-1.25.0-1.tar.bz2 |
Merged with the default branch.1.25.0-1
Diffstat (limited to '')
35 files changed, 692 insertions, 233 deletions
diff --git a/test/conftest.py b/test/conftest.py index 5ea4e49d..4d46e2fc 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -22,8 +22,8 @@ from unit.check.node import check_node from unit.check.regex import check_regex from unit.check.tls import check_openssl from unit.http import TestHTTP -from unit.option import option from unit.log import Log +from unit.option import option from unit.utils import public_dir from unit.utils import waitforfiles @@ -74,7 +74,7 @@ def pytest_addoption(parser): unit_instance = {} _processes = [] -_fds_check = { +_fds_info = { 'main': {'fds': 0, 'skip': False}, 'router': {'name': 'unit: router', 'pid': -1, 'fds': 0, 'skip': False}, 'controller': { @@ -115,6 +115,17 @@ def pytest_configure(config): fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, 0) +def print_log_on_assert(func): + def inner_function(*args, **kwargs): + try: + func(*args, **kwargs) + except AssertionError as e: + _print_log(kwargs.get('log', None)) + raise e + + return inner_function + + def pytest_generate_tests(metafunc): cls = metafunc.cls if ( @@ -275,9 +286,9 @@ def run(request): ] option.skip_sanitizer = False - _fds_check['main']['skip'] = False - _fds_check['router']['skip'] = False - _fds_check['controller']['skip'] = False + _fds_info['main']['skip'] = False + _fds_info['router']['skip'] = False + _fds_info['controller']['skip'] = False yield @@ -299,7 +310,7 @@ def run(request): # clean temp_dir before the next test if not option.restart: - _clear_conf(unit['temp_dir'] + '/control.unit.sock', log) + _clear_conf(unit['temp_dir'] + '/control.unit.sock', log=log) for item in os.listdir(unit['temp_dir']): if item not in [ @@ -317,53 +328,18 @@ def run(request): ): os.remove(path) else: - shutil.rmtree(path) - - # check descriptors (wait for some time before check) - - def waitforfds(diff): - for i in range(600): - fds_diff = diff() - - if fds_diff <= option.fds_threshold: - break - - time.sleep(0.1) - - return fds_diff - - ps = _fds_check['main'] - if not ps['skip']: - fds_diff = waitforfds( - lambda: _count_fds(unit_instance['pid']) - ps['fds'] - ) - ps['fds'] += fds_diff - - assert ( - fds_diff <= option.fds_threshold - ), 'descriptors leak main process' - - else: - ps['fds'] = _count_fds(unit_instance['pid']) - - for name in ['controller', 'router']: - ps = _fds_check[name] - ps_pid = ps['pid'] - ps['pid'] = pid_by_name(ps['name']) - - if not ps['skip']: - fds_diff = waitforfds(lambda: _count_fds(ps['pid']) - ps['fds']) - ps['fds'] += fds_diff - - if not option.restart: - assert ps['pid'] == ps_pid, 'same pid %s' % name + for attempt in range(10): + try: + shutil.rmtree(path) + break + except OSError as err: + if err.errno != 16: + raise + time.sleep(1) - assert fds_diff <= option.fds_threshold, ( - 'descriptors leak %s' % name - ) + # check descriptors - else: - ps['fds'] = _count_fds(ps['pid']) + _check_fds(log=log) # print unit.log in case of error @@ -424,6 +400,8 @@ def unit_run(): with open(temp_dir + '/unit.log', 'w') as log: unit_instance['process'] = subprocess.Popen(unitd_args, stderr=log) + Log.temp_dir = temp_dir + if not waitforfiles(temp_dir + '/control.unit.sock'): _print_log() exit('Could not start unit') @@ -433,20 +411,19 @@ def unit_run(): unit_instance['unitd'] = unitd option.temp_dir = temp_dir - Log.temp_dir = temp_dir with open(temp_dir + '/unit.pid', 'r') as f: unit_instance['pid'] = f.read().rstrip() _clear_conf(unit_instance['temp_dir'] + '/control.unit.sock') - _fds_check['main']['fds'] = _count_fds(unit_instance['pid']) + _fds_info['main']['fds'] = _count_fds(unit_instance['pid']) - router = _fds_check['router'] + router = _fds_info['router'] router['pid'] = pid_by_name(router['name']) router['fds'] = _count_fds(router['pid']) - controller = _fds_check['controller'] + controller = _fds_info['controller'] controller['pid'] = pid_by_name(controller['name']) controller['fds'] = _count_fds(controller['pid']) @@ -481,7 +458,8 @@ def unit_stop(): return 'Could not terminate unit' -def _check_alerts(log=None): +@print_log_on_assert +def _check_alerts(*, log=None): if log is None: with Log.open(encoding='utf-8') as f: log = f.read() @@ -499,22 +477,18 @@ def _check_alerts(log=None): for skip in option.skip_alerts: alerts = [al for al in alerts if re.search(skip, al) is None] - if alerts: - _print_log(log) - assert not alerts, 'alert(s)' + assert not alerts, 'alert(s)' if not option.skip_sanitizer: sanitizer_errors = re.findall('.+Sanitizer.+', log) - if sanitizer_errors: - _print_log(log) - assert not sanitizer_errors, 'sanitizer error(s)' + assert not sanitizer_errors, 'sanitizer error(s)' if found: print('skipped.') -def _print_log(data=None): +def _print_log(log=None): path = Log.get_path() print('Path to unit.log:\n' + path + '\n') @@ -523,19 +497,15 @@ def _print_log(data=None): os.set_blocking(sys.stdout.fileno(), True) sys.stdout.flush() - if data is None: + if log is None: with open(path, 'r', encoding='utf-8', errors='ignore') as f: shutil.copyfileobj(f, sys.stdout) else: - sys.stdout.write(data) + sys.stdout.write(log) -def _clear_conf(sock, log=None): - def check_success(resp): - if 'success' not in resp: - _print_log(log) - assert 'success' in resp - +@print_log_on_assert +def _clear_conf(sock, *, log=None): resp = http.put( url='/config', sock_type='unix', @@ -543,7 +513,7 @@ def _clear_conf(sock, log=None): body=json.dumps({"listeners": {}, "applications": {}}), )['body'] - check_success(resp) + assert 'success' in resp, 'clear conf' if 'openssl' not in option.available['modules']: return @@ -561,7 +531,54 @@ def _clear_conf(sock, log=None): url='/certificates/' + cert, sock_type='unix', addr=sock, )['body'] - check_success(resp) + assert 'success' in resp, 'remove certificate' + + +@print_log_on_assert +def _check_fds(*, log=None): + def waitforfds(diff): + for i in range(600): + fds_diff = diff() + + if fds_diff <= option.fds_threshold: + break + + time.sleep(0.1) + + return fds_diff + + ps = _fds_info['main'] + if not ps['skip']: + fds_diff = waitforfds( + lambda: _count_fds(unit_instance['pid']) - ps['fds'] + ) + ps['fds'] += fds_diff + + assert ( + fds_diff <= option.fds_threshold + ), 'descriptors leak main process' + + else: + ps['fds'] = _count_fds(unit_instance['pid']) + + for name in ['controller', 'router']: + ps = _fds_info[name] + ps_pid = ps['pid'] + ps['pid'] = pid_by_name(ps['name']) + + if not ps['skip']: + fds_diff = waitforfds(lambda: _count_fds(ps['pid']) - ps['fds']) + ps['fds'] += fds_diff + + if not option.restart: + assert ps['pid'] == ps_pid, 'same pid %s' % name + + assert fds_diff <= option.fds_threshold, ( + 'descriptors leak %s' % name + ) + + else: + ps['fds'] = _count_fds(ps['pid']) def _count_fds(pid): @@ -639,9 +656,9 @@ def skip_alert(): @pytest.fixture() def skip_fds_check(): def _skip(main=False, router=False, controller=False): - _fds_check['main']['skip'] = main - _fds_check['router']['skip'] = router - _fds_check['controller']['skip'] = controller + _fds_info['main']['skip'] = main + _fds_info['router']['skip'] = router + _fds_info['controller']['skip'] = controller return _skip diff --git a/test/python/client_ip/wsgi.py b/test/python/client_ip/wsgi.py new file mode 100644 index 00000000..0e12db0a --- /dev/null +++ b/test/python/client_ip/wsgi.py @@ -0,0 +1,4 @@ +def application(env, start_response): + ip = env['REMOTE_ADDR'].encode() + start_response('200', [('Content-Length', str(len(ip)))]) + return ip diff --git a/test/python/restart/longstart.py b/test/python/restart/longstart.py new file mode 100644 index 00000000..777398ac --- /dev/null +++ b/test/python/restart/longstart.py @@ -0,0 +1,10 @@ +import os +import time + +time.sleep(2) + +def application(environ, start_response): + body = str(os.getpid()).encode() + + start_response('200', [('Content-Length', str(len(body)))]) + return [body] diff --git a/test/python/restart/v1.py b/test/python/restart/v1.py new file mode 100644 index 00000000..2e45b269 --- /dev/null +++ b/test/python/restart/v1.py @@ -0,0 +1,7 @@ +import os + +def application(environ, start_response): + body = "v1".encode() + + start_response('200', [('Content-Length', str(len(body)))]) + return [body] diff --git a/test/python/restart/v2.py b/test/python/restart/v2.py new file mode 100644 index 00000000..59e3d30f --- /dev/null +++ b/test/python/restart/v2.py @@ -0,0 +1,7 @@ +import os + +def application(environ, start_response): + body = "v2".encode() + + start_response('200', [('Content-Length', str(len(body)))]) + return [body] diff --git a/test/ruby/hooks/config.ru b/test/ruby/hooks/config.ru new file mode 100644 index 00000000..f3069558 --- /dev/null +++ b/test/ruby/hooks/config.ru @@ -0,0 +1,7 @@ +app = Proc.new do |env| + ['200', { + 'Content-Length' => '0' + }, ['']] +end + +run app diff --git a/test/ruby/hooks/eval.rb b/test/ruby/hooks/eval.rb new file mode 100644 index 00000000..ce7329c1 --- /dev/null +++ b/test/ruby/hooks/eval.rb @@ -0,0 +1,3 @@ +require 'securerandom' + +File.write("./cookie_eval.#{SecureRandom.hex}", "evaluated") diff --git a/test/ruby/hooks/multiple.rb b/test/ruby/hooks/multiple.rb new file mode 100644 index 00000000..b1b659a5 --- /dev/null +++ b/test/ruby/hooks/multiple.rb @@ -0,0 +1,13 @@ +require 'securerandom' + +@mutex = Mutex.new + +on_worker_boot do + File.write("./cookie_worker_boot.#{SecureRandom.hex}", "worker booted") +end + +on_thread_boot do + @mutex.synchronize do + File.write("./cookie_thread_boot.#{SecureRandom.hex}", "thread booted") + end +end diff --git a/test/ruby/hooks/on_thread_boot.rb b/test/ruby/hooks/on_thread_boot.rb new file mode 100644 index 00000000..4f88424e --- /dev/null +++ b/test/ruby/hooks/on_thread_boot.rb @@ -0,0 +1,9 @@ +require 'securerandom' + +@mutex = Mutex.new + +on_thread_boot do + @mutex.synchronize do + File.write("./cookie_thread_boot.#{SecureRandom.hex}", "booted") + end +end diff --git a/test/ruby/hooks/on_thread_shutdown.rb b/test/ruby/hooks/on_thread_shutdown.rb new file mode 100644 index 00000000..d953b8b7 --- /dev/null +++ b/test/ruby/hooks/on_thread_shutdown.rb @@ -0,0 +1,9 @@ +require 'securerandom' + +@mutex = Mutex.new + +on_thread_shutdown do + @mutex.synchronize do + File.write("./cookie_thread_shutdown.#{SecureRandom.hex}", "shutdown") + end +end diff --git a/test/ruby/hooks/on_worker_boot.rb b/test/ruby/hooks/on_worker_boot.rb new file mode 100644 index 00000000..b6529f60 --- /dev/null +++ b/test/ruby/hooks/on_worker_boot.rb @@ -0,0 +1,5 @@ +require 'securerandom' + +on_worker_boot do + File.write("./cookie_worker_boot.#{SecureRandom.hex}", "booted") +end diff --git a/test/ruby/hooks/on_worker_shutdown.rb b/test/ruby/hooks/on_worker_shutdown.rb new file mode 100644 index 00000000..9ffaad93 --- /dev/null +++ b/test/ruby/hooks/on_worker_shutdown.rb @@ -0,0 +1,5 @@ +require 'securerandom' + +on_worker_shutdown do + File.write("./cookie_worker_shutdown.#{SecureRandom.hex}", "shutdown") +end diff --git a/test/test_client_ip.py b/test/test_client_ip.py new file mode 100644 index 00000000..0084574e --- /dev/null +++ b/test/test_client_ip.py @@ -0,0 +1,129 @@ +import pytest + +from unit.applications.lang.python import TestApplicationPython + + +class TestClientIP(TestApplicationPython): + prerequisites = {'modules': {'python': 'any'}} + + def client_ip(self, options): + assert 'success' in self.conf( + { + "127.0.0.1:7081": + {"client_ip": options, "pass": "applications/client_ip"}, + "[::1]:7082": + {"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 + + return self.get( + sock_type=sock_type, + port=port, + headers={'Connection': 'close', 'X-Forwarded-For': xff}, + )['body'] + + def setup_method(self): + self.load('client_ip') + + def test_settings_client_ip_single_ip(self): + self.client_ip( + {'header': 'X-Forwarded-For', 'source': '123.123.123.123'} + ) + + assert self.get(port=7081)['body'] == '127.0.0.1', 'ipv4 default' + assert ( + self.get(sock_type='ipv6', port=7082)['body'] == '::1' + ), 'ipv6 default' + assert self.get_xff('1.1.1.1') == '127.0.0.1', 'bad source' + assert self.get_xff('blah') == '127.0.0.1', 'bad header' + assert self.get_xff('1.1.1.1', 'ipv6') == '::1', 'bad source ipv6' + + self.client_ip({'header': 'X-Forwarded-For', 'source': '127.0.0.1'}) + + assert self.get(port=7081)['body'] == '127.0.0.1', 'ipv4 default 2' + assert ( + self.get(sock_type='ipv6', port=7082)['body'] == '::1' + ), 'ipv6 default 2' + assert self.get_xff('1.1.1.1') == '1.1.1.1', 'replace' + assert self.get_xff('blah') == '127.0.0.1', 'bad header 2' + assert ( + self.get_xff('1.1.1.1', 'ipv6') == '::1' + ), 'bad source ipv6 2' + + self.client_ip({'header': 'X-Forwarded-For', 'source': '!127.0.0.1'}) + + 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): + self.client_ip({'header': 'X-Forwarded-For', 'source': '127.0.0.1'}) + + assert ( + self.get_xff('8.8.8.8, 84.23.23.11') == '84.23.23.11' + ), 'xff replace' + assert ( + self.get_xff('8.8.8.8, 84.23.23.11, 127.0.0.1') == '127.0.0.1' + ), 'xff replace 2' + assert ( + 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): + self.client_ip({'header': 'X-Forwarded-For', 'source': '::1'}) + + assert self.get_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_xff(ip, 'ipv6') == ip, 'replace' + + def test_settings_client_ip_recursive(self): + self.client_ip( + { + 'header': 'X-Forwarded-For', + 'recursive': True, + 'source': ['127.0.0.1', '10.50.0.17', '10.5.2.1'], + } + ) + + assert self.get_xff('1.1.1.1') == '1.1.1.1', 'xff chain' + assert self.get_xff('1.1.1.1, 10.5.2.1') == '1.1.1.1', 'xff chain 2' + assert ( + self.get_xff('8.8.8.8, 1.1.1.1, 10.5.2.1') == '1.1.1.1' + ), 'xff chain 3' + assert ( + self.get_xff('10.50.0.17, 10.5.2.1, 10.5.2.1') == '10.50.0.17' + ), 'xff chain 4' + assert ( + self.get_xff(['8.8.8.8', '1.1.1.1, 127.0.0.1']) == '1.1.1.1' + ), 'xff replace multi' + assert ( + self.get_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_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_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_settings_client_ip_invalid(self): + assert 'error' in self.conf( + {"http": {"client_ip": {'header': 'X-Forwarded-For', 'source': []}}}, + 'settings', + ), 'empty array source' + assert 'error' in self.conf( + {"http":{"client_ip": {'header': 'X-Forwarded-For', 'source': 'a'}}}, + 'settings', + ), 'empty source invalid' diff --git a/test/test_configuration.py b/test/test_configuration.py index 880aef6c..8655968f 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -1,12 +1,22 @@ +import socket + import pytest -import socket from unit.control import TestControl class TestConfiguration(TestControl): prerequisites = {'modules': {'python': 'any'}} + def try_addr(self, addr): + return self.conf( + { + "listeners": {addr: {"pass": "routes"}}, + "routes": [{"action": {"return": 200}}], + "applications": {}, + } + ) + def test_json_empty(self): assert 'error' in self.conf(''), 'empty' @@ -217,50 +227,20 @@ class TestConfiguration(TestControl): {"*:7080": {"pass": "applications/app"}}, 'listeners' ), 'listeners no app' - def test_listeners_wildcard(self): - assert 'success' in self.conf( - { - "listeners": {"*:7080": {"pass": "applications/app"}}, - "applications": { - "app": { - "type": "python", - "processes": {"spare": 0}, - "path": "/app", - "module": "wsgi", - } - }, - } - ), 'listeners wildcard' + 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' + assert 'success' in self.try_addr("[::1]:7082"), 'explicit ipv6' - def test_listeners_explicit(self): - assert 'success' in self.conf( - { - "listeners": {"127.0.0.1:7080": {"pass": "applications/app"}}, - "applications": { - "app": { - "type": "python", - "processes": {"spare": 0}, - "path": "/app", - "module": "wsgi", - } - }, - } - ), 'explicit' + def test_listeners_addr_error(self): + assert 'error' in self.try_addr("127.0.0.1"), 'no port' - def test_listeners_explicit_ipv6(self): - assert 'success' in self.conf( - { - "listeners": {"[::1]:7080": {"pass": "applications/app"}}, - "applications": { - "app": { - "type": "python", - "processes": {"spare": 0}, - "path": "/app", - "module": "wsgi", - } - }, - } - ), 'explicit ipv6' + def test_listeners_addr_error_2(self, skip_alert): + skip_alert(r'bind.*failed', r'failed to apply new conf') + + assert 'error' in self.try_addr( + "[f607:7403:1e4b:6c66:33b2:843f:2517:da27]:7080" + ) def test_listeners_port_release(self): for i in range(10): @@ -289,22 +269,6 @@ class TestConfiguration(TestControl): assert 'success' in resp, 'port release' - @pytest.mark.skip('not yet, unsafe') - def test_listeners_no_port(self): - assert 'error' in self.conf( - { - "listeners": {"127.0.0.1": {"pass": "applications/app"}}, - "applications": { - "app": { - "type": "python", - "processes": {"spare": 0}, - "path": "/app", - "module": "wsgi", - } - }, - } - ), 'no port' - def test_json_application_name_large(self): name = "X" * 1024 * 1024 diff --git a/test/test_node_es_modules.py b/test/test_node_es_modules.py index 0945a967..5464d4a6 100644 --- a/test/test_node_es_modules.py +++ b/test/test_node_es_modules.py @@ -1,6 +1,7 @@ +from distutils.version import LooseVersion + import pytest -from distutils.version import LooseVersion from unit.applications.lang.node import TestApplicationNode from unit.applications.websockets import TestApplicationWebsocket diff --git a/test/test_python_procman.py b/test/test_python_procman.py index b0d0f5af..a95c5680 100644 --- a/test/test_python_procman.py +++ b/test/test_python_procman.py @@ -1,4 +1,5 @@ import re +import shutil import subprocess import time @@ -201,3 +202,81 @@ class TestPythonProcman(TestApplicationPython): assert 'success' in self.conf({"listeners": {}, "applications": {}}) assert len(self.pids_for_process()) == 0, 'stop all' + + def test_python_restart(self, temp_dir): + shutil.copyfile( + option.test_dir + '/python/restart/v1.py', temp_dir + '/wsgi.py' + ) + + self.load( + temp_dir, + name=self.app_name, + processes=1, + environment={'PYTHONDONTWRITEBYTECODE': '1'}, + ) + + b = self.get()['body'] + assert b == "v1", 'process started' + + shutil.copyfile( + option.test_dir + '/python/restart/v2.py', temp_dir + '/wsgi.py' + ) + + b = self.get()['body'] + assert b == "v1", 'still old process' + + assert 'success' in self.conf_get( + '/control/applications/' + self.app_name + '/restart' + ), 'restart processes' + + b = self.get()['body'] + assert b == "v2", 'new process started' + + assert 'error' in self.conf_get( + '/control/applications/blah/restart' + ), 'application incorrect' + + assert 'error' in self.conf_delete( + '/control/applications/' + self.app_name + '/restart' + ), 'method incorrect' + + def test_python_restart_multi(self): + self.conf_proc('2') + + pids = self.pids_for_process() + assert len(pids) == 2, 'restart 2 started' + + assert 'success' in self.conf_get( + '/control/applications/' + self.app_name + '/restart' + ), 'restart processes' + + new_pids = self.pids_for_process() + assert len(new_pids) == 2, 'restart still 2' + + assert len(new_pids.intersection(pids)) == 0, 'restart all new' + + def test_python_restart_longstart(self): + self.load( + 'restart', + name=self.app_name, + module="longstart", + processes={"spare": 1, "max": 2, "idle_timeout": 5}, + ) + + assert len(self.pids_for_process()) == 1, 'longstarts == 1' + + pid = self.get()['body'] + pids = self.pids_for_process() + assert len(pids) == 2, 'longstarts == 2' + + assert 'success' in self.conf_get( + '/control/applications/' + self.app_name + '/restart' + ), 'restart processes' + + # wait for longstarted app + time.sleep(2) + + new_pids = self.pids_for_process() + assert len(new_pids) == 1, 'restart 1' + + assert len(new_pids.intersection(pids)) == 0, 'restart all new' diff --git a/test/test_respawn.py b/test/test_respawn.py index edbfa2a8..5a5d6126 100644 --- a/test/test_respawn.py +++ b/test/test_respawn.py @@ -44,11 +44,16 @@ class TestRespawn(TestApplicationPython): return re.findall(str(ppid) + r'.*' + name, ps_output) def smoke_test(self, unit_pid): - for _ in range(5): - assert 'success' in self.conf( - '1', 'applications/' + self.app_name + '/processes' - ) - assert self.get()['status'] == 200 + for _ in range(10): + r = self.conf('1', 'applications/' + self.app_name + '/processes') + + if 'success' in r: + break + + time.sleep(0.1) + + assert 'success' in r + assert self.get()['status'] == 200 # Check if the only one router, controller, # and application processes running. diff --git a/test/test_routing.py b/test/test_routing.py index eaa0a134..ef5622c2 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -1751,6 +1751,10 @@ class TestRouting(TestApplicationProto): self.route_match_invalid({"source": "*:1-a"}) self.route_match_invalid({"source": "*:65536"}) + def test_routes_match_source_none(self): + self.route_match({"source": []}) + assert self.get()['status'] == 404, 'source none' + def test_routes_match_destination(self): assert 'success' in self.conf( {"*:7080": {"pass": "routes"}, "*:7081": {"pass": "routes"}}, diff --git a/test/test_ruby_hooks.py b/test/test_ruby_hooks.py new file mode 100644 index 00000000..af8ce337 --- /dev/null +++ b/test/test_ruby_hooks.py @@ -0,0 +1,98 @@ +import os +import time +from pathlib import Path + +import pytest + +from conftest import unit_stop +from unit.applications.lang.ruby import TestApplicationRuby +from unit.option import option +from unit.utils import waitforglob + + +class TestRubyHooks(TestApplicationRuby): + prerequisites = {'modules': {'ruby': 'all'}} + + def _wait_cookie(self, pattern, count): + return waitforglob( + option.temp_dir + '/ruby/hooks/cookie_' + pattern, count + ) + + def test_ruby_hooks_eval(self): + processes = 2 + + self.load('hooks', processes=processes, hooks='eval.rb') + + hooked = self._wait_cookie('eval.*', processes) + + assert hooked, 'hooks evaluated' + + def test_ruby_hooks_on_worker_boot(self): + processes = 2 + + self.load('hooks', processes=processes, hooks='on_worker_boot.rb') + + hooked = self._wait_cookie('worker_boot.*', processes) + + assert hooked, 'on_worker_boot called' + + def test_ruby_hooks_on_worker_shutdown(self): + processes = 2 + + self.load('hooks', processes=processes, hooks='on_worker_shutdown.rb') + + assert self.get()['status'] == 200, 'app response' + + self.load('empty') + + hooked = self._wait_cookie('worker_shutdown.*', processes) + + assert hooked, 'on_worker_shutdown called' + + def test_ruby_hooks_on_thread_boot(self): + processes = 1 + threads = 2 + + self.load( + 'hooks', + processes=processes, + threads=threads, + hooks='on_thread_boot.rb', + ) + + hooked = self._wait_cookie('thread_boot.*', processes * threads) + + assert hooked, 'on_thread_boot called' + + def test_ruby_hooks_on_thread_shutdown(self): + processes = 1 + threads = 2 + + self.load( + 'hooks', + processes=processes, + threads=threads, + hooks='on_thread_shutdown.rb', + ) + + assert self.get()['status'] == 200, 'app response' + + self.load('empty') + + hooked = self._wait_cookie('thread_shutdown.*', processes * threads) + + assert hooked, 'on_thread_shutdown called' + + def test_ruby_hooks_multiple(self): + processes = 1 + threads = 1 + + self.load( + 'hooks', processes=processes, threads=threads, hooks='multiple.rb', + ) + + hooked = self._wait_cookie('worker_boot.*', processes) + assert hooked, 'on_worker_boot called' + + hooked = self._wait_cookie('thread_boot.*', threads) + assert hooked, 'on_thread_boot called' diff --git a/test/test_ruby_isolation.py b/test/test_ruby_isolation.py index 8443d857..f414d610 100644 --- a/test/test_ruby_isolation.py +++ b/test/test_ruby_isolation.py @@ -35,13 +35,6 @@ class TestRubyIsolation(TestApplicationRuby): 'pid': True, } - os.mkdir(option.temp_dir + '/ruby') - - shutil.copytree( - option.test_dir + '/ruby/status_int', - option.temp_dir + '/ruby/status_int', - ) - self.load('status_int', isolation=isolation) assert 'success' in self.conf( diff --git a/test/test_share_chroot.py b/test/test_static_chroot.py index 7e53d3f7..f9bc93a8 100644 --- a/test/test_share_chroot.py +++ b/test/test_static_chroot.py @@ -6,17 +6,14 @@ import pytest from unit.applications.proto import TestApplicationProto -class TestShareChroot(TestApplicationProto): +class TestStaticChroot(TestApplicationProto): prerequisites = {'features': ['chroot']} @pytest.fixture(autouse=True) def setup_method_fixture(self, temp_dir): os.makedirs(temp_dir + '/assets/dir') - with open(temp_dir + '/assets/index.html', 'w') as index, open( - temp_dir + '/assets/dir/file', 'w' - ) as file: - index.write('0123456789') - file.write('blah') + 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 @@ -28,7 +25,7 @@ class TestShareChroot(TestApplicationProto): } ) - def test_share_chroot(self, temp_dir): + def test_static_chroot(self, temp_dir): assert self.get(url='/dir/file')['status'] == 200, 'default chroot' assert self.get(url='/index.html')['status'] == 200, 'default chroot 2' @@ -44,7 +41,10 @@ class TestShareChroot(TestApplicationProto): assert self.get(url='/index.html')['status'] == 403, 'chroot 403 2' assert self.get(url='/file')['status'] == 403, 'chroot 403' - def test_share_chroot_permission(self, temp_dir): + def test_static_chroot_permission(self, is_su, temp_dir): + if is_su: + pytest.skip('does\'t work under root') + os.chmod(temp_dir + '/assets/dir', 0o100) assert 'success' in self.conf( @@ -57,7 +57,7 @@ class TestShareChroot(TestApplicationProto): assert self.get(url='/dir/file')['status'] == 200, 'chroot' - def test_share_chroot_empty(self, temp_dir): + def test_static_chroot_empty(self, temp_dir): assert 'success' in self.conf( {"share": temp_dir + "/assets", "chroot": ""}, 'routes/0/action', ), 'configure chroot empty absolute' @@ -74,7 +74,7 @@ class TestShareChroot(TestApplicationProto): self.get(url=self.test_path)['status'] == 200 ), 'chroot empty relative' - def test_share_chroot_relative(self, is_su, temp_dir): + def test_static_chroot_relative(self, is_su, temp_dir): if is_su: pytest.skip('does\'t work under root') @@ -96,7 +96,7 @@ class TestShareChroot(TestApplicationProto): assert self.get(url=self.test_path)['status'] == 200, 'relative' - def test_share_chroot_invalid(self, temp_dir): + def test_static_chroot_invalid(self, temp_dir): assert 'error' in self.conf( {"share": temp_dir, "chroot": True}, 'routes/0/action', ), 'configure chroot error' diff --git a/test/test_share_fallback.py b/test/test_static_fallback.py index 0b1c270e..dc9056b9 100644 --- a/test/test_share_fallback.py +++ b/test/test_static_fallback.py @@ -1,21 +1,21 @@ import os +from pathlib import Path import pytest from unit.applications.proto import TestApplicationProto -from unit.option import option -class TestStatic(TestApplicationProto): +class TestStaticFallback(TestApplicationProto): prerequisites = {} - def setup_method(self): - os.makedirs(option.temp_dir + '/assets/dir') - with open(option.temp_dir + '/assets/index.html', 'w') as index: - index.write('0123456789') + @pytest.fixture(autouse=True) + def setup_method_fixture(self, temp_dir): + os.makedirs(temp_dir + '/assets/dir') + Path(temp_dir + '/assets/index.html').write_text('0123456789') - os.makedirs(option.temp_dir + '/assets/403') - os.chmod(option.temp_dir + '/assets/403', 0o000) + os.makedirs(temp_dir + '/assets/403') + os.chmod(temp_dir + '/assets/403', 0o000) self._load_conf( { @@ -23,21 +23,22 @@ class TestStatic(TestApplicationProto): "*:7080": {"pass": "routes"}, "*:7081": {"pass": "routes"}, }, - "routes": [{"action": {"share": option.temp_dir + "/assets"}}], + "routes": [{"action": {"share": temp_dir + "/assets"}}], "applications": {}, } ) - def teardown_method(self): + yield + try: - os.chmod(option.temp_dir + '/assets/403', 0o777) + os.chmod(temp_dir + '/assets/403', 0o777) except FileNotFoundError: pass def action_update(self, conf): assert 'success' in self.conf(conf, 'routes/0/action') - def test_fallback(self): + def test_static_fallback(self): self.action_update({"share": "/blah"}) assert self.get()['status'] == 404, 'bad path no fallback' @@ -47,7 +48,7 @@ class TestStatic(TestApplicationProto): assert resp['status'] == 200, 'bad path fallback status' assert resp['body'] == '', 'bad path fallback' - def test_fallback_valid_path(self, temp_dir): + def test_static_fallback_valid_path(self, temp_dir): self.action_update( {"share": temp_dir + "/assets", "fallback": {"return": 200}} ) @@ -65,7 +66,7 @@ class TestStatic(TestApplicationProto): assert self.get(url='/dir')['status'] == 301, 'fallback status 301' - def test_fallback_nested(self): + def test_static_fallback_nested(self): self.action_update( { "share": "/blah", @@ -80,7 +81,7 @@ class TestStatic(TestApplicationProto): assert resp['status'] == 200, 'fallback nested status' assert resp['body'] == '', 'fallback nested' - def test_fallback_share(self, temp_dir): + def test_static_fallback_share(self, temp_dir): self.action_update( {"share": "/blah", "fallback": {"share": temp_dir + "/assets"},} ) @@ -97,7 +98,7 @@ class TestStatic(TestApplicationProto): self.get(url='/dir')['status'] == 301 ), 'fallback share status 301' - def test_fallback_proxy(self): + def test_static_fallback_proxy(self): assert 'success' in self.conf( [ { @@ -119,7 +120,7 @@ class TestStatic(TestApplicationProto): assert resp['body'] == '', 'fallback proxy' @pytest.mark.skip('not yet') - def test_fallback_proxy_loop(self, skip_alert): + def test_static_fallback_proxy_loop(self, skip_alert): skip_alert( r'open.*/blah/index.html.*failed', r'accept.*failed', @@ -135,7 +136,7 @@ class TestStatic(TestApplicationProto): assert 'success' in self.conf_delete('listeners/*:7081') self.get(read_timeout=1) - def test_fallback_invalid(self): + def test_static_fallback_invalid(self): def check_error(conf): assert 'error' in self.conf(conf, 'routes/0/action') diff --git a/test/test_share_mount.py b/test/test_static_mount.py index f22fbe75..570f6439 100644 --- a/test/test_share_mount.py +++ b/test/test_static_mount.py @@ -1,12 +1,13 @@ import os import subprocess +from pathlib import Path import pytest from unit.applications.proto import TestApplicationProto -class TestShareMount(TestApplicationProto): +class TestStaticMount(TestApplicationProto): prerequisites = {'features': ['chroot']} @pytest.fixture(autouse=True) @@ -17,12 +18,9 @@ class TestShareMount(TestApplicationProto): os.makedirs(temp_dir + '/assets/dir/mount') os.makedirs(temp_dir + '/assets/dir/dir') os.makedirs(temp_dir + '/assets/mount') - with open(temp_dir + '/assets/index.html', 'w') as index, open( - temp_dir + '/assets/dir/dir/file', 'w' - ) as file, open(temp_dir + '/assets/mount/index.html', 'w') as mount: - index.write('index') - file.write('file') - mount.write('mount') + Path(temp_dir + '/assets/index.html').write_text('index') + Path(temp_dir + '/assets/dir/dir/file').write_text('file') + Path(temp_dir + '/assets/mount/index.html').write_text('mount') try: process = subprocess.Popen( @@ -66,7 +64,7 @@ class TestShareMount(TestApplicationProto): except: pytest.fail('Can\'t run umount process.') - def test_share_mount(self, temp_dir, skip_alert): + def test_static_mount(self, temp_dir, skip_alert): skip_alert(r'opening.*failed') resp = self.get(url='/mount/') @@ -89,7 +87,7 @@ class TestShareMount(TestApplicationProto): assert resp['status'] == 200 assert resp['body'] == 'mount' - def test_share_mount_two_blocks(self, temp_dir, skip_alert): + def test_static_mount_two_blocks(self, temp_dir, skip_alert): skip_alert(r'opening.*failed') os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link') @@ -117,7 +115,7 @@ class TestShareMount(TestApplicationProto): assert self.get(url='/mount/')['status'] == 200, 'block enabled' assert self.head(url='/mount/')['status'] == 403, 'block disabled' - def test_share_mount_chroot(self, temp_dir, skip_alert): + def test_static_mount_chroot(self, temp_dir, skip_alert): skip_alert(r'opening.*failed') assert 'success' in self.conf( diff --git a/test/test_share_symlink.py b/test/test_static_symlink.py index 3970b605..35eb402a 100644 --- a/test/test_share_symlink.py +++ b/test/test_static_symlink.py @@ -1,21 +1,19 @@ import os +from pathlib import Path import pytest from unit.applications.proto import TestApplicationProto -class TestShareSymlink(TestApplicationProto): +class TestStaticSymlink(TestApplicationProto): prerequisites = {'features': ['chroot']} @pytest.fixture(autouse=True) def setup_method_fixture(self, temp_dir): os.makedirs(temp_dir + '/assets/dir/dir') - with open(temp_dir + '/assets/index.html', 'w') as index, open( - temp_dir + '/assets/dir/file', 'w' - ) as file: - index.write('0123456789') - file.write('blah') + Path(temp_dir + '/assets/index.html').write_text('0123456789') + Path(temp_dir + '/assets/dir/file').write_text('blah') self._load_conf( { @@ -24,7 +22,7 @@ class TestShareSymlink(TestApplicationProto): } ) - def test_share_symlink(self, temp_dir, skip_alert): + def test_static_symlink(self, temp_dir, skip_alert): skip_alert(r'opening.*failed') os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link') @@ -48,7 +46,7 @@ class TestShareSymlink(TestApplicationProto): assert self.get(url='/link/file')['status'] == 200, 'symlink enabled' - def test_share_symlink_two_blocks(self, temp_dir, skip_alert): + def test_static_symlink_two_blocks(self, temp_dir, skip_alert): skip_alert(r'opening.*failed') os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link') @@ -76,7 +74,7 @@ class TestShareSymlink(TestApplicationProto): assert self.get(url='/link/file')['status'] == 200, 'block enabled' assert self.head(url='/link/file')['status'] == 403, 'block disabled' - def test_share_symlink_chroot(self, temp_dir, skip_alert): + def test_static_symlink_chroot(self, temp_dir, skip_alert): skip_alert(r'opening.*failed') os.symlink( diff --git a/test/test_share_types.py b/test/test_static_types.py index b5ed97a0..20defddf 100644 --- a/test/test_share_types.py +++ b/test/test_static_types.py @@ -1,12 +1,11 @@ -import os from pathlib import Path import pytest + from unit.applications.proto import TestApplicationProto -from unit.option import option -class TestShareTypes(TestApplicationProto): +class TestStaticTypes(TestApplicationProto): prerequisites = {} @pytest.fixture(autouse=True) @@ -36,7 +35,7 @@ class TestShareTypes(TestApplicationProto): assert resp['status'] == 200, 'status' assert resp['body'] == body, 'body' - def test_share_types_basic(self, temp_dir): + def test_static_types_basic(self, temp_dir): self.action_update({"share": temp_dir + "/assets"}) self.check_body('/index.html', 'index') self.check_body('/file.xml', '.xml') @@ -54,7 +53,7 @@ class TestShareTypes(TestApplicationProto): self.action_update({"share": temp_dir + "/assets", "types": [""]}) assert self.get(url='/file.xml')['status'] == 403, 'no mtype' - def test_share_types_wildcard(self, temp_dir): + def test_static_types_wildcard(self, temp_dir): self.action_update( {"share": temp_dir + "/assets", "types": ["application/*"]} ) @@ -67,7 +66,7 @@ class TestShareTypes(TestApplicationProto): assert self.get(url='/file.xml')['status'] == 403, 'video * mtype xml' self.check_body('/file.mp4', '.mp4') - def test_share_types_negation(self, temp_dir): + def test_static_types_negation(self, temp_dir): self.action_update( {"share": temp_dir + "/assets", "types": ["!application/xml"]} ) @@ -85,7 +84,7 @@ class TestShareTypes(TestApplicationProto): self.check_body('/file.png', '.png') assert self.get(url='/file.jpg')['status'] == 403, 'negation sort jpg' - def test_share_types_regex(self, temp_dir): + def test_static_types_regex(self, temp_dir): self.action_update( {"share": temp_dir + "/assets", "types": ["~text/(html|plain)"]} ) @@ -93,7 +92,7 @@ class TestShareTypes(TestApplicationProto): self.check_body('/file.html', '.html') self.check_body('/file.txt', '.txt') - def test_share_types_case(self, temp_dir): + def test_static_types_case(self, temp_dir): self.action_update( {"share": temp_dir + "/assets", "types": ["!APpliCaTiOn/xMl"]} ) @@ -118,7 +117,7 @@ class TestShareTypes(TestApplicationProto): self.get(url='/file.xml')['status'] == 403 ), 'mixed case video * negation' - def test_share_types_fallback(self, temp_dir): + def test_static_types_fallback(self, temp_dir): assert 'success' in self.conf( [ { @@ -139,7 +138,7 @@ class TestShareTypes(TestApplicationProto): self.check_body('/file.php', '') self.check_body('/file.mp4', '.mp4') - def test_share_types_index(self, temp_dir): + def test_static_types_index(self, temp_dir): self.action_update( {"share": temp_dir + "/assets", "types": "application/xml"} ) @@ -147,7 +146,7 @@ class TestShareTypes(TestApplicationProto): self.check_body('/file.xml', '.xml') assert self.get(url='/file.mp4')['status'] == 403, 'forbidden mtype' - def test_share_types_custom_mime(self, temp_dir): + def test_static_types_custom_mime(self, temp_dir): self._load_conf( { "listeners": {"*:7080": {"pass": "routes"}}, diff --git a/test/test_tls.py b/test/test_tls.py index 0cfeaded..546f0f89 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -665,3 +665,16 @@ basicConstraints = critical,CA:TRUE""" ) assert res['status'] == 200, 'status ok' assert res['body'] == filename + data + + def test_tls_multi_listener(self): + self.load('empty') + + self.certificate() + + self.add_tls() + self.add_tls(port=7081) + + assert self.get_ssl()['status'] == 200, 'listener #1' + + assert self.get_ssl(port=7081)['status'] == 200, 'listener #2' + diff --git a/test/test_tls_sni.py b/test/test_tls_sni.py index 2e5424e2..eba6140a 100644 --- a/test/test_tls_sni.py +++ b/test/test_tls_sni.py @@ -168,6 +168,26 @@ basicConstraints = critical,CA:TRUE""" self.check_cert('alt2.example.com', bundles['example.com']['subj']) self.check_cert('blah', bundles['default']['subj']) + def test_tls_sni_no_hostname(self): + bundles = { + "localhost.com": {"subj": "localhost.com", "alt_names": []}, + "example.com": { + "subj": "example.com", + "alt_names": ["example.com"], + }, + } + self.config_bundles(bundles) + self.add_tls(["localhost.com", "example.com"]) + + resp, sock = self.get_ssl( + headers={'Content-Length': '0', 'Connection': 'close'}, start=True, + ) + assert resp['status'] == 200 + assert ( + sock.getpeercert()['subject'][0][0][1] + == bundles['localhost.com']['subj'] + ) + def test_tls_sni_upper_case(self): bundles = { "localhost.com": {"subj": "LOCALHOST.COM", "alt_names": []}, diff --git a/test/test_variables.py b/test/test_variables.py index 139d867e..d8547b7b 100644 --- a/test/test_variables.py +++ b/test/test_variables.py @@ -100,6 +100,25 @@ class TestVariables(TestApplicationProto): assert self.get(url='/1')['status'] == 200 assert self.get(url='/2')['status'] == 404 + def test_variables_empty(self): + def update_pass(prefix): + assert 'success' in self.conf( + { + "listeners": { + "*:7080": {"pass": prefix + "/$method"}, + }, + }, + ), 'variables empty' + + update_pass("routes"); + assert self.get(url='/1')['status'] == 404 + + update_pass("upstreams"); + assert self.get(url='/2')['status'] == 404 + + update_pass("applications"); + assert self.get(url='/3')['status'] == 404 + def test_variables_invalid(self): def check_variables(routes): assert 'error' in self.conf( diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index c9c2095e..53b27b07 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -52,7 +52,7 @@ class TestApplicationJava(TestApplicationProto): os.makedirs(classes_path) classpath = ( - option.current_dir + '/build/tomcat-servlet-api-9.0.44.jar' + option.current_dir + '/build/tomcat-servlet-api-9.0.52.jar' ) ws_jars = glob.glob( diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py index b399dffd..215aa332 100644 --- a/test/unit/applications/lang/python.py +++ b/test/unit/applications/lang/python.py @@ -44,6 +44,7 @@ class TestApplicationPython(TestApplicationProto): for attr in ( 'callable', + 'environment', 'home', 'limits', 'path', diff --git a/test/unit/applications/lang/ruby.py b/test/unit/applications/lang/ruby.py index 02644584..61d50558 100644 --- a/test/unit/applications/lang/ruby.py +++ b/test/unit/applications/lang/ruby.py @@ -1,24 +1,44 @@ +import os +import shutil + from unit.applications.proto import TestApplicationProto from unit.option import option +from unit.utils import public_dir class TestApplicationRuby(TestApplicationProto): application_type = "ruby" + def prepare_env(self, script): + shutil.copytree( + option.test_dir + '/ruby/' + script, + option.temp_dir + '/ruby/' + script, + ) + + public_dir(option.temp_dir + '/ruby/' + script) + def load(self, script, name='config.ru', **kwargs): - script_path = option.test_dir + '/ruby/' + script + self.prepare_env(script) + + script_path = option.temp_dir + '/ruby/' + script + + app = { + "type": self.get_application_type(), + "processes": {"spare": 0}, + "working_directory": script_path, + "script": script_path + '/' + name, + } + + for key in [ + 'hooks', + ]: + if key in kwargs: + app[key] = kwargs[key] self._load_conf( { "listeners": {"*:7080": {"pass": "applications/" + script}}, - "applications": { - script: { - "type": self.get_application_type(), - "processes": {"spare": 0}, - "working_directory": script_path, - "script": script_path + '/' + name, - } - }, + "applications": {script: app}, }, **kwargs ) diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py index 92754c03..e30d21ff 100644 --- a/test/unit/applications/proto.py +++ b/test/unit/applications/proto.py @@ -47,13 +47,15 @@ class TestApplicationProto(TestControl): if 'applications' in conf: for app in conf['applications'].keys(): app_conf = conf['applications'][app] - if 'user' in kwargs: - app_conf['user'] = kwargs['user'] - if 'group' in kwargs: - app_conf['group'] = kwargs['group'] - - if 'isolation' in kwargs: - app_conf['isolation'] = kwargs['isolation'] + for key in [ + 'user', + 'group', + 'isolation', + 'processes', + 'threads', + ]: + if key in kwargs: + app_conf[key] = kwargs[key] assert 'success' in self.conf(conf), 'load application configuration' diff --git a/test/unit/check/isolation.py b/test/unit/check/isolation.py index 7c83ae35..43c8842f 100644 --- a/test/unit/check/isolation.py +++ b/test/unit/check/isolation.py @@ -3,6 +3,7 @@ import os from unit.applications.lang.go import TestApplicationGo from unit.applications.lang.java import TestApplicationJava +from unit.applications.lang.ruby import TestApplicationRuby from unit.applications.lang.node import TestApplicationNode from unit.applications.proto import TestApplicationProto from unit.http import TestHTTP @@ -65,14 +66,16 @@ def check_isolation(): } elif 'ruby' in available['modules']: + TestApplicationRuby().prepare_env('empty') + conf = { "listeners": {"*:7080": {"pass": "applications/empty"}}, "applications": { "empty": { "type": "ruby", "processes": {"spare": 0}, - "working_directory": option.test_dir + "/ruby/empty", - "script": option.test_dir + "/ruby/empty/config.ru", + "working_directory": option.temp_dir + "/ruby/empty", + "script": option.temp_dir + "/ruby/empty/config.ru", "isolation": {"namespaces": {"credential": True}}, } }, diff --git a/test/unit/http.py b/test/unit/http.py index 797b7681..dcfcd232 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -45,7 +45,7 @@ class TestHTTP: sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) if 'wrapper' in kwargs: - server_hostname = headers.get('Host', 'localhost') + server_hostname = headers.get('Host', None) sock = kwargs['wrapper'](sock, server_hostname=server_hostname) connect_args = addr if sock_type == 'unix' else (addr, port) diff --git a/test/unit/utils.py b/test/unit/utils.py index a627e9f5..43aaa81b 100644 --- a/test/unit/utils.py +++ b/test/unit/utils.py @@ -1,3 +1,4 @@ +import glob import os import socket import subprocess @@ -16,8 +17,8 @@ def public_dir(path): os.chmod(os.path.join(root, f), 0o777) -def waitforfiles(*files): - for i in range(50): +def waitforfiles(*files, timeout=50): + for i in range(timeout): wait = False for f in files: @@ -33,6 +34,21 @@ def waitforfiles(*files): return False +def waitforglob(pattern, count=1, timeout=50): + for i in range(timeout): + n = 0 + + for f in glob.glob(pattern): + n += 1 + + if n == count: + return True + + time.sleep(0.1) + + return False + + def waitforsocket(port): for i in range(50): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: @@ -72,8 +88,8 @@ def sysctl(): return out -def waitformount(template, wait=50): - for i in range(wait): +def waitformount(template, timeout=50): + for i in range(timeout): if findmnt().find(template) != -1: return True @@ -82,8 +98,8 @@ def waitformount(template, wait=50): return False -def waitforunmount(template, wait=50): - for i in range(wait): +def waitforunmount(template, timeout=50): + for i in range(timeout): if findmnt().find(template) == -1: return True |