blob: 7ed11963ef856a367184a12e56a3ade878a100f3 [file] [log] [blame]
// Copyright 2023 The Fuchsia 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 <fcntl.h>
#include <sys/epoll.h>
#include <sys/poll.h>
#include <sys/timerfd.h>
#include <sys/uio.h>
#include <cstdint>
#include <format>
#include <regex>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/starnix/tests/syscalls/cpp/test_helper.h"
using testing::IsSupersetOf;
namespace {
void VerifyReadOutOfBound(const std::string& path) {
constexpr int kOffset = 100;
constexpr int kSize = 10;
char read_buffer[kSize];
struct iovec iov[] = {
{
.iov_base = &read_buffer[0],
.iov_len = kSize,
},
};
int fd = openat(AT_FDCWD, path.c_str(), O_RDONLY);
EXPECT_NE(-1, fd);
EXPECT_EQ(0, preadv(fd, iov, std::size(iov), kOffset));
}
bool attempt_suspend(const std::string& suspend_type) {
GTEST_LOG_(INFO) << "attempt_suspend: " << suspend_type;
return files::WriteFile("/sys/power/state", suspend_type);
}
timer_t start_interval_timer() {
timer_t timer_id;
struct sigevent sev = {};
sev.sigev_notify = SIGEV_NONE;
EXPECT_NE(-1, timer_create(CLOCK_REALTIME_ALARM, &sev, &timer_id));
// Set a test timer that will trigger every 1 second to wake
// up all the subsequent suspends.
struct itimerspec its = {};
its.it_value.tv_sec = 2;
its.it_interval.tv_sec = 2;
EXPECT_NE(-1, timer_settime(timer_id, 0, &its, nullptr));
// TODO(https://fxbug.dev/373676361): This sleep is here to guarantee that
// the hrtimer request has been sent before tests try to suspend.
sleep(5);
return timer_id;
}
void stop_interval_timer(timer_t timer_id) {
struct itimerspec its = {};
its.it_value.tv_sec = 0;
its.it_interval.tv_sec = 0;
EXPECT_NE(-1, timer_settime(timer_id, 0, &its, nullptr));
}
} // namespace
class SysfsPowerTest : public ::testing::Test {
public:
void SetUp() override {
// TODO(https://fxbug.dev/317285180) don't skip on baseline
// Assume starnix always has /sys/power and /sys/kernel/wakeup_reasons
if (!test_helper::IsStarnix() && access("/sys/power", F_OK) == -1 &&
access("/sys/kernel/wakeup_reasons", F_OK) == -1) {
GTEST_SKIP() << "/sys/power not available, skipping...";
}
}
};
TEST_F(SysfsPowerTest, PowerDirectoryContainsExpectedContents) {
std::vector<std::string> files;
EXPECT_TRUE(files::ReadDirContents("/sys/power", &files));
EXPECT_THAT(files, IsSupersetOf({"suspend_stats", "wakeup_count", "state", "sync_on_suspend",
"wake_lock", "wake_unlock"}));
}
TEST_F(SysfsPowerTest, WakeupReasonsDirectoryContainsExpectedContents) {
std::vector<std::string> files;
EXPECT_TRUE(files::ReadDirContents("/sys/kernel/wakeup_reasons", &files));
EXPECT_THAT(files, IsSupersetOf({"last_resume_reason", "last_suspend_time"}));
}
TEST_F(SysfsPowerTest, SuspendStatsDirectoryContainsExpectedContents) {
std::vector<std::string> suspend_stats_files;
EXPECT_TRUE(files::ReadDirContents("/sys/power/suspend_stats", &suspend_stats_files));
EXPECT_THAT(suspend_stats_files,
IsSupersetOf({"success", "fail", "last_failed_dev", "last_failed_errno"}));
}
TEST_F(SysfsPowerTest, SuspendStatsFilesContainDefaultsSuccess) {
std::string success_str;
EXPECT_TRUE(files::ReadFileToString("/sys/power/suspend_stats/success", &success_str));
EXPECT_TRUE(std::regex_match(success_str, std::regex("^[0-9]+\n")));
}
TEST_F(SysfsPowerTest, SuspendStatsFilesUpdateAfterSuccessfulSuspend) {
std::string prev_success_str;
EXPECT_TRUE(files::ReadFileToString("/sys/power/suspend_stats/success", &prev_success_str));
std::string prev_fail_str;
EXPECT_TRUE(files::ReadFileToString("/sys/power/suspend_stats/fail", &prev_fail_str));
timer_t timer_id = start_interval_timer();
ASSERT_TRUE(attempt_suspend("mem"));
stop_interval_timer(timer_id);
std::string new_success_str;
std::string new_fail_str;
EXPECT_TRUE(files::ReadFileToString("/sys/power/suspend_stats/success", &new_success_str));
EXPECT_TRUE(files::ReadFileToString("/sys/power/suspend_stats/fail", &new_fail_str));
EXPECT_EQ(std::atoi(prev_success_str.c_str()) + 1, std::atoi(new_success_str.c_str()));
EXPECT_EQ(std::atoi(prev_fail_str.c_str()), std::atoi(new_fail_str.c_str()));
}
TEST_F(SysfsPowerTest, SuspendStatsFilesUpdateAfterFailedSuspend) {
std::string prev_success_str;
EXPECT_TRUE(files::ReadFileToString("/sys/power/suspend_stats/success", &prev_success_str));
std::string prev_fail_str;
EXPECT_TRUE(files::ReadFileToString("/sys/power/suspend_stats/fail", &prev_fail_str));
fbl::unique_fd timer_fd(timerfd_create(CLOCK_REALTIME, 0));
EXPECT_TRUE(timer_fd.is_valid());
fbl::unique_fd epoll_fd(epoll_create(1));
EXPECT_TRUE(epoll_fd.is_valid());
struct epoll_event ev = epoll_event();
ev.events = EPOLLIN | EPOLLWAKEUP;
EXPECT_EQ(0, epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, timer_fd.get(), &ev));
// Activate the EPOLLWAKEUP event to create the implicit wake lock.
{
struct itimerspec its = {};
its.it_value.tv_sec = 1;
EXPECT_EQ(0, timerfd_settime(timer_fd.get(), 0, &its, nullptr));
int ret = 0;
struct epoll_event out_ev;
ret = epoll_wait(epoll_fd.get(), &out_ev, 1, -1);
EXPECT_EQ(1, ret);
uint64_t val = 0;
// Read the event from the timer_fd to reset the pending events.
EXPECT_EQ(1, read(timer_fd.get(), &val, 1));
}
// This should fail due to the implicit wake lock.
ASSERT_FALSE(attempt_suspend("mem"));
std::string new_success_str;
std::string new_fail_str;
EXPECT_TRUE(files::ReadFileToString("/sys/power/suspend_stats/success", &new_success_str));
EXPECT_TRUE(files::ReadFileToString("/sys/power/suspend_stats/fail", &new_fail_str));
EXPECT_EQ(std::atoi(prev_success_str.c_str()), std::atoi(new_success_str.c_str()));
EXPECT_EQ(std::atoi(prev_fail_str.c_str()) + 1, std::atoi(new_fail_str.c_str()));
// Wait on the events again, which should clear the EPOLLWAKEUP when
// no events are returned.
{
int ret = 0;
struct epoll_event out_ev;
ret = epoll_wait(epoll_fd.get(), &out_ev, 1, 0);
EXPECT_EQ(0, ret);
}
}
TEST_F(SysfsPowerTest, SuspendStatsFilesContainDefaultsFail) {
std::string fail_str;
EXPECT_TRUE(files::ReadFileToString("/sys/power/suspend_stats/fail", &fail_str));
EXPECT_TRUE(std::regex_match(fail_str, std::regex("^[0-9]+\n$")));
}
TEST_F(SysfsPowerTest, SuspendStatsFilesContainDefaultsLastFailedDev) {
std::string last_failed_dev_str;
EXPECT_TRUE(
files::ReadFileToString("/sys/power/suspend_stats/last_failed_dev", &last_failed_dev_str));
EXPECT_TRUE(std::regex_match(last_failed_dev_str, std::regex("^.*\n$")));
}
TEST_F(SysfsPowerTest, SuspendStatsFilesContainDefaultsLastFailedErrno) {
std::string last_failed_errno_str;
EXPECT_TRUE(files::ReadFileToString("/sys/power/suspend_stats/last_failed_errno",
&last_failed_errno_str));
// These tests always run in an environment where suspends don't fail.
std::regex pattern("^(0|-?([1-9]\\d*|\\d))\n$");
EXPECT_TRUE(std::regex_match(last_failed_errno_str, pattern));
}
TEST_F(SysfsPowerTest, WakeupCountFileContainsExpectedContents) {
std::string wakeup_count_str;
EXPECT_TRUE(files::ReadFileToString("/sys/power/wakeup_count", &wakeup_count_str));
EXPECT_TRUE(std::regex_match(wakeup_count_str, std::regex("^[0-9]+\n$")));
}
TEST_F(SysfsPowerTest, WakeupCountFileReadOutofBound) {
VerifyReadOutOfBound("/sys/power/wakeup_count");
}
TEST_F(SysfsPowerTest, WakeupCountFileWrite) {
EXPECT_FALSE(files::WriteFile("/sys/power/wakeup_count", "test"));
EXPECT_FALSE(files::WriteFile("/sys/power/wakeup_count", std::to_string(INT_MAX)));
std::string wakeup_count_str;
EXPECT_TRUE(files::ReadFileToString("/sys/power/wakeup_count", &wakeup_count_str));
EXPECT_TRUE(files::WriteFile("/sys/power/wakeup_count", wakeup_count_str));
}
TEST_F(SysfsPowerTest, SuspendStateFileContainsExpectedContents) {
std::string states_str;
EXPECT_TRUE(files::ReadFileToString("/sys/power/state", &states_str));
EXPECT_TRUE(std::regex_match(states_str, std::regex("([mem|freeze|disk|standby]\\s?)*\n$")));
}
TEST_F(SysfsPowerTest, SuspendStateFileReadOutOfBound) { VerifyReadOutOfBound("/sys/power/state"); }
TEST_F(SysfsPowerTest, SuspendStateFileWrite) {
timer_t timer_id = start_interval_timer();
ASSERT_TRUE(attempt_suspend("mem"));
GTEST_LOG_(INFO) << "wake: 1";
ASSERT_TRUE(attempt_suspend("mem\n"));
GTEST_LOG_(INFO) << "wake: 2";
ASSERT_TRUE(attempt_suspend("mem\nfoobar"));
GTEST_LOG_(INFO) << "wake: 3";
ASSERT_TRUE(attempt_suspend("freeze"));
GTEST_LOG_(INFO) << "wake: 4";
ASSERT_TRUE(attempt_suspend("freeze\n"));
GTEST_LOG_(INFO) << "wake: 5";
ASSERT_TRUE(attempt_suspend("freeze\nfoobar"));
GTEST_LOG_(INFO) << "wake: 6";
stop_interval_timer(timer_id);
}
TEST_F(SysfsPowerTest, SuspendStateFileWriteFailsWaitAgain) {
fbl::unique_fd timer_fd(timerfd_create(CLOCK_REALTIME, 0));
EXPECT_TRUE(timer_fd.is_valid());
fbl::unique_fd epoll_fd(epoll_create(1));
EXPECT_TRUE(epoll_fd.is_valid());
struct epoll_event ev = epoll_event();
ev.events = EPOLLIN | EPOLLWAKEUP;
EXPECT_EQ(0, epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, timer_fd.get(), &ev));
// Activate the EPOLLWAKEUP event to create the implicit wake lock.
{
struct itimerspec its = {};
its.it_value.tv_sec = 1;
EXPECT_EQ(0, timerfd_settime(timer_fd.get(), 0, &its, nullptr));
int ret = 0;
struct epoll_event out_ev;
ret = epoll_wait(epoll_fd.get(), &out_ev, 1, -1);
EXPECT_EQ(1, ret);
uint64_t val = 0;
// Read the event from the timer_fd to reset the pending events.
EXPECT_EQ(1, read(timer_fd.get(), &val, 1));
}
// This should fail due to the implicit wake lock.
ASSERT_FALSE(attempt_suspend("mem"));
// Wait on the events again, which should clear the EPOLLWAKEUP when
// no events are returned.
{
int ret = 0;
struct epoll_event out_ev;
ret = epoll_wait(epoll_fd.get(), &out_ev, 1, 0);
EXPECT_EQ(0, ret);
}
// Attempt to suspend the system now that the implicit wake lock
// should have been deleted.
timer_t timer_id = start_interval_timer();
ASSERT_TRUE(attempt_suspend("mem"));
stop_interval_timer(timer_id);
}
TEST_F(SysfsPowerTest, SuspendStateFileWriteFailsCloseFD) {
fbl::unique_fd timer_fd(timerfd_create(CLOCK_REALTIME, 0));
EXPECT_TRUE(timer_fd.is_valid());
fbl::unique_fd epoll_fd(epoll_create(1));
EXPECT_TRUE(epoll_fd.is_valid());
struct epoll_event ev = epoll_event();
ev.events = EPOLLIN | EPOLLWAKEUP;
EXPECT_EQ(0, epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, timer_fd.get(), &ev));
// Activate the EPOLLWAKEUP event to create the implicit wake lock.
{
struct itimerspec its = {};
its.it_value.tv_sec = 1;
EXPECT_EQ(0, timerfd_settime(timer_fd.get(), 0, &its, nullptr));
int ret = 0;
struct epoll_event out_ev;
ret = epoll_wait(epoll_fd.get(), &out_ev, 1, -1);
EXPECT_EQ(1, ret);
uint64_t val = 0;
// Read the event from the timer_fd to reset the pending events.
EXPECT_EQ(1, read(timer_fd.get(), &val, 1));
}
// This should fail due to the implicit wake lock.
ASSERT_FALSE(attempt_suspend("mem"));
// Closing the epoll file descriptor should remove the wake lock.
timer_fd.reset();
// Attempt to suspend the system now that the implicit wake lock
// should have been deleted.
timer_t timer_id = start_interval_timer();
ASSERT_TRUE(attempt_suspend("mem"));
stop_interval_timer(timer_id);
}
TEST_F(SysfsPowerTest, SuspendStateFileWriteFailsCloseEpollFD) {
fbl::unique_fd timer_fd(timerfd_create(CLOCK_REALTIME, 0));
EXPECT_TRUE(timer_fd.is_valid());
fbl::unique_fd epoll_fd(epoll_create(1));
EXPECT_TRUE(epoll_fd.is_valid());
struct epoll_event ev = epoll_event();
ev.events = EPOLLIN | EPOLLWAKEUP;
EXPECT_EQ(0, epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, timer_fd.get(), &ev));
// Activate the EPOLLWAKEUP event to create the implicit wake lock.
{
struct itimerspec its = {};
its.it_value.tv_sec = 1;
EXPECT_EQ(0, timerfd_settime(timer_fd.get(), 0, &its, nullptr));
int ret = 0;
struct epoll_event out_ev;
ret = epoll_wait(epoll_fd.get(), &out_ev, 1, -1);
EXPECT_EQ(1, ret);
uint64_t val = 0;
// Read the event from the timer_fd to reset the pending events.
EXPECT_EQ(1, read(timer_fd.get(), &val, 1));
}
// This should fail due to the implicit wake lock.
ASSERT_FALSE(attempt_suspend("mem"));
// Closing the epoll file descriptor should remove the wake lock.
epoll_fd.reset();
// Attempt to suspend the system now that the implicit wake lock
// should have been deleted.
timer_t timer_id = start_interval_timer();
ASSERT_TRUE(attempt_suspend("mem"));
stop_interval_timer(timer_id);
}
TEST_F(SysfsPowerTest, SuspendStateFileWriteFailsEpollDelete) {
fbl::unique_fd timer_fd(timerfd_create(CLOCK_REALTIME, 0));
EXPECT_TRUE(timer_fd.is_valid());
fbl::unique_fd epoll_fd(epoll_create(1));
EXPECT_TRUE(epoll_fd.is_valid());
struct epoll_event ev = epoll_event();
ev.events = EPOLLIN | EPOLLWAKEUP;
EXPECT_EQ(0, epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, timer_fd.get(), &ev));
// Activate the EPOLLWAKEUP event to create the implicit wake lock.
{
struct itimerspec its = {};
its.it_value.tv_sec = 1;
EXPECT_EQ(0, timerfd_settime(timer_fd.get(), 0, &its, nullptr));
int ret = 0;
struct epoll_event out_ev;
ret = epoll_wait(epoll_fd.get(), &out_ev, 1, -1);
EXPECT_EQ(1, ret);
uint64_t val = 0;
// Read the event from the timer_fd to reset the pending events.
EXPECT_EQ(1, read(timer_fd.get(), &val, 1));
}
// This should fail due to the implicit wake lock.
ASSERT_FALSE(attempt_suspend("mem"));
// Deleting the epoll file descriptor should remove the wake lock.
EXPECT_EQ(0, epoll_ctl(epoll_fd.get(), EPOLL_CTL_DEL, timer_fd.get(), &ev));
// Attempt to suspend the system now that the implicit wake lock
// should have been deleted.
timer_t timer_id = start_interval_timer();
ASSERT_TRUE(attempt_suspend("mem"));
stop_interval_timer(timer_id);
}
TEST_F(SysfsPowerTest, SuspendStateFileWriteInvalidFails) {
ASSERT_FALSE(files::WriteFile("/sys/power/state", "test"));
ASSERT_FALSE(files::WriteFile("/sys/power/state", "disk"));
}
TEST_F(SysfsPowerTest, SyncOnSuspendFileContainsExpectedContents) {
std::string sync_on_suspend_str;
EXPECT_TRUE(files::ReadFileToString("/sys/power/sync_on_suspend", &sync_on_suspend_str));
EXPECT_TRUE(std::regex_match(sync_on_suspend_str, std::regex("(0|1)\n")));
}
TEST_F(SysfsPowerTest, SyncOnSuspendFileReadOutOfBound) {
VerifyReadOutOfBound("/sys/power/sync_on_suspend");
}
TEST_F(SysfsPowerTest, SyncOnSuspendFileWrite) {
EXPECT_FALSE(files::WriteFile("/sys/power/sync_on_suspend", "test"));
EXPECT_FALSE(files::WriteFile("/sys/power/sync_on_suspend", std::to_string(2)));
EXPECT_TRUE(files::WriteFile("/sys/power/sync_on_suspend", std::to_string(0)));
}
TEST_F(SysfsPowerTest, LastSuspendTimeFileContainsExpectedContents) {
std::string last_suspend_time_str;
EXPECT_TRUE(files::ReadFileToString("/sys/kernel/wakeup_reasons/last_suspend_time",
&last_suspend_time_str));
std::regex pattern("^(0|-?([1-9]\\d*|\\d)\\.\\d+)\\s(0|-?([1-9]\\d*|\\d)\\.\\d+)\n$");
EXPECT_TRUE(std::regex_match(last_suspend_time_str, pattern));
}
TEST_F(SysfsPowerTest, LastResumeReasonFileContainsExpectedContents) {
std::string last_suspend_time_str;
EXPECT_TRUE(files::ReadFileToString("/sys/kernel/wakeup_reasons/last_resume_reason",
&last_suspend_time_str));
EXPECT_TRUE(std::regex_match(last_suspend_time_str, std::regex("^.*\n$")));
}
TEST_F(SysfsPowerTest, AddAndRemoveWakeLock) {
std::string wake_locks_str;
std::string test_wake_lock_str = "test_wake_lock";
EXPECT_TRUE(files::WriteFile("/sys/power/wake_lock", test_wake_lock_str));
EXPECT_TRUE(files::ReadFileToString("/sys/power/wake_lock", &wake_locks_str));
EXPECT_TRUE(std::regex_match(wake_locks_str, std::regex("^.*\n$")));
EXPECT_NE(wake_locks_str.find(test_wake_lock_str), std::string::npos);
std::string wake_unlocks_str;
EXPECT_TRUE(files::WriteFile("/sys/power/wake_unlock", test_wake_lock_str));
EXPECT_TRUE(files::ReadFileToString("/sys/power/wake_unlock", &wake_unlocks_str));
EXPECT_TRUE(files::ReadFileToString("/sys/power/wake_lock", &wake_locks_str));
EXPECT_TRUE(std::regex_match(wake_unlocks_str, std::regex("^.*\n$")));
EXPECT_NE(wake_unlocks_str.find(test_wake_lock_str), std::string::npos);
EXPECT_EQ(wake_locks_str.find(test_wake_lock_str), std::string::npos);
}
TEST_F(SysfsPowerTest, WakeLockBlocksSuspend) {
std::string wake_locks_str;
std::string test_wake_lock_str = "test_wake_lock";
EXPECT_TRUE(files::WriteFile("/sys/power/wake_lock", test_wake_lock_str));
// This should fail due to the implicit wake lock.
ASSERT_FALSE(attempt_suspend("mem"));
std::string wake_unlocks_str;
EXPECT_TRUE(files::WriteFile("/sys/power/wake_unlock", test_wake_lock_str));
EXPECT_TRUE(files::ReadFileToString("/sys/power/wake_unlock", &wake_unlocks_str));
EXPECT_TRUE(files::ReadFileToString("/sys/power/wake_lock", &wake_locks_str));
EXPECT_NE(wake_unlocks_str.find(test_wake_lock_str), std::string::npos);
EXPECT_EQ(wake_locks_str.find(test_wake_lock_str), std::string::npos);
}
TEST_F(SysfsPowerTest, WakeLockWithTimeout) {
std::string wake_locks_str;
std::string wake_unlocks_str;
std::string test_wake_lock_str = "test_wake_lock";
int wake_lock_timeout_s = 1;
EXPECT_TRUE(files::WriteFile(
"/sys/power/wake_lock",
std::format("{} {}", test_wake_lock_str, wake_lock_timeout_s * 1000 * 1000 * 1000)));
EXPECT_TRUE(files::ReadFileToString("/sys/power/wake_lock", &wake_locks_str));
EXPECT_NE(wake_locks_str.find(test_wake_lock_str), std::string::npos);
// Wait until the wake lock timeout is expired
sleep(wake_lock_timeout_s * 2);
EXPECT_TRUE(files::ReadFileToString("/sys/power/wake_lock", &wake_locks_str));
EXPECT_EQ(wake_locks_str.find(test_wake_lock_str), std::string::npos);
EXPECT_TRUE(files::ReadFileToString("/sys/power/wake_unlock", &wake_unlocks_str));
EXPECT_NE(wake_unlocks_str.find(test_wake_lock_str), std::string::npos);
}
TEST_F(SysfsPowerTest, WakeLockFileWrite) {
std::string wake_locks_str;
EXPECT_TRUE(files::WriteFile("/sys/power/wake_lock", "test1"));
EXPECT_TRUE(files::WriteFile("/sys/power/wake_lock", "test2\n"));
EXPECT_TRUE(files::WriteFile("/sys/power/wake_lock", "test3 1000000000000000"));
EXPECT_FALSE(files::WriteFile("/sys/power/wake_lock", "test4 "));
EXPECT_FALSE(files::WriteFile("/sys/power/wake_lock", "test5 e"));
EXPECT_TRUE(files::ReadFileToString("/sys/power/wake_lock", &wake_locks_str));
EXPECT_NE(wake_locks_str.find("test1"), std::string::npos);
EXPECT_NE(wake_locks_str.find("test2"), std::string::npos);
EXPECT_NE(wake_locks_str.find("test3"), std::string::npos);
EXPECT_EQ(wake_locks_str.find("test4"), std::string::npos);
EXPECT_EQ(wake_locks_str.find("test5"), std::string::npos);
EXPECT_TRUE(files::WriteFile("/sys/power/wake_unlock", "test1"));
EXPECT_TRUE(files::WriteFile("/sys/power/wake_unlock", "test2"));
EXPECT_TRUE(files::WriteFile("/sys/power/wake_unlock", "test3"));
EXPECT_TRUE(files::ReadFileToString("/sys/power/wake_lock", &wake_locks_str));
// Ensure there is no active wake lock.
EXPECT_TRUE(std::ranges::all_of(wake_locks_str, isspace));
}