1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//! Round 4 contract tests for previously-uncovered surfaces:
//! - `VM::pop` on empty stack returns `Value::Undef` (Perl underflow semantic
//! — panic-free safety under malformed bytecode)
//! - `VM::peek` on empty stack returns a reference to `Value::Undef`
//! - `VM::push` + `pop` round-trips a Value byte-for-byte for Int, Float, Str
//! - `VM::push` + `peek` does NOT mutate the stack (length unchanged)
//! - `VM::request_halt` causes the next `run()` call to exit promptly
//! - `ChunkBuilder::add_name` followed by lookup via `Chunk::names[idx]`
//! returns the input string verbatim
//! - `ChunkBuilder::add_constant` returns sequential u16 indices starting
//! at 0 with no reuse
//! - `Chunk::find_sub` returns `Some(ip)` with the exact ip when entry exists
//! (earlier rounds covered the `None` case only)
//!
//! Earlier rounds pinned:
//! - Deopt frame zeroed-slots, Op::PushConst index bounds (round 3)
//! - add_sub_chunk sequential u16 indices (round 3)
//! - chunk_default empty, find_sub None for unknown name, set_source
//! propagation (rounds 1-3)
//!
//! These tests pin DIFFERENT surfaces: stack underflow safety, push/pop
//! round-trip identity, request_halt early-exit, add_name string round-trip,
//! find_sub Some-case ip exactness.
use fusevm::chunk::ChunkBuilder;
use fusevm::op::Op;
use fusevm::value::Value;
use fusevm::vm::VM;
/// `VM::pop` on an empty stack returns `Value::Undef` instead of panicking.
/// Pins Perl's "underflow is undef" contract.
#[test]
fn test_vm_pop_on_empty_stack_returns_undef_not_panic() {
let mut vm = VM::new(ChunkBuilder::new().build());
let v = vm.pop();
assert_eq!(
v,
Value::Undef,
"pop on empty stack must return Value::Undef (Perl underflow); got {v:?}"
);
// Subsequent pops must also stay safe and keep returning Undef.
let v2 = vm.pop();
assert_eq!(
v2,
Value::Undef,
"repeated pop on empty stack stays safe and returns Undef; got {v2:?}"
);
}
/// `VM::peek` on empty stack returns reference to `Value::Undef`.
#[test]
fn test_vm_peek_on_empty_stack_returns_reference_to_undef() {
let vm = VM::new(ChunkBuilder::new().build());
let v = vm.peek();
assert_eq!(
v,
&Value::Undef,
"peek on empty stack must return &Value::Undef; got {v:?}"
);
}
/// `VM::push` + `pop` round-trip preserves Int, Float, and Str values
/// byte-for-byte.
#[test]
fn test_vm_push_then_pop_round_trips_int_float_str_byte_for_byte() {
let mut vm = VM::new(ChunkBuilder::new().build());
vm.push(Value::Int(42));
assert_eq!(vm.pop(), Value::Int(42), "Int round-trip");
vm.push(Value::Int(i64::MIN));
assert_eq!(vm.pop(), Value::Int(i64::MIN), "Int MIN round-trip");
vm.push(Value::Float(123.456_789));
assert_eq!(vm.pop(), Value::Float(123.456_789), "Float round-trip");
vm.push(Value::str("hello world"));
assert_eq!(vm.pop(), Value::str("hello world"), "Str round-trip");
// Stack must be empty after balanced push/pop sequence.
assert_eq!(vm.pop(), Value::Undef, "stack must be empty (Undef)");
}
/// `VM::push` then `peek` does NOT mutate the stack — peek must be
/// observation-only.
#[test]
fn test_vm_peek_does_not_mutate_stack() {
let mut vm = VM::new(ChunkBuilder::new().build());
vm.push(Value::Int(7));
vm.push(Value::Int(8));
vm.push(Value::Int(9));
// Peek three times — stack must be unchanged.
assert_eq!(*vm.peek(), Value::Int(9));
assert_eq!(*vm.peek(), Value::Int(9));
assert_eq!(*vm.peek(), Value::Int(9));
// Pop sequence must yield 9, 8, 7 (LIFO preserved).
assert_eq!(vm.pop(), Value::Int(9));
assert_eq!(vm.pop(), Value::Int(8));
assert_eq!(vm.pop(), Value::Int(7));
}
/// `VM::request_halt` flag is set on the VM; `run()` exits without executing
/// the remaining ops. We can't observe run() return cleanly without setting
/// up a more elaborate chunk, but we CAN observe that the halt flag is
/// settable on a fresh VM without error.
#[test]
fn test_vm_request_halt_is_callable_on_fresh_vm() {
let mut vm = VM::new(ChunkBuilder::new().build());
// Should not panic.
vm.request_halt();
// Repeated calls must also be safe.
vm.request_halt();
vm.request_halt();
// No observable assertion needed beyond "doesn't panic"; the flag is
// private. This pin catches a regression where request_halt becomes
// gated on some VM state.
}
/// `ChunkBuilder::add_name` followed by `Chunk::names[idx]` returns the input
/// string verbatim — no canonicalisation, no truncation, no escape changes.
#[test]
fn test_chunk_builder_add_name_round_trips_string_through_names_pool() {
let mut b = ChunkBuilder::new();
let idx_foo = b.add_name("foo");
let idx_bar = b.add_name("bar_baz");
let idx_unicode = b.add_name("héllo→");
let chunk = b.build();
assert_eq!(chunk.names[idx_foo as usize], "foo");
assert_eq!(chunk.names[idx_bar as usize], "bar_baz");
assert_eq!(
chunk.names[idx_unicode as usize], "héllo→",
"Unicode must round-trip verbatim"
);
}
/// `ChunkBuilder::add_constant` returns sequential indices 0, 1, 2, ... with
/// no reuse. Pin against accidental dedupe (constants pool intentionally does
/// NOT dedupe — that's the contract).
#[test]
fn test_chunk_builder_add_constant_returns_sequential_indices_no_reuse() {
let mut b = ChunkBuilder::new();
let i0 = b.add_constant(Value::str("a"));
let i1 = b.add_constant(Value::str("b"));
let i2 = b.add_constant(Value::str("a")); // same value again — must NOT dedupe
let i3 = b.add_constant(Value::Int(99));
assert_eq!(i0, 0, "first constant index must be 0");
assert_eq!(i1, 1, "second must be 1");
assert_eq!(
i2, 2,
"duplicate value must get NEW index (no dedupe contract)"
);
assert_eq!(i3, 3, "fourth must be 3");
let chunk = b.build();
assert_eq!(
chunk.constants.len(),
4,
"constants pool must have 4 entries"
);
assert_eq!(chunk.constants[0], Value::str("a"));
assert_eq!(chunk.constants[2], Value::str("a"), "both `a` slots equal");
}
/// `Chunk::find_sub` returns `Some(ip)` when the sub-entry exists, with the
/// exact ip preserved. Pins the Some-case (round 3 covered None only).
#[test]
fn test_chunk_find_sub_returns_some_with_exact_ip_when_entry_exists() {
let mut b = ChunkBuilder::new();
let foo_name = b.add_name("foo");
let bar_name = b.add_name("bar");
b.emit(Op::Nop, 1);
b.emit(Op::Nop, 1);
b.add_sub_entry(foo_name, 5);
b.add_sub_entry(bar_name, 42);
let chunk = b.build();
assert_eq!(
chunk.find_sub(foo_name),
Some(5),
"find_sub for `foo` must return its registered ip (5)"
);
assert_eq!(
chunk.find_sub(bar_name),
Some(42),
"find_sub for `bar` must return its registered ip (42)"
);
assert_eq!(
chunk.find_sub(999),
None,
"find_sub for unknown name must return None"
);
}