Skip to content

Commit c315776

Browse files
committed
Move basic HTML to Jinja
1 parent 4cd379b commit c315776

File tree

4 files changed

+132
-136
lines changed

4 files changed

+132
-136
lines changed

pytest_mpl/plugin.py

Lines changed: 2 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
import pytest
4545

46-
from pytest_mpl.summary.html import generate_summary_html
46+
from pytest_mpl.summary.html import generate_summary_html, generate_summary_basic_html
4747

4848
SUPPORTED_FORMATS = {'html', 'json', 'basic-html'}
4949

@@ -53,37 +53,6 @@
5353
Actual shape: {actual_shape}
5454
{actual_path}"""
5555

56-
HTML_INTRO = """
57-
<!DOCTYPE html>
58-
<html>
59-
<head>
60-
<style>
61-
table, th, td {
62-
border: 1px solid black;
63-
}
64-
.summary > div {
65-
padding: 0.5em;
66-
}
67-
tr.passed .status, .rms.passed, .hashes.passed {
68-
color: green;
69-
}
70-
tr.failed .status, .rms.failed, .hashes.failed {
71-
color: red;
72-
}
73-
</style>
74-
</head>
75-
<body>
76-
<h2>Image test comparison</h2>
77-
%summary%
78-
<table>
79-
<tr>
80-
<th>Test Name</th>
81-
<th>Baseline image</th>
82-
<th>Diff</th>
83-
<th>New image</th>
84-
</tr>
85-
"""
86-
8756

8857
def _download_file(baseline, filename):
8958
# Note that baseline can be a comma-separated list of URLs that we can
@@ -714,105 +683,6 @@ def item_function_wrapper(*args, **kwargs):
714683
else:
715684
item.obj = item_function_wrapper
716685

717-
def generate_stats(self):
718-
"""
719-
Generate a dictionary of summary statistics.
720-
"""
721-
stats = {'passed': 0, 'failed': 0, 'passed_baseline': 0, 'failed_baseline': 0, 'skipped': 0}
722-
for test in self._test_results.values():
723-
if test['status'] == 'passed':
724-
stats['passed'] += 1
725-
if test['rms'] is not None:
726-
stats['failed_baseline'] += 1
727-
elif test['status'] == 'failed':
728-
stats['failed'] += 1
729-
if test['rms'] is None:
730-
stats['passed_baseline'] += 1
731-
elif test['status'] == 'skipped':
732-
stats['skipped'] += 1
733-
else:
734-
raise ValueError(f"Unknown test status '{test['status']}'.")
735-
self._test_stats = stats
736-
737-
def generate_summary_basic_html(self):
738-
"""
739-
Generate a simple HTML table of the failed test results
740-
"""
741-
html_file = self.results_dir / 'fig_comparison_basic.html'
742-
with open(html_file, 'w') as f:
743-
744-
passed = f"{self._test_stats['passed']} passed"
745-
if self._test_stats['failed_baseline'] > 0:
746-
passed += (" hash comparison, although "
747-
f"{self._test_stats['failed_baseline']} "
748-
"of those have a different baseline image")
749-
750-
failed = f"{self._test_stats['failed']} failed"
751-
if self._test_stats['passed_baseline'] > 0:
752-
failed += (" hash comparison, although "
753-
f"{self._test_stats['passed_baseline']} "
754-
"of those have a matching baseline image")
755-
756-
f.write(HTML_INTRO.replace('%summary%', f'<p>{passed}.</p><p>{failed}.</p>'))
757-
758-
for test_name in sorted(self._test_results.keys()):
759-
summary = self._test_results[test_name]
760-
761-
if not self.results_always and summary['result_image'] is None:
762-
continue # Don't show test if no result image
763-
764-
if summary['rms'] is None and summary['tolerance'] is not None:
765-
rms = (f'<div class="rms passed">\n'
766-
f' <strong>RMS:</strong> '
767-
f' &lt; <span class="tolerance">{summary["tolerance"]}</span>\n'
768-
f'</div>')
769-
elif summary['rms'] is not None:
770-
rms = (f'<div class="rms failed">\n'
771-
f' <strong>RMS:</strong> '
772-
f' <span class="rms">{summary["rms"]}</span>\n'
773-
f'</div>')
774-
else:
775-
rms = ''
776-
777-
hashes = ''
778-
if summary['baseline_hash'] is not None:
779-
hashes += (f' <div class="baseline">Baseline: '
780-
f'{summary["baseline_hash"]}</div>\n')
781-
if summary['result_hash'] is not None:
782-
hashes += (f' <div class="result">Result: '
783-
f'{summary["result_hash"]}</div>\n')
784-
if len(hashes) > 0:
785-
if summary["baseline_hash"] == summary["result_hash"]:
786-
hash_result = 'passed'
787-
else:
788-
hash_result = 'failed'
789-
hashes = f'<div class="hashes {hash_result}">\n{hashes}</div>'
790-
791-
images = {}
792-
for image_type in ['baseline_image', 'diff_image', 'result_image']:
793-
if summary[image_type] is not None:
794-
images[image_type] = f'<img src="{summary[image_type]}" />'
795-
else:
796-
images[image_type] = ''
797-
798-
f.write(f'<tr class="{summary["status"]}">\n'
799-
' <td>\n'
800-
' <div class="summary">\n'
801-
f' <div class="test-name">{test_name}</div>\n'
802-
f' <div class="status">{summary["status"]}</div>\n'
803-
f' {rms}{hashes}\n'
804-
' </td>\n'
805-
f' <td>{images["baseline_image"]}</td>\n'
806-
f' <td>{images["diff_image"]}</td>\n'
807-
f' <td>{images["result_image"]}</td>\n'
808-
'</tr>\n\n')
809-
810-
f.write('</table>\n')
811-
f.write('</body>\n')
812-
f.write('</html>')
813-
814-
return html_file
815-
816686
def generate_summary_json(self):
817687
json_file = self.results_dir / 'results.json'
818688
with open(json_file, 'w') as f:
@@ -845,16 +715,14 @@ def pytest_unconfigure(self, config):
845715
if self._test_results[test_name][image_type] == '%EXISTS%':
846716
self._test_results[test_name][image_type] = str(directory / filename)
847717

848-
self.generate_stats()
849-
850718
if 'json' in self.generate_summary:
851719
summary = self.generate_summary_json()
852720
print(f"A JSON report can be found at: {summary}")
853721
if 'html' in self.generate_summary:
854722
summary = generate_summary_html(self._test_results, self.results_dir)
855723
print(f"A summary of the failed tests can be found at: {summary}")
856724
if 'basic-html' in self.generate_summary:
857-
summary = self.generate_summary_basic_html()
725+
summary = generate_summary_basic_html(self._test_results, self.results_dir)
858726
print(f"A summary of the failed tests can be found at: {summary}")
859727

860728

pytest_mpl/summary/html.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from jinja2 import Environment, PackageLoader, select_autoescape
66

7-
__all__ = ['generate_summary_html']
7+
__all__ = ['generate_summary_html', 'generate_summary_basic_html']
88

99

1010
class Results:
@@ -43,6 +43,24 @@ def __init__(self, results, title="Image comparison"):
4343
self.cards += [Result(name, item, card_id, self.warn_missing)]
4444
self.cards = sorted(self.cards, key=lambda i: i.indexes['status'], reverse=True)
4545

46+
@cached_property
47+
def statistics(self):
48+
"""Generate a dictionary of summary statistics."""
49+
stats = {'passed': 0, 'failed': 0, 'passed_baseline': 0,
50+
'failed_baseline': 0, 'skipped': 0}
51+
for test in self.cards:
52+
if test.status == 'passed':
53+
stats['passed'] += 1
54+
if test.image_status != 'match':
55+
stats['failed_baseline'] += 1
56+
elif test.status == 'failed':
57+
stats['failed'] += 1
58+
if test.image_status == 'match':
59+
stats['passed_baseline'] += 1
60+
elif test.status == 'skipped':
61+
stats['skipped'] += 1
62+
return stats
63+
4664

4765
class Result:
4866
"""
@@ -234,3 +252,32 @@ def generate_summary_html(results, results_dir):
234252
f.write(html + '\n')
235253

