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/SConstruct b/SConstruct
index 4df1395..abf5253 100644
--- a/SConstruct
+++ b/SConstruct
@@ -223,6 +223,7 @@
tarjan.cc
topological_sort.cc
update_attempter.cc
+ update_check_scheduler.cc
update_metadata.pb.cc
utils.cc""")
main = ['main.cc']
@@ -258,6 +259,7 @@
tarjan_unittest.cc
test_utils.cc
topological_sort_unittest.cc
+ update_check_scheduler_unittest.cc
utils_unittest.cc
zip_unittest.cc""")
unittest_main = ['testrunner.cc']
diff --git a/download_action.h b/download_action.h
index a46124f..189121b 100644
--- a/download_action.h
+++ b/download_action.h
@@ -76,6 +76,8 @@
writer_ = writer;
}
+ int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); }
+
// Debugging/logging
static std::string StaticType() { return "DownloadAction"; }
std::string Type() const { return StaticType(); }
diff --git a/main.cc b/main.cc
index 64c6aba..f027033 100644
--- a/main.cc
+++ b/main.cc
@@ -6,20 +6,20 @@
#include <tr1/memory>
#include <vector>
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/string_util.h>
#include <gflags/gflags.h>
#include <glib.h>
+#include <metrics/metrics_library.h>
-#include "base/at_exit.h"
-#include "base/command_line.h"
-#include "base/logging.h"
-#include "base/string_util.h"
-#include "metrics/metrics_library.h"
#include "update_engine/dbus_constants.h"
#include "update_engine/dbus_service.h"
#include "update_engine/prefs.h"
#include "update_engine/subprocess.h"
#include "update_engine/update_attempter.h"
-#include "update_engine/utils.h"
+#include "update_engine/update_check_scheduler.h"
extern "C" {
#include "update_engine/update_engine.dbusserver.h"
@@ -128,7 +128,9 @@
update_attempter.set_dbus_service(service);
chromeos_update_engine::SetupDbusService(service);
- update_attempter.InitiatePeriodicUpdateChecks();
+ // Schedule periodic update checks.
+ chromeos_update_engine::UpdateCheckScheduler scheduler(&update_attempter);
+ scheduler.Run();
// Update boot flags after 45 seconds
g_timeout_add_seconds(45, &chromeos_update_engine::UpdateBootFlags, NULL);
diff --git a/omaha_request_action.h b/omaha_request_action.h
index 6f30a32..7533d14 100644
--- a/omaha_request_action.h
+++ b/omaha_request_action.h
@@ -127,6 +127,8 @@
void PerformAction();
void TerminateProcessing();
+ int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); }
+
// Debugging/logging
static std::string StaticType() { return "OmahaRequestAction"; }
std::string Type() const { return StaticType(); }
diff --git a/update_attempter.cc b/update_attempter.cc
index b0c470b..3614cad 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -15,8 +15,8 @@
#include <vector>
#include <glib.h>
+#include <metrics/metrics_library.h>
-#include "metrics/metrics_library.h"
#include "update_engine/dbus_service.h"
#include "update_engine/download_action.h"
#include "update_engine/filesystem_copier_action.h"
@@ -26,6 +26,7 @@
#include "update_engine/omaha_response_handler_action.h"
#include "update_engine/postinstall_runner_action.h"
#include "update_engine/set_bootable_flag_action.h"
+#include "update_engine/update_check_scheduler.h"
using base::TimeDelta;
using base::TimeTicks;
@@ -35,21 +36,6 @@
namespace chromeos_update_engine {
-namespace {
-
-const int kTimeoutOnce = 7 * 60; // at 7 minutes
-const int kTimeoutPeriodic = 45 * 60; // every 45 minutes
-const int kTimeoutFuzz = 10 * 60; // +/- 5 minutes
-
-gboolean CheckForUpdatePeriodically(void* arg) {
- UpdateAttempter* update_attempter = reinterpret_cast<UpdateAttempter*>(arg);
- update_attempter->Update("", "");
- update_attempter->SchedulePeriodicUpdateCheck(kTimeoutPeriodic);
- return FALSE; // Don't run again.
-}
-
-} // namespace {}
-
const char* kUpdateCompletedMarker = "/tmp/update_engine_autoupdate_completed";
const char* UpdateStatusToString(UpdateStatus status) {
@@ -104,6 +90,8 @@
: dbus_service_(NULL),
prefs_(prefs),
metrics_lib_(metrics_lib),
+ update_check_scheduler_(NULL),
+ http_response_code_(0),
priority_(utils::kProcessPriorityNormal),
manage_priority_source_(NULL),
download_active_(false),
@@ -131,6 +119,7 @@
// Update in progress. Do nothing
return;
}
+ http_response_code_ = 0;
if (!omaha_request_params_.Init(app_version, omaha_url)) {
LOG(ERROR) << "Unable to initialize Omaha request device params.";
return;
@@ -271,9 +260,10 @@
return;
}
- LOG(INFO) << "Update failed.";
- if (ScheduleErrorEventAction())
+ if (ScheduleErrorEventAction()) {
return;
+ }
+ LOG(INFO) << "No update.";
SetStatusAndNotify(UPDATE_STATUS_IDLE);
}
@@ -291,11 +281,23 @@
void UpdateAttempter::ActionCompleted(ActionProcessor* processor,
AbstractAction* action,
ActionExitCode code) {
- // Reset download progress regardless of whether or not the download action
- // succeeded.
+ // Reset download progress regardless of whether or not the download
+ // action succeeded. Also, get the response code from HTTP request
+ // actions (update download as well as the initial update check
+ // actions).
const string type = action->Type();
- if (type == DownloadAction::StaticType())
+ if (type == DownloadAction::StaticType()) {
download_progress_ = 0.0;
+ DownloadAction* download_action = dynamic_cast<DownloadAction*>(action);
+ http_response_code_ = download_action->GetHTTPResponseCode();
+ } else if (type == OmahaRequestAction::StaticType()) {
+ OmahaRequestAction* omaha_request_action =
+ dynamic_cast<OmahaRequestAction*>(action);
+ // If the request is not an event, then it's the update-check.
+ if (!omaha_request_action->IsEvent()) {
+ http_response_code_ = omaha_request_action->GetHTTPResponseCode();
+ }
+ }
if (code != kActionCodeSuccess) {
// On failure, schedule an error event to be sent to Omaha.
CreatePendingErrorEvent(action, code);
@@ -374,6 +376,9 @@
void UpdateAttempter::SetStatusAndNotify(UpdateStatus status) {
status_ = status;
+ if (update_check_scheduler_) {
+ update_check_scheduler_->SetUpdateStatus(status_);
+ }
if (!dbus_service_)
return;
last_notify_time_ = TimeTicks::Now();
@@ -414,6 +419,7 @@
if (error_event_.get() == NULL)
return false;
+ LOG(INFO) << "Update failed -- reporting the error event.";
shared_ptr<OmahaRequestAction> error_event_action(
new OmahaRequestAction(prefs_,
omaha_request_params_,
@@ -426,27 +432,6 @@
return true;
}
-void UpdateAttempter::InitiatePeriodicUpdateChecks() {
- if (!utils::IsOfficialBuild()) {
- LOG(WARNING) << "Non-official build: periodic update checks disabled.";
- return;
- }
- if (utils::IsRemovableDevice(utils::RootDevice(utils::BootDevice()))) {
- LOG(WARNING) << "Removable device boot: periodic update checks disabled.";
- return;
- }
- // Kick off periodic update checks. The first check is scheduled
- // |kTimeoutOnce| seconds from now. Subsequent checks are scheduled
- // at |kTimeoutPeriodic|-second intervals.
- SchedulePeriodicUpdateCheck(kTimeoutOnce);
-}
-
-void UpdateAttempter::SchedulePeriodicUpdateCheck(int seconds) {
- seconds = utils::FuzzInt(seconds, kTimeoutFuzz);
- g_timeout_add_seconds(seconds, CheckForUpdatePeriodically, this);
- LOG(INFO) << "Next update check in " << seconds << " seconds.";
-}
-
void UpdateAttempter::SetPriority(utils::ProcessPriority priority) {
if (priority_ == priority) {
return;
diff --git a/update_attempter.h b/update_attempter.h
index 1c9ad40..fd9bc2a 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -24,9 +24,7 @@
namespace chromeos_update_engine {
-namespace utils {
-enum ProcessPriority;
-};
+class UpdateCheckScheduler;
extern const char* kUpdateCompletedMarker;
@@ -47,12 +45,13 @@
public DownloadActionDelegate {
public:
UpdateAttempter(PrefsInterface* prefs, MetricsLibraryInterface* metrics_lib);
- ~UpdateAttempter();
+ virtual ~UpdateAttempter();
// Checks for update and, if a newer version is available, attempts
// to update the system. Non-empty |in_app_version| or
// |in_update_url| prevents automatic detection of the parameter.
- void Update(const std::string& app_version, const std::string& omaha_url);
+ virtual void Update(const std::string& app_version,
+ const std::string& omaha_url);
// ActionProcessorDelegate methods:
void ProcessingDone(const ActionProcessor* processor, ActionExitCode code);
@@ -75,10 +74,22 @@
std::string* new_version,
int64_t* new_size);
+ UpdateStatus status() const { return status_; }
+
+ int http_response_code() const { return http_response_code_; }
+ void set_http_response_code(int code) { http_response_code_ = code; }
+
void set_dbus_service(struct UpdateEngineService* dbus_service) {
dbus_service_ = dbus_service;
}
+ UpdateCheckScheduler* update_check_scheduler() const {
+ return update_check_scheduler_;
+ }
+ void set_update_check_scheduler(UpdateCheckScheduler* scheduler) {
+ update_check_scheduler_ = scheduler;
+ }
+
// This is the D-Bus service entry point for going through an
// update. If the current status is idle invokes Update.
void CheckForUpdate(const std::string& app_version,
@@ -88,13 +99,6 @@
// UPDATED_NEED_REBOOT. Returns true on sucess, false otherwise.
bool RebootIfNeeded();
- // Kicks off the periodic update checks, if necessary.
- void InitiatePeriodicUpdateChecks();
-
- // Schedules the next periodic update check |seconds| from now. Note
- // that the actual timeout will be fuzzed.
- void SchedulePeriodicUpdateCheck(int seconds);
-
// DownloadActionDelegate methods
void SetDownloadStatus(bool active);
void BytesReceived(uint64_t bytes_received, uint64_t total);
@@ -155,9 +159,14 @@
// Pointer to the UMA metrics collection library.
MetricsLibraryInterface* metrics_lib_;
+ // The current UpdateCheckScheduler to notify of state transitions.
+ UpdateCheckScheduler* update_check_scheduler_;
+
// Pending error event, if any.
scoped_ptr<OmahaEvent> error_event_;
+ int http_response_code_;
+
// Current process priority.
utils::ProcessPriority priority_;
diff --git a/update_attempter_mock.h b/update_attempter_mock.h
new file mode 100644
index 0000000..56a0b42
--- /dev/null
+++ b/update_attempter_mock.h
@@ -0,0 +1,24 @@
+// 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.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_UPDATE_ATTEMPTER_MOCK_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_UPDATE_ATTEMPTER_MOCK_H__
+
+#include <gmock/gmock.h>
+
+#include "update_engine/update_attempter.h"
+
+namespace chromeos_update_engine {
+
+class UpdateAttempterMock : public UpdateAttempter {
+ public:
+ UpdateAttempterMock() : UpdateAttempter(NULL, NULL) {}
+
+ MOCK_METHOD2(Update, void(const std::string& app_version,
+ const std::string& omaha_url));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // CHROMEOS_PLATFORM_UPDATE_ENGINE_UPDATE_ATTEMPTER_MOCK_H__
diff --git a/update_check_scheduler.cc b/update_check_scheduler.cc
new file mode 100644
index 0000000..c0a52ab
--- /dev/null
+++ b/update_check_scheduler.cc
@@ -0,0 +1,128 @@
+// 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 "update_engine/update_check_scheduler.h"
+
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+const int UpdateCheckScheduler::kTimeoutOnce = 7 * 60; // at 7 minutes
+const int UpdateCheckScheduler::kTimeoutPeriodic = 45 * 60; // every 45 minutes
+const int UpdateCheckScheduler::kTimeoutRegularFuzz = 10 * 60; // +/- 5 minutes
+const int UpdateCheckScheduler::kTimeoutMaxBackoff = 4 * 60 * 60; // 4 hours
+
+UpdateCheckScheduler::UpdateCheckScheduler(UpdateAttempter* update_attempter)
+ : update_attempter_(update_attempter),
+ enabled_(false),
+ scheduled_(false),
+ last_interval_(0) {}
+
+UpdateCheckScheduler::~UpdateCheckScheduler() {}
+
+void UpdateCheckScheduler::Run() {
+ enabled_ = false;
+ update_attempter_->set_update_check_scheduler(NULL);
+
+ if (!IsOfficialBuild()) {
+ LOG(WARNING) << "Non-official build: periodic update checks disabled.";
+ return;
+ }
+ if (IsBootDeviceRemovable()) {
+ LOG(WARNING) << "Removable device boot: periodic update checks disabled.";
+ return;
+ }
+ enabled_ = true;
+
+ // Registers this scheduler with the update attempter so that scheduler can be
+ // notified of update status changes.
+ update_attempter_->set_update_check_scheduler(this);
+
+ // Kicks off periodic update checks. The first check is scheduled
+ // |kTimeoutOnce| seconds from now. Subsequent checks are scheduled by
+ // ScheduleNextCheck, normally at |kTimeoutPeriodic|-second intervals.
+ ScheduleCheck(kTimeoutOnce, kTimeoutRegularFuzz);
+}
+
+bool UpdateCheckScheduler::IsBootDeviceRemovable() {
+ return utils::IsRemovableDevice(utils::RootDevice(utils::BootDevice()));
+}
+
+bool UpdateCheckScheduler::IsOfficialBuild() {
+ return utils::IsOfficialBuild();
+}
+
+guint UpdateCheckScheduler::GTimeoutAddSeconds(guint interval,
+ GSourceFunc function) {
+ return g_timeout_add_seconds(interval, function, this);
+}
+
+void UpdateCheckScheduler::ScheduleCheck(int interval, int fuzz) {
+ if (!CanSchedule()) {
+ return;
+ }
+ last_interval_ = interval;
+ interval = utils::FuzzInt(interval, fuzz);
+ if (interval < 0) {
+ interval = 0;
+ }
+ GTimeoutAddSeconds(interval, StaticCheck);
+ scheduled_ = true;
+ LOG(INFO) << "Next update check in " << interval << " seconds.";
+}
+
+gboolean UpdateCheckScheduler::StaticCheck(void* scheduler) {
+ UpdateCheckScheduler* me = reinterpret_cast<UpdateCheckScheduler*>(scheduler);
+ CHECK(me->scheduled_);
+ me->scheduled_ = false;
+ me->update_attempter_->Update("", "");
+ // This check ensures that future update checks will be or are already
+ // scheduled. The check should never fail. A check failure means that there's
+ // a bug that will most likely prevent further automatic update checks. It
+ // seems better to crash in such cases and restart the update_engine daemon
+ // into, hopefully, a known good state.
+ CHECK(me->update_attempter_->status() != UPDATE_STATUS_IDLE ||
+ !me->CanSchedule());
+ return FALSE; // Don't run again.
+}
+
+void UpdateCheckScheduler::ComputeNextIntervalAndFuzz(int* next_interval,
+ int* next_fuzz) {
+ int interval = 0;
+ int fuzz = 0;
+ // Implements exponential back off on 500 (Internal Server Error) and 503
+ // (Service Unavailable) HTTP response codes.
+ if (update_attempter_->http_response_code() == 500 ||
+ update_attempter_->http_response_code() == 503) {
+ interval = 2 * last_interval_;
+ if (interval > kTimeoutMaxBackoff) {
+ interval = kTimeoutMaxBackoff;
+ }
+ // Exponential back off is fuzzed by +/- |interval|/2.
+ fuzz = interval;
+ }
+ // Ensures that under normal conditions the regular update check interval and
+ // fuzz are used. Also covers the case where back off is required based on the
+ // initial update check.
+ if (interval < kTimeoutPeriodic) {
+ interval = kTimeoutPeriodic;
+ fuzz = kTimeoutRegularFuzz;
+ }
+ *next_interval = interval;
+ *next_fuzz = fuzz;
+}
+
+void UpdateCheckScheduler::ScheduleNextCheck() {
+ int interval, fuzz;
+ ComputeNextIntervalAndFuzz(&interval, &fuzz);
+ ScheduleCheck(interval, fuzz);
+}
+
+void UpdateCheckScheduler::SetUpdateStatus(UpdateStatus status) {
+ if (status == UPDATE_STATUS_IDLE) {
+ ScheduleNextCheck();
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/update_check_scheduler.h b/update_check_scheduler.h
new file mode 100644
index 0000000..7253c5a
--- /dev/null
+++ b/update_check_scheduler.h
@@ -0,0 +1,118 @@
+// 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.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_UPDATE_CHECK_SCHEDULER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_UPDATE_CHECK_SCHEDULER_H__
+
+#include <base/basictypes.h>
+#include <glib.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/update_attempter.h"
+
+namespace chromeos_update_engine {
+
+// UpdateCheckScheduler manages the periodic background update checks. This is
+// the basic update check cycle:
+//
+// Run
+// |
+// v
+// /->ScheduleCheck
+// | |
+// | v
+// | StaticCheck (invoked through a GLib timeout source)
+// | |
+// | v
+// | UpdateAttempter::Update
+// | |
+// | v
+// | SetUpdateStatus (invoked by UpdateAttempter on state transitions)
+// | |
+// | v
+// | ScheduleNextCheck (invoked when UpdateAttempter becomes idle)
+// \---/
+class UpdateCheckScheduler {
+ public:
+ static const int kTimeoutOnce;
+ static const int kTimeoutPeriodic;
+ static const int kTimeoutRegularFuzz;
+ static const int kTimeoutMaxBackoff;
+
+ UpdateCheckScheduler(UpdateAttempter* update_attempter);
+ virtual ~UpdateCheckScheduler();
+
+ // Initiates the periodic update checks, if necessary.
+ void Run();
+
+ // Sets the new update status. This is invoked by UpdateAttempter.
+ void SetUpdateStatus(UpdateStatus status);
+
+ private:
+ friend class UpdateCheckSchedulerTest;
+ FRIEND_TEST(UpdateCheckSchedulerTest, CanScheduleTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzBackoffTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, GTimeoutAddSecondsTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, IsBootDeviceRemovableTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, IsOfficialBuildTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, RunBootDeviceRemovableTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, RunNonOfficialBuildTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, RunTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, ScheduleCheckDisabledTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, ScheduleCheckEnabledTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, ScheduleCheckNegativeIntervalTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, ScheduleNextCheckDisabledTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, ScheduleNextCheckEnabledTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, SetUpdateStatusIdleDisabledTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, SetUpdateStatusIdleEnabledTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, SetUpdateStatusNonIdleTest);
+ FRIEND_TEST(UpdateCheckSchedulerTest, StaticCheckTest);
+
+ // Wraps GLib's g_timeout_add_seconds so that it can be mocked in tests.
+ virtual guint GTimeoutAddSeconds(guint interval, GSourceFunc function);
+
+ // Wrappers for utils functions so that they can be mocked in tests.
+ virtual bool IsBootDeviceRemovable();
+ virtual bool IsOfficialBuild();
+
+ // Returns true if an update check can be scheduled. An update check should
+ // not be scheduled if periodic update checks are disabled or if one is
+ // already scheduled.
+ bool CanSchedule() { return enabled_ && !scheduled_; }
+
+ // Schedules the next periodic update check |interval| seconds from now
+ // randomized by +/- |fuzz|/2.
+ void ScheduleCheck(int interval, int fuzz);
+
+ // GLib timeout source callback. Initiates an update check through the update
+ // attempter.
+ static gboolean StaticCheck(void* scheduler);
+
+ // Schedules the next update check by setting up a timeout source.
+ void ScheduleNextCheck();
+
+ // Computes the timeout interval along with its random fuzz range for the next
+ // update check by taking into account the last timeout interval as well as
+ // the last update status.
+ void ComputeNextIntervalAndFuzz(int* next_interval, int* next_fuzz);
+
+ // The UpdateAttempter to use for update checks.
+ UpdateAttempter* update_attempter_;
+
+ // True if automatic update checks should be scheduled, false otherwise.
+ bool enabled_;
+
+ // True if there's an update check scheduled already, false otherwise.
+ bool scheduled_;
+
+ // The timeout interval (before fuzzing) for the last update check.
+ int last_interval_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateCheckScheduler);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // CHROMEOS_PLATFORM_UPDATE_ENGINE_CHECK_SCHEDULER_H__
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