|
8 | 8 | snippets and optional-dependency guards like matplotlib). |
9 | 9 | """ |
10 | 10 |
|
| 11 | +import os |
11 | 12 | import re |
12 | 13 | import textwrap |
13 | 14 | from pathlib import Path |
@@ -375,11 +376,20 @@ def _restore_datasets_module(): |
375 | 376 | [pytest.param(tid, c, s, id=tid) for tid, c, s in _CASES], |
376 | 377 | ) |
377 | 378 | def test_doc_snippet(test_id: str, code: str, skip_reason: Optional[str]): |
378 | | - """Execute a documentation code snippet and assert no API/runtime errors.""" |
| 379 | + """Execute a documentation code snippet and assert no API/runtime errors. |
| 380 | +
|
| 381 | + ``os.environ`` is snapshot/restored around the exec: snippets may |
| 382 | + legitimately mutate the environment (e.g. the troubleshooting |
| 383 | + backend-override block sets ``DIFF_DIFF_BACKEND='python'``), and an |
| 384 | + unreverted mutation leaks process state into every later test in the |
| 385 | + session (it flipped the backend-arm selection of the dCDH pinned |
| 386 | + bootstrap baseline under full-suite order). |
| 387 | + """ |
379 | 388 | if skip_reason: |
380 | 389 | pytest.skip(skip_reason) |
381 | 390 |
|
382 | 391 | ns = _build_namespace() |
| 392 | + env_snapshot = os.environ.copy() |
383 | 393 | try: |
384 | 394 | exec(compile(code, f"<{test_id}>", "exec"), ns) |
385 | 395 | except NameError as exc: |
@@ -411,3 +421,8 @@ def test_doc_snippet(test_id: str, code: str, skip_reason: Optional[str]): |
411 | 421 | f"Snippet {test_id} raised {type(exc).__name__}: {exc}\n\n" |
412 | 422 | f"Code:\n{textwrap.indent(code, ' ')}" |
413 | 423 | ) |
| 424 | + finally: |
| 425 | + # Revert any environment mutation the snippet made (pytest.fail |
| 426 | + # raises, so this must be a finally, not a trailing statement). |
| 427 | + os.environ.clear() |
| 428 | + os.environ.update(env_snapshot) |
0 commit comments