@@ -140,7 +140,7 @@ def pytest_addoption(parser):
140
140
group .addoption ('--mpl-results-path' , help = results_path_help , action = 'store' )
141
141
parser .addini ('mpl-results-path' , help = results_path_help )
142
142
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. "
144
144
"This option is automatically applied when generating a HTML summary." )
145
145
group .addoption ('--mpl-results-always' , action = 'store_true' ,
146
146
help = results_always_help )
@@ -272,7 +272,7 @@ def __init__(self,
272
272
if len (unsupported_formats ) > 0 :
273
273
raise ValueError (f"The mpl summary type(s) '{ sorted (unsupported_formats )} ' "
274
274
"are not supported." )
275
- # Ignore `results_always` and always save result images for HTML output
275
+ # When generating HTML always apply `results_always`
276
276
if generate_summary & {'html' , 'basic-html' }:
277
277
results_always = True
278
278
self .generate_summary = generate_summary
@@ -431,7 +431,7 @@ def compare_image_to_baseline(self, item, fig, result_dir, summary=None):
431
431
432
432
test_image = (result_dir / "result.png" ).absolute ()
433
433
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 ()
435
435
436
436
if not os .path .exists (baseline_image_ref ):
437
437
summary ['status' ] = 'failed'
@@ -447,7 +447,7 @@ def compare_image_to_baseline(self, item, fig, result_dir, summary=None):
447
447
# copy to our tmpdir to be sure to keep them in case of failure
448
448
baseline_image = (result_dir / "baseline.png" ).absolute ()
449
449
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 ()
451
451
452
452
# Compare image size ourselves since the Matplotlib
453
453
# 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):
472
472
else :
473
473
summary ['status' ] = 'failed'
474
474
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 ()
476
477
template = ['Error: Image files did not match.' ,
477
478
'RMS Value: {rms}' ,
478
479
'Expected: \n {expected}' ,
@@ -488,9 +489,7 @@ def load_hash_library(self, library_path):
488
489
return json .load (fp )
489
490
490
491
def compare_image_to_hash_library (self , item , fig , result_dir , summary = None ):
491
- new_test = False
492
492
hash_comparison_pass = False
493
- baseline_image_path = None
494
493
if summary is None :
495
494
summary = {}
496
495
@@ -505,87 +504,58 @@ def compare_image_to_hash_library(self, item, fig, result_dir, summary=None):
505
504
506
505
hash_library = self .load_hash_library (hash_library_filename )
507
506
hash_name = self .generate_test_name (item )
507
+ baseline_hash = hash_library .get (hash_name , None )
508
+ summary ['baseline_hash' ] = baseline_hash
508
509
509
510
test_hash = self .generate_image_hash (item , fig )
510
511
summary ['result_hash' ] = test_hash
511
512
512
- if hash_name not in hash_library :
513
- new_test = True
513
+ if baseline_hash is None : # hash-missing
514
514
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 } ." )
520
526
521
527
# Save the figure for later summary (will be removed later if not needed)
522
528
test_image = (result_dir / "result.png" ).absolute ()
523
529
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 ()
525
531
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 ):
542
534
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
555
542
baseline_comparison = self .compare_image_to_baseline (item , fig , result_dir ,
556
543
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
565
557
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' ]
589
559
590
560
def pytest_runtest_setup (self , item ): # noqa
591
561
@@ -673,7 +643,7 @@ def item_function_wrapper(*args, **kwargs):
673
643
if not self .results_always :
674
644
shutil .rmtree (result_dir )
675
645
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
677
647
else :
678
648
self ._test_results [str (pathify (test_name ))] = summary
679
649
pytest .fail (msg , pytrace = False )
@@ -704,21 +674,6 @@ def pytest_unconfigure(self, config):
704
674
json .dump (self ._generated_hash_library , fp , indent = 2 )
705
675
706
676
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
-
722
677
if 'json' in self .generate_summary :
723
678
summary = self .generate_summary_json ()
724
679
print (f"A JSON report can be found at: { summary } " )
0 commit comments