Source code for stepler.third_party.waiter
"""
------
Waiter
------
"""
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import functools
import sys
from hamcrest import assert_that
import six
import timeout_decorator
import waiting
from stepler.third_party import logger
@six.python_2_unicode_compatible
[docs]class ExpectationError(Exception):
"""Expectation error class."""
def __init__(self, base_ex):
self.base_ex = base_ex
def __str__(self):
return u"{}".format(self.base_ex)
@functools.wraps(assert_that)
def expect_that(*args, **kwargs):
"""Wrapper for hamcrest's ``assert_that``.
It raises ExpectationError instead of AssertionError and can be used with
wait function below to retrieve more verbose messages about predicates
failures.
"""
try:
assert_that(*args, **kwargs)
return True
except AssertionError as e:
six.reraise(ExpectationError, ExpectationError(e), sys.exc_info()[-1])
@six.python_2_unicode_compatible
[docs]class TimeoutExpired(Exception):
"""Timeout expired exception."""
def __init__(self, base_ex):
self.base_ex = base_ex
self.message = ''
def __str__(self):
return u"{}{}".format(self.base_ex, self.message)
[docs]class PredicateTimeout(Exception):
"""Predicate timeout exception class."""
@logger.log
[docs]def wait(predicate,
args=None,
kwargs=None,
expected_exceptions=(),
predicate_timeout=None,
**wait_kwargs):
"""Wait that predicate execution returns non-false result.
It catches all raised ExpectationError and uses last exception to construct
TimeoutException. It also can pass arguments to predicate.
Example:
>>> def predicate(foo, bar='baz'):
... expect_that(foo, equal_to(bar))
... return bar
>>> wait(predicate, args=(1,), kwargs={'bar': 2}, timeout_seconds=1)
TimeoutExpired: Timeout of 1 seconds expired waiting for
<function predicate at 0x7f7798622c08>
Expected: <2>
but: was <1>
>>> wait(lambda: False, timeout_seconds=0.5)
TimeoutExpired: Timeout of 0.5 seconds expired waiting for
<function <lambda> at 0x7f2b54360848>
No exception raised during predicate executing
Args:
predicate (function): predicate to wait execution result
timeout_seconds (int): seconds to wait result
sleep_seconds (float): polling time between predicate executions
multiplier (int): coefficient to multiply polling time
expected_exceptions (tuple): predicate exceptions which will be omitted
during waiting.
predicate_timeout (int): max predicate execution timeout. Equals to
timeout_seconds by default.
waiting_for (str): custom waiting message.
Returns:
tuple: result of predicate execution in format:
(predicate result, "Error message" or None)
Raises:
TimeoutExpired: if predicate execution has false value after timeout
"""
__tracebackhide__ = True
raised_exceptions = []
args = args or ()
kwargs = kwargs or {}
predicate_timeout = predicate_timeout or wait_kwargs.get('timeout_seconds',
60)
if isinstance(expected_exceptions, tuple):
expected_exceptions += (ExpectationError, PredicateTimeout)
elif (isinstance(expected_exceptions, type) and
issubclass(expected_exceptions, Exception)):
expected_exceptions = (expected_exceptions, ExpectationError,
PredicateTimeout)
else:
raise ValueError('expected_exceptions should be tuple or '
'Exception subclass')
@functools.wraps(predicate)
def wrapper():
try:
_predicate = timeout_decorator.timeout(
predicate_timeout,
timeout_exception=PredicateTimeout)(predicate)
return _predicate(*args, **kwargs)
except expected_exceptions as e:
raised_exceptions.append(e)
return False
try:
return waiting.wait(wrapper, **wait_kwargs)
except waiting.TimeoutExpired as e:
ex = TimeoutExpired(e)
if raised_exceptions:
ex.message += "\n{0}: {1}".format(
type(raised_exceptions[-1]).__name__, raised_exceptions[-1])
else:
ex.message += "\nNo exception raised during predicate executing"
raise ex