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,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
131175def _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 )
0 commit comments