Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 27d4c0c

Browse files
committed
Updates to the image comparison testing decorator:
1) Run the test itself that generates figure(s) only once, then savefig it to each output format. This appears to have an approx 2.75x speedup on run time. 2) Cleanup better after each test: Close all figures Reset rcParams to their defaults Reset the unit registry to defaults This requires the following changes to the tests themselves: 1) The tests no longer need to call 'savefig' -- just create figures and the decorator will find them. 2) Tests that don't use the image_comparison decorator should use the new cleanup decorator to make sure that cleanup happens.
1 parent 303d361 commit 27d4c0c

15 files changed

Lines changed: 851 additions & 165 deletions

lib/matplotlib/testing/decorators.py

Lines changed: 100 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from matplotlib.testing.noseclasses import KnownFailureTest, \
22
KnownFailureDidNotFailTest, ImageComparisonFailure
3-
import os, sys, shutil
3+
import os, sys, shutil, new
44
import nose
55
import matplotlib
66
import matplotlib.tests
7+
import matplotlib.units
8+
from matplotlib import pyplot as plt
79
import numpy as np
810
from matplotlib.testing.compare import comparable_formats, compare_images
911

@@ -46,7 +48,83 @@ def failer(*args, **kwargs):
4648
return nose.tools.make_decorator(f)(failer)
4749
return known_fail_decorator
4850

