xref: /unit/test/conftest.py (revision 2074:8e06a879600e)
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):
1622073Szelenkov@nginx.com                generate_tests(list(filter(prereq_version, available_versions)))
1631611Smax.romanov@nginx.com
1641596Szelenkov@nginx.com            else:
1651611Smax.romanov@nginx.com                raise ValueError(
1661611Smax.romanov@nginx.com                    """
1671611Smax.romanov@nginx.comUnexpected prerequisite version "%s" for module "%s" in %s.
1681611Smax.romanov@nginx.com'all', 'any' or callable expected."""
1691611Smax.romanov@nginx.com                    % (str(prereq_version), module, str(cls))
1701611Smax.romanov@nginx.com                )
1711596Szelenkov@nginx.com
1721596Szelenkov@nginx.com
1731596Szelenkov@nginx.comdef pytest_sessionstart(session):
1741596Szelenkov@nginx.com    option.available = {'modules': {}, 'features': {}}
1751596Szelenkov@nginx.com
1761596Szelenkov@nginx.com    unit = unit_run()
1771596Szelenkov@nginx.com
1781596Szelenkov@nginx.com    # read unit.log
1791596Szelenkov@nginx.com
1801596Szelenkov@nginx.com    for i in range(50):
1811850Smax.romanov@nginx.com        with open(Log.get_path(), 'r') as f:
1821596Szelenkov@nginx.com            log = f.read()
1831596Szelenkov@nginx.com            m = re.search('controller started', log)
1841596Szelenkov@nginx.com
1851596Szelenkov@nginx.com            if m is None:
1861596Szelenkov@nginx.com                time.sleep(0.1)
1871596Szelenkov@nginx.com            else:
1881596Szelenkov@nginx.com                break
1891596Szelenkov@nginx.com
1901596Szelenkov@nginx.com    if m is None:
1911654Szelenkov@nginx.com        _print_log(log)
1921596Szelenkov@nginx.com        exit("Unit is writing log too long")
1931596Szelenkov@nginx.com
1941596Szelenkov@nginx.com    # discover available modules from unit.log
1951596Szelenkov@nginx.com
1961596Szelenkov@nginx.com    for module in re.findall(r'module: ([a-zA-Z]+) (.*) ".*"$', log, re.M):
1971705Smax.romanov@nginx.com        versions = option.available['modules'].setdefault(module[0], [])
1981705Smax.romanov@nginx.com        if module[1] not in versions:
1991705Smax.romanov@nginx.com            versions.append(module[1])
2001596Szelenkov@nginx.com
2011621Szelenkov@nginx.com    # discover modules from check
2021621Szelenkov@nginx.com
2031621Szelenkov@nginx.com    option.available['modules']['openssl'] = check_openssl(unit['unitd'])
2042062Smax.romanov@nginx.com    option.available['modules']['go'] = check_go()
2051621Szelenkov@nginx.com    option.available['modules']['node'] = check_node(option.current_dir)
2061807Szelenkov@nginx.com    option.available['modules']['regex'] = check_regex(unit['unitd'])
2071621Szelenkov@nginx.com
2081621Szelenkov@nginx.com    # remove None values
2091621Szelenkov@nginx.com
2101621Szelenkov@nginx.com    option.available['modules'] = {
2111621Szelenkov@nginx.com        k: v for k, v in option.available['modules'].items() if v is not None
2121621Szelenkov@nginx.com    }
2131621Szelenkov@nginx.com
2141858Szelenkov@nginx.com    check_chroot()
2151740Szelenkov@nginx.com    check_isolation()
2161740Szelenkov@nginx.com
2171805Szelenkov@nginx.com    _clear_conf(unit['temp_dir'] + '/control.unit.sock')
2181803Szelenkov@nginx.com
2191596Szelenkov@nginx.com    unit_stop()
2201596Szelenkov@nginx.com
2211730Szelenkov@nginx.com    _check_alerts()
2221730Szelenkov@nginx.com
2231803Szelenkov@nginx.com    if option.restart:
2241803Szelenkov@nginx.com        shutil.rmtree(unit_instance['temp_dir'])
2251596Szelenkov@nginx.com
2261848Szelenkov@nginx.com
2271654Szelenkov@nginx.com@pytest.hookimpl(tryfirst=True, hookwrapper=True)
2281654Szelenkov@nginx.comdef pytest_runtest_makereport(item, call):
2291654Szelenkov@nginx.com    # execute all other hooks to obtain the report object
2301654Szelenkov@nginx.com    outcome = yield
2311654Szelenkov@nginx.com    rep = outcome.get_result()
2321654Szelenkov@nginx.com
2331654Szelenkov@nginx.com    # set a report attribute for each phase of a call, which can
2341654Szelenkov@nginx.com    # be "setup", "call", "teardown"
2351654Szelenkov@nginx.com
2361654Szelenkov@nginx.com    setattr(item, "rep_" + rep.when, rep)
2371654Szelenkov@nginx.com
2381654Szelenkov@nginx.com
2391741Szelenkov@nginx.com@pytest.fixture(scope='class', autouse=True)
2401741Szelenkov@nginx.comdef check_prerequisites(request):
2411741Szelenkov@nginx.com    cls = request.cls
2421741Szelenkov@nginx.com    missed = []
2431741Szelenkov@nginx.com
2441741Szelenkov@nginx.com    # check modules
2451741Szelenkov@nginx.com
2461741Szelenkov@nginx.com    if 'modules' in cls.prerequisites:
2471741Szelenkov@nginx.com        available_modules = list(option.available['modules'].keys())
2481741Szelenkov@nginx.com
2491741Szelenkov@nginx.com        for module in cls.prerequisites['modules']:
2501741Szelenkov@nginx.com            if module in available_modules:
2511741Szelenkov@nginx.com                continue
2521741Szelenkov@nginx.com
2531741Szelenkov@nginx.com            missed.append(module)
2541741Szelenkov@nginx.com
2551741Szelenkov@nginx.com    if missed:
2561741Szelenkov@nginx.com        pytest.skip('Unit has no ' + ', '.join(missed) + ' module(s)')
2571741Szelenkov@nginx.com
2581741Szelenkov@nginx.com    # check features
2591741Szelenkov@nginx.com
2601741Szelenkov@nginx.com    if 'features' in cls.prerequisites:
2611741Szelenkov@nginx.com        available_features = list(option.available['features'].keys())
2621741Szelenkov@nginx.com
2631741Szelenkov@nginx.com        for feature in cls.prerequisites['features']:
2641741Szelenkov@nginx.com            if feature in available_features:
2651741Szelenkov@nginx.com                continue
2661741Szelenkov@nginx.com
2671741Szelenkov@nginx.com            missed.append(feature)
2681741Szelenkov@nginx.com
2691741Szelenkov@nginx.com    if missed:
2701741Szelenkov@nginx.com        pytest.skip(', '.join(missed) + ' feature(s) not supported')
2711741Szelenkov@nginx.com
2721741Szelenkov@nginx.com
2731654Szelenkov@nginx.com@pytest.fixture(autouse=True)
2741654Szelenkov@nginx.comdef run(request):
2751654Szelenkov@nginx.com    unit = unit_run()
2761654Szelenkov@nginx.com
2771596Szelenkov@nginx.com    option.skip_alerts = [
2781596Szelenkov@nginx.com        r'read signalfd\(4\) failed',
2791596Szelenkov@nginx.com        r'sendmsg.+failed',
2801596Szelenkov@nginx.com        r'recvmsg.+failed',
2811596Szelenkov@nginx.com    ]
2821596Szelenkov@nginx.com    option.skip_sanitizer = False
2831596Szelenkov@nginx.com
2841914Szelenkov@nginx.com    _fds_info['main']['skip'] = False
2851914Szelenkov@nginx.com    _fds_info['router']['skip'] = False
2861914Szelenkov@nginx.com    _fds_info['controller']['skip'] = False
2871844Szelenkov@nginx.com
2881654Szelenkov@nginx.com    yield
2891654Szelenkov@nginx.com
2901654Szelenkov@nginx.com    # stop unit
2911654Szelenkov@nginx.com
2921803Szelenkov@nginx.com    error_stop_unit = unit_stop()
2931803Szelenkov@nginx.com    error_stop_processes = stop_processes()
2941803Szelenkov@nginx.com
2951803Szelenkov@nginx.com    # prepare log
2961803Szelenkov@nginx.com
2971850Smax.romanov@nginx.com    with Log.open(encoding='utf-8') as f:
2981803Szelenkov@nginx.com        log = f.read()
2991850Smax.romanov@nginx.com        Log.set_pos(f.tell())
3001654Szelenkov@nginx.com
3011803Szelenkov@nginx.com    if not option.save_log and option.restart:
3021803Szelenkov@nginx.com        shutil.rmtree(unit['temp_dir'])
3031850Smax.romanov@nginx.com        Log.set_pos(0)
3041803Szelenkov@nginx.com
3051803Szelenkov@nginx.com    # clean temp_dir before the next test
3061654Szelenkov@nginx.com
3071803Szelenkov@nginx.com    if not option.restart:
3081914Szelenkov@nginx.com        _clear_conf(unit['temp_dir'] + '/control.unit.sock', log=log)
3091803Szelenkov@nginx.com
3101803Szelenkov@nginx.com        for item in os.listdir(unit['temp_dir']):
3111803Szelenkov@nginx.com            if item not in [
3121803Szelenkov@nginx.com                'control.unit.sock',
3131803Szelenkov@nginx.com                'state',
3141803Szelenkov@nginx.com                'unit.pid',
3151803Szelenkov@nginx.com                'unit.log',
3161803Szelenkov@nginx.com            ]:
3171803Szelenkov@nginx.com                path = os.path.join(unit['temp_dir'], item)
3181654Szelenkov@nginx.com
3191803Szelenkov@nginx.com                public_dir(path)
3201654Szelenkov@nginx.com
3212073Szelenkov@nginx.com                if os.path.isfile(path) or stat.S_ISSOCK(os.stat(path).st_mode):
3221803Szelenkov@nginx.com                    os.remove(path)
3231803Szelenkov@nginx.com                else:
3241933Smax.romanov@nginx.com                    for attempt in range(10):
3251933Smax.romanov@nginx.com                        try:
3261933Smax.romanov@nginx.com                            shutil.rmtree(path)
3271933Smax.romanov@nginx.com                            break
3281933Smax.romanov@nginx.com                        except OSError as err:
3291933Smax.romanov@nginx.com                            if err.errno != 16:
3301933Smax.romanov@nginx.com                                raise
3311933Smax.romanov@nginx.com                            time.sleep(1)
3321654Szelenkov@nginx.com
3331914Szelenkov@nginx.com    # check descriptors
3341844Szelenkov@nginx.com
3351914Szelenkov@nginx.com    _check_fds(log=log)
3361844Szelenkov@nginx.com
337*2074Szelenkov@nginx.com    # check processes id's and amount
338*2074Szelenkov@nginx.com
339*2074Szelenkov@nginx.com    _check_processes()
340*2074Szelenkov@nginx.com
3411654Szelenkov@nginx.com    # print unit.log in case of error
3421654Szelenkov@nginx.com
3431706Smax.romanov@nginx.com    if hasattr(request.node, 'rep_call') and request.node.rep_call.failed:
3441803Szelenkov@nginx.com        _print_log(log)
3451803Szelenkov@nginx.com
3461803Szelenkov@nginx.com    if error_stop_unit or error_stop_processes:
3471803Szelenkov@nginx.com        _print_log(log)
3481654Szelenkov@nginx.com
3491803Szelenkov@nginx.com    # check unit.log for errors
3501654Szelenkov@nginx.com
3511803Szelenkov@nginx.com    assert error_stop_unit is None, 'stop unit'
3521803Szelenkov@nginx.com    assert error_stop_processes is None, 'stop processes'
3531803Szelenkov@nginx.com
3541803Szelenkov@nginx.com    _check_alerts(log=log)
3551654Szelenkov@nginx.com
3561848Szelenkov@nginx.com
3571986Szelenkov@nginx.comdef unit_run(state_dir=None):
3581596Szelenkov@nginx.com    global unit_instance
3591803Szelenkov@nginx.com
3601803Szelenkov@nginx.com    if not option.restart and 'unitd' in unit_instance:
3611803Szelenkov@nginx.com        return unit_instance
3621803Szelenkov@nginx.com
3631596Szelenkov@nginx.com    build_dir = option.current_dir + '/build'
3641596Szelenkov@nginx.com    unitd = build_dir + '/unitd'
3651596Szelenkov@nginx.com
3661596Szelenkov@nginx.com    if not os.path.isfile(unitd):
3671596Szelenkov@nginx.com        exit('Could not find unit')
3681596Szelenkov@nginx.com
3691596Szelenkov@nginx.com    temp_dir = tempfile.mkdtemp(prefix='unit-test-')
3701596Szelenkov@nginx.com    public_dir(temp_dir)
3711596Szelenkov@nginx.com
3721596Szelenkov@nginx.com    if oct(stat.S_IMODE(os.stat(build_dir).st_mode)) != '0o777':
3731596Szelenkov@nginx.com        public_dir(build_dir)
3741596Szelenkov@nginx.com
3751986Szelenkov@nginx.com    state = temp_dir + '/state' if state_dir is None else state_dir
3761986Szelenkov@nginx.com    if not os.path.isdir(state):
3771986Szelenkov@nginx.com        os.mkdir(state)
3781596Szelenkov@nginx.com
3791761Sdefan@nginx.com    unitd_args = [
3801761Sdefan@nginx.com        unitd,
3811761Sdefan@nginx.com        '--no-daemon',
3821761Sdefan@nginx.com        '--modules',
3831761Sdefan@nginx.com        build_dir,
3841761Sdefan@nginx.com        '--state',
3851986Szelenkov@nginx.com        state,
3861761Sdefan@nginx.com        '--pid',
3871761Sdefan@nginx.com        temp_dir + '/unit.pid',
3881761Sdefan@nginx.com        '--log',
3891761Sdefan@nginx.com        temp_dir + '/unit.log',
3901761Sdefan@nginx.com        '--control',
3911761Sdefan@nginx.com        'unix:' + temp_dir + '/control.unit.sock',
3921761Sdefan@nginx.com        '--tmp',
3931761Sdefan@nginx.com        temp_dir,
3941761Sdefan@nginx.com    ]
3951761Sdefan@nginx.com
3961761Sdefan@nginx.com    if option.user:
3971761Sdefan@nginx.com        unitd_args.extend(['--user', option.user])
3981761Sdefan@nginx.com
3991596Szelenkov@nginx.com    with open(temp_dir + '/unit.log', 'w') as log:
4001761Sdefan@nginx.com        unit_instance['process'] = subprocess.Popen(unitd_args, stderr=log)
4011596Szelenkov@nginx.com
4021934Smax.romanov@nginx.com    Log.temp_dir = temp_dir
4031934Smax.romanov@nginx.com
4041596Szelenkov@nginx.com    if not waitforfiles(temp_dir + '/control.unit.sock'):
4051596Szelenkov@nginx.com        _print_log()
4061596Szelenkov@nginx.com        exit('Could not start unit')
4071596Szelenkov@nginx.com
4081596Szelenkov@nginx.com    unit_instance['temp_dir'] = temp_dir
4091596Szelenkov@nginx.com    unit_instance['control_sock'] = temp_dir + '/control.unit.sock'
4101596Szelenkov@nginx.com    unit_instance['unitd'] = unitd
4111596Szelenkov@nginx.com
4121850Smax.romanov@nginx.com    option.temp_dir = temp_dir
4131850Smax.romanov@nginx.com
4141844Szelenkov@nginx.com    with open(temp_dir + '/unit.pid', 'r') as f:
4151844Szelenkov@nginx.com        unit_instance['pid'] = f.read().rstrip()
4161844Szelenkov@nginx.com
4171986Szelenkov@nginx.com    if state_dir is None:
4181986Szelenkov@nginx.com        _clear_conf(unit_instance['temp_dir'] + '/control.unit.sock')
4191844Szelenkov@nginx.com
4201914Szelenkov@nginx.com    _fds_info['main']['fds'] = _count_fds(unit_instance['pid'])
4211844Szelenkov@nginx.com
4221914Szelenkov@nginx.com    router = _fds_info['router']
4231844Szelenkov@nginx.com    router['pid'] = pid_by_name(router['name'])
4241844Szelenkov@nginx.com    router['fds'] = _count_fds(router['pid'])
4251844Szelenkov@nginx.com
4261914Szelenkov@nginx.com    controller = _fds_info['controller']
4271844Szelenkov@nginx.com    controller['pid'] = pid_by_name(controller['name'])
4281844Szelenkov@nginx.com    controller['fds'] = _count_fds(controller['pid'])
4291844Szelenkov@nginx.com
4301596Szelenkov@nginx.com    return unit_instance
4311596Szelenkov@nginx.com
4321596Szelenkov@nginx.com
4331596Szelenkov@nginx.comdef unit_stop():
4341803Szelenkov@nginx.com    if not option.restart:
4351803Szelenkov@nginx.com        if inspect.stack()[1].function.startswith('test_'):
4361803Szelenkov@nginx.com            pytest.skip('no restart mode')
4371803Szelenkov@nginx.com
4381803Szelenkov@nginx.com        return
4391803Szelenkov@nginx.com
440*2074Szelenkov@nginx.com    # check zombies
441*2074Szelenkov@nginx.com
442*2074Szelenkov@nginx.com    out = subprocess.check_output(
443*2074Szelenkov@nginx.com        ['ps', 'ax', '-o', 'state', '-o', 'ppid']
444*2074Szelenkov@nginx.com    ).decode()
445*2074Szelenkov@nginx.com    z_ppids = re.findall(r'Z\s*(\d+)', out)
446*2074Szelenkov@nginx.com    assert unit_instance['pid'] not in z_ppids, 'no zombies'
447*2074Szelenkov@nginx.com
448*2074Szelenkov@nginx.com    # terminate unit
449*2074Szelenkov@nginx.com
4501596Szelenkov@nginx.com    p = unit_instance['process']
4511596Szelenkov@nginx.com
4521596Szelenkov@nginx.com    if p.poll() is not None:
4531596Szelenkov@nginx.com        return
4541596Szelenkov@nginx.com
4551596Szelenkov@nginx.com    p.send_signal(signal.SIGQUIT)
4561596Szelenkov@nginx.com
4571596Szelenkov@nginx.com    try:
4581596Szelenkov@nginx.com        retcode = p.wait(15)
4591596Szelenkov@nginx.com        if retcode:
4601596Szelenkov@nginx.com            return 'Child process terminated with code ' + str(retcode)
4611706Smax.romanov@nginx.com
4621706Smax.romanov@nginx.com    except KeyboardInterrupt:
4631706Smax.romanov@nginx.com        p.kill()
4641706Smax.romanov@nginx.com        raise
4651706Smax.romanov@nginx.com
4661596Szelenkov@nginx.com    except:
4671596Szelenkov@nginx.com        p.kill()
4681596Szelenkov@nginx.com        return 'Could not terminate unit'
4691596Szelenkov@nginx.com
4701596Szelenkov@nginx.com
4711914Szelenkov@nginx.com@print_log_on_assert
4721914Szelenkov@nginx.comdef _check_alerts(*, log=None):
4731803Szelenkov@nginx.com    if log is None:
4741850Smax.romanov@nginx.com        with Log.open(encoding='utf-8') as f:
4751803Szelenkov@nginx.com            log = f.read()
4761654Szelenkov@nginx.com
4771596Szelenkov@nginx.com    found = False
4781596Szelenkov@nginx.com
4791596Szelenkov@nginx.com    alerts = re.findall(r'.+\[alert\].+', log)
4801596Szelenkov@nginx.com
4811596Szelenkov@nginx.com    if alerts:
4821736Szelenkov@nginx.com        print('\nAll alerts/sanitizer errors found in log:')
4831596Szelenkov@nginx.com        [print(alert) for alert in alerts]
4841596Szelenkov@nginx.com        found = True
4851596Szelenkov@nginx.com
4861596Szelenkov@nginx.com    if option.skip_alerts:
4871596Szelenkov@nginx.com        for skip in option.skip_alerts:
4881596Szelenkov@nginx.com            alerts = [al for al in alerts if re.search(skip, al) is None]
4891596Szelenkov@nginx.com
4901914Szelenkov@nginx.com    assert not alerts, 'alert(s)'
4911596Szelenkov@nginx.com
4921596Szelenkov@nginx.com    if not option.skip_sanitizer:
4931596Szelenkov@nginx.com        sanitizer_errors = re.findall('.+Sanitizer.+', log)
4941596Szelenkov@nginx.com
4951914Szelenkov@nginx.com        assert not sanitizer_errors, 'sanitizer error(s)'
4961596Szelenkov@nginx.com
4971596Szelenkov@nginx.com    if found:
4981596Szelenkov@nginx.com        print('skipped.')
4991596Szelenkov@nginx.com
5001596Szelenkov@nginx.com
5011934Smax.romanov@nginx.comdef _print_log(log=None):
5021850Smax.romanov@nginx.com    path = Log.get_path()
5031596Szelenkov@nginx.com
5041621Szelenkov@nginx.com    print('Path to unit.log:\n' + path + '\n')
5051596Szelenkov@nginx.com
5061596Szelenkov@nginx.com    if option.print_log:
5071596Szelenkov@nginx.com        os.set_blocking(sys.stdout.fileno(), True)
5081596Szelenkov@nginx.com        sys.stdout.flush()
5091596Szelenkov@nginx.com
5101914Szelenkov@nginx.com        if log is None:
5111621Szelenkov@nginx.com            with open(path, 'r', encoding='utf-8', errors='ignore') as f:
5121596Szelenkov@nginx.com                shutil.copyfileobj(f, sys.stdout)
5131596Szelenkov@nginx.com        else:
5141914Szelenkov@nginx.com            sys.stdout.write(log)
5151596Szelenkov@nginx.com
5161596Szelenkov@nginx.com
5171914Szelenkov@nginx.com@print_log_on_assert
5181914Szelenkov@nginx.comdef _clear_conf(sock, *, log=None):
5191805Szelenkov@nginx.com    resp = http.put(
5201803Szelenkov@nginx.com        url='/config',
5211803Szelenkov@nginx.com        sock_type='unix',
5221803Szelenkov@nginx.com        addr=sock,
5231803Szelenkov@nginx.com        body=json.dumps({"listeners": {}, "applications": {}}),
5241803Szelenkov@nginx.com    )['body']
5251803Szelenkov@nginx.com
5261914Szelenkov@nginx.com    assert 'success' in resp, 'clear conf'
5271805Szelenkov@nginx.com
5281808Szelenkov@nginx.com    if 'openssl' not in option.available['modules']:
5291808Szelenkov@nginx.com        return
5301808Szelenkov@nginx.com
5311805Szelenkov@nginx.com    try:
5321848Szelenkov@nginx.com        certs = json.loads(
5332073Szelenkov@nginx.com            http.get(url='/certificates', sock_type='unix', addr=sock)['body']
5341848Szelenkov@nginx.com        ).keys()
5351805Szelenkov@nginx.com
5361805Szelenkov@nginx.com    except json.JSONDecodeError:
5371805Szelenkov@nginx.com        pytest.fail('Can\'t parse certificates list.')
5381805Szelenkov@nginx.com
5391805Szelenkov@nginx.com    for cert in certs:
5401805Szelenkov@nginx.com        resp = http.delete(
5412073Szelenkov@nginx.com            url='/certificates/' + cert,
5422073Szelenkov@nginx.com            sock_type='unix',
5432073Szelenkov@nginx.com            addr=sock,
5441805Szelenkov@nginx.com        )['body']
5451805Szelenkov@nginx.com
5461914Szelenkov@nginx.com        assert 'success' in resp, 'remove certificate'
5471914Szelenkov@nginx.com
5481914Szelenkov@nginx.com
549*2074Szelenkov@nginx.comdef _check_processes():
550*2074Szelenkov@nginx.com    router_pid = _fds_info['router']['pid']
551*2074Szelenkov@nginx.com    controller_pid = _fds_info['controller']['pid']
552*2074Szelenkov@nginx.com    unit_pid = unit_instance['pid']
553*2074Szelenkov@nginx.com
554*2074Szelenkov@nginx.com    for i in range(600):
555*2074Szelenkov@nginx.com        out = (
556*2074Szelenkov@nginx.com            subprocess.check_output(
557*2074Szelenkov@nginx.com                ['ps', '-ax', '-o', 'pid', '-o', 'ppid', '-o', 'command']
558*2074Szelenkov@nginx.com            )
559*2074Szelenkov@nginx.com            .decode()
560*2074Szelenkov@nginx.com            .splitlines()
561*2074Szelenkov@nginx.com        )
562*2074Szelenkov@nginx.com        out = [l for l in out if unit_pid in l]
563*2074Szelenkov@nginx.com
564*2074Szelenkov@nginx.com        if len(out) <= 3:
565*2074Szelenkov@nginx.com            break
566*2074Szelenkov@nginx.com
567*2074Szelenkov@nginx.com        time.sleep(0.1)
568*2074Szelenkov@nginx.com
569*2074Szelenkov@nginx.com    assert len(out) == 3, 'main, router, and controller expected'
570*2074Szelenkov@nginx.com
571*2074Szelenkov@nginx.com    out = [l for l in out if 'unit: main' not in l]
572*2074Szelenkov@nginx.com    assert len(out) == 2, 'one main'
573*2074Szelenkov@nginx.com
574*2074Szelenkov@nginx.com    out = [
575*2074Szelenkov@nginx.com        l
576*2074Szelenkov@nginx.com        for l in out
577*2074Szelenkov@nginx.com        if re.search(router_pid + r'\s+' + unit_pid + r'.*unit: router', l)
578*2074Szelenkov@nginx.com        is None
579*2074Szelenkov@nginx.com    ]
580*2074Szelenkov@nginx.com    assert len(out) == 1, 'one router'
581*2074Szelenkov@nginx.com
582*2074Szelenkov@nginx.com    out = [
583*2074Szelenkov@nginx.com        l
584*2074Szelenkov@nginx.com        for l in out
585*2074Szelenkov@nginx.com        if re.search(
586*2074Szelenkov@nginx.com            controller_pid + r'\s+' + unit_pid + r'.*unit: controller', l
587*2074Szelenkov@nginx.com        )
588*2074Szelenkov@nginx.com        is None
589*2074Szelenkov@nginx.com    ]
590*2074Szelenkov@nginx.com    assert len(out) == 0, 'one controller'
591*2074Szelenkov@nginx.com
592*2074Szelenkov@nginx.com
5931914Szelenkov@nginx.com@print_log_on_assert
5941914Szelenkov@nginx.comdef _check_fds(*, log=None):
5951914Szelenkov@nginx.com    def waitforfds(diff):
5961914Szelenkov@nginx.com        for i in range(600):
5971914Szelenkov@nginx.com            fds_diff = diff()
5981914Szelenkov@nginx.com
5991914Szelenkov@nginx.com            if fds_diff <= option.fds_threshold:
6001914Szelenkov@nginx.com                break
6011914Szelenkov@nginx.com
6021914Szelenkov@nginx.com            time.sleep(0.1)
6031914Szelenkov@nginx.com
6041914Szelenkov@nginx.com        return fds_diff
6051914Szelenkov@nginx.com
6061914Szelenkov@nginx.com    ps = _fds_info['main']
6071914Szelenkov@nginx.com    if not ps['skip']:
6081914Szelenkov@nginx.com        fds_diff = waitforfds(
6091914Szelenkov@nginx.com            lambda: _count_fds(unit_instance['pid']) - ps['fds']
6101914Szelenkov@nginx.com        )
6111914Szelenkov@nginx.com        ps['fds'] += fds_diff
6121914Szelenkov@nginx.com
6132073Szelenkov@nginx.com        assert fds_diff <= option.fds_threshold, 'descriptors leak main process'
6141914Szelenkov@nginx.com
6151914Szelenkov@nginx.com    else:
6161914Szelenkov@nginx.com        ps['fds'] = _count_fds(unit_instance['pid'])
6171914Szelenkov@nginx.com
6181914Szelenkov@nginx.com    for name in ['controller', 'router']:
6191914Szelenkov@nginx.com        ps = _fds_info[name]
6201914Szelenkov@nginx.com        ps_pid = ps['pid']
6211914Szelenkov@nginx.com        ps['pid'] = pid_by_name(ps['name'])
6221914Szelenkov@nginx.com
6231914Szelenkov@nginx.com        if not ps['skip']:
6241914Szelenkov@nginx.com            fds_diff = waitforfds(lambda: _count_fds(ps['pid']) - ps['fds'])
6251914Szelenkov@nginx.com            ps['fds'] += fds_diff
6261914Szelenkov@nginx.com
6271914Szelenkov@nginx.com            if not option.restart:
6281914Szelenkov@nginx.com                assert ps['pid'] == ps_pid, 'same pid %s' % name
6291914Szelenkov@nginx.com
6301914Szelenkov@nginx.com            assert fds_diff <= option.fds_threshold, (
6311914Szelenkov@nginx.com                'descriptors leak %s' % name
6321914Szelenkov@nginx.com            )
6331914Szelenkov@nginx.com
6341914Szelenkov@nginx.com        else:
6351914Szelenkov@nginx.com            ps['fds'] = _count_fds(ps['pid'])
6361803Szelenkov@nginx.com
6371848Szelenkov@nginx.com
6381844Szelenkov@nginx.comdef _count_fds(pid):
6391844Szelenkov@nginx.com    procfile = '/proc/%s/fd' % pid
6401844Szelenkov@nginx.com    if os.path.isdir(procfile):
6411844Szelenkov@nginx.com        return len(os.listdir(procfile))
6421844Szelenkov@nginx.com
6431844Szelenkov@nginx.com    try:
6441844Szelenkov@nginx.com        out = subprocess.check_output(
6452073Szelenkov@nginx.com            ['procstat', '-f', pid],
6462073Szelenkov@nginx.com            stderr=subprocess.STDOUT,
6471844Szelenkov@nginx.com        ).decode()
6481844Szelenkov@nginx.com        return len(out.splitlines())
6491844Szelenkov@nginx.com
6501868Szelenkov@nginx.com    except (FileNotFoundError, TypeError, subprocess.CalledProcessError):
6511844Szelenkov@nginx.com        pass
6521844Szelenkov@nginx.com
6531844Szelenkov@nginx.com    try:
6541844Szelenkov@nginx.com        out = subprocess.check_output(
6552073Szelenkov@nginx.com            ['lsof', '-n', '-p', pid],
6562073Szelenkov@nginx.com            stderr=subprocess.STDOUT,
6571844Szelenkov@nginx.com        ).decode()
6581844Szelenkov@nginx.com        return len(out.splitlines())
6591844Szelenkov@nginx.com
6601868Szelenkov@nginx.com    except (FileNotFoundError, TypeError, subprocess.CalledProcessError):
6611844Szelenkov@nginx.com        pass
6621844Szelenkov@nginx.com
6631844Szelenkov@nginx.com    return 0
6641844Szelenkov@nginx.com
6651844Szelenkov@nginx.com
6661654Szelenkov@nginx.comdef run_process(target, *args):
6671654Szelenkov@nginx.com    global _processes
6681654Szelenkov@nginx.com
6691654Szelenkov@nginx.com    process = Process(target=target, args=args)
6701654Szelenkov@nginx.com    process.start()
6711654Szelenkov@nginx.com
6721654Szelenkov@nginx.com    _processes.append(process)
6731654Szelenkov@nginx.com
6741848Szelenkov@nginx.com
6751654Szelenkov@nginx.comdef stop_processes():
6761654Szelenkov@nginx.com    if not _processes:
6771654Szelenkov@nginx.com        return
6781654Szelenkov@nginx.com
6791654Szelenkov@nginx.com    fail = False
6801654Szelenkov@nginx.com    for process in _processes:
6811654Szelenkov@nginx.com        if process.is_alive():
6821654Szelenkov@nginx.com            process.terminate()
6831654Szelenkov@nginx.com            process.join(timeout=15)
6841654Szelenkov@nginx.com
6851654Szelenkov@nginx.com            if process.is_alive():
6861654Szelenkov@nginx.com                fail = True
6871654Szelenkov@nginx.com
6881654Szelenkov@nginx.com    if fail:
6891654Szelenkov@nginx.com        return 'Fail to stop process(es)'
6901654Szelenkov@nginx.com
6911654Szelenkov@nginx.com
6921844Szelenkov@nginx.comdef pid_by_name(name):
6931844Szelenkov@nginx.com    output = subprocess.check_output(['ps', 'ax', '-O', 'ppid']).decode()
6941844Szelenkov@nginx.com    m = re.search(
6951844Szelenkov@nginx.com        r'\s*(\d+)\s*' + str(unit_instance['pid']) + r'.*' + name, output
6961844Szelenkov@nginx.com    )
6971844Szelenkov@nginx.com    return None if m is None else m.group(1)
6981844Szelenkov@nginx.com
6991844Szelenkov@nginx.com
7001844Szelenkov@nginx.comdef find_proc(name, ps_output):
7011844Szelenkov@nginx.com    return re.findall(str(unit_instance['pid']) + r'.*' + name, ps_output)
7021844Szelenkov@nginx.com
7031844Szelenkov@nginx.com
7041736Szelenkov@nginx.com@pytest.fixture()
7051736Szelenkov@nginx.comdef skip_alert():
7061736Szelenkov@nginx.com    def _skip(*alerts):
7071736Szelenkov@nginx.com        option.skip_alerts.extend(alerts)
7081736Szelenkov@nginx.com
7091736Szelenkov@nginx.com    return _skip
7101736Szelenkov@nginx.com
7111736Szelenkov@nginx.com
7121844Szelenkov@nginx.com@pytest.fixture()
7131844Szelenkov@nginx.comdef skip_fds_check():
7141844Szelenkov@nginx.com    def _skip(main=False, router=False, controller=False):
7151914Szelenkov@nginx.com        _fds_info['main']['skip'] = main
7161914Szelenkov@nginx.com        _fds_info['router']['skip'] = router
7171914Szelenkov@nginx.com        _fds_info['controller']['skip'] = controller
7181844Szelenkov@nginx.com
7191844Szelenkov@nginx.com    return _skip
7201844Szelenkov@nginx.com
7211844Szelenkov@nginx.com
7221654Szelenkov@nginx.com@pytest.fixture
7231654Szelenkov@nginx.comdef temp_dir(request):
7241654Szelenkov@nginx.com    return unit_instance['temp_dir']
7251654Szelenkov@nginx.com
7261848Szelenkov@nginx.com
7271596Szelenkov@nginx.com@pytest.fixture
7281596Szelenkov@nginx.comdef is_unsafe(request):
7291596Szelenkov@nginx.com    return request.config.getoption("--unsafe")
7301596Szelenkov@nginx.com
7311848Szelenkov@nginx.com
7321596Szelenkov@nginx.com@pytest.fixture
7331596Szelenkov@nginx.comdef is_su(request):
7341596Szelenkov@nginx.com    return os.geteuid() == 0
7351596Szelenkov@nginx.com
7361848Szelenkov@nginx.com
7371769St.nateldemoura@f5.com@pytest.fixture
7381769St.nateldemoura@f5.comdef unit_pid(request):
7391769St.nateldemoura@f5.com    return unit_instance['process'].pid
7401769St.nateldemoura@f5.com
7411848Szelenkov@nginx.com
7421596Szelenkov@nginx.comdef pytest_sessionfinish(session):
7431803Szelenkov@nginx.com    if not option.restart and option.save_log:
7441850Smax.romanov@nginx.com        print('Path to unit.log:\n' + Log.get_path() + '\n')
7451803Szelenkov@nginx.com
7461803Szelenkov@nginx.com    option.restart = True
7471803Szelenkov@nginx.com
7481596Szelenkov@nginx.com    unit_stop()
7491864Szelenkov@nginx.com
7501864Szelenkov@nginx.com    public_dir(option.cache_dir)
7511757St.nateldemoura@f5.com    shutil.rmtree(option.cache_dir)
7521864Szelenkov@nginx.com
7531868Szelenkov@nginx.com    if not option.save_log and os.path.isdir(option.temp_dir):
7541864Szelenkov@nginx.com        public_dir(option.temp_dir)
7551864Szelenkov@nginx.com        shutil.rmtree(option.temp_dir)
756