from tests import unittest


from botocore.retries import throttling


class TestCubicCalculator(unittest.TestCase):
    def create_cubic_calculator(self, starting_max_rate=10, beta=0.7,
                                scale_constant=0.4):
        return throttling.CubicCalculator(starting_max_rate=starting_max_rate,
                                          scale_constant=scale_constant,
                                          start_time=0, beta=beta)

    # For these tests, rather than duplicate the formulas in the tests,
    # I want to check against a fixed set of inputs with by-hand verified
    # values to ensure we're doing the calculations correctly.

    def test_starting_params(self):
        cubic = self.create_cubic_calculator(starting_max_rate=10)
        self.assertAlmostEqual(
            cubic.get_params_snapshot().k, 1.9574338205844317
        )

    def test_success_responses_until_max_hit(self):
        # For this test we're interested in the behavior less so than
        # the specific numbers.  There's a few cases we care about:
        #
        cubic = self.create_cubic_calculator(starting_max_rate=10)
        params = cubic.get_params_snapshot()
        start_k = params.k
        start_w_max = params.w_max
        # Before we get to t == start_k, our throttle is below our
        # max w_max
        assertLessEqual = self.assertLessEqual
        assertLessEqual(cubic.success_received(start_k / 3.0), start_w_max)
        assertLessEqual(cubic.success_received(start_k / 2.0), start_w_max)
        assertLessEqual(cubic.success_received(start_k / 1.1), start_w_max)
        # At t == start_k, we should be at w_max.
        self.assertAlmostEqual(cubic.success_received(timestamp=start_k), 10.0)
        # And once we pass start_k, we'll be above w_max.
        self.assertGreaterEqual(
            cubic.success_received(start_k * 1.1), start_w_max)
        self.assertGreaterEqual(
            cubic.success_received(start_k * 2.0), start_w_max)

    def test_error_response_decreases_rate_by_beta(self):
        # This is the default value here so we're just being explicit.
        cubic = self.create_cubic_calculator(starting_max_rate=10, beta=0.7)

        # So let's say we made it up to 8 TPS before we were throttled.
        rate_when_throttled = 8
        new_rate = cubic.error_received(current_rate=rate_when_throttled,
                                        timestamp=1)
        self.assertAlmostEqual(new_rate, rate_when_throttled * 0.7)

        new_params = cubic.get_params_snapshot()
        self.assertEqual(
            new_params,
            throttling.CubicParams(w_max=rate_when_throttled,
                                   k=1.8171205928321397,
                                   last_fail=1)
        )

    def test_t_0_should_match_beta_decrease(self):
        # So if I have beta of 0.6
        cubic = self.create_cubic_calculator(starting_max_rate=10, beta=0.6)
        # When I get throttled I should decrease my rate by 60%.
        new_rate = cubic.error_received(current_rate=10, timestamp=1)
        self.assertEqual(new_rate, 6.0)
        # And my starting rate at time t=1 should start at that new rate.
        self.assertAlmostEqual(cubic.success_received(timestamp=1), 6.0)
