blob: df42c2b6fcd660ce4676d4a92e5afd8a7ab90a2b [file] [log] [blame]
// Copyright 2018 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/system/arc_timer_manager.h"
#include <time.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/callback_helpers.h>
#include <base/files/file_descriptor_watcher_posix.h>
#include <base/macros.h>
#include <base/run_loop.h>
#include <chromeos/dbus/service_constants.h>
#include <gtest/gtest.h>
#include <sys/socket.h>
#include "power_manager/common/test_main_loop_runner.h"
#include "power_manager/powerd/system/dbus_wrapper_stub.h"
namespace power_manager {
namespace system {
namespace {
bool CreateSocketPair(base::ScopedFD* one, base::ScopedFD* two) {
int raw_socks[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, raw_socks) != 0) {
PLOG(ERROR) << "Failed to create socket pair";
return false;
}
one->reset(raw_socks[0]);
two->reset(raw_socks[1]);
return true;
}
std::vector<base::ScopedFD> WriteCreateTimersDBusRequest(
const std::string& tag,
const std::vector<clockid_t>& clocks,
dbus::MessageWriter* writer) {
// Create D-Bus arguments i.e. tag followed by array of
// {int32_t clock_id, base::ScopedFD expiration_fd}.
writer->AppendString(tag);
dbus::MessageWriter array_writer(nullptr);
writer->OpenArray("(ih)", &array_writer);
std::vector<base::ScopedFD> result;
for (clockid_t clock_id : clocks) {
// Create a socket pair for each clock. One socket will be part of the
// mojo argument and will be used by the host to indicate when the timer
// expires. The other socket will be used to detect the expiration of the
// timer by epolling / reading.
base::ScopedFD read_fd;
base::ScopedFD write_fd;
if (!CreateSocketPair(&read_fd, &write_fd)) {
LOG(ERROR) << "Failed to create socket pair for ARC timers";
return result;
}
result.push_back(std::move(read_fd));
dbus::MessageWriter struct_writer(nullptr);
array_writer.OpenStruct(&struct_writer);
struct_writer.AppendInt32(static_cast<int32_t>(clock_id));
struct_writer.AppendFileDescriptor(write_fd.get());
array_writer.CloseContainer(&struct_writer);
}
writer->CloseContainer(&array_writer);
return result;
}
std::vector<ArcTimerManager::TimerId> ReadTimerIds(
std::unique_ptr<dbus::Response> response) {
dbus::MessageReader reader(response.get());
dbus::MessageReader array_reader(nullptr);
std::vector<ArcTimerManager::TimerId> result;
if (!reader.PopArray(&array_reader)) {
LOG(ERROR) << "No timer ids returned";
return result;
}
std::vector<ArcTimerManager::TimerId> timer_ids;
while (array_reader.HasMoreData()) {
ArcTimerManager::TimerId timer_id;
if (!array_reader.PopInt32(&timer_id)) {
LOG(ERROR) << "Failed to pop timer id";
return result;
}
result.push_back(timer_id);
}
return result;
}
bool IsResponseValid(dbus::Response* response) {
return response && response->GetMessageType() ==
dbus::Message::MessageType::MESSAGE_METHOD_RETURN;
}
// Returns true iff |a| and |b| are of the same size and all entries in |a| are
// different from |b|.
bool AreTimerIdsIdenticalSizeButDistinct(
const std::vector<ArcTimerManager::TimerId>& a,
const std::vector<ArcTimerManager::TimerId>& b) {
if (a.size() != b.size())
return false;
for (auto id : a) {
if (std::find(b.begin(), b.end(), id) != b.end())
return false;
}
return true;
}
} // namespace
class ArcTimerManagerTest : public ::testing::Test {
public:
ArcTimerManagerTest() {
arc_timer_manager_.set_for_testing_(true);
arc_timer_manager_.Init(&dbus_wrapper_);
}
ArcTimerManagerTest(const ArcTimerManagerTest&) = delete;
ArcTimerManagerTest& operator=(const ArcTimerManagerTest&) = delete;
protected:
bool CreateTimers(const std::string& tag,
const std::vector<clockid_t>& clocks) WARN_UNUSED_RESULT {
dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
power_manager::kCreateArcTimersMethod);
dbus::MessageWriter writer(&method_call);
std::vector<base::ScopedFD> read_fds =
WriteCreateTimersDBusRequest(tag, clocks, &writer);
size_t clocks_size = clocks.size();
if (read_fds.size() != clocks_size) {
LOG(ERROR) << "Failed to create D-Bus request";
return false;
}
std::unique_ptr<dbus::Response> response =
dbus_wrapper_.CallExportedMethodSync(&method_call);
if (!IsResponseValid(response.get())) {
LOG(ERROR) << power_manager::kCreateArcTimersMethod << " call failed";
return false;
}
// Parse timer ids returned from the response.
std::vector<ArcTimerManager::TimerId> timer_ids =
ReadTimerIds(std::move(response));
if (timer_ids.size() != clocks_size) {
LOG(ERROR) << "Expected timer ids size=" << clocks_size
<< " got size=" << timer_ids.size();
return false;
}
// Map each clock id to its corresponding timer id. Also, map each timer id
// to its corresponding read fd that will indicate timer expiration.
auto timer_id_iter = timer_ids.begin();
auto read_fd_iter = read_fds.begin();
auto arc_timer_store = std::make_unique<ArcTimerStore>();
for (clockid_t clock_id : clocks) {
VLOG(1) << "Adding entry for clock=" << clock_id
<< " timer id=" << *timer_id_iter;
if (!arc_timer_store->AddTimerEntry(clock_id, *timer_id_iter,
std::move(*read_fd_iter))) {
return false;
}
timer_id_iter++;
read_fd_iter++;
}
// It's okay to overwrite a |tag|'s value here to support tests that
// create-delete-create with the same tag.
arc_timer_stores_[tag] = std::move(arc_timer_store);
return true;
}
bool StartTimer(const std::string& tag,
clockid_t clock_id,
base::TimeTicks absolute_expiration_time) WARN_UNUSED_RESULT {
dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
power_manager::kStartArcTimerMethod);
if (arc_timer_stores_.find(tag) == arc_timer_stores_.end()) {
LOG(ERROR) << "Tag=" << tag << " not created";
return false;
}
const auto& arc_timer_store = arc_timer_stores_[tag];
// Write timer id corresponding to |clock_id| and 64-bit expiration time
// ticks value as a DBus message.
dbus::MessageWriter writer(&method_call);
ArcTimerManager::TimerId timer_id = arc_timer_store->GetTimerId(clock_id);
if (timer_id < 0) {
LOG(ERROR) << "Timer for clock=" << clock_id << " not created";
return false;
}
writer.AppendInt32(timer_id);
writer.AppendInt64(
(absolute_expiration_time - base::TimeTicks()).InMicroseconds());
std::unique_ptr<dbus::Response> response =
dbus_wrapper_.CallExportedMethodSync(&method_call);
if (!IsResponseValid(response.get())) {
LOG(ERROR) << power_manager::kStartArcTimerMethod << " call failed";
return false;
}
return true;
}
// Returns true iff the read descriptor of a timer is signalled. If the
// signalling is incorrect returns false. Blocks otherwise.
bool WaitForExpiration(const std::string& tag,
clockid_t clock_id) WARN_UNUSED_RESULT {
if (arc_timer_stores_.find(tag) == arc_timer_stores_.end()) {
LOG(ERROR) << "Tag=" << tag << " not created";
return false;
}
const auto& arc_timer_store = arc_timer_stores_[tag];
if (!arc_timer_store->HasTimer(clock_id)) {
LOG(ERROR) << "Timer of type=" << clock_id << " not present";
return false;
}
// Wait for the host to indicate expiration by watching the read end of the
// socket pair.
int timer_read_fd = arc_timer_store->GetTimerReadFd(clock_id);
if (timer_read_fd < 0) {
LOG(ERROR) << "Clock=" << clock_id << " fd not present";
return false;
}
TestMainLoopRunner runner;
// Set up a watcher to watch for the timer's read fd to become readable.
std::unique_ptr<base::FileDescriptorWatcher::Controller> watcher;
watcher = base::FileDescriptorWatcher::WatchReadable(
timer_read_fd,
base::BindRepeating(
[](TestMainLoopRunner* runner,
std::unique_ptr<base::FileDescriptorWatcher::Controller>*
watcher) {
VLOG(1) << "Fd readable";
*watcher = nullptr;
runner->StopLoop();
},
&runner, &watcher));
// Start run loop and error out if the fd isn't readable after a timeout.
if (!runner.StartLoop(base::TimeDelta::FromSeconds(30))) {
LOG(ERROR) << "Timed out waiting for expiration";
return false;
}
// The timer expects 8 bytes to be written from the host upon expiration.
// The read data signifies the number of expirations. The powerd
// implementation always returns one expiration.
uint64_t timer_data = 0;
ssize_t bytes_read = read(timer_read_fd, &timer_data, sizeof(timer_data));
if ((bytes_read != sizeof(timer_data)) || (timer_data != 1)) {
LOG(ERROR) << "Bad expiration data: bytes_read=" << bytes_read
<< " timer_data=" << timer_data;
return false;
}
return true;
}
bool DeleteTimers(const std::string& tag) WARN_UNUSED_RESULT {
dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
power_manager::kDeleteArcTimersMethod);
dbus::MessageWriter writer(&method_call);
writer.AppendString(tag);
std::unique_ptr<dbus::Response> response =
dbus_wrapper_.CallExportedMethodSync(&method_call);
if (!IsResponseValid(response.get())) {
return false;
}
return true;
}
protected:
ArcTimerManager arc_timer_manager_;
private:
// Stores clock ids and their corresponding file descriptors. These file
// descriptors indicate when a timer corresponding to the clock has expired on
// a read.
class ArcTimerStore {
public:
ArcTimerStore() = default;
ArcTimerStore(const ArcTimerStore&) = delete;
ArcTimerStore& operator=(const ArcTimerStore&) = delete;
bool AddTimerEntry(clockid_t clock_id,
ArcTimerManager::TimerId timer_id,
base::ScopedFD read_fd) {
if (!timer_ids_.emplace(clock_id, timer_id).second) {
LOG(ERROR) << "Failed to set timer id for clock=" << clock_id;
return false;
}
if (!arc_timers_.emplace(timer_id, std::move(read_fd)).second) {
LOG(ERROR) << "Failed to store read fd for timer id=" << timer_id;
return false;
}
return true;
}
void ClearTimers() {
timer_ids_.clear();
arc_timers_.clear();
}
int GetTimerReadFd(clockid_t clock_id) {
return HasTimer(clock_id) ? arc_timers_[GetTimerId(clock_id)].get() : -1;
}
bool HasTimer(clockid_t clock_id) const {
ArcTimerManager::TimerId timer_id = GetTimerId(clock_id);
return timer_id >= 0 && arc_timers_.find(timer_id) != arc_timers_.end();
}
ArcTimerManager::TimerId GetTimerId(clockid_t clock_id) const {
auto it = timer_ids_.find(clock_id);
return it == timer_ids_.end() ? -1 : it->second;
}
private:
// Map of clock id to timer id of the associated timer created.
std::map<clockid_t, ArcTimerManager::TimerId> timer_ids_;
// Map of timer id to the read fd that will indicate expiration of the
// timer.
std::map<ArcTimerManager::TimerId, base::ScopedFD> arc_timers_;
};
// Mapping of a client's tag and the |ArcTimerStore| to use with it.
std::map<std::string, std::unique_ptr<ArcTimerStore>> arc_timer_stores_;
DBusWrapperStub dbus_wrapper_;
};
TEST_F(ArcTimerManagerTest, CreateAndStartTimer) {
std::vector<clockid_t> clocks = {CLOCK_REALTIME_ALARM, CLOCK_BOOTTIME_ALARM};
const std::string kTag = "Test";
ASSERT_TRUE(CreateTimers(kTag, clocks));
ASSERT_TRUE(StartTimer(
kTag, CLOCK_BOOTTIME_ALARM,
base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(1)));
ASSERT_TRUE(WaitForExpiration(kTag, CLOCK_BOOTTIME_ALARM));
}
TEST_F(ArcTimerManagerTest, CreateAndDeleteTimers) {
std::vector<clockid_t> clocks = {CLOCK_REALTIME_ALARM, CLOCK_BOOTTIME_ALARM};
const std::string kTag = "Test";
ASSERT_TRUE(CreateTimers(kTag, clocks));
// |DeleteTimers| returns success for an unregistered tag.
ASSERT_TRUE(DeleteTimers("Foo"));
// Delete created timers and then try to start a timer. The call should fail
// as the timer doesn't exist.
ASSERT_TRUE(DeleteTimers(kTag));
ASSERT_FALSE(StartTimer(
kTag, CLOCK_BOOTTIME_ALARM,
base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(1)));
}
TEST_F(ArcTimerManagerTest, CheckInvalidCreateTimersArgs) {
std::vector<clockid_t> clocks = {CLOCK_REALTIME_ALARM, CLOCK_BOOTTIME_ALARM,
CLOCK_REALTIME_ALARM};
// Creating timers should fail when duplicate clock ids are passed in.
ASSERT_FALSE(CreateTimers("Test", clocks));
}
TEST_F(ArcTimerManagerTest, CheckInvalidStartTimerArgs) {
std::vector<clockid_t> clocks = {CLOCK_REALTIME_ALARM};
const std::string kTag = "Test";
ASSERT_TRUE(CreateTimers(kTag, clocks));
// Starting timer for unregistered clock id should fail.
ASSERT_FALSE(StartTimer(
kTag, CLOCK_BOOTTIME_ALARM,
base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(1)));
}
TEST_F(ArcTimerManagerTest, CheckMultipleCreateTimers) {
std::vector<clockid_t> clocks = {CLOCK_REALTIME_ALARM};
const std::string kTag = "Test1";
ASSERT_TRUE(CreateTimers(kTag, clocks));
std::vector<ArcTimerManager::TimerId> first_create_ids =
arc_timer_manager_.GetTimerIdsForTesting(kTag);
// Creating timers with a registered tag should delete the old timers
// associated with the tag and succeed. Check that the delete succeeded by
// checking that the new timer ids are different from the old timer ids.
ASSERT_TRUE(CreateTimers(kTag, clocks));
std::vector<ArcTimerManager::TimerId> second_create_ids =
arc_timer_manager_.GetTimerIdsForTesting(kTag);
ASSERT_TRUE(
AreTimerIdsIdenticalSizeButDistinct(first_create_ids, second_create_ids));
// Creating timers after deleting old timers should succeed.
ASSERT_TRUE(DeleteTimers(kTag));
ASSERT_TRUE(CreateTimers(kTag, clocks));
// Create timers with a different tag should also succeed.
ASSERT_TRUE(CreateTimers("Test2", clocks));
}
TEST_F(ArcTimerManagerTest, CheckDeleteAndStartOther) {
// Create timers with two different tags. Delete the first tag and check if
// the second tag's timers still start.
std::vector<clockid_t> clocks = {CLOCK_REALTIME_ALARM};
const std::string kTag1 = "Test1";
ASSERT_TRUE(CreateTimers(kTag1, clocks));
const std::string kTag2 = "Test2";
ASSERT_TRUE(CreateTimers(kTag2, clocks));
ASSERT_TRUE(DeleteTimers(kTag1));
ASSERT_TRUE(StartTimer(
kTag2, CLOCK_REALTIME_ALARM,
base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(1)));
ASSERT_TRUE(WaitForExpiration(kTag2, CLOCK_REALTIME_ALARM));
}
} // namespace system
} // namespace power_manager