1use crate::console::{
20 self, CharsXY, ClearType, Console, Key, PixelsXY, SizeInPixels, remove_control_chars,
21};
22use crate::program::Program;
23use crate::storage::Storage;
24use crate::{Machine, MachineBuilder, Signal, gpio};
25use async_channel::{Receiver, Sender};
26use async_trait::async_trait;
27use endbasic_core::{
28 Callable, ConstantDatum, ExprType, GetGlobalError, GlobalDef, GlobalDefKind, StopReason,
29 SymbolKey,
30};
31use futures_lite::future::block_on;
32use std::cell::RefCell;
33use std::collections::{HashMap, VecDeque};
34use std::io;
35use std::rc::Rc;
36use std::result::Result as StdResult;
37use std::str;
38
39type CheckerResult = StdResult<Option<i32>, String>;
40
41#[derive(Clone, Debug, Eq, PartialEq)]
43pub enum CapturedOut {
44 Clear(ClearType),
46
47 SetColor(Option<u8>, Option<u8>),
49
50 EnterAlt,
52
53 HideCursor,
55
56 LeaveAlt,
58
59 Locate(CharsXY),
61
62 MoveWithinLine(i16),
64
65 Print(String),
67
68 ShowCursor,
70
71 Write(String),
73
74 DrawCircle(PixelsXY, u16),
76
77 DrawCircleFilled(PixelsXY, u16),
79
80 DrawLine(PixelsXY, PixelsXY),
82
83 DrawPixel(PixelsXY),
85
86 DrawRect(PixelsXY, PixelsXY),
88
89 DrawRectFilled(PixelsXY, PixelsXY),
91
92 SyncNow,
94
95 SetSync(bool),
97}
98
99pub struct MockConsole {
101 golden_in: VecDeque<Key>,
103
104 captured_out: Vec<CapturedOut>,
106
107 size_chars: CharsXY,
109
110 size_pixels: Option<SizeInPixels>,
112
113 interactive: bool,
115
116 signals_tx: Option<Sender<Signal>>,
118}
119
120impl Default for MockConsole {
121 fn default() -> Self {
122 Self::new(None)
123 }
124}
125
126impl MockConsole {
127 fn new(signals_tx: Option<Sender<Signal>>) -> Self {
129 Self {
130 golden_in: VecDeque::new(),
131 captured_out: vec![],
132 size_chars: CharsXY::new(u16::MAX, u16::MAX),
133 size_pixels: None,
134 interactive: false,
135 signals_tx,
136 }
137 }
138
139 pub fn add_input_chars(&mut self, s: &str) {
144 for ch in s.chars() {
145 match ch {
146 '\n' => self.golden_in.push_back(Key::NewLine),
147 '\r' => self.golden_in.push_back(Key::CarriageReturn),
148 ch => self.golden_in.push_back(Key::Char(ch)),
149 }
150 }
151 }
152
153 pub fn add_input_keys(&mut self, keys: &[Key]) {
155 self.golden_in.extend(keys.iter().cloned());
156 }
157
158 pub fn captured_out(&self) -> &[CapturedOut] {
160 self.captured_out.as_slice()
161 }
162
163 #[must_use]
165 pub fn take_captured_out(&mut self) -> Vec<CapturedOut> {
166 let mut copy = Vec::with_capacity(self.captured_out.len());
167 copy.append(&mut self.captured_out);
168 copy
169 }
170
171 pub fn set_size_chars(&mut self, size: CharsXY) {
173 self.size_chars = size;
174 }
175
176 pub fn set_size_pixels(&mut self, size: SizeInPixels) {
178 self.size_pixels = Some(size);
179 }
180
181 pub fn set_interactive(&mut self, interactive: bool) {
183 self.interactive = interactive;
184 }
185}
186
187impl Drop for MockConsole {
188 fn drop(&mut self) {
189 assert!(
190 self.golden_in.is_empty(),
191 "Not all golden input chars were consumed; {} left",
192 self.golden_in.len()
193 );
194 }
195}
196
197#[async_trait(?Send)]
198impl Console for MockConsole {
199 fn clear(&mut self, how: ClearType) -> io::Result<()> {
200 self.captured_out.push(CapturedOut::Clear(how));
201 Ok(())
202 }
203
204 fn color(&self) -> (Option<u8>, Option<u8>) {
205 for o in self.captured_out.iter().rev() {
206 if let CapturedOut::SetColor(fg, bg) = o {
207 return (*fg, *bg);
208 }
209 }
210 (None, None)
211 }
212
213 fn set_color(&mut self, fg: Option<u8>, bg: Option<u8>) -> io::Result<()> {
214 self.captured_out.push(CapturedOut::SetColor(fg, bg));
215 Ok(())
216 }
217
218 fn enter_alt(&mut self) -> io::Result<()> {
219 self.captured_out.push(CapturedOut::EnterAlt);
220 Ok(())
221 }
222
223 fn hide_cursor(&mut self) -> io::Result<()> {
224 self.captured_out.push(CapturedOut::HideCursor);
225 Ok(())
226 }
227
228 fn is_interactive(&self) -> bool {
229 self.interactive
230 }
231
232 fn leave_alt(&mut self) -> io::Result<()> {
233 self.captured_out.push(CapturedOut::LeaveAlt);
234 Ok(())
235 }
236
237 fn locate(&mut self, pos: CharsXY) -> io::Result<()> {
238 assert!(pos.x < self.size_chars.x);
239 assert!(pos.y < self.size_chars.y);
240 self.captured_out.push(CapturedOut::Locate(pos));
241 Ok(())
242 }
243
244 fn move_within_line(&mut self, off: i16) -> io::Result<()> {
245 self.captured_out.push(CapturedOut::MoveWithinLine(off));
246 Ok(())
247 }
248
249 fn print(&mut self, text: &str) -> io::Result<()> {
250 let text = remove_control_chars(text.to_owned());
251
252 self.captured_out.push(CapturedOut::Print(text));
253 Ok(())
254 }
255
256 async fn poll_key(&mut self) -> io::Result<Option<Key>> {
257 match self.golden_in.pop_front() {
258 Some(ch) => {
259 if ch == Key::Interrupt
260 && let Some(signals_tx) = &self.signals_tx
261 {
262 let _ = signals_tx.send(Signal::Break).await;
263 }
264 Ok(Some(ch))
265 }
266 None => Ok(None),
267 }
268 }
269
270 async fn read_key(&mut self) -> io::Result<Key> {
271 match self.golden_in.pop_front() {
272 Some(ch) => {
273 if ch == Key::Interrupt
274 && let Some(signals_tx) = &self.signals_tx
275 {
276 let _ = signals_tx.send(Signal::Break).await;
277 }
278 Ok(ch)
279 }
280 None => Ok(Key::Eof),
281 }
282 }
283
284 fn show_cursor(&mut self) -> io::Result<()> {
285 self.captured_out.push(CapturedOut::ShowCursor);
286 Ok(())
287 }
288
289 fn size_chars(&self) -> io::Result<CharsXY> {
290 Ok(self.size_chars)
291 }
292
293 fn size_pixels(&self) -> io::Result<SizeInPixels> {
294 match self.size_pixels {
295 Some(size) => Ok(size),
296 None => Err(io::Error::other("Graphical console size not yet set")),
297 }
298 }
299
300 fn write(&mut self, text: &str) -> io::Result<()> {
301 let text = remove_control_chars(text.to_owned());
302
303 self.captured_out.push(CapturedOut::Write(text));
304 Ok(())
305 }
306
307 fn draw_circle(&mut self, xy: PixelsXY, r: u16) -> io::Result<()> {
308 self.captured_out.push(CapturedOut::DrawCircle(xy, r));
309 Ok(())
310 }
311
312 fn draw_circle_filled(&mut self, xy: PixelsXY, r: u16) -> io::Result<()> {
313 self.captured_out.push(CapturedOut::DrawCircleFilled(xy, r));
314 Ok(())
315 }
316
317 fn draw_line(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
318 self.captured_out.push(CapturedOut::DrawLine(x1y1, x2y2));
319 Ok(())
320 }
321
322 fn draw_pixel(&mut self, xy: PixelsXY) -> io::Result<()> {
323 self.captured_out.push(CapturedOut::DrawPixel(xy));
324 Ok(())
325 }
326
327 fn draw_rect(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
328 self.captured_out.push(CapturedOut::DrawRect(x1y1, x2y2));
329 Ok(())
330 }
331
332 fn draw_rect_filled(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
333 self.captured_out.push(CapturedOut::DrawRectFilled(x1y1, x2y2));
334 Ok(())
335 }
336
337 fn sync_now(&mut self) -> io::Result<()> {
338 self.captured_out.push(CapturedOut::SyncNow);
339 Ok(())
340 }
341
342 fn set_sync(&mut self, enabled: bool) -> io::Result<bool> {
343 let mut previous = true;
344 for o in self.captured_out.iter().rev() {
345 if let CapturedOut::SetSync(e) = o {
346 previous = *e;
347 break;
348 }
349 }
350 self.captured_out.push(CapturedOut::SetSync(enabled));
351 Ok(previous)
352 }
353}
354
355pub fn flatten_output(captured_out: Vec<CapturedOut>) -> String {
358 let mut flattened = String::new();
359 for out in captured_out {
360 match out {
361 CapturedOut::Write(bs) => flattened.push_str(&bs),
362 CapturedOut::Print(s) => flattened.push_str(&s),
363 _ => (),
364 }
365 }
366 flattened
367}
368
369#[derive(Default)]
372pub struct RecordedProgram {
373 name: Option<String>,
374 content: String,
375 dirty: bool,
376}
377
378#[async_trait(?Send)]
379impl Program for RecordedProgram {
380 fn is_dirty(&self) -> bool {
381 self.dirty
382 }
383
384 async fn edit(&mut self, console: &mut dyn Console) -> io::Result<()> {
385 let append = console::read_line(console, "", "", None).await?;
386 self.content.push_str(&append);
387 self.content.push('\n');
388 self.dirty = true;
389 Ok(())
390 }
391
392 fn load(&mut self, name: Option<&str>, text: &str) {
393 self.name = name.map(str::to_owned);
394 text.clone_into(&mut self.content);
395 self.dirty = false;
396 }
397
398 fn name(&self) -> Option<&str> {
399 self.name.as_deref()
400 }
401
402 fn set_name(&mut self, name: &str) {
403 self.name = Some(name.to_owned());
404 self.dirty = false;
405 }
406
407 fn text(&self) -> String {
408 self.content.clone()
409 }
410}
411
412#[must_use]
414#[derive(Clone)]
415pub struct Tester {
416 console: Rc<RefCell<MockConsole>>,
417 storage: Rc<RefCell<Storage>>,
418 program: Rc<RefCell<RecordedProgram>>,
419 callables: Vec<Rc<dyn Callable>>,
420 global_defs: Vec<GlobalDef>,
421 interactive: bool,
422 signals_tx: Sender<Signal>,
423 signals_rx: Receiver<Signal>,
424}
425
426impl Default for Tester {
427 fn default() -> Self {
429 let (signals_tx, signals_rx) = async_channel::unbounded();
430 let console = Rc::from(RefCell::from(MockConsole::new(Some(signals_tx.clone()))));
431 let program = Rc::from(RefCell::from(RecordedProgram::default()));
432 let storage = Rc::from(RefCell::from(Storage::default()));
433 let callables = vec![];
434 let global_defs = vec![];
435 let interactive = true;
436
437 Self {
438 console,
439 storage,
440 program,
441 callables,
442 global_defs,
443 interactive,
444 signals_tx,
445 signals_rx,
446 }
447 }
448}
449
450impl Tester {
451 fn build_machine(
452 console: Rc<RefCell<MockConsole>>,
453 storage: Rc<RefCell<Storage>>,
454 program: Rc<RefCell<RecordedProgram>>,
455 callables: Vec<Rc<dyn Callable>>,
456 global_defs: Vec<GlobalDef>,
457 interactive: bool,
458 signals_chan: (Sender<Signal>, Receiver<Signal>),
459 ) -> Machine {
460 let gpio_pins = Rc::from(RefCell::from(gpio::NoopPins::default()));
464 let mut builder = MachineBuilder::default()
465 .with_console(console)
466 .with_globals(global_defs)
467 .with_gpio_pins(gpio_pins)
468 .with_signals_chan(signals_chan);
469
470 for callable in callables {
471 builder.add_callable(callable);
472 }
473
474 if interactive {
475 builder.make_interactive().with_program(program).with_storage(storage).build()
476 } else {
477 builder.build()
478 }
479 }
480
481 pub fn empty() -> Self {
483 Self { interactive: false, ..Self::default() }
484 }
485
486 pub fn add_callable(mut self, callable: Rc<dyn Callable>) -> Self {
488 self.callables.push(callable);
489 self
490 }
491
492 pub fn add_input_chars(self, golden_in: &str) -> Self {
494 self.console.borrow_mut().add_input_chars(golden_in);
495 self
496 }
497
498 pub fn add_input_keys(self, keys: &[Key]) -> Self {
500 self.console.borrow_mut().add_input_keys(keys);
501 self
502 }
503
504 pub fn get_console(&self) -> Rc<RefCell<MockConsole>> {
509 self.console.clone()
510 }
511
512 pub fn get_program(&self) -> Rc<RefCell<RecordedProgram>> {
517 self.program.clone()
518 }
519
520 pub fn get_storage(&self) -> Rc<RefCell<Storage>> {
525 self.storage.clone()
526 }
527
528 pub fn send_break(self) -> Self {
530 block_on(self.signals_tx.send(Signal::Break)).unwrap();
531 self
532 }
533
534 pub fn set_var<S: Into<String>, V: Into<ConstantDatum>>(mut self, name: S, value: V) -> Self {
536 let value = value.into();
537 self.global_defs.push(GlobalDef {
538 name: name.into(),
539 kind: GlobalDefKind::Scalar {
540 etype: match &value {
541 ConstantDatum::Boolean(..) => ExprType::Boolean,
542 ConstantDatum::Double(..) => ExprType::Double,
543 ConstantDatum::Integer(..) => ExprType::Integer,
544 ConstantDatum::Text(..) => ExprType::Text,
545 },
546 initial_value: Some(value),
547 },
548 });
549 self
550 }
551
552 pub fn set_program(self, name: Option<&str>, text: &str) -> Self {
555 assert!(!text.is_empty());
556 {
557 let mut program = self.program.borrow_mut();
558 assert!(program.text().is_empty());
559 program.load(name, text);
560 }
561 self
562 }
563
564 pub fn write_file(self, name: &str, content: &str) -> Self {
566 block_on(self.storage.borrow_mut().put(name, content.as_bytes())).unwrap();
567 self
568 }
569
570 pub fn run<S: Into<String>>(&mut self, script: S) -> Checker<'_> {
573 let machine = Self::build_machine(
574 self.console.clone(),
575 self.storage.clone(),
576 self.program.clone(),
577 self.callables.clone(),
578 self.global_defs.clone(),
579 self.interactive,
580 (self.signals_tx.clone(), self.signals_rx.clone()),
581 );
582 let tester = TesterContinuation { tester: self, machine };
583 tester.run(script)
584 }
585
586 pub fn continue_from_here(&self) -> TesterContinuation<'_> {
588 let machine = Self::build_machine(
589 self.console.clone(),
590 self.storage.clone(),
591 self.program.clone(),
592 self.callables.clone(),
593 self.global_defs.clone(),
594 self.interactive,
595 (self.signals_tx.clone(), self.signals_rx.clone()),
596 );
597 TesterContinuation { tester: self, machine }
598 }
599
600 pub fn run_n(&mut self, scripts: &[&str]) -> Checker<'_> {
609 let mut machine = Self::build_machine(
610 self.console.clone(),
611 self.storage.clone(),
612 self.program.clone(),
613 self.callables.clone(),
614 self.global_defs.clone(),
615 self.interactive,
616 (self.signals_tx.clone(), self.signals_rx.clone()),
617 );
618 let mut result = Ok(None);
619 for script in scripts {
620 match machine.compile(&mut script.as_bytes()) {
621 Ok(()) => (),
622 Err(e) => {
623 result = Err(format!("{}", e));
624 break;
625 }
626 }
627 result = block_on(machine.exec()).map_err(|e| format!("{}", e));
628 if result.is_err() {
629 break;
630 }
631 }
632 Checker::new(self, machine, result)
633 }
634}
635
636pub struct TesterContinuation<'a> {
641 tester: &'a Tester,
642 machine: Machine,
643}
644
645impl<'a> TesterContinuation<'a> {
646 pub fn get_machine(&mut self) -> &mut Machine {
648 &mut self.machine
649 }
650
651 pub fn run<S: Into<String>>(mut self, script: S) -> Checker<'a> {
654 let result = match self.machine.compile(&mut script.into().as_bytes()) {
655 Ok(()) => block_on(self.machine.exec()).map_err(|e| format!("{}", e)),
656 Err(e) => Err(format!("{}", e)),
657 };
658 Checker::new(self.tester, self.machine, result)
659 }
660
661 pub fn clear(mut self) -> Self {
663 self.machine.clear();
664 self
665 }
666}
667
668struct ExpArray {
670 subtype: ExprType,
672
673 dimensions: Vec<usize>,
675
676 contents: Vec<(Vec<i32>, ConstantDatum)>,
681}
682
683#[must_use]
685pub struct Checker<'a> {
686 tester: &'a Tester,
687 machine: Machine,
688 result: CheckerResult,
689 exp_result: CheckerResult,
690 exp_output: Vec<CapturedOut>,
691 exp_drives: HashMap<String, String>,
692 exp_program_name: Option<String>,
693 exp_program_text: String,
694 exp_arrays: HashMap<SymbolKey, ExpArray>,
695 exp_vars: HashMap<SymbolKey, ConstantDatum>,
696}
697
698impl<'a> Checker<'a> {
699 fn new(tester: &'a Tester, machine: Machine, result: CheckerResult) -> Self {
704 Self {
705 tester,
706 machine,
707 result,
708 exp_result: Ok(None),
709 exp_output: vec![],
710 exp_drives: HashMap::default(),
711 exp_program_name: None,
712 exp_program_text: String::new(),
713 exp_arrays: HashMap::default(),
714 exp_vars: HashMap::default(),
715 }
716 }
717
718 pub fn expect_ok(mut self, stop_reason: StopReason) -> Self {
723 self.exp_result = Ok(match stop_reason {
724 StopReason::End(code) => Some(code.to_i32()),
725 StopReason::Eof => None,
726 StopReason::Exception(_, _) | StopReason::UpcallAsync(_) | StopReason::Yield => {
727 unreachable!()
728 }
729 });
730 self
731 }
732
733 pub fn expect_compilation_err<S: Into<String>>(mut self, message: S) -> Self {
739 self.exp_result = Err(message.into());
740 self
741 }
742
743 pub fn expect_err<S: Into<String>>(mut self, message: S) -> Self {
748 self.exp_result = Err(message.into());
749 self
750 }
751
752 pub fn expect_array<S: AsRef<str>>(
757 mut self,
758 name: S,
759 subtype: ExprType,
760 dimensions: &[usize],
761 contents: Vec<(Vec<i32>, ConstantDatum)>,
762 ) -> Self {
763 let key = SymbolKey::from(name);
764 assert!(!self.exp_arrays.contains_key(&key));
765 self.exp_arrays
766 .insert(key, ExpArray { subtype, dimensions: dimensions.to_vec(), contents });
767 self
768 }
769
770 pub fn expect_array_simple<S: AsRef<str>>(
773 mut self,
774 name: S,
775 subtype: ExprType,
776 contents: Vec<ConstantDatum>,
777 ) -> Self {
778 let key = SymbolKey::from(name);
779 assert!(!self.exp_arrays.contains_key(&key));
780 let mut exp_array = Vec::with_capacity(contents.len());
781 for (i, value) in contents.into_iter().enumerate() {
782 exp_array.push((vec![i as i32], value));
783 }
784 self.exp_arrays.insert(
785 key,
786 ExpArray { subtype, dimensions: vec![exp_array.len()], contents: exp_array },
787 );
788 self
789 }
790
791 pub fn expect_clear(mut self) -> Self {
793 self.exp_output.append(&mut vec![
794 CapturedOut::LeaveAlt,
795 CapturedOut::SetColor(None, None),
796 CapturedOut::ShowCursor,
797 CapturedOut::SetSync(true),
798 ]);
799 self
800 }
801
802 pub fn expect_file<N: Into<String>, C: Into<String>>(mut self, name: N, content: C) -> Self {
806 let name = name.into();
807 assert!(!self.exp_drives.contains_key(&name));
808 self.exp_drives.insert(name, content.into());
809 self
810 }
811
812 pub fn expect_output<V: Into<Vec<CapturedOut>>>(mut self, out: V) -> Self {
814 self.exp_output.append(&mut out.into());
815 self
816 }
817
818 pub fn expect_prints<S: Into<String>, V: Into<Vec<S>>>(mut self, out: V) -> Self {
823 let out = out.into();
824 self.exp_output
825 .append(&mut out.into_iter().map(|x| CapturedOut::Print(x.into())).collect());
826 self
827 }
828
829 pub fn expect_program<S1: Into<String>, S2: Into<String>>(
832 mut self,
833 name: Option<S1>,
834 text: S2,
835 ) -> Self {
836 assert!(self.exp_program_text.is_empty());
837 let text = text.into();
838 assert!(!text.is_empty());
839 self.exp_program_name = name.map(|x| x.into());
840 self.exp_program_text = text;
841 self
842 }
843
844 pub fn expect_var<S: AsRef<str>, V: Into<ConstantDatum>>(mut self, name: S, value: V) -> Self {
846 let key = SymbolKey::from(name);
847 assert!(!self.exp_vars.contains_key(&key));
848 self.exp_vars.insert(key, value.into());
849 self
850 }
851
852 #[must_use]
854 pub fn take_captured_out(&mut self) -> Vec<CapturedOut> {
855 assert!(
856 self.exp_output.is_empty(),
857 "Cannot take output if we are already expecting prints because the test would fail"
858 );
859 self.tester.console.borrow_mut().take_captured_out()
860 }
861
862 fn query_array_element(&self, name: &SymbolKey, subscripts: &[i32]) -> Option<ConstantDatum> {
863 match self.machine.vm.get_global_array(&self.machine.image, name, subscripts) {
864 Ok(Some(value)) => Some(value),
865 Ok(None) => self
866 .machine
867 .vm
868 .get_program_array(&self.machine.image, name, subscripts)
869 .unwrap_or_else(|e| panic!("Expected array {} has wrong shape: {}", name, e)),
870 Err(e) => panic!("Expected array {} has wrong shape: {}", name, e),
871 }
872 }
873
874 fn check_array_dims(&self, name: &SymbolKey, dimensions: &[usize]) {
875 if dimensions.is_empty() {
876 panic!("Expected array {} must have at least one dimension", name);
877 }
878
879 let mut subscripts = vec![0; dimensions.len()];
880 for i in 0..dimensions.len() {
881 subscripts[i] = dimensions[i] as i32;
882 match self.machine.vm.get_global_array(&self.machine.image, name, &subscripts) {
883 Err(GetGlobalError::SubscriptOutOfBounds(_)) => (),
884 Ok(None) => {
885 match self.machine.vm.get_program_array(&self.machine.image, name, &subscripts)
886 {
887 Err(GetGlobalError::SubscriptOutOfBounds(_)) => (),
888 Ok(Some(_)) => panic!(
889 "Expected array {} dimension {} to be {} but found larger",
890 name, i, dimensions[i]
891 ),
892 Ok(None) => panic!("Expected array {} not defined", name),
893 Err(e) => panic!("Expected array {} has wrong shape: {}", name, e),
894 }
895 }
896 Ok(Some(_)) => panic!(
897 "Expected array {} dimension {} to be {} but found larger",
898 name, i, dimensions[i]
899 ),
900 Err(e) => panic!("Expected array {} has wrong shape: {}", name, e),
901 }
902 subscripts[i] = 0;
903 }
904 }
905
906 fn check_array(&self, name: &SymbolKey, exp_array: &ExpArray) {
907 let mut exp_contents = HashMap::with_capacity(exp_array.contents.len());
908 for (subscripts, value) in exp_array.contents.iter() {
909 assert_eq!(
910 exp_array.dimensions.len(),
911 subscripts.len(),
912 "Expected array {} has wrong number of subscripts",
913 name
914 );
915 for (i, subscript) in subscripts.iter().enumerate() {
916 assert!(
917 *subscript >= 0 && *subscript < exp_array.dimensions[i] as i32,
918 "Expected array {} has out-of-bounds subscript {} at dimension {}",
919 name,
920 subscript,
921 i
922 );
923 }
924 let previous = exp_contents.insert(subscripts.clone(), value.clone());
925 assert!(previous.is_none(), "Expected array {} has duplicate subscripts", name);
926 }
927
928 let default_value = match exp_array.subtype {
929 ExprType::Boolean => ConstantDatum::Boolean(false),
930 ExprType::Double => ConstantDatum::Double(0.0),
931 ExprType::Integer => ConstantDatum::Integer(0),
932 ExprType::Text => ConstantDatum::Text(String::new()),
933 };
934
935 let mut subscripts = vec![0; exp_array.dimensions.len()];
936 loop {
937 let value = self
938 .query_array_element(name, &subscripts)
939 .unwrap_or_else(|| panic!("Expected array {} not defined", name));
940 assert_eq!(
941 exp_contents.get(&subscripts).unwrap_or(&default_value),
942 &value,
943 "Expected array {} at {:?} has wrong value",
944 name,
945 subscripts
946 );
947
948 let mut i = 0;
949 while i < subscripts.len() {
950 subscripts[i] += 1;
951 if subscripts[i] < exp_array.dimensions[i] as i32 {
952 break;
953 }
954 subscripts[i] = 0;
955 i += 1;
956 }
957 if i == subscripts.len() {
958 break;
959 }
960 }
961
962 self.check_array_dims(name, &exp_array.dimensions);
963 }
964
965 pub fn check(self) -> TesterContinuation<'a> {
967 assert_eq!(self.exp_result, self.result);
968
969 for (name, exp_value) in self.exp_vars.iter() {
970 let value = match self.machine.vm.get_global(&self.machine.image, name) {
971 Ok(Some(value)) => Some(value),
972 Ok(None) => {
973 self.machine.vm.get_program(&self.machine.image, name).unwrap_or_else(|e| {
974 panic!("Expected variable {} has wrong shape: {}", name, e)
975 })
976 }
977 Err(e) => panic!("Expected variable {} has wrong shape: {}", name, e),
978 };
979 let value = value.unwrap_or_else(|| panic!("Expected variable {} not defined", name));
980 assert_eq!(exp_value, &value, "Expected variable {} has wrong value", name);
981 }
982
983 for (name, exp_array) in self.exp_arrays.iter() {
984 self.check_array(name, exp_array);
985 }
986
987 let drive_contents = {
988 let mut files = HashMap::new();
989 let storage = self.tester.storage.borrow();
990 for (drive_name, target) in storage.mounted().iter() {
991 if target.starts_with("cloud://") {
992 continue;
995 }
996
997 let root = format!("{}:/", drive_name);
998 for name in block_on(storage.enumerate(&root)).unwrap().dirents().keys() {
999 let path = format!("{}{}", root, name);
1000 let content = block_on(storage.get(&path)).unwrap();
1001 let content = String::from_utf8(content).unwrap();
1002 files.insert(path, content);
1003 }
1004 }
1005 files
1006 };
1007
1008 assert_eq!(self.exp_output, self.tester.console.borrow().captured_out());
1009 assert_eq!(self.exp_program_name.as_deref(), self.tester.program.borrow().name());
1010 assert_eq!(self.exp_program_text, self.tester.program.borrow().text());
1011 assert_eq!(self.exp_drives, drive_contents);
1012
1013 TesterContinuation { tester: self.tester, machine: self.machine }
1014 }
1015}
1016
1017pub fn check_stmt_err<S: Into<String>>(exp_error: S, stmt: &str) {
1019 Tester::default().run(stmt).expect_err(exp_error).check();
1020}
1021
1022pub fn check_stmt_compilation_err<S: Into<String>>(exp_error: S, stmt: &str) {
1025 Tester::default().run(stmt).expect_compilation_err(exp_error).check();
1026}
1027
1028pub fn check_expr_ok<V: Into<ConstantDatum>>(exp_value: V, expr: &str) {
1030 let exp_value = exp_value.into();
1031 Tester::default().run(format!("result = {}", expr)).expect_var("result", exp_value).check();
1032}
1033
1034pub fn check_expr_ok_with_vars<
1039 V: Into<ConstantDatum>,
1040 VS: Into<Vec<(&'static str, ConstantDatum)>>,
1041>(
1042 exp_value: V,
1043 expr: &str,
1044 vars: VS,
1045) {
1046 let vars = vars.into();
1047
1048 let mut input = String::new();
1049 for (name, value) in vars.as_slice() {
1050 input.push_str(name);
1051 input.push_str(" = ");
1052 input.push_str(&value.as_source());
1053 input.push_str(": ");
1054 }
1055 input.push_str(&format!("result = {}", expr));
1056
1057 let exp_value = exp_value.into();
1058
1059 let mut t = Tester::default();
1060 let mut c = t.run(input);
1061 c = c.expect_var("result", exp_value);
1062 for var in vars.into_iter() {
1063 c = c.expect_var(var.0, var.1.clone());
1064 }
1065 c.check();
1066}
1067
1068pub fn check_expr_error<S: Into<String>>(exp_error: S, expr: &str) {
1073 Tester::default().run(format!("result = {}", expr)).expect_err(exp_error).check();
1074}
1075
1076pub fn check_expr_compilation_error<S: Into<String>>(exp_error: S, expr: &str) {
1082 Tester::default().run(format!("result = {}", expr)).expect_compilation_err(exp_error).check();
1083}