236254
return html_file
255+
256+
257+
def generate_summary_basic_html(results, results_dir):
258+
"""Generate the basic HTML summary.
259+
260+
Parameters
261+
----------
262+
results : dict
263+
The `pytest_mpl.plugin.ImageComparison._test_results` object.
264+
results_dir : Path
265+
Path to the output directory.
266+
"""
267+
268+
# Initialize Jinja
269+
env = Environment(
270+
loader=PackageLoader("pytest_mpl.summary.html"),
271+
autoescape=select_autoescape()
272+
)
273+
274+
# Render HTML starting from the base template
275+
template = env.get_template("basic.html")
276+
html = template.render(results=Results(results))
277+
278+
# Write files
279+
html_file = results_dir / 'fig_comparison_basic.html'
280+
with open(html_file, 'w') as f:
281+
f.write(html + '\n')
282+
283+
return html_file
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<style>
7+
table, th, td {
8+
border: 1px solid black;
9+
}
10+
11+
td:first-child {
12+
padding: 1em;
13+
}
14+
15+
td div {
16+
padding: 0.15em 0;
17+
max-width: 20rem;
18+
overflow-wrap: break-word;
19+
}
20+
21+
.test-name {
22+
font-size: 1.5em;
23+
}
24+
25+
.status {
26+
padding: 0.8em 0;
27+
}
28+
29+
img {
30+
width: 100%;
31+
}
32+
</style>
33+
<title>{{ results.title }}</title>
34+
</head>
35+
<body>
36+
<h2>Image test comparison</h2>
37+
<p>
38+
{{ results.statistics['passed'] }} passed
39+
{% if results.statistics['failed_baseline'] > 0 -%}
40+
hash comparison, although {{ results.statistics['failed_baseline'] }}
41+
of those have a different baseline image
42+
{%- endif %}
43+
</p>
44+
<p>
45+
{{ results.statistics['failed'] }} failed
46+
{% if results.statistics['passed_baseline'] > 0 -%}
47+
hash comparison, although {{ results.statistics['passed_baseline'] }}
48+
of those have a matching baseline image
49+
{%- endif %}
50+
</p>
51+
<table>
52+
<tr>
53+
<th></th>
54+
<th>Baseline</th>
55+
<th>Diff</th>
56+
<th>Result</th>
57+
</tr>
58+
{% for result in results.cards -%}
59+
{% if result.result_image -%}
60+
<tr>
61+
<td>
62+
<div>{{ result.module }}</div>
63+
<div class="test-name">{{ result.name }}</div>
64+
<div class="status">{{ result.status | upper }}</div>
65+
<div>RMS: {{ result.rms_str }} ({{ result.image_status }})</div>
66+
{% if result.result_hash -%}
67+
<div>Result hash: {{ result.result_hash }} ({{ result.hash_status}})</div>
68+
{%- endif %}
69+
</td>
70+
{% macro image(file) -%}
71+
<td>{% if file %}<img src="{{ file }}">{% endif %}</td>
72+
{%- endmacro -%}
73+
{{ image(result.baseline_image) }}
74+
{{ image(result.diff_image) }}
75+
{{ image(result.result_image) }}
76+
</tr>
77+
{%- endif %}
78+
{%- endfor %}
79+
</table>
80+
</body>
81+
</html>

tests/test_pytest_mpl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ def test_results_always(tmpdir):
467467

468468
test_name = f'test.{test}'
469469

470-
summary = f'<div class="test-name">{test_name}</div>'
470+
summary = f'<div class="test-name">{test_name.split(".")[-1]}</div>'
471471
assert summary in html
472472

473473
assert test_name in json_results.keys()

0 commit comments

Comments
 (0)