xref: /unit/test/conftest.py (revision 1986)
11596Szelenkov@nginx.comimport fcntl
21803Szelenkov@nginx.comimport inspect
31803Szelenkov@nginx.comimport json
41596Szelenkov@nginx.comimport os
51596Szelenkov@nginx.comimport platform
61635Szelenkov@nginx.comimport re
71621Szelenkov@nginx.comimport shutil
81596Szelenkov@nginx.comimport signal
91596Szelenkov@nginx.comimport stat
101596Szelenkov@nginx.comimport subprocess
111596Szelenkov@nginx.comimport sys
121596Szelenkov@nginx.comimport tempfile
131596Szelenkov@nginx.comimport time
141654Szelenkov@nginx.comfrom multiprocessing import Process
151596Szelenkov@nginx.com
161635Szelenkov@nginx.comimport pytest
171858Szelenkov@nginx.comfrom unit.check.chroot import check_chroot
181621Szelenkov@nginx.comfrom unit.check.go import check_go
191771Szelenkov@nginx.comfrom unit.check.isolation import check_isolation
201621Szelenkov@nginx.comfrom unit.check.node import check_node
211848Szelenkov@nginx.comfrom unit.check.regex import check_regex
221621Szelenkov@nginx.comfrom unit.check.tls import check_openssl
231803Szelenkov@nginx.comfrom unit.http import TestHTTP
241902Szelenkov@nginx.comfrom unit.log import Log
251730Szelenkov@nginx.comfrom unit.option import option
261735Szelenkov@nginx.comfrom unit.utils import public_dir
271735Szelenkov@nginx.comfrom unit.utils import waitforfiles
281621Szelenkov@nginx.com
291596Szelenkov@nginx.com
301596Szelenkov@nginx.comdef pytest_addoption(parser):
311596Szelenkov@nginx.com    parser.addoption(
321596Szelenkov@nginx.com        "--detailed",
331596Szelenkov@nginx.com        default=False,
341596Szelenkov@nginx.com        action="store_true",
351596Szelenkov@nginx.com        help="Detailed output for tests",
361596Szelenkov@nginx.com    )
371596Szelenkov@nginx.com    parser.addoption(
381743Szelenkov@nginx.com        "--print-log",
391596Szelenkov@nginx.com        default=False,
401596Szelenkov@nginx.com        action="store_true",
411596Szelenkov@nginx.com        help="Print unit.log to stdout in case of errors",
421596Szelenkov@nginx.com    )
431596Szelenkov@nginx.com    parser.addoption(
441743Szelenkov@nginx.com        "--save-log",
451596Szelenkov@nginx.com        default=False,
461596Szelenkov@nginx.com        action="store_true",
471596Szelenkov@nginx.com        help="Save unit.log after the test execution",
481596Szelenkov@nginx.com    )
491596Szelenkov@nginx.com    parser.addoption(
501596Szelenkov@nginx.com        "--unsafe",
511596Szelenkov@nginx.com        default=False,
521596Szelenkov@nginx.com        action="store_true",
531596Szelenkov@nginx.com        help="Run unsafe tests",
541596Szelenkov@nginx.com    )
551761Sdefan@nginx.com    parser.addoption(
561761Sdefan@nginx.com        "--user",
571761Sdefan@nginx.com        type=str,
581761Sdefan@nginx.com        help="Default user for non-privileged processes of unitd",
591761Sdefan@nginx.com    )
601803Szelenkov@nginx.com    parser.addoption(
611844Szelenkov@nginx.com        "--fds-threshold",
621844Szelenkov@nginx.com        type=int,
631844Szelenkov@nginx.com        default=0,
641844Szelenkov@nginx.com        help="File descriptors threshold",
651844Szelenkov@nginx.com    )
661844Szelenkov@nginx.com    parser.addoption(
671803Szelenkov@nginx.com        "--restart",
681803Szelenkov@nginx.com        default=False,
691803Szelenkov@nginx.com        action="store_true",
701803Szelenkov@nginx.com        help="Force Unit to restart after every test",
711803Szelenkov@nginx.com    )
721596Szelenkov@nginx.com
731596Szelenkov@nginx.com
741596Szelenkov@nginx.comunit_instance = {}
751654Szelenkov@nginx.com_processes = []
761914Szelenkov@nginx.com_fds_info = {
771844Szelenkov@nginx.com    'main': {'fds': 0, 'skip': False},
781844Szelenkov@nginx.com    'router': {'name': 'unit: router', 'pid': -1, 'fds': 0, 'skip': False},
791844Szelenkov@nginx.com    'controller': {
801844Szelenkov@nginx.com        'name': 'unit: controller',
811844Szelenkov@nginx.com        'pid': -1,
821844Szelenkov@nginx.com        'fds': 0,
831844Szelenkov@nginx.com        'skip': False,
841844Szelenkov@nginx.com    },
851844Szelenkov@nginx.com}
861803Szelenkov@nginx.comhttp = TestHTTP()
871596Szelenkov@nginx.com
881848Szelenkov@nginx.com
891596Szelenkov@nginx.comdef pytest_configure(config):
901730Szelenkov@nginx.com    option.config = config.option
911730Szelenkov@nginx.com
921730Szelenkov@nginx.com    option.detailed = config.option.detailed
931844Szelenkov@nginx.com    option.fds_threshold = config.option.fds_threshold
941730Szelenkov@nginx.com    option.print_log = config.option.print_log
951730Szelenkov@nginx.com    option.save_log = config.option.save_log
961730Szelenkov@nginx.com    option.unsafe = config.option.unsafe
971761Sdefan@nginx.com    option.user = config.option.user
981803Szelenkov@nginx.com    option.restart = config.option.restart
991596Szelenkov@nginx.com
1001596Szelenkov@nginx.com    option.generated_tests = {}
1011596Szelenkov@nginx.com    option.current_dir = os.path.abspath(
1021596Szelenkov@nginx.com        os.path.join(os.path.dirname(__file__), os.pardir)
1031596Szelenkov@nginx.com    )
1041596Szelenkov@nginx.com    option.test_dir = option.current_dir + '/test'
1051596Szelenkov@nginx.com    option.architecture = platform.architecture()[0]
1061596Szelenkov@nginx.com    option.system = platform.system()
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
1171914Szelenkov@nginx.comdef print_log_on_assert(func):
1181914Szelenkov@nginx.com    def inner_function(*args, **kwargs):
1191914Szelenkov@nginx.com        try:
1201914Szelenkov@nginx.com            func(*args, **kwargs)
1211914Szelenkov@nginx.com        except AssertionError as e:
1221914Szelenkov@nginx.com            _print_log(kwargs.get('log', None))
1231914Szelenkov@nginx.com            raise e
1241914Szelenkov@nginx.com
1251914Szelenkov@nginx.com    return inner_function
1261914Szelenkov@nginx.com
1271914Szelenkov@nginx.com
1281596Szelenkov@nginx.comdef pytest_generate_tests(metafunc):
1291596Szelenkov@nginx.com    cls = metafunc.cls
1301848Szelenkov@nginx.com    if (
1311848Szelenkov@nginx.com        not hasattr(cls, 'application_type')
1321848Szelenkov@nginx.com        or cls.application_type == None
1331848Szelenkov@nginx.com        or cls.application_type == 'external'
1341848Szelenkov@nginx.com    ):
1351596Szelenkov@nginx.com        return
1361596Szelenkov@nginx.com
1371596Szelenkov@nginx.com    type = cls.application_type
1381596Szelenkov@nginx.com
1391611Smax.romanov@nginx.com    def generate_tests(versions):
1401611Smax.romanov@nginx.com        metafunc.fixturenames.append('tmp_ct')
1411630Smax.romanov@nginx.com        metafunc.parametrize('tmp_ct', versions)
1421611Smax.romanov@nginx.com
1431630Smax.romanov@nginx.com        for version in versions:
1441611Smax.romanov@nginx.com            option.generated_tests[
1451630Smax.romanov@nginx.com                metafunc.function.__name__ + '[{}]'.format(version)
1461611Smax.romanov@nginx.com            ] = (type + ' ' + version)
1471611Smax.romanov@nginx.com
1481596Szelenkov@nginx.com    # take available module from option and generate tests for each version
1491596Szelenkov@nginx.com
1501611Smax.romanov@nginx.com    for module, prereq_version in cls.prerequisites['modules'].items():
1511596Szelenkov@nginx.com        if module in option.available['modules']:
1521596Szelenkov@nginx.com            available_versions = option.available['modules'][module]
1531596Szelenkov@nginx.com
1541596Szelenkov@nginx.com            if prereq_version == 'all':
1551611Smax.romanov@nginx.com                generate_tests(available_versions)
1561596Szelenkov@nginx.com
1571596Szelenkov@nginx.com            elif prereq_version == 'any':
1581596Szelenkov@nginx.com                option.generated_tests[metafunc.function.__name__] = (
1591596Szelenkov@nginx.com                    type + ' ' + available_versions[0]
1601596Szelenkov@nginx.com                )
1611611Smax.romanov@nginx.com            elif callable(prereq_version):
1621611Smax.romanov@nginx.com                generate_tests(
1631611Smax.romanov@nginx.com                    list(filter(prereq_version, available_versions))
1641611Smax.romanov@nginx.com                )
1651611Smax.romanov@nginx.com
1661596Szelenkov@nginx.com            else:
1671611Smax.romanov@nginx.com                raise ValueError(
1681611Smax.romanov@nginx.com                    """
1691611Smax.romanov@nginx.comUnexpected prerequisite version "%s" for module "%s" in %s.
1701611Smax.romanov@nginx.com'all', 'any' or callable expected."""
1711611Smax.romanov@nginx.com                    % (str(prereq_version), module, str(cls))
1721611Smax.romanov@nginx.com                )
1731596Szelenkov@nginx.com
1741596Szelenkov@nginx.com
1751596Szelenkov@nginx.comdef pytest_sessionstart(session):
1761596Szelenkov@nginx.com    option.available = {'modules': {}, 'features': {}}
1771596Szelenkov@nginx.com
1781596Szelenkov@nginx.com    unit = unit_run()
1791596Szelenkov@nginx.com
1801596Szelenkov@nginx.com    # read unit.log
1811596Szelenkov@nginx.com
1821596Szelenkov@nginx.com    for i in range(50):
1831850Smax.romanov@nginx.com        with open(Log.get_path(), 'r') as f:
1841596Szelenkov@nginx.com            log = f.read()
1851596Szelenkov@nginx.com            m = re.search('controller started', log)
1861596Szelenkov@nginx.com
1871596Szelenkov@nginx.com            if m is None:
1881596Szelenkov@nginx.com                time.sleep(0.1)
1891596Szelenkov@nginx.com            else:
1901596Szelenkov@nginx.com                break
1911596Szelenkov@nginx.com
1921596Szelenkov@nginx.com    if m is None:
1931654Szelenkov@nginx.com        _print_log(log)
1941596Szelenkov@nginx.com        exit("Unit is writing log too long")
1951596Szelenkov@nginx.com
1961596Szelenkov@nginx.com    # discover available modules from unit.log
1971596Szelenkov@nginx.com
1981596Szelenkov@nginx.com    for module in re.findall(r'module: ([a-zA-Z]+) (.*) ".*"$', log, re.M):
1991705Smax.romanov@nginx.com        versions = option.available['modules'].setdefault(module[0], [])
2001705Smax.romanov@nginx.com        if module[1] not in versions:
2011705Smax.romanov@nginx.com            versions.append(module[1])
2021596Szelenkov@nginx.com
2031621Szelenkov@nginx.com    # discover modules from check
2041621Szelenkov@nginx.com
2051621Szelenkov@nginx.com    option.available['modules']['openssl'] = check_openssl(unit['unitd'])
2061621Szelenkov@nginx.com    option.available['modules']['go'] = check_go(
2071621Szelenkov@nginx.com        option.current_dir, unit['temp_dir'], option.test_dir
2081621Szelenkov@nginx.com    )
2091621Szelenkov@nginx.com    option.available['modules']['node'] = check_node(option.current_dir)
2101807Szelenkov@nginx.com    option.available['modules']['regex'] = check_regex(unit['unitd'])
2111621Szelenkov@nginx.com
2121621Szelenkov@nginx.com    # remove None values
2131621Szelenkov@nginx.com
2141621Szelenkov@nginx.com    option.available['modules'] = {
2151621Szelenkov@nginx.com        k: v for k, v in option.available['modules'].items() if v is not None
2161621Szelenkov@nginx.com    }
2171621Szelenkov@nginx.com
2181858Szelenkov@nginx.com    check_chroot()
2191740Szelenkov@nginx.com    check_isolation()
2201740Szelenkov@nginx.com
2211805Szelenkov@nginx.com    _clear_conf(unit['temp_dir'] + '/control.unit.sock')
2221803Szelenkov@nginx.com
2231596Szelenkov@nginx.com    unit_stop()
2241596Szelenkov@nginx.com
2251730Szelenkov@nginx.com    _check_alerts()
2261730Szelenkov@nginx.com
2271803Szelenkov@nginx.com    if option.restart:
2281803Szelenkov@nginx.com        shutil.rmtree(unit_instance['temp_dir'])
2291596Szelenkov@nginx.com
2301848Szelenkov@nginx.com
2311654Szelenkov@nginx.com@pytest.hookimpl(tryfirst=True, hookwrapper=True)
2321654Szelenkov@nginx.comdef pytest_runtest_makereport(item, call):
2331654Szelenkov@nginx.com    # execute all other hooks to obtain the report object
2341654Szelenkov@nginx.com    outcome = yield
2351654Szelenkov@nginx.com    rep = outcome.get_result()
2361654Szelenkov@nginx.com
2371654Szelenkov@nginx.com    # set a report attribute for each phase of a call, which can
2381654Szelenkov@nginx.com    # be "setup", "call", "teardown"
2391654Szelenkov@nginx.com
2401654Szelenkov@nginx.com    setattr(item, "rep_" + rep.when, rep)
2411654Szelenkov@nginx.com
2421654Szelenkov@nginx.com
2431741Szelenkov@nginx.com@pytest.fixture(scope='class', autouse=True)
2441741Szelenkov@nginx.comdef check_prerequisites(request):
2451741Szelenkov@nginx.com    cls = request.cls
2461741Szelenkov@nginx.com    missed = []
2471741Szelenkov@nginx.com
2481741Szelenkov@nginx.com    # check modules
2491741Szelenkov@nginx.com
2501741Szelenkov@nginx.com    if 'modules' in cls.prerequisites:
2511741Szelenkov@nginx.com        available_modules = list(option.available['modules'].keys())
2521741Szelenkov@nginx.com
2531741Szelenkov@nginx.com        for module in cls.prerequisites['modules']:
2541741Szelenkov@nginx.com            if module in available_modules:
2551741Szelenkov@nginx.com                continue
2561741Szelenkov@nginx.com
2571741Szelenkov@nginx.com            missed.append(module)
2581741Szelenkov@nginx.com
2591741Szelenkov@nginx.com    if missed:
2601741Szelenkov@nginx.com        pytest.skip('Unit has no ' + ', '.join(missed) + ' module(s)')
2611741Szelenkov@nginx.com
2621741Szelenkov@nginx.com    # check features
2631741Szelenkov@nginx.com
2641741Szelenkov@nginx.com    if 'features' in cls.prerequisites:
2651741Szelenkov@nginx.com        available_features = list(option.available['features'].keys())
2661741Szelenkov@nginx.com
2671741Szelenkov@nginx.com        for feature in cls.prerequisites['features']:
2681741Szelenkov@nginx.com            if feature in available_features:
2691741Szelenkov@nginx.com                continue
2701741Szelenkov@nginx.com
2711741Szelenkov@nginx.com            missed.append(feature)
2721741Szelenkov@nginx.com
2731741Szelenkov@nginx.com    if missed:
2741741Szelenkov@nginx.com        pytest.skip(', '.join(missed) + ' feature(s) not supported')
2751741Szelenkov@nginx.com
2761741Szelenkov@nginx.com
2771654Szelenkov@nginx.com@pytest.fixture(autouse=True)
2781654Szelenkov@nginx.comdef run(request):
2791654Szelenkov@nginx.com    unit = unit_run()
2801654Szelenkov@nginx.com
2811596Szelenkov@nginx.com    option.skip_alerts = [
2821596Szelenkov@nginx.com        r'read signalfd\(4\) failed',
2831596Szelenkov@nginx.com        r'sendmsg.+failed',
2841596Szelenkov@nginx.com        r'recvmsg.+failed',
2851596Szelenkov@nginx.com    ]
2861596Szelenkov@nginx.com    option.skip_sanitizer = False
2871596Szelenkov@nginx.com
2881914Szelenkov@nginx.com    _fds_info['main']['skip'] = False
2891914Szelenkov@nginx.com    _fds_info['router']['skip'] = False
2901914Szelenkov@nginx.com    _fds_info['controller']['skip'] = False
2911844Szelenkov@nginx.com
2921654Szelenkov@nginx.com    yield
2931654Szelenkov@nginx.com
2941654Szelenkov@nginx.com    # stop unit
2951654Szelenkov@nginx.com
2961803Szelenkov@nginx.com    error_stop_unit = unit_stop()
2971803Szelenkov@nginx.com    error_stop_processes = stop_processes()
2981803Szelenkov@nginx.com
2991803Szelenkov@nginx.com    # prepare log
3001803Szelenkov@nginx.com
3011850Smax.romanov@nginx.com    with Log.open(encoding='utf-8') as f:
3021803Szelenkov@nginx.com        log = f.read()
3031850Smax.romanov@nginx.com        Log.set_pos(f.tell())
3041654Szelenkov@nginx.com
3051803Szelenkov@nginx.com    if not option.save_log and option.restart:
3061803Szelenkov@nginx.com        shutil.rmtree(unit['temp_dir'])
3071850Smax.romanov@nginx.com        Log.set_pos(0)
3081803Szelenkov@nginx.com
3091803Szelenkov@nginx.com    # clean temp_dir before the next test
3101654Szelenkov@nginx.com
3111803Szelenkov@nginx.com    if not option.restart:
3121914Szelenkov@nginx.com        _clear_conf(unit['temp_dir'] + '/control.unit.sock', log=log)
3131803Szelenkov@nginx.com
3141803Szelenkov@nginx.com        for item in os.listdir(unit['temp_dir']):
3151803Szelenkov@nginx.com            if item not in [
3161803Szelenkov@nginx.com                'control.unit.sock',
3171803Szelenkov@nginx.com                'state',
3181803Szelenkov@nginx.com                'unit.pid',
3191803Szelenkov@nginx.com                'unit.log',
3201803Szelenkov@nginx.com            ]:
3211803Szelenkov@nginx.com                path = os.path.join(unit['temp_dir'], item)
3221654Szelenkov@nginx.com
3231803Szelenkov@nginx.com                public_dir(path)
3241654Szelenkov@nginx.com
3251848Szelenkov@nginx.com                if os.path.isfile(path) or stat.S_ISSOCK(
3261848Szelenkov@nginx.com                    os.stat(path).st_mode
3271848Szelenkov@nginx.com                ):
3281803Szelenkov@nginx.com                    os.remove(path)
3291803Szelenkov@nginx.com                else:
3301933Smax.romanov@nginx.com                    for attempt in range(10):
3311933Smax.romanov@nginx.com                        try:
3321933Smax.romanov@nginx.com                            shutil.rmtree(path)
3331933Smax.romanov@nginx.com                            break
3341933Smax.romanov@nginx.com                        except OSError as err:
3351933Smax.romanov@nginx.com                            if err.errno != 16:
3361933Smax.romanov@nginx.com                                raise
3371933Smax.romanov@nginx.com                            time.sleep(1)
3381654Szelenkov@nginx.com
3391914Szelenkov@nginx.com    # check descriptors
3401844Szelenkov@nginx.com
3411914Szelenkov@nginx.com    _check_fds(log=log)
3421844Szelenkov@nginx.com
3431654Szelenkov@nginx.com    # print unit.log in case of error
3441654Szelenkov@nginx.com
3451706Smax.romanov@nginx.com    if hasattr(request.node, 'rep_call') and request.node.rep_call.failed:
3461803Szelenkov@nginx.com        _print_log(log)
3471803Szelenkov@nginx.com
3481803Szelenkov@nginx.com    if error_stop_unit or error_stop_processes:
3491803Szelenkov@nginx.com        _print_log(log)
3501654Szelenkov@nginx.com
3511803Szelenkov@nginx.com    # check unit.log for errors
3521654Szelenkov@nginx.com
3531803Szelenkov@nginx.com    assert error_stop_unit is None, 'stop unit'
3541803Szelenkov@nginx.com    assert error_stop_processes is None, 'stop processes'
3551803Szelenkov@nginx.com
3561803Szelenkov@nginx.com    _check_alerts(log=log)
3571654Szelenkov@nginx.com
3581848Szelenkov@nginx.com
359*1986Szelenkov@nginx.comdef unit_run(state_dir=None):
3601596Szelenkov@nginx.com    global unit_instance
3611803Szelenkov@nginx.com
3621803Szelenkov@nginx.com    if not option.restart and 'unitd' in unit_instance:
3631803Szelenkov@nginx.com        return unit_instance
3641803Szelenkov@nginx.com
3651596Szelenkov@nginx.com    build_dir = option.current_dir + '/build'
3661596Szelenkov@nginx.com    unitd = build_dir + '/unitd'
3671596Szelenkov@nginx.com
3681596Szelenkov@nginx.com    if not os.path.isfile(unitd):
3691596Szelenkov@nginx.com        exit('Could not find unit')
3701596Szelenkov@nginx.com
3711596Szelenkov@nginx.com    temp_dir = tempfile.mkdtemp(prefix='unit-test-')
3721596Szelenkov@nginx.com    public_dir(temp_dir)
3731596Szelenkov@nginx.com
3741596Szelenkov@nginx.com    if oct(stat.S_IMODE(os.stat(build_dir).st_mode)) != '0o777':
3751596Szelenkov@nginx.com        public_dir(build_dir)
3761596Szelenkov@nginx.com
377*1986Szelenkov@nginx.com    state = temp_dir + '/state' if state_dir is None else state_dir
378*1986Szelenkov@nginx.com    if not os.path.isdir(state):
379*1986Szelenkov@nginx.com        os.mkdir(state)
3801596Szelenkov@nginx.com
3811761Sdefan@nginx.com    unitd_args = [
3821761Sdefan@nginx.com        unitd,
3831761Sdefan@nginx.com        '--no-daemon',
3841761Sdefan@nginx.com        '--modules',
3851761Sdefan@nginx.com        build_dir,
3861761Sdefan@nginx.com        '--state',
387*1986Szelenkov@nginx.com        state,
3881761Sdefan@nginx.com        '--pid',
3891761Sdefan@nginx.com        temp_dir + '/unit.pid',
3901761Sdefan@nginx.com        '--log',
3911761Sdefan@nginx.com        temp_dir + '/unit.log',
3921761Sdefan@nginx.com        '--control',
3931761Sdefan@nginx.com        'unix:' + temp_dir + '/control.unit.sock',
3941761Sdefan@nginx.com        '--tmp',
3951761Sdefan@nginx.com        temp_dir,
3961761Sdefan@nginx.com    ]
3971761Sdefan@nginx.com
3981761Sdefan@nginx.com    if option.user:
3991761Sdefan@nginx.com        unitd_args.extend(['--user', option.user])
4001761Sdefan@nginx.com
4011596Szelenkov@nginx.com    with open(temp_dir + '/unit.log', 'w') as log:
4021761Sdefan@nginx.com        unit_instance['process'] = subprocess.Popen(unitd_args, stderr=log)
4031596Szelenkov@nginx.com
4041934Smax.romanov@nginx.com    Log.temp_dir = temp_dir
4051934Smax.romanov@nginx.com
4061596Szelenkov@nginx.com    if not waitforfiles(temp_dir + '/control.unit.sock'):
4071596Szelenkov@nginx.com        _print_log()
4081596Szelenkov@nginx.com        exit('Could not start unit')
4091596Szelenkov@nginx.com
4101596Szelenkov@nginx.com    unit_instance['temp_dir'] = temp_dir
4111596Szelenkov@nginx.com    unit_instance['control_sock'] = temp_dir + '/control.unit.sock'
4121596Szelenkov@nginx.com    unit_instance['unitd'] = unitd
4131596Szelenkov@nginx.com
4141850Smax.romanov@nginx.com    option.temp_dir = temp_dir
4151850Smax.romanov@nginx.com
4161844Szelenkov@nginx.com    with open(temp_dir + '/unit.pid', 'r') as f:
4171844Szelenkov@nginx.com        unit_instance['pid'] = f.read().rstrip()
4181844Szelenkov@nginx.com
419*1986Szelenkov@nginx.com    if state_dir is None:
420*1986Szelenkov@nginx.com        _clear_conf(unit_instance['temp_dir'] + '/control.unit.sock')
4211844Szelenkov@nginx.com
4221914Szelenkov@nginx.com    _fds_info['main']['fds'] = _count_fds(unit_instance['pid'])
4231844Szelenkov@nginx.com
4241914Szelenkov@nginx.com    router = _fds_info['router']
4251844Szelenkov@nginx.com    router['pid'] = pid_by_name(router['name'])
4261844Szelenkov@nginx.com    router['fds'] = _count_fds(router['pid'])
4271844Szelenkov@nginx.com
4281914Szelenkov@nginx.com    controller = _fds_info['controller']
4291844Szelenkov@nginx.com    controller['pid'] = pid_by_name(controller['name'])
4301844Szelenkov@nginx.com    controller['fds'] = _count_fds(controller['pid'])
4311844Szelenkov@nginx.com
4321596Szelenkov@nginx.com    return unit_instance
4331596Szelenkov@nginx.com
4341596Szelenkov@nginx.com
4351596Szelenkov@nginx.comdef unit_stop():
4361803Szelenkov@nginx.com    if not option.restart:
4371803Szelenkov@nginx.com        if inspect.stack()[1].function.startswith('test_'):
4381803Szelenkov@nginx.com            pytest.skip('no restart mode')
4391803Szelenkov@nginx.com
4401803Szelenkov@nginx.com        return
4411803Szelenkov@nginx.com
4421596Szelenkov@nginx.com    p = unit_instance['process']
4431596Szelenkov@nginx.com
4441596Szelenkov@nginx.com    if p.poll() is not None:
4451596Szelenkov@nginx.com        return
4461596Szelenkov@nginx.com
4471596Szelenkov@nginx.com    p.send_signal(signal.SIGQUIT)
4481596Szelenkov@nginx.com
4491596Szelenkov@nginx.com    try:
4501596Szelenkov@nginx.com        retcode = p.wait(15)
4511596Szelenkov@nginx.com        if retcode:
4521596Szelenkov@nginx.com            return 'Child process terminated with code ' + str(retcode)
4531706Smax.romanov@nginx.com
4541706Smax.romanov@nginx.com    except KeyboardInterrupt:
4551706Smax.romanov@nginx.com        p.kill()
4561706Smax.romanov@nginx.com        raise
4571706Smax.romanov@nginx.com
4581596Szelenkov@nginx.com    except:
4591596Szelenkov@nginx.com        p.kill()
4601596Szelenkov@nginx.com        return 'Could not terminate unit'
4611596Szelenkov@nginx.com
4621596Szelenkov@nginx.com
4631914Szelenkov@nginx.com@print_log_on_assert
4641914Szelenkov@nginx.comdef _check_alerts(*, log=None):
4651803Szelenkov@nginx.com    if log is None:
4661850Smax.romanov@nginx.com        with Log.open(encoding='utf-8') as f:
4671803Szelenkov@nginx.com            log = f.read()
4681654Szelenkov@nginx.com
4691596Szelenkov@nginx.com    found = False
4701596Szelenkov@nginx.com
4711596Szelenkov@nginx.com    alerts = re.findall(r'.+\[alert\].+', log)
4721596Szelenkov@nginx.com
4731596Szelenkov@nginx.com    if alerts:
4741736Szelenkov@nginx.com        print('\nAll alerts/sanitizer errors found in log:')
4751596Szelenkov@nginx.com        [print(alert) for alert in alerts]
4761596Szelenkov@nginx.com        found = True
4771596Szelenkov@nginx.com
4781596Szelenkov@nginx.com    if option.skip_alerts:
4791596Szelenkov@nginx.com        for skip in option.skip_alerts:
4801596Szelenkov@nginx.com            alerts = [al for al in alerts if re.search(skip, al) is None]
4811596Szelenkov@nginx.com
4821914Szelenkov@nginx.com    assert not alerts, 'alert(s)'
4831596Szelenkov@nginx.com
4841596Szelenkov@nginx.com    if not option.skip_sanitizer:
4851596Szelenkov@nginx.com        sanitizer_errors = re.findall('.+Sanitizer.+', log)
4861596Szelenkov@nginx.com
4871914Szelenkov@nginx.com        assert not sanitizer_errors, 'sanitizer error(s)'
4881596Szelenkov@nginx.com
4891596Szelenkov@nginx.com    if found:
4901596Szelenkov@nginx.com        print('skipped.')
4911596Szelenkov@nginx.com
4921596Szelenkov@nginx.com
4931934Smax.romanov@nginx.comdef _print_log(log=None):
4941850Smax.romanov@nginx.com    path = Log.get_path()
4951596Szelenkov@nginx.com
4961621Szelenkov@nginx.com    print('Path to unit.log:\n' + path + '\n')
4971596Szelenkov@nginx.com
4981596Szelenkov@nginx.com    if option.print_log:
4991596Szelenkov@nginx.com        os.set_blocking(sys.stdout.fileno(), True)
5001596Szelenkov@nginx.com        sys.stdout.flush()
5011596Szelenkov@nginx.com
5021914Szelenkov@nginx.com        if log is None:
5031621Szelenkov@nginx.com            with open(path, 'r', encoding='utf-8', errors='ignore') as f:
5041596Szelenkov@nginx.com                shutil.copyfileobj(f, sys.stdout)
5051596Szelenkov@nginx.com        else:
5061914Szelenkov@nginx.com            sys.stdout.write(log)
5071596Szelenkov@nginx.com
5081596Szelenkov@nginx.com
5091914Szelenkov@nginx.com@print_log_on_assert
5101914Szelenkov@nginx.comdef _clear_conf(sock, *, log=None):
5111805Szelenkov@nginx.com    resp = http.put(
5121803Szelenkov@nginx.com        url='/config',
5131803Szelenkov@nginx.com        sock_type='unix',
5141803Szelenkov@nginx.com        addr=sock,
5151803Szelenkov@nginx.com        body=json.dumps({"listeners": {}, "applications": {}}),
5161803Szelenkov@nginx.com    )['body']
5171803Szelenkov@nginx.com
5181914Szelenkov@nginx.com    assert 'success' in resp, 'clear conf'
5191805Szelenkov@nginx.com
5201808Szelenkov@nginx.com    if 'openssl' not in option.available['modules']:
5211808Szelenkov@nginx.com        return
5221808Szelenkov@nginx.com
5231805Szelenkov@nginx.com    try:
5241848Szelenkov@nginx.com        certs = json.loads(
5251848Szelenkov@nginx.com            http.get(url='/certificates', sock_type='unix', addr=sock,)['body']
5261848Szelenkov@nginx.com        ).keys()
5271805Szelenkov@nginx.com
5281805Szelenkov@nginx.com    except json.JSONDecodeError:
5291805Szelenkov@nginx.com        pytest.fail('Can\'t parse certificates list.')
5301805Szelenkov@nginx.com
5311805Szelenkov@nginx.com    for cert in certs:
5321805Szelenkov@nginx.com        resp = http.delete(
5331848Szelenkov@nginx.com            url='/certificates/' + cert, sock_type='unix', addr=sock,
5341805Szelenkov@nginx.com        )['body']
5351805Szelenkov@nginx.com
5361914Szelenkov@nginx.com        assert 'success' in resp, 'remove certificate'
5371914Szelenkov@nginx.com
5381914Szelenkov@nginx.com
5391914Szelenkov@nginx.com@print_log_on_assert
5401914Szelenkov@nginx.comdef _check_fds(*, log=None):
5411914Szelenkov@nginx.com    def waitforfds(diff):
5421914Szelenkov@nginx.com        for i in range(600):
5431914Szelenkov@nginx.com            fds_diff = diff()
5441914Szelenkov@nginx.com
5451914Szelenkov@nginx.com            if fds_diff <= option.fds_threshold:
5461914Szelenkov@nginx.com                break
5471914Szelenkov@nginx.com
5481914Szelenkov@nginx.com            time.sleep(0.1)
5491914Szelenkov@nginx.com
5501914Szelenkov@nginx.com        return fds_diff
5511914Szelenkov@nginx.com
5521914Szelenkov@nginx.com    ps = _fds_info['main']
5531914Szelenkov@nginx.com    if not ps['skip']:
5541914Szelenkov@nginx.com        fds_diff = waitforfds(
5551914Szelenkov@nginx.com            lambda: _count_fds(unit_instance['pid']) - ps['fds']
5561914Szelenkov@nginx.com        )
5571914Szelenkov@nginx.com        ps['fds'] += fds_diff
5581914Szelenkov@nginx.com
5591914Szelenkov@nginx.com        assert (
5601914Szelenkov@nginx.com            fds_diff <= option.fds_threshold
5611914Szelenkov@nginx.com        ), 'descriptors leak main process'
5621914Szelenkov@nginx.com
5631914Szelenkov@nginx.com    else:
5641914Szelenkov@nginx.com        ps['fds'] = _count_fds(unit_instance['pid'])
5651914Szelenkov@nginx.com
5661914Szelenkov@nginx.com    for name in ['controller', 'router']:
5671914Szelenkov@nginx.com        ps = _fds_info[name]
5681914Szelenkov@nginx.com        ps_pid = ps['pid']
5691914Szelenkov@nginx.com        ps['pid'] = pid_by_name(ps['name'])
5701914Szelenkov@nginx.com
5711914Szelenkov@nginx.com        if not ps['skip']:
5721914Szelenkov@nginx.com            fds_diff = waitforfds(lambda: _count_fds(ps['pid']) - ps['fds'])
5731914Szelenkov@nginx.com            ps['fds'] += fds_diff
5741914Szelenkov@nginx.com
5751914Szelenkov@nginx.com            if not option.restart:
5761914Szelenkov@nginx.com                assert ps['pid'] == ps_pid, 'same pid %s' % name
5771914Szelenkov@nginx.com
5781914Szelenkov@nginx.com            assert fds_diff <= option.fds_threshold, (
5791914Szelenkov@nginx.com                'descriptors leak %s' % name
5801914Szelenkov@nginx.com            )
5811914Szelenkov@nginx.com
5821914Szelenkov@nginx.com        else:
5831914Szelenkov@nginx.com            ps['fds'] = _count_fds(ps['pid'])
5841803Szelenkov@nginx.com
5851848Szelenkov@nginx.com
5861844Szelenkov@nginx.comdef _count_fds(pid):
5871844Szelenkov@nginx.com    procfile = '/proc/%s/fd' % pid
5881844Szelenkov@nginx.com    if os.path.isdir(procfile):
5891844Szelenkov@nginx.com        return len(os.listdir(procfile))
5901844Szelenkov@nginx.com
5911844Szelenkov@nginx.com    try:
5921844Szelenkov@nginx.com        out = subprocess.check_output(
5931844Szelenkov@nginx.com            ['procstat', '-f', pid], stderr=subprocess.STDOUT,
5941844Szelenkov@nginx.com        ).decode()
5951844Szelenkov@nginx.com        return len(out.splitlines())
5961844Szelenkov@nginx.com
5971868Szelenkov@nginx.com    except (FileNotFoundError, TypeError, subprocess.CalledProcessError):
5981844Szelenkov@nginx.com        pass
5991844Szelenkov@nginx.com
6001844Szelenkov@nginx.com    try:
6011844Szelenkov@nginx.com        out = subprocess.check_output(
6021844Szelenkov@nginx.com            ['lsof', '-n', '-p', pid], stderr=subprocess.STDOUT,
6031844Szelenkov@nginx.com        ).decode()
6041844Szelenkov@nginx.com        return len(out.splitlines())
6051844Szelenkov@nginx.com
6061868Szelenkov@nginx.com    except (FileNotFoundError, TypeError, subprocess.CalledProcessError):
6071844Szelenkov@nginx.com        pass
6081844Szelenkov@nginx.com
6091844Szelenkov@nginx.com    return 0
6101844Szelenkov@nginx.com
6111844Szelenkov@nginx.com
6121654Szelenkov@nginx.comdef run_process(target, *args):
6131654Szelenkov@nginx.com    global _processes
6141654Szelenkov@nginx.com
6151654Szelenkov@nginx.com    process = Process(target=target, args=args)
6161654Szelenkov@nginx.com    process.start()
6171654Szelenkov@nginx.com
6181654Szelenkov@nginx.com    _processes.append(process)
6191654Szelenkov@nginx.com
6201848Szelenkov@nginx.com
6211654Szelenkov@nginx.comdef stop_processes():
6221654Szelenkov@nginx.com    if not _processes:
6231654Szelenkov@nginx.com        return
6241654Szelenkov@nginx.com
6251654Szelenkov@nginx.com    fail = False
6261654Szelenkov@nginx.com    for process in _processes:
6271654Szelenkov@nginx.com        if process.is_alive():
6281654Szelenkov@nginx.com            process.terminate()
6291654Szelenkov@nginx.com            process.join(timeout=15)
6301654Szelenkov@nginx.com
6311654Szelenkov@nginx.com            if process.is_alive():
6321654Szelenkov@nginx.com                fail = True
6331654Szelenkov@nginx.com
6341654Szelenkov@nginx.com    if fail:
6351654Szelenkov@nginx.com        return 'Fail to stop process(es)'
6361654Szelenkov@nginx.com
6371654Szelenkov@nginx.com
6381844Szelenkov@nginx.comdef pid_by_name(name):
6391844Szelenkov@nginx.com    output = subprocess.check_output(['ps', 'ax', '-O', 'ppid']).decode()
6401844Szelenkov@nginx.com    m = re.search(
6411844Szelenkov@nginx.com        r'\s*(\d+)\s*' + str(unit_instance['pid']) + r'.*' + name, output
6421844Szelenkov@nginx.com    )
6431844Szelenkov@nginx.com    return None if m is None else m.group(1)
6441844Szelenkov@nginx.com
6451844Szelenkov@nginx.com
6461844Szelenkov@nginx.comdef find_proc(name, ps_output):
6471844Szelenkov@nginx.com    return re.findall(str(unit_instance['pid']) + r'.*' + name, ps_output)
6481844Szelenkov@nginx.com
6491844Szelenkov@nginx.com
6501736Szelenkov@nginx.com@pytest.fixture()
6511736Szelenkov@nginx.comdef skip_alert():
6521736Szelenkov@nginx.com    def _skip(*alerts):
6531736Szelenkov@nginx.com        option.skip_alerts.extend(alerts)
6541736Szelenkov@nginx.com
6551736Szelenkov@nginx.com    return _skip
6561736Szelenkov@nginx.com
6571736Szelenkov@nginx.com
6581844Szelenkov@nginx.com@pytest.fixture()
6591844Szelenkov@nginx.comdef skip_fds_check():
6601844Szelenkov@nginx.com    def _skip(main=False, router=False, controller=False):
6611914Szelenkov@nginx.com        _fds_info['main']['skip'] = main
6621914Szelenkov@nginx.com        _fds_info['router']['skip'] = router
6631914Szelenkov@nginx.com        _fds_info['controller']['skip'] = controller
6641844Szelenkov@nginx.com
6651844Szelenkov@nginx.com    return _skip
6661844Szelenkov@nginx.com
6671844Szelenkov@nginx.com
6681654Szelenkov@nginx.com@pytest.fixture
6691654Szelenkov@nginx.comdef temp_dir(request):
6701654Szelenkov@nginx.com    return unit_instance['temp_dir']
6711654Szelenkov@nginx.com
6721848Szelenkov@nginx.com
6731596Szelenkov@nginx.com@pytest.fixture
6741596Szelenkov@nginx.comdef is_unsafe(request):
6751596Szelenkov@nginx.com    return request.config.getoption("--unsafe")
6761596Szelenkov@nginx.com
6771848Szelenkov@nginx.com
6781596Szelenkov@nginx.com@pytest.fixture
6791596Szelenkov@nginx.comdef is_su(request):
6801596Szelenkov@nginx.com    return os.geteuid() == 0
6811596Szelenkov@nginx.com
6821848Szelenkov@nginx.com
6831769St.nateldemoura@f5.com@pytest.fixture
6841769St.nateldemoura@f5.comdef unit_pid(request):
6851769St.nateldemoura@f5.com    return unit_instance['process'].pid
6861769St.nateldemoura@f5.com
6871848Szelenkov@nginx.com
6881596Szelenkov@nginx.comdef pytest_sessionfinish(session):
6891803Szelenkov@nginx.com    if not option.restart and option.save_log:
6901850Smax.romanov@nginx.com        print('Path to unit.log:\n' + Log.get_path() + '\n')
6911803Szelenkov@nginx.com
6921803Szelenkov@nginx.com    option.restart = True
6931803Szelenkov@nginx.com
6941596Szelenkov@nginx.com    unit_stop()
6951864Szelenkov@nginx.com
6961864Szelenkov@nginx.com    public_dir(option.cache_dir)
6971757St.nateldemoura@f5.com    shutil.rmtree(option.cache_dir)
6981864Szelenkov@nginx.com
6991868Szelenkov@nginx.com    if not option.save_log and os.path.isdir(option.temp_dir):
7001864Szelenkov@nginx.com        public_dir(option.temp_dir)
7011864Szelenkov@nginx.com        shutil.rmtree(option.temp_dir)
702