1 | """ |
---|
2 | py.test hooks for sasmodels |
---|
3 | |
---|
4 | Hooks for running sasmodels tests via py.test. |
---|
5 | |
---|
6 | *pytest_collection_modifyitems* adds the test description to the end of |
---|
7 | the test name. This is needed for the generated list of tests is sasmodels, |
---|
8 | where each test has a description giving the name of the model. For example |
---|
9 | "model_tests::[3]" becomes "model_tests::[3]::bcc_paracrystal-dll". Need to |
---|
10 | leave the "::[3]" in the name since that is the way you can indicate this |
---|
11 | test specifically from the py.test command line. [This is perhaps because |
---|
12 | the modifyitems hook is only called after test selection.] |
---|
13 | |
---|
14 | *pytest_ignore_collect* skips kernelcl.py if pyopencl cannot be imported. |
---|
15 | """ |
---|
16 | from __future__ import print_function |
---|
17 | |
---|
18 | import os.path |
---|
19 | |
---|
20 | import pytest |
---|
21 | from _pytest.unittest import TestCaseFunction |
---|
22 | from _pytest.compat import is_generator |
---|
23 | |
---|
24 | def pytest_pycollect_makeitem(collector, name, obj): |
---|
25 | """ |
---|
26 | Convert test generator into list of function tests so that pytest doesn't |
---|
27 | complain about deprecated yield tests. |
---|
28 | |
---|
29 | Note that unlike nose, the tests are generated and saved instead of run |
---|
30 | immediately. This means that any dynamic context, such as a for-loop |
---|
31 | variable, must be captured by wrapping the yield result in a function call. |
---|
32 | |
---|
33 | For example:: |
---|
34 | |
---|
35 | for value in 1, 2, 3: |
---|
36 | for test in test_cases: |
---|
37 | yield test, value |
---|
38 | |
---|
39 | will need to be changed to:: |
---|
40 | |
---|
41 | def build_test(test, value): |
---|
42 | return test, value |
---|
43 | for value in 1, 2, 3: |
---|
44 | for test in test_cases: |
---|
45 | yield build_test(test, value) |
---|
46 | |
---|
47 | This allows the context (test and value) to be captured by lexical closure |
---|
48 | in build_test. See https://stackoverflow.com/a/233835/6195051. |
---|
49 | """ |
---|
50 | if collector.istestfunction(obj, name) and is_generator(obj): |
---|
51 | tests = [pytest.Function(name, parent=collector, args=yielded[1:], callobj=yielded[0]) |
---|
52 | for yielded in obj()] |
---|
53 | return tests |
---|
54 | |
---|
55 | |
---|
56 | USE_DOCSTRING_AS_DESCRIPTION = True |
---|
57 | def pytest_collection_modifyitems(session, config, items): |
---|
58 | """ |
---|
59 | Add description to the test node id if item is a function and function |
---|
60 | has a description attribute or __doc__ attribute. |
---|
61 | """ |
---|
62 | for item in items: |
---|
63 | #print(item.nodeid, type(item)) |
---|
64 | #for attr in dir(item): not attr.startswith('__') and print(attr, getattr(item, attr)) |
---|
65 | if isinstance(item, pytest.Function): |
---|
66 | if isinstance(item, TestCaseFunction): |
---|
67 | # TestCase uses item.name to find the method so skip |
---|
68 | continue |
---|
69 | function = item.obj |
---|
70 | |
---|
71 | # If the test case provides a "description" attribute then use it |
---|
72 | # as an extended description. If there is no description attribute, |
---|
73 | # then perhaps use the test docstring. |
---|
74 | if USE_DOCSTRING_AS_DESCRIPTION: |
---|
75 | description = getattr(function, 'description', function.__doc__) |
---|
76 | else: |
---|
77 | description = getattr(function, 'description', "") |
---|
78 | |
---|
79 | # If description is not supplied but yield args are, then use the |
---|
80 | # yield args for the description |
---|
81 | if not description and getattr(item, '_args', ()): |
---|
82 | description = str(item._args) if len(item._args) > 1 else str(item._args[0]) |
---|
83 | #print(item.nodeid, description, item._args) |
---|
84 | |
---|
85 | if description: |
---|
86 | # Strip spaces from start and end and strip dots from end |
---|
87 | # pytest converts '.' to '::' on output for some reason. |
---|
88 | description = description.strip().rstrip('.') |
---|
89 | # Join multi-line descriptions into a single line |
---|
90 | if '\n' in description: |
---|
91 | description = " ".join(line.strip() for line in description.split('\n')) |
---|
92 | |
---|
93 | # Set the description as part of the node identifier. |
---|
94 | if description: |
---|
95 | #print(type(item), dir(item)) |
---|
96 | #print(item.nodeid, description) |
---|
97 | #print(item.location, item.name, item.nodeid, item.originalname) |
---|
98 | # Note: leave the current name mostly as-is since the prefix |
---|
99 | # is needed to specify the nth test from a list of tests. |
---|
100 | #print("updating with", description) |
---|
101 | item.name += "::" + description |
---|
102 | #print("=>", item.nodeid) |
---|