-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathtest_config.py
More file actions
296 lines (244 loc) · 8.68 KB
/
test_config.py
File metadata and controls
296 lines (244 loc) · 8.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
import importlib
import os
import pathlib
import subprocess
import sys
import threading
from queue import Queue
import pytest
import ultraplot as uplt
def test_wrong_keyword_reset():
"""
The context should reset after a failed attempt.
"""
# Init context
uplt.rc.context()
config = uplt.rc
# Set a wrong key
with pytest.raises(KeyError):
config._get_item_dicts("non_existing_key", "non_existing_value")
# Set a known good value
config._get_item_dicts("coastcolor", "black")
# Confirm we can still plot
fig, ax = uplt.subplots(proj="cyl")
ax.format(coastcolor="black")
fig.canvas.draw()
def test_cycle_in_rc_file(tmp_path):
"""
Test that loading an rc file correctly overwrites the cycle setting.
"""
rc_content = "cycle: colorblind"
rc_file = tmp_path / "test.rc"
rc_file.write_text(rc_content)
# Load the file directly. This should overwrite any existing settings.
uplt.rc.load(str(rc_file))
assert uplt.rc["cycle"] == "colorblind"
def test_sankey_rc_defaults():
"""
Sanity check the new sankey defaults in rc.
"""
assert uplt.rc["sankey.nodepad"] == 0.02
assert uplt.rc["sankey.nodewidth"] == 0.03
assert uplt.rc["sankey.margin"] == 0.05
assert uplt.rc["sankey.flow.alpha"] == 0.75
assert uplt.rc["sankey.flow.curvature"] == 0.5
assert uplt.rc["sankey.node.facecolor"] == "0.75"
def test_ribbon_rc_defaults():
"""
Sanity check ribbon defaults in rc.
"""
assert uplt.rc["ribbon.xmargin"] == 0.12
assert uplt.rc["ribbon.ymargin"] == 0.08
assert uplt.rc["ribbon.rowheightratio"] == 2.2
assert uplt.rc["ribbon.nodewidth"] == 0.018
assert uplt.rc["ribbon.flow.curvature"] == 0.45
assert uplt.rc["ribbon.flow.alpha"] == 0.58
assert uplt.rc["ribbon.topic_labels"] is True
assert uplt.rc["ribbon.topic_label_offset"] == 0.028
assert uplt.rc["ribbon.topic_label_size"] == 7.4
assert uplt.rc["ribbon.topic_label_box"] is True
import io
from importlib.metadata import PackageNotFoundError
from unittest.mock import MagicMock, patch
from ultraplot.utils import check_for_update
@patch("builtins.print")
@patch("importlib.metadata.version")
def test_package_not_installed(mock_version, mock_print):
mock_version.side_effect = PackageNotFoundError
check_for_update("fakepkg")
mock_print.assert_not_called()
@patch("builtins.print")
@patch("importlib.metadata.version", return_value="1.0.0")
@patch("urllib.request.urlopen")
def test_network_failure(mock_urlopen, mock_version, mock_print):
mock_urlopen.side_effect = Exception("Network down")
check_for_update("fakepkg")
mock_print.assert_not_called()
@patch("builtins.print")
@patch("importlib.metadata.version", return_value="1.0.0")
@patch("urllib.request.urlopen")
def test_no_update_available(mock_urlopen, mock_version, mock_print):
mock_resp = MagicMock()
mock_resp.__enter__.return_value = io.StringIO('{"info": {"version": "1.0.0"}}')
mock_urlopen.return_value = mock_resp
check_for_update("fakepkg")
mock_print.assert_not_called()
@patch("builtins.print")
@patch("importlib.metadata.version", return_value="1.0.0")
@patch("urllib.request.urlopen")
def test_update_available(mock_urlopen, mock_version, mock_print):
mock_resp = MagicMock()
mock_resp.__enter__.return_value = io.StringIO('{"info": {"version": "1.2.0"}}')
mock_urlopen.return_value = mock_resp
check_for_update("fakepkg")
mock_print.assert_called_once()
msg = mock_print.call_args[0][0]
assert "A newer version of fakepkg is available" in msg
assert "1.0.0 → 1.2.0" in msg
@patch("builtins.print")
@patch("importlib.metadata.version", return_value="1.0.0dev")
@patch("urllib.request.urlopen")
def test_dev_version_skipped(mock_urlopen, mock_version, mock_print):
mock_resp = MagicMock()
mock_resp.__enter__.return_value = io.StringIO('{"info": {"version": "2.0.0"}}')
mock_urlopen.return_value = mock_resp
check_for_update("fakepkg")
mock_print.assert_not_called()
@pytest.mark.parametrize(
"cycle, raises_error",
[
("qual1", False),
(["#5790fc", "#f89c20", "#e42536", "#964a8b", "#9c9ca1", "#7a21dd"], False),
(
uplt.constructor.Cycle(
["#5790fc", "#f89c20", "#e42536", "#964a8b", "#9c9ca1", "#7a21dd"]
),
False,
),
(uplt.colormaps.get_cmap("viridis"), False),
(1234, True),
],
)
def test_cycle_rc_setting(cycle, raises_error):
"""
Test various ways to set the cycle in rc
"""
if raises_error:
with pytest.raises(ValueError):
uplt.rc["cycle"] = cycle
else:
uplt.rc["cycle"] = cycle
def test_cycle_consistent_across_threads():
"""
Sanity check: concurrent reads of the prop cycle should be consistent.
"""
import matplotlib as mpl
expected = repr(mpl.rcParams["axes.prop_cycle"])
q = Queue()
start = threading.Barrier(4)
def _read_cycle():
start.wait()
q.put(repr(mpl.rcParams["axes.prop_cycle"]))
threads = [threading.Thread(target=_read_cycle) for _ in range(4)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
results = [q.get() for _ in threads]
assert all(result == expected for result in results)
def test_cycle_mutation_does_not_corrupt_rcparams():
"""
Stress test: concurrent cycle mutations should not corrupt rcParams.
"""
import matplotlib as mpl
import matplotlib.pyplot as plt
cycle_a = "colorblind"
cycle_b = "default"
plt.switch_backend("Agg")
uplt.rc["cycle"] = cycle_a
expected_a = repr(mpl.rcParams["axes.prop_cycle"])
uplt.rc["cycle"] = cycle_b
expected_b = repr(mpl.rcParams["axes.prop_cycle"])
allowed = {expected_a, expected_b}
start = threading.Barrier(2)
done = threading.Event()
results = Queue()
def _writer():
start.wait()
for _ in range(200):
uplt.rc["cycle"] = cycle_a
uplt.rc["cycle"] = cycle_b
done.set()
def _reader():
start.wait()
while not done.is_set():
results.put(repr(mpl.rcParams["axes.prop_cycle"]))
fig, ax = uplt.subplots()
ax.plot([0, 1], [0, 1])
fig.canvas.draw()
uplt.close(fig)
threads = [
threading.Thread(target=_writer),
threading.Thread(target=_reader),
]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
observed = [results.get() for _ in range(results.qsize())]
assert observed, "No rcParams observations were recorded."
assert all(value in allowed for value in observed)
def _run_in_subprocess(code):
code = (
"import pathlib\n"
"import sys\n"
"sys.path.insert(0, str(pathlib.Path.cwd()))\n" + code
)
env = os.environ.copy()
env["MPLBACKEND"] = "Agg"
return subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True,
cwd=str(pathlib.Path(__file__).resolve().parents[2]),
env=env,
)
def test_matplotlib_import_before_ultraplot_allows_rc_mutation():
"""
Import order regression test for issue #568.
"""
result = _run_in_subprocess(
"import matplotlib.pyplot as plt\n"
"import ultraplot as uplt\n"
"uplt.rc['figure.facecolor'] = 'white'\n"
)
assert result.returncode == 0, result.stderr
def test_matplotlib_import_before_ultraplot_allows_custom_fontsize_tokens():
"""
Ensure patched fontsize validators are active regardless of import order.
"""
result = _run_in_subprocess(
"import matplotlib.pyplot as plt\n"
"import ultraplot as uplt\n"
"for key in ('axes.titlesize', 'figure.titlesize', 'legend.fontsize', 'xtick.labelsize'):\n"
" uplt.rc[key] = 'med-large'\n"
)
assert result.returncode == 0, result.stderr
def test_ultraplot_subplots_does_not_force_global_image_interpolation():
"""
Regression test for issue #588: creating UltraPlot figures should not force
global raster interpolation defaults used by third-party OffsetImage artists.
"""
result = _run_in_subprocess(
"import matplotlib as mpl\n"
"from matplotlib.offsetbox import OffsetImage\n"
"mpl.rcParams['image.interpolation'] = 'auto'\n"
"import ultraplot as uplt\n"
"fig, ax = uplt.subplots()\n"
"fig.canvas.draw()\n"
"uplt.close(fig)\n"
"assert mpl.rcParams['image.interpolation'] == 'auto'\n"
"img = OffsetImage([[[0, 0, 0, 0]]]).get_children()[0]\n"
"assert img.get_interpolation() == 'auto'\n"
)
assert result.returncode == 0, result.stderr