Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7f40ba5

Browse filesBrowse files
committed
TST: Add interactive timer tests
This adds more robust interactive timer tests to assert against some of the discrepencies that were found in testing. - Run loop shouldn't depend on callback time - Slow callbacks shouldn't cause a timer to drift over time, it should continually fire at the requested cadence - When start() is called again it should invalidate the previous timer associated with that Timer object
1 parent e0c484d commit 7f40ba5
Copy full SHA for 7f40ba5

File tree

1 file changed

+29
-18
lines changed
Filter options

1 file changed

+29
-18
lines changed

‎lib/matplotlib/tests/test_backends_interactive.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_backends_interactive.py
+29-18Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -624,14 +624,12 @@ def _impl_test_interactive_timers():
624624
# NOTE: We run the timer tests in parallel to avoid longer sequential
625625
# delays which adds to the testing time. Add new tests to one of
626626
# the current event loop iterations if possible.
627+
import time
627628
from unittest.mock import Mock
628629
import matplotlib.pyplot as plt
629630

630631
fig = plt.figure()
631-
event_loop_time = 1 # in seconds
632-
expected_200ms_calls = int(event_loop_time / 0.2)
633-
634-
# Start at 2s interval (would only get one firing), then update to 200ms
632+
# Start at 2s interval (wouldn't get any firings), then update to 100ms
635633
timer_repeating = fig.canvas.new_timer(2000)
636634
mock_repeating = Mock()
637635
timer_repeating.add_callback(mock_repeating)
@@ -642,42 +640,55 @@ def _impl_test_interactive_timers():
642640

643641
timer_repeating.start()
644642
# Test updating the interval updates a running timer
645-
timer_repeating.interval = 200
643+
timer_repeating.interval = 100
646644
# Start as a repeating timer then change to singleshot via the attribute
647645
timer_single_shot.start()
648646
timer_single_shot.single_shot = True
649647

650-
fig.canvas.start_event_loop(event_loop_time)
651-
assert 1 < mock_repeating.call_count <= expected_200ms_calls + 1, \
652-
f"Interval update: Expected between 2 and {expected_200ms_calls + 1} calls, " \
653-
f"got {mock_repeating.call_count}"
648+
fig.canvas.start_event_loop(0.5)
649+
assert 2 <= mock_repeating.call_count <= 5, \
650+
f"Interval update: Expected 2-5 calls, got {mock_repeating.call_count}"
654651
assert mock_single_shot.call_count == 1, \
655652
f"Singleshot: Expected 1 call, got {mock_single_shot.call_count}"
656653

657-
# 200ms timer triggers and the callback takes 100ms to run
658-
# Test that we don't drift and that we get called on every 200ms
659-
# interval and not every 300ms
660-
mock_repeating.side_effect = lambda: time.sleep(0.1)
654+
# 250ms timer triggers and the callback takes 150ms to run
655+
# Test that we don't drift and that we get called on every 250ms
656+
# firing and not every 400ms
657+
timer_repeating.interval = 250
658+
mock_repeating.side_effect = lambda: time.sleep(0.15)
659+
# calling start() again on a repeating timer should remove the old
660+
# one, so we don't want double the number of calls here either because
661+
# two timers are potentially running.
662+
timer_repeating.start()
661663
mock_repeating.call_count = 0
662664
# Make sure we can start the timer after stopping a singleshot timer
663665
timer_single_shot.stop()
664666
timer_single_shot.start()
665667

668+
event_loop_time = 2 # in seconds
669+
expected_calls = int(event_loop_time / (timer_repeating.interval / 1000))
670+
671+
t_start = time.perf_counter()
666672
fig.canvas.start_event_loop(event_loop_time)
667-
# Not exact timers, so add a little slop. We really want to make sure we are
668-
# getting more than 3 (every 300ms).
669-
assert mock_repeating.call_count >= expected_200ms_calls - 1, \
670-
f"Slow callback: Expected at least {expected_200ms_calls - 1} calls, " \
673+
t_loop = time.perf_counter() - t_start
674+
# Should be around 2s, but allow for some slop on CI. We want to make sure
675+
# we aren't getting 2 + (callback time) 0.5s/iteration, which would be 4+ s.
676+
assert 1.8 < t_loop < 3, \
677+
f"Event loop: Expected to run for around 2s, but ran for {t_loop:.2f}s"
678+
# Not exact timers, so add some slop. (Quite a bit for CI resources)
679+
assert abs(mock_repeating.call_count - expected_calls) <= 2, \
680+
f"Slow callback: Expected {expected_calls} calls, " \
671681
f"got {mock_repeating.call_count}"
672682
assert mock_single_shot.call_count == 2, \
673683
f"Singleshot: Expected 2 calls, got {mock_single_shot.call_count}"
674-
plt.close("all")
675684

676685

677686
@pytest.mark.parametrize("env", _get_testable_interactive_backends())
678687
def test_interactive_timers(env):
679688
if env["MPLBACKEND"] == "wx":
680689
pytest.skip("wx backend is deprecated; tests failed on appveyor")
690+
if env["MPLBACKEND"].startswith("gtk3") and is_ci_environment():
691+
pytest.xfail("GTK3 backend timer is slow on CI resources")
681692
_run_helper(_impl_test_interactive_timers,
682693
timeout=_test_timeout, extra_env=env)
683694

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.