1#[cfg(test)]
19mod t {
20 use std::collections::HashMap;
21 use std::hint;
22 use std::sync::atomic::AtomicUsize;
23 use std::sync::atomic::Ordering;
24 use std::sync::Arc;
25 use std::thread;
26 use std::thread::ScopedJoinHandle;
27 use std::time::Duration;
28 use std::time::Instant;
29
30 use debugserver_types::*;
31 use dupe::Dupe;
32
33 use crate::assert::test_functions;
34 use crate::debug::adapter::implementation::prepare_dap_adapter;
35 use crate::debug::adapter::implementation::resolve_breakpoints;
36 use crate::debug::DapAdapter;
37 use crate::debug::DapAdapterClient;
38 use crate::debug::DapAdapterEvalHook;
39 use crate::debug::StepKind;
40 use crate::debug::VariablePath;
41 use crate::environment::GlobalsBuilder;
42 use crate::environment::Module;
43 use crate::eval::Evaluator;
44 use crate::eval::ReturnFileLoader;
45 use crate::syntax::AstModule;
46 use crate::syntax::Dialect;
47 use crate::values::OwnedFrozenValue;
48 use crate::wasm::is_wasm;
49
50 #[derive(Debug)]
51 struct Client {
52 controller: BreakpointController,
53 }
54
55 impl Client {
56 pub fn new(controller: BreakpointController) -> Self {
57 Self { controller }
58 }
59 }
60
61 impl DapAdapterClient for Client {
62 fn event_stopped(&self) -> crate::Result<()> {
63 println!("stopped!");
64 self.controller.eval_stopped()
65 }
66 }
67
68 #[derive(Debug, Clone, Dupe)]
69 struct BreakpointController {
70 breakpoints_hit: Arc<AtomicUsize>,
72 }
73
74 impl BreakpointController {
75 fn new() -> Self {
76 Self {
77 breakpoints_hit: Arc::new(AtomicUsize::new(0)),
78 }
79 }
80
81 fn get_client(&self) -> Box<dyn DapAdapterClient> {
82 Box::new(Client::new(self.dupe()))
83 }
84
85 fn eval_stopped(&self) -> crate::Result<()> {
86 loop {
87 let breakpoints_hit = self.breakpoints_hit.load(Ordering::SeqCst);
88 if breakpoints_hit == 999999 {
89 eprintln!("eval_stopped: cancelled");
90 return Err(anyhow::anyhow!("cancelled").into());
91 }
92 if self.breakpoints_hit.compare_exchange(
93 breakpoints_hit,
94 breakpoints_hit + 1,
95 Ordering::SeqCst,
96 Ordering::SeqCst,
97 ) == Ok(breakpoints_hit)
98 {
99 return Ok(());
100 }
101 }
102 }
103
104 fn wait_for_eval_stopped(&self, breakpoint_count: usize, timeout: Duration) {
105 let now = Instant::now();
106 loop {
107 let breakpoints_hit = self.breakpoints_hit.load(Ordering::SeqCst);
108 assert_ne!(breakpoints_hit, 999999, "cancelled");
109 assert!(breakpoints_hit <= breakpoint_count);
110 if breakpoints_hit == breakpoint_count {
111 break;
112 }
113 if now.elapsed() > timeout {
114 panic!("didn't hit expected breakpoint");
115 }
116 hint::spin_loop();
117 }
118 }
119 }
120
121 struct BreakpointControllerDropGuard {
122 controller: BreakpointController,
123 }
124
125 impl Drop for BreakpointControllerDropGuard {
126 fn drop(&mut self) {
127 eprintln!("dropping controller");
128 self.controller
129 .breakpoints_hit
130 .store(999999, Ordering::SeqCst);
131 }
132 }
133
134 fn breakpoint(line: i64, condition: Option<&str>) -> SourceBreakpoint {
135 SourceBreakpoint {
136 column: None,
137 condition: condition.map(|v| v.to_owned()),
138 hit_condition: None,
139 line,
140 log_message: None,
141 }
142 }
143
144 fn breakpoints_args(path: &str, lines: &[(i64, Option<&str>)]) -> SetBreakpointsArguments {
145 SetBreakpointsArguments {
146 breakpoints: Some(
147 lines
148 .iter()
149 .map(|(line, condition)| breakpoint(*line, condition.as_deref()))
150 .collect(),
151 ),
152 lines: None,
153 source: Source {
154 adapter_data: None,
155 checksums: None,
156 name: None,
157 origin: None,
158 path: Some(path.to_owned()),
159 presentation_hint: None,
160 source_reference: None,
161 sources: None,
162 },
163 source_modified: None,
164 }
165 }
166
167 fn eval_with_hook(
168 ast: AstModule,
169 hook: Box<dyn DapAdapterEvalHook>,
170 ) -> crate::Result<OwnedFrozenValue> {
171 let modules = HashMap::new();
172 let loader = ReturnFileLoader { modules: &modules };
173 let globals = GlobalsBuilder::extended().with(test_functions).build();
174 let env = Module::new();
175 let res = {
176 let mut eval = Evaluator::new(&env);
177 hook.add_dap_hooks(&mut eval);
178 eval.set_loader(&loader);
179 eval.eval_module(ast, &globals)?
180 };
181
182 env.set("_", res);
183 Ok(env
184 .freeze()
185 .expect("error freezing module")
186 .get("_")
187 .unwrap())
188 }
189
190 fn join_timeout<T>(waiting: ScopedJoinHandle<T>, timeout: Duration) -> T {
191 let start = Instant::now();
192 while !waiting.is_finished() {
193 if start.elapsed() > timeout {
194 panic!();
195 }
196 }
197 waiting.join().unwrap()
198 }
199
200 static TIMEOUT: Duration = Duration::from_secs(10);
201
202 fn dap_test_template<'env, F, R>(f: F) -> crate::Result<R>
203 where
204 F: for<'scope> FnOnce(
205 &'scope thread::Scope<'scope, 'env>,
206 BreakpointController,
207 Box<dyn DapAdapter>,
208 Box<dyn DapAdapterEvalHook>,
209 ) -> crate::Result<R>,
210 {
211 let controller = BreakpointController::new();
212
213 let _guard = BreakpointControllerDropGuard {
214 controller: controller.dupe(),
215 };
216
217 let (adapter, eval_hook) = prepare_dap_adapter(controller.get_client());
218 thread::scope(|s| f(s, controller, Box::new(adapter), Box::new(eval_hook)))
219 }
220
221 #[test]
222 fn test_breakpoint() -> crate::Result<()> {
223 if is_wasm() {
224 return Ok(());
226 }
227
228 let file_contents = "
229x = [1, 2, 3]
230print(x)
231 ";
232 dap_test_template(|s, controller, adapter, eval_hook| {
233 let ast = AstModule::parse(
234 "test.bzl",
235 file_contents.to_owned(),
236 &Dialect::AllOptionsInternal,
237 )?;
238 let breakpoints =
239 resolve_breakpoints(&breakpoints_args("test.bzl", &[(3, None)]), &ast)?;
240 adapter.set_breakpoints("test.bzl", &breakpoints)?;
241 let eval_result =
242 s.spawn(move || -> crate::Result<_> { eval_with_hook(ast, eval_hook) });
243 controller.wait_for_eval_stopped(1, TIMEOUT);
244 adapter.continue_()?;
246 controller.wait_for_eval_stopped(2, TIMEOUT);
247
248 adapter.continue_()?;
249
250 join_timeout(eval_result, TIMEOUT)?;
251 Ok(())
252 })
253 }
254
255 #[test]
256 fn test_breakpoint_with_failing_condition() -> crate::Result<()> {
257 if is_wasm() {
258 return Ok(());
259 }
260
261 let file_contents = "
262x = [1, 2, 3]
263print(x)
264 ";
265 dap_test_template(|s, _, adapter, eval_hook| {
266 let ast = AstModule::parse(
267 "test.bzl",
268 file_contents.to_owned(),
269 &Dialect::AllOptionsInternal,
270 )?;
271 let breakpoints =
272 resolve_breakpoints(&breakpoints_args("test.bzl", &[(3, Some("5 in x"))]), &ast)?;
273 adapter.set_breakpoints("test.bzl", &breakpoints)?;
274 let eval_result =
275 s.spawn(move || -> crate::Result<_> { eval_with_hook(ast, eval_hook) });
276 join_timeout(eval_result, TIMEOUT)?;
277 Ok(())
278 })
279 }
280
281 #[test]
282 fn test_breakpoint_with_passing_condition() -> crate::Result<()> {
283 if is_wasm() {
284 return Ok(());
285 }
286
287 let file_contents = "
288x = [1, 2, 3]
289print(x)
290 ";
291 dap_test_template(|s, controller, adapter, eval_hook| {
292 let ast = AstModule::parse(
293 "test.bzl",
294 file_contents.to_owned(),
295 &Dialect::AllOptionsInternal,
296 )?;
297 let breakpoints =
298 resolve_breakpoints(&breakpoints_args("test.bzl", &[(3, Some("2 in x"))]), &ast)?;
299 adapter.set_breakpoints("test.bzl", &breakpoints)?;
300 let eval_result =
301 s.spawn(move || -> crate::Result<_> { eval_with_hook(ast, eval_hook) });
302 controller.wait_for_eval_stopped(1, TIMEOUT);
303 adapter.continue_()?;
304 controller.wait_for_eval_stopped(2, TIMEOUT);
306 adapter.continue_()?;
307
308 join_timeout(eval_result, TIMEOUT)?;
309 Ok(())
310 })
311 }
312
313 #[test]
314 fn test_step_over() -> crate::Result<()> {
315 if is_wasm() {
316 return Ok(());
317 }
318
319 let file_contents = "
320def adjust(y):
321 y[0] += 1
322 y[1] += 1 # line 4
323 y[2] += 1
324x = [1, 2, 3]
325adjust(x) # line 7
326adjust(x)
327print(x)
328 ";
329 dap_test_template(|s, controller, adapter, eval_hook| {
330 let ast = AstModule::parse(
331 "test.bzl",
332 file_contents.to_owned(),
333 &Dialect::AllOptionsInternal,
334 )?;
335 let breakpoints =
336 resolve_breakpoints(&breakpoints_args("test.bzl", &[(7, None)]), &ast)?;
337 adapter.set_breakpoints("test.bzl", &breakpoints)?;
338 let eval_result =
339 s.spawn(move || -> crate::Result<_> { eval_with_hook(ast, eval_hook) });
340 controller.wait_for_eval_stopped(1, TIMEOUT);
341 adapter.continue_()?;
343 controller.wait_for_eval_stopped(2, TIMEOUT);
344
345 assert_eq!("1", adapter.evaluate("x[0]")?.result);
346 assert_eq!("2", adapter.evaluate("x[1]")?.result);
347 assert_eq!("3", adapter.evaluate("x[2]")?.result);
348 adapter.step(StepKind::Over)?;
349 controller.wait_for_eval_stopped(3, TIMEOUT);
350 assert_eq!("2", adapter.evaluate("x[0]")?.result);
351 assert_eq!("3", adapter.evaluate("x[1]")?.result);
352 assert_eq!("4", adapter.evaluate("x[2]")?.result);
353
354 adapter.step(StepKind::Over)?;
356 controller.wait_for_eval_stopped(4, TIMEOUT);
357 adapter.step(StepKind::Over)?;
358 controller.wait_for_eval_stopped(5, TIMEOUT);
359 assert_eq!("3", adapter.evaluate("x[0]")?.result);
360 assert_eq!("4", adapter.evaluate("x[1]")?.result);
361 assert_eq!("5", adapter.evaluate("x[2]")?.result);
362 adapter.continue_()?;
363 join_timeout(eval_result, TIMEOUT)?;
364 Ok(())
365 })
366 }
367
368 #[test]
369 fn test_step_into() -> crate::Result<()> {
370 if is_wasm() {
371 return Ok(());
372 }
373
374 let file_contents = "
375def adjust(y):
376 y[0] += 1
377 y[1] += 1 # line 4
378 y[2] += 1
379x = [1, 2, 3]
380adjust(x) # line 7
381adjust(x)
382print(x)
383 ";
384 dap_test_template(|s, controller, adapter, eval_hook| {
385 let ast = AstModule::parse(
386 "test.bzl",
387 file_contents.to_owned(),
388 &Dialect::AllOptionsInternal,
389 )?;
390 let breakpoints =
391 resolve_breakpoints(&breakpoints_args("test.bzl", &[(7, None)]), &ast)?;
392 adapter.set_breakpoints("test.bzl", &breakpoints)?;
393 let eval_result =
394 s.spawn(move || -> crate::Result<_> { eval_with_hook(ast, eval_hook) });
395 controller.wait_for_eval_stopped(1, TIMEOUT);
396 adapter.continue_()?;
398 controller.wait_for_eval_stopped(2, TIMEOUT);
399
400 assert_eq!("1", adapter.evaluate("x[0]")?.result);
401 assert_eq!("2", adapter.evaluate("x[1]")?.result);
402 assert_eq!("3", adapter.evaluate("x[2]")?.result);
403
404 adapter.step(StepKind::Into)?;
406 controller.wait_for_eval_stopped(3, TIMEOUT);
407 assert_eq!("1", adapter.evaluate("y[0]")?.result);
408 assert_eq!("2", adapter.evaluate("y[1]")?.result);
409 assert_eq!("3", adapter.evaluate("y[2]")?.result);
410
411 adapter.step(StepKind::Into)?;
413 controller.wait_for_eval_stopped(4, TIMEOUT);
414 assert_eq!("2", adapter.evaluate("y[0]")?.result);
415 assert_eq!("2", adapter.evaluate("y[1]")?.result);
416 assert_eq!("3", adapter.evaluate("y[2]")?.result);
417
418 adapter.step(StepKind::Into)?;
420 controller.wait_for_eval_stopped(5, TIMEOUT);
421 adapter.step(StepKind::Into)?;
422 controller.wait_for_eval_stopped(6, TIMEOUT);
423 assert_eq!("2", adapter.evaluate("x[0]")?.result);
424 assert_eq!("3", adapter.evaluate("x[1]")?.result);
425 assert_eq!("4", adapter.evaluate("x[2]")?.result);
426
427 adapter.step(StepKind::Into)?;
429 controller.wait_for_eval_stopped(7, TIMEOUT);
430
431 adapter.step(StepKind::Into)?;
433 controller.wait_for_eval_stopped(8, TIMEOUT);
434
435 assert_eq!("2", adapter.evaluate("y[0]")?.result);
436 assert_eq!("3", adapter.evaluate("y[1]")?.result);
437 assert_eq!("4", adapter.evaluate("y[2]")?.result);
438
439 adapter.continue_()?;
440 join_timeout(eval_result, TIMEOUT)?;
441 Ok(())
442 })
443 }
444
445 #[test]
446 fn test_step_out() -> crate::Result<()> {
447 if is_wasm() {
448 return Ok(());
449 }
450
451 let file_contents = "
452def adjust(y):
453 y[0] += 1
454 y[1] += 1 # line 4
455 y[2] += 1
456x = [1, 2, 3]
457adjust(x) # line 7
458adjust(x)
459print(x)
460 ";
461 dap_test_template(|s, controller, adapter, eval_hook| {
462 let ast = AstModule::parse(
463 "test.bzl",
464 file_contents.to_owned(),
465 &Dialect::AllOptionsInternal,
466 )?;
467 let breakpoints =
468 resolve_breakpoints(&breakpoints_args("test.bzl", &[(4, None)]), &ast)?;
469 adapter.set_breakpoints("test.bzl", &breakpoints)?;
470 let eval_result =
471 s.spawn(move || -> crate::Result<_> { eval_with_hook(ast, eval_hook) });
472 controller.wait_for_eval_stopped(1, TIMEOUT);
474 assert_eq!("2", adapter.evaluate("y[0]")?.result);
475 assert_eq!("2", adapter.evaluate("y[1]")?.result);
476 assert_eq!("3", adapter.evaluate("y[2]")?.result);
477
478 adapter.step(StepKind::Out)?;
480 controller.wait_for_eval_stopped(2, TIMEOUT);
481 assert_eq!("2", adapter.evaluate("x[0]")?.result);
482 assert_eq!("3", adapter.evaluate("x[1]")?.result);
483 assert_eq!("4", adapter.evaluate("x[2]")?.result);
484
485 adapter.step(StepKind::Out)?;
487 controller.wait_for_eval_stopped(3, TIMEOUT);
488 assert_eq!("3", adapter.evaluate("y[0]")?.result);
489 assert_eq!("3", adapter.evaluate("y[1]")?.result);
490 assert_eq!("4", adapter.evaluate("y[2]")?.result);
491
492 adapter.step(StepKind::Out)?;
494 controller.wait_for_eval_stopped(4, TIMEOUT);
495 assert_eq!("3", adapter.evaluate("x[0]")?.result);
496 assert_eq!("4", adapter.evaluate("x[1]")?.result);
497 assert_eq!("5", adapter.evaluate("x[2]")?.result);
498
499 adapter.step(StepKind::Out)?;
501 join_timeout(eval_result, TIMEOUT)?;
502 Ok(())
503 })
504 }
505
506 #[test]
507 fn test_local_variables() -> crate::Result<()> {
508 if is_wasm() {
509 return Ok(());
510 }
511
512 let file_contents = "
513def do():
514 a = struct(
515 f1 = \"1\",
516 f2 = 123,
517 )
518 arr = [1, 2, 3, 4, 6, \"234\", 123.32]
519 t = (1, 2)
520 d = dict(a = 1, b = \"2\")
521 empty_dict = {}
522 empty_list = []
523 empty_tuple = ()
524 return d # line 13
525print(do())
526 ";
527 let result = dap_test_template(|s, controller, adapter, eval_hook| {
528 let ast = AstModule::parse(
529 "test.bzl",
530 file_contents.to_owned(),
531 &Dialect::AllOptionsInternal,
532 )?;
533 let breakpoints =
534 resolve_breakpoints(&breakpoints_args("test.bzl", &[(13, None)]), &ast)?;
535 adapter.set_breakpoints("test.bzl", &breakpoints)?;
536 let eval_result =
537 s.spawn(move || -> crate::Result<_> { eval_with_hook(ast, eval_hook) });
538 controller.wait_for_eval_stopped(1, TIMEOUT);
539 let result = adapter.variables();
540 adapter.continue_()?;
541 join_timeout(eval_result, TIMEOUT)?;
542 result.map_err(crate::Error::from)
543 })?;
544 assert_eq!(
547 vec![
548 ("a".to_owned(), String::from("<type:struct, size=2>"), true),
549 ("arr".to_owned(), String::from("<list, size=7>"), true),
550 ("t".to_owned(), String::from("<tuple, size=2>"), true),
551 ("d".to_owned(), String::from("<dict, size=2>"), true),
552 ("empty_dict".to_owned(), String::from("{}"), false),
553 ("empty_list".to_owned(), String::from("[]"), false),
554 ("empty_tuple".to_owned(), String::from("()"), false),
555 ],
556 result
557 .locals
558 .into_iter()
559 .map(|v| (v.name.to_string(), v.value, v.has_children))
560 .collect::<Vec<_>>()
561 );
562
563 Ok(())
564 }
565
566 #[test]
567 fn test_inspect_variables() -> crate::Result<()> {
568 if is_wasm() {
569 return Ok(());
570 }
571
572 let file_contents = "
573def do():
574 a = struct(
575 f1 = \"1\",
576 f2 = 123,
577 )
578 arr = [1, 2, 3, 4, 6, \"234\", 123.32]
579 t = (1, 2)
580 d = dict(a = 1, b = \"2\")
581 empty_dict = {}
582 empty_list = []
583 empty_tuple = ()
584 return d # line 13
585print(do())
586 ";
587 let result = dap_test_template(|s, controller, adapter, eval_hook| {
588 let mut result = Vec::new();
589 let ast = AstModule::parse(
590 "test.bzl",
591 file_contents.to_owned(),
592 &Dialect::AllOptionsInternal,
593 )?;
594 let breakpoints =
595 resolve_breakpoints(&breakpoints_args("test.bzl", &[(13, None)]), &ast)?;
596 adapter.set_breakpoints("test.bzl", &breakpoints)?;
597 let eval_result =
598 s.spawn(move || -> crate::Result<_> { eval_with_hook(ast, eval_hook) });
599 controller.wait_for_eval_stopped(1, TIMEOUT);
600 result.extend([
601 adapter.inspect_variable(VariablePath::new_local("a")),
602 adapter.inspect_variable(VariablePath::new_local("arr")),
603 adapter.inspect_variable(VariablePath::new_local("t")),
604 adapter.inspect_variable(VariablePath::new_local("d")),
605 ]);
606 adapter.continue_()?;
607 join_timeout(eval_result, TIMEOUT)?;
608 crate::Result::Ok(result)
609 })?
610 .into_iter()
611 .collect::<anyhow::Result<Vec<_>>>()?;
612
613 assert_variable("f1", "1", false, &result[0].sub_values[0]);
617 assert_variable("f2", "123", false, &result[0].sub_values[1]);
618 assert_variable("0", "1", false, &result[1].sub_values[0]);
619 assert_variable("5", "234", false, &result[1].sub_values[5]);
620 assert_variable("0", "1", false, &result[2].sub_values[0]);
621 assert_variable("1", "2", false, &result[2].sub_values[1]);
622 assert_variable("\"a\"", "1", false, &result[3].sub_values[0]);
623 assert_variable("\"b\"", "2", false, &result[3].sub_values[1]);
624 Ok(())
625 }
626
627 #[test]
628 fn test_evaluate_expression() -> crate::Result<()> {
629 if is_wasm() {
630 return Ok(());
631 }
632
633 let file_contents = "
634def do():
635 s = struct(
636 inner = struct(
637 inner = struct(
638 value = \"more_inner\"
639 ),
640 value = \"inner\",
641 arr = [dict(a = 1, b = \"2\"), 1337]
642 )
643 )
644 return s # line 12
645print(do())
646 ";
647 let result = dap_test_template(|s, controller, adapter, eval_hook| {
648 let mut result = Vec::new();
649 let ast = AstModule::parse(
650 "test.bzl",
651 file_contents.to_owned(),
652 &Dialect::AllOptionsInternal,
653 )?;
654 let breakpoints =
655 resolve_breakpoints(&breakpoints_args("test.bzl", &[(12, None)]), &ast)?;
656 adapter.set_breakpoints("test.bzl", &breakpoints)?;
657 let eval_result =
658 s.spawn(move || -> crate::Result<_> { eval_with_hook(ast, eval_hook) });
659 controller.wait_for_eval_stopped(1, TIMEOUT);
660 result.extend([
661 adapter.evaluate("s.inner.value"),
662 adapter.evaluate("s.inner.inner.value"),
663 adapter.evaluate("s.inner.arr[0]"),
664 adapter.evaluate("s.inner.arr[0][\"a\"]"),
665 adapter.evaluate("s.inner.arr[1]"),
666 ]);
667 adapter.continue_()?;
668 join_timeout(eval_result, TIMEOUT)?;
669 crate::Result::Ok(result)
670 })?
671 .into_iter()
672 .collect::<anyhow::Result<Vec<_>>>()?;
673
674 assert_eq!(
677 vec![
678 ("inner", false),
679 ("more_inner", false),
680 ("<dict, size=2>", true),
681 ("1", false),
682 ("1337", false),
683 ],
684 result
685 .iter()
686 .map(|v| (v.result.as_str(), v.has_children))
687 .collect::<Vec<_>>()
688 );
689
690 Ok(())
691 }
692
693 fn assert_variable(
694 name: &str,
695 value: &str,
696 has_children: bool,
697 var: &crate::debug::adapter::Variable,
698 ) {
699 assert_eq!(
700 (name.to_owned(), value, has_children),
701 (var.name.to_string(), var.value.as_str(), var.has_children)
702 );
703 }
704
705 #[test]
706 pub fn test_truncate_string() {
707 assert_eq!(
708 "Hello",
709 crate::debug::adapter::Variable::truncate_string("Hello".to_owned(), 10)
710 );
711 assert_eq!(
712 "Hello",
713 crate::debug::adapter::Variable::truncate_string("Hello".to_owned(), 5)
714 );
715 assert_eq!(
716 "Hello, ...(truncated)",
717 crate::debug::adapter::Variable::truncate_string("Hello, 世界".to_owned(), 7)
719 );
720 assert_eq!(
721 "Hello, ...(truncated)",
722 crate::debug::adapter::Variable::truncate_string("Hello, 世界".to_owned(), 8)
724 );
725 assert_eq!(
726 "Hello, ...(truncated)",
727 crate::debug::adapter::Variable::truncate_string("Hello, 世界".to_owned(), 9)
729 );
730 assert_eq!(
731 "Hello, 世...(truncated)",
732 crate::debug::adapter::Variable::truncate_string("Hello, 世界".to_owned(), 10)
733 );
734 }
735}