31
31
import io
32
32
import os
33
33
import json
34
+ import uuid
34
35
import shutil
35
36
import hashlib
36
37
import logging
@@ -216,6 +217,12 @@ def pytest_addoption(parser):
216
217
parser .addini (option , help = msg )
217
218
218
219
220
+ class XdistPlugin :
221
+ def pytest_configure_node (self , node ):
222
+ node .workerinput ["pytest_mpl_uid" ] = node .config .pytest_mpl_uid
223
+ node .workerinput ["pytest_mpl_results_dir" ] = node .config .pytest_mpl_results_dir
224
+
225
+
219
226
def pytest_configure (config ):
220
227
221
228
config .addinivalue_line (
@@ -288,12 +295,20 @@ def get_cli_or_ini(name, default=None):
288
295
if not _hash_library_from_cli :
289
296
hash_library = os .path .abspath (hash_library )
290
297
298
+ if not hasattr (config , "workerinput" ):
299
+ uid = uuid .uuid4 ().hex
300
+ results_dir_path = results_dir or tempfile .mkdtemp ()
301
+ config .pytest_mpl_uid = uid
302
+ config .pytest_mpl_results_dir = results_dir_path
303
+
304
+ if config .pluginmanager .hasplugin ("xdist" ):
305
+ config .pluginmanager .register (XdistPlugin (), name = "pytest_mpl_xdist_plugin" )
306
+
291
307
plugin = ImageComparison (
292
308
config ,
293
309
baseline_dir = baseline_dir ,
294
310
baseline_relative_dir = baseline_relative_dir ,
295
311
generate_dir = generate_dir ,
296
- results_dir = results_dir ,
297
312
hash_library = hash_library ,
298
313
generate_hash_library = generate_hash_lib ,
299
314
generate_summary = generate_summary ,
@@ -356,7 +371,6 @@ def __init__(
356
371
baseline_dir = None ,
357
372
baseline_relative_dir = None ,
358
373
generate_dir = None ,
359
- results_dir = None ,
360
374
hash_library = None ,
361
375
generate_hash_library = None ,
362
376
generate_summary = None ,
@@ -372,7 +386,7 @@ def __init__(
372
386
self .baseline_dir = baseline_dir
373
387
self .baseline_relative_dir = path_is_not_none (baseline_relative_dir )
374
388
self .generate_dir = path_is_not_none (generate_dir )
375
- self .results_dir = path_is_not_none ( results_dir )
389
+ self .results_dir = None
376
390
self .hash_library = path_is_not_none (hash_library )
377
391
self ._hash_library_from_cli = _hash_library_from_cli # for backwards compatibility
378
392
self .generate_hash_library = path_is_not_none (generate_hash_library )
@@ -394,11 +408,6 @@ def __init__(
394
408
self .deterministic = deterministic
395
409
self .default_backend = default_backend
396
410
397
- # Generate the containing dir for all test results
398
- if not self .results_dir :
399
- self .results_dir = Path (tempfile .mkdtemp (dir = self .results_dir ))
400
- self .results_dir .mkdir (parents = True , exist_ok = True )
401
-
402
411
# Decide what to call the downloadable results hash library
403
412
if self .hash_library is not None :
404
413
self .results_hash_library_name = self .hash_library .name
@@ -411,6 +420,14 @@ def __init__(
411
420
self ._test_stats = None
412
421
self .return_value = {}
413
422
423
+ def pytest_sessionstart (self , session ):
424
+ config = session .config
425
+ if hasattr (config , "workerinput" ):
426
+ config .pytest_mpl_uid = config .workerinput ["pytest_mpl_uid" ]
427
+ config .pytest_mpl_results_dir = config .workerinput ["pytest_mpl_results_dir" ]
428
+ self .results_dir = Path (config .pytest_mpl_results_dir )
429
+ self .results_dir .mkdir (parents = True , exist_ok = True )
430
+
414
431
def get_logger (self ):
415
432
# configure a separate logger for this pluggin which is independent
416
433
# of the options that are configured for pytest or for the code that
@@ -933,15 +950,20 @@ def pytest_runtest_call(self, item): # noqa
933
950
result ._excinfo = (type (e ), e , e .__traceback__ )
934
951
935
952
def generate_summary_json (self ):
936
- json_file = self .results_dir / 'results.json'
953
+ filename = "results.json"
954
+ if hasattr (self .config , "workerinput" ):
955
+ worker_id = os .environ .get ("PYTEST_XDIST_WORKER" )
956
+ filename = f"results-xdist-{ self .config .pytest_mpl_uid } -{ worker_id } .json"
957
+ json_file = self .results_dir / filename
937
958
with open (json_file , 'w' ) as f :
938
959
json .dump (self ._test_results , f , indent = 2 )
939
960
return json_file
940
961
941
- def pytest_unconfigure (self , config ):
962
+ def pytest_sessionfinish (self , session ):
942
963
"""
943
964
Save out the hash library at the end of the run.
944
965
"""
966
+ config = session .config
945
967
result_hash_library = self .results_dir / (self .results_hash_library_name or "temp.json" )
946
968
if self .generate_hash_library is not None :
947
969
hash_library_path = Path (config .rootdir ) / self .generate_hash_library
@@ -960,10 +982,24 @@ def pytest_unconfigure(self, config):
960
982
json .dump (result_hashes , fp , indent = 2 )
961
983
962
984
if self .generate_summary :
985
+ try :
986
+ import xdist
987
+ is_xdist_controller = xdist .is_xdist_controller (session )
988
+ is_xdist_worker = xdist .is_xdist_worker (session )
989
+ except ImportError :
990
+ is_xdist_controller = False
991
+ is_xdist_worker = False
963
992
kwargs = {}
964
993
if 'json' in self .generate_summary :
994
+ if is_xdist_controller :
995
+ uid = config .pytest_mpl_uid
996
+ for worker_results in self .results_dir .glob (f"results-xdist-{ uid } -*.json" ):
997
+ with worker_results .open () as f :
998
+ self ._test_results .update (json .load (f ))
965
999
summary = self .generate_summary_json ()
966
1000
print (f"A JSON report can be found at: { summary } " )
1001
+ if is_xdist_worker :
1002
+ return
967
1003
if result_hash_library .exists (): # link to it in the HTML
968
1004
kwargs ["hash_library" ] = result_hash_library .name
969
1005
if 'html' in self .generate_summary :
0 commit comments