#[cfg(test)]
mod tests {
use parser::parse;
use crate::compiler::Compiler;
use crate::snapshot::{
bytecode_abi_fingerprint, write_bytecode, SnapshotError, FORMAT_VERSION,
};
use crate::snapshot_layout::{
describe_bytecode, SnapshotLayout, SnapshotRegion, SnapshotSection,
};
const SOURCE: &str = r#"let add = fn(a, b) { a + b }; add(1, "hi")"#;
fn snapshot_of(source: &str, strip_debug: bool) -> Vec<u8> {
let program = parse(source).expect("source parses");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(&program).expect("source compiles");
write_bytecode(&bytecode, strip_debug).expect("bytecode serializes")
}
fn region<'a>(layout: &'a SnapshotLayout, label: &str) -> &'a SnapshotRegion {
layout
.regions
.iter()
.find(|region| region.label == label)
.unwrap_or_else(|| panic!("missing region {:?}", label))
}
#[test]
fn regions_tile_the_entire_buffer() {
for strip_debug in [false, true] {
let bytes = snapshot_of(SOURCE, strip_debug);
let layout = describe_bytecode(&bytes).expect("valid snapshot describes");
assert_eq!(layout.byte_length, bytes.len());
let mut cursor = 0;
for region in &layout.regions {
assert_eq!(region.offset, cursor, "gap or overlap at {:?}", region.label);
assert!(region.length >= 1, "empty region {:?}", region.label);
cursor = region.offset + region.length;
}
assert_eq!(cursor, layout.byte_length);
}
}
#[test]
fn header_regions_expose_format_facts() {
let layout = describe_bytecode(&snapshot_of(SOURCE, false)).unwrap();
assert_eq!(layout.format_version, FORMAT_VERSION);
assert_eq!(layout.abi_fingerprint, format!("0x{:08x}", bytecode_abi_fingerprint()));
assert!(layout.has_debug_info);
let labels: Vec<&str> = layout.regions[..4]
.iter()
.map(|region| region.label.as_str())
.collect();
assert_eq!(labels, ["magic", "version", "abi fingerprint", "flags"]);
assert!(layout.regions[..4]
.iter()
.all(|region| region.section == SnapshotSection::Header));
assert!(region(&layout, "abi fingerprint")
.detail
.contains(&layout.abi_fingerprint));
assert!(region(&layout, "flags")
.detail
.contains("debug info present"));
let stripped = describe_bytecode(&snapshot_of(SOURCE, true)).unwrap();
assert!(!stripped.has_debug_info);
assert!(region(&stripped, "flags")
.detail
.contains("debug info stripped"));
}
#[test]
fn instruction_regions_disassemble_both_streams() {
let layout = describe_bytecode(&snapshot_of(SOURCE, false)).unwrap();
let call = region(&layout, "OpCall 2");
assert_eq!(call.section, SnapshotSection::Main);
assert!(call.detail.starts_with("main pc "));
let add = region(&layout, "OpAdd");
assert_eq!(add.section, SnapshotSection::Constants);
assert!(add.detail.starts_with("fn add pc "));
}
#[test]
fn constant_regions_annotate_tags_and_values() {
let layout = describe_bytecode(&snapshot_of(SOURCE, false)).unwrap();
assert_eq!(region(&layout, "constant count").detail, "3 constants (ULEB128)");
assert!(region(&layout, "const[0] tag")
.detail
.contains("TAG_FUNCTION"));
assert_eq!(region(&layout, "const[0] name").detail, "\"add\"");
assert_eq!(region(&layout, "const[0] locals").detail, "2 local slots");
assert_eq!(region(&layout, "const[0] params").detail, "2 parameters");
assert!(region(&layout, "const[1] tag")
.detail
.contains("TAG_INTEGER"));
assert_eq!(region(&layout, "const[1] value").detail, "1 (SLEB128)");
assert!(region(&layout, "const[2] tag")
.detail
.contains("TAG_STRING"));
assert_eq!(region(&layout, "const[2] text").detail, "\"hi\"");
}
#[test]
fn debug_regions_map_pc_to_source_and_stripping_removes_them() {
let layout = describe_bytecode(&snapshot_of(SOURCE, false)).unwrap();
let main_spans = region(&layout, "main span count");
assert_eq!(main_spans.section, SnapshotSection::Debug);
assert!(layout.regions.iter().any(|region| {
region.section == SnapshotSection::Debug
&& region.label.starts_with("main pc ")
&& region.detail.starts_with("source ")
}));
assert!(region(&layout, "debug fn index").detail.contains("fn add"));
let stripped = describe_bytecode(&snapshot_of(SOURCE, true)).unwrap();
assert!(stripped
.regions
.iter()
.all(|region| region.section != SnapshotSection::Debug));
}
#[test]
fn describe_rejects_what_the_loader_rejects() {
let mut bytes = snapshot_of(SOURCE, false);
bytes[0] = b'X';
assert_eq!(describe_bytecode(&bytes), Err(SnapshotError::BadMagic));
}
}