-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathamc-pytest.py
132 lines (105 loc) · 3.68 KB
/
amc-pytest.py
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
"""Code from the slides for pico-pytest."""
import sys
from itertools import chain
from pathlib import Path
import pytest
def get_module(path):
path = Path(path)
sys.path.insert(0, str(path.parent))
name = path.stem
module = __import__(name)
print(f"<get_module('{path}') => import '{name}'>", file=sys.stderr)
return module
def collect_test_functions(module):
return [
obj
for obj in module.__dict__.values()
if obj.__class__.__name__ == "function" and obj.__name__.startswith("test_")
]
name2fixture = {}
def fixture(function):
name2fixture[function.__name__] = function
print(f"<fixture({function.__name__}) => registered>", file=sys.stderr)
def execute_test(function):
names = function.__code__.co_varnames[:function.__code__.co_argcount]
kwargs = {name: f() for name, f in name2fixture.items() if name in names}
result = "."
try:
function(**kwargs) # pass as keyword arguments => order doesn't matter
return "passed"
except Exception as e:
result = "F"
return e
finally:
print(result, end="")
def mark_attacher_factory(name):
def attach_mark(function):
print(
f"<attach_mark({function.__name__}) => '{name}'>", file=sys.stderr
)
try:
function.pico_pytest_marks.append(name)
except AttributeError:
function.pico_pytest_marks = [name]
return function
return attach_mark
class MarkAttacherFactoryFactory:
def __getattr__(self, name): # called when attribute lookup fails
return mark_attacher_factory(name)
def satisfies_expression(expression, markers):
newTokens = []
for token in expression.split():
if token in markers:
newTokens.append("True")
elif token in ["and", "or", "not"]:
newTokens.append(token)
else:
newTokens.append("False")
return eval(" ".join(newTokens))
def filter_tests(tests, expression):
remaining = []
for test in tests:
markers = getattr(test, "pico_pytest_marks", [])
if satisfies_expression(expression, markers):
remaining.append(test)
if deselected := len(tests) - len(remaining): # 3.8 assignment expression
print(
f"<filter_tests(...)> => deselected {deselected} tests>",
file=sys.stderr
)
return remaining
def display_collected(tests):
print(f"collected {len(tests)} tests:")
for test in tests:
print(f"\t<{type(test).__name__} {test.__name__}>")
def report(results):
failed = {
test: result for test, result in results.items()
if not isinstance(result, str)
}
if failed:
print("\n\nFAILURES:")
for test, e in failed.items():
result = f"{e.__class__.__name__}('{e.args[0]}')"
print(f"{test.__name__}: {result}")
print(f"\nexecuted: {len(results)}, failed: {len(failed)}")
else:
print(f"\nexecuted: {len(results)} (all is fine!)")
def pico_pytest(path=Path.cwd(), collect_only=False, mark_expression=""):
paths = list(Path(path).glob("**/test_*.py"))
modules = [get_module(path) for path in paths]
tests = list(chain(*[collect_test_functions(m) for m in modules]))
if mark_expression:
tests = filter_tests(tests, mark_expression)
if collect_only:
display_collected(tests)
else:
report({test: execute_test(test) for test in tests})
if __name__ == '__main__':
pytest.fixture = fixture
pytest.mark = MarkAttacherFactoryFactory()
pico_pytest()
print()
pico_pytest(collect_only=True)
print()
pico_pytest(collect_only=True, mark_expression="charlie or lucy")