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
27
20
# Minimal smoke-testing of the backends for which the dependencies are
@@ -95,8 +88,8 @@ def _test_interactive_impl():
95
88
"webagg.open_in_browser" : False ,
96
89
"webagg.port_retries" : 1 ,
97
90
})
98
- if len ( sys . argv ) >= 2 : # Second argument is json-encoded rcParams.
99
- rcParams .update (json .loads (sys .argv [1 ]))
91
+
92
+ rcParams .update (json .loads (sys .argv [1 ]))
100
93
backend = plt .rcParams ["backend" ].lower ()
101
94
assert_equal = TestCase ().assertEqual
102
95
assert_raises = TestCase ().assertRaises
@@ -171,36 +164,23 @@ def test_interactive_backend(env, toolbar):
171
164
if env ["MPLBACKEND" ] == "macosx" :
172
165
if toolbar == "toolmanager" :
173
166
pytest .skip ("toolmanager is not implemented for macosx." )
167
+ proc = _run_helper (_test_interactive_impl ,
168
+ json .dumps ({"toolbar" : toolbar }),
169
+ timeout = _test_timeout ,
170
+ ** env )
174
171
175
- proc = subprocess .run (
176
- [sys .executable , "-c" ,
177
- inspect .getsource (_test_interactive_impl )
178
- + "\n _test_interactive_impl()" ,
179
- json .dumps ({"toolbar" : toolbar })],
180
- env = {** os .environ , "SOURCE_DATE_EPOCH" : "0" , ** env },
181
- timeout = _test_timeout ,
182
- stdout = subprocess .PIPE , universal_newlines = True )
183
- if proc .returncode :
184
- pytest .fail ("The subprocess returned with non-zero exit status "
185
- f"{ proc .returncode } ." )
186
172
assert proc .stdout .count ("CloseEvent" ) == 1
187
173
188
174
189
- # The source of this function gets extracted and run in another process, so it
190
- # must be fully self-contained.
191
175
def _test_thread_impl ():
192
176
from concurrent .futures import ThreadPoolExecutor
193
- import json
194
- import sys
195
177
196
178
from matplotlib import pyplot as plt , rcParams
197
179
198
180
rcParams .update ({
199
181
"webagg.open_in_browser" : False ,
200
182
"webagg.port_retries" : 1 ,
201
183
})
202
- if len (sys .argv ) >= 2 : # Second argument is json-encoded rcParams.
203
- rcParams .update (json .loads (sys .argv [1 ]))
204
184
205
185
# Test artist creation and drawing does not crash from thread
206
186
# No other guarantees!
@@ -254,40 +234,65 @@ def _test_thread_impl():
254
234
@pytest .mark .parametrize ("env" , _thread_safe_backends )
255
235
@pytest .mark .flaky (reruns = 3 )
256
236
def test_interactive_thread_safety (env ):
257
- proc = subprocess .run (
258
- [sys .executable , "-c" ,
259
- inspect .getsource (_test_thread_impl ) + "\n _test_thread_impl()" ],
260
- env = {** os .environ , "SOURCE_DATE_EPOCH" : "0" , ** env },
261
- timeout = _test_timeout , check = True ,
262
- stdout = subprocess .PIPE , universal_newlines = True )
237
+ proc = _run_helper (_test_thread_impl ,
238
+ timeout = _test_timeout , ** env )
263
239
assert proc .stdout .count ("CloseEvent" ) == 1
264
240
265
241
242
+ def _impl_test_lazy_auto_backend_selection ():
243
+ import matplotlib
244
+ import matplotlib .pyplot as plt
245
+ # just importing pyplot should not be enough to trigger resolution
246
+ bk = dict .__getitem__ (matplotlib .rcParams , 'backend' )
247
+ assert not isinstance (bk , str )
248
+ assert plt ._backend_mod is None
249
+ # but actually plotting should
250
+ plt .plot (5 )
251
+ assert plt ._backend_mod is not None
252
+ bk = dict .__getitem__ (matplotlib .rcParams , 'backend' )
253
+ assert isinstance (bk , str )
254
+
255
+
266
256
def test_lazy_auto_backend_selection ():
257
+ _run_helper (_impl_test_lazy_auto_backend_selection ,
258
+ timeout = _test_timeout )
267
259
268
- @_run_function_in_subprocess
269
- def _impl ():
270
- import matplotlib
271
- import matplotlib .pyplot as plt
272
- # just importing pyplot should not be enough to trigger resolution
273
- bk = dict .__getitem__ (matplotlib .rcParams , 'backend' )
274
- assert not isinstance (bk , str )
275
- assert plt ._backend_mod is None
276
- # but actually plotting should
277
- plt .plot (5 )
278
- assert plt ._backend_mod is not None
279
- bk = dict .__getitem__ (matplotlib .rcParams , 'backend' )
280
- assert isinstance (bk , str )
281
-
282
- proc = subprocess .run (
283
- [sys .executable , "-c" , _impl ],
284
- env = {** os .environ , "SOURCE_DATE_EPOCH" : "0" },
285
- timeout = _test_timeout , check = True ,
286
- stdout = subprocess .PIPE , universal_newlines = True )
287
260
261
+ def _implqt5agg ():
262
+ import matplotlib .backends .backend_qt5agg # noqa
263
+ import sys
264
+
265
+ assert 'PyQt6' not in sys .modules
266
+ assert 'pyside6' not in sys .modules
267
+ assert 'PyQt5' in sys .modules or 'pyside2' in sys .modules
268
+
269
+ import matplotlib .backends .backend_qt5
270
+ matplotlib .backends .backend_qt5 .qApp
271
+
272
+
273
+ def _implcairo ():
274
+ import matplotlib .backends .backend_qt5cairo # noqa
275
+ import sys
276
+
277
+ assert 'PyQt6' not in sys .modules
278
+ assert 'pyside6' not in sys .modules
279
+ assert 'PyQt5' in sys .modules or 'pyside2' in sys .modules
280
+
281
+ import matplotlib .backends .backend_qt5
282
+ matplotlib .backends .backend_qt5 .qApp
283
+
284
+
285
+ def _implcore ():
286
+ import matplotlib .backends .backend_qt5
287
+ import sys
288
+
289
+ assert 'PyQt6' not in sys .modules
290
+ assert 'pyside6' not in sys .modules
291
+ assert 'PyQt5' in sys .modules or 'pyside2' in sys .modules
292
+ matplotlib .backends .backend_qt5 .qApp
288
293
289
- def test_qt5backends_uses_qt5 ():
290
294
295
+ def test_qt5backends_uses_qt5 ():
291
296
qt5_bindings = [
292
297
dep for dep in ['PyQt5' , 'pyside2' ]
293
298
if importlib .util .find_spec (dep ) is not None
@@ -298,51 +303,10 @@ def test_qt5backends_uses_qt5():
298
303
]
299
304
if len (qt5_bindings ) == 0 or len (qt6_bindings ) == 0 :
300
305
pytest .skip ('need both QT6 and QT5 bindings' )
301
-
302
- @_run_function_in_subprocess
303
- def _implagg ():
304
- import matplotlib .backends .backend_qt5agg # noqa
305
- import sys
306
-
307
- assert 'PyQt6' not in sys .modules
308
- assert 'pyside6' not in sys .modules
309
- assert 'PyQt5' in sys .modules or 'pyside2' in sys .modules
310
-
311
- @_run_function_in_subprocess
312
- def _implcairo ():
313
- import matplotlib .backends .backend_qt5cairo # noqa
314
- import sys
315
-
316
- assert 'PyQt6' not in sys .modules
317
- assert 'pyside6' not in sys .modules
318
- assert 'PyQt5' in sys .modules or 'pyside2' in sys .modules
319
-
320
- @_run_function_in_subprocess
321
- def _implcore ():
322
- import matplotlib .backends .backend_qt5 # noqa
323
- import sys
324
-
325
- assert 'PyQt6' not in sys .modules
326
- assert 'pyside6' not in sys .modules
327
- assert 'PyQt5' in sys .modules or 'pyside2' in sys .modules
328
-
329
- subprocess .run (
330
- [sys .executable , "-c" , _implagg ],
331
- env = {** os .environ , "SOURCE_DATE_EPOCH" : "0" },
332
- timeout = _test_timeout , check = True ,
333
- stdout = subprocess .PIPE , universal_newlines = True )
334
-
335
- subprocess .run (
336
- [sys .executable , "-c" , _implcairo ],
337
- env = {** os .environ , "SOURCE_DATE_EPOCH" : "0" },
338
- timeout = _test_timeout , check = True ,
339
- stdout = subprocess .PIPE , universal_newlines = True )
340
-
341
- subprocess .run (
342
- [sys .executable , "-c" , _implcore ],
343
- env = {** os .environ , "SOURCE_DATE_EPOCH" : "0" },
344
- timeout = _test_timeout , check = True ,
345
- stdout = subprocess .PIPE , universal_newlines = True )
306
+ _run_helper (_implqt5agg , timeout = _test_timeout )
307
+ if importlib .util .find_spec ('pycairo' ) is not None :
308
+ _run_helper (_implcairo , timeout = _test_timeout )
309
+ _run_helper (_implcore , timeout = _test_timeout )
346
310
347
311
348
312
def _impl_test_cross_Qt_imports ():
@@ -378,11 +342,11 @@ def test_cross_Qt_imports():
378
342
for qt6 in qt6_bindings :
379
343
for pair in ([qt5 , qt6 ], [qt6 , qt5 ]):
380
344
try :
381
- _run_helper (__name__ , _impl_test_cross_Qt_imports ,
345
+ _run_helper (_impl_test_cross_Qt_imports ,
382
346
* pair ,
383
347
timeout = _test_timeout )
384
348
except subprocess .CalledProcessError as ex :
385
- # if segfauldt , carry on. We do try to warn the user they
349
+ # if segfault , carry on. We do try to warn the user they
386
350
# are doing something that we do not expect to work
387
351
if ex .returncode == - 11 :
388
352
continue
@@ -397,7 +361,7 @@ def test_webagg():
397
361
proc = subprocess .Popen (
398
362
[sys .executable , "-c" ,
399
363
inspect .getsource (_test_interactive_impl )
400
- + "\n _test_interactive_impl()" ],
364
+ + "\n _test_interactive_impl()" , "{}" ],
401
365
env = {** os .environ , "MPLBACKEND" : "webagg" , "SOURCE_DATE_EPOCH" : "0" })
402
366
url = "http://{}:{}" .format (
403
367
mpl .rcParams ["webagg.address" ], mpl .rcParams ["webagg.port" ])
@@ -419,37 +383,33 @@ def test_webagg():
419
383
assert proc .wait (timeout = _test_timeout ) == 0
420
384
421
385
386
+ def _lazy_headless ():
387
+ import os
388
+ import sys
389
+
390
+ # make it look headless
391
+ os .environ .pop ('DISPLAY' , None )
392
+ os .environ .pop ('WAYLAND_DISPLAY' , None )
393
+
394
+ # we should fast-track to Agg
395
+ import matplotlib .pyplot as plt
396
+ plt .get_backend () == 'agg'
397
+ assert 'PyQt5' not in sys .modules
398
+
399
+ # make sure we really have pyqt installed
400
+ import PyQt5 # noqa
401
+ assert 'PyQt5' in sys .modules
402
+
403
+ # try to switch and make sure we fail with ImportError
404
+ try :
405
+ plt .switch_backend ('qt5agg' )
406
+ except ImportError :
407
+ ...
408
+ else :
409
+ sys .exit (1 )
410
+
411
+
422
412
@pytest .mark .skipif (sys .platform != "linux" , reason = "this a linux-only test" )
423
413
@pytest .mark .backend ('QtAgg' , skip_on_importerror = True )
424
414
def test_lazy_linux_headless ():
425
- test_script = """
426
- import os
427
- import sys
428
-
429
- # make it look headless
430
- os.environ.pop('DISPLAY', None)
431
- os.environ.pop('WAYLAND_DISPLAY', None)
432
-
433
- # we should fast-track to Agg
434
- import matplotlib.pyplot as plt
435
- plt.get_backend() == 'agg'
436
- assert 'PyQt5' not in sys.modules
437
-
438
- # make sure we really have pyqt installed
439
- import PyQt5
440
- assert 'PyQt5' in sys.modules
441
-
442
- # try to switch and make sure we fail with ImportError
443
- try:
444
- plt.switch_backend('qt5agg')
445
- except ImportError:
446
- ...
447
- else:
448
- sys.exit(1)
449
-
450
- """
451
- proc = subprocess .run ([sys .executable , "-c" , test_script ],
452
- env = {** os .environ , "MPLBACKEND" : "" })
453
- if proc .returncode :
454
- pytest .fail ("The subprocess returned with non-zero exit status "
455
- f"{ proc .returncode } ." )
415
+ proc = _run_helper (_lazy_headless , timeout = _test_timeout , MPLBACKEND = "" )
0 commit comments