1 /**
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "combined_event_loop.h"
17 #include "expect_pauses.h"
18 #include "inspector_test_base.h"
19 #include "instruction_pointer.h"
20 #include "json_object_matcher.h"
21 #include "test_frame.h"
22 #include "test_method.h"
23
24 #include "utils/json_builder.h"
25 #include "utils/json_parser.h"
26 #include "utils/utf.h"
27
28 #include "gtest/gtest.h"
29 #include "gmock/gmock.h"
30
31 #include <cstddef>
32 #include <optional>
33 #include <string>
34 #include <utility>
35
36 using testing::_;
37 using testing::Matcher;
38
39 namespace panda::tooling::inspector::test {
40 class BreakpointTest : public InspectorTestBase {
41 protected:
SetUpSourceFiles()42 void SetUpSourceFiles() override
43 {
44 auto klass = LoadSourceFile(R"(
45 .function void func() {
46 nop
47
48 return
49 }
50
51 .function void main() {
52 call func
53 call func
54 return
55 }
56 )");
57
58 mainFrame_.SetMethod(klass->GetDirectMethod(utf::CStringAsMutf8("main")));
59 func_.Set(klass->GetDirectMethod(utf::CStringAsMutf8("func")));
60 }
61
SetUp()62 void SetUp() override
63 {
64 InspectorTestBase::SetUp();
65
66 client_.OnCall("Debugger.scriptParsed", [this](const JsonObject &script) {
67 auto scriptId = script.GetValue<JsonObject::StringT>("scriptId");
68 ASSERT_NE(scriptId, nullptr) << "No 'scriptId' property";
69 scriptId_ = *scriptId;
70
71 auto url = script.GetValue<JsonObject::StringT>("url");
72 ASSERT_NE(url, nullptr) << "No 'url' property";
73 url_ = *url;
74 });
75
76 main_.Resume();
77 (server_ + client_).Poll();
78 EXPECT_FALSE(scriptId_.empty());
79 }
80
TearDown()81 void TearDown() override
82 {
83 main_.Finish();
84 InspectorTestBase::TearDown();
85 }
86
87 template <size_t... Breakpoint>
88 void CheckPossibleBreakpoints(size_t startLine, std::optional<size_t> endLine,
89 std::optional<bool> restrictToFunction,
90 std::index_sequence<Breakpoint...> /* breakpoints */);
91
92 std::string scriptId_;
93 std::string url_;
94 TestFrame mainFrame_ {debugger_};
95 InstructionPointer main_ {mainFrame_, client_, debugger_};
96 TestMethod func_ {debugger_, client_};
97 };
98
99 template <size_t... Breakpoint>
CheckPossibleBreakpoints(size_t startLine,std::optional<size_t> endLine,std::optional<bool> restrictToFunction,std::index_sequence<Breakpoint...>)100 void BreakpointTest::CheckPossibleBreakpoints(size_t startLine, std::optional<size_t> endLine,
101 std::optional<bool> restrictToFunction,
102 std::index_sequence<Breakpoint...> /* breakpoints */)
103 {
104 CallSync(
105 "Debugger.getPossibleBreakpoints",
106 [this, startLine, endLine, restrictToFunction](JsonObjectBuilder ¶ms) {
107 params.AddProperty("start", [this, startLine](JsonObjectBuilder &start) {
108 start.AddProperty("scriptId", scriptId_);
109 start.AddProperty("lineNumber", startLine);
110 });
111 if (endLine) {
112 params.AddProperty("end", [this, endLine](JsonObjectBuilder &end) {
113 end.AddProperty("scriptId", scriptId_);
114 end.AddProperty("lineNumber", *endLine);
115 });
116 }
117 if (restrictToFunction) {
118 params.AddProperty("restrictToFunction", *restrictToFunction);
119 }
120 },
121 [this](const JsonObject &result) {
122 (void)this;
123 auto resultMatcher = JsonProperties(JsonProperty<JsonObject::ArrayT> {
124 "array", JsonElements(Matcher<JsonObject::JsonObjPointer> {
125 Pointee(JsonProperties(JsonProperty<JsonObject::StringT> {"scriptId", scriptId_},
126 JsonProperty<JsonObject::NumT> {"lineNumber", Breakpoint}))}...)});
127 EXPECT_THAT(result, resultMatcher);
128 }); // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo)
129 }
130
TEST_F(BreakpointTest,GetsPossibleBreakpoints)131 TEST_F(BreakpointTest, GetsPossibleBreakpoints)
132 {
133 CheckPossibleBreakpoints(4, 10, std::nullopt, std::index_sequence<4, 8, 9> {});
134 CheckPossibleBreakpoints(9, std::nullopt, std::nullopt, std::index_sequence<9, 10> {});
135 CheckPossibleBreakpoints(4, 9, true, std::index_sequence<4> {});
136 CheckPossibleBreakpoints(3, std::nullopt, true, std::index_sequence<4> {});
137 CheckPossibleBreakpoints(6, 10, true, std::index_sequence<> {});
138 CheckPossibleBreakpoints(0, std::nullopt, true, std::index_sequence<> {});
139 }
140
141 // When stopped at a breakpoint while doing a step and resumed,
142 // the unfinished step command should be canceled.
TEST_F(BreakpointTest,InterruptsStep)143 TEST_F(BreakpointTest, InterruptsStep)
144 {
145 CallSync("Debugger.setBreakpoint", [&](auto ¶ms) {
146 params.AddProperty("location", [&](JsonObjectBuilder &location) {
147 location.AddProperty("scriptId", scriptId_);
148 location.AddProperty("lineNumber", 2);
149 });
150 });
151
152 CallSync("Debugger.setBreakpoint", [&](auto ¶ms) {
153 params.AddProperty("location", [&](JsonObjectBuilder &location) {
154 location.AddProperty("scriptId", scriptId_);
155 location.AddProperty("lineNumber", 9);
156 });
157 });
158
159 ExpectPauses(client_, {
160 Breakpoint(CallFrame(func_, 2), CallFrame(main_.GetMethod(), 8)),
161 Breakpoint(CallFrame(main_.GetMethod(), 9)),
162 Breakpoint(CallFrame(func_, 2), CallFrame(main_.GetMethod(), 9)),
163 });
164
165 func_.Call([](auto &func) { func.Resume(); });
166 main_.StepOver();
167 func_.Call([](auto &func) { func.Resume(); });
168 main_.Finish();
169 }
170
TEST_F(BreakpointTest,RemovesBreakpoint)171 TEST_F(BreakpointTest, RemovesBreakpoint)
172 {
173 auto breakpointId = CallSync(
174 "Debugger.setBreakpoint",
175 [&this](auto ¶ms) {
176 params.AddProperty("location", [&this](JsonObjectBuilder &location) {
177 location.AddProperty("scriptId", scriptId_);
178 location.AddProperty("lineNumber", 2);
179 });
180 },
181 [](const JsonObject &result) {
182 auto id = result.GetValue<JsonObject::StringT>("breakpointId");
183 return id != nullptr ? std::make_optional(*id) : std::nullopt;
184 });
185 ASSERT_TRUE(breakpointId.has_value());
186
187 ExpectPauses(client_, {
188 Breakpoint(CallFrame(func_, 2), CallFrame(main_.GetMethod(), 8)),
189 });
190
191 func_.Call([](auto &func) { func.Resume(); });
192 main_.Step();
193
194 CallSync(
195 "Debugger.removeBreakpoint", [&](auto ¶ms) { params.AddProperty("breakpointId", *breakpointId); },
196 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo)
197 [](auto &result) { EXPECT_THAT(result, JsonProperties()); });
198
199 func_.Call([](auto &func) { func.Resume(); });
200 main_.Finish();
201 }
202
TEST_F(BreakpointTest,SetsBreakpoint)203 TEST_F(BreakpointTest, SetsBreakpoint)
204 {
205 CallSync(
206 "Debugger.setBreakpoint",
207 [&](auto ¶ms) {
208 params.AddProperty("location", [&](JsonObjectBuilder &location) {
209 location.AddProperty("scriptId", scriptId_);
210 location.AddProperty("lineNumber", 2);
211 });
212 },
213 [&](auto &result) {
214 EXPECT_THAT(
215 result,
216 JsonProperties(JsonProperty<JsonObject::StringT> {"breakpointId", _},
217 JsonProperty<JsonObject::JsonObjPointer> {
218 "actualLocation",
219 Pointee(JsonProperties(JsonProperty<JsonObject::StringT> {"scriptId", scriptId_},
220 JsonProperty<JsonObject::NumT> {"lineNumber", 2}))}));
221 });
222
223 ExpectPauses(client_, {
224 Breakpoint(CallFrame(func_, 2), CallFrame(main_.GetMethod(), 8)),
225 Breakpoint(CallFrame(func_, 2), CallFrame(main_.GetMethod(), 9)),
226 });
227
228 func_.Call([](auto &func) { func.Resume(); });
229 main_.Step();
230 func_.Call([](auto &func) { func.Resume(); });
231 main_.Finish();
232 }
233
TEST_F(BreakpointTest,SetsBreakpointByUrl)234 TEST_F(BreakpointTest, SetsBreakpointByUrl)
235 {
236 CallSync(
237 "Debugger.setBreakpointByUrl",
238 [&](auto ¶ms) {
239 params.AddProperty("lineNumber", 2);
240 params.AddProperty("url", url_);
241 },
242 [&](auto &result) {
243 EXPECT_THAT(result,
244 JsonProperties(JsonProperty<JsonObject::StringT> {"breakpointId", _},
245 JsonProperty<JsonObject::ArrayT> {
246 "locations",
247 JsonElements(Matcher<JsonObject::JsonObjPointer> {Pointee(
248 JsonProperties(JsonProperty<JsonObject::StringT> {"scriptId", scriptId_},
249 JsonProperty<JsonObject::NumT> {"lineNumber", 2}))})}));
250 });
251
252 ExpectPauses(client_, {
253 Breakpoint(CallFrame(func_, 2), CallFrame(main_.GetMethod(), 8)),
254 Breakpoint(CallFrame(func_, 2), CallFrame(main_.GetMethod(), 9)),
255 });
256
257 func_.Call([](auto &func) { func.Resume(); });
258 main_.Step();
259 func_.Call([](auto &func) { func.Resume(); });
260 main_.Finish();
261 }
262
TEST_F(BreakpointTest,SetsBreakpointByUrlRegex)263 TEST_F(BreakpointTest, SetsBreakpointByUrlRegex)
264 {
265 CallSync(
266 "Debugger.setBreakpointByUrl",
267 [](auto ¶ms) {
268 params.AddProperty("lineNumber", 2);
269 params.AddProperty("urlRegex", ".*");
270 },
271 [&](auto &result) {
272 EXPECT_THAT(result,
273 JsonProperties(JsonProperty<JsonObject::StringT> {"breakpointId", _},
274 JsonProperty<JsonObject::ArrayT> {
275 "locations",
276 JsonElements(Matcher<JsonObject::JsonObjPointer> {Pointee(
277 JsonProperties(JsonProperty<JsonObject::StringT> {"scriptId", scriptId_},
278 JsonProperty<JsonObject::NumT> {"lineNumber", 2}))})}));
279 });
280
281 ExpectPauses(client_, {
282 Breakpoint(CallFrame(func_, 2), CallFrame(main_.GetMethod(), 8)),
283 Breakpoint(CallFrame(func_, 2), CallFrame(main_.GetMethod(), 9)),
284 });
285
286 func_.Call([](auto &func) { func.Resume(); });
287 main_.Step();
288 func_.Call([](auto &func) { func.Resume(); });
289 main_.Finish();
290 }
291
TEST_F(BreakpointTest,SetsBreakpointsActive)292 TEST_F(BreakpointTest, SetsBreakpointsActive)
293 {
294 CallSync("Debugger.setBreakpoint", [&](auto ¶ms) {
295 params.AddProperty("location", [&](JsonObjectBuilder &location) {
296 location.AddProperty("scriptId", scriptId_);
297 location.AddProperty("lineNumber", 2);
298 });
299 });
300
301 CallSync(
302 "Debugger.setBreakpointsActive", [](auto ¶ms) { params.AddProperty("active", false); },
303 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo)
304 [](auto &result) { EXPECT_THAT(result, JsonProperties()); });
305
306 ExpectPauses(client_, {
307 Breakpoint(CallFrame(func_, 2), CallFrame(main_.GetMethod(), 9)),
308 });
309
310 func_.Call([](auto &func) { func.Resume(); });
311 main_.Step();
312
313 CallSync(
314 "Debugger.setBreakpointsActive", [](auto ¶ms) { params.AddProperty("active", true); },
315 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) due to bug in clang-tidy #3553 (gtest repo)
316 [](auto &result) { EXPECT_THAT(result, JsonProperties()); });
317
318 func_.Call([](auto &func) { func.Resume(); });
319 main_.Finish();
320 }
321 } // namespace panda::tooling::inspector::test
322