Example #1
0
def test_while_until_true_invoke_inline(mock_time_sleep):
    """while_until_true with dynamic invocation."""
    mock = MagicMock()
    mock.side_effect = [
        'test string 1', 'test string 2', 'test string 3', 'expected value',
        'test string 5'
    ]

    actual_counter = 0

    def decorate_me(counter, arg1, arg2):
        """Test static decorator syntax."""
        assert arg1 == 'v1'
        assert arg2 == 'v2'
        nonlocal actual_counter
        actual_counter += 1
        assert actual_counter == counter
        if mock(arg1) == 'expected value':
            return True
        else:
            return False

    assert poll.while_until_true(interval=0.01,
                                 max_attempts=10)(decorate_me)('v1', 'v2')
    assert mock.call_count == 4
    mock.assert_called_with('v1')
    assert mock_time_sleep.call_count == 3
    mock_time_sleep.assert_called_with(0.01)
Example #2
0
def test_while_until_true_once_found(mock_time_sleep):
    """wait_until_true max_attempts 1."""
    mock = MagicMock()
    mock.side_effect = [
        'expected value',
        'test string 2',
    ]

    actual_counter = 0

    def decorate_me(counter, arg1, arg2):
        """Test static decorator syntax."""
        assert arg1 == 'v1'
        assert arg2 == 'v2'
        nonlocal actual_counter
        actual_counter += 1
        assert actual_counter == counter
        if mock(arg1) == 'expected value':
            return True
        else:
            return False

    assert poll.while_until_true(interval=0.01,
                                 max_attempts=1)(decorate_me)('v1', 'v2')
    mock.assert_called_once_with('v1')
    mock_time_sleep.assert_not_called()
Example #3
0
def test_while_until_true_no_max(mock_time_sleep):
    """while_until_true with dynamic invocation, infinite (max is None)."""
    mock = MagicMock()
    mock.side_effect = [
        'test string 1',
        'test string 2',
        'test string 3',
        'test string 4',
        'test string 5',
        'test string 6',
        'test string 7',
        'test string 8',
        'test string 9',
        'test string 10',
        'test string 11',
    ]

    actual_counter = 0

    def decorate_me(counter, arg1, arg2):
        """Test static decorator syntax"""
        assert arg1 == 'v1'
        assert arg2 == 'v2'
        nonlocal actual_counter
        actual_counter += 1
        assert actual_counter == counter
        out = mock(arg1)
        assert out == f'test string {counter}'
        if out == 'test string 11':
            return True
        else:
            return False

    logger = logging.getLogger('pypyr.utils.poll')
    with patch.object(logger, 'debug') as mock_logger_debug:
        assert (poll.while_until_true(interval=0.01,
                                      max_attempts=None)(decorate_me)('v1',
                                                                      'v2'))
    assert mock_logger_debug.mock_calls == [
        call('started'),
        call('Looping every 0.01 seconds.'),
        call('iteration 1. Still waiting. . .'),
        call('iteration 2. Still waiting. . .'),
        call('iteration 3. Still waiting. . .'),
        call('iteration 4. Still waiting. . .'),
        call('iteration 5. Still waiting. . .'),
        call('iteration 6. Still waiting. . .'),
        call('iteration 7. Still waiting. . .'),
        call('iteration 8. Still waiting. . .'),
        call('iteration 9. Still waiting. . .'),
        call('iteration 10. Still waiting. . .'),
        call('iteration 11. Desired state reached.'),
        call('done')
    ]

    assert mock.call_count == 11
    mock.assert_called_with('v1')
    assert mock_time_sleep.call_count == 10
    mock_time_sleep.assert_called_with(0.01)
