3131import io
3232import os
3333import json
34+ import uuid
3435import shutil
3536import hashlib
3637import logging
@@ -216,6 +217,12 @@ def pytest_addoption(parser):
216217 parser .addini (option , help = msg )
217218
218219
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+
219226def pytest_configure (config ):
220227
221228 config .addinivalue_line (
@@ -288,12 +295,20 @@ def get_cli_or_ini(name, default=None):
288295 if not _hash_library_from_cli :
289296 hash_library = os .path .abspath (hash_library )
290297
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+
291307 plugin = ImageComparison (
292308 config ,
293309 baseline_dir = baseline_dir ,
294310 baseline_relative_dir = baseline_relative_dir ,
295311 generate_dir = generate_dir ,
296- results_dir = results_dir ,
297312 hash_library = hash_library ,
298313 generate_hash_library = generate_hash_lib ,
299314 generate_summary = generate_summary ,
@@ -356,7 +371,6 @@ def __init__(
356371 baseline_dir = None ,
357372 baseline_relative_dir = None ,
358373 generate_dir = None ,
359- results_dir = None ,
360374 hash_library = None ,
361375 generate_hash_library = None ,
362376 generate_summary = None ,
@@ -372,7 +386,7 @@ def __init__(
372386 self .baseline_dir = baseline_dir
373387 self .baseline_relative_dir = path_is_not_none (baseline_relative_dir )
374388 self .generate_dir = path_is_not_none (generate_dir )
375- self .results_dir = path_is_not_none ( results_dir )
389+ self .results_dir = None
376390 self .hash_library = path_is_not_none (hash_library )
377391 self ._hash_library_from_cli = _hash_library_from_cli # for backwards compatibility
378392 self .generate_hash_library = path_is_not_none (generate_hash_library )
@@ -394,11 +408,6 @@ def __init__(
394408 self .deterministic = deterministic
395409 self .default_backend = default_backend
396410
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-
402411 # Decide what to call the downloadable results hash library
403412 if self .hash_library is not None :
404413 self .results_hash_library_name = self .hash_library .name
@@ -411,6 +420,14 @@ def __init__(
411420 self ._test_stats = None
412421 self .return_value = {}
413422
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+
414431 def get_logger (self ):
415432 # configure a separate logger for this pluggin which is independent
416433 # of the options that are configured for pytest or for the code that
@@ -933,15 +950,20 @@ def pytest_runtest_call(self, item): # noqa
933950 result ._excinfo = (type (e ), e , e .__traceback__ )
934951
935952 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
937958 with open (json_file , 'w' ) as f :
938959 json .dump (self ._test_results , f , indent = 2 )
939960 return json_file
940961
941- def pytest_unconfigure (self , config ):
962+ def pytest_sessionfinish (self , session ):
942963 """
943964 Save out the hash library at the end of the run.
944965 """
966+ config = session .config
945967 result_hash_library = self .results_dir / (self .results_hash_library_name or "temp.json" )
946968 if self .generate_hash_library is not None :
947969 hash_library_path = Path (config .rootdir ) / self .generate_hash_library
@@ -960,10 +982,24 @@ def pytest_unconfigure(self, config):
960982 json .dump (result_hashes , fp , indent = 2 )
961983
962984 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
963992 kwargs = {}
964993 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 ))
965999 summary = self .generate_summary_json ()
9661000 print (f"A JSON report can be found at: { summary } " )
1001+ if is_xdist_worker :
1002+ return
9671003 if result_hash_library .exists (): # link to it in the HTML
9681004 kwargs ["hash_library" ] = result_hash_library .name
9691005 if 'html' in self .generate_summary :
0 commit comments