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
content / services / auction_worklet / debug_command_queue_unittest.cc [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/services/auction_worklet/debug_command_queue.h"
#include <string>
#include <vector>
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/thread_annotations.h"
#include "content/services/auction_worklet/auction_v8_helper.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::ElementsAre;
namespace auction_worklet {
class DebugCommandQueueTest : public testing::Test {
public:
static const int kId = 4;
DebugCommandQueueTest()
: v8_runner_(AuctionV8Helper::CreateTaskRunner()),
command_queue_(base::MakeRefCounted<DebugCommandQueue>(v8_runner_)) {}
void QueueFromV8ThreadAndWait(base::OnceClosure to_post) {
base::RunLoop run_loop;
v8_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<DebugCommandQueue> queue,
base::OnceClosure to_post, base::OnceClosure done) {
queue->QueueTaskForV8Thread(std::move(to_post));
std::move(done).Run();
},
command_queue_, std::move(to_post), run_loop.QuitClosure()));
run_loop.Run();
}
std::vector<std::string> TakeLog() {
base::AutoLock auto_lock(lock_);
std::vector<std::string> result = std::move(log_);
return result;
}
base::OnceClosure LogString(std::string msg) {
return base::BindOnce(&DebugCommandQueueTest::DoLog, base::Unretained(this),
std::move(msg));
}
base::OnceClosure PauseForDebuggerAndRunCommands(
base::OnceClosure abort_helper) {
return base::BindOnce(
&DebugCommandQueueTest::DoPauseForDebuggerAndRunCommands,
base::Unretained(this), std::move(abort_helper));
}
base::OnceClosure ShouldNotUseAbortHelper() {
return base::BindOnce(
[]() { ADD_FAILURE() << "Abort helper shouldn't get used here"; });
}
base::OnceClosure QuitPauseForDebugger() {
return base::BindOnce(&DebugCommandQueueTest::DoQuitPauseForDebugger,
base::Unretained(this));
}
protected:
void DoLog(std::string message) {
DCHECK(v8_runner_->RunsTasksInCurrentSequence());
base::AutoLock auto_lock(lock_);
log_.push_back(std::move(message));
}
void DoPauseForDebuggerAndRunCommands(base::OnceClosure abort_helper) {
DoLog("Pause start");
command_queue_->PauseForDebuggerAndRunCommands(kId,
std::move(abort_helper));
DoLog("Pause end");
}
void DoQuitPauseForDebugger() {
DoLog("Requested pause end");
command_queue_->QuitPauseForDebugger();
}
base::test::TaskEnvironment task_environment_;
scoped_refptr<base::SingleThreadTaskRunner> v8_runner_;
scoped_refptr<DebugCommandQueue> command_queue_;
std::vector<std::string> log_ GUARDED_BY(lock_);
base::Lock lock_;
};
TEST_F(DebugCommandQueueTest, TopLevel) {
base::RunLoop run_loop;
command_queue_->QueueTaskForV8Thread(LogString("1"));
command_queue_->QueueTaskForV8Thread(LogString("2"));
command_queue_->QueueTaskForV8Thread(LogString("3"));
command_queue_->QueueTaskForV8Thread(run_loop.QuitClosure());
run_loop.Run();
EXPECT_THAT(TakeLog(), ElementsAre("1", "2", "3"));
}
TEST_F(DebugCommandQueueTest, Paused) {
base::RunLoop run_loop;
command_queue_->QueueTaskForV8Thread(LogString("1"));
command_queue_->QueueTaskForV8Thread(LogString("2"));
command_queue_->QueueTaskForV8Thread(
PauseForDebuggerAndRunCommands(ShouldNotUseAbortHelper()));
command_queue_->QueueTaskForV8Thread(LogString("3"));
command_queue_->QueueTaskForV8Thread(LogString("4"));
command_queue_->QueueTaskForV8Thread(QuitPauseForDebugger());
command_queue_->QueueTaskForV8Thread(LogString("5"));
command_queue_->QueueTaskForV8Thread(LogString("6"));
command_queue_->QueueTaskForV8Thread(run_loop.QuitClosure());
run_loop.Run();
EXPECT_THAT(TakeLog(),
ElementsAre("1", "2", "Pause start", "3", "4",
"Requested pause end", "Pause end", "5", "6"));
}
TEST_F(DebugCommandQueueTest, QueueFromV8Thread) {
base::RunLoop run_loop;
QueueFromV8ThreadAndWait(LogString("1"));
QueueFromV8ThreadAndWait(LogString("2"));
command_queue_->QueueTaskForV8Thread(LogString("3"));
command_queue_->QueueTaskForV8Thread(LogString("4"));
QueueFromV8ThreadAndWait(LogString("5"));
QueueFromV8ThreadAndWait(LogString("6"));
command_queue_->QueueTaskForV8Thread(run_loop.QuitClosure());
run_loop.Run();
EXPECT_THAT(TakeLog(), ElementsAre("1", "2", "3", "4", "5", "6"));
}
TEST_F(DebugCommandQueueTest, QueueFromTask) {
// A task that itself queues more tasks.
base::RunLoop run_loop;
command_queue_->QueueTaskForV8Thread(base::BindOnce(
[](scoped_refptr<DebugCommandQueue> command_queue, base::OnceClosure log1,
base::OnceClosure log2, base::OnceClosure log3,
base::OnceClosure quit_closure) {
std::move(log1).Run();
command_queue->QueueTaskForV8Thread(std::move(log2));
command_queue->QueueTaskForV8Thread(std::move(log3));
command_queue->QueueTaskForV8Thread(std::move(quit_closure));
},
command_queue_, LogString("1"), LogString("2"), LogString("3"),
run_loop.QuitClosure()));
run_loop.Run();
EXPECT_THAT(TakeLog(), ElementsAre("1", "2", "3"));
}
TEST_F(DebugCommandQueueTest, QueueFromPauseTask) {
// A task run from within PauseForDebuggerAndRunCommands() that itself queues
// more tasks.
base::RunLoop run_loop;
command_queue_->QueueTaskForV8Thread(
PauseForDebuggerAndRunCommands(ShouldNotUseAbortHelper()));
command_queue_->QueueTaskForV8Thread(base::BindOnce(
[](scoped_refptr<DebugCommandQueue> command_queue, base::OnceClosure log1,
base::OnceClosure log2, base::OnceClosure log3,
base::OnceClosure quit_closure) {
std::move(log1).Run();
command_queue->QueueTaskForV8Thread(std::move(log2));
command_queue->QueueTaskForV8Thread(std::move(log3));
command_queue->QueueTaskForV8Thread(std::move(quit_closure));
},
command_queue_, LogString("1"), LogString("2"), LogString("3"),
run_loop.QuitClosure()));
run_loop.Run();
base::RunLoop run_loop2;
command_queue_->QueueTaskForV8Thread(QuitPauseForDebugger());
command_queue_->QueueTaskForV8Thread(run_loop2.QuitClosure());
run_loop2.Run();
EXPECT_THAT(TakeLog(), ElementsAre("Pause start", "1", "2", "3",
"Requested pause end", "Pause end"));
}
TEST_F(DebugCommandQueueTest, AbortPausesBeforePause) {
// Check effect of AbortPauses before pause was even request.
command_queue_->AbortPauses(kId);
command_queue_->QueueTaskForV8Thread(
PauseForDebuggerAndRunCommands(LogString("Should not happen")));
command_queue_->QueueTaskForV8Thread(LogString("1"));
// Second attempt to pause should also be cancelled.
command_queue_->QueueTaskForV8Thread(
PauseForDebuggerAndRunCommands(LogString("Nor this")));
command_queue_->QueueTaskForV8Thread(LogString("2"));
// Wait for other thread to finish all work.
task_environment_.RunUntilIdle();
EXPECT_THAT(TakeLog(), ElementsAre("Pause start", "Pause end", "1",
"Pause start", "Pause end", "2"));
// Now unblock kId, ask to abort pauses for a different one.
command_queue_->RecycleContextGroupId(kId);
command_queue_->AbortPauses(kId + 1);
// This pause should happen.
base::RunLoop run_loop;
command_queue_->QueueTaskForV8Thread(
PauseForDebuggerAndRunCommands(LogString("Nope")));
command_queue_->QueueTaskForV8Thread(LogString("3"));
command_queue_->QueueTaskForV8Thread(QuitPauseForDebugger());
command_queue_->QueueTaskForV8Thread(run_loop.QuitClosure());
run_loop.Run();
EXPECT_THAT(TakeLog(), ElementsAre("Pause start", "3", "Requested pause end",
"Pause end"));
}
TEST_F(DebugCommandQueueTest, AbortPausesAfterPause) {
// Make sure that AbortPauses happening after pause happened with same ID
// actually aborts it.
base::WaitableEvent pause_happened;
command_queue_->QueueTaskForV8Thread(
PauseForDebuggerAndRunCommands(QuitPauseForDebugger()));
command_queue_->QueueTaskForV8Thread(LogString("1"));
// Make sure we're actually paused.
command_queue_->QueueTaskForV8Thread(
base::BindLambdaForTesting([&]() { pause_happened.Signal(); }));
pause_happened.Wait();
// Request exit of a different ID.
command_queue_->AbortPauses(kId + 1);
command_queue_->QueueTaskForV8Thread(LogString("2"));
// And then right one.
command_queue_->AbortPauses(kId);
command_queue_->QueueTaskForV8Thread(LogString("3"));
// Make sure the thread is unwedged.
task_environment_.RunUntilIdle();
EXPECT_THAT(TakeLog(), ElementsAre("Pause start", "1", "2",
"Requested pause end", "Pause end", "3"));
}
} // namespace auction_worklet