Example #4
0
    def retry_loop(self, context, step_method):
        """Run step inside a retry loop.

        Args:
            context: (pypyr.context.Context) The pypyr context. This arg will
                     mutate - after method execution will contain the new
                     updated context.
            step_method: (method/function) This is the method/function that
                         will execute on every loop iteration. Signature is:
                         function(context)

        """
        logger.debug("starting")

        context['retryCounter'] = 0
        self.retry_counter = 0

        sleep = context.get_formatted_value(self.sleep)
        backoff_name = context.get_formatted_value(
            self.backoff) if self.backoff else 'fixed'

        max_sleep = None
        if self.sleep_max:
            max_sleep = context.get_formatted_as_type(self.sleep_max,
                                                      out_type=float)

        jrc = context.get_formatted_value(self.jrc)
        backoff_args = context.get_formatted_value(self.backoff_args)

        backoff_callable = backoff_cache.get_backoff(backoff_name)(
            sleep=sleep, max_sleep=max_sleep, jrc=jrc, kwargs=backoff_args)

        if self.max:
            max = context.get_formatted_as_type(self.max, out_type=int)

            logger.info(
                "retry decorator will try %d times with %s backoff starting "
                "at %ss intervals.", max, backoff_name, sleep)
        else:
            max = None
            logger.info(
                "retry decorator will try indefinitely with %s backoff "
                "starting at %ss intervals.", backoff_name, sleep)

        # this will never be false. because on (counter == max) exec_iteration
        # raises an exception, breaking out of the loop.
        is_retry_ok = poll.while_until_true(
            interval=backoff_callable,
            max_attempts=max)(self.exec_iteration)(context=context,
                                                   step_method=step_method,
                                                   max=max)
        assert is_retry_ok
        logger.debug("retry loop complete, reporting success.")

        logger.debug("done")
Example #5
0
def test_while_until_true_callable_with_max_attempts(mock_sleep):
    """Interval calculated from callable per iteration with max set."""
    decorate_me = MagicMock()
    decorate_me.side_effect = [False, False, True, False]

    assert poll.while_until_true(interval=arb_callable,
                                 max_attempts=3)(decorate_me)('arg1', 'arg2')

    assert decorate_me.mock_calls == [call(1, 'arg1', 'arg2'),
                                      call(2, 'arg1', 'arg2'),
                                      call(3, 'arg1', 'arg2')]

    assert mock_sleep.mock_calls == [call(2), call(4)]
Example #6
0
    def retry_loop(self, context, step_method):
        """Run step inside a retry loop.

        Args:
            context: (pypyr.context.Context) The pypyr context. This arg will
                     mutate - after method execution will contain the new
                     updated context.
            step_method: (method/function) This is the method/function that
                         will execute on every loop iteration. Signature is:
                         function(context)

        """
        logger.debug("starting")

        context['retryCounter'] = 0
        self.retry_counter = 0

        sleep = context.get_formatted_as_type(self.sleep, out_type=float)
        if self.max:
            max = context.get_formatted_as_type(self.max, out_type=int)

            logger.info(
                "retry decorator will try %d times at %ss "
                "intervals.", max, sleep)
        else:
            max = None
            logger.info(
                "retry decorator will try indefinitely at %ss "
                "intervals.", sleep)

        # this will never be false. because on counter == max,
        # exec_iteration raises an exception, breaking out of the loop.
        # pragma because cov doesn't know the implied else is impossible.
        # unit test cov is 100%, though.
        if poll.while_until_true(interval=sleep, max_attempts=max)(
                self.exec_iteration)(
                    context=context,
                    step_method=step_method):  # pragma: no cover
            logger.debug("retry loop complete, reporting success.")

        logger.debug("retry loop done")

        logger.debug("done")
Example #7
0
def test_while_until_true_with_exhaust(mock_time_sleep):
    """while_until_true with dynamic invocation, exhaust wait attempts."""
    mock = MagicMock()
    mock.side_effect = [
        'test string 1',
        'test string 2',
        'test string 3',
        'test string 4',
        'test string 5',
        'test string 6',
        'test string 7',
        'test string 8',
        'test string 9',
        'test string 10',
        'test string 11',
    ]

    actual_counter = 0

    def decorate_me(counter, arg1, arg2):
        """Test static decorator syntax."""
        assert arg1 == 'v1'
        assert arg2 == 'v2'
        nonlocal actual_counter
        actual_counter += 1
        assert actual_counter == counter
        out = mock(arg1)
        assert out == f'test string {counter}'
        if out == 'expected value':
            return True
        else:
            return False

    assert not poll.while_until_true(interval=0.01,
                                     max_attempts=10)(decorate_me)('v1', 'v2')
    assert mock.call_count == 10
    mock.assert_called_with('v1')
    assert mock_time_sleep.call_count == 9
    mock_time_sleep.assert_called_with(0.01)
