##// END OF EJS Templates
Isolate tests so state doesn't leak to other tests
Ian Thomas -
Show More
@@ -1,376 +1,375
1 1 """Tests for pylab tools module.
2 2 """
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 from binascii import a2b_base64
9 9 from io import BytesIO
10 10
11 11 import pytest
12 12
13 13 matplotlib = pytest.importorskip("matplotlib")
14 14 matplotlib.use('Agg')
15 15 from matplotlib.figure import Figure
16 16
17 17 from matplotlib import pyplot as plt
18 18 from matplotlib_inline import backend_inline
19 19 import numpy as np
20 20
21 21 from IPython.core.getipython import get_ipython
22 22 from IPython.core.interactiveshell import InteractiveShell
23 23 from IPython.core.display import _PNG, _JPEG
24 24 from .. import pylabtools as pt
25 25
26 26 from IPython.testing import decorators as dec
27 27
28 28
29 29 def test_figure_to_svg():
30 30 # simple empty-figure test
31 31 fig = plt.figure()
32 32 assert pt.print_figure(fig, "svg") is None
33 33
34 34 plt.close('all')
35 35
36 36 # simple check for at least svg-looking output
37 37 fig = plt.figure()
38 38 ax = fig.add_subplot(1,1,1)
39 39 ax.plot([1,2,3])
40 40 plt.draw()
41 41 svg = pt.print_figure(fig, "svg")[:100].lower()
42 42 assert "doctype svg" in svg
43 43
44 44
45 45 def _check_pil_jpeg_bytes():
46 46 """Skip if PIL can't write JPEGs to BytesIO objects"""
47 47 # PIL's JPEG plugin can't write to BytesIO objects
48 48 # Pillow fixes this
49 49 from PIL import Image
50 50 buf = BytesIO()
51 51 img = Image.new("RGB", (4,4))
52 52 try:
53 53 img.save(buf, 'jpeg')
54 54 except Exception as e:
55 55 ename = e.__class__.__name__
56 56 raise pytest.skip("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e
57 57
58 58 @dec.skip_without("PIL.Image")
59 59 def test_figure_to_jpeg():
60 60 _check_pil_jpeg_bytes()
61 61 # simple check for at least jpeg-looking output
62 62 fig = plt.figure()
63 63 ax = fig.add_subplot(1,1,1)
64 64 ax.plot([1,2,3])
65 65 plt.draw()
66 66 jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower()
67 67 assert jpeg.startswith(_JPEG)
68 68
69 69 def test_retina_figure():
70 70 # simple empty-figure test
71 71 fig = plt.figure()
72 72 assert pt.retina_figure(fig) == None
73 73 plt.close('all')
74 74
75 75 fig = plt.figure()
76 76 ax = fig.add_subplot(1,1,1)
77 77 ax.plot([1,2,3])
78 78 plt.draw()
79 79 png, md = pt.retina_figure(fig)
80 80 assert png.startswith(_PNG)
81 81 assert "width" in md
82 82 assert "height" in md
83 83
84 84
85 85 _fmt_mime_map = {
86 86 'png': 'image/png',
87 87 'jpeg': 'image/jpeg',
88 88 'pdf': 'application/pdf',
89 89 'retina': 'image/png',
90 90 'svg': 'image/svg+xml',
91 91 }
92 92
93 93 def test_select_figure_formats_str():
94 94 ip = get_ipython()
95 95 for fmt, active_mime in _fmt_mime_map.items():
96 96 pt.select_figure_formats(ip, fmt)
97 97 for mime, f in ip.display_formatter.formatters.items():
98 98 if mime == active_mime:
99 99 assert Figure in f
100 100 else:
101 101 assert Figure not in f
102 102
103 103 def test_select_figure_formats_kwargs():
104 104 ip = get_ipython()
105 105 kwargs = dict(bbox_inches="tight")
106 106 pt.select_figure_formats(ip, "png", **kwargs)
107 107 formatter = ip.display_formatter.formatters["image/png"]
108 108 f = formatter.lookup_by_type(Figure)
109 109 cell = f.keywords
110 110 expected = kwargs
111 111 expected["base64"] = True
112 112 expected["fmt"] = "png"
113 113 assert cell == expected
114 114
115 115 # check that the formatter doesn't raise
116 116 fig = plt.figure()
117 117 ax = fig.add_subplot(1,1,1)
118 118 ax.plot([1,2,3])
119 119 plt.draw()
120 120 formatter.enabled = True
121 121 png = formatter(fig)
122 122 assert isinstance(png, str)
123 123 png_bytes = a2b_base64(png)
124 124 assert png_bytes.startswith(_PNG)
125 125
126 126 def test_select_figure_formats_set():
127 127 ip = get_ipython()
128 128 for fmts in [
129 129 {'png', 'svg'},
130 130 ['png'],
131 131 ('jpeg', 'pdf', 'retina'),
132 132 {'svg'},
133 133 ]:
134 134 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
135 135 pt.select_figure_formats(ip, fmts)
136 136 for mime, f in ip.display_formatter.formatters.items():
137 137 if mime in active_mimes:
138 138 assert Figure in f
139 139 else:
140 140 assert Figure not in f
141 141
142 142 def test_select_figure_formats_bad():
143 143 ip = get_ipython()
144 144 with pytest.raises(ValueError):
145 145 pt.select_figure_formats(ip, 'foo')
146 146 with pytest.raises(ValueError):
147 147 pt.select_figure_formats(ip, {'png', 'foo'})
148 148 with pytest.raises(ValueError):
149 149 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
150 150
151 151 def test_import_pylab():
152 152 ns = {}
153 153 pt.import_pylab(ns, import_all=False)
154 154 assert "plt" in ns
155 155 assert ns["np"] == np
156 156
157 157
158 158 class TestPylabSwitch(object):
159 159 class Shell(InteractiveShell):
160 160 def init_history(self):
161 161 """Sets up the command history, and starts regular autosaves."""
162 162 self.config.HistoryManager.hist_file = ":memory:"
163 163 super().init_history()
164 164
165 165 def enable_gui(self, gui):
166 166 pass
167 167
168 168 def setup_method(self):
169 169 import matplotlib
170 170 def act_mpl(backend):
171 171 matplotlib.rcParams['backend'] = backend
172 172
173 173 # Save rcParams since they get modified
174 174 self._saved_rcParams = matplotlib.rcParams
175 175 self._saved_rcParamsOrig = matplotlib.rcParamsOrig
176 176 matplotlib.rcParams = dict(backend="QtAgg")
177 177 matplotlib.rcParamsOrig = dict(backend="QtAgg")
178 178
179 179 # Mock out functions
180 180 self._save_am = pt.activate_matplotlib
181 181 pt.activate_matplotlib = act_mpl
182 182 self._save_ip = pt.import_pylab
183 183 pt.import_pylab = lambda *a,**kw:None
184 184 self._save_cis = backend_inline.configure_inline_support
185 185 backend_inline.configure_inline_support = lambda *a, **kw: None
186 186
187 187 def teardown_method(self):
188 188 pt.activate_matplotlib = self._save_am
189 189 pt.import_pylab = self._save_ip
190 190 backend_inline.configure_inline_support = self._save_cis
191 191 import matplotlib
192 192 matplotlib.rcParams = self._saved_rcParams
193 193 matplotlib.rcParamsOrig = self._saved_rcParamsOrig
194 194
195 195 def test_qt(self):
196 196 s = self.Shell()
197 197 gui, backend = s.enable_matplotlib(None)
198 198 assert gui == "qt"
199 199 assert s.pylab_gui_select == "qt"
200 200
201 201 gui, backend = s.enable_matplotlib("inline")
202 202 assert gui is None
203 203 assert s.pylab_gui_select == "qt"
204 204
205 205 gui, backend = s.enable_matplotlib("qt")
206 206 assert gui == "qt"
207 207 assert s.pylab_gui_select == "qt"
208 208
209 209 gui, backend = s.enable_matplotlib("inline")
210 210 assert gui is None
211 211 assert s.pylab_gui_select == "qt"
212 212
213 213 gui, backend = s.enable_matplotlib()
214 214 assert gui == "qt"
215 215 assert s.pylab_gui_select == "qt"
216 216
217 217 def test_inline(self):
218 218 s = self.Shell()
219 219 gui, backend = s.enable_matplotlib("inline")
220 220 assert gui is None
221 221 assert s.pylab_gui_select == None
222 222
223 223 gui, backend = s.enable_matplotlib("inline")
224 224 assert gui is None
225 225 assert s.pylab_gui_select == None
226 226
227 227 gui, backend = s.enable_matplotlib("qt")
228 228 assert gui == "qt"
229 229 assert s.pylab_gui_select == "qt"
230 230
231 231 def test_inline_twice(self):
232 232 "Using '%matplotlib inline' twice should not reset formatters"
233 233
234 234 ip = self.Shell()
235 235 gui, backend = ip.enable_matplotlib("inline")
236 236 assert gui is None
237 237
238 238 fmts = {'png'}
239 239 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
240 240 pt.select_figure_formats(ip, fmts)
241 241
242 242 gui, backend = ip.enable_matplotlib("inline")
243 243 assert gui is None
244 244
245 245 for mime, f in ip.display_formatter.formatters.items():
246 246 if mime in active_mimes:
247 247 assert Figure in f
248 248 else:
249 249 assert Figure not in f
250 250
251 251 def test_qt_gtk(self):
252 252 s = self.Shell()
253 253 gui, backend = s.enable_matplotlib("qt")
254 254 assert gui == "qt"
255 255 assert s.pylab_gui_select == "qt"
256 256
257 257 gui, backend = s.enable_matplotlib("gtk3")
258 258 assert gui == "qt"
259 259 assert s.pylab_gui_select == "qt"
260 260
261 @dec.skipif(not pt._matplotlib_manages_backends())
262 def test_backend_module_name_case_sensitive(self):
263 # Matplotlib backend names are case insensitive unless explicitly specified using
264 # "module://some_module.some_name" syntax which are case sensitive for mpl >= 3.9.1
265 all_lowercase = "module://matplotlib_inline.backend_inline"
266 some_uppercase = "module://matplotlib_inline.Backend_inline"
267 mpl3_9_1 = matplotlib.__version_info__ >= (3, 9, 1)
268
269 s = self.Shell()
270 s.enable_matplotlib(all_lowercase)
271 if mpl3_9_1:
272 with pytest.raises(RuntimeError):
273 s.enable_matplotlib(some_uppercase)
274 else:
275 s.enable_matplotlib(some_uppercase)
276
277 s.run_line_magic("matplotlib", all_lowercase)
278 if mpl3_9_1:
279 with pytest.raises(RuntimeError):
280 s.run_line_magic("matplotlib", some_uppercase)
281 else:
282 s.run_line_magic("matplotlib", some_uppercase)
283
261 284
262 285 def test_no_gui_backends():
263 286 for k in ['agg', 'svg', 'pdf', 'ps']:
264 287 assert k not in pt.backend2gui
265 288
266 289
267 290 def test_figure_no_canvas():
268 291 fig = Figure()
269 292 fig.canvas = None
270 293 pt.print_figure(fig)
271 294
272 295
273 296 @pytest.mark.parametrize(
274 297 "name, expected_gui, expected_backend",
275 298 [
276 299 # name is gui
277 300 ("gtk3", "gtk3", "gtk3agg"),
278 301 ("gtk4", "gtk4", "gtk4agg"),
279 302 ("headless", None, "agg"),
280 303 ("osx", "osx", "macosx"),
281 304 ("qt", "qt", "qtagg"),
282 305 ("qt5", "qt5", "qt5agg"),
283 306 ("qt6", "qt6", "qtagg"),
284 307 ("tk", "tk", "tkagg"),
285 308 ("wx", "wx", "wxagg"),
286 309 # name is backend
287 310 ("agg", None, "agg"),
288 311 ("cairo", None, "cairo"),
289 312 ("pdf", None, "pdf"),
290 313 ("ps", None, "ps"),
291 314 ("svg", None, "svg"),
292 315 ("template", None, "template"),
293 316 ("gtk3agg", "gtk3", "gtk3agg"),
294 317 ("gtk3cairo", "gtk3", "gtk3cairo"),
295 318 ("gtk4agg", "gtk4", "gtk4agg"),
296 319 ("gtk4cairo", "gtk4", "gtk4cairo"),
297 320 ("macosx", "osx", "macosx"),
298 321 ("nbagg", "nbagg", "nbagg"),
299 322 ("notebook", "nbagg", "notebook"),
300 323 ("qtagg", "qt", "qtagg"),
301 324 ("qtcairo", "qt", "qtcairo"),
302 325 ("qt5agg", "qt5", "qt5agg"),
303 326 ("qt5cairo", "qt5", "qt5cairo"),
304 327 ("tkagg", "tk", "tkagg"),
305 328 ("tkcairo", "tk", "tkcairo"),
306 329 ("webagg", "webagg", "webagg"),
307 330 ("wxagg", "wx", "wxagg"),
308 331 ("wxcairo", "wx", "wxcairo"),
309 332 ],
310 333 )
311 334 def test_backend_builtin(name, expected_gui, expected_backend):
312 335 # Test correct identification of Matplotlib built-in backends without importing and using them,
313 336 # otherwise we would need to ensure all the complex dependencies such as windowing toolkits are
314 337 # installed.
315 338
316 339 mpl_manages_backends = pt._matplotlib_manages_backends()
317 340 if not mpl_manages_backends:
318 341 # Backends not supported before _matplotlib_manages_backends or supported
319 342 # but with different expected_gui or expected_backend.
320 343 if (
321 344 name.endswith("agg")
322 345 or name.endswith("cairo")
323 346 or name in ("headless", "macosx", "pdf", "ps", "svg", "template")
324 347 ):
325 348 pytest.skip()
326 349 elif name == "qt6":
327 350 expected_backend = "qtagg"
328 351 elif name == "notebook":
329 352 expected_backend, expected_gui = expected_gui, expected_backend
330 353
331 354 gui, backend = pt.find_gui_and_backend(name)
332 355 if not mpl_manages_backends:
333 356 gui = gui.lower() if gui else None
334 357 backend = backend.lower() if backend else None
335 358 assert gui == expected_gui
336 359 assert backend == expected_backend
337 360
338 361
339 362 def test_backend_entry_point():
340 363 gui, backend = pt.find_gui_and_backend("inline")
341 364 assert gui is None
342 365 expected_backend = (
343 366 "inline"
344 367 if pt._matplotlib_manages_backends()
345 368 else "module://matplotlib_inline.backend_inline"
346 369 )
347 370 assert backend == expected_backend
348 371
349 372
350 373 def test_backend_unknown():
351 374 with pytest.raises(RuntimeError if pt._matplotlib_manages_backends() else KeyError):
352 375 pt.find_gui_and_backend("name-does-not-exist")
353
354
355 @dec.skipif(not pt._matplotlib_manages_backends())
356 def test_backend_module_name_case_sensitive():
357 # Matplotlib backend names are case insensitive unless explicitly specified using
358 # "module://some_module.some_name" syntax which are case sensitive for mpl >= 3.9.1
359 all_lowercase = "module://matplotlib_inline.backend_inline"
360 some_uppercase = "module://matplotlib_inline.Backend_inline"
361 ip = get_ipython()
362 mpl3_9_1 = matplotlib.__version_info__ >= (3, 9, 1)
363
364 ip.enable_matplotlib(all_lowercase)
365 if mpl3_9_1:
366 with pytest.raises(RuntimeError):
367 ip.enable_matplotlib(some_uppercase)
368 else:
369 ip.enable_matplotlib(some_uppercase)
370
371 ip.run_line_magic("matplotlib", all_lowercase)
372 if mpl3_9_1:
373 with pytest.raises(RuntimeError):
374 ip.run_line_magic("matplotlib", some_uppercase)
375 else:
376 ip.run_line_magic("matplotlib", some_uppercase)
General Comments 0
You need to be logged in to leave comments. Login now