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

253 lines
8.1 KiB
Rust

use std::collections::VecDeque;
use tb_runtime::{
host::{Ereignis, Host},
screen::TextScreen,
snapshot,
value::Value,
};
use tb_vm::interp::{PollResult, RunEvent, Vm};
#[derive(Default)]
struct HostStub {
now: u64,
clocks: usize,
input: VecDeque<Ereignis>,
}
impl Host for HostStub {
fn present(&mut self, _: &TextScreen) {}
fn next_event(&mut self, blocking: bool) -> Option<Ereignis> {
assert!(!blocking);
self.input.pop_front()
}
fn warten(&mut self, _: Option<u64>) -> Option<Ereignis> {
panic!("poll blocked")
}
fn jetzt_ms(&mut self) -> u64 {
self.clocks += 1;
self.now
}
}
fn vm(source: &str) -> Vm {
Vm::new(tb_vm::compile_source("TEST", source).unwrap())
}
fn text(vm: &Vm, name: &str) -> String {
match vm.inspect(name) {
Some(Value::Str(s)) => s.to_string(),
v => panic!("{v:?}"),
}
}
fn input(host: &mut HostStub, chars: &str) {
host.input
.extend(chars.chars().map(|c| Ereignis::Taste(c.to_string(), 0)));
}
fn poll(vm: &mut Vm, host: &mut HostStub) -> PollResult {
vm.poll(host, 200)
}
fn end(vm: &mut Vm, host: &mut HostStub) {
for _ in 0..1000 {
match poll(vm, host) {
PollResult::Event(RunEvent::Ended) => return,
PollResult::Yield | PollResult::Waiting { .. } => {}
other => panic!("{other:?}"),
}
}
panic!("did not end");
}
fn pause(vm: &mut Vm, host: &mut HostStub) {
vm.rt.abbruch = true;
assert!(matches!(
poll(vm, host),
PollResult::Event(RunEvent::Interrupted { .. })
));
vm.rt.abbruch = false;
}
#[test]
fn finite_slices_preserve_events_without_clock_queries() {
for source in ["DO\nLOOP", "10 GOTO 10"] {
let mut vm = vm(source);
vm.set_poll_interrupt(true);
let mut host = HostStub::default();
for _ in 0..10 {
assert_eq!(poll(&mut vm, &mut host), PollResult::Yield);
}
assert_eq!(host.clocks, 0);
pause(&mut vm, &mut host);
}
let mut vm = vm("STOP\nPRINT 42\nEND");
let mut host = HostStub::default();
assert!(matches!(
poll(&mut vm, &mut host),
PollResult::Event(RunEvent::Stopped { .. })
));
end(&mut vm, &mut host);
assert!(snapshot::text(&vm.rt.screen).contains("42"));
assert_eq!(host.clocks, 0);
}
#[test]
fn partial_console_operations_keep_prompt_arguments_and_assignment() {
for instruction in [
"INPUT \"Name\"; s$",
"LINE INPUT \"Name\"; s$",
"s$=INPUT$(3)",
] {
let mut vm = vm(&format!("{instruction}\nn%=n%+1\nEND"));
let mut host = HostStub::default();
input(&mut host, "a");
assert!(matches!(
poll(&mut vm, &mut host),
PollResult::Waiting { .. }
));
let before = snapshot::text(&vm.rt.screen);
pause(&mut vm, &mut host);
assert_eq!(snapshot::text(&vm.rt.screen), before);
input(
&mut host,
if instruction.contains("INPUT$(") {
"bc"
} else {
"bc\r"
},
);
end(&mut vm, &mut host);
assert_eq!(text(&vm, "s"), "abc");
assert!(matches!(vm.inspect("n"), Some(Value::Int(1))));
assert!(snapshot::text(&vm.rt.screen).matches("Name").count() <= 1);
}
let mut vm = vm("RANDOMIZE\nPRINT RND\nEND");
let mut host = HostStub::default();
input(&mut host, "12");
assert!(matches!(
poll(&mut vm, &mut host),
PollResult::Waiting { .. }
));
pause(&mut vm, &mut host);
input(&mut host, "3\r");
end(&mut vm, &mut host);
assert_eq!(
snapshot::text(&vm.rt.screen)
.matches("Random Number Seed")
.count(),
1
);
}
#[test]
fn sleep_deadline_survives_pause_and_does_not_pop_argument_twice() {
let mut vm = vm("SLEEP 2\nn%=n%+1\nEND");
let mut host = HostStub::default();
assert_eq!(
poll(&mut vm, &mut host),
PollResult::Waiting {
deadline: Some(2000)
}
);
host.now = 1000;
pause(&mut vm, &mut host);
assert_eq!(
poll(&mut vm, &mut host),
PollResult::Waiting {
deadline: Some(2000)
}
);
host.now = 2000;
end(&mut vm, &mut host);
assert!(matches!(vm.inspect("n"), Some(Value::Int(1))));
}
#[test]
fn dialogs_keep_partial_text_focus_and_caller_arguments() {
for (source, chars) in [
("s$=INPUTBOX$(\"Prompt\",\"Title\")\nEND", "ab"),
("n%=MSGBOX(\"Prompt\",256+1,\"Title\")\nEND", ""),
] {
let mut vm = vm(source);
let mut host = HostStub::default();
input(&mut host, chars);
for _ in 0..5 {
assert!(matches!(
poll(&mut vm, &mut host),
PollResult::Waiting { .. }
));
}
pause(&mut vm, &mut host);
input(&mut host, "\r");
end(&mut vm, &mut host);
if chars.is_empty() {
assert!(matches!(vm.inspect("n"), Some(Value::Int(2))));
} else {
assert_eq!(text(&vm, "s"), "ab");
}
}
}
#[test]
fn modal_and_modeless_forms_resume_handlers_across_tiny_budgets() {
use tb_frontend::forms::{FormCatalog, ObjectClass};
let mut catalog = FormCatalog::default();
catalog.add("Form1", ObjectClass::Form, None, false);
catalog.add("Timer1", ObjectClass::Timer, Some("Form1"), false);
for modal in [false, true] {
let source = format!("Timer1.Interval=100\nTimer1.Enabled=-1\nForm1.Show {}\n{}\nSUB Timer1_Timer\nFOR i%=1 TO 100\nNEXT\nForm1.Hide\nEND SUB", if modal {"1"} else {"0"}, if modal {"END"} else {""});
let mut vm = Vm::new(tb_vm::compile_source_with_forms("FORM1", &source, &catalog).unwrap());
let mut host = HostStub::default();
let result = poll(&mut vm, &mut host);
if modal {
assert!(matches!(result, PollResult::Waiting { .. }));
} else {
assert_eq!(result, PollResult::Event(RunEvent::Ended));
}
pause(&mut vm, &mut host);
host.now = 100;
for _ in 0..1000 {
let result = if modal {
vm.poll(&mut host, 3)
} else {
vm.poll_visible_forms(&mut host, 3)
};
if result == PollResult::Event(RunEvent::Ended) && !vm.forms.has_visible_forms() {
break;
}
}
assert!(!vm.forms.has_visible_forms());
}
}
#[test]
fn pending_dialog_keeps_physical_resize_and_modeless_handler_finishes_after_hide() {
let mut vm = vm("s$=INPUTBOX$(\"Prompt\")\nEND");
let mut host = HostStub::default();
assert!(matches!(
poll(&mut vm, &mut host),
PollResult::Waiting { .. }
));
vm.rt.ereignis(Ereignis::Groesse {
cols: 120,
rows: 40,
});
input(&mut host, "\r");
end(&mut vm, &mut host);
assert_eq!((vm.rt.screen.cols(), vm.rt.screen.rows()), (120, 40));
use tb_frontend::forms::{FormCatalog, ObjectClass};
let mut catalog = FormCatalog::default();
catalog.add("Form1", ObjectClass::Form, None, false);
let module=tb_vm::compile_source_with_forms("FORM1","Form1.Show\nSUB Form_KeyPress(KeyAscii AS INTEGER)\nForm1.Hide\nPRINT \"after hide\"\nEND SUB",&catalog).unwrap();
let mut vm = Vm::new(module);
assert_eq!(poll(&mut vm, &mut host), PollResult::Event(RunEvent::Ended));
input(&mut host, "a");
for _ in 0..100 {
if vm.poll_visible_forms(&mut host, 1) == PollResult::Event(RunEvent::Ended) {
break;
}
}
assert!(snapshot::text(&vm.rt.screen).contains("after hide"));
}
#[test]
fn common_dialog_external_call_keeps_byref_arguments_until_completion() {
let mut vm=vm("DECLARE SUB FindText (s AS STRING, flags AS INTEGER, cancelled AS INTEGER)\ns$=\"before\"\nFindText s$, flags%, cancelled%\nEND");
let mut host = HostStub::default();
assert!(matches!(
poll(&mut vm, &mut host),
PollResult::Waiting { .. }
));
pause(&mut vm, &mut host);
input(&mut host, "\r");
end(&mut vm, &mut host);
assert_eq!(text(&vm, "s"), "before");
assert!(matches!(vm.inspect("cancelled"), Some(Value::Int(0))));
}