blob: 35f9d00f011f55a67b949db9c23e3c487e4a3f31 [file] [log] [blame]
// Copyright 2021 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/common/fake_prefs.h"
#include "power_manager/common/power_constants.h"
#include "power_manager/powerd/system/suspend_freezer.h"
#include <algorithm>
#include <cstring>
#include <map>
#include <set>
#include <string>
#include <base/files/file_util.h>
#include <base/stl_util.h>
#include <gtest/gtest.h>
namespace power_manager {
namespace system {
namespace {
const base::FilePath kTestPath("/sys/fs/cgroup/freezer/test");
// Testing abstraction that pretends to perform file actions and allows setting
// responses.
class MockSystemUtils : public SuspendFreezer::SystemUtilsInterface {
public:
bool PathExists(const base::FilePath& path) override { return true; }
bool ReadFileToString(const base::FilePath& path,
std::string* contents) override {
if (path == kWakeupCountPath) {
*contents = "1";
return true;
}
if (file_contents_.find(path) == file_contents_.end())
return false;
*contents = file_contents_[path];
return true;
}
int WriteFile(const base::FilePath& path,
const char* data,
int size) override {
if (file_contents_.find(path) == file_contents_.end() || permission_fail_)
return -1;
if (set_write_) {
file_contents_[path] = data;
if (strcmp(data, kFreezerStateFrozen) == 0)
freeze_order_.push_back(path);
}
return size;
}
void GetSubDirs(const base::FilePath& root_path,
std::vector<base::FilePath>* dirs) override {
if (root_path != base::FilePath(kBasePath))
return;
for (auto cgroup : file_contents_) {
dirs->push_back(cgroup.first.DirName());
}
}
bool set_write_;
bool permission_fail_;
std::map<base::FilePath, std::string> file_contents_;
std::vector<base::FilePath> freeze_order_;
};
} // namespace
class SuspendFreezerTest : public ::testing::Test {
public:
SuspendFreezerTest()
: mock_sys_utils_(new MockSystemUtils),
suspend_freezer_(),
test_state_(kTestPath.Append(kStateFile)) {
mock_sys_utils_->set_write_ = true;
mock_sys_utils_->permission_fail_ = false;
mock_sys_utils_->file_contents_[test_state_] = kFreezerStateThawed;
suspend_freezer_.set_sys_utils_for_testing(mock_sys_utils_);
suspend_freezer_.Init(&prefs_);
}
~SuspendFreezerTest() override = default;
protected:
FakePrefs prefs_;
MockSystemUtils* mock_sys_utils_;
SuspendFreezer suspend_freezer_;
base::FilePath test_state_;
};
// Test that FreezeUserspace times out and returns failure if the state doesn't
// change.
TEST_F(SuspendFreezerTest, TestFreezeTimeout) {
mock_sys_utils_->set_write_ = false;
suspend_freezer_.clock()->set_time_step_for_testing(
base::TimeDelta::FromSeconds(5));
EXPECT_EQ(FreezeResult::FAILURE, suspend_freezer_.FreezeUserspace(1, true));
}
// Test that FreezeUserspace and ThawUserspace write the correct state and
// return under normal operation.
TEST_F(SuspendFreezerTest, TestSuccess) {
EXPECT_EQ(FreezeResult::SUCCESS, suspend_freezer_.FreezeUserspace(1, true));
EXPECT_EQ(kFreezerStateFrozen, mock_sys_utils_->file_contents_[test_state_]);
EXPECT_TRUE(suspend_freezer_.ThawUserspace());
EXPECT_EQ(kFreezerStateThawed, mock_sys_utils_->file_contents_[test_state_]);
}
// Test that Init() will thaw processes in test cgroup (if powerd crashes and
// restarts after freezing them).
TEST_F(SuspendFreezerTest, TestReInit) {
EXPECT_EQ(FreezeResult::SUCCESS, suspend_freezer_.FreezeUserspace(1, true));
EXPECT_EQ(kFreezerStateFrozen, mock_sys_utils_->file_contents_[test_state_]);
suspend_freezer_.Init(&prefs_);
EXPECT_EQ(kFreezerStateThawed, mock_sys_utils_->file_contents_[test_state_]);
}
// Test that if multiple freezer cgroups exist, all are frozen/thawed.
TEST_F(SuspendFreezerTest, TestAll) {
base::FilePath test1 =
base::FilePath("/sys/fs/cgroup/freezer/test1").Append(kStateFile);
base::FilePath test2 =
base::FilePath("/sys/fs/cgroup/freezer/test2").Append(kStateFile);
mock_sys_utils_->file_contents_[test1] = kFreezerStateThawed;
mock_sys_utils_->file_contents_[test2] = kFreezerStateThawed;
EXPECT_EQ(FreezeResult::SUCCESS, suspend_freezer_.FreezeUserspace(1, true));
EXPECT_EQ(kFreezerStateFrozen, mock_sys_utils_->file_contents_[test_state_]);
EXPECT_EQ(kFreezerStateFrozen, mock_sys_utils_->file_contents_[test1]);
EXPECT_EQ(kFreezerStateFrozen, mock_sys_utils_->file_contents_[test2]);
EXPECT_TRUE(suspend_freezer_.ThawUserspace());
EXPECT_EQ(kFreezerStateThawed, mock_sys_utils_->file_contents_[test_state_]);
EXPECT_EQ(kFreezerStateThawed, mock_sys_utils_->file_contents_[test1]);
EXPECT_EQ(kFreezerStateThawed, mock_sys_utils_->file_contents_[test2]);
}
// Test that we fail properly if a root level cgroup is added that we don't have
// permission to write to.
TEST_F(SuspendFreezerTest, TestPermissionsFail) {
mock_sys_utils_->permission_fail_ = true;
EXPECT_EQ(FreezeResult::FAILURE, suspend_freezer_.FreezeUserspace(1, true));
}
// Test early wakeup by passing in a wakeup_count of 0 to FreezeUserspace. It
// will read back a wakeup count of 1 by default, resulting in a mismatch which
// should cancel Freeze.
TEST_F(SuspendFreezerTest, TestEarlyWakeup) {
mock_sys_utils_->set_write_ = false;
EXPECT_EQ(FreezeResult::CANCELED, suspend_freezer_.FreezeUserspace(0, true));
}
// Test that cgroups will freeze in the correct order based on dependencies
// specified in prefs_.
TEST_F(SuspendFreezerTest, TestOrdering) {
std::vector<base::FilePath> test_order;
base::FilePath test1 =
base::FilePath("/sys/fs/cgroup/freezer/test1").Append(kStateFile);
base::FilePath test2 =
base::FilePath("/sys/fs/cgroup/freezer/test2").Append(kStateFile);
base::FilePath test3 =
base::FilePath("/sys/fs/cgroup/freezer/test3").Append(kStateFile);
mock_sys_utils_->file_contents_[test1] = kFreezerStateThawed;
mock_sys_utils_->file_contents_[test2] = kFreezerStateThawed;
mock_sys_utils_->file_contents_[test3] = kFreezerStateThawed;
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test", "test3");
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test3", "test2");
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test2", "test1");
suspend_freezer_.Init(&prefs_);
EXPECT_EQ(FreezeResult::SUCCESS, suspend_freezer_.FreezeUserspace(1, true));
test_order = {test_state_, test3, test2, test1};
EXPECT_EQ(test_order, mock_sys_utils_->freeze_order_);
EXPECT_TRUE(suspend_freezer_.ThawUserspace());
mock_sys_utils_->freeze_order_.clear();
mock_sys_utils_->file_contents_[test1] = kFreezerStateThawed;
mock_sys_utils_->file_contents_[test2] = kFreezerStateThawed;
mock_sys_utils_->file_contents_[test3] = kFreezerStateThawed;
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test", "test1");
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test1", "test2");
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test2", "test3");
prefs_.Unset(std::string(kSuspendFreezerDepsPrefix) + "test3");
suspend_freezer_.Init(&prefs_);
EXPECT_EQ(FreezeResult::SUCCESS, suspend_freezer_.FreezeUserspace(1, true));
test_order = {test_state_, test1, test2, test3};
EXPECT_EQ(test_order, mock_sys_utils_->freeze_order_);
EXPECT_TRUE(suspend_freezer_.ThawUserspace());
mock_sys_utils_->freeze_order_.clear();
mock_sys_utils_->file_contents_[test1] = kFreezerStateThawed;
mock_sys_utils_->file_contents_[test2] = kFreezerStateThawed;
mock_sys_utils_->file_contents_[test3] = kFreezerStateThawed;
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test", "test3");
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test3", "test1");
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test1", "test2");
prefs_.Unset(std::string(kSuspendFreezerDepsPrefix) + "test2");
suspend_freezer_.Init(&prefs_);
EXPECT_EQ(FreezeResult::SUCCESS, suspend_freezer_.FreezeUserspace(1, true));
test_order = {test_state_, test3, test1, test2};
EXPECT_EQ(test_order, mock_sys_utils_->freeze_order_);
EXPECT_TRUE(suspend_freezer_.ThawUserspace());
}
// Test the partial ordering of |freeze_order_| when multiple dependencies are
// included for individual cgroups.
TEST_F(SuspendFreezerTest, TestMultipleDeps) {
base::FilePath test1 =
base::FilePath("/sys/fs/cgroup/freezer/test1").Append(kStateFile);
base::FilePath test2 =
base::FilePath("/sys/fs/cgroup/freezer/test2").Append(kStateFile);
base::FilePath test3 =
base::FilePath("/sys/fs/cgroup/freezer/test3").Append(kStateFile);
base::FilePath test4 =
base::FilePath("/sys/fs/cgroup/freezer/test4").Append(kStateFile);
mock_sys_utils_->file_contents_[test1] = kFreezerStateThawed;
mock_sys_utils_->file_contents_[test2] = kFreezerStateThawed;
mock_sys_utils_->file_contents_[test3] = kFreezerStateThawed;
mock_sys_utils_->file_contents_[test4] = kFreezerStateThawed;
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test",
"test1\ntest2");
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test1",
"test3\ntest4");
suspend_freezer_.Init(&prefs_);
EXPECT_EQ(FreezeResult::SUCCESS, suspend_freezer_.FreezeUserspace(1, true));
EXPECT_EQ(test_state_, mock_sys_utils_->freeze_order_[0]);
EXPECT_TRUE(base::Contains(mock_sys_utils_->freeze_order_, test1));
EXPECT_TRUE(base::Contains(mock_sys_utils_->freeze_order_, test2));
EXPECT_TRUE(base::Contains(mock_sys_utils_->freeze_order_, test3));
EXPECT_TRUE(base::Contains(mock_sys_utils_->freeze_order_, test4));
const auto test1_it = std::find(mock_sys_utils_->freeze_order_.begin(),
mock_sys_utils_->freeze_order_.end(), test1);
EXPECT_TRUE(test1_it < std::find(mock_sys_utils_->freeze_order_.begin(),
mock_sys_utils_->freeze_order_.end(),
test3));
EXPECT_TRUE(test1_it < std::find(mock_sys_utils_->freeze_order_.begin(),
mock_sys_utils_->freeze_order_.end(),
test4));
}
// Test that FreezeUserspace fails when there's a circular dependency chain.
TEST_F(SuspendFreezerTest, TestCircularDeps) {
base::FilePath test1 =
base::FilePath("/sys/fs/cgroup/freezer/test1").Append(kStateFile);
base::FilePath test2 =
base::FilePath("/sys/fs/cgroup/freezer/test2").Append(kStateFile);
mock_sys_utils_->file_contents_[test1] = kFreezerStateThawed;
mock_sys_utils_->file_contents_[test2] = kFreezerStateThawed;
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test", "test1");
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test1", "test2");
prefs_.SetString(std::string(kSuspendFreezerDepsPrefix) + "test2", "test");
suspend_freezer_.Init(&prefs_);
EXPECT_EQ(FreezeResult::FAILURE, suspend_freezer_.FreezeUserspace(1, true));
// Check that cgroups are unfrozen
EXPECT_EQ(mock_sys_utils_->file_contents_[test_state_], kFreezerStateThawed);
EXPECT_EQ(mock_sys_utils_->file_contents_[test1], kFreezerStateThawed);
EXPECT_EQ(mock_sys_utils_->file_contents_[test2], kFreezerStateThawed);
}
} // namespace system
} // namespace power_manager