@@ -140,7 +140,7 @@ def pytest_addoption(parser):
140140 group .addoption ('--mpl-results-path' , help = results_path_help , action = 'store' )
141141 parser .addini ('mpl-results-path' , help = results_path_help )
142142
143- results_always_help = ("Always generate result images, not just for failed tests. "
143+ results_always_help = ("Always compare to baseline images and save result images, even for passing tests. "
144144 "This option is automatically applied when generating a HTML summary." )
145145 group .addoption ('--mpl-results-always' , action = 'store_true' ,
146146 help = results_always_help )
@@ -272,7 +272,7 @@ def __init__(self,
272272 if len (unsupported_formats ) > 0 :
273273 raise ValueError (f"The mpl summary type(s) '{ sorted (unsupported_formats )} ' "
274274 "are not supported." )
275- # Ignore `results_always` and always save result images for HTML output
275+ # When generating HTML always apply `results_always`
276276 if generate_summary & {'html' , 'basic-html' }:
277277 results_always = True
278278 self .generate_summary = generate_summary
@@ -431,7 +431,7 @@ def compare_image_to_baseline(self, item, fig, result_dir, summary=None):
431431
432432 test_image = (result_dir / "result.png" ).absolute ()
433433 fig .savefig (str (test_image ), ** savefig_kwargs )
434- summary ['result_image' ] = '%EXISTS%'
434+ summary ['result_image' ] = test_image . relative_to ( self . results_dir ). as_posix ()
435435
436436 if not os .path .exists (baseline_image_ref ):
437437 summary ['status' ] = 'failed'
@@ -447,7 +447,7 @@ def compare_image_to_baseline(self, item, fig, result_dir, summary=None):
447447 # copy to our tmpdir to be sure to keep them in case of failure
448448 baseline_image = (result_dir / "baseline.png" ).absolute ()
449449 shutil .copyfile (baseline_image_ref , baseline_image )
450- summary ['baseline_image' ] = '%EXISTS%'
450+ summary ['baseline_image' ] = baseline_image . relative_to ( self . results_dir ). as_posix ()
451451
452452 # Compare image size ourselves since the Matplotlib
453453 # exception is a bit cryptic in this case and doesn't show
@@ -472,7 +472,8 @@ def compare_image_to_baseline(self, item, fig, result_dir, summary=None):
472472 else :
473473 summary ['status' ] = 'failed'
474474 summary ['rms' ] = results ['rms' ]
475- summary ['diff_image' ] = '%EXISTS%'
475+ diff_image = (result_dir / 'result-failed-diff.png' ).absolute ()
476+ summary ['diff_image' ] = diff_image .relative_to (self .results_dir ).as_posix ()
476477 template = ['Error: Image files did not match.' ,
477478 'RMS Value: {rms}' ,
478479 'Expected: \n {expected}' ,
@@ -488,9 +489,7 @@ def load_hash_library(self, library_path):
488489 return json .load (fp )
489490
490491 def compare_image_to_hash_library (self , item , fig , result_dir , summary = None ):
491- new_test = False
492492 hash_comparison_pass = False
493- baseline_image_path = None
494493 if summary is None :
495494 summary = {}
496495
@@ -505,87 +504,58 @@ def compare_image_to_hash_library(self, item, fig, result_dir, summary=None):
505504
506505 hash_library = self .load_hash_library (hash_library_filename )
507506 hash_name = self .generate_test_name (item )
507+ baseline_hash = hash_library .get (hash_name , None )
508+ summary ['baseline_hash' ] = baseline_hash
508509
509510 test_hash = self .generate_image_hash (item , fig )
510511 summary ['result_hash' ] = test_hash
511512
512- if hash_name not in hash_library :
513- new_test = True
513+ if baseline_hash is None : # hash-missing
514514 summary ['status' ] = 'failed'
515- error_message = (f"Hash for test '{ hash_name } ' not found in { hash_library_filename } . "
516- f"Generated hash is { test_hash } ." )
517- summary ['status_msg' ] = error_message
518- else :
519- summary ['baseline_hash' ] = hash_library [hash_name ]
515+ summary ['status_msg' ] = (f"Hash for test '{ hash_name } ' not found in { hash_library_filename } . "
516+ f"Generated hash is { test_hash } ." )
517+ elif test_hash == baseline_hash : # hash-match
518+ hash_comparison_pass = True
519+ summary ['status' ] = 'passed'
520+ summary ['status_msg' ] = 'Test hash matches baseline hash.'
521+ else : # hash-diff
522+ summary ['status' ] = 'failed'
523+ summary ['status_msg' ] = (f"Hash { test_hash } doesn't match hash "
524+ f"{ baseline_hash } in library "
525+ f"{ hash_library_filename } for test { hash_name } ." )
520526
521527 # Save the figure for later summary (will be removed later if not needed)
522528 test_image = (result_dir / "result.png" ).absolute ()
523529 fig .savefig (str (test_image ), ** savefig_kwargs )
524- summary ['result_image' ] = '%EXISTS%'
530+ summary ['result_image' ] = test_image . relative_to ( self . results_dir ). as_posix ()
525531
526- if not new_test :
527- if test_hash == hash_library [hash_name ]:
528- hash_comparison_pass = True
529- summary ['status' ] = 'passed'
530- summary ['status_msg' ] = 'Test hash matches baseline hash.'
531- else :
532- error_message = (f"Hash { test_hash } doesn't match hash "
533- f"{ hash_library [hash_name ]} in library "
534- f"{ hash_library_filename } for test { hash_name } ." )
535- summary ['status' ] = 'failed'
536- summary ['status_msg' ] = 'Test hash does not match baseline hash.'
537-
538- # If the compare has only been specified with hash and not baseline
539- # dir, don't attempt to find a baseline image at the default path.
540- if not hash_comparison_pass and not self .baseline_directory_specified (item ) or new_test :
541- return error_message
532+ # Hybrid mode (hash and image comparison)
533+ if self .baseline_directory_specified (item ):
542534
543- # If this is not a new test try and get the baseline image.
544- if not new_test :
545- baseline_error = None
546- baseline_summary = {}
547- # Ignore Errors here as it's possible the reference image dosen't exist yet.
548- try :
549- baseline_image_path = self .obtain_baseline_image (item , result_dir )
550- baseline_image = baseline_image_path
551- if baseline_image and not baseline_image .exists ():
552- baseline_image = None
553- # Get the baseline and generate a diff image, always so that
554- # --mpl-results-always can be respected.
535+ # Skip image comparison if hash matches (unless `--mpl-results-always`)
536+ if hash_comparison_pass and not self .results_always :
537+ return
538+
539+ # Run image comparison
540+ baseline_summary = {} # summary for image comparison to merge with hash comparison summary
541+ try : # Ignore all errors as success does not influence the overall test result
555542 baseline_comparison = self .compare_image_to_baseline (item , fig , result_dir ,
556543 summary = baseline_summary )
557- except Exception as e :
558- baseline_image = None
559- baseline_error = e
560- for k in ['baseline_image' , 'diff_image' , 'rms' , 'tolerance' , 'result_image' ]:
561- summary [k ] = summary [k ] or baseline_summary .get (k )
562-
563- # If the hash comparison passes then return
564- if hash_comparison_pass :
544+ except Exception as baseline_error : # Append to test error later
545+ baseline_comparison = str (baseline_error )
546+ else : # Update main summary
547+ for k in ['baseline_image' , 'diff_image' , 'rms' , 'tolerance' , 'result_image' ]:
548+ summary [k ] = summary [k ] or baseline_summary .get (k )
549+
550+ # Append the log from image comparison
551+ r = baseline_comparison or "The comparison to the baseline image succeeded."
552+ summary ['status_msg' ] += ("\n \n "
553+ "Image comparison test\n "
554+ "---------------------\n " ) + r
555+
556+ if hash_comparison_pass : # Return None to indicate test passed
565557 return
566-
567- if baseline_image is None :
568- error_message += f"\n Unable to find baseline image for { item } ."
569- if baseline_error :
570- error_message += f"\n { baseline_error } "
571- summary ['status' ] = 'failed'
572- summary ['status_msg' ] = error_message
573- return error_message
574-
575- summary ['baseline_image' ] = '%EXISTS%'
576-
577- # Override the tolerance (if not explicitly set) to 0 as the hashes are not forgiving
578- tolerance = compare .kwargs .get ('tolerance' , None )
579- if not tolerance :
580- compare .kwargs ['tolerance' ] = 0
581-
582- comparison_error = (baseline_comparison or
583- "\n However, the comparison to the baseline image succeeded." )
584-
585- error_message = f"{ error_message } \n { comparison_error } "
586- summary ['status' ] = 'failed'
587- summary ['status_msg' ] = error_message
588- return error_message
558+ return summary ['status_msg' ]
589559
590560 def pytest_runtest_setup (self , item ): # noqa
591561
@@ -673,7 +643,7 @@ def item_function_wrapper(*args, **kwargs):
673643 if not self .results_always :
674644 shutil .rmtree (result_dir )
675645 for image_type in ['baseline_image' , 'diff_image' , 'result_image' ]:
676- summary [image_type ] = None # image no longer %EXISTS%
646+ summary [image_type ] = None # image no longer exists
677647 else :
678648 self ._test_results [str (pathify (test_name ))] = summary
679649 pytest .fail (msg , pytrace = False )
@@ -704,21 +674,6 @@ def pytest_unconfigure(self, config):
704674 json .dump (self ._generated_hash_library , fp , indent = 2 )
705675
706676 if self .generate_summary :
707- # Generate a list of test directories
708- dir_list = [p .relative_to (self .results_dir )
709- for p in self .results_dir .iterdir () if p .is_dir ()]
710-
711- # Resolve image paths
712- for directory in dir_list :
713- test_name = directory .parts [- 1 ]
714- for image_type , filename in [
715- ('baseline_image' , 'baseline.png' ),
716- ('diff_image' , 'result-failed-diff.png' ),
717- ('result_image' , 'result.png' ),
718- ]:
719- if self ._test_results [test_name ][image_type ] == '%EXISTS%' :
720- self ._test_results [test_name ][image_type ] = str (directory / filename )
721-
722677 if 'json' in self .generate_summary :
723678 summary = self .generate_summary_json ()
724679 print (f"A JSON report can be found at: { summary } " )
0 commit comments