49-
def image_comparison(baseline_images=None,extensions=None,tol=1e-3):
51+
class CleanupTest:
52+
@classmethod
53+
def setup_class(cls):
54+
cls.original_rcParams = {}
55+
cls.original_rcParams.update(matplotlib.rcParams)
56+
57+
cls.original_units_registry = {}
58+
cls.original_units_registry.update(matplotlib.units.registry)
59+
60+
@classmethod
61+
def teardown_class(cls):
62+
plt.close('all')
63+
64+
matplotlib.rcParams.clear()
65+
matplotlib.rcParams.update(cls.original_rcParams)
66+
67+
matplotlib.units.registry.clear()
68+
matplotlib.units.registry.update(cls.original_units_registry)
69+
70+
def test(self):
71+
self.func()
72+
73+
def cleanup(func):
74+
new_class = new.classobj(
75+
func.__name__,
76+
(CleanupTest,),
77+
{'func': staticmethod(func)})
78+
return new_class
79+
80+
class ImageComparisonTest(CleanupTest):
81+
@classmethod
82+
def setup_class(cls):
83+
CleanupTest.setup_class()
84+
85+
cls.func()
86+
87+
def test(self):
88+
baseline_dir, result_dir = _image_directories(self.func)
89+
90+
for fignum, baseline in zip(plt.get_fignums(), self.baseline_images):
91+
figure = plt.figure(fignum)
92+
93+
for extension in self.extensions:
94+
will_fail = not extension in comparable_formats()
95+
if will_fail:
96+
fail_msg = 'Cannot compare %s files on this system' % extension
97+
else:
98+
fail_msg = 'No failure expected'
99+
100+
orig_expected_fname = os.path.join(baseline_dir, baseline) + '.' + extension
101+
expected_fname = os.path.join(result_dir, 'expected-' + baseline) + '.' + extension
102+
actual_fname = os.path.join(result_dir, baseline) + '.' + extension
103+
if os.path.exists(orig_expected_fname):
104+
shutil.copyfile(orig_expected_fname, expected_fname)
105+
else:
106+
will_fail = True
107+
fail_msg = 'Do not have baseline image %s' % expected_fname
108+
109+
@knownfailureif(
110+
will_fail, fail_msg,
111+
known_exception_class=ImageComparisonFailure)
112+
def do_test():
113+
figure.savefig(actual_fname)
114+
115+
if not os.path.exists(expected_fname):
116+
raise ImageComparisonFailure(
117+
'image does not exist: %s' % expected_fname)
118+
119+
err = compare_images(expected_fname, actual_fname, self.tol, in_decorator=True)
120+
if err:
121+
raise ImageComparisonFailure(
122+
'images not close: %(actual)s vs. %(expected)s '
123+
'(RMS %(rms).3f)'%err)
124+
125+
yield (do_test,)
126+
127+
def image_comparison(baseline_images=None, extensions=None, tol=1e-3):
50128
"""
51129
call signature::
52130
@@ -76,56 +154,22 @@ def image_comparison(baseline_images=None,extensions=None,tol=1e-3):
76154
# default extensions to test
77155
extensions = ['png', 'pdf', 'svg']
78156

79-
# The multiple layers of defs are required because of how
80-
# parameterized decorators work, and because we want to turn the
81-
# single test_foo function to a generator that generates a
82-
# separate test case for each file format.
83157
def compare_images_decorator(func):
84-
baseline_dir, result_dir = _image_directories(func)
85-
86-
def compare_images_generator():
87-
for extension in extensions:
88-
orig_expected_fnames = [os.path.join(baseline_dir,fname) + '.' + extension for fname in baseline_images]
89-
expected_fnames = [os.path.join(result_dir,'expected-'+fname) + '.' + extension for fname in baseline_images]
90-
actual_fnames = [os.path.join(result_dir, fname) + '.' + extension for fname in baseline_images]
91-
have_baseline_images = [os.path.exists(expected) for expected in orig_expected_fnames]
92-
have_baseline_image = np.all(have_baseline_images)
93-
is_comparable = extension in comparable_formats()
94-
if not is_comparable:
95-
fail_msg = 'Cannot compare %s files on this system' % extension
96-
elif not have_baseline_image:
97-
fail_msg = 'Do not have baseline images %s' % expected_fnames
98-
else:
99-
fail_msg = 'No failure expected'
100-
will_fail = not (is_comparable and have_baseline_image)
101-
@knownfailureif(will_fail, fail_msg,
102-
known_exception_class=ImageComparisonFailure )
103-
def decorated_compare_images():
104-
# set the default format of savefig
105-
matplotlib.rc('savefig', extension=extension)
106-
# change to the result directory for the duration of the test
107-
old_dir = os.getcwd()
108-
os.chdir(result_dir)
109-
try:
110-
result = func() # actually call the test function
111-
finally:
112-
os.chdir(old_dir)
113-
for original, expected in zip(orig_expected_fnames, expected_fnames):
114-
if not os.path.exists(original):
115-
raise ImageComparisonFailure(
116-
'image does not exist: %s'%original)
117-
shutil.copyfile(original, expected)
118-
for actual,expected in zip(actual_fnames,expected_fnames):
119-
# compare the images
120-
err = compare_images( expected, actual, tol,
121-
in_decorator=True )
122-
if err:
123-
raise ImageComparisonFailure(
124-
'images not close: %(actual)s vs. %(expected)s '
125-
'(RMS %(rms).3f)'%err)
126-
return result
127-
yield (decorated_compare_images,)
128-
return nose.tools.make_decorator(func)(compare_images_generator)
158+
# We want to run the setup function (the actual test function
159+
# that generates the figure objects) only once for each type
160+
# of output file. The only way to achieve this with nose
161+
# appears to be to create a test class with "setup_class" and
162+
# "teardown_class" methods. Creating a class instance doesn't
163+
# work, so we use new.classobj to actually create a class and
164+
# fill it with the appropriate methods.
165+
new_class = new.classobj(
166+
func.__name__,
167+
(ImageComparisonTest,),
168+
{'func': staticmethod(func),
169+
'baseline_images': baseline_images,
170+
'extensions': extensions,
171+
'tol': tol})
172+
return new_class
129173
return compare_images_decorator
130174

131175
def _image_directories(func):
@@ -134,7 +178,7 @@ def _image_directories(func):
134178
Create the result directory if it doesn't exist.
135179
"""
136180
module_name = func.__module__
137-
if module_name=='__main__':
181+
if module_name == '__main__':
138182
# FIXME: this won't work for nested packages in matplotlib.tests
139183
import warnings
140184
warnings.warn('test module run as script. guessing baseline image locations')
@@ -143,13 +187,13 @@ def _image_directories(func):
143187
subdir = os.path.splitext(os.path.split(script_name)[1])[0]
144188
else:
145189
mods = module_name.split('.')
146-
assert mods.pop(0)=='matplotlib'
147-
assert mods.pop(0)=='tests'
190+
assert mods.pop(0) == 'matplotlib'
191+
assert mods.pop(0) == 'tests'
148192
subdir = os.path.join(*mods)
149193
basedir = os.path.dirname(matplotlib.tests.__file__)
150194

151-
baseline_dir = os.path.join(basedir,'baseline_images',subdir)
152-
result_dir = os.path.abspath(os.path.join('result_images',subdir))
195+
baseline_dir = os.path.join(basedir, 'baseline_images', subdir)
196+
result_dir = os.path.abspath(os.path.join('result_images', subdir))
153197

154198
if not os.path.exists(result_dir):
155199
os.makedirs(result_dir)
18.7 KB
Binary file not shown.
81 KB
Loading

0 commit comments

Comments
 (0)