| // 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 |