//! Regressionen zu Ereigniszustellung, Hostgrenzen und Forms-Zustand. use tb_frontend::forms::{FormCatalog, ObjectClass}; use tb_runtime::host::{CaptureHost, Ereignis, Host, MausArt, MausEreignis}; use tb_runtime::screen::TextScreen; use tb_runtime::value::Value; use tb_vm::interp::{RunEvent, Vm}; #[path = "../../../tests/support/prozessfrist.rs"] mod prozessfrist; fn vm(src: &str) -> Vm { let mut catalog = FormCatalog::default(); catalog.add("Form1", ObjectClass::Form, None, false); catalog.add("Text1", ObjectClass::TextBox, Some("Form1"), false); catalog.add("Text2", ObjectClass::TextBox, Some("Form1"), false); catalog.add("Timer1", ObjectClass::Timer, Some("Form1"), false); catalog.add("Menu1", ObjectClass::Menu, Some("Form1"), false); catalog.add("Item1", ObjectClass::Menu, Some("Menu1"), false); Vm::new( tb_vm::compile_source_with_forms("FORM1", src, &catalog) .unwrap_or_else(|d| panic!("{d:?}")), ) } fn stop(vm: &mut Vm, host: &mut dyn Host) { assert!(matches!(vm.run(host), RunEvent::Stopped { .. })); } fn string(vm: &Vm, name: &str) -> String { match vm.inspect(name) { Some(Value::Str(s)) => s.to_string(), other => panic!("{other:?}"), } } fn integer(vm: &Vm, name: &str) -> i16 { match vm.inspect(name) { Some(Value::Int(n)) => n, other => panic!("{other:?}"), } } fn key(s: &str) -> Ereignis { Ereignis::Taste(s.into(), 0) } fn mouse() -> Ereignis { Ereignis::Maus(MausEreignis { art: MausArt::Druck, taste: 1, shift: 0, zeile: 1, spalte: 1, }) } #[test] fn beide_signale_werden_zugestellt_und_beginnen_in_quellreihenfolge() { let mut vm = vm("ON SIGNAL(1) GOSUB IntHandler\nON SIGNAL(2) GOSUB TermHandler\nSIGNAL(1) ON\nSIGNAL(2) ON\nSTOP\nDOEVENTS\nEND\nIntHandler:\ns$ = s$ + \"1\"\nRETURN\nTermHandler:\ns$ = s$ + \"2\"\nRETURN"); let mut host = CaptureHost::default(); stop(&mut vm, &mut host); host.ereignis(Ereignis::Signal(2)); host.ereignis(Ereignis::Signal(1)); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(string(&vm, "s"), "12"); for n in [0, 3] { assert!( tb_vm::compile_source("TEST", &format!("ON SIGNAL({n}) GOSUB H\nEND\nH:\nRETURN")) .is_err() ); let mut bad = vm_for_source(&format!("n% = {n}\nON SIGNAL(n%) GOSUB H\nEND\nH:\nRETURN")); assert!(matches!( bad.run(&mut host), RunEvent::Error { code: 5, .. } )); } } fn vm_for_source(src: &str) -> Vm { Vm::new(tb_vm::compile_source("TEST", src).unwrap()) } #[test] fn key_on_off_stop_gelten_fuer_alle_konsoleneingaben() { for state in ["ON", "OFF", "STOP"] { for input in ["s$ = INKEY$", "INPUT s$", "LINE INPUT s$", "s$ = INPUT$(1)"] { let mut vm = vm_for_source(&format!("ON KEY(15) GOSUB H\nKEY 15, CHR$(0) + CHR$(30)\nKEY(15) {state}\nSTOP\n{input}\nKEY(15) ON\nDOEVENTS\nEND\nH:\nn% = n% + 1\nRETURN")); let mut host = CaptureHost::default(); stop(&mut vm, &mut host); host.ereignis(key("a")); if state == "ON" { host.ereignis(key("b")); } host.ereignis(key("\r")); assert_eq!(vm.run(&mut host), RunEvent::Ended, "{state}: {input}"); assert_eq!( string(&vm, "s"), if state == "ON" { "b" } else { "a" }, "{state}: {input}" ); assert_eq!( integer(&vm, "n"), if state == "OFF" { 0 } else { 1 }, "{state}: {input}" ); } } } #[test] fn taste_maus_und_trap_behalten_die_annahmereihenfolge() { for (events, expected) in [ (vec![key("a"), mouse()], "aM"), (vec![mouse(), key("a")], "Ma"), ( vec![key("a"), mouse(), key("b"), key("t"), mouse()], "aMbTM", ), ] { let mut vm = vm("DIM SHARED s$\nON KEY(15) GOSUB H\nKEY 15, CHR$(0) + CHR$(20)\nKEY(15) ON\nForm1.Show\nText1.SetFocus\nSTOP\nn% = DOEVENTS + LEN(s$)\nDOEVENTS\nDOEVENTS\nDOEVENTS\nDOEVENTS\nEND\nH:\ns$ = s$ + \"T\"\nRETURN\nSUB Text1_KeyPress(KeyAscii AS INTEGER)\nSHARED s$\ns$ = s$ + CHR$(KeyAscii)\nEND SUB\nSUB Form_MouseDown(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)\nSHARED s$\ns$ = s$ + \"M\"\nEND SUB"); let mut host = CaptureHost::default(); stop(&mut vm, &mut host); for event in events { host.ereignis(event); } assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(string(&vm, "s"), expected); assert!( matches!(vm.inspect("n"), Some(Value::Int(n)) if n > 0), "DOEVENTS muss vor der Auswertung von LEN zustellen" ); } } #[test] fn fokus_handler_laufen_geordnet_bis_zur_rueckkehr() { let mut vm = vm("DIM SHARED s$\nText1.TabIndex = 0\nText2.TabIndex = 1\nForm1.Show\nText1.SetFocus\nSTOP\nDOEVENTS\nEND\nSUB Text1_LostFocus()\nSHARED s$\ns$ = s$ + \"L\"\ns$ = s$ + \"l\"\nEND SUB\nSUB Text2_GotFocus()\nSHARED s$\ns$ = s$ + \"G\"\ns$ = s$ + \"g\"\nEND SUB"); let mut host = CaptureHost::default(); stop(&mut vm, &mut host); host.ereignis(key("\t")); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(string(&vm, "s"), "LlGg"); } #[test] fn doevents_verhindert_rekursion_derselben_forms_prozedur() { let mut vm = vm("DIM SHARED s$\nForm1.Show\nText1.SetFocus\nSTOP\nDOEVENTS\nEND\nSUB Text1_KeyPress(KeyAscii AS INTEGER)\nSHARED s$\ns$ = s$ + CHR$(KeyAscii)\nDOEVENTS\ns$ = s$ + \"r\"\nEND SUB"); let mut host = CaptureHost::default(); stop(&mut vm, &mut host); host.ereignis(key("a")); host.ereignis(key("b")); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(string(&vm, "s"), "arbr"); } #[test] fn offenes_menu_sperrt_traps_und_wartende_forms_timer_an_allen_zustellpunkten() { let mut vm = vm("DIM SHARED n%\nON UEVENT GOSUB H\nUEVENT ON\nMenu1.Caption = \"&Datei\"\nItem1.Caption = \"&Ende\"\nForm1.Show\nSTOP\nCALL SetUEvent\nn% = n% + 1\nDOEVENTS\nSLEEP .001\nLINE INPUT s$\nSTOP\nDOEVENTS\nEND\nH:\nn% = n% + 10\nRETURN\nSUB Timer1_Timer()\nSHARED n%\nn% = n% + 100\nEND SUB"); let mut host = CaptureHost::default(); stop(&mut vm, &mut host); vm.forms.handle_key("d", 4); assert!(vm.forms.menu_is_open()); vm.queue_form_event(tb_ui::forms::FormEvent { object: 3, array_index: None, name: "TIMER".into(), args: vec![], }); // Erst die blockierende Konsole holt Enter ab, damit das Menü offen bleibt. struct InputHost(CaptureHost); impl Host for InputHost { fn present(&mut self, s: &TextScreen) { self.0.present(s); } fn next_event(&mut self, block: bool) -> Option { block.then(|| key("\r")) } fn jetzt_ms(&mut self) -> u64 { self.0.jetzt_ms() } fn warten(&mut self, deadline: Option) -> Option { if deadline.is_none() { self.next_event(true) } else { self.0.warten(deadline) } } } stop(&mut vm, &mut InputHost(CaptureHost::default())); assert_eq!(integer(&vm, "n"), 1); assert!(vm.forms.menu_is_open()); host.ereignis(key("\u{1b}")); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(integer(&vm, "n"), 111); } #[test] fn ohne_zeitbedarf_keine_uhrabfrage_und_keine_mausansammlung() { struct OhneUhr(CaptureHost); impl Host for OhneUhr { fn present(&mut self, s: &TextScreen) { self.0.present(s); } fn next_event(&mut self, b: bool) -> Option { self.0.next_event(b) } fn jetzt_ms(&mut self) -> u64 { panic!("unerwartete Uhrabfrage") } fn warten(&mut self, _: Option) -> Option { panic!("unerwartetes Warten") } } for src in ["a% = 1\na% = 2\nEND", "Form1.Show\na% = 1\nDOEVENTS\nEND"] { let mut vm = vm(src); let mut host = OhneUhr(CaptureHost::default()); for _ in 0..500 { host.0.ereignis(mouse()); } assert_eq!(vm.run(&mut host), RunEvent::Ended); assert!(vm.rt.eingaben.is_empty()); } } #[test] fn sleep_folgt_nur_der_hostfrist_und_forms_timer_beendet_es() { let _frist = prozessfrist::Prozessfrist::neu("Host-SLEEP", std::time::Duration::from_secs(5)); struct WarteHost { now: u64, deadlines: Vec>, timer: bool, } impl Host for WarteHost { fn present(&mut self, _: &TextScreen) {} fn next_event(&mut self, _: bool) -> Option { None } fn jetzt_ms(&mut self) -> u64 { self.now } fn warten(&mut self, deadline: Option) -> Option { self.deadlines.push(deadline); if self.deadlines.len() == 1 && !self.timer { // Reale Zeit verstreicht bei eingefrorener Hostuhr. Erst die // folgende Host-Wartephase darf die Deadline erreichen. std::thread::sleep(std::time::Duration::from_millis(10)); return Some(Ereignis::Groesse { cols: 80, rows: 25 }); } self.now = deadline.expect("Deadline fehlt"); None } } let mut host = WarteHost { now: 0, deadlines: vec![], timer: false, }; let mut plain = vm_for_source("SLEEP .001\nEND"); assert_eq!(plain.run(&mut host), RunEvent::Ended); assert_eq!(host.deadlines, [Some(1), Some(1)]); let mut vm = vm("DIM SHARED n%\nForm1.Show\nTimer1.Interval = 100\nTimer1.Enabled = -1\nSLEEP 60\nEND\nSUB Timer1_Timer()\nSHARED n%\nn% = n% + 1\nEND SUB"); let mut host = WarteHost { now: 0, deadlines: vec![], timer: true, }; assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(host.deadlines, [Some(100)]); assert_eq!(integer(&vm, "n"), 1); } #[test] fn dialog_erhaelt_stop_markierung_unverbrauchter_tasten() { let mut vm = vm_for_source("ON KEY(15) GOSUB H\nKEY 15, CHR$(0) + CHR$(30)\nKEY(15) STOP\nSTOP\nx% = MSGBOX(\"Weiter\")\nKEY(15) ON\nDOEVENTS\ns$ = INKEY$\nEND\nH:\nn% = n% + 1\nRETURN"); let mut host = CaptureHost::default(); stop(&mut vm, &mut host); host.ereignis(key("\r")); host.ereignis(key("a")); vm.rt.pump(&mut host, false); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(string(&vm, "s"), "a"); assert_eq!(integer(&vm, "n"), 1); } #[test] fn unload_aus_unload_handler_ruft_dieselbe_prozedur_nicht_rekursiv_auf() { let mut vm = vm("DIM SHARED s$\nForm1.Show\nUNLOAD Form1\nEND\nSUB Form_Unload(Cancel AS INTEGER)\nSHARED s$\ns$ = s$ + \"u\"\nUNLOAD Form1\ns$ = s$ + \"r\"\nEND SUB"); assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended); assert_eq!(string(&vm, "s"), "ur"); assert!(!vm.forms.is_loaded(0)); } // Nachverifikation V1–V5: unveränderte Sollwerte aus den Review-Proben. #[test] fn review_signal_waehrend_blockierender_eingabe() { struct Input { reads: usize, } impl Host for Input { fn present(&mut self, _: &TextScreen) {} fn next_event(&mut self, block: bool) -> Option { if !block { return None; } self.reads += 1; Some(if self.reads == 1 { Ereignis::Signal(2) } else { Ereignis::Ende }) } fn jetzt_ms(&mut self) -> u64 { 0 } fn warten(&mut self, _: Option) -> Option { self.next_event(true) } } let mut results = vec![]; for statement in ["INPUT s$", "LINE INPUT s$", "s$ = INPUT$(1)"] { let src = format!("ON SIGNAL(2) GOSUB H\nSIGNAL(2) ON\n{statement}\nEND\nH:\nPRINT \"H\"\nEND"); let mut vm = Vm::new(tb_vm::compile_source("TEST", &src).unwrap()); let result = vm.run(&mut Input { reads: 0 }); println!( "{statement}: {result:?}; Ausgabe={:?}", tb_runtime::snapshot::text(&vm.rt.screen) ); results.push(result); } assert!(results.iter().all(|result| *result == RunEvent::Ended)); } #[test] fn review_objektargument_darf_gotfocus_nicht_vorziehen() { for action in [ "CALL Mark(Text1)", "n% = Text1.TabIndex\nCALL Mark(Text1)", "Text1.Text = \"neu\"\nCALL Mark(Text1)", "Text1.Refresh\nCALL Mark(Text1)", "LOAD Form1\nCALL Mark(Text1)", ] { let mut vm = vm(&format!("DIM SHARED s$\nText1.TabIndex = 0\nText2.TabIndex = 1\nForm1.Show\nText1.SetFocus\nSTOP\nDOEVENTS\nEND\nSUB Text1_LostFocus()\nSHARED s$\n{action}\ns$ = s$ + \"l\"\nEND SUB\nSUB Mark(c AS CONTROL)\nSHARED s$\ns$ = s$ + \"L\"\nEND SUB\nSUB Text2_GotFocus()\nSHARED s$\ns$ = s$ + \"G\"\ns$ = s$ + \"g\"\nEND SUB")); let mut host = CaptureHost::default(); stop(&mut vm, &mut host); host.ereignis(key("\t")); assert_eq!(vm.run(&mut host), RunEvent::Ended, "{action}"); assert_eq!(string(&vm, "s"), "LlGg", "{action}"); } } #[test] fn review_gleichzeitig_key_und_uevent_behalten_prioritaet() { let mut vm = vm("ON KEY(15) GOSUB K\nKEY 15, CHR$(0) + CHR$(20)\nKEY(15) ON\nON UEVENT GOSUB U\nUEVENT ON\nSTOP\nDOEVENTS\nEND\nK:\ns$ = s$ + \"K\"\nRETURN\nU:\ns$ = s$ + \"U\"\nRETURN"); let mut host = CaptureHost::default(); stop(&mut vm, &mut host); host.ereignis(key("t")); vm.rt.pump(&mut host, false); vm.rt.traps.melden(tb_runtime::traps::Quelle::UEvent); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(string(&vm, "s"), "KU"); } #[test] fn review_modale_eingabe_vor_eof_wird_zugestellt() { let mut vm = vm("DIM SHARED s$\nForm1.Show 1\nEND\nSUB Form_MouseDown(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)\nSHARED s$\ns$ = \"M\"\nUNLOAD Form1\nEND SUB"); let mut host = CaptureHost::default(); host.ereignis_nach(2, mouse()); host.ereignis_nach(2, Ereignis::Ende); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(string(&vm, "s"), "M"); } #[test] fn eof_leert_angenommene_eingaben_bei_modalen_und_modellosen_forms() { for modal in [false, true] { let show = if modal { "Form1.Show 1" } else { "Form1.Show" }; let mut vm = vm(&format!("DIM SHARED s$\n{show}\nEND\nSUB Form_MouseDown(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)\nSHARED s$\ns$ = s$ + \"M\"\nEND SUB")); let mut host = CaptureHost::default(); if !modal { assert_eq!(vm.run(&mut host), RunEvent::Ended); } host.ereignis_nach(2, mouse()); host.ereignis_nach(2, mouse()); host.ereignis_nach(2, Ereignis::Ende); let result = if modal { vm.run(&mut host) } else { vm.run_visible_forms(&mut host) }; assert_eq!(result, RunEvent::Ended, "modal={modal}"); assert_eq!(string(&vm, "s"), "MM", "modal={modal}"); } } #[test] fn review_menu_schliessen_stellt_pending_trap_vor_erneutem_warten_zu() { struct MenuHost { time: u64, waits: usize, } impl Host for MenuHost { fn present(&mut self, _: &TextScreen) {} fn next_event(&mut self, _: bool) -> Option { None } fn jetzt_ms(&mut self) -> u64 { self.time } fn warten(&mut self, deadline: Option) -> Option { self.waits += 1; if self.waits == 1 { return Some(key("\u{1b}")); } self.time = deadline.unwrap(); None } } let mut vm = vm("ON UEVENT GOSUB H\nUEVENT ON\nMenu1.Caption = \"&Datei\"\nItem1.Caption = \"&Ende\"\nForm1.Show\nSTOP\nSLEEP 60\nEND\nH:\ns$ = \"U\"\nRETURN"); let mut host = CaptureHost::default(); stop(&mut vm, &mut host); vm.forms.handle_key("d", 4); vm.rt.traps.melden(tb_runtime::traps::Quelle::UEvent); let mut host = MenuHost { time: 0, waits: 0 }; assert_eq!(vm.run(&mut host), RunEvent::Ended); println!( "Menü/SLEEP: Zeit={}, Warteaufrufe={}, Handler={}", host.time, host.waits, string(&vm, "s") ); assert_eq!(host.waits, 1); } #[test] fn review_maus_vor_getrappter_taste() { let mut vm = vm("DIM SHARED s$\nON KEY(15) GOSUB H\nKEY 15, CHR$(0) + CHR$(20)\nKEY(15) ON\nForm1.Show\nSTOP\nDOEVENTS\nDOEVENTS\nEND\nH:\ns$ = s$ + \"K\"\nRETURN\nSUB Form_MouseDown(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)\nSHARED s$\ns$ = s$ + \"M\"\nEND SUB"); let mut host = CaptureHost::default(); stop(&mut vm, &mut host); host.ereignis(mouse()); host.ereignis(key("t")); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(string(&vm, "s"), "MK"); } /// Ereignisse treffen erst beim Warten ein, auch mitten in einer Eingabe. struct EingabeHost { events: std::collections::VecDeque, now: u64, deadlines: Vec>, } impl Host for EingabeHost { fn present(&mut self, _: &TextScreen) {} fn next_event(&mut self, _: bool) -> Option { None } fn jetzt_ms(&mut self) -> u64 { self.now } fn warten(&mut self, deadline: Option) -> Option { self.deadlines.push(deadline); if let Some(deadline) = deadline { self.now = deadline; None } else { Some(self.events.pop_front().unwrap_or(Ereignis::Ende)) } } } fn eingabe_host(events: Vec) -> EingabeHost { EingabeHost { events: events.into(), now: 0, deadlines: vec![], } } #[test] fn input_fortsetzung_erhaelt_teiltext_ausdruck_und_byref_ziel() { for (statement, expected, enter) in [ ("INPUT s$", "ab", true), ("LINE INPUT s$", "ab", true), ("s$ = \"x\" + INPUT$(2) + \"y\"", "xaby", false), ] { let src = format!("ON SIGNAL(2) GOSUB H\nSIGNAL(2) ON\nCALL Lesen(s$)\nEND\nH:\nn% = n% + 1\nRETURN\nSUB Lesen(s$)\n{statement}\nEND SUB"); let mut vm = vm_for_source(&src); let mut events = vec![key("a"), Ereignis::Signal(2), key("b")]; if enter { events.push(key("\r")); } let mut host = eingabe_host(events); assert_eq!(vm.run(&mut host), RunEvent::Ended, "{statement}"); assert_eq!(string(&vm, "s"), expected); assert_eq!(integer(&vm, "n"), 1); assert_eq!(host.deadlines.len(), if enter { 4 } else { 3 }); } } #[test] fn handler_eingabe_und_stop_bewahren_die_aeussere_eingabe() { let mut vm = vm_for_source( "ON SIGNAL(2) GOSUB H\nSIGNAL(2) ON\nINPUT s$\nEND\nH:\nSTOP\nINPUT t$\nRETURN", ); let mut host = eingabe_host(vec![ key("a"), Ereignis::Signal(2), key("h"), key("\r"), key("b"), key("\r"), ]); stop(&mut vm, &mut host); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(string(&vm, "s"), "ab"); assert_eq!(string(&vm, "t"), "h"); } #[test] fn timer_waehrend_input_wird_vor_erneutem_warten_zugestellt() { let mut vm = vm_for_source( "ON TIMER(1) GOSUB H\nTIMER ON\nINPUT s$\nEND\nH:\nn% = n% + 1\nTIMER OFF\nRETURN", ); let mut host = eingabe_host(vec![key("a"), key("\r")]); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(string(&vm, "s"), "a"); assert_eq!(integer(&vm, "n"), 1); assert_eq!(host.deadlines, [Some(1000), None, None]); } #[test] fn input_redo_verliert_weder_referenzen_noch_teiltext_beim_trap() { let mut vm = vm_for_source("ON SIGNAL(2) GOSUB H\nSIGNAL(2) ON\nINPUT \"Zahl:\"; n%\nEND\nH:\nRETURN"); let mut host = eingabe_host(vec![ key("x"), key("\r"), key("4"), Ereignis::Signal(2), key("2"), key("\r"), ]); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(integer(&vm, "n"), 42); assert_eq!( tb_runtime::snapshot::text(&vm.rt.screen) .matches("Zahl:") .count(), 2 ); } #[test] fn lokaler_error_handler_nach_trap_verwirft_abgebrochene_teileingabe() { let mut vm = vm_for_source("ON SIGNAL(2) GOSUB H\nSIGNAL(2) ON\nCALL Lesen(s$)\nEND\nH:\nERROR 5\nRETURN\nSUB Lesen(s$)\nON LOCAL ERROR GOTO Fehler\nINPUT s$\nEXIT SUB\nFehler:\nINPUT s$\nRESUME NEXT\nEND SUB"); let mut host = eingabe_host(vec![key("a"), Ereignis::Signal(2), key("b"), key("\r")]); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(string(&vm, "s"), "b"); } #[test] fn randomize_eingabe_teilt_den_fortsetzbaren_konsolenpfad() { let mut vm = vm_for_source( "ON SIGNAL(2) GOSUB H\nSIGNAL(2) ON\nRANDOMIZE\nEND\nH:\nn% = n% + 1\nRETURN", ); let mut host = eingabe_host(vec![key("4"), Ereignis::Signal(2), key("2"), key("\r")]); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(integer(&vm, "n"), 1); assert_eq!( tb_runtime::snapshot::text(&vm.rt.screen) .matches("Random Number Seed") .count(), 1 ); } #[test] fn return_label_verwirft_abgebrochene_eingabe_und_ausdruck() { for statement in ["INPUT s$", "s$ = \"alt\" + INPUT$(2)"] { let mut vm = vm_for_source(&format!("ON SIGNAL(2) GOSUB H\nSIGNAL(2) ON\n{statement}\nEND\nWeiter:\nINPUT t$\nEND\nH:\nRETURN Weiter")); let mut host = eingabe_host(vec![key("a"), Ereignis::Signal(2), key("b"), key("\r")]); assert_eq!(vm.run(&mut host), RunEvent::Ended, "{statement}"); assert_eq!(string(&vm, "s"), ""); assert_eq!(string(&vm, "t"), "b"); } } #[test] fn spaete_timeraktivierung_nutzt_hostzeit_auch_ueber_control_parameter() { for (setup, start) in [ ("", "Timer1.Interval = 100"), ("", "CALL Starten(Timer1)"), ( "Timer1.Enabled = 0\nTimer1.Interval = 100", "Timer1.Enabled = -1", ), ] { let mut vm = vm(&format!("DIM SHARED n%\nForm1.Show\n{setup}\nSTOP\n{start}\nSTOP\nDOEVENTS\nSTOP\nDOEVENTS\nEND\nSUB Starten(t AS CONTROL)\nt.Interval = 100\nEND SUB\nSUB Timer1_Timer()\nSHARED n%\nn% = n% + 1\nEND SUB")); let mut host = CaptureHost::default(); stop(&mut vm, &mut host); host.uhr_vorruecken(1000); stop(&mut vm, &mut host); assert_eq!(vm.forms.next_deadline(), Some(1100)); host.uhr_vorruecken(99); stop(&mut vm, &mut host); assert_eq!(integer(&vm, "n"), 0); host.uhr_vorruecken(1); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(integer(&vm, "n"), 1); } } #[test] fn intervallaenderung_im_handler_verwirft_bereits_faellige_altintervalle() { let mut vm = vm("DIM SHARED n%\nForm1.Show\nTimer1.Interval = 100\nSLEEP 60\nSTOP\nDOEVENTS\nSTOP\nDOEVENTS\nEND\nSUB Timer1_Timer()\nSHARED n%\nn% = n% + 1\nIF n% = 1 THEN\nSTOP\nTimer1.Interval = 200\nELSE\nTimer1.Interval = 0\nEND IF\nEND SUB"); let mut host = CaptureHost::default(); stop(&mut vm, &mut host); // Im ersten Handler bei 100 ms. assert_eq!(host.jetzt_ms(), 100); host.uhr_vorruecken(350); stop(&mut vm, &mut host); // Alter Rückstau darf nach dem Handler nicht laufen. assert_eq!(integer(&vm, "n"), 1); assert_eq!(vm.forms.next_deadline(), Some(650)); host.uhr_vorruecken(199); stop(&mut vm, &mut host); assert_eq!(integer(&vm, "n"), 1); host.uhr_vorruecken(1); assert_eq!(vm.run(&mut host), RunEvent::Ended); assert_eq!(integer(&vm, "n"), 2); assert_eq!(vm.forms.next_deadline(), None); }