#[path = "common/mod.rs"]
mod common;
fn layout(start_address: u32, endianness: &str, data_content: &str) -> String {
format!(
r#"
[mint]
endianness = "{endianness}"
[block.header]
start_address = 0x{start_address:X}
length = 0x1000
padding = 0xFF
[block.data]
{data_content}
"#
)
}
fn ref_layout(start_address: u32, data_content: &str) -> String {
layout(start_address, "little", data_content)
}
fn ref_layout_with_endian(start_address: u32, endianness: &str, data_content: &str) -> String {
layout(start_address, endianness, data_content)
}
fn load_and_build(name: &str, toml_str: &str) -> (Vec<u8>, u32) {
common::ensure_out_dir();
let path = common::write_layout_file(name, toml_str);
let config = mint_core::layout::load_layout(&path).expect("layout loads");
let block = &config.blocks["block"];
common::build_block(block, &config.mint, false, None).expect("build succeeds")
}
fn load_and_build_with_values(name: &str, toml_str: &str) -> ((Vec<u8>, u32), serde_json::Value) {
common::ensure_out_dir();
let path = common::write_layout_file(name, toml_str);
let config = mint_core::layout::load_layout(&path).expect("layout loads");
let block = &config.blocks["block"];
common::build_block_with_values(block, &config.mint).expect("build succeeds")
}
fn load_and_fail(name: &str, toml_str: &str) -> String {
common::ensure_out_dir();
let path = common::write_layout_file(name, toml_str);
let config = mint_core::layout::load_layout(&path).expect("layout loads");
let block = &config.blocks["block"];
let err = common::build_block(block, &config.mint, false, None).unwrap_err();
format!("{}", err)
}
#[test]
fn ref_resolves_forward_pointer_u32_little_endian() {
let toml = ref_layout(
0x8000,
r#"
ptr = { ref = "target", type = "u32" }
target = { value = 0xDEADBEEF, type = "u32" }
"#,
);
let (bytes, _) = load_and_build("ref_forward", &toml);
assert_eq!(bytes.len(), 8);
assert_eq!(&bytes[0..4], &0x8004u32.to_le_bytes());
assert_eq!(&bytes[4..8], &0xDEADBEEFu32.to_le_bytes());
}
#[test]
fn ref_resolves_backward_pointer() {
let toml = ref_layout(
0x1000,
r#"
target = { value = 0x42, type = "u32" }
ptr = { ref = "target", type = "u32" }
"#,
);
let (bytes, _) = load_and_build("ref_backward", &toml);
assert_eq!(bytes.len(), 8);
assert_eq!(&bytes[0..4], &0x42u32.to_le_bytes());
assert_eq!(&bytes[4..8], &0x1000u32.to_le_bytes());
}
#[test]
fn ref_with_u16_type() {
let toml = ref_layout(
0x100,
r#"
field_a = { value = 1, type = "u16" }
field_b = { value = 2, type = "u16" }
ptr = { ref = "field_b", type = "u16" }
"#,
);
let (bytes, _) = load_and_build("ref_u16", &toml);
assert_eq!(bytes.len(), 6);
assert_eq!(&bytes[4..6], &0x102u16.to_le_bytes());
}
#[test]
fn ref_u16_rejects_address_out_of_range_without_strict_flag() {
let toml = ref_layout(
0x1_0000,
r#"
target = { value = 0x42, type = "u32" }
ptr = { ref = "target", type = "u16" }
"#,
);
let err = load_and_fail("ref_u16_overflow", &toml);
assert!(
err.contains("out of range for u16"),
"expected u16 range error, got: {err}"
);
}
#[test]
fn ref_with_u64_type() {
let toml = ref_layout(
0x2000,
r#"
ptr = { ref = "target", type = "u64" }
target = { value = 0xFF, type = "u32" }
"#,
);
let (bytes, _) = load_and_build("ref_u64", &toml);
assert_eq!(bytes.len(), 12);
let expected_addr: u64 = 0x2000 + 8;
assert_eq!(&bytes[0..8], &expected_addr.to_le_bytes());
}
#[test]
fn ref_big_endian() {
let toml = ref_layout_with_endian(
0x4000,
"big",
r#"
ptr = { ref = "target", type = "u32" }
target = { value = 0xAB, type = "u32" }
"#,
);
let (bytes, _) = load_and_build("ref_big_endian", &toml);
assert_eq!(bytes.len(), 8);
assert_eq!(&bytes[0..4], &0x4004u32.to_be_bytes());
assert_eq!(&bytes[4..8], &0xABu32.to_be_bytes());
}
#[test]
fn ref_to_branch_node() {
let toml = ref_layout(
0x0,
r#"
header_field = { value = 0x01, type = "u32" }
nested.a = { value = 0x0A, type = "u16" }
nested.b = { value = 0x0B, type = "u16" }
ptr = { ref = "nested", type = "u32" }
"#,
);
let (bytes, _) = load_and_build("ref_branch", &toml);
assert_eq!(bytes.len(), 12);
assert_eq!(&bytes[8..12], &0x4u32.to_le_bytes());
}
#[test]
fn ref_to_nested_leaf() {
let toml = ref_layout(
0x100,
r#"
group.x = { value = 1, type = "u16" }
group.y = { value = 2, type = "u16" }
ptr = { ref = "group.y", type = "u32" }
"#,
);
let (bytes, _) = load_and_build("ref_nested_leaf", &toml);
assert_eq!(bytes.len(), 8);
assert_eq!(&bytes[4..8], &0x102u32.to_le_bytes());
}
#[test]
fn ref_multiple_refs_in_same_block() {
let toml = ref_layout(
0x0,
r#"
field_a = { value = 0xAA, type = "u16" }
field_b = { value = 0xBB, type = "u16" }
ptr_a = { ref = "field_a", type = "u32" }
ptr_b = { ref = "field_b", type = "u32" }
"#,
);
let (bytes, _) = load_and_build("ref_multi", &toml);
assert_eq!(bytes.len(), 12);
assert_eq!(&bytes[4..8], &0x0u32.to_le_bytes());
assert_eq!(&bytes[8..12], &0x2u32.to_le_bytes());
}
#[test]
fn ref_two_refs_same_target() {
let toml = ref_layout(
0x0,
r#"
target = { value = 0x42, type = "u32" }
ptr1 = { ref = "target", type = "u32" }
ptr2 = { ref = "target", type = "u32" }
"#,
);
let (bytes, _) = load_and_build("ref_same_target", &toml);
assert_eq!(bytes.len(), 12);
assert_eq!(&bytes[4..8], &0x0u32.to_le_bytes());
assert_eq!(&bytes[8..12], &0x0u32.to_le_bytes());
}
#[test]
fn ref_value_exported_to_json() {
let toml = ref_layout(
0x1000,
r#"
target = { value = 0x42, type = "u32" }
ptr = { ref = "target", type = "u32" }
"#,
);
let ((bytes, _), values) = load_and_build_with_values("ref_json_export", &toml);
assert_eq!(&bytes[4..8], &0x1000u32.to_le_bytes());
assert_eq!(&values["ptr"], &serde_json::json!(0x1000u64));
assert_eq!(&values["target"], &serde_json::json!(0x42u64));
}
#[test]
fn ref_with_alignment_padding() {
let toml = ref_layout(
0x0,
r#"
small = { value = 0x01, type = "u8" }
target = { value = 0xDEAD, type = "u32" }
ptr = { ref = "target", type = "u32" }
"#,
);
let (bytes, padding) = load_and_build("ref_align", &toml);
assert_eq!(bytes.len(), 12);
assert_eq!(padding, 3);
assert_eq!(&bytes[8..12], &0x4u32.to_le_bytes());
}
#[test]
fn ref_rejects_invalid_configs() {
let cases = [
(
"ref_err_unknown",
ref_layout(
0x0,
r#"
ptr = { ref = "nonexistent", type = "u32" }
"#,
),
"not found",
),
(
"ref_err_size",
ref_layout(
0x0,
r#"
target = { value = 0x42, type = "u32" }
ptr = { ref = "target", type = "u32", size = 4 }
"#,
),
"size",
),
(
"ref_err_float",
ref_layout(
0x0,
r#"
target = { value = 0x42, type = "u32" }
ptr = { ref = "target", type = "f32" }
"#,
),
"integer",
),
(
"ref_err_u8",
ref_layout(
0x0,
r#"
target = { value = 0x42, type = "u32" }
ptr = { ref = "target", type = "u8" }
"#,
),
"u16, u32, u64",
),
(
"ref_err_empty",
ref_layout(
0x0,
r#"
ptr = { ref = "", type = "u32" }
"#,
),
"empty",
),
(
"empty_branch",
ref_layout(
0x0,
r#"
field = { value = 0x42, type = "u32" }
[block.data.empty]
"#,
),
"Empty branch",
),
];
for (name, toml, expected) in cases {
let err = load_and_fail(name, &toml);
assert!(
err.contains(expected),
"Expected '{}' error for {}, got: {}",
expected,
name,
err
);
}
}
#[test]
fn ref_no_overhead_without_refs() {
let toml = ref_layout(
0x8000,
r#"
field_a = { value = 0xAAAA, type = "u16" }
field_b = { value = 0xBBBB, type = "u16" }
"#,
);
let (bytes, _) = load_and_build("ref_no_refs", &toml);
assert_eq!(bytes.len(), 4);
assert_eq!(&bytes[0..2], &0xAAAAu16.to_le_bytes());
assert_eq!(&bytes[2..4], &0xBBBBu16.to_le_bytes());
}
#[test]
fn ref_branch_offset_accounts_for_alignment() {
let toml = ref_layout(
0x0,
r#"
small = { value = 0x01, type = "u8" }
nested.big = { value = 0xDEAD, type = "u32" }
ptr = { ref = "nested", type = "u32" }
"#,
);
let (bytes, _) = load_and_build("ref_branch_align", &toml);
assert_eq!(bytes.len(), 12);
assert_eq!(&bytes[8..12], &0x4u32.to_le_bytes());
}