Ereigniszustellung und Hostgrenzen korrigieren und Change archivieren
This commit is contained in:
584
crates/tb-vm/tests/events.rs
Normal file
584
crates/tb-vm/tests/events.rs
Normal file
@@ -0,0 +1,584 @@
|
||||
//! 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 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<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");
|
||||
}
|
||||
}
|
||||
@@ -859,6 +859,12 @@ fn run_mit_uhr(src: &str, schritt_ms: u64) -> (RunEvent, String) {
|
||||
fn next_event(&mut self, b: bool) -> Option<tb_runtime::host::Ereignis> {
|
||||
self.inner.next_event(b)
|
||||
}
|
||||
fn warten(&mut self, deadline_ms: Option<u64>) -> Option<tb_runtime::host::Ereignis> {
|
||||
if let Some(event) = self.next_event(false) {
|
||||
return Some(event);
|
||||
}
|
||||
self.inner.warten(deadline_ms)
|
||||
}
|
||||
fn jetzt_ms(&mut self) -> u64 {
|
||||
let j = self.inner.jetzt_ms();
|
||||
self.inner.uhr_vorruecken(self.schritt);
|
||||
|
||||
Reference in New Issue
Block a user