|
1 | | -"""Importing moabb must not touch the caller's global state. |
| 1 | +"""Importing moabb must not restyle the caller's matplotlib. |
2 | 2 |
|
3 | | -``moabb.analysis.plotting`` applies a seaborn theme to matplotlib's global |
4 | | -rcParams at import time. That is fine for someone who asked for plotting, but |
5 | | -it used to reach anyone who merely imported a dataset: ``moabb.datasets`` |
6 | | -imports ``moabb.analysis.results`` for ``get_digest``, which ran |
7 | | -``moabb/analysis/__init__.py``, which imported ``plotting`` eagerly. |
| 3 | +``moabb.analysis.plotting`` themes the global rcParams at import time. That is |
| 4 | +fine for someone who asked for plotting, but it used to reach anyone who merely |
| 5 | +imported a dataset, via ``moabb.datasets.bids_interface`` -> ``analysis.results``. |
8 | 6 | """ |
9 | 7 |
|
| 8 | +import json |
10 | 9 | import subprocess |
11 | 10 | import sys |
12 | 11 |
|
| 12 | +import pytest |
13 | 13 |
|
14 | | -# Read at figure/axes creation, so a caller cannot undo them after the fact. |
15 | | -_WATCHED = ("font.family", "axes.grid", "axes.spines.left", "axes.spines.top") |
| 14 | + |
| 15 | +# Read at axes creation, so a caller cannot undo them afterwards. |
| 16 | +_WATCHED = ("font.family", "axes.grid", "axes.spines.left") |
16 | 17 |
|
17 | 18 | _PROBE = """ |
18 | | -import json, sys |
19 | | -import matplotlib |
| 19 | +import json, sys, matplotlib |
20 | 20 | matplotlib.use("Agg") |
21 | 21 | import matplotlib.pyplot as plt |
22 | | -
|
23 | | -watched = {watched!r} |
24 | | -before = {{k: repr(plt.rcParams[k]) for k in watched}} |
| 22 | +before = {{k: repr(plt.rcParams[k]) for k in {watched!r}}} |
25 | 23 | import {module} # noqa: F401 |
26 | | -after = {{k: repr(plt.rcParams[k]) for k in watched}} |
27 | | -json.dump({{"before": before, "after": after}}, sys.stdout) |
| 24 | +after = {{k: repr(plt.rcParams[k]) for k in {watched!r}}} |
| 25 | +json.dump([before, after], sys.stdout) |
28 | 26 | """ |
29 | 27 |
|
30 | 28 |
|
31 | | -def _rcparams_around_import(module): |
32 | | - """Import ``module`` in a fresh interpreter, reporting rcParams either side.""" |
| 29 | +def _around_import(module): |
| 30 | + """rcParams before and after importing ``module`` in a fresh interpreter.""" |
33 | 31 | out = subprocess.run( |
34 | 32 | [sys.executable, "-c", _PROBE.format(module=module, watched=_WATCHED)], |
35 | 33 | capture_output=True, |
36 | 34 | text=True, |
37 | 35 | check=True, |
38 | 36 | ) |
39 | | - import json |
40 | | - |
41 | 37 | return json.loads(out.stdout) |
42 | 38 |
|
43 | 39 |
|
44 | 40 | def test_importing_moabb_does_not_restyle_matplotlib(): |
45 | | - for module in ("moabb", "moabb.datasets", "moabb.analysis", "moabb.paradigms"): |
46 | | - seen = _rcparams_around_import(module) |
47 | | - assert seen["before"] == seen["after"], ( |
48 | | - f"import {module} mutated global rcParams: " |
49 | | - f"{seen['before']} -> {seen['after']}" |
50 | | - ) |
51 | | - |
| 41 | + for module in ("moabb", "moabb.datasets", "moabb.analysis"): |
| 42 | + before, after = _around_import(module) |
| 43 | + assert before == after, f"import {module} mutated rcParams: {before} -> {after}" |
52 | 44 |
|
53 | | -def test_plotting_still_applies_the_theme_when_asked_for(): |
54 | | - """The theme must not be lost -- only stopped from leaking.""" |
55 | | - seen = _rcparams_around_import("moabb.analysis.plotting") |
56 | | - assert seen["before"] != seen["after"] |
| 45 | + # The theme must be kept, not lost -- only stopped from leaking. |
| 46 | + before, after = _around_import("moabb.analysis.plotting") |
| 47 | + assert before != after |
57 | 48 |
|
58 | 49 |
|
59 | 50 | def test_lazily_exported_names_still_resolve(): |
60 | 51 | import moabb |
61 | 52 | import moabb.analysis |
62 | 53 |
|
63 | | - assert callable(moabb.benchmark) |
64 | | - assert "benchmark" in dir(moabb) |
| 54 | + assert callable(moabb.benchmark) and "benchmark" in dir(moabb) |
65 | 55 | for name in ("codecarbon_plot", "distribution_plot", "emissions_summary"): |
66 | | - assert callable(getattr(moabb.analysis, name)) |
67 | | - assert name in dir(moabb.analysis) |
68 | | - |
69 | | - |
70 | | -def test_unknown_attribute_still_raises_attributeerror(): |
71 | | - import moabb |
| 56 | + assert callable(getattr(moabb.analysis, name)) and name in dir(moabb.analysis) |
72 | 57 |
|
73 | | - try: |
74 | | - moabb.definitely_not_a_real_attribute |
75 | | - except AttributeError as exc: |
76 | | - assert "definitely_not_a_real_attribute" in str(exc) |
77 | | - else: # pragma: no cover |
78 | | - raise AssertionError("expected AttributeError") |
| 58 | + with pytest.raises(AttributeError, match="definitely_not_real"): |
| 59 | + _ = moabb.definitely_not_real |
0 commit comments