11from matplotlib .testing .noseclasses import KnownFailureTest , \
22 KnownFailureDidNotFailTest , ImageComparisonFailure
3- import os , sys , shutil
3+ import os , sys , shutil , new
44import nose
55import matplotlib
66import matplotlib .tests
7+ import matplotlib .units
8+ from matplotlib import pyplot as plt
79import numpy as np
810from matplotlib .testing .compare import comparable_formats , compare_images
911
@@ -46,7 +48,81 @@ 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_units_registry = matplotlib .units .registry .copy ()
55+
56+ @classmethod
57+ def teardown_class (cls ):
58+ plt .close ('all' )
59+
60+ matplotlib .tests .setup ()
61+
62+ matplotlib .units .registry .clear ()
63+ matplotlib .units .registry .update (cls .original_units_registry )
64+
65+ def test (self ):
66+ self ._func ()
67+
68+ def cleanup (func ):
69+ name = func .__name__
70+ func = staticmethod (func )
71+ func .__get__ (1 ).__name__ = '_private'
72+ new_class = new .classobj (
73+ name ,
74+ (CleanupTest ,),
75+ {'_func' : func })
76+ return new_class
77+
78+ class ImageComparisonTest (CleanupTest ):
79+ @classmethod
80+ def setup_class (cls ):
81+ CleanupTest .setup_class ()
82+
83+ cls ._func ()
84+
85+ def test (self ):
86+ baseline_dir , result_dir = _image_directories (self ._func )
87+
88+ for fignum , baseline in zip (plt .get_fignums (), self ._baseline_images ):
89+ figure = plt .figure (fignum )
90+
91+ for extension in self ._extensions :
92+ will_fail = not extension in comparable_formats ()
93+ if will_fail :
94+ fail_msg = 'Cannot compare %s files on this system' % extension
95+ else :
96+ fail_msg = 'No failure expected'
97+
98+ orig_expected_fname = os .path .join (baseline_dir , baseline ) + '.' + extension
99+ expected_fname = os .path .join (result_dir , 'expected-' + baseline ) + '.' + extension
100+ actual_fname = os .path .join (result_dir , baseline ) + '.' + extension
101+ if os .path .exists (orig_expected_fname ):
102+ shutil .copyfile (orig_expected_fname , expected_fname )
103+ else :
104+ will_fail = True
105+ fail_msg = 'Do not have baseline image %s' % expected_fname
106+
107+ @knownfailureif (
108+ will_fail , fail_msg ,
109+ known_exception_class = ImageComparisonFailure )
110+ def do_test ():
111+ figure .savefig (actual_fname )
112+
113+ if not os .path .exists (expected_fname ):
114+ raise ImageComparisonFailure (
115+ 'image does not exist: %s' % expected_fname )
116+
117+ err = compare_images (expected_fname , actual_fname , self ._tol , in_decorator = True )
118+ if err :
119+ raise ImageComparisonFailure (
120+ 'images not close: %(actual)s vs. %(expected)s '
121+ '(RMS %(rms).3f)' % err )
122+
123+ yield (do_test ,)
124+
125+ def image_comparison (baseline_images = None , extensions = None , tol = 1e-3 ):
50126 """
51127 call signature::
52128
@@ -76,56 +152,29 @@ def image_comparison(baseline_images=None,extensions=None,tol=1e-3):
76152 # default extensions to test
77153 extensions = ['png' , 'pdf' , 'svg' ]
78154
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.
83155 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 )
156+ # We want to run the setup function (the actual test function
157+ # that generates the figure objects) only once for each type
158+ # of output file. The only way to achieve this with nose
159+ # appears to be to create a test class with "setup_class" and
160+ # "teardown_class" methods. Creating a class instance doesn't
161+ # work, so we use new.classobj to actually create a class and
162+ # fill it with the appropriate methods.
163+ name = func .__name__
164+ # For nose 1.0, we need to rename the test function to
165+ # something without the word "test", or it will be run as
166+ # well, outside of the context of our image comparison test
167+ # generator.
168+ func = staticmethod (func )
169+ func .__get__ (1 ).__name__ = '_private'
170+ new_class = new .classobj (
171+ name ,
172+ (ImageComparisonTest ,),
173+ {'_func' : func ,
174+ '_baseline_images' : baseline_images ,
175+ '_extensions' : extensions ,
176+ '_tol' : tol })
177+ return new_class
129178 return compare_images_decorator
130179
131180def _image_directories (func ):
@@ -134,7 +183,7 @@ def _image_directories(func):
134183 Create the result directory if it doesn't exist.
135184 """
136185 module_name = func .__module__
137- if module_name == '__main__' :
186+ if module_name == '__main__' :
138187 # FIXME: this won't work for nested packages in matplotlib.tests
139188 import warnings
140189 warnings .warn ('test module run as script. guessing baseline image locations' )
@@ -143,13 +192,13 @@ def _image_directories(func):
143192 subdir = os .path .splitext (os .path .split (script_name )[1 ])[0 ]
144193 else :
145194 mods = module_name .split ('.' )
146- assert mods .pop (0 )== 'matplotlib'
147- assert mods .pop (0 )== 'tests'
195+ assert mods .pop (0 ) == 'matplotlib'
196+ assert mods .pop (0 ) == 'tests'
148197 subdir = os .path .join (* mods )
149198 basedir = os .path .dirname (matplotlib .tests .__file__ )
150199
151- baseline_dir = os .path .join (basedir ,'baseline_images' ,subdir )
152- result_dir = os .path .abspath (os .path .join ('result_images' ,subdir ))
200+ baseline_dir = os .path .join (basedir , 'baseline_images' , subdir )
201+ result_dir = os .path .abspath (os .path .join ('result_images' , subdir ))
153202
154203 if not os .path .exists (result_dir ):
155204 os .makedirs (result_dir )
0 commit comments