-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathpytest.py
More file actions
93 lines (78 loc) · 3.5 KB
/
pytest.py
File metadata and controls
93 lines (78 loc) · 3.5 KB
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
from importlib.metadata import version
import pytest
try:
from pytest_django.django_compat import is_django_unittest
except ImportError:
def is_django_unittest(_item):
return False
from _appmap import noappmap, testing_framework, wrapt
from _appmap.env import Env
logger = Env.current.getLogger(__name__)
class recorded_testcase: # pylint: disable=too-few-public-methods
def __init__(self, item):
self.item = item
@wrapt.decorator
def __call__(self, wrapped, _, args, kwargs):
item = self.item
with item.session.appmap.record(
item.cls, item.name, method_id=item.originalname, location=item.location
) as metadata:
with testing_framework.collect_result_metadata(metadata):
return wrapped(*args, **kwargs)
if not Env.current.is_appmap_repo and Env.current.enables("tests"):
logger.debug("Test recording is enabled (Pytest)")
@pytest.hookimpl
def pytest_sessionstart(session):
session.appmap = testing_framework.session(
name="pytest", recorder_type="tests", version=version("pytest")
)
@pytest.hookimpl
def pytest_runtest_call(item):
# The presence of a `_testcase` attribute on an item indicates
# that it was created from a `unittest.TestCase`. An item
# created from a TestCase has an `obj` attribute, assigned
# during in setup, which holds the actual test
# function. Wrapping that function will capture the recording
# we want. `obj` gets unset during teardown, so there's no
# chance of rewrapping it.
#
# However, depending on the user's configuration, `item.obj`
# may have been already instrumented for recording. In this
# case, it will be a `wrapt` class, rather than just a
# function. This is fine: the decorator we apply here will be
# called first, starting the recording. Next, the
# instrumentation decorator will be called, recording the
# `call` event. Finally, the original function will be called,
# running the test case. (This nesting of function calls is
# verified by the expected appmap in the test for a unittest
# TestCase run by pytest.)
if hasattr(item, "_testcase") and not is_django_unittest(item):
setattr(
item._testcase, # pylint: disable=protected-access
"_appmap_pytest_recording",
True,
)
if not noappmap.disables(item.obj, item.cls):
testing_framework.disable_test_case(item.obj)
item.obj = recorded_testcase(item)(item.obj)
@pytest.hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem):
# There definitely shouldn't be a `_testcase` attribute on a
# pytest test.
assert not hasattr(pyfuncitem, "_testcase")
if noappmap.disables(pyfuncitem.function, pyfuncitem.cls):
yield
return
with pyfuncitem.session.appmap.record(
pyfuncitem.cls,
pyfuncitem.name,
method_id=pyfuncitem.originalname,
location=pyfuncitem.location,
) as metadata:
testing_framework.disable_test_case(pyfuncitem.obj)
result = yield
try:
with testing_framework.collect_result_metadata(metadata):
result.get_result()
except: # pylint: disable=bare-except # noqa: E722
pass # exception got recorded in metadata