You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
311 lines
11 KiB
311 lines
11 KiB
// Copyright 2015 The Chromium OS Authors. All rights reserved. |
|
// Use of this source code is governed by a BSD-style license that can be |
|
// found in the LICENSE file. |
|
|
|
#include <brillo/backoff_entry.h> |
|
#include <gtest/gtest.h> |
|
|
|
using base::TimeDelta; |
|
using base::TimeTicks; |
|
|
|
namespace brillo { |
|
|
|
BackoffEntry::Policy base_policy = { 0, 1000, 2.0, 0.0, 20000, 2000, false }; |
|
|
|
class TestBackoffEntry : public BackoffEntry { |
|
public: |
|
explicit TestBackoffEntry(const Policy* const policy) |
|
: BackoffEntry(policy), |
|
now_(TimeTicks()) { |
|
// Work around initialization in constructor not picking up |
|
// fake time. |
|
SetCustomReleaseTime(TimeTicks()); |
|
} |
|
|
|
~TestBackoffEntry() override {} |
|
|
|
TimeTicks ImplGetTimeNow() const override { return now_; } |
|
|
|
void set_now(const TimeTicks& now) { |
|
now_ = now; |
|
} |
|
|
|
private: |
|
TimeTicks now_; |
|
|
|
DISALLOW_COPY_AND_ASSIGN(TestBackoffEntry); |
|
}; |
|
|
|
TEST(BackoffEntryTest, BaseTest) { |
|
TestBackoffEntry entry(&base_policy); |
|
EXPECT_FALSE(entry.ShouldRejectRequest()); |
|
EXPECT_EQ(TimeDelta(), entry.GetTimeUntilRelease()); |
|
|
|
entry.InformOfRequest(false); |
|
EXPECT_TRUE(entry.ShouldRejectRequest()); |
|
EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); |
|
} |
|
|
|
TEST(BackoffEntryTest, CanDiscardNeverExpires) { |
|
BackoffEntry::Policy never_expires_policy = base_policy; |
|
never_expires_policy.entry_lifetime_ms = -1; |
|
TestBackoffEntry never_expires(&never_expires_policy); |
|
EXPECT_FALSE(never_expires.CanDiscard()); |
|
never_expires.set_now(TimeTicks() + TimeDelta::FromDays(100)); |
|
EXPECT_FALSE(never_expires.CanDiscard()); |
|
} |
|
|
|
TEST(BackoffEntryTest, CanDiscard) { |
|
TestBackoffEntry entry(&base_policy); |
|
// Because lifetime is non-zero, we shouldn't be able to discard yet. |
|
EXPECT_FALSE(entry.CanDiscard()); |
|
|
|
// Test the "being used" case. |
|
entry.InformOfRequest(false); |
|
EXPECT_FALSE(entry.CanDiscard()); |
|
|
|
// Test the case where there are errors but we can time out. |
|
entry.set_now( |
|
entry.GetReleaseTime() + TimeDelta::FromMilliseconds(1)); |
|
EXPECT_FALSE(entry.CanDiscard()); |
|
entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds( |
|
base_policy.maximum_backoff_ms + 1)); |
|
EXPECT_TRUE(entry.CanDiscard()); |
|
|
|
// Test the final case (no errors, dependent only on specified lifetime). |
|
entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds( |
|
base_policy.entry_lifetime_ms - 1)); |
|
entry.InformOfRequest(true); |
|
EXPECT_FALSE(entry.CanDiscard()); |
|
entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds( |
|
base_policy.entry_lifetime_ms)); |
|
EXPECT_TRUE(entry.CanDiscard()); |
|
} |
|
|
|
TEST(BackoffEntryTest, CanDiscardAlwaysDelay) { |
|
BackoffEntry::Policy always_delay_policy = base_policy; |
|
always_delay_policy.always_use_initial_delay = true; |
|
always_delay_policy.entry_lifetime_ms = 0; |
|
|
|
TestBackoffEntry entry(&always_delay_policy); |
|
|
|
// Because lifetime is non-zero, we shouldn't be able to discard yet. |
|
entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000)); |
|
EXPECT_TRUE(entry.CanDiscard()); |
|
|
|
// Even with no failures, we wait until the delay before we allow discard. |
|
entry.InformOfRequest(true); |
|
EXPECT_FALSE(entry.CanDiscard()); |
|
|
|
// Wait until the delay expires, and we can discard the entry again. |
|
entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(1000)); |
|
EXPECT_TRUE(entry.CanDiscard()); |
|
} |
|
|
|
TEST(BackoffEntryTest, CanDiscardNotStored) { |
|
BackoffEntry::Policy no_store_policy = base_policy; |
|
no_store_policy.entry_lifetime_ms = 0; |
|
TestBackoffEntry not_stored(&no_store_policy); |
|
EXPECT_TRUE(not_stored.CanDiscard()); |
|
} |
|
|
|
TEST(BackoffEntryTest, ShouldIgnoreFirstTwo) { |
|
BackoffEntry::Policy lenient_policy = base_policy; |
|
lenient_policy.num_errors_to_ignore = 2; |
|
|
|
BackoffEntry entry(&lenient_policy); |
|
|
|
entry.InformOfRequest(false); |
|
EXPECT_FALSE(entry.ShouldRejectRequest()); |
|
|
|
entry.InformOfRequest(false); |
|
EXPECT_FALSE(entry.ShouldRejectRequest()); |
|
|
|
entry.InformOfRequest(false); |
|
EXPECT_TRUE(entry.ShouldRejectRequest()); |
|
} |
|
|
|
TEST(BackoffEntryTest, ReleaseTimeCalculation) { |
|
TestBackoffEntry entry(&base_policy); |
|
|
|
// With zero errors, should return "now". |
|
TimeTicks result = entry.GetReleaseTime(); |
|
EXPECT_EQ(entry.ImplGetTimeNow(), result); |
|
|
|
// 1 error. |
|
entry.InformOfRequest(false); |
|
result = entry.GetReleaseTime(); |
|
EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(1000), result); |
|
EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); |
|
|
|
// 2 errors. |
|
entry.InformOfRequest(false); |
|
result = entry.GetReleaseTime(); |
|
EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(2000), result); |
|
EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease()); |
|
|
|
// 3 errors. |
|
entry.InformOfRequest(false); |
|
result = entry.GetReleaseTime(); |
|
EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(4000), result); |
|
EXPECT_EQ(TimeDelta::FromMilliseconds(4000), entry.GetTimeUntilRelease()); |
|
|
|
// 6 errors (to check it doesn't pass maximum). |
|
entry.InformOfRequest(false); |
|
entry.InformOfRequest(false); |
|
entry.InformOfRequest(false); |
|
result = entry.GetReleaseTime(); |
|
EXPECT_EQ( |
|
entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(20000), result); |
|
} |
|
|
|
TEST(BackoffEntryTest, ReleaseTimeCalculationAlwaysDelay) { |
|
BackoffEntry::Policy always_delay_policy = base_policy; |
|
always_delay_policy.always_use_initial_delay = true; |
|
always_delay_policy.num_errors_to_ignore = 2; |
|
|
|
TestBackoffEntry entry(&always_delay_policy); |
|
|
|
// With previous requests, should return "now". |
|
TimeTicks result = entry.GetReleaseTime(); |
|
EXPECT_EQ(TimeDelta(), entry.GetTimeUntilRelease()); |
|
|
|
// 1 error. |
|
entry.InformOfRequest(false); |
|
EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); |
|
|
|
// 2 errors. |
|
entry.InformOfRequest(false); |
|
EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); |
|
|
|
// 3 errors, exponential backoff starts. |
|
entry.InformOfRequest(false); |
|
EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease()); |
|
|
|
// 4 errors. |
|
entry.InformOfRequest(false); |
|
EXPECT_EQ(TimeDelta::FromMilliseconds(4000), entry.GetTimeUntilRelease()); |
|
|
|
// 8 errors (to check it doesn't pass maximum). |
|
entry.InformOfRequest(false); |
|
entry.InformOfRequest(false); |
|
entry.InformOfRequest(false); |
|
entry.InformOfRequest(false); |
|
result = entry.GetReleaseTime(); |
|
EXPECT_EQ(TimeDelta::FromMilliseconds(20000), entry.GetTimeUntilRelease()); |
|
} |
|
|
|
TEST(BackoffEntryTest, ReleaseTimeCalculationWithJitter) { |
|
for (int i = 0; i < 10; ++i) { |
|
BackoffEntry::Policy jittery_policy = base_policy; |
|
jittery_policy.jitter_factor = 0.2; |
|
|
|
TestBackoffEntry entry(&jittery_policy); |
|
|
|
entry.InformOfRequest(false); |
|
entry.InformOfRequest(false); |
|
entry.InformOfRequest(false); |
|
TimeTicks result = entry.GetReleaseTime(); |
|
EXPECT_LE( |
|
entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(3200), result); |
|
EXPECT_GE( |
|
entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(4000), result); |
|
} |
|
} |
|
|
|
TEST(BackoffEntryTest, FailureThenSuccess) { |
|
TestBackoffEntry entry(&base_policy); |
|
|
|
// Failure count 1, establishes horizon. |
|
entry.InformOfRequest(false); |
|
TimeTicks release_time = entry.GetReleaseTime(); |
|
EXPECT_EQ(TimeTicks() + TimeDelta::FromMilliseconds(1000), release_time); |
|
|
|
// Success, failure count 0, should not advance past |
|
// the horizon that was already set. |
|
entry.set_now(release_time - TimeDelta::FromMilliseconds(200)); |
|
entry.InformOfRequest(true); |
|
EXPECT_EQ(release_time, entry.GetReleaseTime()); |
|
|
|
// Failure, failure count 1. |
|
entry.InformOfRequest(false); |
|
EXPECT_EQ(release_time + TimeDelta::FromMilliseconds(800), |
|
entry.GetReleaseTime()); |
|
} |
|
|
|
TEST(BackoffEntryTest, FailureThenSuccessAlwaysDelay) { |
|
BackoffEntry::Policy always_delay_policy = base_policy; |
|
always_delay_policy.always_use_initial_delay = true; |
|
always_delay_policy.num_errors_to_ignore = 1; |
|
|
|
TestBackoffEntry entry(&always_delay_policy); |
|
|
|
// Failure count 1. |
|
entry.InformOfRequest(false); |
|
EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); |
|
|
|
// Failure count 2. |
|
entry.InformOfRequest(false); |
|
EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease()); |
|
entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000)); |
|
|
|
// Success. We should go back to the original delay. |
|
entry.InformOfRequest(true); |
|
EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease()); |
|
|
|
// Failure count reaches 2 again. We should increase the delay once more. |
|
entry.InformOfRequest(false); |
|
EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease()); |
|
entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000)); |
|
} |
|
|
|
TEST(BackoffEntryTest, RetainCustomHorizon) { |
|
TestBackoffEntry custom(&base_policy); |
|
TimeTicks custom_horizon = TimeTicks() + TimeDelta::FromDays(3); |
|
custom.SetCustomReleaseTime(custom_horizon); |
|
custom.InformOfRequest(false); |
|
custom.InformOfRequest(true); |
|
custom.set_now(TimeTicks() + TimeDelta::FromDays(2)); |
|
custom.InformOfRequest(false); |
|
custom.InformOfRequest(true); |
|
EXPECT_EQ(custom_horizon, custom.GetReleaseTime()); |
|
|
|
// Now check that once we are at or past the custom horizon, |
|
// we get normal behavior. |
|
custom.set_now(TimeTicks() + TimeDelta::FromDays(3)); |
|
custom.InformOfRequest(false); |
|
EXPECT_EQ( |
|
TimeTicks() + TimeDelta::FromDays(3) + TimeDelta::FromMilliseconds(1000), |
|
custom.GetReleaseTime()); |
|
} |
|
|
|
TEST(BackoffEntryTest, RetainCustomHorizonWhenInitialErrorsIgnored) { |
|
// Regression test for a bug discovered during code review. |
|
BackoffEntry::Policy lenient_policy = base_policy; |
|
lenient_policy.num_errors_to_ignore = 1; |
|
TestBackoffEntry custom(&lenient_policy); |
|
TimeTicks custom_horizon = TimeTicks() + TimeDelta::FromDays(3); |
|
custom.SetCustomReleaseTime(custom_horizon); |
|
custom.InformOfRequest(false); // This must not reset the horizon. |
|
EXPECT_EQ(custom_horizon, custom.GetReleaseTime()); |
|
} |
|
|
|
TEST(BackoffEntryTest, OverflowProtection) { |
|
BackoffEntry::Policy large_multiply_policy = base_policy; |
|
large_multiply_policy.multiply_factor = 256; |
|
TestBackoffEntry custom(&large_multiply_policy); |
|
|
|
// Trigger enough failures such that more than 11 bits of exponent are used |
|
// to represent the exponential backoff intermediate values. Given a multiply |
|
// factor of 256 (2^8), 129 iterations is enough: 2^(8*(129-1)) = 2^1024. |
|
for (int i = 0; i < 129; ++i) { |
|
custom.set_now(custom.ImplGetTimeNow() + custom.GetTimeUntilRelease()); |
|
custom.InformOfRequest(false); |
|
ASSERT_TRUE(custom.ShouldRejectRequest()); |
|
} |
|
|
|
// Max delay should still be respected. |
|
EXPECT_EQ(20000, custom.GetTimeUntilRelease().InMilliseconds()); |
|
} |
|
|
|
} // namespace
|
|
|