Files
TerminalBasic/crates/tb-vm/tests/events.rs

585 lines
22 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Regressionen zum Change ereigniszustellung-und-hostgrenzen.
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<Ereignis> {
block.then(|| key("\r"))
}
fn jetzt_ms(&mut self) -> u64 {
self.0.jetzt_ms()
}
fn warten(&mut self, deadline: Option<u64>) -> Option<Ereignis> {
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<Ereignis> {
self.0.next_event(b)
}
fn jetzt_ms(&mut self) -> u64 {
panic!("unerwartete Uhrabfrage")
}
fn warten(&mut self, _: Option<u64>) -> Option<Ereignis> {
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<Option<u64>>,
timer: bool,
}
impl Host for WarteHost {
fn present(&mut self, _: &TextScreen) {}
fn next_event(&mut self, _: bool) -> Option<Ereignis> {
None
}
fn jetzt_ms(&mut self) -> u64 {
self.now
}
fn warten(&mut self, deadline: Option<u64>) -> Option<Ereignis> {
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 V1V5: 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<Ereignis> {
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<u64>) -> Option<Ereignis> {
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<Ereignis> {
None
}
fn jetzt_ms(&mut self) -> u64 {
self.time
}
fn warten(&mut self, deadline: Option<u64>) -> Option<Ereignis> {
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<Ereignis>,
now: u64,
deadlines: Vec<Option<u64>>,
}
impl Host for EingabeHost {
fn present(&mut self, _: &TextScreen) {}
fn next_event(&mut self, _: bool) -> Option<Ereignis> {
None
}
fn jetzt_ms(&mut self) -> u64 {
self.now
}
fn warten(&mut self, deadline: Option<u64>) -> Option<Ereignis> {
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<Ereignis>) -> 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");
}
}