AU: Implement exponential back off for 500 and 503 HTTP response codes.
Also refactors the automatic update checks into a separate
scheduler class and adds unit tests for them.
update_check_scheduler.cc 59 / 59: 100.0%
update_check_scheduler.h 1 / 1: 100.0%
Note: because the unit tests for this CL use the
UpdateAttempter class, the CL brings in several untested
modules into the test report reducing the overall test
coverage to ~82%.
BUG=2394
TEST=unit tests, gmerged on device and inspected logs
Change-Id: I078b1727b5338f6fc34e51f5e04a375518d63cef
Review URL: http://codereview.chromium.org/3215006
diff --git a/update_check_scheduler_unittest.cc b/update_check_scheduler_unittest.cc
new file mode 100644
index 0000000..8a0ca19
--- /dev/null
+++ b/update_check_scheduler_unittest.cc
@@ -0,0 +1,238 @@
+// Copyright (c) 2010 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 <gtest/gtest.h>
+
+#include "update_engine/update_attempter_mock.h"
+#include "update_engine/update_check_scheduler.h"
+
+using std::string;
+using testing::_;
+using testing::AllOf;
+using testing::Ge;
+using testing::Le;
+using testing::MockFunction;
+using testing::Return;
+
+namespace chromeos_update_engine {
+
+namespace {
+void FuzzRange(int interval, int fuzz, int* interval_min, int* interval_max) {
+ *interval_min = interval - fuzz / 2;
+ *interval_max = interval + fuzz - fuzz / 2;
+}
+} // namespace {}
+
+// Test a subclass rather than the main class directly so that we can mock out
+// GLib and utils in tests. There're explicit unit test for the wrapper methods.
+class UpdateCheckSchedulerUnderTest : public UpdateCheckScheduler {
+ public:
+ UpdateCheckSchedulerUnderTest(UpdateAttempter* update_attempter)
+ : UpdateCheckScheduler(update_attempter) {}
+
+ MOCK_METHOD2(GTimeoutAddSeconds, guint(guint seconds, GSourceFunc function));
+ MOCK_METHOD0(IsBootDeviceRemovable, bool());
+ MOCK_METHOD0(IsOfficialBuild, bool());
+};
+
+class UpdateCheckSchedulerTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ test_ = this;
+ loop_ = NULL;
+ scheduler_.reset(new UpdateCheckSchedulerUnderTest(&attempter_));
+ EXPECT_EQ(&attempter_, scheduler_->update_attempter_);
+ EXPECT_FALSE(scheduler_->enabled_);
+ EXPECT_FALSE(scheduler_->scheduled_);
+ EXPECT_EQ(0, scheduler_->last_interval_);
+ }
+
+ virtual void TearDown() {
+ test_ = NULL;
+ loop_ = NULL;
+ scheduler_.reset(NULL);
+ }
+
+ static gboolean SourceCallback(gpointer data) {
+ g_main_loop_quit(test_->loop_);
+ // Forwards the call to the function mock so that expectations can be set.
+ return test_->source_callback_.Call(data);
+ }
+
+ scoped_ptr<UpdateCheckSchedulerUnderTest> scheduler_;
+ UpdateAttempterMock attempter_;
+ MockFunction<gboolean(gpointer data)> source_callback_;
+ GMainLoop* loop_;
+ static UpdateCheckSchedulerTest* test_;
+};
+
+UpdateCheckSchedulerTest* UpdateCheckSchedulerTest::test_ = NULL;
+
+TEST_F(UpdateCheckSchedulerTest, CanScheduleTest) {
+ EXPECT_FALSE(scheduler_->CanSchedule());
+ scheduler_->enabled_ = true;
+ EXPECT_TRUE(scheduler_->CanSchedule());
+ scheduler_->scheduled_ = true;
+ EXPECT_FALSE(scheduler_->CanSchedule());
+ scheduler_->enabled_ = false;
+ EXPECT_FALSE(scheduler_->CanSchedule());
+}
+
+TEST_F(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzBackoffTest) {
+ int interval, fuzz;
+ attempter_.set_http_response_code(500);
+ int last_interval = UpdateCheckScheduler::kTimeoutPeriodic + 50;
+ scheduler_->last_interval_ = last_interval;
+ scheduler_->ComputeNextIntervalAndFuzz(&interval, &fuzz);
+ EXPECT_EQ(2 * last_interval, interval);
+ EXPECT_EQ(2 * last_interval, fuzz);
+
+ attempter_.set_http_response_code(503);
+ last_interval = UpdateCheckScheduler::kTimeoutMaxBackoff / 2 + 1;
+ scheduler_->last_interval_ = last_interval;
+ scheduler_->ComputeNextIntervalAndFuzz(&interval, &fuzz);
+ EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, interval);
+ EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, fuzz);
+}
+
+TEST_F(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzTest) {
+ int interval, fuzz;
+ scheduler_->ComputeNextIntervalAndFuzz(&interval, &fuzz);
+ EXPECT_EQ(UpdateCheckScheduler::kTimeoutPeriodic, interval);
+ EXPECT_EQ(UpdateCheckScheduler::kTimeoutRegularFuzz, fuzz);
+}
+
+TEST_F(UpdateCheckSchedulerTest, GTimeoutAddSecondsTest) {
+ loop_ = g_main_loop_new(g_main_context_default(), FALSE);
+ // Invokes the actual GLib wrapper method rather than the subclass mock.
+ scheduler_->UpdateCheckScheduler::GTimeoutAddSeconds(0, SourceCallback);
+ EXPECT_CALL(source_callback_, Call(scheduler_.get())).Times(1);
+ g_main_loop_run(loop_);
+ g_main_loop_unref(loop_);
+}
+
+TEST_F(UpdateCheckSchedulerTest, IsBootDeviceRemovableTest) {
+ // Invokes the actual utils wrapper method rather than the subclass mock.
+ EXPECT_FALSE(scheduler_->UpdateCheckScheduler::IsBootDeviceRemovable());
+}
+
+TEST_F(UpdateCheckSchedulerTest, IsOfficialBuildTest) {
+ // Invokes the actual utils wrapper method rather than the subclass mock.
+ EXPECT_TRUE(scheduler_->UpdateCheckScheduler::IsOfficialBuild());
+}
+
+TEST_F(UpdateCheckSchedulerTest, RunBootDeviceRemovableTest) {
+ scheduler_->enabled_ = true;
+ EXPECT_CALL(*scheduler_, IsOfficialBuild()).Times(1).WillOnce(Return(true));
+ EXPECT_CALL(*scheduler_, IsBootDeviceRemovable())
+ .Times(1)
+ .WillOnce(Return(true));
+ scheduler_->Run();
+ EXPECT_FALSE(scheduler_->enabled_);
+ EXPECT_EQ(NULL, attempter_.update_check_scheduler());
+}
+
+TEST_F(UpdateCheckSchedulerTest, RunNonOfficialBuildTest) {
+ scheduler_->enabled_ = true;
+ EXPECT_CALL(*scheduler_, IsOfficialBuild()).Times(1).WillOnce(Return(false));
+ scheduler_->Run();
+ EXPECT_FALSE(scheduler_->enabled_);
+ EXPECT_EQ(NULL, attempter_.update_check_scheduler());
+}
+
+TEST_F(UpdateCheckSchedulerTest, RunTest) {
+ int interval_min, interval_max;
+ FuzzRange(UpdateCheckScheduler::kTimeoutOnce,
+ UpdateCheckScheduler::kTimeoutRegularFuzz,
+ &interval_min,
+ &interval_max);
+ EXPECT_CALL(*scheduler_, IsOfficialBuild()).Times(1).WillOnce(Return(true));
+ EXPECT_CALL(*scheduler_, IsBootDeviceRemovable())
+ .Times(1)
+ .WillOnce(Return(false));
+ EXPECT_CALL(*scheduler_,
+ GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)),
+ scheduler_->StaticCheck)).Times(1);
+ scheduler_->Run();
+ EXPECT_TRUE(scheduler_->enabled_);
+ EXPECT_EQ(scheduler_.get(), attempter_.update_check_scheduler());
+}
+
+TEST_F(UpdateCheckSchedulerTest, ScheduleCheckDisabledTest) {
+ EXPECT_CALL(*scheduler_, GTimeoutAddSeconds(_, _)).Times(0);
+ scheduler_->ScheduleCheck(250, 30);
+ EXPECT_EQ(0, scheduler_->last_interval_);
+ EXPECT_FALSE(scheduler_->scheduled_);
+}
+
+TEST_F(UpdateCheckSchedulerTest, ScheduleCheckEnabledTest) {
+ int interval_min, interval_max;
+ FuzzRange(100, 10, &interval_min,&interval_max);
+ EXPECT_CALL(*scheduler_,
+ GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)),
+ scheduler_->StaticCheck)).Times(1);
+ scheduler_->enabled_ = true;
+ scheduler_->ScheduleCheck(100, 10);
+ EXPECT_EQ(100, scheduler_->last_interval_);
+ EXPECT_TRUE(scheduler_->scheduled_);
+}
+
+TEST_F(UpdateCheckSchedulerTest, ScheduleCheckNegativeIntervalTest) {
+ EXPECT_CALL(*scheduler_, GTimeoutAddSeconds(0, scheduler_->StaticCheck))
+ .Times(1);
+ scheduler_->enabled_ = true;
+ scheduler_->ScheduleCheck(-50, 20);
+ EXPECT_TRUE(scheduler_->scheduled_);
+}
+
+TEST_F(UpdateCheckSchedulerTest, ScheduleNextCheckDisabledTest) {
+ EXPECT_CALL(*scheduler_, GTimeoutAddSeconds(_, _)).Times(0);
+ scheduler_->ScheduleNextCheck();
+}
+
+TEST_F(UpdateCheckSchedulerTest, ScheduleNextCheckEnabledTest) {
+ int interval_min, interval_max;
+ FuzzRange(UpdateCheckScheduler::kTimeoutPeriodic,
+ UpdateCheckScheduler::kTimeoutRegularFuzz,
+ &interval_min,
+ &interval_max);
+ EXPECT_CALL(*scheduler_,
+ GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)),
+ scheduler_->StaticCheck)).Times(1);
+ scheduler_->enabled_ = true;
+ scheduler_->ScheduleNextCheck();
+}
+
+TEST_F(UpdateCheckSchedulerTest, SetUpdateStatusIdleDisabledTest) {
+ EXPECT_CALL(*scheduler_, GTimeoutAddSeconds(_, _)).Times(0);
+ scheduler_->SetUpdateStatus(UPDATE_STATUS_IDLE);
+}
+
+TEST_F(UpdateCheckSchedulerTest, SetUpdateStatusIdleEnabledTest) {
+ int interval_min, interval_max;
+ FuzzRange(UpdateCheckScheduler::kTimeoutPeriodic,
+ UpdateCheckScheduler::kTimeoutRegularFuzz,
+ &interval_min,
+ &interval_max);
+ EXPECT_CALL(*scheduler_,
+ GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)),
+ scheduler_->StaticCheck)).Times(1);
+ scheduler_->enabled_ = true;
+ scheduler_->SetUpdateStatus(UPDATE_STATUS_IDLE);
+}
+
+TEST_F(UpdateCheckSchedulerTest, SetUpdateStatusNonIdleTest) {
+ EXPECT_CALL(*scheduler_, GTimeoutAddSeconds(_, _)).Times(0);
+ scheduler_->SetUpdateStatus(UPDATE_STATUS_DOWNLOADING);
+ scheduler_->enabled_ = true;
+ scheduler_->SetUpdateStatus(UPDATE_STATUS_DOWNLOADING);
+}
+
+TEST_F(UpdateCheckSchedulerTest, StaticCheckTest) {
+ scheduler_->scheduled_ = true;
+ EXPECT_CALL(attempter_, Update("", "")).Times(1);
+ UpdateCheckSchedulerUnderTest::StaticCheck(scheduler_.get());
+}
+
+} // namespace chromeos_update_engine