xref: /unit/test/conftest.py (revision 1735)
11596Szelenkov@nginx.comimport fcntl
21596Szelenkov@nginx.comimport os
31596Szelenkov@nginx.comimport platform
41635Szelenkov@nginx.comimport re
51621Szelenkov@nginx.comimport shutil
61596Szelenkov@nginx.comimport signal
71654Szelenkov@nginx.comimport socket
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
141596Szelenkov@nginx.com
151635Szelenkov@nginx.comimport pytest
161635Szelenkov@nginx.com
171621Szelenkov@nginx.comfrom unit.check.go import check_go
181621Szelenkov@nginx.comfrom unit.check.node import check_node
191621Szelenkov@nginx.comfrom unit.check.tls import check_openssl
201730Szelenkov@nginx.comfrom unit.option import option
21*1735Szelenkov@nginx.comfrom unit.utils import public_dir
22*1735Szelenkov@nginx.comfrom unit.utils import waitforfiles
231621Szelenkov@nginx.com
241596Szelenkov@nginx.com
251596Szelenkov@nginx.comdef pytest_addoption(parser):
261596Szelenkov@nginx.com    parser.addoption(
271596Szelenkov@nginx.com        "--detailed",
281596Szelenkov@nginx.com        default=False,
291596Szelenkov@nginx.com        action="store_true",
301596Szelenkov@nginx.com        help="Detailed output for tests",
311596Szelenkov@nginx.com    )
321596Szelenkov@nginx.com    parser.addoption(
331596Szelenkov@nginx.com        "--print_log",
341596Szelenkov@nginx.com        default=False,
351596Szelenkov@nginx.com        action="store_true",
361596Szelenkov@nginx.com        help="Print unit.log to stdout in case of errors",
371596Szelenkov@nginx.com    )
381596Szelenkov@nginx.com    parser.addoption(
391596Szelenkov@nginx.com        "--save_log",
401596Szelenkov@nginx.com        default=False,
411596Szelenkov@nginx.com        action="store_true",
421596Szelenkov@nginx.com        help="Save unit.log after the test execution",
431596Szelenkov@nginx.com    )
441596Szelenkov@nginx.com    parser.addoption(
451596Szelenkov@nginx.com        "--unsafe",
461596Szelenkov@nginx.com        default=False,
471596Szelenkov@nginx.com        action="store_true",
481596Szelenkov@nginx.com        help="Run unsafe tests",
491596Szelenkov@nginx.com    )
501596Szelenkov@nginx.com
511596Szelenkov@nginx.com
521596Szelenkov@nginx.comunit_instance = {}
531654Szelenkov@nginx.com_processes = []
541596Szelenkov@nginx.com
551596Szelenkov@nginx.comdef pytest_configure(config):
561730Szelenkov@nginx.com    option.config = config.option
571730Szelenkov@nginx.com
581730Szelenkov@nginx.com    option.detailed = config.option.detailed
591730Szelenkov@nginx.com    option.print_log = config.option.print_log
601730Szelenkov@nginx.com    option.save_log = config.option.save_log
611730Szelenkov@nginx.com    option.unsafe = config.option.unsafe
621596Szelenkov@nginx.com
631596Szelenkov@nginx.com    option.generated_tests = {}
641596Szelenkov@nginx.com    option.current_dir = os.path.abspath(
651596Szelenkov@nginx.com        os.path.join(os.path.dirname(__file__), os.pardir)
661596Szelenkov@nginx.com    )
671596Szelenkov@nginx.com    option.test_dir = option.current_dir + '/test'
681596Szelenkov@nginx.com    option.architecture = platform.architecture()[0]
691596Szelenkov@nginx.com    option.system = platform.system()
701596Szelenkov@nginx.com
711596Szelenkov@nginx.com    # set stdout to non-blocking
721596Szelenkov@nginx.com
731596Szelenkov@nginx.com    if option.detailed or option.print_log:
741596Szelenkov@nginx.com        fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, 0)
751596Szelenkov@nginx.com
761596Szelenkov@nginx.com
771654Szelenkov@nginx.comdef skip_alert(*alerts):
781654Szelenkov@nginx.com    option.skip_alerts.extend(alerts)
791654Szelenkov@nginx.com
801654Szelenkov@nginx.com
811596Szelenkov@nginx.comdef pytest_generate_tests(metafunc):
821596Szelenkov@nginx.com    cls = metafunc.cls
831670Smax.romanov@nginx.com    if (not hasattr(cls, 'application_type')
841670Smax.romanov@nginx.com            or cls.application_type == None
851670Smax.romanov@nginx.com            or cls.application_type == 'external'):
861596Szelenkov@nginx.com        return
871596Szelenkov@nginx.com
881596Szelenkov@nginx.com    type = cls.application_type
891596Szelenkov@nginx.com
901611Smax.romanov@nginx.com    def generate_tests(versions):
911611Smax.romanov@nginx.com        metafunc.fixturenames.append('tmp_ct')
921630Smax.romanov@nginx.com        metafunc.parametrize('tmp_ct', versions)
931611Smax.romanov@nginx.com
941630Smax.romanov@nginx.com        for version in versions:
951611Smax.romanov@nginx.com            option.generated_tests[
961630Smax.romanov@nginx.com                metafunc.function.__name__ + '[{}]'.format(version)
971611Smax.romanov@nginx.com            ] = (type + ' ' + version)
981611Smax.romanov@nginx.com
991596Szelenkov@nginx.com    # take available module from option and generate tests for each version
1001596Szelenkov@nginx.com
1011611Smax.romanov@nginx.com    for module, prereq_version in cls.prerequisites['modules'].items():
1021596Szelenkov@nginx.com        if module in option.available['modules']:
1031596Szelenkov@nginx.com            available_versions = option.available['modules'][module]
1041596Szelenkov@nginx.com
1051596Szelenkov@nginx.com            if prereq_version == 'all':
1061611Smax.romanov@nginx.com                generate_tests(available_versions)
1071596Szelenkov@nginx.com
1081596Szelenkov@nginx.com            elif prereq_version == 'any':
1091596Szelenkov@nginx.com                option.generated_tests[metafunc.function.__name__] = (
1101596Szelenkov@nginx.com                    type + ' ' + available_versions[0]
1111596Szelenkov@nginx.com                )
1121611Smax.romanov@nginx.com            elif callable(prereq_version):
1131611Smax.romanov@nginx.com                generate_tests(
1141611Smax.romanov@nginx.com                    list(filter(prereq_version, available_versions))
1151611Smax.romanov@nginx.com                )
1161611Smax.romanov@nginx.com
1171596Szelenkov@nginx.com            else:
1181611Smax.romanov@nginx.com                raise ValueError(
1191611Smax.romanov@nginx.com                    """
1201611Smax.romanov@nginx.comUnexpected prerequisite version "%s" for module "%s" in %s.
1211611Smax.romanov@nginx.com'all', 'any' or callable expected."""
1221611Smax.romanov@nginx.com                    % (str(prereq_version), module, str(cls))
1231611Smax.romanov@nginx.com                )
1241596Szelenkov@nginx.com
1251596Szelenkov@nginx.com
1261596Szelenkov@nginx.comdef pytest_sessionstart(session):
1271596Szelenkov@nginx.com    option.available = {'modules': {}, 'features': {}}
1281596Szelenkov@nginx.com
1291596Szelenkov@nginx.com    unit = unit_run()
1301596Szelenkov@nginx.com
1311596Szelenkov@nginx.com    # read unit.log
1321596Szelenkov@nginx.com
1331596Szelenkov@nginx.com    for i in range(50):
1341596Szelenkov@nginx.com        with open(unit['temp_dir'] + '/unit.log', 'r') as f:
1351596Szelenkov@nginx.com            log = f.read()
1361596Szelenkov@nginx.com            m = re.search('controller started', log)
1371596Szelenkov@nginx.com
1381596Szelenkov@nginx.com            if m is None:
1391596Szelenkov@nginx.com                time.sleep(0.1)
1401596Szelenkov@nginx.com            else:
1411596Szelenkov@nginx.com                break
1421596Szelenkov@nginx.com
1431596Szelenkov@nginx.com    if m is None:
1441654Szelenkov@nginx.com        _print_log(log)
1451596Szelenkov@nginx.com        exit("Unit is writing log too long")
1461596Szelenkov@nginx.com
1471596Szelenkov@nginx.com    # discover available modules from unit.log
1481596Szelenkov@nginx.com
1491596Szelenkov@nginx.com    for module in re.findall(r'module: ([a-zA-Z]+) (.*) ".*"$', log, re.M):
1501705Smax.romanov@nginx.com        versions = option.available['modules'].setdefault(module[0], [])
1511705Smax.romanov@nginx.com        if module[1] not in versions:
1521705Smax.romanov@nginx.com            versions.append(module[1])
1531596Szelenkov@nginx.com
1541621Szelenkov@nginx.com    # discover modules from check
1551621Szelenkov@nginx.com
1561621Szelenkov@nginx.com    option.available['modules']['openssl'] = check_openssl(unit['unitd'])
1571621Szelenkov@nginx.com    option.available['modules']['go'] = check_go(
1581621Szelenkov@nginx.com        option.current_dir, unit['temp_dir'], option.test_dir
1591621Szelenkov@nginx.com    )
1601621Szelenkov@nginx.com    option.available['modules']['node'] = check_node(option.current_dir)
1611621Szelenkov@nginx.com
1621621Szelenkov@nginx.com    # remove None values
1631621Szelenkov@nginx.com
1641621Szelenkov@nginx.com    option.available['modules'] = {
1651621Szelenkov@nginx.com        k: v for k, v in option.available['modules'].items() if v is not None
1661621Szelenkov@nginx.com    }
1671621Szelenkov@nginx.com
1681596Szelenkov@nginx.com    unit_stop()
1691596Szelenkov@nginx.com
1701730Szelenkov@nginx.com    _check_alerts()
1711730Szelenkov@nginx.com
1721654Szelenkov@nginx.com    shutil.rmtree(unit_instance['temp_dir'])
1731596Szelenkov@nginx.com
1741654Szelenkov@nginx.com
1751654Szelenkov@nginx.com@pytest.hookimpl(tryfirst=True, hookwrapper=True)
1761654Szelenkov@nginx.comdef pytest_runtest_makereport(item, call):
1771654Szelenkov@nginx.com    # execute all other hooks to obtain the report object
1781654Szelenkov@nginx.com    outcome = yield
1791654Szelenkov@nginx.com    rep = outcome.get_result()
1801654Szelenkov@nginx.com
1811654Szelenkov@nginx.com    # set a report attribute for each phase of a call, which can
1821654Szelenkov@nginx.com    # be "setup", "call", "teardown"
1831654Szelenkov@nginx.com
1841654Szelenkov@nginx.com    setattr(item, "rep_" + rep.when, rep)
1851654Szelenkov@nginx.com
1861654Szelenkov@nginx.com
1871654Szelenkov@nginx.com@pytest.fixture(autouse=True)
1881654Szelenkov@nginx.comdef run(request):
1891654Szelenkov@nginx.com    unit = unit_run()
1901654Szelenkov@nginx.com    option.temp_dir = unit['temp_dir']
1911654Szelenkov@nginx.com
1921596Szelenkov@nginx.com    option.skip_alerts = [
1931596Szelenkov@nginx.com        r'read signalfd\(4\) failed',
1941596Szelenkov@nginx.com        r'sendmsg.+failed',
1951596Szelenkov@nginx.com        r'recvmsg.+failed',
1961596Szelenkov@nginx.com    ]
1971596Szelenkov@nginx.com    option.skip_sanitizer = False
1981596Szelenkov@nginx.com
1991654Szelenkov@nginx.com    yield
2001654Szelenkov@nginx.com
2011654Szelenkov@nginx.com    # stop unit
2021654Szelenkov@nginx.com
2031654Szelenkov@nginx.com    error = unit_stop()
2041654Szelenkov@nginx.com
2051654Szelenkov@nginx.com    if error:
2061654Szelenkov@nginx.com        _print_log()
2071654Szelenkov@nginx.com
2081654Szelenkov@nginx.com    assert error is None, 'stop unit'
2091654Szelenkov@nginx.com
2101654Szelenkov@nginx.com    # stop all processes
2111654Szelenkov@nginx.com
2121654Szelenkov@nginx.com    error = stop_processes()
2131654Szelenkov@nginx.com
2141654Szelenkov@nginx.com    if error:
2151654Szelenkov@nginx.com        _print_log()
2161654Szelenkov@nginx.com
2171654Szelenkov@nginx.com    assert error is None, 'stop unit'
2181654Szelenkov@nginx.com
2191654Szelenkov@nginx.com    # check unit.log for alerts
2201654Szelenkov@nginx.com
2211654Szelenkov@nginx.com    _check_alerts()
2221654Szelenkov@nginx.com
2231654Szelenkov@nginx.com    # print unit.log in case of error
2241654Szelenkov@nginx.com
2251706Smax.romanov@nginx.com    if hasattr(request.node, 'rep_call') and request.node.rep_call.failed:
2261654Szelenkov@nginx.com        _print_log()
2271654Szelenkov@nginx.com
2281654Szelenkov@nginx.com    # remove unit.log
2291654Szelenkov@nginx.com
2301654Szelenkov@nginx.com    if not option.save_log:
2311654Szelenkov@nginx.com        shutil.rmtree(unit['temp_dir'])
2321654Szelenkov@nginx.com
2331596Szelenkov@nginx.comdef unit_run():
2341596Szelenkov@nginx.com    global unit_instance
2351596Szelenkov@nginx.com    build_dir = option.current_dir + '/build'
2361596Szelenkov@nginx.com    unitd = build_dir + '/unitd'
2371596Szelenkov@nginx.com
2381596Szelenkov@nginx.com    if not os.path.isfile(unitd):
2391596Szelenkov@nginx.com        exit('Could not find unit')
2401596Szelenkov@nginx.com
2411596Szelenkov@nginx.com    temp_dir = tempfile.mkdtemp(prefix='unit-test-')
2421596Szelenkov@nginx.com    public_dir(temp_dir)
2431596Szelenkov@nginx.com
2441596Szelenkov@nginx.com    if oct(stat.S_IMODE(os.stat(build_dir).st_mode)) != '0o777':
2451596Szelenkov@nginx.com        public_dir(build_dir)
2461596Szelenkov@nginx.com
2471596Szelenkov@nginx.com    os.mkdir(temp_dir + '/state')
2481596Szelenkov@nginx.com
2491596Szelenkov@nginx.com    with open(temp_dir + '/unit.log', 'w') as log:
2501596Szelenkov@nginx.com        unit_instance['process'] = subprocess.Popen(
2511596Szelenkov@nginx.com            [
2521596Szelenkov@nginx.com                unitd,
2531596Szelenkov@nginx.com                '--no-daemon',
2541596Szelenkov@nginx.com                '--modules',
2551596Szelenkov@nginx.com                build_dir,
2561596Szelenkov@nginx.com                '--state',
2571596Szelenkov@nginx.com                temp_dir + '/state',
2581596Szelenkov@nginx.com                '--pid',
2591596Szelenkov@nginx.com                temp_dir + '/unit.pid',
2601596Szelenkov@nginx.com                '--log',
2611596Szelenkov@nginx.com                temp_dir + '/unit.log',
2621596Szelenkov@nginx.com                '--control',
2631596Szelenkov@nginx.com                'unix:' + temp_dir + '/control.unit.sock',
2641596Szelenkov@nginx.com                '--tmp',
2651596Szelenkov@nginx.com                temp_dir,
2661596Szelenkov@nginx.com            ],
2671596Szelenkov@nginx.com            stderr=log,
2681596Szelenkov@nginx.com        )
2691596Szelenkov@nginx.com
2701596Szelenkov@nginx.com    if not waitforfiles(temp_dir + '/control.unit.sock'):
2711596Szelenkov@nginx.com        _print_log()
2721596Szelenkov@nginx.com        exit('Could not start unit')
2731596Szelenkov@nginx.com
2741596Szelenkov@nginx.com    unit_instance['temp_dir'] = temp_dir
2751596Szelenkov@nginx.com    unit_instance['log'] = temp_dir + '/unit.log'
2761596Szelenkov@nginx.com    unit_instance['control_sock'] = temp_dir + '/control.unit.sock'
2771596Szelenkov@nginx.com    unit_instance['unitd'] = unitd
2781596Szelenkov@nginx.com
2791596Szelenkov@nginx.com    return unit_instance
2801596Szelenkov@nginx.com
2811596Szelenkov@nginx.com
2821596Szelenkov@nginx.comdef unit_stop():
2831596Szelenkov@nginx.com    p = unit_instance['process']
2841596Szelenkov@nginx.com
2851596Szelenkov@nginx.com    if p.poll() is not None:
2861596Szelenkov@nginx.com        return
2871596Szelenkov@nginx.com
2881596Szelenkov@nginx.com    p.send_signal(signal.SIGQUIT)
2891596Szelenkov@nginx.com
2901596Szelenkov@nginx.com    try:
2911596Szelenkov@nginx.com        retcode = p.wait(15)
2921596Szelenkov@nginx.com        if retcode:
2931596Szelenkov@nginx.com            return 'Child process terminated with code ' + str(retcode)
2941706Smax.romanov@nginx.com
2951706Smax.romanov@nginx.com    except KeyboardInterrupt:
2961706Smax.romanov@nginx.com        p.kill()
2971706Smax.romanov@nginx.com        raise
2981706Smax.romanov@nginx.com
2991596Szelenkov@nginx.com    except:
3001596Szelenkov@nginx.com        p.kill()
3011596Szelenkov@nginx.com        return 'Could not terminate unit'
3021596Szelenkov@nginx.com
3031596Szelenkov@nginx.com
3041596Szelenkov@nginx.com
3051654Szelenkov@nginx.comdef _check_alerts(path=None):
3061654Szelenkov@nginx.com    if path is None:
3071654Szelenkov@nginx.com        path = unit_instance['log']
3081596Szelenkov@nginx.com
3091654Szelenkov@nginx.com    with open(path, 'r', encoding='utf-8', errors='ignore') as f:
3101654Szelenkov@nginx.com        log = f.read()
3111654Szelenkov@nginx.com
3121596Szelenkov@nginx.com    found = False
3131596Szelenkov@nginx.com
3141596Szelenkov@nginx.com    alerts = re.findall(r'.+\[alert\].+', log)
3151596Szelenkov@nginx.com
3161596Szelenkov@nginx.com    if alerts:
3171596Szelenkov@nginx.com        print('All alerts/sanitizer errors found in log:')
3181596Szelenkov@nginx.com        [print(alert) for alert in alerts]
3191596Szelenkov@nginx.com        found = True
3201596Szelenkov@nginx.com
3211596Szelenkov@nginx.com    if option.skip_alerts:
3221596Szelenkov@nginx.com        for skip in option.skip_alerts:
3231596Szelenkov@nginx.com            alerts = [al for al in alerts if re.search(skip, al) is None]
3241596Szelenkov@nginx.com
3251596Szelenkov@nginx.com    if alerts:
3261654Szelenkov@nginx.com        _print_log(log)
3271596Szelenkov@nginx.com        assert not alerts, 'alert(s)'
3281596Szelenkov@nginx.com
3291596Szelenkov@nginx.com    if not option.skip_sanitizer:
3301596Szelenkov@nginx.com        sanitizer_errors = re.findall('.+Sanitizer.+', log)
3311596Szelenkov@nginx.com
3321596Szelenkov@nginx.com        if sanitizer_errors:
3331654Szelenkov@nginx.com            _print_log(log)
3341596Szelenkov@nginx.com            assert not sanitizer_errors, 'sanitizer error(s)'
3351596Szelenkov@nginx.com
3361596Szelenkov@nginx.com    if found:
3371596Szelenkov@nginx.com        print('skipped.')
3381596Szelenkov@nginx.com
3391596Szelenkov@nginx.com
3401654Szelenkov@nginx.comdef _print_log(data=None):
3411654Szelenkov@nginx.com    path = unit_instance['log']
3421596Szelenkov@nginx.com
3431621Szelenkov@nginx.com    print('Path to unit.log:\n' + path + '\n')
3441596Szelenkov@nginx.com
3451596Szelenkov@nginx.com    if option.print_log:
3461596Szelenkov@nginx.com        os.set_blocking(sys.stdout.fileno(), True)
3471596Szelenkov@nginx.com        sys.stdout.flush()
3481596Szelenkov@nginx.com
3491596Szelenkov@nginx.com        if data is None:
3501621Szelenkov@nginx.com            with open(path, 'r', encoding='utf-8', errors='ignore') as f:
3511596Szelenkov@nginx.com                shutil.copyfileobj(f, sys.stdout)
3521596Szelenkov@nginx.com        else:
3531596Szelenkov@nginx.com            sys.stdout.write(data)
3541596Szelenkov@nginx.com
3551596Szelenkov@nginx.com
3561654Szelenkov@nginx.comdef run_process(target, *args):
3571654Szelenkov@nginx.com    global _processes
3581654Szelenkov@nginx.com
3591654Szelenkov@nginx.com    process = Process(target=target, args=args)
3601654Szelenkov@nginx.com    process.start()
3611654Szelenkov@nginx.com
3621654Szelenkov@nginx.com    _processes.append(process)
3631654Szelenkov@nginx.com
3641654Szelenkov@nginx.comdef stop_processes():
3651654Szelenkov@nginx.com    if not _processes:
3661654Szelenkov@nginx.com        return
3671654Szelenkov@nginx.com
3681654Szelenkov@nginx.com    fail = False
3691654Szelenkov@nginx.com    for process in _processes:
3701654Szelenkov@nginx.com        if process.is_alive():
3711654Szelenkov@nginx.com            process.terminate()
3721654Szelenkov@nginx.com            process.join(timeout=15)
3731654Szelenkov@nginx.com
3741654Szelenkov@nginx.com            if process.is_alive():
3751654Szelenkov@nginx.com                fail = True
3761654Szelenkov@nginx.com
3771654Szelenkov@nginx.com    if fail:
3781654Szelenkov@nginx.com        return 'Fail to stop process(es)'
3791654Szelenkov@nginx.com
3801654Szelenkov@nginx.com
3811654Szelenkov@nginx.com@pytest.fixture
3821654Szelenkov@nginx.comdef temp_dir(request):
3831654Szelenkov@nginx.com    return unit_instance['temp_dir']
3841654Szelenkov@nginx.com
3851596Szelenkov@nginx.com@pytest.fixture
3861596Szelenkov@nginx.comdef is_unsafe(request):
3871596Szelenkov@nginx.com    return request.config.getoption("--unsafe")
3881596Szelenkov@nginx.com
3891596Szelenkov@nginx.com@pytest.fixture
3901596Szelenkov@nginx.comdef is_su(request):
3911596Szelenkov@nginx.com    return os.geteuid() == 0
3921596Szelenkov@nginx.com
3931596Szelenkov@nginx.comdef pytest_sessionfinish(session):
3941596Szelenkov@nginx.com    unit_stop()
395