Phase 5: Ausführung und Output implementieren und archivieren
This commit is contained in:
252
crates/tb-vm/tests/cooperative.rs
Normal file
252
crates/tb-vm/tests/cooperative.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
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))));
|
||||
}
|
||||
Reference in New Issue
Block a user