aboutsummaryrefslogtreecommitdiffstats
path: root/library/python/pytest/main.py
blob: 6059ff45a3a357b04a0ddf37f76f03574a761a03 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import os
import sys
import time

import __res

FORCE_EXIT_TESTSFAILED_ENV = 'FORCE_EXIT_TESTSFAILED'


def main():
    import library.python.pytest.context as context

    context.Ctx["YA_PYTEST_START_TIMESTAMP"] = time.time()

    profile = None
    if '--profile-pytest' in sys.argv:
        sys.argv.remove('--profile-pytest')

        import pstats
        import cProfile

        profile = cProfile.Profile()
        profile.enable()

    # Reset influencing env. vars
    # For more info see library/python/testing/yatest_common/yatest/common/errors.py
    if FORCE_EXIT_TESTSFAILED_ENV in os.environ:
        del os.environ[FORCE_EXIT_TESTSFAILED_ENV]

    if "Y_PYTHON_CLEAR_ENTRY_POINT" in os.environ:
        if "Y_PYTHON_ENTRY_POINT" in os.environ:
            del os.environ["Y_PYTHON_ENTRY_POINT"]
        del os.environ["Y_PYTHON_CLEAR_ENTRY_POINT"]

    listing_mode = '--collect-only' in sys.argv
    yatest_runner = os.environ.get('YA_TEST_RUNNER') == '1'

    import pytest

    import library.python.pytest.plugins.collection as collection
    import library.python.pytest.plugins.ya as ya
    import library.python.pytest.plugins.conftests as conftests

    import _pytest.assertion
    from _pytest.monkeypatch import MonkeyPatch
    from . import rewrite

    m = MonkeyPatch()
    m.setattr(_pytest.assertion.rewrite, "AssertionRewritingHook", rewrite.AssertionRewritingHook)

    prefix = '__tests__.'

    test_modules = [
        # fmt: off
        name[len(prefix) :]
        for name in sys.extra_modules
        if name.startswith(prefix) and not name.endswith('.conftest')
        # fmt: on
    ]

    doctest_packages = __res.find("PY_DOCTEST_PACKAGES") or ""
    if isinstance(doctest_packages, bytes):
        doctest_packages = doctest_packages.decode('utf-8')
    doctest_packages = doctest_packages.split()

    def is_doctest_module(name):
        for package in doctest_packages:
            if name == package or name.startswith(str(package) + "."):
                return True
        return False

    doctest_modules = [
        # fmt: off
        name
        for name in sys.extra_modules
        if is_doctest_module(name)
        # fmt: on
    ]

    def remove_user_site(paths):
        site_paths = ('site-packages', 'site-python')

        def is_site_path(path):
            for p in site_paths:
                if path.find(p) != -1:
                    return True
            return False

        new_paths = list(paths)
        for p in paths:
            if is_site_path(p):
                new_paths.remove(p)

        return new_paths

    sys.path = remove_user_site(sys.path)
    rc = pytest.main(
        plugins=[
            collection.CollectionPlugin(test_modules, doctest_modules),
            ya,
            conftests,
        ]
    )

    if rc == 5:
        # don't care about EXIT_NOTESTSCOLLECTED
        rc = 0

    if rc == 1 and yatest_runner and not listing_mode and not os.environ.get(FORCE_EXIT_TESTSFAILED_ENV) == '1':
        # XXX it's place for future improvements
        # Test wrapper should terminate with 0 exit code if there are common test failures
        # and report it with trace-file machinery.
        # However, there are several case when we don't want to suppress exit_code:
        #  - listing machinery doesn't use trace-file currently and rely on stdout and exit_code
        #  - RestartTestException and InfrastructureException required non-zero exit_code to be processes correctly
        rc = 0

    if profile:
        profile.disable()
        ps = pstats.Stats(profile, stream=sys.stderr).sort_stats('cumulative')
        ps.print_stats()
        if '--output-dir' in sys.argv:
            output_dir = sys.argv[sys.argv.index('--output-dir') + 1]
            prof_filename = os.path.join(output_dir, 'pytest.profile')
            ps.dump_stats(prof_filename)

            try:
                import gprof2dot
            except ImportError as e:
                sys.stderr.write("Failed to generate call graph: {}\n".format(e))
                gprof2dot = None

            if gprof2dot:
                import shlex

                dot_filename = os.path.join(output_dir, 'pytest.profile.dot')
                args = [
                    prof_filename,
                    '--format=pstats',
                    '--output={}'.format(dot_filename),
                ]
                if 'PYTEST_GPROF2DOT_ARGS' in os.environ:
                    x = os.environ['PYTEST_GPROF2DOT_ARGS']
                    args.extend(shlex.split(x))

                gprof2dot.main(argv=args)

    sys.exit(rc)


if __name__ == '__main__':
    main()