Skip to content

Commit 9e7c62e

Browse files
committed
Add implementation of html generation
1 parent a8d9a76 commit 9e7c62e

File tree

1 file changed

+85
-16
lines changed

1 file changed

+85
-16
lines changed

pytest_mpl/plugin.py

Lines changed: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@
4949
Actual shape: {actual_shape}
5050
{actual_path}"""
5151

52+
HTML_INTRO = """
53+
<!DOCTYPE html>
54+
<html>
55+
<head>
56+
<style>
57+
table, th, td {
58+
border: 1px solid black;
59+
}
60+
</style>
61+
</head>
62+
<body>
63+
<h2>Image test comparison</h2>
64+
<table>
65+
<tr>
66+
<th>Test Name</th>
67+
<th>Baseline image</th>
68+
<th>Diff</th>
69+
<th>New image</th>
70+
</tr>
71+
"""
72+
5273

5374
def _download_file(baseline, filename):
5475
# Note that baseline can be a comma-separated list of URLs that we can
@@ -82,6 +103,20 @@ def _hash_file(in_stream):
82103
return hasher.hexdigest()
83104

84105

106+
def pathify(path):
107+
"""
108+
Remove non-path safe characters.
109+
"""
110+
path = Path(path)
111+
ext = path.suffix
112+
path = str(path).split(ext)[0]
113+
path = path.replace('[', '_').replace(']', '_')
114+
path = path.replace('/', '_')
115+
if path.endswith('_'):
116+
path = path[:-2]
117+
return Path(path + ext)
118+
119+
85120
def pytest_report_header(config, startdir):
86121
import matplotlib
87122
import matplotlib.ft2font
@@ -116,6 +151,8 @@ def pytest_addoption(parser):
116151
results_path_help = "directory for test results, relative to location where py.test is run"
117152
group.addoption('--mpl-results-path', help=results_path_help, action='store')
118153
parser.addini('mpl-results-path', help=results_path_help)
154+
parser.addini('mpl-use-full-test-name', help="use fully qualified test name as the filename.",
155+
type='bool')
119156

120157

121158
def pytest_configure(config):
@@ -211,7 +248,7 @@ def path_is_not_none(apath):
211248
return Path(apath) if apath is not None else apath
212249

213250

214-
class ImageComparison(object):
251+
class ImageComparison:
215252

216253
def __init__(self,
217254
config,
@@ -250,17 +287,16 @@ def generate_filename(self, item):
250287
"""
251288
Given a pytest item, generate the figure filename.
252289
"""
253-
return self.generate_test_name(item) + '.png'
254-
compare = self.get_compare(item)
255-
# Find test name to use as plot name
256-
filename = compare.kwargs.get('filename', None)
257-
if filename is None:
258-
filename = item.name + '.png'
259-
filename = filename.replace('[', '_').replace(']', '_')
260-
filename = filename.replace('/', '_')
261-
filename = filename.replace('_.png', '.png')
290+
if self.config.getini('mpl-use-full-test-name'):
291+
filename = self.generate_test_name(item) + '.png'
292+
else:
293+
compare = self.get_compare(item)
294+
# Find test name to use as plot name
295+
filename = compare.kwargs.get('filename', None)
296+
if filename is None:
297+
filename = item.name + '.png'
262298

263-
return filename
299+
return str(pathify(filename))
264300

265301
def generate_test_name(self, item):
266302
"""
@@ -413,6 +449,7 @@ def load_hash_library(self, library_path):
413449

414450
def compare_image_to_hash_library(self, item, fig, result_dir):
415451
compare = self.get_compare(item)
452+
savefig_kwargs = compare.kwargs.get('savefig_kwargs', {})
416453

417454
hash_library_filename = self.hash_library or compare.kwargs.get('hash_library', None)
418455
hash_library_filename = (Path(item.fspath).parent / hash_library_filename).absolute()
@@ -431,13 +468,16 @@ def compare_image_to_hash_library(self, item, fig, result_dir):
431468
if test_hash == hash_library[hash_name]:
432469
return
433470

434-
error_message = (f"hash {test_hash} doesn't match hash "
471+
error_message = (f"Hash {test_hash} doesn't match hash "
435472
f"{hash_library[hash_name]} in library "
436473
f"{hash_library_filename} for test {hash_name}.")
437474

438475
# If the compare has only been specified with hash and not baseline
439476
# dir, don't attempt to find a baseline image at the default path.
440477
if not self.baseline_directory_specified(item):
478+
# Save the figure for later summary
479+
test_image = (result_dir / "result.png").absolute()
480+
fig.savefig(str(test_image), **savefig_kwargs)
441481
return error_message
442482

443483
baseline_image_path = self.obtain_baseline_image(item, result_dir)
@@ -449,10 +489,13 @@ def compare_image_to_hash_library(self, item, fig, result_dir):
449489

450490
if baseline_image is None:
451491
error_message += f"\nUnable to find baseline image {baseline_image_path}."
452-
453-
if baseline_image is None:
454492
return error_message
455493

494+
# Override the tolerance (if not explicitly set) to 0 as the hashes are not forgiving
495+
tolerance = compare.kwargs.get('tolerance', None)
496+
if not tolerance:
497+
compare.kwargs['tolerance'] = 0
498+
456499
comparison_error = (self.compare_image_to_baseline(item, fig, result_dir) or
457500
"\nHowever, the comparison to the baseline image succeeded.")
458501

@@ -532,6 +575,28 @@ def item_function_wrapper(*args, **kwargs):
532575
else:
533576
item.obj = item_function_wrapper
534577

578+
def generate_summary_html(self, dir_list):
579+
"""
580+
Generate a simple HTML table of the failed test results
581+
"""
582+
html_file = self.results_dir / 'fig_comparison.html'
583+
with open(html_file, 'w') as f:
584+
f.write(HTML_INTRO)
585+
586+
for directory in dir_list:
587+
f.write('<tr>'
588+
f'<td>{directory.parts[-1]}\n'
589+
f'<td><img src="{directory / "baseline.png"}"></td>\n'
590+
f'<td><img src="{directory / "result-failed-diff.png"}"></td>\n'
591+
f'<td><img src="{directory / "result.png"}"></td>\n'
592+
'</tr>\n\n')
593+
594+
f.write('</table>\n')
595+
f.write('</body>\n')
596+
f.write('</html>')
597+
598+
return html_file
599+
535600
def pytest_unconfigure(self, config):
536601
"""
537602
Save out the hash library at the end of the run.
@@ -543,10 +608,14 @@ def pytest_unconfigure(self, config):
543608
json.dump(self._generated_hash_library, fp, indent=2)
544609

545610
if self.generate_summary:
546-
breakpoint()
611+
# Generate a list of test directories
612+
dir_list = [p.relative_to(self.results_dir)
613+
for p in self.results_dir.iterdir() if p.is_dir()]
614+
html_summary = self.generate_summary_html(dir_list)
615+
print(f"A summary of the failed tests can be found at: {html_summary}")
547616

548617

549-
class FigureCloser(object):
618+
class FigureCloser:
550619
"""
551620
This is used in place of ImageComparison when the --mpl option is not used,
552621
to make sure that we still close figures returned by tests.

0 commit comments

Comments
 (0)