blob: 32b8544d6543f6bc18597e7d327a049e9f704ba2 [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/dbus_sender_stub.h"
#include "power_manager/common/fake_prefs.h"
#include "power_manager/common/power_constants.h"
#include "power_manager/powerd/system/dark_resume_stub.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 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()
: lid_closed_(false),
report_success_for_read_wakeup_count_(true),
suspend_result_(SUSPEND_SUCCESSFUL),
wakeup_count_(0),
suspend_announced_(false),
suspend_wakeup_count_(0),
suspend_wakeup_count_valid_(false),
suspend_was_successful_(false),
num_suspend_attempts_(0),
suspend_canceled_while_in_dark_resume_(false),
can_safely_exit_dark_resume_(true) {
}
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_suspend_callback(base::Closure callback) {
suspend_callback_ = callback;
}
void set_completion_callback(base::Closure callback) {
completion_callback_ = callback;
}
void set_shutdown_callback(base::Closure callback) {
shutdown_callback_ = callback;
}
void set_can_safely_exit_dark_resume(bool can_exit) {
can_safely_exit_dark_resume_ = can_exit;
}
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_; }
bool suspend_canceled_while_in_dark_resume() const {
return suspend_canceled_while_in_dark_resume_;
}
// 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;
RunAndResetCallback(&suspend_callback_);
return suspend_result_;
}
void UndoPrepareToSuspend(bool success,
int num_suspend_attempts,
bool canceled_while_in_dark_resume) override {
AppendAction(kUnprepare);
suspend_was_successful_ = success;
num_suspend_attempts_ = num_suspend_attempts;
suspend_canceled_while_in_dark_resume_ = canceled_while_in_dark_resume;
RunAndResetCallback(&completion_callback_);
}
void ShutDownForFailedSuspend() override {
AppendAction(kShutDown);
RunAndResetCallback(&shutdown_callback_);
}
void ShutDownForDarkResume() override {
AppendAction(kShutDown);
RunAndResetCallback(&shutdown_callback_);
}
bool CanSafelyExitDarkResume() override {
return can_safely_exit_dark_resume_;
}
private:
// If |callback| is non-null, runs and resets it.
static void RunAndResetCallback(base::Closure* callback) {
if (callback->is_null())
return;
base::Closure callback_copy = *callback;
callback->Reset();
callback_copy.Run();
}
// Value returned by IsLidClosedForSuspend().
bool lid_closed_;
// Should ReadSuspendWakeupCount() and DoSuspend() report success?
bool report_success_for_read_wakeup_count_;
SuspendResult suspend_result_;
// Count that should be returned by ReadSuspendWakeupCount().
uint64_t wakeup_count_;
// Updated by SetSuspendAnnounced() and returned by GetSuspendAnnounced().
bool suspend_announced_;
// Callback that will be run once (if non-null) when DoSuspend() is called.
base::Closure suspend_callback_;
// Callback that will be run once (if non-null) when
// UndoPrepareToSuspend() is called.
base::Closure completion_callback_;
// Callback that will be run once (if non-null) when ShutDown*() is called.
base::Closure shutdown_callback_;
// Arguments passed to last invocation of DoSuspend().
uint64_t suspend_wakeup_count_;
bool suspend_wakeup_count_valid_;
base::TimeDelta suspend_duration_;
// Arguments passed to last invocation of UndoPrepareToSuspend().
bool suspend_was_successful_;
int num_suspend_attempts_;
bool suspend_canceled_while_in_dark_resume_;
// Value returned by CanSafelyExitDarkResume().
bool can_safely_exit_dark_resume_;
DISALLOW_COPY_AND_ASSIGN(TestDelegate);
};
} // namespace
class SuspenderTest : public testing::Test {
public:
SuspenderTest()
: test_api_(&suspender_),
pref_retry_delay_ms_(10000),
pref_num_retries_(10) {
}
protected:
void Init() {
prefs_.SetInt64(kRetrySuspendMsPref, pref_retry_delay_ms_);
prefs_.SetInt64(kRetrySuspendAttemptsPref, pref_num_retries_);
suspender_.Init(&delegate_, &dbus_sender_, &dark_resume_, &prefs_);
}
// Returns the ID from a SuspendImminent signal at |position|, or -1 if the
// signal wasn't sent.
int GetSuspendImminentId(int position) {
SuspendImminent proto;
if (!dbus_sender_.GetSentSignal(position, kSuspendImminentSignal, &proto))
return -1;
return proto.suspend_id();
}
// Returns the ID from a DarkSuspendImminent signal at |position|, or -1 if
// the signal wasn't sent.
int GetDarkSuspendImminentId(int position) {
SuspendImminent proto;
if (!dbus_sender_.GetSentSignal(
position, kDarkSuspendImminentSignal, &proto))
return -1;
return proto.suspend_id();
}
// Returns the ID from a SuspendDone signal at |position|, or -1 if the signal
// wasn't sent.
int GetSuspendDoneId(int position) {
SuspendDone proto;
if (!dbus_sender_.GetSentSignal(position, kSuspendDoneSignal, &proto))
return -1;
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);
}
FakePrefs prefs_;
TestDelegate delegate_;
DBusSenderStub dbus_sender_;
system::DarkResumeStub dark_resume_;
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;
const base::Time kRequestTime = base::Time::FromInternalValue(123);
test_api_.SetCurrentWallTime(kRequestTime);
delegate_.set_wakeup_count(kWakeupCount);
suspender_.RequestSuspend();
const int suspend_id = test_api_.suspend_id();
EXPECT_EQ(suspend_id, GetSuspendImminentId(0));
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_announced());
// Advance the time and register a callback to advance the time again
// when the suspend request is received.
const base::Time kSuspendTime = base::Time::FromInternalValue(301);
test_api_.SetCurrentWallTime(kSuspendTime);
const base::Time kResumeTime = base::Time::FromInternalValue(567);
delegate_.set_suspend_callback(
base::Bind(&Suspender::TestApi::SetCurrentWallTime,
base::Unretained(&test_api_), kResumeTime));
// When Suspender receives notice that the system is ready to be
// suspended, it should immediately suspend the system.
dbus_sender_.ClearSentSignals();
AnnounceReadyForSuspend(suspend_id);
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), 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());
EXPECT_FALSE(delegate_.suspend_canceled_while_in_dark_resume());
// A SuspendDone signal should be emitted to announce that the attempt is
// complete.
SuspendDone done_proto;
EXPECT_TRUE(dbus_sender_.GetSentSignal(0, kSuspendDoneSignal, &done_proto));
EXPECT_EQ(suspend_id, done_proto.suspend_id());
EXPECT_EQ((kResumeTime - kRequestTime).ToInternalValue(),
done_proto.suspend_duration());
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();
EXPECT_EQ(kPrepare, delegate_.GetActions());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), 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();
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();
EXPECT_EQ(kNoActions, delegate_.GetActions());
EXPECT_EQ(orig_suspend_id, test_api_.suspend_id());
}
// 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::SUSPEND_FAILED);
suspender_.RequestSuspend();
const int suspend_id = test_api_.suspend_id();
EXPECT_EQ(suspend_id, GetSuspendImminentId(0));
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_announced());
const uint64_t kRetryWakeupCount = 67;
delegate_.set_wakeup_count(kRetryWakeupCount);
dbus_sender_.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_sender_.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_sender_.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(kExternalWakeupCount);
EXPECT_EQ(kNoActions, delegate_.GetActions());
EXPECT_EQ(0, dbus_sender_.num_sent_signals());
// Report success this time and check that the timer isn't running.
delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_SUCCESSFUL);
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
EXPECT_NE(kExternalWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_TRUE(delegate_.suspend_was_successful());
EXPECT_EQ(3, delegate_.num_suspend_attempts());
EXPECT_FALSE(delegate_.suspend_canceled_while_in_dark_resume());
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_sender_.ClearSentSignals();
suspender_.RequestSuspend();
EXPECT_EQ(kPrepare, delegate_.GetActions());
const int new_suspend_id = test_api_.suspend_id();
EXPECT_EQ(new_suspend_id, GetSuspendImminentId(0));
dbus_sender_.ClearSentSignals();
AnnounceReadyForSuspend(new_suspend_id);
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_was_successful());
EXPECT_EQ(1, delegate_.num_suspend_attempts());
EXPECT_FALSE(delegate_.suspend_canceled_while_in_dark_resume());
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::SUSPEND_FAILED);
suspender_.RequestSuspend();
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();
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, NULL), 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) {
// This test doesn't exercise the dark resume code. Make sure that resuspend
// attempts are still handled correctly on kernels that can't exit dark
// resume: http://crbug.com/406512
delegate_.set_can_safely_exit_dark_resume(false);
Init();
// User activity should cancel suspending.
suspender_.RequestSuspend();
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());
EXPECT_FALSE(delegate_.suspend_canceled_while_in_dark_resume());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kNoActions, delegate_.GetActions());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// The lid being opened should also cancel.
dbus_sender_.ClearSentSignals();
suspender_.RequestSuspend();
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());
EXPECT_FALSE(delegate_.suspend_canceled_while_in_dark_resume());
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_sender_.ClearSentSignals();
suspender_.RequestSuspend();
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());
EXPECT_FALSE(delegate_.suspend_canceled_while_in_dark_resume());
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();
EXPECT_EQ(kNoActions, delegate_.GetActions());
}
// Tests that a suspend-canceling action after a failed suspend attempt
// should remove the retry timeout.
TEST_F(SuspenderTest, CancelAfterSuspend) {
Init();
delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_FAILED);
suspender_.RequestSuspend();
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(delegate_.suspend_canceled_while_in_dark_resume());
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();
EXPECT_EQ(kPrepare, delegate_.GetActions());
suspender_.HandleUserActivity();
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
// Report user activity after powerd_suspend fails and check that the
// resuspend timer isn't stopped.
delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_CANCELED);
suspender_.RequestSuspend();
EXPECT_EQ(kPrepare, delegate_.GetActions());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
suspender_.HandleUserActivity();
delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_SUCCESSFUL);
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
}
// 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(kWakeupCount - 1);
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::SUSPEND_CANCELED);
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), 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(delegate_.suspend_canceled_while_in_dark_resume());
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(kWakeupCount);
EXPECT_EQ(kPrepare, delegate_.GetActions());
delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_FAILED);
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
EXPECT_EQ(kWakeupCount, delegate_.suspend_wakeup_count());
// Let the retry succeed and check that another retry isn't scheduled.
delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_SUCCESSFUL);
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
EXPECT_EQ(kWakeupCount, delegate_.suspend_wakeup_count());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
}
// Tests that the SuspendDone signal contains a zero duration rather than a
// negative duration if the system clock jumps backward between suspend and
// resume.
TEST_F(SuspenderTest, SystemClockGoesBackward) {
Init();
test_api_.SetCurrentWallTime(base::Time::FromInternalValue(5000));
suspender_.RequestSuspend();
delegate_.set_suspend_callback(
base::Bind(&Suspender::TestApi::SetCurrentWallTime,
base::Unretained(&test_api_),
base::Time::FromInternalValue(1000)));
dbus_sender_.ClearSentSignals();
AnnounceReadyForSuspend(test_api_.suspend_id());
SuspendDone done_proto;
EXPECT_TRUE(dbus_sender_.GetSentSignal(0, kSuspendDoneSignal, &done_proto));
EXPECT_EQ(base::TimeDelta().ToInternalValue(), done_proto.suspend_duration());
}
// 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();
EXPECT_EQ(kPrepare, delegate_.GetActions());
EXPECT_EQ(test_api_.suspend_id(), GetSuspendImminentId(0));
delegate_.set_completion_callback(
base::Bind(&Suspender::RequestSuspend, base::Unretained(&suspender_)));
// Check that the SuspendDone signal from the first request contains the first
// request's ID, and that a second request was started immediately.
dbus_sender_.ClearSentSignals();
const int kOldSuspendId = test_api_.suspend_id();
AnnounceReadyForSuspend(kOldSuspendId);
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, kPrepare, NULL),
delegate_.GetActions());
EXPECT_EQ(kOldSuspendId, GetSuspendDoneId(0));
const int kNewSuspendId = test_api_.suspend_id();
EXPECT_NE(kOldSuspendId, kNewSuspendId);
EXPECT_EQ(kNewSuspendId, GetSuspendImminentId(1));
// Finish the second request.
dbus_sender_.ClearSentSignals();
AnnounceReadyForSuspend(kNewSuspendId);
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
EXPECT_EQ(kNewSuspendId, GetSuspendDoneId(0));
dbus_sender_.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();
EXPECT_EQ(kPrepare, delegate_.GetActions());
dark_resume_.set_action(system::DarkResumeInterface::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_sender_.GetSentSignal(0, kSuspendDoneSignal, &proto));
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();
EXPECT_EQ(kPrepare, delegate_.GetActions());
const int kSuspendId = test_api_.suspend_id();
EXPECT_EQ(kSuspendId, GetSuspendImminentId(0));
// Instruct |dark_resume_| to request a ten-second suspend and report that the
// system did a dark resume.
const int64_t kSuspendSec = 10;
dark_resume_.set_action(system::DarkResumeInterface::SUSPEND);
dark_resume_.set_in_dark_resume(true);
dark_resume_.set_suspend_duration(base::TimeDelta::FromSeconds(kSuspendSec));
dbus_sender_.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());
EXPECT_EQ(kSuspendSec, delegate_.suspend_duration().InSeconds());
// The system should resuspend for another ten seconds, 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, NULL), delegate_.GetActions());
EXPECT_TRUE(delegate_.suspend_wakeup_count_valid());
EXPECT_EQ(kSuspendSec, delegate_.suspend_duration().InSeconds());
EXPECT_EQ(kSuspendId, GetSuspendDoneId(1));
}
TEST_F(SuspenderTest, DarkResumeShutDown) {
Init();
suspender_.RequestSuspend();
EXPECT_EQ(kPrepare, delegate_.GetActions());
dark_resume_.set_action(system::DarkResumeInterface::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();
EXPECT_EQ(kPrepare, delegate_.GetActions());
// Suspend for ten seconds.
const int64_t kSuspendSec = 10;
const uint64_t kDarkWakeupCount = 71;
delegate_.set_wakeup_count(kDarkWakeupCount);
dark_resume_.set_action(system::DarkResumeInterface::SUSPEND);
dark_resume_.set_in_dark_resume(true);
dark_resume_.set_suspend_duration(base::TimeDelta::FromSeconds(kSuspendSec));
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::SUSPEND_FAILED);
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::SUSPEND_SUCCESSFUL);
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::SUSPEND_FAILED);
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_EQ(kSuspendSec, delegate_.suspend_duration().InSeconds());
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
}
// The next failure should result in the system shutting down.
EXPECT_EQ(JoinActions(kSuspend, kShutDown, NULL), delegate_.GetActions());
}
TEST_F(SuspenderTest, DarkResumeCancelBeforeResuspend) {
Init();
// Suspend for 10 seconds.
const int64_t kSuspendSec = 10;
dark_resume_.set_action(system::DarkResumeInterface::SUSPEND);
dark_resume_.set_in_dark_resume(true);
dark_resume_.set_suspend_duration(base::TimeDelta::FromSeconds(kSuspendSec));
// User activity should trigger the transition to fully resumed.
suspender_.RequestSuspend();
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_TRUE(delegate_.suspend_canceled_while_in_dark_resume());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// Opening the lid should also trigger the transition.
dbus_sender_.ClearSentSignals();
suspender_.RequestSuspend();
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_TRUE(delegate_.suspend_canceled_while_in_dark_resume());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
// Shutting down the system will also trigger the transition so that clients
// can perform cleanup.
dbus_sender_.ClearSentSignals();
suspender_.RequestSuspend();
EXPECT_EQ(kPrepare, delegate_.GetActions());
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
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_TRUE(delegate_.suspend_canceled_while_in_dark_resume());
EXPECT_FALSE(test_api_.TriggerResuspendTimeout());
}
// Tests that user activity is ignored and that no dbus signals are sent out
// during dark resume on legacy systems.
TEST_F(SuspenderTest, DarkResumeOnLegacySystems) {
Init();
// Systems with older kernels cannot safely transition from dark resume to
// fully resumed.
delegate_.set_can_safely_exit_dark_resume(false);
// Suspend for 10 seconds.
const int64_t kSuspendSec = 10;
dark_resume_.set_action(system::DarkResumeInterface::SUSPEND);
dark_resume_.set_suspend_duration(base::TimeDelta::FromSeconds(kSuspendSec));
// User activity should be ignored.
dark_resume_.set_in_dark_resume(true);
suspender_.RequestSuspend();
EXPECT_EQ(kPrepare, delegate_.GetActions());
dbus_sender_.ClearSentSignals();
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
EXPECT_EQ(0, dbus_sender_.num_sent_signals());
suspender_.HandleUserActivity();
EXPECT_EQ(0, dbus_sender_.num_sent_signals());
EXPECT_TRUE(delegate_.suspend_announced());
EXPECT_EQ(kNoActions, delegate_.GetActions());
dark_resume_.set_in_dark_resume(false);
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
// Opening the lid should also be ignored.
dbus_sender_.ClearSentSignals();
dark_resume_.set_in_dark_resume(true);
suspender_.RequestSuspend();
EXPECT_EQ(kPrepare, delegate_.GetActions());
dbus_sender_.ClearSentSignals();
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
EXPECT_EQ(0, dbus_sender_.num_sent_signals());
suspender_.HandleLidOpened();
EXPECT_EQ(0, dbus_sender_.num_sent_signals());
EXPECT_TRUE(delegate_.suspend_announced());
EXPECT_EQ(kNoActions, delegate_.GetActions());
dark_resume_.set_in_dark_resume(false);
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), delegate_.GetActions());
// Shutting down the system will not trigger a transition.
dbus_sender_.ClearSentSignals();
dark_resume_.set_in_dark_resume(true);
suspender_.RequestSuspend();
EXPECT_EQ(kPrepare, delegate_.GetActions());
dbus_sender_.ClearSentSignals();
AnnounceReadyForSuspend(test_api_.suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
EXPECT_EQ(0, dbus_sender_.num_sent_signals());
suspender_.HandleShutdown();
EXPECT_EQ(0, dbus_sender_.num_sent_signals());
EXPECT_TRUE(delegate_.suspend_announced());
EXPECT_EQ(kNoActions, delegate_.GetActions());
dark_resume_.set_in_dark_resume(false);
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(kNoActions, delegate_.GetActions());
}
TEST_F(SuspenderTest, ReportInitialSuspendAttempts) {
Init();
suspender_.RequestSuspend();
EXPECT_EQ(kPrepare, delegate_.GetActions());
// Suspend successfully once and do a dark resume.
dark_resume_.set_action(system::DarkResumeInterface::SUSPEND);
dark_resume_.set_in_dark_resume(true);
dark_resume_.set_suspend_duration(base::TimeDelta::FromSeconds(10));
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::SUSPEND_FAILED);
AnnounceReadyForDarkSuspend(test_api_.dark_suspend_id());
EXPECT_EQ(kSuspend, delegate_.GetActions());
dark_resume_.set_in_dark_resume(false);
delegate_.set_suspend_result(Suspender::Delegate::SUSPEND_SUCCESSFUL);
EXPECT_TRUE(test_api_.TriggerResuspendTimeout());
EXPECT_EQ(JoinActions(kSuspend, kUnprepare, NULL), 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());
EXPECT_FALSE(delegate_.suspend_canceled_while_in_dark_resume());
}
} // namespace policy
} // namespace power_manager