-
Notifications
You must be signed in to change notification settings - Fork 29
/
conftest.py
121 lines (101 loc) · 4.74 KB
/
conftest.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
"""
pytest hooks for sasmodels
Hooks for running sasmodels tests via pytest.
*pytest_collection_modifyitems* adds the test description to the end of
the test name. This is needed for the generated list of tests is sasmodels,
where each test has a description giving the name of the model. For example
"model_tests::[3]" becomes "model_tests::[3]::bcc_paracrystal-dll". Need to
leave the "::[3]" in the name since that is the way you can indicate this
test specifically from the pytest command line. [This is perhaps because
the modifyitems hook is only called after test selection.]
*pytest_ignore_collect* skips kernelcl.py if pyopencl cannot be imported.
"""
from __future__ import print_function
import os.path
import inspect
import pytest
from _pytest.unittest import TestCaseFunction
# CRUFT: pytest<5.4 does not have from_parent class method.
function_test = getattr(pytest.Function, 'from_parent', pytest.Function)
def pytest_pycollect_makeitem(collector, name, obj):
"""
Convert test generator into list of function tests so that pytest doesn't
complain about deprecated yield tests.
Note that unlike nose, the tests are generated and saved instead of run
immediately. This means that any dynamic context, such as a for-loop
variable, must be captured by wrapping the yield result in a function call.
For example::
for value in 1, 2, 3:
for test in test_cases:
yield test, value
will need to be changed to::
def build_test(test, value):
return test, value
for value in 1, 2, 3:
for test in test_cases:
yield build_test(test, value)
This allows the context (test and value) to be captured by lexical closure
in build_test. See https://stackoverflow.com/a/233835/6195051.
"""
if collector.istestfunction(obj, name) and is_generator(obj):
tests = []
for number, yielded in enumerate(obj()):
index, call = split_yielded_test(yielded, number)
description = getattr(call, 'description', name+index)
test = function_test(
parent=collector, name=description, callobj=call)
tests.append(test)
return tests
def is_generator(func):
"""
Returns True if function has yield.
"""
# Cribbed from _pytest.compat is_generator and iscoroutinefunction; these
# may not be available as part of pytest 4.
coroutine = (getattr(func, '_is_coroutine', False) or
getattr(inspect, 'iscoroutinefunction', lambda f: False)(func))
generator = inspect.isgeneratorfunction(func)
return generator and not coroutine
def split_yielded_test(obj, number):
if not isinstance(obj, (tuple, list)):
obj = (obj,)
assert callable(obj[0])
index = "[%d]"%number
call = obj[0]
return index, call
USE_DOCSTRING_AS_DESCRIPTION = True
def pytest_collection_modifyitems(session, config, items):
"""
Add description to the test node id if item is a function and function
has a description attribute or __doc__ attribute.
"""
for item in items:
if isinstance(item, pytest.Function):
if isinstance(item, TestCaseFunction):
# TestCase uses item.name to find the method so skip
continue
function = item.obj
# If the test case provides a "description" attribute then use it
# as an extended description. If there is no description attribute,
# then perhaps use the test docstring.
if USE_DOCSTRING_AS_DESCRIPTION:
description = getattr(function, 'description', function.__doc__)
else:
description = getattr(function, 'description', "")
# If description is not supplied but yield args are, then use the
# yield args for the description
if not description and getattr(item, '_args', ()):
description = (str(item._args) if len(item._args) > 1
else str(item._args[0]))
# Set the description as part of the node identifier.
if description:
# Strip spaces from start and end and strip dots from end
# pytest converts '.' to '::' on output for some reason.
description = description.strip().rstrip('.')
# Join multi-line descriptions into a single line
if '\n' in description:
description = " ".join(line.strip()
for line in description.split('\n'))
# Note: leave the current name mostly as-is since the prefix
# is needed to specify the nth test from a list of tests.
item.name += "::" + description