|
59 | 59 | table, th, td { |
60 | 60 | border: 1px solid black; |
61 | 61 | } |
| 62 | +.summary > div { |
| 63 | + padding: 0.5em; |
| 64 | +} |
| 65 | +tr.passed .status, .rms.passed, .hashes.passed { |
| 66 | + color: green; |
| 67 | +} |
| 68 | +tr.failed .status, .rms.failed, .hashes.failed { |
| 69 | + color: red; |
| 70 | +} |
62 | 71 | </style> |
63 | 72 | </head> |
64 | 73 | <body> |
65 | 74 | <h2>Image test comparison</h2> |
| 75 | +%summary% |
66 | 76 | <table> |
67 | 77 | <tr> |
68 | 78 | <th>Test Name</th> |
@@ -301,6 +311,7 @@ def __init__(self, |
301 | 311 | # We need global state to store all the hashes generated over the run |
302 | 312 | self._generated_hash_library = {} |
303 | 313 | self._test_results = {} |
| 314 | + self._test_stats = None |
304 | 315 |
|
305 | 316 | def get_compare(self, item): |
306 | 317 | """ |
@@ -701,22 +712,94 @@ def item_function_wrapper(*args, **kwargs): |
701 | 712 | else: |
702 | 713 | item.obj = item_function_wrapper |
703 | 714 |
|
704 | | - def generate_summary_html(self, dir_list): |
| 715 | + def generate_stats(self): |
| 716 | + """ |
| 717 | + Generate a dictionary of summary statistics. |
| 718 | + """ |
| 719 | + stats = {'passed': 0, 'failed': 0, 'passed_baseline': 0, 'failed_baseline': 0, 'skipped': 0} |
| 720 | + for test in self._test_results.values(): |
| 721 | + if test['status'] == 'passed': |
| 722 | + stats['passed'] += 1 |
| 723 | + if test['rms'] is not None: |
| 724 | + stats['failed_baseline'] += 1 |
| 725 | + elif test['status'] == 'failed': |
| 726 | + stats['failed'] += 1 |
| 727 | + if test['rms'] is None: |
| 728 | + stats['passed_baseline'] += 1 |
| 729 | + elif test['status'] == 'skipped': |
| 730 | + stats['skipped'] += 1 |
| 731 | + else: |
| 732 | + raise ValueError(f"Unknown test status '{test['status']}'.") |
| 733 | + self._test_stats = stats |
| 734 | + |
| 735 | + def generate_summary_html(self): |
705 | 736 | """ |
706 | 737 | Generate a simple HTML table of the failed test results |
707 | 738 | """ |
708 | 739 | html_file = self.results_dir / 'fig_comparison.html' |
709 | 740 | with open(html_file, 'w') as f: |
710 | | - f.write(HTML_INTRO) |
711 | 741 |
|
712 | | - for directory in dir_list: |
713 | | - test_name = directory.parts[-1] |
714 | | - test_result = 'passed' if self._test_results[test_name] is True else 'failed' |
715 | | - f.write('<tr>' |
716 | | - f'<td>{test_name} ({test_result})\n' |
717 | | - f'<td><img src="{directory / "baseline.png"}"></td>\n' |
718 | | - f'<td><img src="{directory / "result-failed-diff.png"}"></td>\n' |
719 | | - f'<td><img src="{directory / "result.png"}"></td>\n' |
| 742 | + passed = f"{self._test_stats['passed']} passed" |
| 743 | + if self._test_stats['failed_baseline'] > 0: |
| 744 | + passed += (" hash comparison, although " |
| 745 | + f"{self._test_stats['failed_baseline']} " |
| 746 | + "of those have a different baseline image") |
| 747 | + |
| 748 | + failed = f"{self._test_stats['failed']} failed" |
| 749 | + if self._test_stats['passed_baseline'] > 0: |
| 750 | + failed += (" hash comparison, although " |
| 751 | + f"{self._test_stats['passed_baseline']} " |
| 752 | + "of those have a matching baseline image") |
| 753 | + |
| 754 | + f.write(HTML_INTRO.replace('%summary%', f'<p>{passed}.</p><p>{failed}.</p>')) |
| 755 | + |
| 756 | + for test_name in sorted(self._test_results.keys()): |
| 757 | + summary = self._test_results[test_name] |
| 758 | + |
| 759 | + if summary['rms'] is None and summary['tolerance'] is not None: |
| 760 | + rms = (f'<div class="rms passed">\n' |
| 761 | + f' <strong>RMS:</strong> ' |
| 762 | + f' < <span class="tolerance">{summary["tolerance"]}</span>\n' |
| 763 | + f'</div>') |
| 764 | + elif summary['rms'] is not None: |
| 765 | + rms = (f'<div class="rms failed">\n' |
| 766 | + f' <strong>RMS:</strong> ' |
| 767 | + f' <span class="rms">{summary["rms"]}</span>\n' |
| 768 | + f'</div>') |
| 769 | + else: |
| 770 | + rms = '' |
| 771 | + |
| 772 | + hashes = '' |
| 773 | + if summary['baseline_hash'] is not None: |
| 774 | + hashes += (f' <div class="baseline">Baseline: ' |
| 775 | + f'{summary["baseline_hash"]}</div>\n') |
| 776 | + if summary['result_hash'] is not None: |
| 777 | + hashes += (f' <div class="result">Result: ' |
| 778 | + f'{summary["result_hash"]}</div>\n') |
| 779 | + if len(hashes) > 0: |
| 780 | + if summary["baseline_hash"] == summary["result_hash"]: |
| 781 | + hash_result = 'passed' |
| 782 | + else: |
| 783 | + hash_result = 'failed' |
| 784 | + hashes = f'<div class="hashes {hash_result}">\n{hashes}</div>' |
| 785 | + |
| 786 | + images = {} |
| 787 | + for image_type in ['baseline_image', 'diff_image', 'result_image']: |
| 788 | + if summary[image_type] is not None: |
| 789 | + images[image_type] = f'<img src="{summary[image_type]}" />' |
| 790 | + else: |
| 791 | + images[image_type] = '' |
| 792 | + |
| 793 | + f.write(f'<tr class="{summary["status"]}">\n' |
| 794 | + ' <td>\n' |
| 795 | + ' <div class="summary">\n' |
| 796 | + f' <div class="test-name">{test_name}</div>\n' |
| 797 | + f' <div class="status">{summary["status"]}</div>\n' |
| 798 | + f' {rms}{hashes}\n' |
| 799 | + ' </td>\n' |
| 800 | + f' <td>{images["baseline_image"]}</td>\n' |
| 801 | + f' <td>{images["diff_image"]}</td>\n' |
| 802 | + f' <td>{images["result_image"]}</td>\n' |
720 | 803 | '</tr>\n\n') |
721 | 804 |
|
722 | 805 | f.write('</table>\n') |
@@ -757,6 +840,8 @@ def pytest_unconfigure(self, config): |
757 | 840 | if self._test_results[test_name][image_type] == '%EXISTS%': |
758 | 841 | self._test_results[test_name][image_type] = str(directory / filename) |
759 | 842 |
|
| 843 | + self.generate_stats() |
| 844 | + |
760 | 845 | if 'json' in self.generate_summary: |
761 | 846 | summary = self.generate_summary_json() |
762 | 847 | print(f"A JSON report can be found at: {summary}") |
|
0 commit comments