summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/harness/wptrunner/executors/pytestrunner/runner.py
blob: 28b8f609cbf1f0f8dfb8458e8d08b9c3cf2a74dd (plain)
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
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

"""Provides interface to deal with pytest.

Usage::

    session = webdriver.client.Session("127.0.0.1", "4444", "/")
    harness_result = ("OK", None)
    subtest_results = pytestrunner.run("/path/to/test", session.url)
    return (harness_result, subtest_results)
"""

import errno
import shutil
import tempfile

from . import fixtures


pytest = None


def do_delayed_imports():
    global pytest
    import pytest


def run(path, session, url_getter, timeout=0):
    """Run Python test at ``path`` in pytest.  The provided ``session``
    is exposed as a fixture available in the scope of the test functions.

    :param path: Path to the test file.
    :param session: WebDriver session to expose.
    :param url_getter: Function to get server url from test environment, given
        a protocol.
    :param timeout: Duration before interrupting potentially hanging
        tests.  If 0, there is no timeout.

    :returns: List of subtest results, which are tuples of (test id,
        status, message, stacktrace).
    """

    if pytest is None:
        do_delayed_imports()

    recorder = SubtestResultRecorder()
    plugins = [recorder,
               fixtures.Session(session),
               fixtures.Server(url_getter)]

    # TODO(ato): Deal with timeouts

    with TemporaryDirectory() as cache:
        pytest.main(["--strict",  # turn warnings into errors
                     "--verbose",  # show each individual subtest
                     "--capture", "no",  # enable stdout/stderr from tests
                     "--basetemp", cache,  # temporary directory
                     path],
                    plugins=plugins)

    return recorder.results


class SubtestResultRecorder(object):
    def __init__(self):
        self.results = []

    def pytest_runtest_logreport(self, report):
        if report.passed and report.when == "call":
            self.record_pass(report)
        elif report.failed:
            if report.when != "call":
                self.record_error(report)
            else:
                self.record_fail(report)
        elif report.skipped:
            self.record_skip(report)

    def record_pass(self, report):
        self.record(report.nodeid, "PASS")

    def record_fail(self, report):
        self.record(report.nodeid, "FAIL", stack=report.longrepr)

    def record_error(self, report):
        # error in setup/teardown
        if report.when != "call":
            message = "%s error" % report.when
        self.record(report.nodeid, "ERROR", message, report.longrepr)

    def record_skip(self, report):
        self.record(report.nodeid, "ERROR",
                    "In-test skip decorators are disallowed, "
                    "please use WPT metadata to ignore tests.")

    def record(self, test, status, message=None, stack=None):
        if stack is not None:
            stack = str(stack)
        new_result = (test, status, message, stack)
        self.results.append(new_result)


class TemporaryDirectory(object):
    def __enter__(self):
        self.path = tempfile.mkdtemp(prefix="pytest-")
        return self.path

    def __exit__(self, *args):
        try:
            shutil.rmtree(self.path)
        except OSError as e:
            # no such file or directory
            if e.errno != errno.ENOENT:
                raise