use fusevm::chunk::ChunkBuilder;
use fusevm::op::Op;
use fusevm::value::Value;
use fusevm::vm::VM;
#[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:?}"
);
let v2 = vm.pop();
assert_eq!(
v2,
Value::Undef,
"repeated pop on empty stack stays safe and returns Undef; got {v2:?}"
);
}
#[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:?}"
);
}
#[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");
assert_eq!(vm.pop(), Value::Undef, "stack must be empty (Undef)");
}
#[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));
assert_eq!(*vm.peek(), Value::Int(9));
assert_eq!(*vm.peek(), Value::Int(9));
assert_eq!(*vm.peek(), Value::Int(9));
assert_eq!(vm.pop(), Value::Int(9));
assert_eq!(vm.pop(), Value::Int(8));
assert_eq!(vm.pop(), Value::Int(7));
}
#[test]
fn test_vm_request_halt_is_callable_on_fresh_vm() {
let mut vm = VM::new(ChunkBuilder::new().build());
vm.request_halt();
vm.request_halt();
vm.request_halt();
}
#[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"
);
}
#[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")); 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");
}
#[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"
);
}