blob: e26d46df07e23dda807663f08ddb02ee197a6c4e [file] [log] [blame]
// Copyright (c) 2013 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 "power_manager/powerd/policy/suspender.h"
#include <base/bind.h>
#include <base/callback.h>
#include <base/compiler_specific.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include <gtest/gtest.h>
#include "power_manager/common/action_recorder.h"
#include "power_manager/common/clock.h"
#include "power_manager/common/fake_prefs.h"
#include "power_manager/common/power_constants.h"
#include "power_manager/powerd/policy/shutdown_from_suspend_stub.h"
#include "power_manager/powerd/system/dark_resume_stub.h"
#include "power_manager/powerd/system/dbus_wrapper_stub.h"
#include "power_manager/powerd/system/display/display_watcher_stub.h"
#include "power_manager/powerd/system/wakeup_source_identifier_stub.h"
#include "power_manager/proto_bindings/suspend.pb.h"
namespace power_manager {
namespace policy {
namespace {
// Various actions that can be returned by TestDelegate::GetActions().
const char kPrepare[] = "prepare";
const char kSuspend[] = "suspend";
const char kUnprepare[] = "unprepare";
const char kShutDown[] = "shut_down";
const char kGenerateDarkResumeMetrics[] = "generate_dark_resume_metrics";
const char kNoActions[] = "";
// Test implementation of Suspender::Delegate that just records the actions it
// was asked to perform.
class TestDelegate : public Suspender::Delegate, public ActionRecorder {
public:
TestDelegate() = default;
TestDelegate(const TestDelegate&) = delete;
TestDelegate& operator=(const TestDelegate&) = delete;
~TestDelegate() override = default;
void set_lid_closed(bool closed) { lid_closed_ = closed; }
void set_report_success_for_read_wakeup_count(bool success) {
report_success_for_read_wakeup_count_ = success;
}
void set_suspend_announced(bool announced) { suspend_announced_ = announced; }
void set_suspend_result(SuspendResult result) { suspend_result_ = result; }
void set_wakeup_count(uint64_t count) { wakeup_count_ = count; }
void set_clock(Clock* clock) { clock_ = clock; }
// TODO(chromeos-power): Delete this and use set_suspend_callback instead.
void set_suspend_advance_time(base::TimeDelta delta) {
suspend_advance_time_ = delta;
}
void set_completion_callback(base::Closure callback) {
completion_callback_ = callback;
}
void set_shutdown_callback(base::Closure callback) {
shutdown_callback_ = callback;
}
void set_suspend_callback(base::Closure callback) {
suspend_callback_ = callback;
}
bool suspend_announced() const { return suspend_announced_; }
uint64_t suspend_wakeup_count() const { return suspend_wakeup_count_; }
bool suspend_wakeup_count_valid() const {
return suspend_wakeup_count_valid_;
}
const base::TimeDelta& suspend_duration() const { return suspend_duration_; }
bool suspend_was_successful() const { return suspend_was_successful_; }
int num_suspend_attempts() const { return num_suspend_attempts_; }
const std::vector<Suspender::DarkResumeInfo>& dark_resume_wake_durations()
const {
return dark_resume_wake_durations_;
}
base::TimeDelta last_suspend_duration() const {
return last_suspend_duration_;
}
// Delegate implementation:
int GetInitialSuspendId() override { return 1; }
int GetInitialDarkSuspendId() override { return 12701; }
bool IsLidClosedForSuspend() override { return lid_closed_; }
bool ReadSuspendWakeupCount(uint64_t* wakeup_count) override {
if (!report_success_for_read_wakeup_count_)
return false;
*wakeup_count = wakeup_count_;
return true;
}
void SetSuspendAnnounced(bool announced) override {
suspend_announced_ = announced;
}
bool GetSuspendAnnounced() override { return suspend_announced_; }
void PrepareToSuspend() override { AppendAction(kPrepare); }
SuspendResult DoSuspend(uint64_t wakeup_count,
bool wakeup_count_valid,
base::TimeDelta duration) override {
AppendAction(kSuspend);
suspend_wakeup_count_ = wakeup_count;
suspend_wakeup_count_valid_ = wakeup_count_valid;
suspend_duration_ = duration;
if (clock_)
clock_->advance_current_boot_time_for_testing(suspend_advance_time_);
if (suspend_callback_)
suspend_callback_.Run();
return suspend_result_;
}
void UndoPrepareToSuspend(bool success, int num_suspend_attempts) override {
AppendAction(kUnprepare);
suspend_was_successful_ = success;
num_suspend_attempts_ = num_suspend_attempts;
if (!completion_callback_.is_null())
completion_callback_.Run();
}
void GenerateDarkResumeMetrics(
const std::vector<Suspender::DarkResumeInfo>& dark_resume_wake_durations,
base::TimeDelta suspend_duration) override {
AppendAction(kGenerateDarkResumeMetrics);
dark_resume_wake_durations_ = dark_resume_wake_durations;
last_suspend_duration_ = suspend_duration;
}
void ShutDownForFailedSuspend() override {
AppendAction(kShutDown);
if (!shutdown_callback_.is_null())
shutdown_callback_.Run();
}
void ShutDownFromSuspend() override {
AppendAction(kShutDown);
if (!shutdown_callback_.is_null())
shutdown_callback_.Run();
}
private:
// Value returned by IsLidClosedForSuspend().
bool lid_closed_ = false;
// Should ReadSuspendWakeupCount() and DoSuspend() report success?
bool report_success_for_read_wakeup_count_ = true;
SuspendResult suspend_result_ = SuspendResult::SUCCESS;
// Count that should be returned by ReadSuspendWakeupCount().
uint64_t wakeup_count_ = 0;
// Updated by SetSuspendAnnounced() and returned by GetSuspendAnnounced().
bool suspend_announced_ = false;
// If non-null, |clock_|'s boot time is advanced by |suspend_advance_time_|
// each time DoSuspend() is called.
Clock* clock_ = nullptr;
base::TimeDelta suspend_advance_time_;
// Callback to run each time UndoPrepareToSuspend() is called.
base::Closure completion_callback_;
// Callback to run each time ShutDown*() is called.
base::Closure shutdown_callback_;
// Callback to run each time DoSuspend() is called.
base::Closure suspend_callback_;
// Arguments passed to last invocation of DoSuspend().
uint64_t suspend_wakeup_count_ = 0;
bool suspend_wakeup_count_valid_ = false;
base::TimeDelta suspend_duration_;
// Arguments passed to last invocation of UndoPrepareToSuspend().
bool suspend_was_successful_ = false;
int num_suspend_attempts_ = 0;
// Dark resume wake data provided to GenerateDarkResumeMetrics().
std::vector<Suspender::DarkResumeInfo> dark_resume_wake_durations_;
base::TimeDelta last_suspend_duration_;
};
} // namespace
class SuspenderTest : public testing::Test {
public:
SuspenderTest()
: test_api_(&suspender_),
pref_retry_delay_ms_(10000),
pref_num_retries_(10) {}
SuspenderTest(const SuspenderTest&) = delete;
SuspenderTest& operator=(const SuspenderTest&) = delete;
protected:
void Init() {
prefs_.SetInt64(kRetrySuspendMsPref, pref_retry_delay_ms_);
prefs_.SetInt64(kRetrySuspendAttemptsPref, pref_num_retries_);
test_api_.clock()->set_current_boot_time_for_testing(
base::TimeTicks() + base::TimeDelta::FromHours(1));
delegate_.set_clock(test_api_.clock());
suspender_.Init(&delegate_, &dbus_wrapper_, &dark_resume_,
&display_watcher_, &wakeup_source_identifier_,
&shutdown_from_suspend_, &prefs_);
}
// Returns the ID from a SuspendImminent signal at |position|.
int GetSuspendImminentId(int position) {
SuspendImminent proto;
EXPECT_TRUE(dbus_wrapper_.GetSentSignal(position, kSuspendImminentSignal,
&proto, nullptr));
return proto.suspend_id();
}
// Returns the reason from a SuspendImminent signal at |position|.
SuspendImminent::Reason GetSuspendImminentReason(int position) {
SuspendImminent proto;
EXPECT_TRUE(dbus_wrapper_.GetSentSignal(position, kSuspendImminentSignal,
&proto, nullptr));
return proto.reason();
}
// Returns the ID from a DarkSuspendImminent signal at |position|.
int GetDarkSuspendImminentId(int position) {
SuspendImminent proto;
EXPECT_TRUE(dbus_wrapper_.GetSentSignal(
position, kDarkSuspendImminentSignal, &proto, nullptr));
return proto.suspend_id();
}
// Returns the ID from a SuspendDone signal at |position|.
int GetSuspendDoneId(int position) {
SuspendDone proto;
EXPECT_TRUE(dbus_wrapper_.GetSentSignal(position, kSuspendDoneSignal,
&proto, nullptr));
return proto.suspend_id();
}
// Announces the readiness of registered delays for a regular suspend request.
void AnnounceReadyForSuspend(int suspend_request_id) {
suspender_.OnReadyForSuspend(test_api_.suspend_delay_controller(),
suspend_request_id);
}
// Announces the readiness of registered delays for a suspend from dark
// resume.
void AnnounceReadyForDarkSuspend(int dark_suspend_id) {
suspender_.OnReadyForSuspend(test_api_.dark_suspend_delay_controller(),
dark_suspend_id);
}
// Records the |last_dark_resume_wake_reason_| in |suspender_|. Takes a
// shortcut and sets the member rather than simulating the actual DBus method
// call handling.
void RecordDarkResumeWakeReason(const std::string& wake_reason) {
test_api_.set_last_dark_resume_wake_reason(wake_reason);
}
FakePrefs prefs_;
TestDelegate delegate_;
system::DBusWrapperStub dbus_wrapper_;
system::DarkResumeStub dark_resume_;
system::DisplayWatcherStub display_watcher_;
system::WakeupSourceIdentifierStub wakeup_source_identifier_;
policy::ShutdownFromSuspendStub shutdown_from_suspend_;
Suspender suspender_;
Suspender::TestApi test_api_;
int64_t pref_retry_delay_ms_;
int64_t pref_num_retries_;
};
// Tests the standard suspend/resume cycle.
TEST_F(SuspenderTest, SuspendResume) {
Init();
// Suspender shouldn't run powerd_suspend until it receives notice that
// SuspendDelayController is ready.
const uint64_t kWakeupCount = 452;
delegate_.set_wakeup_count(kWakeupCount);
suspender_.RequestSuspend(SuspendImminent_Reason_IDLE, base::TimeDelta());
const int suspend_id = test_api_.suspend_id();
EXPECT_EQ(suspend_id, GetSuspendImminentId(0));
EXPECT_EQ(SuspendImminent_Reason_IDLE, GetSuspendImminentReason(0));
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_announced());
// Simulate suspending for 20 minutes.
const base::TimeDelta kDuration = base::TimeDelta::FromMinutes(20);
delegate_.set_suspend_advance_time(kDuration);
// Indicate to suspender that input device triggered the wake.
wakeup_source_identifier_.SetInputDeviceCausedLastWake(false);
// When Suspender receives notice that the system is ready to be
// suspended, it should immediately suspend the system.
dbus_wrapper_.ClearSentSignals();
AnnounceReadyForSuspend(suspend_id);
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
EXPECT_EQ(kWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
EXPECT_TRUE(delegate_.suspend_was_successful());
EXPECT_EQ(1, delegate_.num_suspend_attempts());
// A SuspendDone signal should be emitted to announce that the attempt is
// complete.
SuspendDone done_proto;
EXPECT_TRUE(
dbus_wrapper_.GetSentSignal(0, kSuspendDoneSignal, &done_proto, nullptr));
EXPECT_EQ(suspend_id, done_proto.suspend_id());
EXPECT_EQ(kDuration.ToInternalValue(), done_proto.suspend_duration());
EXPECT_EQ(done_proto.wakeup_type(), SuspendDone_WakeupType_OTHER);
EXPECT_FALSE(delegate_.suspend_announced());
// A resuspend timeout shouldn't be set.
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
}
// Tests that Suspender doesn't pass a wakeup count to the delegate when it was
// unable to fetch one.
TEST_F(SuspenderTest, MissingWakeupCount) {
Init();
delegate_.set_report_success_for_read_wakeup_count(false);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
EXPECT_FALSE(delegate_.suspend_wakeup_count_valid());
}
// Tests that calls to RequestSuspend() are ignored when a suspend request is
// already underway.
TEST_F(SuspenderTest, IgnoreDuplicateSuspendRequests) {
Init();
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
const int orig_suspend_id = test_api_.suspend_id();
// The suspend ID should be left unchanged after a second call to
// RequestSuspend().
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kNoActions, delegate_.GetActions());
EXPECT_EQ(orig_suspend_id, test_api_.suspend_id());
}
// Tests that suspend cancel due to wake event from input device is treated as
// successful resume.
TEST_F(SuspenderTest, SuspendCancelDueToInputDeviceWakeEvent) {
Init();
const uint64_t kOrigWakeupCount = 46;
delegate_.set_wakeup_count(kOrigWakeupCount);
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::CANCELED);
suspender_.RequestSuspend(SuspendImminent_Reason_LID_CLOSED,
base::TimeDelta());
const int suspend_id = test_api_.suspend_id();
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_announced());
wakeup_source_identifier_.SetInputDeviceCausedLastWake(true);
dbus_wrapper_.ClearSentSignals();
AnnounceReadyForSuspend(suspend_id);
EXPECT_EQ(kOrigWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
EXPECT_TRUE(delegate_.suspend_was_successful());
EXPECT_EQ(1, delegate_.num_suspend_attempts());
// A SuspendDone signal should be emitted to announce that the attempt is
// complete.
SuspendDone done_proto;
EXPECT_TRUE(
dbus_wrapper_.GetSentSignal(0, kSuspendDoneSignal, &done_proto, nullptr));
EXPECT_EQ(suspend_id, done_proto.suspend_id());
EXPECT_EQ(done_proto.wakeup_type(), SuspendDone_WakeupType_INPUT);
EXPECT_FALSE(delegate_.suspend_announced());
// A resuspend timeout shouldn't be set.
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
}
// Tests that suspend is retried on failure.
TEST_F(SuspenderTest, RetryOnFailure) {
Init();
const uint64_t kOrigWakeupCount = 46;
delegate_.set_wakeup_count(kOrigWakeupCount);
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::FAILURE);
suspender_.RequestSuspend(SuspendImminent_Reason_LID_CLOSED,
base::TimeDelta());
const int suspend_id = test_api_.suspend_id();
EXPECT_EQ(suspend_id, GetSuspendImminentId(0));
EXPECT_EQ(SuspendImminent_Reason_LID_CLOSED, GetSuspendImminentReason(0));
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_announced());
const uint64_t kRetryWakeupCount = 67;
delegate_.set_wakeup_count(kRetryWakeupCount);
dbus_wrapper_.ClearSentSignals();
AnnounceReadyForSuspend(suspend_id);
EXPECT_EQ(kSuspend, delegate_.GetActions());
EXPECT_EQ(kOrigWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
EXPECT_EQ(0, dbus_wrapper_.num_sent_signals());
// The timeout should trigger another suspend attempt.
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(kSuspend, delegate_.GetActions());
EXPECT_EQ(kRetryWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
EXPECT_EQ(0, dbus_wrapper_.num_sent_signals());
// A second suspend request should be ignored so we'll avoid trying to
// re-suspend immediately if an attempt fails while the lid is closed
// (http://crbug.com/384610). Also check that an external wakeup count passed
// in the request gets ignored for the eventual retry.
const uint64_t kExternalWakeupCount = 32542;
suspender_.RequestSuspendWithExternalWakeupCount(
SuspendImminent_Reason_IDLE, kExternalWakeupCount, base::TimeDelta());
EXPECT_EQ(kNoActions, delegate_.GetActions());
EXPECT_EQ(0, dbus_wrapper_.num_sent_signals());
// Report success this time and check that the timer isn't running.
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::SUCCESS);
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
EXPECT_NE(kExternalWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_TRUE(delegate_.suspend_was_successful());
EXPECT_EQ(3, delegate_.num_suspend_attempts());
EXPECT_EQ(suspend_id, GetSuspendDoneId(0));
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// Suspend successfully again and check that the number of attempts are
// reported as 1 now.
dbus_wrapper_.ClearSentSignals();
suspender_.RequestSuspend(SuspendImminent_Reason_IDLE, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
const int new_suspend_id = test_api_.suspend_id();
EXPECT_EQ(new_suspend_id, GetSuspendImminentId(0));
EXPECT_EQ(SuspendImminent_Reason_IDLE, GetSuspendImminentReason(0));
dbus_wrapper_.ClearSentSignals();
AnnounceReadyForSuspend(new_suspend_id);
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_was_successful());
EXPECT_EQ(1, delegate_.num_suspend_attempts());
EXPECT_EQ(new_suspend_id, GetSuspendDoneId(0));
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
}
// Tests that the system is shut down after repeated suspend failures.
TEST_F(SuspenderTest, ShutDownAfterRepeatedFailures) {
pref_num_retries_ = 5;
Init();
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::FAILURE);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
// Proceed through all retries, reporting failure each time.
for (int i = 1; i <= pref_num_retries_ - 1; ++i) {
EXPECT_TRUE(test_api_.TriggerResuspendTimeout()) << "Retry #" << i;
EXPECT_EQ(kSuspend, delegate_.GetActions()) << "Retry #" << i;
}
// Check that another suspend request doesn't reset the retry count
// (http://crbug.com/384610).
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kNoActions, delegate_.GetActions());
// After the last failed attempt, the system should shut down immediately.
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(JoinActions(kSuspend, kShutDown, nullptr), delegate_.GetActions());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
}
// Tests that announcing suspend readiness doesn't trigger a call to Suspend()
// if activity that should cancel the current suspend attempt was previously
// received.
TEST_F(SuspenderTest, CancelBeforeSuspend) {
Init();
// User activity should cancel suspending.
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_EQ(test_api_.suspend_id(), GetSuspendImminentId(0));
EXPECT_TRUE(delegate_.suspend_announced());
suspender_.HandleUserActivity();
EXPECT_EQ(test_api_.suspend_id(), GetSuspendDoneId(1));
EXPECT_FALSE(delegate_.suspend_announced());
EXPECT_EQ(kUnprepare, delegate_.GetActions());
EXPECT_FALSE(delegate_.suspend_was_successful());
EXPECT_EQ(0, delegate_.num_suspend_attempts());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kNoActions, delegate_.GetActions());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// The lid being opened should also cancel.
dbus_wrapper_.ClearSentSignals();
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_EQ(test_api_.suspend_id(), GetSuspendImminentId(0));
suspender_.HandleLidOpened();
EXPECT_EQ(test_api_.suspend_id(), GetSuspendDoneId(1));
EXPECT_EQ(kUnprepare, delegate_.GetActions());
EXPECT_FALSE(delegate_.suspend_was_successful());
EXPECT_EQ(0, delegate_.num_suspend_attempts());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kNoActions, delegate_.GetActions());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// A wake notification should also cancel.
dbus_wrapper_.ClearSentSignals();
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_EQ(test_api_.suspend_id(), GetSuspendImminentId(0));
suspender_.HandleWakeNotification();
EXPECT_EQ(test_api_.suspend_id(), GetSuspendDoneId(1));
EXPECT_EQ(kUnprepare, delegate_.GetActions());
EXPECT_FALSE(delegate_.suspend_was_successful());
EXPECT_EQ(0, delegate_.num_suspend_attempts());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kNoActions, delegate_.GetActions());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// The request should also be canceled if the system starts shutting down.
dbus_wrapper_.ClearSentSignals();
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_EQ(test_api_.suspend_id(), GetSuspendImminentId(0));
suspender_.HandleShutdown();
EXPECT_EQ(test_api_.suspend_id(), GetSuspendDoneId(1));
EXPECT_EQ(kUnprepare, delegate_.GetActions());
EXPECT_FALSE(delegate_.suspend_was_successful());
EXPECT_EQ(0, delegate_.num_suspend_attempts());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kNoActions, delegate_.GetActions());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// Subsequent requests after shutdown has started should be ignored.
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kNoActions, delegate_.GetActions());
}
// Tests that a suspend-canceling action (user activity) after a failed suspend
// attempt should remove the retry timeout.
TEST_F(SuspenderTest, CancelAfterSuspend) {
Init();
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::FAILURE);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_EQ(test_api_.suspend_id(), GetSuspendImminentId(0));
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
// Fail a second time.
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(kSuspend, delegate_.GetActions());
// This time, report user activity first, which should cancel the request.
suspender_.HandleUserActivity();
EXPECT_EQ(kUnprepare, delegate_.GetActions());
EXPECT_FALSE(delegate_.suspend_was_successful());
EXPECT_EQ(2, delegate_.num_suspend_attempts());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(test_api_.suspend_id(), GetSuspendDoneId(1));
}
// Tests that Chrome-reported user activity received while suspending with
// a closed lid doesn't abort the suspend attempt (http://crosbug.com/38819).
TEST_F(SuspenderTest, DontCancelForUserActivityWhileLidClosed) {
delegate_.set_lid_closed(true);
Init();
// Report user activity before powerd_suspend is executed and check that
// Suspender still suspends when suspend readiness is announced.
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
suspender_.HandleUserActivity();
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
// Report user activity after powerd_suspend fails and check that the
// resuspend timer isn't stopped.
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::CANCELED);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
suspender_.HandleUserActivity();
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::SUCCESS);
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
// Report user activity after powerd_suspend fails when the system can safely
// wake from dark resume and check that the suspend attempt is not aborted.
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::CANCELED);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
suspender_.HandleUserActivity();
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::SUCCESS);
AnnounceReadyForDarkSuspend(test_api_.dark_suspend_id());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
}
// Tests that announcing suspend readiness doesn't trigger a call to Suspend()
// if user activity that should cancel the current suspend attempt was
// previously received when the device is docked.
TEST_F(SuspenderTest, CancelForUserActivityWhileDocked) {
delegate_.set_lid_closed(true);
Init();
suspender_.HandleDisplayModeChange(DisplayMode::PRESENTATION);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_EQ(test_api_.suspend_id(), GetSuspendImminentId(0));
EXPECT_TRUE(delegate_.suspend_announced());
suspender_.HandleUserActivity();
EXPECT_EQ(test_api_.suspend_id(), GetSuspendDoneId(1));
EXPECT_FALSE(delegate_.suspend_announced());
EXPECT_EQ(kUnprepare, delegate_.GetActions());
EXPECT_FALSE(delegate_.suspend_was_successful());
EXPECT_EQ(0, delegate_.num_suspend_attempts());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kNoActions, delegate_.GetActions());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
}
// Tests that expected wakeup counts passed to
// RequestSuspendWithExternalWakeupCount() are honored.
TEST_F(SuspenderTest, ExternalWakeupCount) {
Init();
// Pass a wakeup count less than the one that the delegate returns.
const uint64_t kWakeupCount = 452;
delegate_.set_wakeup_count(kWakeupCount);
suspender_.RequestSuspendWithExternalWakeupCount(
SuspendImminent_Reason_OTHER, kWakeupCount - 1, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
// Make the delegate report that powerd_suspend reported a wakeup count
// mismatch. Suspender should avoid retrying after a mismatch when using an
// external wakeup count.
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::CANCELED);
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
EXPECT_EQ(kWakeupCount - 1, delegate_.suspend_wakeup_count());
EXPECT_FALSE(delegate_.suspend_was_successful());
EXPECT_EQ(1, delegate_.num_suspend_attempts());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// Send another suspend request with the current wakeup count. Report failure
// and check that the suspend attempt is retried using the external wakeup
// count.
suspender_.RequestSuspendWithExternalWakeupCount(
SuspendImminent_Reason_OTHER, kWakeupCount, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::FAILURE);
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
EXPECT_EQ(kWakeupCount, delegate_.suspend_wakeup_count());
// A retry at this point shouldn't attempt suspend again.
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// Now do a successful suspend and check that another retry isn't scheduled.
suspender_.RequestSuspendWithExternalWakeupCount(
SuspendImminent_Reason_OTHER, kWakeupCount, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::SUCCESS);
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
EXPECT_EQ(kWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
}
// Tests that things don't go haywire when
// Suspender::Delegate::UndoPrepareToSuspend() synchronously starts another
// suspend request. Previously, this could result in the new request being
// started before the previous one had completed.
TEST_F(SuspenderTest, EventReceivedWhileHandlingEvent) {
// Instruct the delegate to send another suspend request when the current one
// finishes.
Init();
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_EQ(test_api_.suspend_id(), GetSuspendImminentId(0));
delegate_.set_completion_callback(
base::Bind(&Suspender::RequestSuspend, base::Unretained(&suspender_),
SuspendImminent_Reason_OTHER, base::TimeDelta()));
// Check that the SuspendDone signal from the first request contains the first
// request's ID, and that a second request was started immediately.
dbus_wrapper_.ClearSentSignals();
const int kOldSuspendId = test_api_.suspend_id();
AnnounceReadyForSuspend(kOldSuspendId);
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, kPrepare, nullptr),
delegate_.GetActions());
EXPECT_EQ(kOldSuspendId, GetSuspendDoneId(0));
const int kNewSuspendId = test_api_.suspend_id();
EXPECT_NE(kOldSuspendId, kNewSuspendId);
EXPECT_EQ(kNewSuspendId, GetSuspendImminentId(1));
// Don't send additional suspend requests automatically.
delegate_.set_completion_callback(base::Closure());
// Finish the second request.
dbus_wrapper_.ClearSentSignals();
AnnounceReadyForSuspend(kNewSuspendId);
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
EXPECT_EQ(kNewSuspendId, GetSuspendDoneId(0));
dbus_wrapper_.ClearSentSignals();
// Now make the delegate's shutdown method report that the system is shutting
// down.
delegate_.set_shutdown_callback(
base::Bind(&Suspender::HandleShutdown, base::Unretained(&suspender_)));
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
shutdown_from_suspend_.set_action(
policy::ShutdownFromSuspendInterface::Action::SHUT_DOWN);
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kShutDown, delegate_.GetActions());
}
// Tests that a SuspendDone signal is emitted at startup and the "suspend
// announced" state is cleared if the delegate claims that a previous suspend
// attempt was abandoned after being announced.
TEST_F(SuspenderTest, SendSuspendDoneAtStartupForAbandonedAttempt) {
delegate_.set_suspend_announced(true);
Init();
SuspendDone proto;
EXPECT_TRUE(
dbus_wrapper_.GetSentSignal(0, kSuspendDoneSignal, &proto, nullptr));
EXPECT_EQ(0, proto.suspend_id());
EXPECT_EQ(base::TimeDelta().ToInternalValue(), proto.suspend_duration());
EXPECT_FALSE(delegate_.suspend_announced());
}
TEST_F(SuspenderTest, DarkResume) {
Init();
const int kWakeupCount = 45;
delegate_.set_wakeup_count(kWakeupCount);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
const int kSuspendId = test_api_.suspend_id();
EXPECT_EQ(kSuspendId, GetSuspendImminentId(0));
shutdown_from_suspend_.set_action(
policy::ShutdownFromSuspendInterface::Action::SUSPEND);
dark_resume_.set_in_dark_resume(true);
dbus_wrapper_.ClearSentSignals();
AnnounceReadyForSuspend(kSuspendId);
const int64_t kDarkSuspendId = test_api_.dark_suspend_id();
EXPECT_EQ(kDarkSuspendId, GetDarkSuspendImminentId(0));
EXPECT_EQ(kSuspend, delegate_.GetActions());
EXPECT_EQ(kWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
// The system should resuspend but without using the wakeup count this time.
// Make it do a normal resume.
dark_resume_.set_in_dark_resume(false);
AnnounceReadyForDarkSuspend(kDarkSuspendId);
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
EXPECT_EQ(kSuspendId, GetSuspendDoneId(1));
}
TEST_F(SuspenderTest, DarkResumeShutDown) {
Init();
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
shutdown_from_suspend_.set_action(
policy::ShutdownFromSuspendInterface::Action::SHUT_DOWN);
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kShutDown, delegate_.GetActions());
}
TEST_F(SuspenderTest, DarkResumeRetry) {
pref_num_retries_ = 2;
Init();
const uint64_t kOrigWakeupCount = 42;
delegate_.set_wakeup_count(kOrigWakeupCount);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
const uint64_t kDarkWakeupCount = 71;
delegate_.set_wakeup_count(kDarkWakeupCount);
shutdown_from_suspend_.set_action(
policy::ShutdownFromSuspendInterface::Action::SUSPEND);
dark_resume_.set_in_dark_resume(true);
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
EXPECT_EQ(kOrigWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
// Now make two resuspend attempts while in dark resume fail and a third
// attempt succeed. The successful attempt should reset the retry counter.
const uint64_t kOnReadyWakeupCount = 102;
delegate_.set_wakeup_count(kOnReadyWakeupCount);
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::FAILURE);
AnnounceReadyForDarkSuspend(test_api_.dark_suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
// In dark resume, we read the wakeup count after all delays have reported
// ready. This is because the wakeup count may naturally increase due to the
// work that clients do in dark resume and we don't want that to incorrectly
// make the suspend attempt fail.
EXPECT_EQ(kOnReadyWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
const uint64_t kRetryWakeupCount = 105;
delegate_.set_wakeup_count(kRetryWakeupCount);
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(kSuspend, delegate_.GetActions());
EXPECT_EQ(kOnReadyWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::SUCCESS);
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(kSuspend, delegate_.GetActions());
EXPECT_EQ(kRetryWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
// Fail to resuspend one time short of the retry limit.
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::FAILURE);
AnnounceReadyForDarkSuspend(test_api_.dark_suspend_id());
for (int i = 0; i < pref_num_retries_; ++i) {
SCOPED_TRACE(base::StringPrintf("Attempt #%d", i));
EXPECT_EQ(kSuspend, delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
}
// The next failure should result in the system shutting down.
EXPECT_EQ(JoinActions(kSuspend, kShutDown, nullptr), delegate_.GetActions());
}
TEST_F(SuspenderTest, DarkResumeCancelBeforeResuspend) {
Init();
shutdown_from_suspend_.set_action(
policy::ShutdownFromSuspendInterface::Action::SUSPEND);
// Simulate being in dark resume after each suspend attempt.
delegate_.set_suspend_callback(base::Bind(
[](system::DarkResumeStub* dark_resume) {
dark_resume->set_in_dark_resume(true);
},
&dark_resume_));
// User activity should trigger the transition to fully resumed.
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
suspender_.HandleUserActivity();
EXPECT_EQ(test_api_.suspend_id(), GetSuspendDoneId(2));
EXPECT_FALSE(delegate_.suspend_announced());
EXPECT_EQ(kUnprepare, delegate_.GetActions());
EXPECT_FALSE(delegate_.suspend_was_successful());
EXPECT_EQ(1, delegate_.num_suspend_attempts());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// Clear dark resume state now that the device is transitioned to State::IDLE.
// This mimics real world behavior.
dark_resume_.set_in_dark_resume(false);
// Opening the lid should also trigger the transition.
dbus_wrapper_.ClearSentSignals();
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
suspender_.HandleLidOpened();
EXPECT_EQ(test_api_.suspend_id(), GetSuspendDoneId(2));
EXPECT_FALSE(delegate_.suspend_announced());
EXPECT_EQ(kUnprepare, delegate_.GetActions());
EXPECT_FALSE(delegate_.suspend_was_successful());
EXPECT_EQ(1, delegate_.num_suspend_attempts());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// Clear dark resume state now that the device is transitioned to State::IDLE.
// This mimics real world behavior.
dark_resume_.set_in_dark_resume(false);
// A wake notification should also trigger the transition.
dbus_wrapper_.ClearSentSignals();
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
suspender_.HandleWakeNotification();
EXPECT_EQ(test_api_.suspend_id(), GetSuspendDoneId(2));
EXPECT_FALSE(delegate_.suspend_announced());
EXPECT_EQ(kUnprepare, delegate_.GetActions());
EXPECT_FALSE(delegate_.suspend_was_successful());
EXPECT_EQ(1, delegate_.num_suspend_attempts());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// Clear dark resume state now that the device is transitioned to State::IDLE.
// This mimics real world behavior.
dark_resume_.set_in_dark_resume(false);
// Shutting down the system will also trigger the transition so that clients
// can perform cleanup.
dbus_wrapper_.ClearSentSignals();
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
// Dark resume is true at this point.
suspender_.HandleShutdown();
EXPECT_EQ(test_api_.suspend_id(), GetSuspendDoneId(2));
EXPECT_FALSE(delegate_.suspend_announced());
EXPECT_EQ(kUnprepare, delegate_.GetActions());
EXPECT_FALSE(delegate_.suspend_was_successful());
EXPECT_EQ(1, delegate_.num_suspend_attempts());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
}
// Test that we re-run the registered dark suspend delays if a resuspend attempt
// is canceled due to a wake event but not if the suspend failed for any other
// reason.
TEST_F(SuspenderTest, RerunDarkSuspendDelaysForCanceledSuspend) {
Init();
shutdown_from_suspend_.set_action(
policy::ShutdownFromSuspendInterface::Action::SUSPEND);
// Simulate being in dark resume after each suspend attempt.
delegate_.set_suspend_callback(base::Bind(
[](system::DarkResumeStub* dark_resume) {
dark_resume->set_in_dark_resume(true);
},
&dark_resume_));
// Do the initial suspend.
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
dbus_wrapper_.ClearSentSignals();
// The resuspend attempt is canceled due to a wake event.
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::CANCELED);
AnnounceReadyForDarkSuspend(test_api_.dark_suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
EXPECT_TRUE(dbus_wrapper_.GetSentSignal(0, kDarkSuspendImminentSignal,
nullptr, nullptr));
dbus_wrapper_.ClearSentSignals();
// The resuspend attempt fails due to a transient kernel error.
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::FAILURE);
AnnounceReadyForDarkSuspend(test_api_.dark_suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
EXPECT_EQ(0, dbus_wrapper_.num_sent_signals());
// The resuspend attempt is finally sucessful. Reset the suspend callback so
// that dark resume is not set to true after Suspend() runs.
delegate_.set_suspend_callback(base::Closure());
dark_resume_.set_in_dark_resume(false);
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::SUCCESS);
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
}
// Test that we report dark resume metrics only if it is enabled.
TEST_F(SuspenderTest, GenerateDarkResumeMetricsOnlyIfEnabled) {
Init();
// Don't report metrics when dark resume is disabled.
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(JoinActions(kPrepare, kSuspend, kUnprepare, nullptr),
delegate_.GetActions());
// Report metrics when dark resume is enabled.
dark_resume_.set_enabled(true);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(JoinActions(kPrepare, kSuspend, kUnprepare,
kGenerateDarkResumeMetrics, nullptr),
delegate_.GetActions());
}
// Tests that dark resume wake data is correct when no dark resumes occur.
TEST_F(SuspenderTest, DarkResumeWakeDataNoDarkResume) {
Init();
dark_resume_.set_enabled(true);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
const base::TimeDelta kSuspendDuration = base::TimeDelta::FromMinutes(24);
delegate_.set_suspend_advance_time(kSuspendDuration);
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(0, delegate_.dark_resume_wake_durations().size());
EXPECT_EQ(kSuspendDuration, delegate_.last_suspend_duration());
}
// Tests that dark resume wake data is correct when one dark resume occurs.
TEST_F(SuspenderTest, DarkResumeWakeDataOneDarkResume) {
Init();
dark_resume_.set_enabled(true);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
dark_resume_.set_in_dark_resume(true);
const base::TimeDelta kInitialSuspendDuration =
base::TimeDelta::FromMinutes(7);
delegate_.set_suspend_advance_time(kInitialSuspendDuration);
AnnounceReadyForSuspend(test_api_.suspend_id());
// Simulate the system being briefly awake in the dark resume state.
const base::TimeDelta kDarkResumeDuration =
base::TimeDelta::FromMilliseconds(4566);
test_api_.clock()->advance_current_boot_time_for_testing(kDarkResumeDuration);
dark_resume_.set_in_dark_resume(false);
const base::TimeDelta kDarkSuspendDuration = base::TimeDelta::FromMinutes(3);
delegate_.set_suspend_advance_time(kDarkSuspendDuration);
AnnounceReadyForDarkSuspend(test_api_.dark_suspend_id());
ASSERT_EQ(1, delegate_.dark_resume_wake_durations().size());
EXPECT_STREQ(test_api_.GetDefaultWakeReason().c_str(),
delegate_.dark_resume_wake_durations().at(0).first.c_str());
EXPECT_EQ(kDarkResumeDuration,
delegate_.dark_resume_wake_durations().at(0).second);
EXPECT_EQ(
kInitialSuspendDuration + kDarkResumeDuration + kDarkSuspendDuration,
delegate_.last_suspend_duration());
}
// Tests that dark resume wake data is correct when the initial resuspend fails.
TEST_F(SuspenderTest, DarkResumeWakeDataFailedResuspend) {
Init();
dark_resume_.set_enabled(true);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
dark_resume_.set_in_dark_resume(true);
const base::TimeDelta kInitialSuspendDuration =
base::TimeDelta::FromMinutes(7);
delegate_.set_suspend_advance_time(kInitialSuspendDuration);
AnnounceReadyForSuspend(test_api_.suspend_id());
// Simulate the system being briefly awake in the dark resume state.
const base::TimeDelta kDarkResumeDuration = base::TimeDelta::FromSeconds(5);
test_api_.clock()->advance_current_boot_time_for_testing(kDarkResumeDuration);
// Now simulate a failed resuspend.
const std::string kWakeReason = "WiFi.Pattern";
RecordDarkResumeWakeReason(kWakeReason);
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::CANCELED);
delegate_.set_suspend_advance_time(base::TimeDelta());
AnnounceReadyForDarkSuspend(test_api_.dark_suspend_id());
// Finally, resuspend successfully after 10 seconds.
dark_resume_.set_in_dark_resume(false);
const base::TimeDelta kResuspendRetryDuration =
base::TimeDelta::FromSeconds(10);
test_api_.clock()->advance_current_boot_time_for_testing(
kResuspendRetryDuration);
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::SUCCESS);
const base::TimeDelta kResuspendDuration = base::TimeDelta::FromMinutes(3);
delegate_.set_suspend_advance_time(kResuspendDuration);
AnnounceReadyForDarkSuspend(test_api_.dark_suspend_id());
// The wake reason should be reported, and the dark resume wake duration
// should include both the time in dark resume and the time waiting to
// resuspend after the failed initial attempt.
ASSERT_EQ(1, delegate_.dark_resume_wake_durations().size());
EXPECT_STREQ(kWakeReason.c_str(),
delegate_.dark_resume_wake_durations().at(0).first.c_str());
EXPECT_EQ(kDarkResumeDuration + kResuspendRetryDuration,
delegate_.dark_resume_wake_durations().at(0).second);
EXPECT_EQ(kInitialSuspendDuration + kDarkResumeDuration +
kResuspendRetryDuration + kResuspendDuration,
delegate_.last_suspend_duration());
}
TEST_F(SuspenderTest, ReportInitialSuspendAttempts) {
Init();
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, base::TimeDelta());
EXPECT_EQ(kPrepare, delegate_.GetActions());
// Suspend successfully once and do a dark resume.
shutdown_from_suspend_.set_action(
policy::ShutdownFromSuspendInterface::Action::SUSPEND);
dark_resume_.set_in_dark_resume(true);
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
// Report failure for the first attempt to resuspend from dark resume; then
// report success for the second attempt.
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::FAILURE);
AnnounceReadyForDarkSuspend(test_api_.dark_suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
dark_resume_.set_in_dark_resume(false);
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::SUCCESS);
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
// Check that the single initial suspend attempt is reported rather than the
// two attempts that occurred while in dark resume.
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
EXPECT_TRUE(delegate_.suspend_was_successful());
EXPECT_EQ(1, delegate_.num_suspend_attempts());
}
// Tests the standard suspend/resume cycle with a wakeup timeout and wakeup
// count.
TEST_F(SuspenderTest, SuspendWakeupTimeout) {
Init();
const uint64_t kWakeupCount = 452;
delegate_.set_wakeup_count(kWakeupCount);
const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(5);
suspender_.RequestSuspendWithExternalWakeupCount(SuspendImminent_Reason_OTHER,
kWakeupCount, kDuration);
const int suspend_id = test_api_.suspend_id();
EXPECT_EQ(suspend_id, GetSuspendImminentId(0));
EXPECT_EQ(SuspendImminent_Reason_OTHER, GetSuspendImminentReason(0));
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_announced());
// Simulate suspending.
delegate_.set_suspend_advance_time(kDuration);
// When Suspender receives notice that the system is ready to be
// suspended, it should immediately suspend the system.
AnnounceReadyForSuspend(suspend_id);
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
EXPECT_EQ(kWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
EXPECT_TRUE(delegate_.suspend_was_successful());
// Suspender shall pass a right duration to delegate
EXPECT_EQ(kDuration, delegate_.suspend_duration());
EXPECT_EQ(1, delegate_.num_suspend_attempts());
// A resuspend timeout shouldn't be set.
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
}
// Tests the standard suspend/resume cycle with a wakeup timeout but no wakeup
// count.
TEST_F(SuspenderTest, SuspendWakeupTimeoutNoWakeupCount) {
Init();
const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(5);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, kDuration);
const int suspend_id = test_api_.suspend_id();
EXPECT_EQ(suspend_id, GetSuspendImminentId(0));
EXPECT_EQ(SuspendImminent_Reason_OTHER, GetSuspendImminentReason(0));
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_announced());
// Simulate suspending.
delegate_.set_suspend_advance_time(kDuration);
// When Suspender receives notice that the system is ready to be
// suspended, it should immediately suspend the system.
AnnounceReadyForSuspend(suspend_id);
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, nullptr), delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_was_successful());
// Suspender shall pass a right duration to delegate
EXPECT_EQ(kDuration, delegate_.suspend_duration());
EXPECT_EQ(1, delegate_.num_suspend_attempts());
// A resuspend timeout shouldn't be set.
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
}
// Tests that suspend is retried on failure.
TEST_F(SuspenderTest, SuspendWakeupTimoutRetryOnFailure) {
Init();
const uint64_t kWakeupCount = 46;
delegate_.set_wakeup_count(kWakeupCount);
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::FAILURE);
const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(7);
suspender_.RequestSuspend(SuspendImminent_Reason_OTHER, kDuration);
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_announced());
AnnounceReadyForSuspend(test_api_.suspend_id());
// Before the next attempt, suspender should keep the assigned duration
EXPECT_EQ(kDuration, delegate_.suspend_duration());
// Simulate a successful suspend attempt then.
delegate_.set_suspend_advance_time(kDuration);
delegate_.set_suspend_result(Suspender::Delegate::SuspendResult::SUCCESS);
// The timeout should trigger another suspend attempt.
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(kWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
EXPECT_TRUE(delegate_.suspend_was_successful());
EXPECT_EQ(kDuration, delegate_.suspend_duration());
// There shall be two attempts.
EXPECT_EQ(2, delegate_.num_suspend_attempts());
// A resuspend timeout shouldn't be set.
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// Simulate another suspend request, validate empty duration passed in
suspender_.RequestSuspend(SuspendImminent_Reason_IDLE, base::TimeDelta());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(base::TimeDelta(), delegate_.suspend_duration());
}
} // namespace policy
} // namespace power_manager