blob: d91abf12cc5a73bc01de1f9829e04ecb3c9fe8e3 [file] [log] [blame]
// Copyright 2018 The Chromium 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 <map>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/files/scoped_file.h"
#include "base/optional.h"
#include "base/posix/unix_domain_socket.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/power_manager_client.h"
#include "components/arc/arc_bridge_service.h"
#include "components/arc/arc_service_manager.h"
#include "components/arc/common/timer.mojom.h"
#include "components/arc/connection_holder.h"
#include "components/arc/test/connection_holder_util.h"
#include "components/arc/test/fake_timer_instance.h"
#include "components/arc/test/test_browser_context.h"
#include "components/arc/timer/arc_timer_bridge.h"
#include "components/arc/timer/arc_timer_struct_traits.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "mojo/public/cpp/system/handle.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace arc {
namespace {
// Converts a system file descriptor to a mojo handle that can be sent to the
// host.
mojo::ScopedHandle WrapPlatformFd(base::ScopedFD scoped_fd) {
mojo::ScopedHandle handle = mojo::WrapPlatformFile(scoped_fd.release());
if (!handle.is_valid()) {
LOG(ERROR) << "Failed to wrap platform handle";
return mojo::ScopedHandle();
}
return handle;
}
// Callback for D-Bus operations.
void TimerOperationCallback(base::OnceClosure quit_callback,
bool* op_result,
mojom::ArcTimerResult result) {
*op_result = (result == mojom::ArcTimerResult::SUCCESS);
std::move(quit_callback).Run();
}
// 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;
bool AddTimer(clockid_t clock_id, base::ScopedFD read_fd) {
return arc_timers_.emplace(clock_id, std::move(read_fd)).second;
}
void ClearTimers() { return arc_timers_.clear(); }
base::Optional<int> GetTimerReadFd(clockid_t clock_id) {
if (!HasTimer(clock_id))
return base::nullopt;
return base::Optional<int>(arc_timers_[clock_id].get());
}
bool HasTimer(clockid_t clock_id) const {
auto it = arc_timers_.find(clock_id);
return it != arc_timers_.end() && it->second.is_valid();
}
private:
// Map of a clock id to read fd that is signalled when the timer corresponding
// the clock expires.
std::map<clockid_t, base::ScopedFD> arc_timers_;
DISALLOW_COPY_AND_ASSIGN(ArcTimerStore);
};
class ArcTimerTest : public testing::Test {
public:
ArcTimerTest()
: thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {
chromeos::DBusThreadManager::Initialize();
timer_bridge_ = ArcTimerBridge::GetForBrowserContextForTesting(&context_);
// This results in ArcTimerBridge::OnInstanceReady being called.
ArcServiceManager::Get()->arc_bridge_service()->timer()->SetInstance(
&timer_instance_);
WaitForInstanceReady(
ArcServiceManager::Get()->arc_bridge_service()->timer());
}
~ArcTimerTest() override {
// Destroys the FakeTimerInstance. This results in
// ArcTimerBridge::OnInstanceClosed being called.
ArcServiceManager::Get()->arc_bridge_service()->timer()->CloseInstance(
&timer_instance_);
timer_bridge_->Shutdown();
chromeos::DBusThreadManager::Shutdown();
}
protected:
// Returns true iff timer creation of each clock type succeeded.
bool CreateTimers(const std::vector<clockid_t>& clocks);
// Returns true iff a timer for |clock_id| is successfully scheduled.
bool StartTimer(clockid_t clock_id, base::TimeTicks absolute_expiration_time);
// Returns true iff the read descriptor of a timer is signalled. If the
// signalling is incorrect returns false. Blocks otherwise.
bool WaitForExpiration(clockid_t clock_id);
private:
// Stores |read_fds| corresponding to clock ids in |clocks| in
// |arc_timer_store_|.
bool StoreReadFds(const std::vector<clockid_t> clocks,
std::vector<base::ScopedFD> read_fds);
content::TestBrowserThreadBundle thread_bundle_;
ArcServiceManager arc_service_manager_;
TestBrowserContext context_;
FakeTimerInstance timer_instance_;
ArcTimerStore arc_timer_store_;
ArcTimerBridge* timer_bridge_;
DISALLOW_COPY_AND_ASSIGN(ArcTimerTest);
};
bool ArcTimerTest::StoreReadFds(const std::vector<clockid_t> clocks,
std::vector<base::ScopedFD> read_fds) {
auto read_fd_iter = read_fds.begin();
for (auto clock_id : clocks) {
// This should never fail because at this point timers have been created by
// powerd and |clocks| doesn't have any duplicate clock ids.
if (!arc_timer_store_.AddTimer(clock_id, std::move(*read_fd_iter))) {
LOG(ERROR) << "Error while adding clock=" << clock_id << " to store";
arc_timer_store_.ClearTimers();
return false;
}
read_fd_iter++;
}
return true;
}
bool ArcTimerTest::CreateTimers(const std::vector<clockid_t>& clocks) {
// Create requests to create a timer for each clock.
std::vector<mojom::CreateTimerRequestPtr> arc_timer_requests;
std::vector<base::ScopedFD> read_fds;
for (auto clock_id : clocks) {
mojom::CreateTimerRequestPtr request = mojom::CreateTimerRequest::New();
// 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 and reading.
base::ScopedFD read_fd;
base::ScopedFD write_fd;
if (!base::CreateSocketPair(&read_fd, &write_fd)) {
LOG(ERROR) << "Failed to create socket pair for ARC timers";
return false;
}
request->clock_id = clock_id;
request->expiration_fd = WrapPlatformFd(std::move(write_fd));
arc_timer_requests.emplace_back(std::move(request));
read_fds.emplace_back(std::move(read_fd));
}
// Clear local test state before creating timers.
arc_timer_store_.ClearTimers();
// Call the host to create timers. Safe to use base::Unretained(this) as the
// class is guaranteed to exist for the duration of the test.
bool result;
base::RunLoop loop;
timer_instance_.GetTimerHost()->CreateTimers(
std::move(arc_timer_requests),
base::BindOnce(&TimerOperationCallback, loop.QuitClosure(), &result));
loop.Run();
if (!result)
return false;
// If timer creation succeeded, store the read fds associated with each clock
// in the store. The read fd will be used to wait on for a timer expiration.
if (!StoreReadFds(clocks, std::move(read_fds))) {
return false;
}
return true;
}
bool ArcTimerTest::StartTimer(clockid_t clock_id,
base::TimeTicks absolute_expiration_time) {
// Call the host to start a timer corresponding to |clock_id|. Safe to use
// base::Unretained(this) as the class is guaranteed to exist for the
// duration of the test.
base::RunLoop loop;
bool result;
timer_instance_.GetTimerHost()->StartTimer(
clock_id, absolute_expiration_time,
base::BindOnce(&TimerOperationCallback, loop.QuitClosure(), &result));
loop.Run();
return result;
}
bool ArcTimerTest::WaitForExpiration(clockid_t clock_id) {
if (!arc_timer_store_.HasTimer(clock_id)) {
LOG(ERROR) << "Timer of clock=" << clock_id << " not present";
return false;
}
// Wait for the host to indicate expiration by watching the read end of the
// socket pair.
base::Optional<int> timer_read_fd_opt =
arc_timer_store_.GetTimerReadFd(clock_id);
// This should never happen if the timer was present in the store.
if (!timer_read_fd_opt.has_value()) {
ADD_FAILURE() << "Clock=" << clock_id << " read fd not found";
return false;
}
int timer_read_fd = timer_read_fd_opt.value();
base::RunLoop loop;
std::unique_ptr<base::FileDescriptorWatcher::Controller>
watch_readable_controller = base::FileDescriptorWatcher::WatchReadable(
timer_read_fd, loop.QuitClosure());
loop.Run();
// The timer expects 8 bytes to be written from the host upon expiration and
// the number of expirations to be 1.
uint64_t num_expirations;
std::vector<base::ScopedFD> fds;
ssize_t bytes_read = base::UnixDomainSocket::RecvMsg(
timer_read_fd, &num_expirations, sizeof(num_expirations), &fds);
if (bytes_read < static_cast<ssize_t>(sizeof(num_expirations))) {
LOG(ERROR) << "Incorrect timer wake up bytes_read=" << bytes_read;
return false;
}
EXPECT_EQ(num_expirations, 1ULL);
// TODO(fdoray): Remove this hack once WatchReadable fixes crbug.com/74118.
// This is required for |watch_readable_controller| to clean up properly.
base::RunLoop run_loop;
run_loop.RunUntilIdle();
return true;
}
TEST_F(ArcTimerTest, StartTimerTest) {
std::vector<clockid_t> clocks = {CLOCK_REALTIME_ALARM, CLOCK_BOOTTIME_ALARM};
// Create timers before starting it.
EXPECT_TRUE(CreateTimers(clocks));
// Start timer and check if timer expired.
base::TimeDelta delay = base::TimeDelta::FromMilliseconds(20);
EXPECT_TRUE(StartTimer(CLOCK_BOOTTIME_ALARM, base::TimeTicks::Now() + delay));
EXPECT_TRUE(WaitForExpiration(CLOCK_BOOTTIME_ALARM));
}
TEST_F(ArcTimerTest, InvalidCreateTimersArgsTest) {
std::vector<clockid_t> clocks = {CLOCK_REALTIME_ALARM, CLOCK_BOOTTIME_ALARM,
CLOCK_BOOTTIME_ALARM};
// Timers with duplicate clock ids shouldn't succeed.
EXPECT_FALSE(CreateTimers(clocks));
}
TEST_F(ArcTimerTest, InvalidStartTimerArgsTest) {
std::vector<clockid_t> clocks = {CLOCK_REALTIME_ALARM};
EXPECT_TRUE(CreateTimers(clocks));
// Start timer should fail due to un-registered clock id.
base::TimeDelta delay = base::TimeDelta::FromMilliseconds(20);
EXPECT_FALSE(
StartTimer(CLOCK_BOOTTIME_ALARM, base::TimeTicks::Now() + delay));
}
TEST_F(ArcTimerTest, CheckMultipleCreateTimersTest) {
std::vector<clockid_t> clocks = {CLOCK_REALTIME_ALARM};
EXPECT_TRUE(CreateTimers(clocks));
// The create implementation calls powerd's delete API before calling create.
// The second create should thus succeed.
EXPECT_TRUE(CreateTimers(clocks));
}
} // namespace
} // namespace arc