|
17 | 17 | REPORT_CONTEXT: ReportContext | None = None |
18 | 18 |
|
19 | 19 | _PARAMETRIZE_PATH_KEY = pytest.StashKey[tuple[str, ...]]() |
20 | | -_PARAMETRIZE_STACK: list[NewStep] = [] |
| 20 | +# Each frame: (path_key, open NewStep). Frames are shared across sibling test |
| 21 | +# items and drained at module teardown / session end. |
| 22 | +_PARAMETRIZE_STACK: list[tuple[str, NewStep]] = [] |
| 23 | + |
| 24 | + |
| 25 | +def _drain_parametrize_stack() -> None: |
| 26 | + while _PARAMETRIZE_STACK: |
| 27 | + _, ns = _PARAMETRIZE_STACK.pop() |
| 28 | + ns.__exit__(None, None, None) |
21 | 29 |
|
22 | 30 |
|
23 | 31 | def _build_parametrize_path(item: pytest.Item) -> tuple[str, ...]: |
@@ -45,8 +53,7 @@ def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item |
45 | 53 |
|
46 | 54 | def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None: |
47 | 55 | """Drain any parametrize parents still open (e.g. when module_substep was disabled).""" |
48 | | - while _PARAMETRIZE_STACK: |
49 | | - _PARAMETRIZE_STACK.pop().__exit__(None, None, None) |
| 56 | + _drain_parametrize_stack() |
50 | 57 |
|
51 | 58 |
|
52 | 59 | def pytest_addoption(parser: pytest.Parser) -> None: |
@@ -201,14 +208,8 @@ def _step_impl( |
201 | 208 | report_context: ReportContext, request: pytest.FixtureRequest |
202 | 209 | ) -> Generator[NewStep | None, None, None]: |
203 | 210 | item = request.node |
204 | | - callspec = getattr(item, "callspec", None) |
205 | | - if callspec is not None and callspec.params: |
206 | | - # Bottom decorator's axis is first in callspec.params, which matches the |
207 | | - # innermost (leaf) frame in the parametrize path. |
208 | | - axis, value = next(iter(callspec.params.items())) |
209 | | - name = f"{axis}={value!r}" |
210 | | - else: |
211 | | - name = str(item.name) |
| 211 | + path = item.stash.get(_PARAMETRIZE_PATH_KEY, ()) |
| 212 | + name = path[-1] if path else str(item.name) |
212 | 213 | existing_docstring = item.obj.__doc__ or None |
213 | 214 | with report_context.new_step( |
214 | 215 | name=name, description=existing_docstring, assertion_as_fail_not_error=False |
@@ -243,19 +244,20 @@ def _parametrize_parents( |
243 | 244 | return |
244 | 245 | desired = request.node.stash.get(_PARAMETRIZE_PATH_KEY, ()) |
245 | 246 | parents = desired[:-1] |
246 | | - open_keys = [getattr(ns, "_parametrize_key", None) for ns in _PARAMETRIZE_STACK] |
247 | 247 | common = 0 |
248 | 248 | while ( |
249 | | - common < len(open_keys) and common < len(parents) and open_keys[common] == parents[common] |
| 249 | + common < len(_PARAMETRIZE_STACK) |
| 250 | + and common < len(parents) |
| 251 | + and _PARAMETRIZE_STACK[common][0] == parents[common] |
250 | 252 | ): |
251 | 253 | common += 1 |
252 | 254 | while len(_PARAMETRIZE_STACK) > common: |
253 | | - _PARAMETRIZE_STACK.pop().__exit__(None, None, None) |
| 255 | + _, ns = _PARAMETRIZE_STACK.pop() |
| 256 | + ns.__exit__(None, None, None) |
254 | 257 | for display in parents[common:]: |
255 | 258 | ns = report_context.new_step(name=display, assertion_as_fail_not_error=False) |
256 | 259 | ns.__enter__() |
257 | | - ns._parametrize_key = display # type: ignore[attr-defined] |
258 | | - _PARAMETRIZE_STACK.append(ns) |
| 260 | + _PARAMETRIZE_STACK.append((display, ns)) |
259 | 261 | yield |
260 | 262 |
|
261 | 263 |
|
@@ -301,11 +303,9 @@ def module_substep( |
301 | 303 | name=name, description=existing_docstring, assertion_as_fail_not_error=False |
302 | 304 | ) as new_step: |
303 | 305 | yield new_step |
304 | | - # Drain parametrize parents nested under this module before the module |
305 | | - # step exits — otherwise ReportContext.exit_step asserts that the module |
306 | | - # step is the stack top. |
307 | | - while _PARAMETRIZE_STACK: |
308 | | - _PARAMETRIZE_STACK.pop().__exit__(None, None, None) |
| 306 | + # Drain parametrize parents nested under this module step before it |
| 307 | + # exits — ReportContext.exit_step asserts the module step is the top. |
| 308 | + _drain_parametrize_stack() |
309 | 309 |
|
310 | 310 |
|
311 | 311 | @pytest.fixture(scope="session") |
|
0 commit comments