1use crate::console::{
20 self, CharsXY, ClearType, Console, Key, PixelsXY, SizeInPixels, remove_control_chars,
21};
22use crate::datetime::DateTime;
23use crate::program::Program;
24use crate::sound::Tone;
25use crate::storage::Storage;
26use crate::{Machine, MachineBuilder, Signal, gpio};
27use async_channel::{Receiver, Sender};
28use async_trait::async_trait;
29use endbasic_core::{
30 Callable, ConstantDatum, ExprType, GetGlobalError, GlobalDef, GlobalDefKind, StopReason,
31 SymbolKey,
32};
33use futures_lite::future::{BoxedLocal, FutureExt, block_on};
34use std::cell::RefCell;
35use std::collections::{HashMap, VecDeque};
36use std::io;
37use std::rc::Rc;
38use std::result::Result as StdResult;
39use std::str;
40use std::time::Duration;
41
42type CheckerResult = StdResult<Option<i32>, String>;
43
44#[derive(Clone, Debug, Eq, PartialEq)]
46pub enum CapturedOut {
47 Clear(ClearType),
49
50 SetColor(Option<u8>, Option<u8>),
52
53 EnterAlt,
55
56 HideCursor,
58
59 LeaveAlt,
61
62 Locate(CharsXY),
64
65 MoveWithinLine(i16),
67
68 Print(String),
70
71 ShowCursor,
73
74 PlayTone(Tone),
76
77 Write(String),
79
80 BucketFill(PixelsXY),
82
83 DrawCircle(PixelsXY, u16),
85
86 DrawCircleFilled(PixelsXY, u16),
88
89 DrawLine(PixelsXY, PixelsXY),
91
92 DrawPixel(PixelsXY),
94
95 DrawPoly(Vec<PixelsXY>),
97
98 DrawPolyFilled(Vec<PixelsXY>),
100
101 DrawRect(PixelsXY, PixelsXY),
103
104 DrawRectFilled(PixelsXY, PixelsXY),
106
107 DrawTri(PixelsXY, PixelsXY, PixelsXY),
109
110 DrawTriFilled(PixelsXY, PixelsXY, PixelsXY),
112
113 SyncNow,
115
116 SetSync(bool),
118}
119
120pub struct MockConsole {
122 golden_in: VecDeque<Key>,
124
125 captured_out: Vec<CapturedOut>,
127
128 size_chars: CharsXY,
130
131 size_pixels: Option<SizeInPixels>,
133
134 glyph_size: Option<SizeInPixels>,
136
137 interactive: bool,
139
140 peek_pixels: HashMap<PixelsXY, Option<u8>>,
142
143 signals_tx: Option<Sender<Signal>>,
145}
146
147impl Default for MockConsole {
148 fn default() -> Self {
149 Self::new(None)
150 }
151}
152
153impl MockConsole {
154 fn new(signals_tx: Option<Sender<Signal>>) -> Self {
156 Self {
157 golden_in: VecDeque::new(),
158 captured_out: vec![],
159 size_chars: CharsXY::new(u16::MAX, u16::MAX),
160 size_pixels: None,
161 glyph_size: None,
162 interactive: false,
163 peek_pixels: HashMap::default(),
164 signals_tx,
165 }
166 }
167
168 pub fn add_input_chars(&mut self, s: &str) {
173 for ch in s.chars() {
174 match ch {
175 '\n' => self.golden_in.push_back(Key::NewLine),
176 '\r' => self.golden_in.push_back(Key::CarriageReturn),
177 ch => self.golden_in.push_back(Key::Char(ch)),
178 }
179 }
180 }
181
182 pub fn add_input_keys(&mut self, keys: &[Key]) {
184 self.golden_in.extend(keys.iter().cloned());
185 }
186
187 pub fn captured_out(&self) -> &[CapturedOut] {
189 self.captured_out.as_slice()
190 }
191
192 #[must_use]
194 pub fn take_captured_out(&mut self) -> Vec<CapturedOut> {
195 let mut copy = Vec::with_capacity(self.captured_out.len());
196 copy.append(&mut self.captured_out);
197 copy
198 }
199
200 pub fn set_size_chars(&mut self, size: CharsXY) {
202 self.size_chars = size;
203 }
204
205 pub fn set_size_pixels(&mut self, size: SizeInPixels) {
207 self.size_pixels = Some(size);
208 }
209
210 pub fn set_glyph_size(&mut self, size: SizeInPixels) {
212 self.glyph_size = Some(size);
213 }
214
215 pub fn set_interactive(&mut self, interactive: bool) {
217 self.interactive = interactive;
218 }
219
220 pub fn set_peek_pixel(&mut self, xy: PixelsXY, color: Option<u8>) {
222 self.peek_pixels.insert(xy, color);
223 }
224}
225
226impl Drop for MockConsole {
227 fn drop(&mut self) {
228 assert!(
229 self.golden_in.is_empty(),
230 "Not all golden input chars were consumed; {} left",
231 self.golden_in.len()
232 );
233 }
234}
235
236#[async_trait(?Send)]
237impl Console for MockConsole {
238 fn clear(&mut self, how: ClearType) -> io::Result<()> {
239 self.captured_out.push(CapturedOut::Clear(how));
240 Ok(())
241 }
242
243 fn color(&self) -> (Option<u8>, Option<u8>) {
244 for o in self.captured_out.iter().rev() {
245 if let CapturedOut::SetColor(fg, bg) = o {
246 return (*fg, *bg);
247 }
248 }
249 (None, None)
250 }
251
252 fn set_color(&mut self, fg: Option<u8>, bg: Option<u8>) -> io::Result<()> {
253 self.captured_out.push(CapturedOut::SetColor(fg, bg));
254 Ok(())
255 }
256
257 fn enter_alt(&mut self) -> io::Result<()> {
258 self.captured_out.push(CapturedOut::EnterAlt);
259 Ok(())
260 }
261
262 fn hide_cursor(&mut self) -> io::Result<()> {
263 self.captured_out.push(CapturedOut::HideCursor);
264 Ok(())
265 }
266
267 fn is_interactive(&self) -> bool {
268 self.interactive
269 }
270
271 fn leave_alt(&mut self) -> io::Result<()> {
272 self.captured_out.push(CapturedOut::LeaveAlt);
273 Ok(())
274 }
275
276 fn locate(&mut self, pos: CharsXY) -> io::Result<()> {
277 assert!(pos.x < self.size_chars.x);
278 assert!(pos.y < self.size_chars.y);
279 self.captured_out.push(CapturedOut::Locate(pos));
280 Ok(())
281 }
282
283 fn move_within_line(&mut self, off: i16) -> io::Result<()> {
284 self.captured_out.push(CapturedOut::MoveWithinLine(off));
285 Ok(())
286 }
287
288 fn print(&mut self, text: &str) -> io::Result<()> {
289 let text = remove_control_chars(text.to_owned());
290
291 self.captured_out.push(CapturedOut::Print(text));
292 Ok(())
293 }
294
295 async fn poll_key(&mut self) -> io::Result<Option<Key>> {
296 match self.golden_in.pop_front() {
297 Some(ch) => {
298 if ch == Key::Interrupt
299 && let Some(signals_tx) = &self.signals_tx
300 {
301 let _ = signals_tx.send(Signal::Break).await;
302 }
303 Ok(Some(ch))
304 }
305 None => Ok(None),
306 }
307 }
308
309 async fn read_key(&mut self) -> io::Result<Key> {
310 match self.golden_in.pop_front() {
311 Some(ch) => {
312 if ch == Key::Interrupt
313 && let Some(signals_tx) = &self.signals_tx
314 {
315 let _ = signals_tx.send(Signal::Break).await;
316 }
317 Ok(ch)
318 }
319 None => Ok(Key::EofOrDelete),
320 }
321 }
322
323 fn show_cursor(&mut self) -> io::Result<()> {
324 self.captured_out.push(CapturedOut::ShowCursor);
325 Ok(())
326 }
327
328 fn size_chars(&self) -> io::Result<CharsXY> {
329 Ok(self.size_chars)
330 }
331
332 fn size_pixels(&self) -> io::Result<SizeInPixels> {
333 match self.size_pixels {
334 Some(size) => Ok(size),
335 None => Err(io::Error::other("Graphical console size not yet set")),
336 }
337 }
338
339 fn glyph_size(&self) -> io::Result<SizeInPixels> {
340 match self.glyph_size {
341 Some(size) => Ok(size),
342 None => Err(io::Error::other("Glyph size not yet set")),
343 }
344 }
345
346 fn write(&mut self, text: &str) -> io::Result<()> {
347 let text = remove_control_chars(text.to_owned());
348
349 self.captured_out.push(CapturedOut::Write(text));
350 Ok(())
351 }
352
353 fn bucket_fill(&mut self, xy: PixelsXY) -> io::Result<()> {
354 self.captured_out.push(CapturedOut::BucketFill(xy));
355 Ok(())
356 }
357
358 fn draw_circle(&mut self, xy: PixelsXY, r: u16) -> io::Result<()> {
359 self.captured_out.push(CapturedOut::DrawCircle(xy, r));
360 Ok(())
361 }
362
363 fn draw_circle_filled(&mut self, xy: PixelsXY, r: u16) -> io::Result<()> {
364 self.captured_out.push(CapturedOut::DrawCircleFilled(xy, r));
365 Ok(())
366 }
367
368 fn draw_line(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
369 self.captured_out.push(CapturedOut::DrawLine(x1y1, x2y2));
370 Ok(())
371 }
372
373 fn draw_pixel(&mut self, xy: PixelsXY) -> io::Result<()> {
374 self.captured_out.push(CapturedOut::DrawPixel(xy));
375 Ok(())
376 }
377
378 fn draw_poly(&mut self, points: &[PixelsXY]) -> io::Result<()> {
379 match points.len() {
380 0 => Ok(()),
381 1 => self.draw_pixel(points[0]),
382 2 => self.draw_line(points[0], points[1]),
383 3 => self.draw_tri(points[0], points[1], points[2]),
384 _ => {
385 self.captured_out.push(CapturedOut::DrawPoly(points.to_vec()));
386 Ok(())
387 }
388 }
389 }
390
391 fn draw_poly_filled(&mut self, points: &[PixelsXY]) -> io::Result<()> {
392 match points.len() {
393 0 => Ok(()),
394 1 => self.draw_pixel(points[0]),
395 2 => self.draw_line(points[0], points[1]),
396 3 => self.draw_tri_filled(points[0], points[1], points[2]),
397 _ => {
398 self.captured_out.push(CapturedOut::DrawPolyFilled(points.to_vec()));
399 Ok(())
400 }
401 }
402 }
403
404 fn draw_rect(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
405 self.captured_out.push(CapturedOut::DrawRect(x1y1, x2y2));
406 Ok(())
407 }
408
409 fn draw_rect_filled(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
410 self.captured_out.push(CapturedOut::DrawRectFilled(x1y1, x2y2));
411 Ok(())
412 }
413
414 fn draw_tri(&mut self, x1y1: PixelsXY, x2y2: PixelsXY, x3y3: PixelsXY) -> io::Result<()> {
415 self.captured_out.push(CapturedOut::DrawTri(x1y1, x2y2, x3y3));
416 Ok(())
417 }
418
419 fn draw_tri_filled(
420 &mut self,
421 x1y1: PixelsXY,
422 x2y2: PixelsXY,
423 x3y3: PixelsXY,
424 ) -> io::Result<()> {
425 self.captured_out.push(CapturedOut::DrawTriFilled(x1y1, x2y2, x3y3));
426 Ok(())
427 }
428
429 fn peek_pixel(&self, xy: PixelsXY) -> io::Result<Option<u8>> {
430 Ok(self.peek_pixels.get(&xy).copied().unwrap_or(None))
431 }
432
433 fn sync_now(&mut self) -> io::Result<()> {
434 self.captured_out.push(CapturedOut::SyncNow);
435 Ok(())
436 }
437
438 fn set_sync(&mut self, enabled: bool) -> io::Result<bool> {
439 let mut previous = true;
440 for o in self.captured_out.iter().rev() {
441 if let CapturedOut::SetSync(e) = o {
442 previous = *e;
443 break;
444 }
445 }
446 self.captured_out.push(CapturedOut::SetSync(enabled));
447 Ok(previous)
448 }
449
450 async fn play_tone(&mut self, tone: Tone) -> io::Result<()> {
451 self.captured_out.push(CapturedOut::PlayTone(tone));
452 Ok(())
453 }
454}
455
456pub fn flatten_output(captured_out: Vec<CapturedOut>) -> String {
459 let mut flattened = String::new();
460 for out in captured_out {
461 match out {
462 CapturedOut::Write(bs) => flattened.push_str(&bs),
463 CapturedOut::Print(s) => flattened.push_str(&s),
464 _ => (),
465 }
466 }
467 flattened
468}
469
470type SleepFn = Box<dyn Fn(Duration) -> BoxedLocal<Result<(), String>>>;
476
477pub struct MockDateTime {
479 monotonic: RefCell<VecDeque<Duration>>,
480 sleeps: RefCell<Vec<Duration>>,
481 sleep_fn: RefCell<SleepFn>,
482}
483
484impl Default for MockDateTime {
485 fn default() -> Self {
486 Self {
487 monotonic: RefCell::default(),
488 sleeps: RefCell::default(),
489 sleep_fn: RefCell::new(Box::new(|_d: Duration| async move { Ok(()) }.boxed_local())),
490 }
491 }
492}
493
494impl MockDateTime {
495 pub fn add_monotonic(&self, d: Duration) {
497 self.monotonic.borrow_mut().push_back(d);
498 }
499
500 pub fn set_sleep_fn(&self, sleep_fn: SleepFn) {
502 *self.sleep_fn.borrow_mut() = sleep_fn;
503 }
504
505 pub fn sleeps(&self) -> Vec<Duration> {
507 self.sleeps.borrow().clone()
508 }
509}
510
511#[async_trait(?Send)]
512impl DateTime for MockDateTime {
513 fn monotonic(&self) -> Duration {
514 self.monotonic.borrow_mut().pop_front().unwrap_or(Duration::ZERO)
515 }
516
517 async fn sleep(&self, d: Duration) -> Result<(), String> {
518 self.sleeps.borrow_mut().push(d);
519 let future = {
520 let sleep_fn = self.sleep_fn.borrow();
521 (sleep_fn)(d)
522 };
523 future.await
524 }
525}
526
527#[derive(Default)]
530pub struct RecordedProgram {
531 name: Option<String>,
532 content: String,
533 dirty: bool,
534}
535
536#[async_trait(?Send)]
537impl Program for RecordedProgram {
538 fn is_dirty(&self) -> bool {
539 self.dirty
540 }
541
542 async fn edit(&mut self, console: &mut dyn Console) -> io::Result<()> {
543 let append = console::read_line(console, "", "", None).await?;
544 self.content.push_str(&append);
545 self.content.push('\n');
546 self.dirty = true;
547 Ok(())
548 }
549
550 fn load(&mut self, name: Option<&str>, text: &str) {
551 self.name = name.map(str::to_owned);
552 text.clone_into(&mut self.content);
553 self.dirty = false;
554 }
555
556 fn name(&self) -> Option<&str> {
557 self.name.as_deref()
558 }
559
560 fn set_name(&mut self, name: &str) {
561 self.name = Some(name.to_owned());
562 self.dirty = false;
563 }
564
565 fn text(&self) -> String {
566 self.content.clone()
567 }
568}
569
570#[must_use]
572#[derive(Clone)]
573pub struct Tester {
574 console: Rc<RefCell<MockConsole>>,
575 datetime: Rc<MockDateTime>,
576 storage: Rc<RefCell<Storage>>,
577 program: Rc<RefCell<RecordedProgram>>,
578 callables: Vec<Rc<dyn Callable>>,
579 global_defs: Vec<GlobalDef>,
580 interactive: bool,
581 signals_tx: Sender<Signal>,
582 signals_rx: Receiver<Signal>,
583}
584
585impl Default for Tester {
586 fn default() -> Self {
588 let (signals_tx, signals_rx) = async_channel::unbounded();
589 let console = Rc::from(RefCell::from(MockConsole::new(Some(signals_tx.clone()))));
590 let datetime = Rc::from(MockDateTime::default());
591 let program = Rc::from(RefCell::from(RecordedProgram::default()));
592 let storage = Rc::from(RefCell::from(Storage::default()));
593 let callables = vec![];
594 let global_defs = vec![];
595 let interactive = true;
596
597 Self {
598 console,
599 datetime,
600 storage,
601 program,
602 callables,
603 global_defs,
604 interactive,
605 signals_tx,
606 signals_rx,
607 }
608 }
609}
610
611impl Tester {
612 fn build_machine(&self) -> Machine {
613 let gpio_pins = Rc::from(RefCell::from(gpio::NoopPins::default()));
617 let mut builder = MachineBuilder::default()
618 .with_console(self.console.clone())
619 .with_datetime(self.datetime.clone())
620 .with_globals(self.global_defs.clone())
621 .with_gpio_pins(gpio_pins)
622 .with_signals_chan((self.signals_tx.clone(), self.signals_rx.clone()));
623
624 for callable in self.callables.clone() {
625 builder.add_callable(callable);
626 }
627
628 if self.interactive {
629 builder
630 .make_interactive()
631 .with_program(self.program.clone())
632 .with_storage(self.storage.clone())
633 .build()
634 } else {
635 builder.build()
636 }
637 }
638
639 pub fn empty() -> Self {
641 Self { interactive: false, ..Self::default() }
642 }
643
644 pub fn add_callable(mut self, callable: Rc<dyn Callable>) -> Self {
646 self.callables.push(callable);
647 self
648 }
649
650 pub fn add_input_chars(self, golden_in: &str) -> Self {
652 self.console.borrow_mut().add_input_chars(golden_in);
653 self
654 }
655
656 pub fn add_input_keys(self, keys: &[Key]) -> Self {
658 self.console.borrow_mut().add_input_keys(keys);
659 self
660 }
661
662 pub fn get_console(&self) -> Rc<RefCell<MockConsole>> {
667 self.console.clone()
668 }
669
670 pub fn get_datetime(&self) -> Rc<MockDateTime> {
675 self.datetime.clone()
676 }
677
678 pub fn get_program(&self) -> Rc<RefCell<RecordedProgram>> {
683 self.program.clone()
684 }
685
686 pub fn get_storage(&self) -> Rc<RefCell<Storage>> {
691 self.storage.clone()
692 }
693
694 pub fn send_break(self) -> Self {
696 block_on(self.signals_tx.send(Signal::Break)).unwrap();
697 self
698 }
699
700 pub fn set_var<S: Into<String>, V: Into<ConstantDatum>>(mut self, name: S, value: V) -> Self {
702 let value = value.into();
703 self.global_defs.push(GlobalDef {
704 name: name.into(),
705 kind: GlobalDefKind::Scalar {
706 etype: match &value {
707 ConstantDatum::Boolean(..) => ExprType::Boolean,
708 ConstantDatum::Double(..) => ExprType::Double,
709 ConstantDatum::Integer(..) => ExprType::Integer,
710 ConstantDatum::Text(..) => ExprType::Text,
711 },
712 initial_value: Some(value),
713 },
714 });
715 self
716 }
717
718 pub fn set_program(self, name: Option<&str>, text: &str) -> Self {
721 assert!(!text.is_empty());
722 {
723 let mut program = self.program.borrow_mut();
724 assert!(program.text().is_empty());
725 program.load(name, text);
726 }
727 self
728 }
729
730 pub fn write_file(self, name: &str, content: &str) -> Self {
732 block_on(self.storage.borrow_mut().put(name, content.as_bytes())).unwrap();
733 self
734 }
735
736 pub fn run<S: Into<String>>(&mut self, script: S) -> Checker<'_> {
739 let machine = self.build_machine();
740 let tester = TesterContinuation { tester: self, machine };
741 tester.run(script)
742 }
743
744 pub fn continue_from_here(&self) -> TesterContinuation<'_> {
746 let machine = self.build_machine();
747 TesterContinuation { tester: self, machine }
748 }
749
750 pub fn run_n(&mut self, scripts: &[&str]) -> Checker<'_> {
759 let mut machine = self.build_machine();
760 let mut result = Ok(None);
761 for script in scripts {
762 match machine.compile(&mut script.as_bytes()) {
763 Ok(()) => (),
764 Err(e) => {
765 result = Err(format!("{}", e));
766 break;
767 }
768 }
769 result = block_on(machine.exec()).map_err(|e| format!("{}", e));
770 if result.is_err() {
771 break;
772 }
773 }
774 Checker::new(self, machine, result)
775 }
776}
777
778pub struct TesterContinuation<'a> {
783 tester: &'a Tester,
784 machine: Machine,
785}
786
787impl<'a> TesterContinuation<'a> {
788 pub fn get_machine(&mut self) -> &mut Machine {
790 &mut self.machine
791 }
792
793 pub fn run<S: Into<String>>(mut self, script: S) -> Checker<'a> {
796 let result = match self.machine.compile(&mut script.into().as_bytes()) {
797 Ok(()) => block_on(self.machine.exec()).map_err(|e| format!("{}", e)),
798 Err(e) => Err(format!("{}", e)),
799 };
800 Checker::new(self.tester, self.machine, result)
801 }
802
803 pub fn clear(mut self) -> Self {
805 self.machine.clear();
806 self
807 }
808}
809
810struct ExpArray {
812 subtype: ExprType,
814
815 dimensions: Vec<usize>,
817
818 contents: Vec<(Vec<i32>, ConstantDatum)>,
823}
824
825#[must_use]
827pub struct Checker<'a> {
828 tester: &'a Tester,
829 machine: Machine,
830 result: CheckerResult,
831 exp_result: CheckerResult,
832 exp_output: Vec<CapturedOut>,
833 exp_drives: HashMap<String, String>,
834 exp_program_name: Option<String>,
835 exp_program_text: String,
836 exp_arrays: HashMap<SymbolKey, ExpArray>,
837 exp_vars: HashMap<SymbolKey, ConstantDatum>,
838}
839
840impl<'a> Checker<'a> {
841 fn new(tester: &'a Tester, machine: Machine, result: CheckerResult) -> Self {
846 Self {
847 tester,
848 machine,
849 result,
850 exp_result: Ok(None),
851 exp_output: vec![],
852 exp_drives: HashMap::default(),
853 exp_program_name: None,
854 exp_program_text: String::new(),
855 exp_arrays: HashMap::default(),
856 exp_vars: HashMap::default(),
857 }
858 }
859
860 pub fn expect_ok(mut self, stop_reason: StopReason) -> Self {
865 self.exp_result = Ok(match stop_reason {
866 StopReason::End(code) => Some(code.to_i32()),
867 StopReason::Eof => None,
868 StopReason::Exception(_, _) | StopReason::UpcallAsync(_) | StopReason::Yield => {
869 unreachable!()
870 }
871 });
872 self
873 }
874
875 pub fn expect_compilation_err<S: Into<String>>(mut self, message: S) -> Self {
881 self.exp_result = Err(message.into());
882 self
883 }
884
885 pub fn expect_err<S: Into<String>>(mut self, message: S) -> Self {
890 self.exp_result = Err(message.into());
891 self
892 }
893
894 pub fn expect_array<S: AsRef<str>>(
899 mut self,
900 name: S,
901 subtype: ExprType,
902 dimensions: &[usize],
903 contents: Vec<(Vec<i32>, ConstantDatum)>,
904 ) -> Self {
905 let key = SymbolKey::from(name);
906 assert!(!self.exp_arrays.contains_key(&key));
907 self.exp_arrays
908 .insert(key, ExpArray { subtype, dimensions: dimensions.to_vec(), contents });
909 self
910 }
911
912 pub fn expect_array_simple<S: AsRef<str>>(
915 mut self,
916 name: S,
917 subtype: ExprType,
918 contents: Vec<ConstantDatum>,
919 ) -> Self {
920 let key = SymbolKey::from(name);
921 assert!(!self.exp_arrays.contains_key(&key));
922 let mut exp_array = Vec::with_capacity(contents.len());
923 for (i, value) in contents.into_iter().enumerate() {
924 exp_array.push((vec![i as i32], value));
925 }
926 self.exp_arrays.insert(
927 key,
928 ExpArray { subtype, dimensions: vec![exp_array.len()], contents: exp_array },
929 );
930 self
931 }
932
933 pub fn expect_clear(mut self) -> Self {
935 self.exp_output.append(&mut vec![
936 CapturedOut::LeaveAlt,
937 CapturedOut::SetColor(None, None),
938 CapturedOut::ShowCursor,
939 CapturedOut::SetSync(true),
940 ]);
941 self
942 }
943
944 pub fn expect_file<N: Into<String>, C: Into<String>>(mut self, name: N, content: C) -> Self {
948 let name = name.into();
949 assert!(!self.exp_drives.contains_key(&name));
950 self.exp_drives.insert(name, content.into());
951 self
952 }
953
954 pub fn expect_output<V: Into<Vec<CapturedOut>>>(mut self, out: V) -> Self {
956 self.exp_output.append(&mut out.into());
957 self
958 }
959
960 pub fn expect_prints<S: Into<String>, V: Into<Vec<S>>>(mut self, out: V) -> Self {
965 let out = out.into();
966 self.exp_output
967 .append(&mut out.into_iter().map(|x| CapturedOut::Print(x.into())).collect());
968 self
969 }
970
971 pub fn expect_program<S1: Into<String>, S2: Into<String>>(
974 mut self,
975 name: Option<S1>,
976 text: S2,
977 ) -> Self {
978 assert!(self.exp_program_text.is_empty());
979 let text = text.into();
980 assert!(!text.is_empty());
981 self.exp_program_name = name.map(|x| x.into());
982 self.exp_program_text = text;
983 self
984 }
985
986 pub fn expect_var<S: AsRef<str>, V: Into<ConstantDatum>>(mut self, name: S, value: V) -> Self {
988 let key = SymbolKey::from(name);
989 assert!(!self.exp_vars.contains_key(&key));
990 self.exp_vars.insert(key, value.into());
991 self
992 }
993
994 #[must_use]
996 pub fn take_captured_out(&mut self) -> Vec<CapturedOut> {
997 assert!(
998 self.exp_output.is_empty(),
999 "Cannot take output if we are already expecting prints because the test would fail"
1000 );
1001 self.tester.console.borrow_mut().take_captured_out()
1002 }
1003
1004 fn query_array_element(&self, name: &SymbolKey, subscripts: &[i32]) -> Option<ConstantDatum> {
1005 match self.machine.vm.get_global_array(&self.machine.image, name, subscripts) {
1006 Ok(Some(value)) => Some(value),
1007 Ok(None) => self
1008 .machine
1009 .vm
1010 .get_program_array(&self.machine.image, name, subscripts)
1011 .unwrap_or_else(|e| panic!("Expected array {} has wrong shape: {}", name, e)),
1012 Err(e) => panic!("Expected array {} has wrong shape: {}", name, e),
1013 }
1014 }
1015
1016 fn check_array_dims(&self, name: &SymbolKey, dimensions: &[usize]) {
1017 if dimensions.is_empty() {
1018 panic!("Expected array {} must have at least one dimension", name);
1019 }
1020
1021 let mut subscripts = vec![0; dimensions.len()];
1022 for i in 0..dimensions.len() {
1023 subscripts[i] = dimensions[i] as i32;
1024 match self.machine.vm.get_global_array(&self.machine.image, name, &subscripts) {
1025 Err(GetGlobalError::SubscriptOutOfBounds(_)) => (),
1026 Ok(None) => {
1027 match self.machine.vm.get_program_array(&self.machine.image, name, &subscripts)
1028 {
1029 Err(GetGlobalError::SubscriptOutOfBounds(_)) => (),
1030 Ok(Some(_)) => panic!(
1031 "Expected array {} dimension {} to be {} but found larger",
1032 name, i, dimensions[i]
1033 ),
1034 Ok(None) => panic!("Expected array {} not defined", name),
1035 Err(e) => panic!("Expected array {} has wrong shape: {}", name, e),
1036 }
1037 }
1038 Ok(Some(_)) => panic!(
1039 "Expected array {} dimension {} to be {} but found larger",
1040 name, i, dimensions[i]
1041 ),
1042 Err(e) => panic!("Expected array {} has wrong shape: {}", name, e),
1043 }
1044 subscripts[i] = 0;
1045 }
1046 }
1047
1048 fn check_array(&self, name: &SymbolKey, exp_array: &ExpArray) {
1049 let mut exp_contents = HashMap::with_capacity(exp_array.contents.len());
1050 for (subscripts, value) in exp_array.contents.iter() {
1051 assert_eq!(
1052 exp_array.dimensions.len(),
1053 subscripts.len(),
1054 "Expected array {} has wrong number of subscripts",
1055 name
1056 );
1057 for (i, subscript) in subscripts.iter().enumerate() {
1058 assert!(
1059 *subscript >= 0 && *subscript < exp_array.dimensions[i] as i32,
1060 "Expected array {} has out-of-bounds subscript {} at dimension {}",
1061 name,
1062 subscript,
1063 i
1064 );
1065 }
1066 let previous = exp_contents.insert(subscripts.clone(), value.clone());
1067 assert!(previous.is_none(), "Expected array {} has duplicate subscripts", name);
1068 }
1069
1070 let default_value = match exp_array.subtype {
1071 ExprType::Boolean => ConstantDatum::Boolean(false),
1072 ExprType::Double => ConstantDatum::Double(0.0),
1073 ExprType::Integer => ConstantDatum::Integer(0),
1074 ExprType::Text => ConstantDatum::Text(String::new()),
1075 };
1076
1077 let mut subscripts = vec![0; exp_array.dimensions.len()];
1078 loop {
1079 let value = self
1080 .query_array_element(name, &subscripts)
1081 .unwrap_or_else(|| panic!("Expected array {} not defined", name));
1082 assert_eq!(
1083 exp_contents.get(&subscripts).unwrap_or(&default_value),
1084 &value,
1085 "Expected array {} at {:?} has wrong value",
1086 name,
1087 subscripts
1088 );
1089
1090 let mut i = 0;
1091 while i < subscripts.len() {
1092 subscripts[i] += 1;
1093 if subscripts[i] < exp_array.dimensions[i] as i32 {
1094 break;
1095 }
1096 subscripts[i] = 0;
1097 i += 1;
1098 }
1099 if i == subscripts.len() {
1100 break;
1101 }
1102 }
1103
1104 self.check_array_dims(name, &exp_array.dimensions);
1105 }
1106
1107 pub fn check(self) -> TesterContinuation<'a> {
1109 assert_eq!(self.exp_result, self.result);
1110
1111 for (name, exp_value) in self.exp_vars.iter() {
1112 let value = match self.machine.vm.get_global(&self.machine.image, name) {
1113 Ok(Some(value)) => Some(value),
1114 Ok(None) => {
1115 self.machine.vm.get_program(&self.machine.image, name).unwrap_or_else(|e| {
1116 panic!("Expected variable {} has wrong shape: {}", name, e)
1117 })
1118 }
1119 Err(e) => panic!("Expected variable {} has wrong shape: {}", name, e),
1120 };
1121 let value = value.unwrap_or_else(|| panic!("Expected variable {} not defined", name));
1122 assert_eq!(exp_value, &value, "Expected variable {} has wrong value", name);
1123 }
1124
1125 for (name, exp_array) in self.exp_arrays.iter() {
1126 self.check_array(name, exp_array);
1127 }
1128
1129 let drive_contents = {
1130 let mut files = HashMap::new();
1131 let storage = self.tester.storage.borrow();
1132 for (drive_name, target) in storage.mounted().iter() {
1133 if target.starts_with("cloud://") {
1134 continue;
1137 }
1138
1139 let root = format!("{}:/", drive_name);
1140 for name in block_on(storage.enumerate(&root)).unwrap().dirents().keys() {
1141 let path = format!("{}{}", root, name);
1142 let content = block_on(storage.get(&path)).unwrap();
1143 let content = String::from_utf8(content).unwrap();
1144 files.insert(path, content);
1145 }
1146 }
1147 files
1148 };
1149
1150 assert_eq!(self.exp_output, self.tester.console.borrow().captured_out());
1151 assert_eq!(self.exp_program_name.as_deref(), self.tester.program.borrow().name());
1152 assert_eq!(self.exp_program_text, self.tester.program.borrow().text());
1153 assert_eq!(self.exp_drives, drive_contents);
1154
1155 TesterContinuation { tester: self.tester, machine: self.machine }
1156 }
1157}
1158
1159pub fn check_stmt_err<S: Into<String>>(exp_error: S, stmt: &str) {
1161 Tester::default().run(stmt).expect_err(exp_error).check();
1162}
1163
1164pub fn check_stmt_compilation_err<S: Into<String>>(exp_error: S, stmt: &str) {
1167 Tester::default().run(stmt).expect_compilation_err(exp_error).check();
1168}
1169
1170pub fn check_expr_ok<V: Into<ConstantDatum>>(exp_value: V, expr: &str) {
1172 let exp_value = exp_value.into();
1173 Tester::default().run(format!("result = {}", expr)).expect_var("result", exp_value).check();
1174}
1175
1176pub fn check_expr_ok_with_vars<
1181 V: Into<ConstantDatum>,
1182 VS: Into<Vec<(&'static str, ConstantDatum)>>,
1183>(
1184 exp_value: V,
1185 expr: &str,
1186 vars: VS,
1187) {
1188 let vars = vars.into();
1189
1190 let mut input = String::new();
1191 for (name, value) in vars.as_slice() {
1192 input.push_str(name);
1193 input.push_str(" = ");
1194 input.push_str(&value.as_source());
1195 input.push_str(": ");
1196 }
1197 input.push_str(&format!("result = {}", expr));
1198
1199 let exp_value = exp_value.into();
1200
1201 let mut t = Tester::default();
1202 let mut c = t.run(input);
1203 c = c.expect_var("result", exp_value);
1204 for var in vars.into_iter() {
1205 c = c.expect_var(var.0, var.1.clone());
1206 }
1207 c.check();
1208}
1209
1210pub fn check_expr_error<S: Into<String>>(exp_error: S, expr: &str) {
1215 Tester::default().run(format!("result = {}", expr)).expect_err(exp_error).check();
1216}
1217
1218pub fn check_expr_compilation_error<S: Into<String>>(exp_error: S, expr: &str) {
1224 Tester::default().run(format!("result = {}", expr)).expect_compilation_err(exp_error).check();
1225}