Bug 1293614 - Ensure that Wait().until() aligns as best as possible to the original interval sequence.
MozReview-Commit-ID: 2JEQtLEsdux
--- a/testing/marionette/client/marionette_driver/wait.py
+++ b/testing/marionette/client/marionette_driver/wait.py
@@ -49,18 +49,20 @@ class Wait(object):
:param timeout: How long to wait for the evaluated condition
to become true. The default timeout is the `timeout`
property on the `Marionette` object if set, or
`wait.DEFAULT_TIMEOUT`.
:param interval: How often the condition should be evaluated.
In reality the interval may be greater as the cost of
- evaluating the condition function is not factored in. The
- default polling interval is `wait.DEFAULT_INTERVAL`.
+ evaluating the condition function. If that is not the case the
+ interval for the next condition function call is shortend to keep
+ the original interval sequence as best as possible.
+ The default polling interval is `wait.DEFAULT_INTERVAL`.
:param ignored_exceptions: Ignore specific types of exceptions
whilst waiting for the condition. Any exceptions not
whitelisted will be allowed to propagate, terminating the
wait.
:param clock: Allows overriding the use of the runtime's
default time library. See `wait.SystemClock` for
@@ -115,30 +117,35 @@ class Wait(object):
rv = None
last_exc = None
until = is_true or until_pred
start = self.clock.now
while not until(self.clock, self.end):
try:
+ next = self.clock.now + self.interval
rv = condition(self.marionette)
except (KeyboardInterrupt, SystemExit):
raise
except self.exceptions:
last_exc = sys.exc_info()
+ # Re-adjust the interval depending on how long the callback
+ # took to evaluate the condition
+ interval_new = max(next - self.clock.now, 0)
+
if not rv:
- self.clock.sleep(self.interval)
+ self.clock.sleep(interval_new)
continue
if rv is not None:
return rv
- self.clock.sleep(self.interval)
+ self.clock.sleep(interval_new)
if message:
message = " with message: %s" % message
raise errors.TimeoutException(
"Timed out after %s seconds%s" %
(round((self.clock.now - start), 1), message if message else ""),
cause=last_exc)
--- a/testing/marionette/harness/marionette/tests/unit/test_wait.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_wait.py
@@ -12,18 +12,19 @@ from marionette_driver.wait import Wait
from marionette import MarionetteTestCase
class TickingClock(object):
def __init__(self, incr=1):
self.ticks = 0
self.increment = incr
- def sleep(self, dur):
- self.ticks += self.increment
+ def sleep(self, dur=None):
+ dur = dur if dur is not None else self.increment
+ self.ticks += dur
@property
def now(self):
return self.ticks
class SequenceClock(object):
def __init__(self, times):
self.times = times
@@ -261,16 +262,50 @@ class WaitUntilTest(MarionetteTestCase):
self.wt.until(lambda x: x.true(wait=4), is_true=at_third_attempt)
def test_timeout_elapsed_rounding(self):
wt = Wait(self.m, clock=SequenceClock([1, 0.01, 1]), timeout=0)
with self.assertRaisesRegexp(errors.TimeoutException,
"Timed out after 1.0 seconds"):
wt.until(lambda x: x.true(), is_true=now)
+ def test_timeout_elapsed_interval_by_delayed_condition_return(self):
+ def callback(mn):
+ self.clock.sleep(11)
+ return mn.false()
+
+ with self.assertRaisesRegexp(errors.TimeoutException,
+ "Timed out after 11.0 seconds"):
+ self.wt.until(callback)
+ # With a delayed conditional return > timeout, only 1 iteration is possible
+ self.assertEqual(self.m.waited, 1)
+
+ def test_timeout_with_delayed_condition_return(self):
+ def callback(mn):
+ self.clock.sleep(.5)
+ return mn.false()
+
+ with self.assertRaisesRegexp(errors.TimeoutException,
+ "Timed out after 10.0 seconds"):
+ self.wt.until(callback)
+ # With a delayed conditional return < interval, 10 iterations should be possible
+ self.assertEqual(self.m.waited, 10)
+
+ def test_timeout_interval_shorter_than_delayed_condition_return(self):
+ def callback(mn):
+ self.clock.sleep(2)
+ return mn.false()
+
+ with self.assertRaisesRegexp(errors.TimeoutException,
+ "Timed out after 10.0 seconds"):
+ self.wt.until(callback)
+ # With a delayed return of the conditional which takes twice that long than the interval,
+ # half of the iterations should be possible
+ self.assertEqual(self.m.waited, 5)
+
def test_message(self):
self.wt.exceptions = (TypeError,)
exc = None
try:
self.wt.until(lambda x: x.exception(e=TypeError), message="hooba")
except errors.TimeoutException as e:
exc = e