7
7
import warnings
8
8
import doctest
9
9
10
- from _pytest import doctest as pydoctest , outcomes
10
+
11
+ import pytest
12
+ from _pytest import doctest as pydoctest , mark , outcomes
11
13
from _pytest .doctest import DoctestModule , DoctestTextfile
12
14
from _pytest .pathlib import import_path
13
15
@@ -54,11 +56,50 @@ def pytest_ignore_collect(collection_path, config):
54
56
if "tests" in path_str or "test_" in path_str :
55
57
return True
56
58
57
- for entry in config .dt_config .pytest_extra_skips :
59
+ for entry in config .dt_config .pytest_extra_ignore :
58
60
if entry in str (collection_path ):
59
61
return True
60
62
61
63
64
+ def is_private (item ):
65
+ """Decide if an DocTestItem `item` is private.
66
+
67
+ Private items are ignored in pytest_collect_modifyitem`.
68
+ """
69
+ # Here we look at the name of a test module/object. A seemingly less
70
+ # hacky alternative is to populate a set of seen `item.dtest` attributes
71
+ # (which are actual DocTest objects). The issue with that is it's tricky
72
+ # for explicit skips/ignores. Do we skip linalg.det or linalg._basic.det?
73
+ # (collection order is not guaranteed)
74
+ parent_full_name = item .parent .module .__name__
75
+ is_private = "._" in parent_full_name
76
+ return is_private
77
+
78
+
79
+ def _maybe_add_markers (item , config ):
80
+ """Add xfail/skip markers to `item` if DTConfig says so.
81
+
82
+ Modifies the item in-place.
83
+ """
84
+ dt_config = config .dt_config
85
+
86
+ extra_skip = dt_config .pytest_extra_skip
87
+ skip_it = item .name in extra_skip
88
+ if item .name in extra_skip :
89
+ reason = extra_skip [item .name ] or ''
90
+ item .add_marker (
91
+ pytest .mark .skip (reason = reason )
92
+ )
93
+
94
+ extra_xfail = dt_config .pytest_extra_xfail
95
+ fail_it = item .name in extra_xfail
96
+ if fail_it :
97
+ reason = extra_xfail [item .name ] or ''
98
+ item .add_marker (
99
+ pytest .mark .xfail (reason = reason )
100
+ )
101
+
102
+
62
103
def pytest_collection_modifyitems (config , items ):
63
104
"""
64
105
This hook is executed after test collection and allows you to modify the list of collected items.
@@ -73,45 +114,45 @@ def pytest_collection_modifyitems(config, items):
73
114
# XXX: The logic in this function can probably be folded into DTModule.collect.
74
115
# I (E.B.) quickly tried it and it does not seem to just work. Apparently something
75
116
# pytest-y runs in between DTModule.collect and this hook (should that something
76
- # be the proper home for all collection?)
117
+ # be the proper home for all collection?).
118
+ # Also note that DTTextfile needs _maybe_add_markers, too.
77
119
78
- need_filter_unique = config . getvalue ( "collection_strategy" ) == 'api'
79
-
80
- if config .getoption ( "--doctest-modules " ) and need_filter_unique :
81
- unique_items = []
120
+ need_filter_unique = (
121
+ config . getoption ( "--doctest-modules" ) and
122
+ config .getvalue ( "collection_strategy " ) == 'api'
123
+ )
82
124
83
- for item in items :
84
- assert isinstance (item .parent , DTModule )
125
+ unique_items = []
85
126
127
+ for item in items :
128
+ if isinstance (item .parent , DTModule ) and need_filter_unique :
86
129
# objects are collected twice: from their public module + from the impl module
87
130
# e.g. for `levy_stable` we have
88
131
# (Pdb) p item.name, item.parent.name
89
132
# ('scipy.stats.levy_stable', 'build-install/lib/python3.10/site-packages/scipy/stats/__init__.py')
133
+ # and
90
134
# ('scipy.stats.distributions.levy_stable', 'distributions.py')
91
135
# so we filter out the second occurence
92
136
#
93
137
# There are two options:
94
- # - either the impl module has a leading underscore, or
95
- # - it needs to be explicitly listed in 'extra_skips ' config key
138
+ # - either the impl module has a leading underscore (scipy.linalg._basic) , or
139
+ # - it needs to be explicitly listed in the 'extra_ignore ' config key (distributions.py)
96
140
#
97
141
# Note that the last part cannot be automated: scipy.cluster.vq is public, but
98
142
# scipy.stats.distributions is not
99
- extra_skips = config .dt_config .pytest_extra_skips
100
-
101
- # NB: The below looks at the name of a test module/object. A seemingly less
102
- # hacky alternative is to populate a set of seen `item.dtest` attributes
103
- # (which are actual DocTest objects). The issue with that is it's tricky
104
- # for skips. Do we skip linalg.det or linalg._basic.det? (collection order
105
- # is not guaranteed)
143
+ extra_ignore = config .dt_config .pytest_extra_ignore
106
144
parent_full_name = item .parent .module .__name__
107
- is_public = "._" not in parent_full_name
108
- is_duplicate = parent_full_name in extra_skips or item .name in extra_skips
145
+ is_duplicate = parent_full_name in extra_ignore or item .name in extra_ignore
146
+
147
+ if is_duplicate or is_private (item ):
148
+ # ignore it
149
+ continue
109
150
110
- if is_public and not is_duplicate :
111
- unique_items .append (item )
151
+ _maybe_add_markers ( item , config )
152
+ unique_items .append (item )
112
153
113
- # Replace the original list of test items with the unique ones
114
- items [:] = unique_items
154
+ # Replace the original list of test items with the unique ones
155
+ items [:] = unique_items
115
156
116
157
117
158
class DTModule (DoctestModule ):
0 commit comments