xref: /unit/test/conftest.py (revision 2616:ab2896c980ab)
11596Szelenkov@nginx.comimport fcntl
21803Szelenkov@nginx.comimport inspect
31803Szelenkov@nginx.comimport json
41596Szelenkov@nginx.comimport os
51635Szelenkov@nginx.comimport re
61621Szelenkov@nginx.comimport shutil
71596Szelenkov@nginx.comimport signal
81596Szelenkov@nginx.comimport stat
91596Szelenkov@nginx.comimport subprocess
101596Szelenkov@nginx.comimport sys
111596Szelenkov@nginx.comimport tempfile
121596Szelenkov@nginx.comimport time
131654Szelenkov@nginx.comfrom multiprocessing import Process
14*2616Szelenkov@nginx.comfrom pathlib import Path
151596Szelenkov@nginx.com
161635Szelenkov@nginx.comimport pytest
17*2616Szelenkov@nginx.com
18*2616Szelenkov@nginx.comfrom unit.check.check_prerequisites import check_prerequisites
192488Szelenkov@nginx.comfrom unit.check.discover_available import discover_available
202491Szelenkov@nginx.comfrom unit.http import HTTP1
211902Szelenkov@nginx.comfrom unit.log import Log
222481Szelenkov@nginx.comfrom unit.log import print_log_on_assert
231730Szelenkov@nginx.comfrom unit.option import option
242187Szelenkov@nginx.comfrom unit.status import Status
252264Szelenkov@nginx.comfrom unit.utils import check_findmnt
261735Szelenkov@nginx.comfrom unit.utils import public_dir
271735Szelenkov@nginx.comfrom unit.utils import waitforfiles
282264Szelenkov@nginx.comfrom unit.utils import waitforunmount
291621Szelenkov@nginx.com
301596Szelenkov@nginx.com
311596Szelenkov@nginx.comdef pytest_addoption(parser):
321596Szelenkov@nginx.com    parser.addoption(
331596Szelenkov@nginx.com        "--detailed",
341596Szelenkov@nginx.com        default=False,
351596Szelenkov@nginx.com        action="store_true",
361596Szelenkov@nginx.com        help="Detailed output for tests",
371596Szelenkov@nginx.com    )
381596Szelenkov@nginx.com    parser.addoption(
391743Szelenkov@nginx.com        "--print-log",
401596Szelenkov@nginx.com        default=False,
411596Szelenkov@nginx.com        action="store_true",
421596Szelenkov@nginx.com        help="Print unit.log to stdout in case of errors",
431596Szelenkov@nginx.com    )
441596Szelenkov@nginx.com    parser.addoption(
451743Szelenkov@nginx.com        "--save-log",
461596Szelenkov@nginx.com        default=False,
471596Szelenkov@nginx.com        action="store_true",
481596Szelenkov@nginx.com        help="Save unit.log after the test execution",
491596Szelenkov@nginx.com    )
501596Szelenkov@nginx.com    parser.addoption(
511596Szelenkov@nginx.com        "--unsafe",
521596Szelenkov@nginx.com        default=False,
531596Szelenkov@nginx.com        action="store_true",
541596Szelenkov@nginx.com        help="Run unsafe tests",
551596Szelenkov@nginx.com    )
561761Sdefan@nginx.com    parser.addoption(
571761Sdefan@nginx.com        "--user",
581761Sdefan@nginx.com        type=str,
591761Sdefan@nginx.com        help="Default user for non-privileged processes of unitd",
601761Sdefan@nginx.com    )
611803Szelenkov@nginx.com    parser.addoption(
621844Szelenkov@nginx.com        "--fds-threshold",
631844Szelenkov@nginx.com        type=int,
641844Szelenkov@nginx.com        default=0,
651844Szelenkov@nginx.com        help="File descriptors threshold",
661844Szelenkov@nginx.com    )
671844Szelenkov@nginx.com    parser.addoption(
681803Szelenkov@nginx.com        "--restart",
691803Szelenkov@nginx.com        default=False,
701803Szelenkov@nginx.com        action="store_true",
711803Szelenkov@nginx.com        help="Force Unit to restart after every test",
721803Szelenkov@nginx.com    )
731596Szelenkov@nginx.com
741596Szelenkov@nginx.com
751596Szelenkov@nginx.comunit_instance = {}
761654Szelenkov@nginx.com_processes = []
771914Szelenkov@nginx.com_fds_info = {
781844Szelenkov@nginx.com    'main': {'fds': 0, 'skip': False},
791844Szelenkov@nginx.com    'router': {'name': 'unit: router', 'pid': -1, 'fds': 0, 'skip': False},
801844Szelenkov@nginx.com    'controller': {
811844Szelenkov@nginx.com        'name': 'unit: controller',
821844Szelenkov@nginx.com        'pid': -1,
831844Szelenkov@nginx.com        'fds': 0,
841844Szelenkov@nginx.com        'skip': False,
851844Szelenkov@nginx.com    },
861844Szelenkov@nginx.com}
872491Szelenkov@nginx.comhttp = HTTP1()
882264Szelenkov@nginx.comis_findmnt = check_findmnt()
891596Szelenkov@nginx.com
901848Szelenkov@nginx.com
911596Szelenkov@nginx.comdef pytest_configure(config):
921730Szelenkov@nginx.com    option.config = config.option
931730Szelenkov@nginx.com
941730Szelenkov@nginx.com    option.detailed = config.option.detailed
951844Szelenkov@nginx.com    option.fds_threshold = config.option.fds_threshold
961730Szelenkov@nginx.com    option.print_log = config.option.print_log
971730Szelenkov@nginx.com    option.save_log = config.option.save_log
981730Szelenkov@nginx.com    option.unsafe = config.option.unsafe
991761Sdefan@nginx.com    option.user = config.option.user
1001803Szelenkov@nginx.com    option.restart = config.option.restart
1011596Szelenkov@nginx.com
1021596Szelenkov@nginx.com    option.generated_tests = {}
1031596Szelenkov@nginx.com    option.current_dir = os.path.abspath(
1041596Szelenkov@nginx.com        os.path.join(os.path.dirname(__file__), os.pardir)
1051596Szelenkov@nginx.com    )
1062330Szelenkov@nginx.com    option.test_dir = f'{option.current_dir}/test'
1071596Szelenkov@nginx.com
1081757St.nateldemoura@f5.com    option.cache_dir = tempfile.mkdtemp(prefix='unit-test-cache-')
1091757St.nateldemoura@f5.com    public_dir(option.cache_dir)
1101757St.nateldemoura@f5.com
1111596Szelenkov@nginx.com    # set stdout to non-blocking
1121596Szelenkov@nginx.com
1131596Szelenkov@nginx.com    if option.detailed or option.print_log:
1141596Szelenkov@nginx.com        fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, 0)
1151596Szelenkov@nginx.com
1161596Szelenkov@nginx.com
1171596Szelenkov@nginx.comdef pytest_generate_tests(metafunc):
1182491Szelenkov@nginx.com    module = metafunc.module
1191848Szelenkov@nginx.com    if (
1202491Szelenkov@nginx.com        not hasattr(module, 'client')
1212491Szelenkov@nginx.com        or not hasattr(module.client, 'application_type')
1222491Szelenkov@nginx.com        or module.client.application_type is None
1232491Szelenkov@nginx.com        or module.client.application_type == 'external'
1241848Szelenkov@nginx.com    ):
1251596Szelenkov@nginx.com        return
1261596Szelenkov@nginx.com
1272491Szelenkov@nginx.com    app_type = module.client.application_type
1281596Szelenkov@nginx.com
1291611Smax.romanov@nginx.com    def generate_tests(versions):
1302488Szelenkov@nginx.com        if not versions:
1312488Szelenkov@nginx.com            pytest.skip('no available module versions')
1322488Szelenkov@nginx.com
1331611Smax.romanov@nginx.com        metafunc.fixturenames.append('tmp_ct')
1341630Smax.romanov@nginx.com        metafunc.parametrize('tmp_ct', versions)
1351611Smax.romanov@nginx.com
1361630Smax.romanov@nginx.com        for version in versions:
1371611Smax.romanov@nginx.com            option.generated_tests[
1382330Szelenkov@nginx.com                f'{metafunc.function.__name__} [{version}]'
1392491Szelenkov@nginx.com            ] = f'{app_type} {version}'
1401611Smax.romanov@nginx.com
1411596Szelenkov@nginx.com    # take available module from option and generate tests for each version
1421596Szelenkov@nginx.com
1432488Szelenkov@nginx.com    available_modules = option.available['modules']
1441596Szelenkov@nginx.com
1452488Szelenkov@nginx.com    for module, version in metafunc.module.prerequisites['modules'].items():
1462488Szelenkov@nginx.com        if module in available_modules and available_modules[module]:
1472488Szelenkov@nginx.com            available_versions = available_modules[module]
1482488Szelenkov@nginx.com
1492488Szelenkov@nginx.com            if version == 'all':
1501611Smax.romanov@nginx.com                generate_tests(available_versions)
1511596Szelenkov@nginx.com
1522488Szelenkov@nginx.com            elif version == 'any':
1532330Szelenkov@nginx.com                option.generated_tests[
1542330Szelenkov@nginx.com                    metafunc.function.__name__
1552491Szelenkov@nginx.com                ] = f'{app_type} {available_versions[0]}'
1562488Szelenkov@nginx.com            elif callable(version):
1572488Szelenkov@nginx.com                generate_tests(list(filter(version, available_versions)))
1581611Smax.romanov@nginx.com
1591596Szelenkov@nginx.com            else:
1601611Smax.romanov@nginx.com                raise ValueError(
1612330Szelenkov@nginx.com                    f'''
1622488Szelenkov@nginx.comUnexpected prerequisite version "{version}" for module "{module}".
1632488Szelenkov@nginx.com'all', 'any' or callable expected.'''
1641611Smax.romanov@nginx.com                )
1651596Szelenkov@nginx.com
1661596Szelenkov@nginx.com
1672477Szelenkov@nginx.comdef pytest_sessionstart():
1681596Szelenkov@nginx.com    unit = unit_run()
1691596Szelenkov@nginx.com
1702488Szelenkov@nginx.com    discover_available(unit)
1711621Szelenkov@nginx.com
1722488Szelenkov@nginx.com    _clear_conf()
1731803Szelenkov@nginx.com
1741596Szelenkov@nginx.com    unit_stop()
1751596Szelenkov@nginx.com
1762481Szelenkov@nginx.com    Log.check_alerts()
1771730Szelenkov@nginx.com
1781803Szelenkov@nginx.com    if option.restart:
1792488Szelenkov@nginx.com        shutil.rmtree(unit['temp_dir'])
1802370Szelenkov@nginx.com    else:
1812370Szelenkov@nginx.com        _clear_temp_dir()
1821596Szelenkov@nginx.com
1831848Szelenkov@nginx.com
1841654Szelenkov@nginx.com@pytest.hookimpl(tryfirst=True, hookwrapper=True)
1852477Szelenkov@nginx.comdef pytest_runtest_makereport(item):
1861654Szelenkov@nginx.com    # execute all other hooks to obtain the report object
1871654Szelenkov@nginx.com    outcome = yield
1881654Szelenkov@nginx.com    rep = outcome.get_result()
1891654Szelenkov@nginx.com
1901654Szelenkov@nginx.com    # set a report attribute for each phase of a call, which can
1911654Szelenkov@nginx.com    # be "setup", "call", "teardown"
1921654Szelenkov@nginx.com
1932330Szelenkov@nginx.com    setattr(item, f'rep_{rep.when}', rep)
1941654Szelenkov@nginx.com
1951654Szelenkov@nginx.com
1962488Szelenkov@nginx.com@pytest.fixture(scope='module', autouse=True)
1972488Szelenkov@nginx.comdef check_prerequisites_module(request):
1982488Szelenkov@nginx.com    if hasattr(request.module, 'prerequisites'):
1992488Szelenkov@nginx.com        check_prerequisites(request.module.prerequisites)
2001741Szelenkov@nginx.com
2011741Szelenkov@nginx.com
2021654Szelenkov@nginx.com@pytest.fixture(autouse=True)
2031654Szelenkov@nginx.comdef run(request):
2041654Szelenkov@nginx.com    unit = unit_run()
2051654Szelenkov@nginx.com
2061596Szelenkov@nginx.com    option.skip_alerts = [
2071596Szelenkov@nginx.com        r'read signalfd\(4\) failed',
2081596Szelenkov@nginx.com        r'sendmsg.+failed',
2091596Szelenkov@nginx.com        r'recvmsg.+failed',
2101596Szelenkov@nginx.com    ]
2111596Szelenkov@nginx.com    option.skip_sanitizer = False
2121596Szelenkov@nginx.com
2131914Szelenkov@nginx.com    _fds_info['main']['skip'] = False
2141914Szelenkov@nginx.com    _fds_info['router']['skip'] = False
2151914Szelenkov@nginx.com    _fds_info['controller']['skip'] = False
2161844Szelenkov@nginx.com
2171654Szelenkov@nginx.com    yield
2181654Szelenkov@nginx.com
2191654Szelenkov@nginx.com    # stop unit
2201654Szelenkov@nginx.com
2211803Szelenkov@nginx.com    error_stop_unit = unit_stop()
2221803Szelenkov@nginx.com    error_stop_processes = stop_processes()
2231803Szelenkov@nginx.com
2241803Szelenkov@nginx.com    # prepare log
2251803Szelenkov@nginx.com
2262488Szelenkov@nginx.com    with Log.open() as f:
2271803Szelenkov@nginx.com        log = f.read()
2281850Smax.romanov@nginx.com        Log.set_pos(f.tell())
2291654Szelenkov@nginx.com
2301803Szelenkov@nginx.com    if not option.save_log and option.restart:
2311803Szelenkov@nginx.com        shutil.rmtree(unit['temp_dir'])
2321850Smax.romanov@nginx.com        Log.set_pos(0)
2331803Szelenkov@nginx.com
2341803Szelenkov@nginx.com    # clean temp_dir before the next test
2351654Szelenkov@nginx.com
2361803Szelenkov@nginx.com    if not option.restart:
2372488Szelenkov@nginx.com        _clear_conf(log=log)
2382370Szelenkov@nginx.com        _clear_temp_dir()
2391654Szelenkov@nginx.com
2401914Szelenkov@nginx.com    # check descriptors
2411844Szelenkov@nginx.com
2421914Szelenkov@nginx.com    _check_fds(log=log)
2431844Szelenkov@nginx.com
2442074Szelenkov@nginx.com    # check processes id's and amount
2452074Szelenkov@nginx.com
2462074Szelenkov@nginx.com    _check_processes()
2472074Szelenkov@nginx.com
2481654Szelenkov@nginx.com    # print unit.log in case of error
2491654Szelenkov@nginx.com
2501706Smax.romanov@nginx.com    if hasattr(request.node, 'rep_call') and request.node.rep_call.failed:
2512481Szelenkov@nginx.com        Log.print_log(log)
2521803Szelenkov@nginx.com
2531803Szelenkov@nginx.com    if error_stop_unit or error_stop_processes:
2542481Szelenkov@nginx.com        Log.print_log(log)
2551654Szelenkov@nginx.com
2561803Szelenkov@nginx.com    # check unit.log for errors
2571654Szelenkov@nginx.com
2581803Szelenkov@nginx.com    assert error_stop_unit is None, 'stop unit'
2591803Szelenkov@nginx.com    assert error_stop_processes is None, 'stop processes'
2601803Szelenkov@nginx.com
2612481Szelenkov@nginx.com    Log.check_alerts(log=log)
2621654Szelenkov@nginx.com
2631848Szelenkov@nginx.com
2641986Szelenkov@nginx.comdef unit_run(state_dir=None):
2651596Szelenkov@nginx.com    global unit_instance
2661803Szelenkov@nginx.com
2671803Szelenkov@nginx.com    if not option.restart and 'unitd' in unit_instance:
2681803Szelenkov@nginx.com        return unit_instance
2691803Szelenkov@nginx.com
270*2616Szelenkov@nginx.com    builddir = f'{option.current_dir}/build'
271*2616Szelenkov@nginx.com    libdir = f'{builddir}/lib'
2722397Salx@nginx.com    modulesdir = f'{libdir}/unit/modules'
273*2616Szelenkov@nginx.com    sbindir = f'{builddir}/sbin'
274*2616Szelenkov@nginx.com    unitd = f'{sbindir}/unitd'
2751596Szelenkov@nginx.com
276*2616Szelenkov@nginx.com    if not Path(unitd).is_file():
277*2616Szelenkov@nginx.com        sys.exit('Could not find unit')
2781596Szelenkov@nginx.com
279*2616Szelenkov@nginx.com    temporary_dir = tempfile.mkdtemp(prefix='unit-test-')
280*2616Szelenkov@nginx.com    option.temp_dir = temporary_dir
281*2616Szelenkov@nginx.com    public_dir(temporary_dir)
2821596Szelenkov@nginx.com
283*2616Szelenkov@nginx.com    if oct(stat.S_IMODE(Path(builddir).stat().st_mode)) != '0o777':
2842397Salx@nginx.com        public_dir(builddir)
2851596Szelenkov@nginx.com
286*2616Szelenkov@nginx.com    statedir = f'{temporary_dir}/state' if state_dir is None else state_dir
287*2616Szelenkov@nginx.com    Path(statedir).mkdir(exist_ok=True)
2881596Szelenkov@nginx.com
289*2616Szelenkov@nginx.com    control_sock = f'{temporary_dir}/control.unit.sock'
2902330Szelenkov@nginx.com
2911761Sdefan@nginx.com    unitd_args = [
2921761Sdefan@nginx.com        unitd,
2931761Sdefan@nginx.com        '--no-daemon',
2942314Salx.manpages@gmail.com        '--modulesdir',
2952397Salx@nginx.com        modulesdir,
2962396Salx@nginx.com        '--statedir',
2972397Salx@nginx.com        statedir,
2981761Sdefan@nginx.com        '--pid',
299*2616Szelenkov@nginx.com        f'{temporary_dir}/unit.pid',
3001761Sdefan@nginx.com        '--log',
301*2616Szelenkov@nginx.com        f'{temporary_dir}/unit.log',
3021761Sdefan@nginx.com        '--control',
303*2616Szelenkov@nginx.com        f'unix:{temporary_dir}/control.unit.sock',
3042314Salx.manpages@gmail.com        '--tmpdir',
305*2616Szelenkov@nginx.com        temporary_dir,
3061761Sdefan@nginx.com    ]
3071761Sdefan@nginx.com
3081761Sdefan@nginx.com    if option.user:
3091761Sdefan@nginx.com        unitd_args.extend(['--user', option.user])
3101761Sdefan@nginx.com
311*2616Szelenkov@nginx.com    with open(f'{temporary_dir}/unit.log', 'w', encoding='utf-8') as log:
3121761Sdefan@nginx.com        unit_instance['process'] = subprocess.Popen(unitd_args, stderr=log)
3131596Szelenkov@nginx.com
3142330Szelenkov@nginx.com    if not waitforfiles(control_sock):
3152481Szelenkov@nginx.com        Log.print_log()
316*2616Szelenkov@nginx.com        sys.exit('Could not start unit')
3171596Szelenkov@nginx.com
318*2616Szelenkov@nginx.com    unit_instance['temp_dir'] = temporary_dir
3192330Szelenkov@nginx.com    unit_instance['control_sock'] = control_sock
3201596Szelenkov@nginx.com    unit_instance['unitd'] = unitd
3211596Szelenkov@nginx.com
322*2616Szelenkov@nginx.com    unit_instance['pid'] = (
323*2616Szelenkov@nginx.com        Path(f'{temporary_dir}/unit.pid').read_text(encoding='utf-8').rstrip()
324*2616Szelenkov@nginx.com    )
3251844Szelenkov@nginx.com
3261986Szelenkov@nginx.com    if state_dir is None:
3272488Szelenkov@nginx.com        _clear_conf()
3281844Szelenkov@nginx.com
3291914Szelenkov@nginx.com    _fds_info['main']['fds'] = _count_fds(unit_instance['pid'])
3301844Szelenkov@nginx.com
3311914Szelenkov@nginx.com    router = _fds_info['router']
3321844Szelenkov@nginx.com    router['pid'] = pid_by_name(router['name'])
3331844Szelenkov@nginx.com    router['fds'] = _count_fds(router['pid'])
3341844Szelenkov@nginx.com
3351914Szelenkov@nginx.com    controller = _fds_info['controller']
3361844Szelenkov@nginx.com    controller['pid'] = pid_by_name(controller['name'])
3371844Szelenkov@nginx.com    controller['fds'] = _count_fds(controller['pid'])
3381844Szelenkov@nginx.com
3392187Szelenkov@nginx.com    Status._check_zeros()
3402187Szelenkov@nginx.com
3411596Szelenkov@nginx.com    return unit_instance
3421596Szelenkov@nginx.com
3431596Szelenkov@nginx.com
3441596Szelenkov@nginx.comdef unit_stop():
3451803Szelenkov@nginx.com    if not option.restart:
3461803Szelenkov@nginx.com        if inspect.stack()[1].function.startswith('test_'):
3471803Szelenkov@nginx.com            pytest.skip('no restart mode')
3481803Szelenkov@nginx.com
3491803Szelenkov@nginx.com        return
3501803Szelenkov@nginx.com
3512074Szelenkov@nginx.com    # check zombies
3522074Szelenkov@nginx.com
3532074Szelenkov@nginx.com    out = subprocess.check_output(
3542074Szelenkov@nginx.com        ['ps', 'ax', '-o', 'state', '-o', 'ppid']
3552074Szelenkov@nginx.com    ).decode()
3562074Szelenkov@nginx.com    z_ppids = re.findall(r'Z\s*(\d+)', out)
3572074Szelenkov@nginx.com    assert unit_instance['pid'] not in z_ppids, 'no zombies'
3582074Szelenkov@nginx.com
3592074Szelenkov@nginx.com    # terminate unit
3602074Szelenkov@nginx.com
3611596Szelenkov@nginx.com    p = unit_instance['process']
3621596Szelenkov@nginx.com
3631596Szelenkov@nginx.com    if p.poll() is not None:
3641596Szelenkov@nginx.com        return
3651596Szelenkov@nginx.com
3661596Szelenkov@nginx.com    p.send_signal(signal.SIGQUIT)
3671596Szelenkov@nginx.com
3681596Szelenkov@nginx.com    try:
3691596Szelenkov@nginx.com        retcode = p.wait(15)
3701596Szelenkov@nginx.com        if retcode:
3712330Szelenkov@nginx.com            return f'Child process terminated with code {retcode}'
3721706Smax.romanov@nginx.com
3731706Smax.romanov@nginx.com    except KeyboardInterrupt:
3741706Smax.romanov@nginx.com        p.kill()
3751706Smax.romanov@nginx.com        raise
3761706Smax.romanov@nginx.com
3771596Szelenkov@nginx.com    except:
3781596Szelenkov@nginx.com        p.kill()
3791596Szelenkov@nginx.com        return 'Could not terminate unit'
3801596Szelenkov@nginx.com
3811596Szelenkov@nginx.com
3821914Szelenkov@nginx.com@print_log_on_assert
3832488Szelenkov@nginx.comdef _clear_conf(*, log=None):
3842488Szelenkov@nginx.com    sock = unit_instance['control_sock']
3852488Szelenkov@nginx.com
3861805Szelenkov@nginx.com    resp = http.put(
3871803Szelenkov@nginx.com        url='/config',
3881803Szelenkov@nginx.com        sock_type='unix',
3891803Szelenkov@nginx.com        addr=sock,
3901803Szelenkov@nginx.com        body=json.dumps({"listeners": {}, "applications": {}}),
3911803Szelenkov@nginx.com    )['body']
3921803Szelenkov@nginx.com
3931914Szelenkov@nginx.com    assert 'success' in resp, 'clear conf'
3941805Szelenkov@nginx.com
3952459Szelenkov@nginx.com    def get(url):
3962459Szelenkov@nginx.com        return http.get(url=url, sock_type='unix', addr=sock)['body']
3972459Szelenkov@nginx.com
3982459Szelenkov@nginx.com    def delete(url):
3992459Szelenkov@nginx.com        return http.delete(url=url, sock_type='unix', addr=sock)['body']
4001808Szelenkov@nginx.com
4012488Szelenkov@nginx.com    if (
4022488Szelenkov@nginx.com        'openssl' in option.available['modules']
4032488Szelenkov@nginx.com        and option.available['modules']['openssl']
4042488Szelenkov@nginx.com    ):
4052459Szelenkov@nginx.com        try:
4062459Szelenkov@nginx.com            certs = json.loads(get('/certificates')).keys()
4072459Szelenkov@nginx.com
4082459Szelenkov@nginx.com        except json.JSONDecodeError:
4092459Szelenkov@nginx.com            pytest.fail("Can't parse certificates list.")
4101805Szelenkov@nginx.com
4112459Szelenkov@nginx.com        for cert in certs:
4122459Szelenkov@nginx.com            assert 'success' in delete(f'/certificates/{cert}'), 'delete cert'
4131805Szelenkov@nginx.com
4142488Szelenkov@nginx.com    if (
4152488Szelenkov@nginx.com        'njs' in option.available['modules']
4162488Szelenkov@nginx.com        and option.available['modules']['njs']
4172488Szelenkov@nginx.com    ):
4182459Szelenkov@nginx.com        try:
4192459Szelenkov@nginx.com            scripts = json.loads(get('/js_modules')).keys()
4201805Szelenkov@nginx.com
4212459Szelenkov@nginx.com        except json.JSONDecodeError:
4222459Szelenkov@nginx.com            pytest.fail("Can't parse njs modules list.")
4231914Szelenkov@nginx.com
4242459Szelenkov@nginx.com        for script in scripts:
4252459Szelenkov@nginx.com            assert 'success' in delete(f'/js_modules/{script}'), 'delete script'
4261914Szelenkov@nginx.com
4272482Szelenkov@nginx.com
4282370Szelenkov@nginx.comdef _clear_temp_dir():
429*2616Szelenkov@nginx.com    temporary_dir = unit_instance['temp_dir']
4302370Szelenkov@nginx.com
431*2616Szelenkov@nginx.com    if is_findmnt and not waitforunmount(temporary_dir, timeout=600):
432*2616Szelenkov@nginx.com        sys.exit('Could not unmount filesystems in tmpdir ({temporary_dir}).')
4332370Szelenkov@nginx.com
434*2616Szelenkov@nginx.com    for item in Path(temporary_dir).iterdir():
435*2616Szelenkov@nginx.com        if item.name not in [
4362370Szelenkov@nginx.com            'control.unit.sock',
4372370Szelenkov@nginx.com            'state',
4382370Szelenkov@nginx.com            'unit.pid',
4392370Szelenkov@nginx.com            'unit.log',
4402370Szelenkov@nginx.com        ]:
441*2616Szelenkov@nginx.com
442*2616Szelenkov@nginx.com            public_dir(item)
443*2616Szelenkov@nginx.com
444*2616Szelenkov@nginx.com            if item.is_file() or stat.S_ISSOCK(item.stat().st_mode):
445*2616Szelenkov@nginx.com                item.unlink()
4462370Szelenkov@nginx.com            else:
4472477Szelenkov@nginx.com                for _ in range(10):
4482370Szelenkov@nginx.com                    try:
449*2616Szelenkov@nginx.com                        shutil.rmtree(item)
4502370Szelenkov@nginx.com                        break
4512370Szelenkov@nginx.com                    except OSError as err:
4522501Szelenkov@nginx.com                        # OSError: [Errno 16] Device or resource busy
4532501Szelenkov@nginx.com                        # OSError: [Errno 39] Directory not empty
4542501Szelenkov@nginx.com                        if err.errno not in [16, 39]:
4552370Szelenkov@nginx.com                            raise
4562370Szelenkov@nginx.com                        time.sleep(1)
4572370Szelenkov@nginx.com
4582370Szelenkov@nginx.com
4592074Szelenkov@nginx.comdef _check_processes():
4602074Szelenkov@nginx.com    router_pid = _fds_info['router']['pid']
4612074Szelenkov@nginx.com    controller_pid = _fds_info['controller']['pid']
462*2616Szelenkov@nginx.com    main_pid = unit_instance['pid']
4632074Szelenkov@nginx.com
4642477Szelenkov@nginx.com    for _ in range(600):
4652074Szelenkov@nginx.com        out = (
4662074Szelenkov@nginx.com            subprocess.check_output(
4672074Szelenkov@nginx.com                ['ps', '-ax', '-o', 'pid', '-o', 'ppid', '-o', 'command']
4682074Szelenkov@nginx.com            )
4692074Szelenkov@nginx.com            .decode()
4702074Szelenkov@nginx.com            .splitlines()
4712074Szelenkov@nginx.com        )
472*2616Szelenkov@nginx.com        out = [l for l in out if main_pid in l]
4732074Szelenkov@nginx.com
4742074Szelenkov@nginx.com        if len(out) <= 3:
4752074Szelenkov@nginx.com            break
4762074Szelenkov@nginx.com
4772074Szelenkov@nginx.com        time.sleep(0.1)
4782074Szelenkov@nginx.com
4792238Szelenkov@nginx.com    if option.restart:
4802238Szelenkov@nginx.com        assert len(out) == 0, 'all termimated'
4812238Szelenkov@nginx.com        return
4822238Szelenkov@nginx.com
4832074Szelenkov@nginx.com    assert len(out) == 3, 'main, router, and controller expected'
4842074Szelenkov@nginx.com
4852074Szelenkov@nginx.com    out = [l for l in out if 'unit: main' not in l]
4862074Szelenkov@nginx.com    assert len(out) == 2, 'one main'
4872074Szelenkov@nginx.com
4882074Szelenkov@nginx.com    out = [
4892074Szelenkov@nginx.com        l
4902074Szelenkov@nginx.com        for l in out
491*2616Szelenkov@nginx.com        if re.search(fr'{router_pid}\s+{main_pid}.*unit: router', l) is None
4922074Szelenkov@nginx.com    ]
4932074Szelenkov@nginx.com    assert len(out) == 1, 'one router'
4942074Szelenkov@nginx.com
4952074Szelenkov@nginx.com    out = [
4962074Szelenkov@nginx.com        l
4972074Szelenkov@nginx.com        for l in out
498*2616Szelenkov@nginx.com        if re.search(fr'{controller_pid}\s+{main_pid}.*unit: controller', l)
4992074Szelenkov@nginx.com        is None
5002074Szelenkov@nginx.com    ]
5012074Szelenkov@nginx.com    assert len(out) == 0, 'one controller'
5022074Szelenkov@nginx.com
5032074Szelenkov@nginx.com
5041914Szelenkov@nginx.com@print_log_on_assert
5051914Szelenkov@nginx.comdef _check_fds(*, log=None):
5061914Szelenkov@nginx.com    def waitforfds(diff):
5072477Szelenkov@nginx.com        for _ in range(600):
5081914Szelenkov@nginx.com            fds_diff = diff()
5091914Szelenkov@nginx.com
5101914Szelenkov@nginx.com            if fds_diff <= option.fds_threshold:
5111914Szelenkov@nginx.com                break
5121914Szelenkov@nginx.com
5131914Szelenkov@nginx.com            time.sleep(0.1)
5141914Szelenkov@nginx.com
5151914Szelenkov@nginx.com        return fds_diff
5161914Szelenkov@nginx.com
5171914Szelenkov@nginx.com    ps = _fds_info['main']
5181914Szelenkov@nginx.com    if not ps['skip']:
5191914Szelenkov@nginx.com        fds_diff = waitforfds(
5201914Szelenkov@nginx.com            lambda: _count_fds(unit_instance['pid']) - ps['fds']
5211914Szelenkov@nginx.com        )
5221914Szelenkov@nginx.com        ps['fds'] += fds_diff
5231914Szelenkov@nginx.com
5242073Szelenkov@nginx.com        assert fds_diff <= option.fds_threshold, 'descriptors leak main process'
5251914Szelenkov@nginx.com
5261914Szelenkov@nginx.com    else:
5271914Szelenkov@nginx.com        ps['fds'] = _count_fds(unit_instance['pid'])
5281914Szelenkov@nginx.com
5291914Szelenkov@nginx.com    for name in ['controller', 'router']:
5301914Szelenkov@nginx.com        ps = _fds_info[name]
5311914Szelenkov@nginx.com        ps_pid = ps['pid']
5321914Szelenkov@nginx.com        ps['pid'] = pid_by_name(ps['name'])
5331914Szelenkov@nginx.com
5341914Szelenkov@nginx.com        if not ps['skip']:
5351914Szelenkov@nginx.com            fds_diff = waitforfds(lambda: _count_fds(ps['pid']) - ps['fds'])
5361914Szelenkov@nginx.com            ps['fds'] += fds_diff
5371914Szelenkov@nginx.com
5381914Szelenkov@nginx.com            if not option.restart:
5392330Szelenkov@nginx.com                assert ps['pid'] == ps_pid, f'same pid {name}'
5401914Szelenkov@nginx.com
5412330Szelenkov@nginx.com            assert fds_diff <= option.fds_threshold, f'descriptors leak {name}'
5421914Szelenkov@nginx.com
5431914Szelenkov@nginx.com        else:
5441914Szelenkov@nginx.com            ps['fds'] = _count_fds(ps['pid'])
5451803Szelenkov@nginx.com
5461848Szelenkov@nginx.com
5471844Szelenkov@nginx.comdef _count_fds(pid):
548*2616Szelenkov@nginx.com    procfile = Path(f'/proc/{pid}/fd')
549*2616Szelenkov@nginx.com    if procfile.is_dir():
550*2616Szelenkov@nginx.com        return len(list(procfile.iterdir()))
5511844Szelenkov@nginx.com
5521844Szelenkov@nginx.com    try:
5531844Szelenkov@nginx.com        out = subprocess.check_output(
5542073Szelenkov@nginx.com            ['procstat', '-f', pid],
5552073Szelenkov@nginx.com            stderr=subprocess.STDOUT,
5561844Szelenkov@nginx.com        ).decode()
5571844Szelenkov@nginx.com        return len(out.splitlines())
5581844Szelenkov@nginx.com
5591868Szelenkov@nginx.com    except (FileNotFoundError, TypeError, subprocess.CalledProcessError):
5601844Szelenkov@nginx.com        pass
5611844Szelenkov@nginx.com
5621844Szelenkov@nginx.com    try:
5631844Szelenkov@nginx.com        out = subprocess.check_output(
5642073Szelenkov@nginx.com            ['lsof', '-n', '-p', pid],
5652073Szelenkov@nginx.com            stderr=subprocess.STDOUT,
5661844Szelenkov@nginx.com        ).decode()
5671844Szelenkov@nginx.com        return len(out.splitlines())
5681844Szelenkov@nginx.com
5691868Szelenkov@nginx.com    except (FileNotFoundError, TypeError, subprocess.CalledProcessError):
5701844Szelenkov@nginx.com        pass
5711844Szelenkov@nginx.com
5721844Szelenkov@nginx.com    return 0
5731844Szelenkov@nginx.com
5741844Szelenkov@nginx.com
5751654Szelenkov@nginx.comdef run_process(target, *args):
5761654Szelenkov@nginx.com    global _processes
5771654Szelenkov@nginx.com
5781654Szelenkov@nginx.com    process = Process(target=target, args=args)
5791654Szelenkov@nginx.com    process.start()
5801654Szelenkov@nginx.com
5811654Szelenkov@nginx.com    _processes.append(process)
5821654Szelenkov@nginx.com
5831848Szelenkov@nginx.com
5841654Szelenkov@nginx.comdef stop_processes():
5851654Szelenkov@nginx.com    if not _processes:
5861654Szelenkov@nginx.com        return
5871654Szelenkov@nginx.com
5881654Szelenkov@nginx.com    fail = False
5891654Szelenkov@nginx.com    for process in _processes:
5901654Szelenkov@nginx.com        if process.is_alive():
5911654Szelenkov@nginx.com            process.terminate()
5921654Szelenkov@nginx.com            process.join(timeout=15)
5931654Szelenkov@nginx.com
5941654Szelenkov@nginx.com            if process.is_alive():
5951654Szelenkov@nginx.com                fail = True
5961654Szelenkov@nginx.com
5971654Szelenkov@nginx.com    if fail:
5981654Szelenkov@nginx.com        return 'Fail to stop process(es)'
5991654Szelenkov@nginx.com
6001654Szelenkov@nginx.com
6011844Szelenkov@nginx.comdef pid_by_name(name):
6021844Szelenkov@nginx.com    output = subprocess.check_output(['ps', 'ax', '-O', 'ppid']).decode()
6032330Szelenkov@nginx.com    m = re.search(fr'\s*(\d+)\s*{unit_instance["pid"]}.*{name}', output)
6041844Szelenkov@nginx.com    return None if m is None else m.group(1)
6051844Szelenkov@nginx.com
6061844Szelenkov@nginx.com
6071844Szelenkov@nginx.comdef find_proc(name, ps_output):
6082330Szelenkov@nginx.com    return re.findall(f'{unit_instance["pid"]}.*{name}', ps_output)
6091844Szelenkov@nginx.com
6101844Szelenkov@nginx.com
6112482Szelenkov@nginx.comdef pytest_sessionfinish():
6122482Szelenkov@nginx.com    if not option.restart and option.save_log:
6132482Szelenkov@nginx.com        Log.print_path()
6142482Szelenkov@nginx.com
6152482Szelenkov@nginx.com    option.restart = True
6162482Szelenkov@nginx.com
6172482Szelenkov@nginx.com    unit_stop()
6182482Szelenkov@nginx.com
6192482Szelenkov@nginx.com    public_dir(option.cache_dir)
6202482Szelenkov@nginx.com    shutil.rmtree(option.cache_dir)
6212482Szelenkov@nginx.com
622*2616Szelenkov@nginx.com    if not option.save_log and Path(option.temp_dir).is_dir():
6232482Szelenkov@nginx.com        public_dir(option.temp_dir)
6242482Szelenkov@nginx.com        shutil.rmtree(option.temp_dir)
6252482Szelenkov@nginx.com
6262482Szelenkov@nginx.com
6272482Szelenkov@nginx.com@pytest.fixture
6282482Szelenkov@nginx.comdef date_to_sec_epoch():
6292482Szelenkov@nginx.com    def _date_to_sec_epoch(date, template='%a, %d %b %Y %X %Z'):
6302482Szelenkov@nginx.com        return time.mktime(time.strptime(date, template))
6312482Szelenkov@nginx.com
6322482Szelenkov@nginx.com    return _date_to_sec_epoch
6332482Szelenkov@nginx.com
6342482Szelenkov@nginx.com
6352482Szelenkov@nginx.com@pytest.fixture
6362482Szelenkov@nginx.comdef findall():
6372488Szelenkov@nginx.com    def _findall(*args, **kwargs):
6382488Szelenkov@nginx.com        return Log.findall(*args, **kwargs)
6392482Szelenkov@nginx.com
6402482Szelenkov@nginx.com    return _findall
6412482Szelenkov@nginx.com
6422482Szelenkov@nginx.com
6432482Szelenkov@nginx.com@pytest.fixture
6442482Szelenkov@nginx.comdef is_su():
6452482Szelenkov@nginx.com    return option.is_privileged
6462482Szelenkov@nginx.com
6472482Szelenkov@nginx.com
6482482Szelenkov@nginx.com@pytest.fixture
6492482Szelenkov@nginx.comdef is_unsafe(request):
6502482Szelenkov@nginx.com    return request.config.getoption("--unsafe")
6512482Szelenkov@nginx.com
6522482Szelenkov@nginx.com
6532482Szelenkov@nginx.com@pytest.fixture
6542488Szelenkov@nginx.comdef require():
6552488Szelenkov@nginx.com    return check_prerequisites
6562488Szelenkov@nginx.com
6572488Szelenkov@nginx.com
6582488Szelenkov@nginx.com@pytest.fixture
6592482Szelenkov@nginx.comdef search_in_file():
6602482Szelenkov@nginx.com    def _search_in_file(pattern, name='unit.log', flags=re.M):
6612482Szelenkov@nginx.com        return re.search(pattern, Log.read(name), flags)
6622482Szelenkov@nginx.com
6632482Szelenkov@nginx.com    return _search_in_file
6642482Szelenkov@nginx.com
6652482Szelenkov@nginx.com
6662482Szelenkov@nginx.com@pytest.fixture
6672482Szelenkov@nginx.comdef sec_epoch():
6682482Szelenkov@nginx.com    return time.mktime(time.gmtime())
6692482Szelenkov@nginx.com
6702482Szelenkov@nginx.com
6711736Szelenkov@nginx.com@pytest.fixture()
6721736Szelenkov@nginx.comdef skip_alert():
6731736Szelenkov@nginx.com    def _skip(*alerts):
6741736Szelenkov@nginx.com        option.skip_alerts.extend(alerts)
6751736Szelenkov@nginx.com
6761736Szelenkov@nginx.com    return _skip
6771736Szelenkov@nginx.com
6781736Szelenkov@nginx.com
6791844Szelenkov@nginx.com@pytest.fixture()
6801844Szelenkov@nginx.comdef skip_fds_check():
6811844Szelenkov@nginx.com    def _skip(main=False, router=False, controller=False):
6821914Szelenkov@nginx.com        _fds_info['main']['skip'] = main
6831914Szelenkov@nginx.com        _fds_info['router']['skip'] = router
6841914Szelenkov@nginx.com        _fds_info['controller']['skip'] = controller
6851844Szelenkov@nginx.com
6861844Szelenkov@nginx.com    return _skip
6871844Szelenkov@nginx.com
6881844Szelenkov@nginx.com
6892482Szelenkov@nginx.com@pytest.fixture()
6902482Szelenkov@nginx.comdef system():
6912482Szelenkov@nginx.com    return option.system
6921654Szelenkov@nginx.com
6931848Szelenkov@nginx.com
6941596Szelenkov@nginx.com@pytest.fixture
6952482Szelenkov@nginx.comdef temp_dir():
6962482Szelenkov@nginx.com    return unit_instance['temp_dir']
6971596Szelenkov@nginx.com
6981848Szelenkov@nginx.com
6991769St.nateldemoura@f5.com@pytest.fixture
7002477Szelenkov@nginx.comdef unit_pid():
7011769St.nateldemoura@f5.com    return unit_instance['process'].pid
7021769St.nateldemoura@f5.com
7031848Szelenkov@nginx.com
7042482Szelenkov@nginx.com@pytest.fixture
7052482Szelenkov@nginx.comdef wait_for_record():
7062488Szelenkov@nginx.com    def _wait_for_record(*args, **kwargs):
7072488Szelenkov@nginx.com        return Log.wait_for_record(*args, **kwargs)
7082488Szelenkov@nginx.com
7092482Szelenkov@nginx.com    return _wait_for_record
710