253 lines
8.1 KiB
Rust
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))));
|
|
}
|