1313from matplotlib import animation
1414
1515
16+ @pytest .fixture ()
17+ def anim (request ):
18+ """Create a simple animation (with options)."""
19+ fig , ax = plt .subplots ()
20+ line , = ax .plot ([], [])
21+
22+ ax .set_xlim (0 , 10 )
23+ ax .set_ylim (- 1 , 1 )
24+
25+ def init ():
26+ line .set_data ([], [])
27+ return line ,
28+
29+ def animate (i ):
30+ x = np .linspace (0 , 10 , 100 )
31+ y = np .sin (x + i )
32+ line .set_data (x , y )
33+ return line ,
34+
35+ # "klass" can be passed to determine the class returned by the fixture
36+ kwargs = dict (getattr (request , 'param' , {})) # make a copy
37+ klass = kwargs .pop ('klass' , animation .FuncAnimation )
38+ if 'frames' not in kwargs :
39+ kwargs ['frames' ] = 5
40+ return klass (fig = fig , func = animate , init_func = init , ** kwargs )
41+
42+
1643class NullMovieWriter (animation .AbstractMovieWriter ):
1744 """
1845 A minimal MovieWriter. It doesn't actually write anything.
@@ -40,26 +67,8 @@ def finish(self):
4067 pass
4168
4269
43- def make_animation (** kwargs ):
44- fig , ax = plt .subplots ()
45- line , = ax .plot ([])
46-
47- def init ():
48- pass
49-
50- def animate (i ):
51- line .set_data ([0 , 1 ], [0 , i ])
52- return line ,
53-
54- return animation .FuncAnimation (fig , animate , ** kwargs )
55-
56-
57- def test_null_movie_writer ():
70+ def test_null_movie_writer (anim ):
5871 # Test running an animation with NullMovieWriter.
59-
60- num_frames = 5
61- anim = make_animation (frames = num_frames )
62-
6372 filename = "unused.null"
6473 dpi = 50
6574 savefig_kwargs = dict (foo = 0 )
@@ -68,17 +77,17 @@ def test_null_movie_writer():
6877 anim .save (filename , dpi = dpi , writer = writer ,
6978 savefig_kwargs = savefig_kwargs )
7079
71- assert writer .fig == plt .figure (1 ) # The figure used by make_animation.
80+ assert writer .fig == plt .figure (1 ) # The figure used by anim fixture
7281 assert writer .outfile == filename
7382 assert writer .dpi == dpi
7483 assert writer .args == ()
7584 assert writer .savefig_kwargs == savefig_kwargs
76- assert writer ._count == num_frames
77-
85+ assert writer ._count == anim .save_count
7886
79- def test_animation_delete ():
80- anim = make_animation (frames = 5 )
8187
88+ @pytest .mark .parametrize ('anim' , [dict (klass = dict )], indirect = ['anim' ])
89+ def test_animation_delete (anim ):
90+ anim = animation .FuncAnimation (** anim )
8291 with pytest .warns (Warning , match = 'Animation was deleted' ):
8392 del anim
8493 gc .collect ()
@@ -140,44 +149,62 @@ def isAvailable(cls):
140149# design more sophisticated tests which compare resulting frames a-la
141150# matplotlib.testing.image_comparison
142151@pytest .mark .parametrize ('writer, output' , WRITER_OUTPUT )
143- def test_save_animation_smoketest (tmpdir , writer , output ):
152+ @pytest .mark .parametrize ('anim' , [dict (klass = dict )], indirect = ['anim' ])
153+ def test_save_animation_smoketest (tmpdir , writer , output , anim ):
144154 if not animation .writers .is_available (writer ):
145155 pytest .skip ("writer '%s' not available on this system" % writer )
146- fig , ax = plt .subplots ()
147- line , = ax .plot ([], [])
148-
149- ax .set_xlim (0 , 10 )
150- ax .set_ylim (- 1 , 1 )
151156
157+ anim = animation .FuncAnimation (** anim )
152158 dpi = None
153159 codec = None
154160 if writer == 'ffmpeg' :
155161 # Issue #8253
156- fig .set_size_inches ((10.85 , 9.21 ))
162+ anim . _fig .set_size_inches ((10.85 , 9.21 ))
157163 dpi = 100.
158164 codec = 'h264'
159165
160- def init ():
161- line .set_data ([], [])
162- return line ,
163-
164- def animate (i ):
165- x = np .linspace (0 , 10 , 100 )
166- y = np .sin (x + i )
167- line .set_data (x , y )
168- return line ,
169-
170166 # Use temporary directory for the file-based writers, which produce a file
171167 # per frame with known names.
172168 with tmpdir .as_cwd ():
173- anim = animation .FuncAnimation (fig , animate , init_func = init , frames = 5 )
174169 anim .save (output , fps = 30 , writer = writer , bitrate = 500 , dpi = dpi ,
175170 codec = codec )
171+ with pytest .warns (None ):
172+ del anim
176173
177174
178- def test_no_length_frames ():
179- (make_animation (frames = iter (range (5 )))
180- .save ('unused.null' , writer = NullMovieWriter ()))
175+ @pytest .mark .parametrize ('writer' , [
176+ pytest .param (
177+ 'ffmpeg' , marks = pytest .mark .skipif (
178+ not animation .FFMpegWriter .isAvailable (),
179+ reason = 'Requires FFMpeg' )),
180+ pytest .param (
181+ 'imagemagick' , marks = pytest .mark .skipif (
182+ not animation .ImageMagickWriter .isAvailable (),
183+ reason = 'Requires ImageMagick' )),
184+ ])
185+ @pytest .mark .parametrize ('html, want' , [
186+ ('none' , None ),
187+ ('html5' , '<video width' ),
188+ ('jshtml' , '<script ' )
189+ ])
190+ @pytest .mark .parametrize ('anim' , [dict (klass = dict )], indirect = ['anim' ])
191+ def test_animation_repr_html (writer , html , want , anim ):
192+ # create here rather than in the fixture otherwise we get __del__ warnings
193+ # about producing no output
194+ anim = animation .FuncAnimation (** anim )
195+ with plt .rc_context ({'animation.writer' : writer ,
196+ 'animation.html' : html }):
197+ html = anim ._repr_html_ ()
198+ if want is None :
199+ assert html is None
200+ else :
201+ assert want in html
202+
203+
204+ @pytest .mark .parametrize ('anim' , [dict (frames = iter (range (5 )))],
205+ indirect = ['anim' ])
206+ def test_no_length_frames (anim ):
207+ anim .save ('unused.null' , writer = NullMovieWriter ())
181208
182209
183210def test_movie_writer_registry ():
@@ -196,11 +223,12 @@ def test_movie_writer_registry():
196223 not animation .writers .is_available (mpl .rcParams ["animation.writer" ]),
197224 reason = "animation writer not installed" )),
198225 "to_jshtml" ])
199- def test_embed_limit (method_name , caplog , tmpdir ):
226+ @pytest .mark .parametrize ('anim' , [dict (frames = 1 )], indirect = ['anim' ])
227+ def test_embed_limit (method_name , caplog , tmpdir , anim ):
200228 caplog .set_level ("WARNING" )
201229 with tmpdir .as_cwd ():
202230 with mpl .rc_context ({"animation.embed_limit" : 1e-6 }): # ~1 byte.
203- getattr (make_animation ( frames = 1 ) , method_name )()
231+ getattr (anim , method_name )()
204232 assert len (caplog .records ) == 1
205233 record , = caplog .records
206234 assert (record .name == "matplotlib.animation"
@@ -213,14 +241,15 @@ def test_embed_limit(method_name, caplog, tmpdir):
213241 not animation .writers .is_available (mpl .rcParams ["animation.writer" ]),
214242 reason = "animation writer not installed" )),
215243 "to_jshtml" ])
216- def test_cleanup_temporaries (method_name , tmpdir ):
244+ @pytest .mark .parametrize ('anim' , [dict (frames = 1 )], indirect = ['anim' ])
245+ def test_cleanup_temporaries (method_name , tmpdir , anim ):
217246 with tmpdir .as_cwd ():
218- getattr (make_animation ( frames = 1 ) , method_name )()
247+ getattr (anim , method_name )()
219248 assert list (Path (str (tmpdir )).iterdir ()) == []
220249
221250
222251@pytest .mark .skipif (os .name != "posix" , reason = "requires a POSIX OS" )
223- def test_failing_ffmpeg (tmpdir , monkeypatch ):
252+ def test_failing_ffmpeg (tmpdir , monkeypatch , anim ):
224253 """
225254 Test that we correctly raise a CalledProcessError when ffmpeg fails.
226255
@@ -235,7 +264,7 @@ def test_failing_ffmpeg(tmpdir, monkeypatch):
235264 "[[ $@ -eq 0 ]]\n " )
236265 os .chmod (str (exe_path ), 0o755 )
237266 with pytest .raises (subprocess .CalledProcessError ):
238- make_animation () .save ("test.mpeg" )
267+ anim .save ("test.mpeg" )
239268
240269
241270@pytest .mark .parametrize ("cache_frame_data" , [False , True ])
0 commit comments