diff options
author | Andrei Zeliankou <zelenkov@nginx.com> | 2024-02-07 16:29:11 +0000 |
---|---|---|
committer | andrey-zelenkov <xim.andrew@gmail.com> | 2024-02-21 17:40:25 +0000 |
commit | cabea47de7287ea421317c92bc868c2d451cb757 (patch) | |
tree | 69446d92c54c4fee2d1006f99b414c35072fcd2c /test/test_procman.py | |
parent | dbd9d25f17f4e5f248f6fb2071a5b20a43e27b24 (diff) | |
download | unit-cabea47de7287ea421317c92bc868c2d451cb757.tar.gz unit-cabea47de7287ea421317c92bc868c2d451cb757.tar.bz2 |
Tests: renamed test_python_procman.py since it's not Python-specific
Python applications are used only to generate responses here and can be
replaced by applications written in any other language. While the
"_python" prefix is used to indicate that the file contains tests
specific to the Python module.
Diffstat (limited to 'test/test_procman.py')
-rw-r--r-- | test/test_procman.py | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/test/test_procman.py b/test/test_procman.py new file mode 100644 index 00000000..b4378c4f --- /dev/null +++ b/test/test_procman.py @@ -0,0 +1,303 @@ +import re +import shutil +import subprocess +import time + +import pytest + +from unit.applications.lang.python import ApplicationPython +from unit.option import option + +prerequisites = {'modules': {'python': 'any'}} + +client = ApplicationPython() + + +@pytest.fixture(autouse=True) +def setup_method_fixture(temp_dir): + client.app_name = f'app-{temp_dir.split("/")[-1]}' + client.app_proc = f'applications/{client.app_name}/processes' + client.load('empty', client.app_name) + + +def pids_for_process(): + time.sleep(0.2) + + output = subprocess.check_output(['ps', 'ax']) + + pids = set() + for m in re.findall( + fr'.*unit: "{client.app_name}" application', output.decode() + ): + pids.add(re.search(r'^\s*(\d+)', m).group(1)) + + return pids + + +def conf_proc(conf, path=None): + if path is None: + path = client.app_proc + + assert 'success' in client.conf(conf, path), 'configure processes' + + +def stop_all(): + assert 'success' in client.conf({"listeners": {}, "applications": {}}) + + assert len(pids_for_process()) == 0, 'stop all' + + +@pytest.mark.skip('not yet') +def test_python_processes_idle_timeout_zero(): + conf_proc({"spare": 0, "max": 2, "idle_timeout": 0}) + + client.get() + assert len(pids_for_process()) == 0, 'idle timeout 0' + + +def test_python_prefork(): + conf_proc('2') + + pids = pids_for_process() + assert len(pids) == 2, 'prefork 2' + + client.get() + assert pids_for_process() == pids, 'prefork still 2' + + conf_proc('4') + + pids = pids_for_process() + assert len(pids) == 4, 'prefork 4' + + client.get() + assert pids_for_process() == pids, 'prefork still 4' + + stop_all() + + +@pytest.mark.skip('not yet') +def test_python_prefork_same_processes(): + conf_proc('2') + pids = pids_for_process() + + conf_proc('4') + pids_new = pids_for_process() + + assert pids.issubset(pids_new), 'prefork same processes' + + +def test_python_ondemand(): + conf_proc({"spare": 0, "max": 8, "idle_timeout": 1}) + + assert len(pids_for_process()) == 0, 'on-demand 0' + + client.get() + pids = pids_for_process() + assert len(pids) == 1, 'on-demand 1' + + client.get() + assert pids_for_process() == pids, 'on-demand still 1' + + time.sleep(1) + + assert len(pids_for_process()) == 0, 'on-demand stop idle' + + stop_all() + + +def test_python_scale_updown(): + conf_proc({"spare": 2, "max": 8, "idle_timeout": 1}) + + pids = pids_for_process() + assert len(pids) == 2, 'updown 2' + + client.get() + pids_new = pids_for_process() + assert len(pids_new) == 3, 'updown 3' + assert pids.issubset(pids_new), 'updown 3 only 1 new' + + client.get() + assert pids_for_process() == pids_new, 'updown still 3' + + time.sleep(1) + + pids = pids_for_process() + assert len(pids) == 2, 'updown stop idle' + + client.get() + pids_new = pids_for_process() + assert len(pids_new) == 3, 'updown again 3' + assert pids.issubset(pids_new), 'updown again 3 only 1 new' + + stop_all() + + +def test_python_reconfigure(): + conf_proc({"spare": 2, "max": 6, "idle_timeout": 1}) + + pids = pids_for_process() + assert len(pids) == 2, 'reconf 2' + + client.get() + pids_new = pids_for_process() + assert len(pids_new) == 3, 'reconf 3' + assert pids.issubset(pids_new), 'reconf 3 only 1 new' + + conf_proc('6', f'{client.app_proc}/spare') + + pids = pids_for_process() + assert len(pids) == 6, 'reconf 6' + + client.get() + assert pids_for_process() == pids, 'reconf still 6' + + stop_all() + + +def test_python_idle_timeout(): + conf_proc({"spare": 0, "max": 6, "idle_timeout": 2}) + + client.get() + pids = pids_for_process() + assert len(pids) == 1, 'idle timeout 1' + + time.sleep(1) + + client.get() + + time.sleep(1) + + pids_new = pids_for_process() + assert len(pids_new) == 1, 'idle timeout still 1' + assert pids_for_process() == pids, 'idle timeout still 1 same pid' + + time.sleep(1) + + assert len(pids_for_process()) == 0, 'idle timed out' + + +def test_python_processes_connection_keepalive(): + conf_proc({"spare": 0, "max": 6, "idle_timeout": 2}) + + (_, sock) = client.get( + headers={'Host': 'localhost', 'Connection': 'keep-alive'}, + start=True, + read_timeout=1, + ) + assert len(pids_for_process()) == 1, 'keepalive connection 1' + + time.sleep(2) + + assert len(pids_for_process()) == 0, 'keepalive connection 0' + + sock.close() + + +def test_python_processes_access(): + conf_proc('1') + + path = f'/{client.app_proc}' + assert 'error' in client.conf_get(f'{path}/max') + assert 'error' in client.conf_get(f'{path}/spare') + assert 'error' in client.conf_get(f'{path}/idle_timeout') + + +def test_python_processes_invalid(): + assert 'error' in client.conf( + {"spare": -1}, client.app_proc + ), 'negative spare' + assert 'error' in client.conf({"max": -1}, client.app_proc), 'negative max' + assert 'error' in client.conf( + {"idle_timeout": -1}, client.app_proc + ), 'negative idle_timeout' + assert 'error' in client.conf( + {"spare": 2}, client.app_proc + ), 'spare gt max default' + assert 'error' in client.conf( + {"spare": 2, "max": 1}, client.app_proc + ), 'spare gt max' + assert 'error' in client.conf( + {"spare": 0, "max": 0}, client.app_proc + ), 'max zero' + + +def test_python_restart(temp_dir): + shutil.copyfile( + f'{option.test_dir}/python/restart/v1.py', f'{temp_dir}/wsgi.py' + ) + + client.load( + temp_dir, + name=client.app_name, + processes=1, + environment={'PYTHONDONTWRITEBYTECODE': '1'}, + ) + + b = client.get()['body'] + assert b == "v1", 'process started' + + shutil.copyfile( + f'{option.test_dir}/python/restart/v2.py', f'{temp_dir}/wsgi.py' + ) + + b = client.get()['body'] + assert b == "v1", 'still old process' + + assert 'success' in client.conf_get( + f'/control/applications/{client.app_name}/restart' + ), 'restart processes' + + b = client.get()['body'] + assert b == "v2", 'new process started' + + assert 'error' in client.conf_get( + '/control/applications/blah/restart' + ), 'application incorrect' + + assert 'error' in client.conf_delete( + f'/control/applications/{client.app_name}/restart' + ), 'method incorrect' + + +def test_python_restart_multi(): + conf_proc('2') + + pids = pids_for_process() + assert len(pids) == 2, 'restart 2 started' + + assert 'success' in client.conf_get( + f'/control/applications/{client.app_name}/restart' + ), 'restart processes' + + new_pids = 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(): + client.load( + 'restart', + name=client.app_name, + module="longstart", + processes={"spare": 1, "max": 2, "idle_timeout": 5}, + ) + + assert len(pids_for_process()) == 1, 'longstarts == 1' + + client.get() + + pids = pids_for_process() + assert len(pids) == 2, 'longstarts == 2' + + assert 'success' in client.conf_get( + f'/control/applications/{client.app_name}/restart' + ), 'restart processes' + + # wait for longstarted app + time.sleep(2) + + new_pids = pids_for_process() + assert len(new_pids) == 1, 'restart 1' + + assert len(new_pids.intersection(pids)) == 0, 'restart all new' |