Skip to content

Commit 0fd4022

Browse files
committed
TST: Test number of draw events from a blitted animation
This adds a test that only one draw_event is called when using blitting with animations. There will be more than one draw_event if blitting is not implemented properly and instead calls a full redraw of the canvas each time.
1 parent 7a41642 commit 0fd4022

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

lib/matplotlib/tests/test_backends_interactive.py

+73
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,76 @@ def test_lazy_linux_headless():
319319
if proc.returncode:
320320
pytest.fail("The subprocess returned with non-zero exit status "
321321
f"{proc.returncode}.")
322+
323+
324+
def _test_number_of_draws_script():
325+
import matplotlib.pyplot as plt
326+
327+
fig, ax = plt.subplots()
328+
329+
# animated=True tells matplotlib to only draw the artist when we
330+
# explicitly request it
331+
ln, = ax.plot([0, 1], [1, 2], animated=True)
332+
333+
# make sure the window is raised, but the script keeps going
334+
plt.show(block=False)
335+
plt.pause(0.1)
336+
# Connect to draw_event to count the occurrences
337+
fig.canvas.mpl_connect('draw_event', print)
338+
339+
# get copy of entire figure (everything inside fig.bbox)
340+
# sans animated artist
341+
bg = fig.canvas.copy_from_bbox(fig.bbox)
342+
# draw the animated artist, this uses a cached renderer
343+
ax.draw_artist(ln)
344+
# show the result to the screen
345+
fig.canvas.blit(fig.bbox)
346+
347+
for j in range(10):
348+
# reset the background back in the canvas state, screen unchanged
349+
fig.canvas.restore_region(bg)
350+
# Create a **new** artist here, this is poor usage of blitting
351+
# but good for testing to make sure that this doesn't create
352+
# excessive draws
353+
ln, = ax.plot([0, 1], [1, 2])
354+
# render the artist, updating the canvas state, but not the screen
355+
ax.draw_artist(ln)
356+
# copy the image to the GUI state, but screen might not changed yet
357+
fig.canvas.blit(fig.bbox)
358+
# flush any pending GUI events, re-painting the screen if needed
359+
fig.canvas.flush_events()
360+
361+
# Let the event loop process everything before leaving
362+
plt.pause(0.1)
363+
364+
365+
_blit_backends = _get_testable_interactive_backends()
366+
for param in _blit_backends:
367+
backend = param.values[0]["MPLBACKEND"]
368+
if backend == "gtk3cairo":
369+
# copy_from_bbox only works when rendering to an ImageSurface
370+
param.marks.append(
371+
pytest.mark.skip("gtk3cairo does not support blitting"))
372+
elif backend == "wx":
373+
param.marks.append(
374+
pytest.mark.skip("wx does not support blitting"))
375+
376+
377+
@pytest.mark.parametrize("env", _blit_backends)
378+
# subprocesses can struggle to get the display, so rerun a few times
379+
@pytest.mark.flaky(reruns=4)
380+
def test_blitting_events(env):
381+
proc = subprocess.run(
382+
[sys.executable, "-c",
383+
inspect.getsource(_test_number_of_draws_script)
384+
+ "\n_test_number_of_draws_script()"],
385+
env={**os.environ, "SOURCE_DATE_EPOCH": "0", **env},
386+
timeout=_test_timeout,
387+
stdout=subprocess.PIPE, universal_newlines=True)
388+
389+
# Count the number of draw_events we got. We could count some initial
390+
# canvas draws (which vary in number by backend), but the critical
391+
# check here is that it isn't 10 draws, which would be called if
392+
# blitting is not properly implemented
393+
ndraws = proc.stdout.count("DrawEvent")
394+
assert 0 < ndraws < 5

0 commit comments

Comments
 (0)