Example #8
0
def test_while_until_true_max_exhaust(mock_time_sleep):
    """while_until_true with dynamic invocation, exhaust max."""
    mock = MagicMock()
    mock.side_effect = [
        'test string 1',
        'test string 2',
        'test string 3',
    ]

    actual_counter = 0

    def decorate_me(counter, arg1, arg2):
        """Test static decorator syntax"""
        assert arg1 == 'v1'
        assert arg2 == 'v2'
        nonlocal actual_counter
        actual_counter += 1
        assert actual_counter == counter
        out = mock(arg1)
        assert out == f'test string {counter}'
        return False

    logger = logging.getLogger('pypyr.utils.poll')
    with patch.object(logger, 'debug') as mock_logger_debug:
        assert not (poll.while_until_true(
            interval=0.01, max_attempts=3)(decorate_me)('v1', 'v2'))
    assert mock_logger_debug.mock_calls == [
        call('started'),
        call('Looping every 0.01 seconds for 3 attempts'),
        call('iteration 1. Still waiting. . .'),
        call('iteration 2. Still waiting. . .'),
        call('iteration 3. Max attempts exhausted.'),
        call('done')
    ]

    assert mock.call_count == 3
    mock.assert_called_with('v1')
    assert mock_time_sleep.call_count == 2
    mock_time_sleep.assert_called_with(0.01)
Example #9
0
    def while_loop(self, context, step_method):
        """Run step inside a while loop.

        Args:
            context: (pypyr.context.Context) The pypyr context. This arg will
                     mutate - after method execution will contain the new
                     updated context.
            step_method: (method/function) This is the method/function that
                         will execute on every loop iteration. Signature is:
                         function(context)

        """
        logger.debug("starting")

        context['whileCounter'] = 0
        self.while_counter = 0

        if self.stop is None and self.max is None:
            # the ctor already does this check, but guess theoretically
            # consumer could have messed with the props since ctor
            logger.error("while decorator missing both max and stop.")
            raise PipelineDefinitionError("the while decorator must have "
                                          "either max or stop, or both. "
                                          "But not neither.")

        error_on_max = context.get_formatted_as_type(self.error_on_max,
                                                     out_type=bool)
        sleep = context.get_formatted_as_type(self.sleep, out_type=float)
        if self.max is None:
            max = None
            logger.info(
                "while decorator will loop until %s "
                "evaluates to True at %ss intervals.", self.stop, sleep)
        else:
            max = context.get_formatted_as_type(self.max, out_type=int)

            if max < 1:
                logger.info("max %s is %s. while only runs when max > 0.",
                            self.max, max)
                logger.debug("done")
                return

            if self.stop is None:
                logger.info(
                    "while decorator will loop %s times at "
                    "%ss intervals.", max, sleep)
            else:
                logger.info(
                    "while decorator will loop %s times, or "
                    "until %s evaluates to True at "
                    "%ss intervals.", max, self.stop, sleep)

        if not poll.while_until_true(interval=sleep, max_attempts=max)(
                self.exec_iteration)(context=context, step_method=step_method):
            # False means loop exhausted and stop never eval-ed True.
            if error_on_max:
                logger.error(
                    "exhausted %s iterations of while loop, "
                    "and errorOnMax is True.", max)
                if self.stop and max:
                    raise LoopMaxExhaustedError("while loop reached "
                                                f"{max} and {self.stop} "
                                                "never evaluated to True.")
                else:
                    raise LoopMaxExhaustedError(f"while loop reached {max}.")
            else:
                if self.stop and max:
                    logger.info(
                        "while decorator looped %s times, "
                        "and %s never evaluated to True.", max, self.stop)

            logger.debug("while loop done")
        else:
            logger.info(
                "while loop done, stop condition %s "
                "evaluated True.", self.stop)

        logger.debug("done")