1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
ash / components / arc / timer / arc_timer_bridge_unittest.cc [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/components/arc/timer/arc_timer_bridge.h"
#include <map>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "ash/components/arc/mojom/timer.mojom.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/components/arc/session/connection_holder.h"
#include "ash/components/arc/test/connection_holder_util.h"
#include "ash/components/arc/test/fake_timer_instance.h"
#include "ash/components/arc/timer/arc_timer_mojom_traits.h"
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/files/scoped_file.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/posix/unix_domain_socket.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "chromeos/ash/components/dbus/upstart/fake_upstart_client.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "components/user_prefs/test/test_browser_context_with_prefs.h"
#include "content/public/test/browser_task_environment.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(std::move(scoped_fd));
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;
ArcTimerStore(const ArcTimerStore&) = delete;
ArcTimerStore& operator=(const ArcTimerStore&) = delete;
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(); }
std::optional<int> GetTimerReadFd(clockid_t clock_id) {
if (!HasTimer(clock_id))
return std::nullopt;
return std::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_;
};
class ArcTimerTest : public testing::Test {
public:
ArcTimerTest()
: task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {
ash::UpstartClient::InitializeFake();
chromeos::PowerManagerClient::InitializeFake();
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(const ArcTimerTest&) = delete;
ArcTimerTest& operator=(const ArcTimerTest&) = delete;
~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::PowerManagerClient::Shutdown();
ash::UpstartClient::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);
mojom::TimerHost* GetTimerHost() { return timer_instance_.GetTimerHost(); }
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::BrowserTaskEnvironment task_environment_;
ArcServiceManager arc_service_manager_;
user_prefs::TestBrowserContextWithPrefs context_;
FakeTimerInstance timer_instance_;
ArcTimerStore arc_timer_store_;
raw_ptr<ArcTimerBridge> timer_bridge_;
};
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.
std::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::Milliseconds(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::Milliseconds(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 power manager implicitly deletes old timers associated with a tag
// during a create call. Thus, consecutive create calls should succeed.
EXPECT_TRUE(CreateTimers(clocks));
}
TEST_F(ArcTimerTest, SetTimeTest_RequestedTimeIsInvalid) {
// Time::Now() + 25 hours should be rejected.
base::Time time_to_set =
base::Time::Now() + kArcSetTimeMaxTimeDelta + base::Hours(1);
base::test::TestFuture<mojom::ArcTimerResult> future;
GetTimerHost()->SetTime(time_to_set, future.GetCallback());
EXPECT_EQ(future.Get(), mojom::ArcTimerResult::FAILURE);
// Time::Now() - 25 hours should be rejected.
time_to_set = base::Time::Now() - kArcSetTimeMaxTimeDelta - base::Hours(1);
base::test::TestFuture<mojom::ArcTimerResult> future2;
GetTimerHost()->SetTime(time_to_set, future2.GetCallback());
EXPECT_EQ(future2.Get(), mojom::ArcTimerResult::FAILURE);
}
TEST_F(ArcTimerTest, SetTimeTest_RequestedTimeIsValid) {
// Time::Now() + 23 hours should be accepted.
const base::Time time_to_set =
base::Time::Now() + kArcSetTimeMaxTimeDelta - base::Hours(1);
ash::FakeUpstartClient::Get()->set_start_job_cb(base::BindLambdaForTesting(
[time_to_set](const std::string& job_name,
const std::vector<std::string>& env) {
EXPECT_EQ(job_name, kArcSetTimeJobName);
EXPECT_EQ(env.size(), 1U); // Can't use ASSERT_EQ inside the closure.
if (env.size() >= 1) {
EXPECT_EQ(env[0], base::StringPrintf("UNIXTIME_TO_SET=%ld",
time_to_set.ToTimeT()));
}
return ash::FakeUpstartClient::StartJobResult(true /* success */);
}));
base::test::TestFuture<mojom::ArcTimerResult> future;
GetTimerHost()->SetTime(time_to_set, future.GetCallback());
EXPECT_EQ(future.Get(), mojom::ArcTimerResult::SUCCESS);
}
TEST_F(ArcTimerTest, SetTimeTest_UpstartJobFails) {
const base::Time time_to_set =
base::Time::Now() + kArcSetTimeMaxTimeDelta - base::Hours(1);
ash::FakeUpstartClient::Get()->set_start_job_cb(base::BindRepeating(
[](const std::string& job_name, const std::vector<std::string>& env) {
// Upstart job fails.
return ash::FakeUpstartClient::StartJobResult(false /* success */);
}));
base::test::TestFuture<mojom::ArcTimerResult> future;
GetTimerHost()->SetTime(time_to_set, future.GetCallback());
EXPECT_EQ(future.Get(), mojom::ArcTimerResult::FAILURE);
}
} // namespace
} // namespace arc