extern crate mrubyedge;
use super::helpers::*;
#[test]
fn test_rite_parse_hello_world() {
let code = r#"
puts "Hello, World!"
"#;
let binary = mrbc_compile("hello", code);
let rite = mrubyedge::rite::load(&binary).unwrap();
assert_eq!(&rite.binary_header.ident, b"RITE");
assert_eq!(&rite.binary_header.major_version, b"04");
assert_eq!(&rite.binary_header.minor_version, b"00");
assert_eq!(rite.irep.len(), 1);
let irep = &rite.irep[0];
assert!(!irep.insn.is_empty());
assert!(irep.plen > 0);
}
#[test]
fn test_rite_parse_with_local_variables() {
let code = r#"
def greet(name)
message = "Hello, #{name}!"
puts message
message
end
greet("Alice")
"#;
let binary = mrbc_compile("greet", code);
let rite = mrubyedge::rite::load(&binary).unwrap();
assert_eq!(&rite.binary_header.ident, b"RITE");
assert!(rite.irep.len() == 2);
assert!(rite.irep[0].lv.is_empty());
assert!(rite.irep[1].lv.len() == 3);
let lvar = rite.lvar.unwrap();
assert!(lvar.syms.len() == 2);
let name = rite.irep[1].lv[0].as_ref().cloned().unwrap();
assert_eq!(&name.to_string_lossy(), "name");
let message = rite.irep[1].lv[2].as_ref().cloned().unwrap();
assert_eq!(&message.to_string_lossy(), "message");
}
#[test]
fn test_rite_parse_pool_values() {
let code = r#"
a = "string"
b = 42
c = 3.14
d = 9999999999
[a, b, c, d]
"#;
let binary = mrbc_compile("pool", code);
let rite = mrubyedge::rite::load(&binary).unwrap();
assert_eq!(&rite.binary_header.ident, b"RITE");
let irep = &rite.irep[0];
assert!(rite.lvar.unwrap().syms.len() == 4);
assert!(irep.pool.len() == 3);
assert!(irep.lv.len() == 4);
use mrubyedge::rite::PoolValue;
let has_string = irep
.pool
.iter()
.any(|p| matches!(p, PoolValue::Str(_) | PoolValue::SStr(_)));
let has_float = irep.pool.iter().any(|p| matches!(p, PoolValue::Float(_)));
let has_int64 = irep.pool.iter().any(|p| matches!(p, PoolValue::Int64(_)));
assert!(has_string, "Should have string in pool");
assert!(has_float, "Should have float in pool");
assert!(has_int64, "Should have int64 in pool");
}
#[test]
fn test_rite_parse_bigint_pool_value() {
let code = r#"
x = 99999999999999999999999
x
"#;
let binary = mrbc_compile("bigint", code);
let rite = mrubyedge::rite::load(&binary).unwrap();
use mrubyedge::rite::PoolValue;
let bigint = rite.irep[0]
.pool
.iter()
.find_map(|p| match p {
PoolValue::BigInt(bytes) => Some(bytes),
_ => None,
})
.expect("Should have a BigInt in pool");
assert_eq!(bigint.len(), 24);
assert_eq!(bigint[0], 10);
assert!(bigint[1..].iter().all(|&b| b == b'9'));
}
#[test]
fn test_rite_truncated_bigint_is_error_not_panic() {
let code = r#"
x = 99999999999999999999999
"#;
let binary = mrbc_compile("bigint", code);
let digits = [b'9'; 23];
let digits_start = binary
.windows(digits.len())
.position(|w| w == digits)
.expect("Should find the bigint digits");
let len_pos = digits_start - 2;
let mut chunk = binary.clone();
chunk[len_pos] = 0xff;
assert!(mrubyedge::rite::load(&chunk).is_err());
}
#[test]
fn a_chunk_of_an_unknown_format_version_is_refused_test() {
let code = r#"
1 + 1
"#;
let binary = mrbc_compile("compiled", code);
for major in [b"02", b"03", b"05"] {
let mut chunk = binary.clone();
chunk[4..6].copy_from_slice(major);
let err = mrubyedge::rite::load(&chunk).unwrap_err();
assert!(matches!(err, mrubyedge::rite::Error::UnsupportedVersion(m) if &m == major));
}
}
#[test]
fn dbg_lines_from_ruby_source_are_retained_test() {
let code = r#"
def answer
x = 40
x + 2
end
answer
"#;
let binary = mrbc_compile_with_debug(code);
let rite = mrubyedge::rite::load(&binary).unwrap();
assert_eq!(rite.irep.len(), 2);
assert_eq!(rite.irep[0].lines, vec![(0, 2), (4, 6)]);
assert_eq!(rite.irep[1].lines, vec![(0, 2), (4, 3), (7, 4)]);
use mrubyedge::rite::line_at;
assert_eq!(line_at(&rite.irep[0].lines, 4), Some(6)); assert_eq!(line_at(&rite.irep[1].lines, 4), Some(3)); assert_eq!(line_at(&rite.irep[1].lines, 6), Some(3));
assert_eq!(line_at(&rite.irep[1].lines, 7), Some(4)); }
fn splice_before_end(mut chunk: Vec<u8>, section: &[u8]) -> Vec<u8> {
let end_at = chunk.len() - 8; let total = u32::from_be_bytes(chunk[8..12].try_into().unwrap()) + section.len() as u32;
chunk[8..12].copy_from_slice(&total.to_be_bytes());
let mut out = chunk[..end_at].to_vec();
out.extend_from_slice(section);
out.extend_from_slice(&chunk[end_at..]);
out
}
fn dbg_section(line_type: u8, entry_count: u32, payload: &[u8]) -> Vec<u8> {
let mut record = Vec::new();
record.extend_from_slice(&1u16.to_be_bytes()); record.extend_from_slice(&0u32.to_be_bytes()); record.extend_from_slice(&0u16.to_be_bytes()); record.extend_from_slice(&entry_count.to_be_bytes());
record.push(line_type);
record.extend_from_slice(payload);
let mut body = Vec::new();
body.extend_from_slice(&1u16.to_be_bytes()); body.extend_from_slice(&4u16.to_be_bytes());
body.extend_from_slice(b"a.rb");
body.extend_from_slice(&((record.len() as u32 + 4).to_be_bytes()));
body.extend_from_slice(&record);
let mut section = Vec::new();
section.extend_from_slice(b"DBG\0");
section.extend_from_slice(&((8 + body.len()) as u32).to_be_bytes());
section.extend_from_slice(&body);
section
}
fn compiled_two_line_snippet() -> Vec<u8> {
let code = r#"
x = 1
y = 2
"#;
mrbc_compile("dbg_snip", code)
}
#[test]
fn dbg_flat_map_lines_are_parsed_test() {
let mut payload = Vec::new();
payload.extend_from_slice(&0u32.to_be_bytes());
payload.extend_from_slice(&1u16.to_be_bytes());
payload.extend_from_slice(&2u32.to_be_bytes());
payload.extend_from_slice(&2u16.to_be_bytes());
let chunk = splice_before_end(compiled_two_line_snippet(), &dbg_section(1, 2, &payload));
let rite = mrubyedge::rite::load(&chunk).unwrap();
assert_eq!(rite.irep[0].lines, vec![(0, 1), (2, 2)]);
use mrubyedge::rite::line_at;
assert_eq!(line_at(&rite.irep[0].lines, 0), Some(1));
assert_eq!(line_at(&rite.irep[0].lines, 1), Some(1));
assert_eq!(line_at(&rite.irep[0].lines, 2), Some(2));
assert_eq!(line_at(&rite.irep[0].lines, 99), Some(2));
}
#[test]
fn dbg_line_ary_lines_are_parsed_test() {
let payload = [1u16, 1, 2]
.iter()
.flat_map(|l| l.to_be_bytes())
.collect::<Vec<u8>>();
let chunk = splice_before_end(compiled_two_line_snippet(), &dbg_section(0, 3, &payload));
let rite = mrubyedge::rite::load(&chunk).unwrap();
assert_eq!(rite.irep[0].lines, vec![(0, 1), (2, 2)]);
}
#[test]
fn dbg_packed_map_lines_are_parsed_test() {
let payload = [0x00u8, 0x01, 0x02, 0x01];
let chunk = splice_before_end(
compiled_two_line_snippet(),
&dbg_section(2, payload.len() as u32, &payload),
);
let rite = mrubyedge::rite::load(&chunk).unwrap();
assert_eq!(rite.irep[0].lines, vec![(0, 1), (2, 2)]);
}
#[test]
fn dbg_record_size_mismatch_is_rejected_test() {
let mut payload = Vec::new();
payload.extend_from_slice(&0u32.to_be_bytes());
payload.extend_from_slice(&1u16.to_be_bytes());
let mut section = dbg_section(1, 1, &payload);
section[16] = 0xff;
let chunk = splice_before_end(compiled_two_line_snippet(), §ion);
assert!(mrubyedge::rite::load(&chunk).is_err());
}
#[test]
fn dbg_section_shorter_than_declared_is_rejected_test() {
let mut section = dbg_section(0, 0, &[]);
let size = u32::from_be_bytes(section[4..8].try_into().unwrap()) + 64;
section[4..8].copy_from_slice(&size.to_be_bytes());
let chunk = splice_before_end(compiled_two_line_snippet(), §ion);
let err = mrubyedge::rite::load(&chunk).unwrap_err();
assert!(matches!(err, mrubyedge::rite::Error::TooShort));
}
#[test]
fn chunk_without_dbg_section_still_loads_with_empty_lines_test() {
let code = r#"
x = 1
"#;
let binary = mrbc_compile("no_dbg", code);
let rite = mrubyedge::rite::load(&binary).unwrap();
assert!(rite.irep[0].lines.is_empty());
}
#[test]
fn vm_irep_retains_source_lines_test() {
let code = r#"
def answer
x = 40
x + 2
end
answer
"#;
let binary = mrbc_compile_with_debug(code);
let mut rite = mrubyedge::rite::load(&binary).unwrap();
let vm = mrubyedge::yamrb::vm::VM::open(&mut rite);
drop(rite);
drop(binary);
assert_eq!(vm.irep.debug_line(0), Some(2));
assert_eq!(vm.irep.debug_line(4), Some(6));
let method = &vm.irep.reps[0];
assert_eq!(method.debug_line(4), Some(3));
assert_eq!(method.debug_line(6), Some(3));
assert_eq!(method.debug_line(7), Some(4));
assert_eq!(
method.debug_line(method.debug_info.as_ref().unwrap().pc_count),
None
);
assert_eq!(method.debug_line(usize::MAX), None);
let code = r#"
1
"#;
let binary = mrbc_compile("no_debug", code);
let mut rite = mrubyedge::rite::load(&binary).unwrap();
let vm = mrubyedge::yamrb::vm::VM::open(&mut rite);
assert!(vm.irep.debug_info.is_none());
assert_eq!(vm.irep.debug_line(0), None);
}
#[test]
fn debug_filenames_survive_loading_into_vm_test() {
let code = r#"
def answer
42
end
answer
"#;
let binary = mrbc_compile_named_debug("source.rb", code);
let mut rite = mrubyedge::rite::load(&binary).unwrap();
for irep in &rite.irep {
assert_eq!(irep.debug_files[0].filename.to_bytes(), b"source.rb");
}
let vm = mrubyedge::yamrb::vm::VM::open(&mut rite);
drop(rite);
drop(binary);
assert_eq!(vm.irep.debug_filename(0).unwrap().to_bytes(), b"source.rb");
assert_eq!(
vm.irep.reps[0].debug_filename(0).unwrap().to_bytes(),
b"source.rb"
);
assert_eq!(vm.irep.debug_filename(usize::MAX), None);
assert_eq!(
mrubyedge::yamrb::vm::VM::empty().irep.debug_filename(0),
None
);
}
#[test]
fn debug_filename_index_is_validated_test() {
let mut section = dbg_section(0, 1, &1u16.to_be_bytes());
section[26..28].copy_from_slice(&1u16.to_be_bytes());
let binary = splice_before_end(compiled_two_line_snippet(), §ion);
assert!(matches!(
mrubyedge::rite::load(&binary),
Err(mrubyedge::rite::Error::InvalidFormat)
));
}
#[test]
fn debug_multiple_files_select_their_own_line_maps_test() {
let mut body = Vec::new();
body.extend_from_slice(&2u16.to_be_bytes());
for name in [b"a.rb", b"b.rb"] {
body.extend_from_slice(&4u16.to_be_bytes());
body.extend_from_slice(name);
}
let mut record = Vec::new();
record.extend_from_slice(&2u16.to_be_bytes());
for (start, filename, pos, line) in [(0u32, 0u16, 0u32, 10u16), (2, 1, 3, 20)] {
record.extend_from_slice(&start.to_be_bytes());
record.extend_from_slice(&filename.to_be_bytes());
record.extend_from_slice(&1u32.to_be_bytes());
record.push(1); record.extend_from_slice(&pos.to_be_bytes());
record.extend_from_slice(&line.to_be_bytes());
}
body.extend_from_slice(&((record.len() + 4) as u32).to_be_bytes());
body.extend_from_slice(&record);
let mut section = b"DBG\0".to_vec();
section.extend_from_slice(&((body.len() + 8) as u32).to_be_bytes());
section.extend_from_slice(&body);
let binary = splice_before_end(compiled_two_line_snippet(), §ion);
let mut rite = mrubyedge::rite::load(&binary).unwrap();
let vm = mrubyedge::yamrb::vm::VM::open(&mut rite);
assert_eq!(vm.irep.debug_filename(1).unwrap().to_bytes(), b"a.rb");
assert_eq!(vm.irep.debug_line(1), Some(10));
assert_eq!(vm.irep.debug_filename(2).unwrap().to_bytes(), b"b.rb");
assert_eq!(vm.irep.debug_line(2), None);
assert_eq!(vm.irep.debug_line(3), Some(20));
}