diff --git a/humanfriendly/terminal/spinners.py b/humanfriendly/terminal/spinners.py index e4dc55d..e1876da 100644 --- a/humanfriendly/terminal/spinners.py +++ b/humanfriendly/terminal/spinners.py @@ -98,6 +98,7 @@ # Modules included in our package. from humanfriendly import Timer +from humanfriendly.compat import monotonic from humanfriendly.deprecation import deprecated_args from humanfriendly.terminal import ANSI_ERASE_LINE @@ -166,7 +167,7 @@ def __init__(self, **options): self.total = options.get('total') # Define instance variables. self.counter = 0 - self.last_update = 0 + self.last_update = None # Try to automatically discover whether the stream is connected to # a terminal, but don't fail if no isatty() method is available. if self.interactive is None: @@ -199,8 +200,8 @@ def step(self, progress=0, label=None): 99% of the time. """ if self.interactive: - time_now = time.time() - if time_now - self.last_update >= self.interval: + time_now = monotonic() + if self.last_update is None or time_now - self.last_update >= self.interval: self.last_update = time_now state = self.states[self.counter % len(self.states)] label = label or self.label @@ -226,7 +227,7 @@ def sleep(self): This method doesn't redraw the spinner, you still have to call :func:`step()` in order to do that. """ - time.sleep(MINIMUM_INTERVAL) + time.sleep(self.interval) def clear(self): """ diff --git a/humanfriendly/tests.py b/humanfriendly/tests.py index 72dad99..b5d6b37 100644 --- a/humanfriendly/tests.py +++ b/humanfriendly/tests.py @@ -797,6 +797,31 @@ def test_spinner(self): self.assertTrue(all('%' in line for line in lines)) self.assertEqual(sorted(set(lines)), sorted(lines)) + def test_spinner_uses_elapsed_time(self): + """Keep redrawing after a wall-clock correction and at clock zero.""" + from humanfriendly.terminal import spinners + stream = StringIO() + clock = [0.0] + with PatchedAttribute(spinners, 'monotonic', lambda: clock[0]): + with PatchedAttribute(spinners.time, 'time', lambda: 1000 - clock[0]): + spinner = Spinner(label='working', stream=stream, interactive=True, interval=1) + spinner.step() + assert spinner.counter == 1 + clock[0] = 0.5 + spinner.step() + assert spinner.counter == 1 + clock[0] = 1.0 + spinner.step() + assert spinner.counter == 2 + + def test_spinner_sleep_uses_configured_interval(self): + """Sleep for the interval chosen for the spinner's redraw cadence.""" + from humanfriendly.terminal import spinners + sleep = MagicMock() + with PatchedAttribute(spinners.time, 'sleep', sleep): + Spinner(interval=0.7).sleep() + sleep.assert_called_once_with(0.7) + def test_automatic_spinner(self): """ Test :func:`humanfriendly.AutomaticSpinner`.