7
7
import signal
8
8
import subprocess
9
9
import sys
10
- import textwrap
11
10
import time
12
11
import urllib .request
13
12
14
13
import pytest
15
14
16
15
import matplotlib as mpl
17
16
from matplotlib import _c_internal_utils
18
-
19
-
20
- def _run_function_in_subprocess (func ):
21
- func_source = textwrap .dedent (inspect .getsource (func ))
22
- func_source = func_source [func_source .index ('\n ' )+ 1 :] # Remove decorator
23
- return f"{ func_source } \n { func .__name__ } ()"
24
-
17
+ from matplotlib .testing import subprocess_run_helper as _run_helper
25
18
26
19
# Minimal smoke-testing of the backends for which the dependencies are
27
20
# PyPI-installable on CI. They are not available for all tested Python
@@ -94,8 +87,8 @@ def _test_interactive_impl():
94
87
"webagg.open_in_browser" : False ,
95
88
"webagg.port_retries" : 1 ,
96
89
})
97
- if len ( sys . argv ) >= 2 : # Second argument is json-encoded rcParams.
98
- rcParams .update (json .loads (sys .argv [1 ]))
90
+
91
+ rcParams .update (json .loads (sys .argv [1 ]))
99
92
backend = plt .rcParams ["backend" ].lower ()
100
93
assert_equal = TestCase ().assertEqual
101
94
assert_raises = TestCase ().assertRaises
@@ -170,36 +163,23 @@ def test_interactive_backend(env, toolbar):
170
163
if env ["MPLBACKEND" ] == "macosx" :
171
164
if toolbar == "toolmanager" :
172
165
pytest .skip ("toolmanager is not implemented for macosx." )
166
+ proc = _run_helper (__name__ , _test_interactive_impl ,
167
+ json .dumps ({"toolbar" : toolbar }),
168
+ timeout = _test_timeout ,
169
+ ** env )
173
170
174
- proc = subprocess .run (
175
- [sys .executable , "-c" ,
176
- inspect .getsource (_test_interactive_impl )
177
- + "\n _test_interactive_impl()" ,
178
- json .dumps ({"toolbar" : toolbar })],
179
- env = {** os .environ , "SOURCE_DATE_EPOCH" : "0" , ** env },
180
- timeout = _test_timeout ,
181
- stdout = subprocess .PIPE , universal_newlines = True )
182
- if proc .returncode :
183
- pytest .fail ("The subprocess returned with non-zero exit status "
184
- f"{ proc .returncode } ." )
185
171
assert proc .stdout .count ("CloseEvent" ) == 1
186
172
187
173
188
- # The source of this function gets extracted and run in another process, so it
189
- # must be fully self-contained.
190
174
def _test_thread_impl ():
191
175
from concurrent .futures import ThreadPoolExecutor
192
- import json
193
- import sys
194
176
195
177
from matplotlib import pyplot as plt , rcParams
196
178
197
179
rcParams .update ({
198
180
"webagg.open_in_browser" : False ,
199
181
"webagg.port_retries" : 1 ,
200
182
})
201
- if len (sys .argv ) >= 2 : # Second argument is json-encoded rcParams.
202
- rcParams .update (json .loads (sys .argv [1 ]))
203
183
204
184
# Test artist creation and drawing does not crash from thread
205
185
# No other guarantees!
@@ -253,40 +233,65 @@ def _test_thread_impl():
253
233
@pytest .mark .parametrize ("env" , _thread_safe_backends )
254
234
@pytest .mark .flaky (reruns = 3 )
255
235
def test_interactive_thread_safety (env ):
256
- proc = subprocess .run (
257
- [sys .executable , "-c" ,
258
- inspect .getsource (_test_thread_impl ) + "\n _test_thread_impl()" ],
259
- env = {** os .environ , "SOURCE_DATE_EPOCH" : "0" , ** env },
260
- timeout = _test_timeout , check = True ,
261
- stdout = subprocess .PIPE , universal_newlines = True )
236
+ proc = _run_helper (__name__ , _test_thread_impl ,
237
+ timeout = _test_timeout , ** env )
262
238
assert proc .stdout .count ("CloseEvent" ) == 1
263
239
264
240
241
+ def _impl_test_lazy_auto_backend_selection ():
242
+ import matplotlib
243
+ import matplotlib .pyplot as plt
244
+ # just importing pyplot should not be enough to trigger resolution
245
+ bk = dict .__getitem__ (matplotlib .rcParams , 'backend' )
246
+ assert not isinstance (bk , str )
247
+ assert plt ._backend_mod is None
248
+ # but actually plotting should
249
+ plt .plot (5 )
250
+ assert plt ._backend_mod is not None
251
+ bk = dict .__getitem__ (matplotlib .rcParams , 'backend' )
252
+ assert isinstance (bk , str )
253
+
254
+
265
255
def test_lazy_auto_backend_selection ():
256
+ _run_helper (__name__ , _impl_test_lazy_auto_backend_selection ,
257
+ timeout = _test_timeout )
266
258
267
-
E377
span> @_run_function_in_subprocess
268
- def _impl ():
269
- import matplotlib
270
- import matplotlib .pyplot as plt
271
- # just importing pyplot should not be enough to trigger resolution
272
- bk = dict .__getitem__ (matplotlib .rcParams , 'backend' )
273
- assert not isinstance (bk , str )
274
- assert plt ._backend_mod is None
275
- # but actually plotting should
276
- plt .plot (5 )
277
- assert plt ._backend_mod is not None
278
- bk = dict .__getitem__ (matplotlib .rcParams , 'backend' )
279
- assert isinstance (bk , str )
280
-
281
- proc = subprocess .run (
282
- [sys .executable , "-c" , _impl ],
283
- env = {** os .environ , "SOURCE_DATE_EPOCH" : "0" },
284
- timeout = _test_timeout , check = True ,
285
- stdout = subprocess .PIPE , universal_newlines = True )
286
259
260
+ def _implqt5agg ():
261
+ import matplotlib .backends .backend_qt5agg # noqa
262
+ import sys
263
+
264
+ assert 'PyQt6' not in sys .modules
265
+ assert 'pyside6' not in sys .modules
266
+ assert 'PyQt5' in sys .modules or 'pyside2' in sys .modules
267
+
268
+ import matplotlib .backends .backend_qt5
269
+ matplotlib .backends .backend_qt5 .qApp
270
+
271
+
272
+ def _implcairo ():
273
+ import matplotlib .backends .backend_qt5cairo # noqa
274
+ import sys
275
+
276
+ assert 'PyQt6' not in sys .modules
277
+ assert 'pyside6' not in sys .modules
278
+ assert 'PyQt5' in sys .modules or 'pyside2' in sys .modules
279
+
280
+ import matplotlib .backends .backend_qt5
281
+ matplotlib .backends .backend_qt5 .qApp
282
+
283
+
284
+ def _implcore ():
285
+ import matplotlib .backends .backend_qt5
286
+ import sys
287
+
288
+ assert 'PyQt6' not in sys .modules
289
+ assert 'pyside6' not in sys .modules
290
+ assert 'PyQt5' in sys .modules or 'pyside2' in sys .modules
291
+ matplotlib .backends .backend_qt5 .qApp
287
292
288
- def test_qt5backends_uses_qt5 ():
289
293
294
+ def test_qt5backends_uses_qt5 ():
290
295
qt5_bindings = [
291
296
dep for dep in ['PyQt5' , 'pyside2' ]
292
297
if importlib .util .find_spec (dep ) is not None
@@ -297,51 +302,9 @@ def test_qt5backends_uses_qt5():
297
302
]
298
303
if len (qt5_bindings ) == 0 or len (qt6_bindings ) == 0 :
299
304
pytest .skip ('need both QT6 and QT5 bindings' )
300
-
301
- @_run_function_in_subprocess
302
- def _implagg ():
303
- import matplotlib .backends .backend_qt5agg # noqa
304
- import sys
305
-
306
- assert 'PyQt6' not in sys .modules
307
- assert 'pyside6' not in sys .modules
308
- assert 'PyQt5' in sys .modules or 'pyside2' in sys .modules
309
-
310
- @_run_function_in_subprocess
311
- def _implcairo ():
312
- import matplotlib .backends .backend_qt5cairo # noqa
313
- import sys
314
-
315
- assert 'PyQt6' not in sys .modules
316
- assert 'pyside6' not in sys .modules
317
- assert 'PyQt5' in sys .modules or 'pyside2' in sys .modules
318
-
319
- @_run_function_in_subprocess
320
- def _implcore ():
321
- import matplotlib .backends .backend_qt5 # noqa
322
- import sys
323
-
324
- assert 'PyQt6' not in sys .modules
325
- assert 'pyside6' not in sys .modules
326
- assert 'PyQt5' in sys .modules or 'pyside2' in sys .modules
327
-
328
- subprocess .run (
329
- [sys .executable , "-c" , _implagg ],
330
- env = {** os .environ , "SOURCE_DATE_EPOCH" : "0" },
331
- timeout = _test_timeout , check = True ,
332
- stdout = subprocess .PIPE , universal_newlines = True )
333
-
334
- subprocess .run (
335
- [sys .executable , "-c" , _implcairo ],
336
- env = {** os .environ , "SOURCE_DATE_EPOCH" : "0" },
337
- timeout = _test_timeout , check = True ,
338
- stdout = subprocess .PIPE , universal_newlines = True )
339
-
340
- subprocess .run (
341
- [sys .executable , "-c" , _implcore ],
342
- env = {** os .environ , "SOURCE_DATE_EPOCH" : "0" },
343
- timeout = _test_timeout , check = True ,
344
- stdout = subprocess .PIPE , universal_newlines = True )
305
+ _run_helper (__name__ , _implqt5agg , timeout = _test_timeout )
306
+ _run_helper (__name__ , _implcairo , timeout = _test_timeout )
307
+ _run_helper (__name__ , _implcore , timeout = _test_timeout )
345
308
346
309
347
310
@pytest .mark .skipif ('TF_BUILD' in os .environ ,
@@ -352,7 +315,7 @@ def test_webagg():
352
315
proc = subprocess .Popen (
353
316
[sys .executable , "-c" ,
354
317
inspect .getsource (_test_interactive_impl )
355
- + "\n _test_interactive_impl()" ],
318
+ + "\n _test_interactive_impl()" , "{}" ],
356
319
env = {** os .environ , "MPLBACKEND" : "webagg" , "SOURCE_DATE_EPOCH" : "0" })
357
320
url = "http://{}:{}" .format (
358
321
mpl .rcParams ["webagg.address" ], mpl .rcParams ["webagg.port" ])
@@ -374,37 +337,35 @@ def test_webagg():
374
337
assert proc .wait (timeout = _test_timeout ) == 0
375
338
376
339
340
+ def _lazy_headless ():
341
+ import os
342
+ import sys
343
+
344
+ # make it look headless
345
+ os .environ .pop ('DISPLAY' , None )
346
+ os .environ .pop ('WAYLAND_DISPLAY' , None )
347
+
348
+ # we should fast-track to Agg
349
+ import matplotlib .pyplot as plt
350
+ plt .get_backend () == 'agg'
351
+ assert 'PyQt5' not in sys .modules
352
+
353
+ # make sure we really have pyqt installed
354
+ import PyQt5 # noqa
355
+ assert 'PyQt5' in sys .modules
356
+
357
+ # try to switch and make sure we fail with ImportError
358
+ try :
359
+ plt .switch_backend ('qt5agg' )
360
+ except ImportError :
361
+ ...
362
+ else :
363
+ sys .exit (1 )
364
+
365
+
377
366
@pytest .mark .skipif (sys .platform != "linux" , reason = "this a linux-only test" )
378
367
@pytest .mark .backend ('QtAgg' , skip_on_importerror = True )
379
368
def test_lazy_linux_headless ():
380
- test_script = """
381
- import os
382
- import sys
383
-
384
- # make it look headless
385
- os.environ.pop('DISPLAY', None)
386
- os.environ.pop('WAYLAND_DISPLAY', None)
387
-
388
- # we should fast-track to Agg
389
- import matplotlib.pyplot as plt
390
- plt.get_backend() == 'agg'
391
- assert 'PyQt5' not in sys.modules
392
-
393
- # make sure we really have pyqt installed
394
- import PyQt5
395
- assert 'PyQt5' in sys.modules
396
-
397
- # try to switch and make sure we fail with ImportError
398
- try:
399
- plt.switch_backend('qt5agg')
400
- except ImportError:
401
- ...
402
- else:
403
- sys.exit(1)
404
-
405
- """
406
- proc = subprocess .run ([sys .executable , "-c" , test_script ],
407
- env = {** os .environ , "MPLBACKEND" : "" })
408
- if proc .returncode :
409
- pytest .fail ("The subprocess returned with non-zero exit status "
410
- f"{ proc .returncode } ." )
369
+ proc = _run_helper (__name__ , _lazy_headless ,
370
+ timeout = _test_timeout ,
371
+ MPLBACKEND = "" )
0 commit comments