|
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