Skip to content

Commit d02e75c

Browse files
test(vm): Expose nested run() host-yield state-loss bug.
1 parent 1a7a597 commit d02e75c

2 files changed

Lines changed: 26 additions & 7 deletions

File tree

compiler/tests/cases/vm.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1688,5 +1688,6 @@
16881688
{"src": "def setup(label):\n sleep(0)\n return f'ready:{label}'\nprint(setup('x'))\nprint(setup('y'))", "output": ["ready:x", "ready:y"]},
16891689
{"src": "def grab():\n return receive()\nprint(grab())\nprint(grab())", "events": ["a", "b"], "output": ["a", "b"]},
16901690
{"src": "for evt in (receive(), receive()):\n print(evt)", "events": ["one", "two"], "output": ["one", "two"]},
1691-
{"src": "evt = receive()\nasync def main():\n print(f'main:{receive()}')\nprint(f'top:{evt}')\nrun(main())", "events": ["a", "b"], "output": ["top:a", "main:b"]}
1691+
{"src": "evt = receive()\nasync def main():\n print(f'main:{receive()}')\nprint(f'top:{evt}')\nrun(main())", "events": ["a", "b"], "output": ["top:a", "main:b"]},
1692+
{"src": "print('ready')\nasync def main():\n for _ in range(2):\n print(receive())\nrun(main())\nprint('done')", "interactive_events": ["a", "b"], "output": ["ready", "a", "b", "done"]}
16921693
]

compiler/tests/vm.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod test {
44
use compiler_lib::modules::lexer::lex;
55
use compiler_lib::modules::parser::Parser;
66
use compiler_lib::modules::vm::VM;
7+
use compiler_lib::modules::vm::types::{SchedulerStatus, VmErr};
78

89
#[derive(serde::Deserialize)]
910
struct Case {
@@ -15,6 +16,25 @@ mod test {
1516
input: Vec<String>,
1617
#[serde(default)]
1718
events: Vec<String>,
19+
// Events pushed one-at-a-time after each PendingEvent yield (host-resume path).
20+
#[serde(default)]
21+
interactive_events: Vec<String>,
22+
}
23+
24+
// Resume on each PendingEvent by pushing the next interactive_events entry.
25+
fn drive(vm: &mut VM, interactive: &[String]) -> Result<(), VmErr> {
26+
let mut idx = 0;
27+
loop {
28+
match vm.run() {
29+
Ok(_) => return Ok(()),
30+
Err(VmErr::HostYield(SchedulerStatus::PendingEvent)) => {
31+
if idx >= interactive.len() { return Ok(()); }
32+
vm.push_event(&interactive[idx]).expect("push_event");
33+
idx += 1;
34+
}
35+
Err(e) => return Err(e),
36+
}
37+
}
1838
}
1939

2040
#[test]
@@ -34,7 +54,7 @@ mod test {
3454
let mut vm = VM::new(&chunk);
3555
vm.input_buffer = case.input.clone();
3656
for evt in &case.events { vm.push_event(evt).expect("push_event"); }
37-
let result = vm.run();
57+
let result = drive(&mut vm, &case.interactive_events);
3858

3959
match result {
4060
Ok(_obj) => { assert_eq!(vm.output, case.output, "output mismatch on: {:?}", case.src); }
@@ -46,8 +66,7 @@ mod test {
4666
}
4767
}
4868

49-
/* Reruns every vm.json case in strict_input mode (host-supplied buffer; reading past = RuntimeError).
50-
Lex/parse errors are also asserted here. */
69+
/* Reruns every vm.json case in strict_input mode (host-supplied buffer; reading past = RuntimeError). Lex/parse errors are also asserted here. */
5170
#[test]
5271
fn strict_cases() {
5372
let cases: Vec<Case> = serde_json::from_str(include_str!("cases/vm.json")).expect("invalid JSON");
@@ -89,10 +108,9 @@ mod test {
89108
vm.strict_input = true;
90109
vm.input_buffer = case.input.clone();
91110
for evt in &case.events { vm.push_event(evt).expect("push_event"); }
92-
let expects_input_error = case.input.is_empty()
93-
&& (case.src.contains("input(") || case.src.contains("input ("));
111+
let expects_input_error = case.input.is_empty() && (case.src.contains("input(") || case.src.contains("input ("));
94112

95-
match vm.run() {
113+
match drive(&mut vm, &case.interactive_events) {
96114
Ok(_) => {
97115
assert!(!expects_input_error, "expected input() to error under strict mode for: {:?}", case.src);
98116
assert_eq!(vm.output, case.output, "output mismatch on: {:?}", case.src);

0 commit comments

Comments
 (0)