plugins/native/znippy-plugin-elf/src/tests.rs
//! One test per column, one test per refusal.
use super::*;
use crate::fixture::*;
fn row(path: &str, data: &[u8]) -> ExtensionRow {
NativeElfPlugin::new().extract_metadata(path, data).expect("plugin claimed the member")
}
fn s<'a>(r: &'a ExtensionRow, k: &str) -> Option<&'a str> {
match r.fields.get(k) {
Some(ExtensionValue::Str(v)) => Some(v.as_str()),
_ => None,
}
}
fn n(r: &ExtensionRow, k: &str) -> Option<u32> {
match r.fields.get(k) {
Some(ExtensionValue::U32(v)) => Some(*v),
_ => None,
}
}
// ─────────────────────────── the gate ───────────────────────────
#[test]
fn a_non_elf_member_gets_no_elf_columns() {
// The gate is the magic, not the name. A file CALLED libfoo.so that is not
// an ELF must not acquire a single fact.
let p = NativeElfPlugin::new();
assert!(p.extract_metadata("usr/lib/libfoo.so", b"I am not an ELF").is_none());
assert!(p.extract_metadata("bin/gunnar", b"#!/bin/sh\necho hi\n").is_none());
assert!(p.extract_metadata("empty", b"").is_none());
}
#[test]
fn an_extensionless_binary_is_still_claimed() {
// Most real binaries live in bin/ with no extension at all. Claiming by
// extension would have missed every one of them.
let data = Builder::default().etype(ET_EXEC).text(16).build();
assert_eq!(s(&row("bin/gunnar", &data), "elf_machine"), Some("x86-64"));
}
// ─────────────────────── identity columns ───────────────────────
#[test]
fn member_kind_separates_a_pie_executable_from_a_shared_library() {
// Both are ET_DYN. PT_INTERP is the only thing that tells them apart, and
// `elf_type` alone cannot.
let pie = Builder::default().etype(ET_DYN).interp("/lib64/ld-linux-x86-64.so.2").build();
let lib = Builder::default().etype(ET_DYN).build();
assert_eq!(s(&row("bin/app", &pie), "member_kind"), Some("pie-executable"));
assert_eq!(s(&row("lib/libx.so", &lib), "member_kind"), Some("shared-object"));
assert_eq!(s(&row("bin/app", &pie), "elf_type"), Some("dyn"));
assert_eq!(s(&row("lib/libx.so", &lib), "elf_type"), Some("dyn"));
}
#[test]
fn member_kind_names_an_executable_a_kernel_module_and_a_core() {
let exe = Builder::default().etype(ET_EXEC).build();
let ko = Builder::default()
.etype(ET_REL)
.section(Section::new(".modinfo", SHT_PROGBITS, b"license=GPL\0".to_vec()))
.build();
let core = Builder::default().etype(ET_CORE).build();
assert_eq!(s(&row("bin/x", &exe), "member_kind"), Some("executable"));
assert_eq!(s(&row("lib/modules/x.ko", &ko), "member_kind"), Some("kernel-module"));
assert_eq!(s(&row("crash/core.1", &core), "member_kind"), Some("core"));
}
#[test]
fn elf_build_id_is_read_from_the_note() {
let data = Builder::default()
.note(".note.gnu.build-id", b"GNU", 3, &[0xde, 0xad, 0xbe, 0xef, 0x01])
.build();
assert_eq!(s(&row("bin/x", &data), "elf_build_id"), Some("deadbeef01"));
}
#[test]
fn a_binary_without_a_build_id_leaves_the_column_null_which_is_the_finding() {
let data = Builder::default().build();
assert_eq!(s(&row("bin/x", &data), "elf_build_id"), None);
}
// ──────────────────────── header columns ────────────────────────
#[test]
fn elf_machine_is_the_header_not_the_path() {
// The path screams x86_64. The header says aarch64. The header wins.
let data = Builder::default().machine(EM_AARCH64).build();
let r = row("target/x86_64-unknown-linux-gnu/release/gunnar", &data);
assert_eq!(s(&r, "elf_machine"), Some("aarch64"));
}
#[test]
fn elf_class_and_osabi_come_from_e_ident() {
let data = Builder::default().osabi(3).build(); // ELFOSABI_GNU
let r = row("bin/x", &data);
assert_eq!(s(&r, "elf_class"), Some("ELF64"));
assert_eq!(s(&r, "elf_osabi"), Some("GNU"));
}
#[test]
fn elf_interp_is_null_for_a_static_binary_and_set_for_a_dynamic_one() {
let stat = Builder::default().etype(ET_EXEC).build();
let dyn_ = Builder::default().etype(ET_EXEC).interp("/lib/ld-musl-x86_64.so.1").build();
assert_eq!(s(&row("bin/x", &stat), "elf_interp"), None);
assert_eq!(s(&row("bin/y", &dyn_), "elf_interp"), Some("/lib/ld-musl-x86_64.so.1"));
}
#[test]
fn elf_abi_level_reads_the_x86_isa_property() {
// GNU_PROPERTY_X86_ISA_1_NEEDED, bit 2 set = x86-64-v3.
let data = Builder::default()
.note(".note.gnu.property", b"GNU", 5, &property_desc(0xc000_8002, 0b100))
.build();
assert_eq!(s(&row("bin/x", &data), "elf_abi_level"), Some("x86-64-v3"));
}
#[test]
fn elf_abi_level_reads_the_aarch64_feature_property() {
let data = Builder::default()
.machine(EM_AARCH64)
.note(".note.gnu.property", b"GNU", 5, &property_desc(0xc000_0000, 0b11))
.build();
assert_eq!(s(&row("bin/x", &data), "elf_abi_level"), Some("bti+pac"));
}
// ───────────────────── dependency column ─────────────────────
#[test]
fn elf_needed_lists_sonames_sorted() {
let data = Builder::default().needed(&["libssl.so.3", "libc.so.6"]).build();
assert_eq!(s(&row("bin/x", &data), "elf_needed"), Some("libc.so.6 libssl.so.3"));
}
#[test]
fn elf_needed_carries_the_symbol_version_so_libssl_3_0_x_is_findable() {
// THE query: "which binaries link libssl 3.0.x". The soname gives the ABI
// major; only .gnu.version_r gives the minor.
let data = Builder::default()
.needed(&["libssl.so.3", "libc.so.6"])
.verneed("libssl.so.3", &["OPENSSL_3.0.0"])
.build();
let needed = s(&row("bin/x", &data), "elf_needed").unwrap().to_string();
assert!(needed.contains("libssl.so.3(OPENSSL_3.0.0)"), "got {needed}");
assert!(needed.contains("libc.so.6"), "got {needed}");
}
#[test]
fn a_static_binary_has_no_needed_column() {
let data = Builder::default().etype(ET_EXEC).build();
assert_eq!(s(&row("bin/x", &data), "elf_needed"), None);
}
// ─────────────────────── cost columns ───────────────────────
#[test]
fn text_bytes_counts_only_allocated_executable_sections() {
let data = Builder::default()
.text(1024)
.section(Section::new(".rodata", SHT_PROGBITS, vec![0; 4096]).flags(SHF_ALLOC))
.build();
// 4096 bytes of rodata must not be counted as code.
assert_eq!(n(&row("bin/x", &data), "text_bytes"), Some(1024));
}
#[test]
fn debug_bytes_and_has_debug_measure_what_a_fetcher_would_pay() {
let with = Builder::default().text(64).debug(2048).build();
let without = Builder::default().text(64).build();
let r = row("bin/x", &with);
assert_eq!(n(&r, "debug_bytes"), Some(2048));
assert_eq!(n(&r, "has_debug"), Some(1));
let r2 = row("bin/y", &without);
assert_eq!(n(&r2, "debug_bytes"), Some(0));
assert_eq!(n(&r2, "has_debug"), Some(0));
}
#[test]
fn a_debuglink_is_has_debug_with_zero_debug_bytes() {
// "stripped, the symbols live elsewhere" — the case a single byte count
// cannot express, and the case debuginfod exists for.
let data = Builder::default()
.text(64)
.section(Section::new(".gnu_debuglink", SHT_PROGBITS, b"x.debug\0\0\0\0abcd".to_vec()))
.build();
let r = row("bin/x", &data);
assert_eq!(n(&r, "has_debug"), Some(1));
assert_eq!(n(&r, "debug_bytes"), Some(0));
}
// ───────────────────── recipe columns ─────────────────────
fn receipt_json(version: u32) -> Vec<u8> {
format!(
r#"{{"receipt_version":{version},
"build_thing":{{"krate":"gunnar-build-thing","version":"0.3.1","hash":"abc123"}},
"atom":"gunnar-installer-metered",
"target":"iso-installer",
"settings":{{"containers":"on","iceberg":"off"}},
"inputs":{{"commit":"451288a0000000000000000000000000000000aa",
"toolchain_image":"nordisk-cargo-toolchain",
"toolchain_image_sha256":"3d5115baa07d03ca51eb3e44ebb86b4e64e679bd88f38e156ef9da12c35d2143"}}}}"#
)
.into_bytes()
}
fn with_receipt(version: u32) -> Vec<u8> {
Builder::default()
.section(Section::new(".nordisk.buildthing", SHT_PROGBITS, receipt_json(version)))
.build()
}
#[test]
fn the_toolchain_image_digest_is_read_from_the_binarys_own_receipt() {
// The column this whole format exists for.
let r = row("bin/gunnar", &with_receipt(1));
assert_eq!(
s(&r, "toolchain_image_sha256"),
Some("3d5115baa07d03ca51eb3e44ebb86b4e64e679bd88f38e156ef9da12c35d2143")
);
assert_eq!(s(&r, "toolchain_image"), Some("nordisk-cargo-toolchain"));
}
#[test]
fn the_recipe_coordinate_variant_target_features_and_commit_come_from_the_receipt() {
let r = row("bin/gunnar", &with_receipt(1));
assert_eq!(s(&r, "recipe_coordinate"), Some("gunnar-build-thing:0.3.1:abc123"));
assert_eq!(s(&r, "recipe_variant"), Some("gunnar-installer-metered"));
assert_eq!(s(&r, "recipe_target"), Some("iso-installer"));
assert_eq!(s(&r, "recipe_features"), Some("containers=on iceberg=off"));
assert_eq!(s(&r, "source_commit"), Some("451288a0000000000000000000000000000000aa"));
}
#[test]
fn a_receipt_from_the_future_is_refused_not_partially_interpreted() {
// nornir-build-thing's own rule: a misread receipt answers the provenance
// question WRONGLY, which is worse than not answering it.
let r = row("bin/gunnar", &with_receipt(99));
assert_eq!(s(&r, "recipe_coordinate"), None);
assert_eq!(s(&r, "toolchain_image_sha256"), None);
}
#[test]
fn two_receipts_in_one_artifact_answer_neither() {
let mut two = receipt_json(1);
two.push(0);
two.extend_from_slice(&receipt_json(1));
let data = Builder::default()
.section(Section::new(".nordisk.buildthing", SHT_PROGBITS, two))
.build();
assert_eq!(s(&row("bin/x", &data), "recipe_variant"), None);
}
#[test]
fn note_package_fills_what_the_receipt_did_not() {
let note = br#"{"type":"rpm","name":"gunnar","version":"1.4.0","architecture":"x86_64",
"variant":"gunnar-open","sourceCommit":"deadbeef",
"toolchainImage":"nordisk-cargo-toolchain@sha256:ffff"}"#;
let data = Builder::default().note(".note.package", b"FDO", 0xcafe_1a7e, note).build();
let r = row("bin/gunnar", &data);
assert_eq!(s(&r, "recipe_variant"), Some("gunnar-open"));
assert_eq!(s(&r, "source_commit"), Some("deadbeef"));
// the `name@sha256:hex` ref is split, never stored whole in a digest column
assert_eq!(s(&r, "toolchain_image"), Some("nordisk-cargo-toolchain"));
assert_eq!(s(&r, "toolchain_image_sha256"), Some("ffff"));
}
#[test]
fn the_receipt_wins_over_the_note() {
let note = br#"{"variant":"gunnar-open","sourceCommit":"0000000"}"#;
let data = Builder::default()
.section(Section::new(".nordisk.buildthing", SHT_PROGBITS, receipt_json(1)))
.note(".note.package", b"FDO", 0xcafe_1a7e, note)
.build();
let r = row("bin/gunnar", &data);
assert_eq!(s(&r, "recipe_variant"), Some("gunnar-installer-metered"));
assert_eq!(s(&r, "source_commit"), Some("451288a0000000000000000000000000000000aa"));
}
// ───────────────────── the audit column ─────────────────────
#[test]
fn auditable_root_is_read_from_the_dep_v0_tree() {
let json = br#"{"packages":[{"name":"serde","version":"1.0.203","kind":"build"},
{"name":"gunnar","version":"1.4.0","root":true}]}"#;
let data = Builder::default()
.section(Section::new(".dep-v0", SHT_PROGBITS, zlib_stored(json)))
.build();
assert_eq!(s(&row("bin/gunnar", &data), "auditable_root"), Some("gunnar 1.4.0"));
}
#[test]
fn a_binary_with_no_dep_v0_cannot_be_swept_and_says_so_by_being_null() {
let data = Builder::default().build();
assert_eq!(s(&row("bin/x", &data), "auditable_root"), None);
}
// ───────────────────── the lie columns ─────────────────────
#[test]
fn arch_claim_and_arch_lies_find_a_member_that_lies_about_its_architecture() {
let data = Builder::default().machine(EM_AARCH64).build();
let r = row("target/x86_64-unknown-linux-gnu/release/gunnar", &data);
assert_eq!(s(&r, "arch_claim"), Some("x86-64"));
assert_eq!(s(&r, "elf_machine"), Some("aarch64"));
assert_eq!(n(&r, "arch_lies"), Some(1));
}
#[test]
fn a_truthful_path_does_not_lie_and_a_silent_path_does_not_either() {
let data = Builder::default().machine(EM_X86_64).build();
assert_eq!(n(&row("target/x86_64-unknown-linux-gnu/release/x", &data), "arch_lies"), Some(0));
let r = row("bin/x", &data);
assert_eq!(s(&r, "arch_claim"), None);
assert_eq!(n(&r, "arch_lies"), Some(0));
}
// ───────────────────── P-4: never panic ─────────────────────
#[test]
fn a_truncated_elf_never_panics_and_still_yields_what_it_proved() {
let full = Builder::default().etype(ET_EXEC).text(512).build();
for cut in [4usize, 5, 8, 16, 32, 48, 63, 64, 70, 100, 200] {
if cut > full.len() {
continue;
}
// Must not panic. May or may not claim.
let _ = NativeElfPlugin::new().extract_metadata("bin/x", &full[..cut]);
}
// The magic alone is enough to read class and osabi.
let head = &full[..8];
if let Some(r) = NativeElfPlugin::new().extract_metadata("bin/x", head) {
assert_eq!(s(&r, "elf_class"), Some("ELF64"));
}
}
#[test]
fn a_dep_v0_bomb_yields_a_null_column_not_an_oom() {
// A stored-block header claiming a length its own bytes cannot supply.
let bomb = vec![0x78, 0x01, 0x01, 0xff, 0xff, 0x00, 0x00, 0x41];
let data = Builder::default().section(Section::new(".dep-v0", SHT_PROGBITS, bomb)).build();
assert_eq!(s(&row("bin/x", &data), "auditable_root"), None);
}
#[test]
fn garbage_in_every_readable_section_leaves_nulls_and_keeps_the_header_facts() {
let data = Builder::default()
.machine(EM_RISCV)
.section(Section::new(".nordisk.buildthing", SHT_PROGBITS, b"}{not json".to_vec()))
.section(Section::new(".dep-v0", SHT_PROGBITS, b"\x00\x00\x00".to_vec()))
.note(".note.package", b"FDO", 0xcafe_1a7e, b"also not json")
.build();
let r = row("bin/x", &data);
assert_eq!(s(&r, "elf_machine"), Some("riscv64"));
assert_eq!(s(&r, "recipe_variant"), None);
assert_eq!(s(&r, "auditable_root"), None);
}
// ───────────────────── schema and registration ─────────────────────
#[test]
fn schema_and_meta_are_consistent_and_every_column_is_declared() {
let p = NativeElfPlugin::new();
assert_eq!(p.type_id(), ELF_TYPE_ID);
assert_eq!(p.meta().type_id, ELF_TYPE_ID);
assert_eq!(p.schema_fields().len(), 22);
// P-2: a row may never carry a key the schema did not declare.
let declared: std::collections::HashSet<String> =
p.schema_fields().iter().map(|f| f.name().to_string()).collect();
let data = Builder::default()
.etype(ET_EXEC)
.interp("/lib64/ld-linux-x86-64.so.2")
.text(64)
.debug(16)
.needed(&["libc.so.6"])
.note(".note.gnu.build-id", b"GNU", 3, &[1, 2, 3])
.section(Section::new(".nordisk.buildthing", SHT_PROGBITS, receipt_json(1)))
.build();
let r = row("target/x86_64-unknown-linux-gnu/release/gunnar", &data);
for k in r.fields.keys() {
assert!(declared.contains(k), "undeclared column '{k}' (P-2)");
}
}
// ═══════════════════════════ refusals ═══════════════════════════
use crate::refusal::{Coordinate, ElfRefusal, HostFacts, Linkage, RecipeVerdict, check};
fn a_host() -> HostFacts {
HostFacts {
machine: "x86-64".into(),
abi_level: Some("x86-64-v2".into()),
interpreters: vec!["/lib64/ld-linux-x86-64.so.2".into()],
}
}
fn facts_of(data: &[u8]) -> crate::facts::ElfFacts {
crate::facts::read(data).expect("fixture is an ELF")
}
#[test]
fn elf_machine_refuses_when_the_header_disagrees_with_the_coordinate() {
let f = facts_of(&Builder::default().machine(EM_AARCH64).build());
let coord = Coordinate {
path: "target/x86_64-unknown-linux-gnu/release/gunnar".into(),
..Default::default()
};
let refusals = check(&f, &a_host(), &coord);
let r = refusals
.iter()
.find(|r| matches!(r, ElfRefusal::Machine { whose: "coordinate", .. }))
.expect("elf-machine (coordinate) must fire");
assert_eq!(r.name(), "elf-machine");
let text = r.to_string();
assert!(text.starts_with("REFUSED [elf-machine] "), "{text}");
// both sides must be named
assert!(text.contains("aarch64") && text.contains("x86-64"), "{text}");
}
#[test]
fn elf_machine_refuses_when_the_header_disagrees_with_the_host() {
let f = facts_of(&Builder::default().machine(EM_AARCH64).build());
let coord = Coordinate { path: "bin/gunnar".into(), ..Default::default() };
assert!(
check(&f, &a_host(), &coord)
.iter()
.any(|r| matches!(r, ElfRefusal::Machine { whose: "host", .. })),
"a binary for another machine must be refused by name, not started and killed"
);
}
#[test]
fn a_matching_machine_is_not_refused() {
let f = facts_of(&Builder::default().machine(EM_X86_64).etype(ET_EXEC).build());
let coord = Coordinate {
path: "target/x86_64-unknown-linux-gnu/release/x".into(),
..Default::default()
};
assert!(!check(&f, &a_host(), &coord).iter().any(|r| r.name() == "elf-machine"));
}
#[test]
fn elf_libc_refuses_a_dynamic_binary_where_the_coordinate_says_static() {
let f = facts_of(
&Builder::default().etype(ET_EXEC).interp("/lib64/ld-linux-x86-64.so.2").build(),
);
let coord = Coordinate {
path: "bin/gunnar".into(),
linkage: Some(Linkage::Static),
..Default::default()
};
let r = check(&f, &a_host(), &coord)
.into_iter()
.find(|r| matches!(r, ElfRefusal::LibcDynamicWhereStatic { .. }))
.expect("elf-libc must fire");
assert_eq!(r.name(), "elf-libc");
assert!(r.to_string().contains("/lib64/ld-linux-x86-64.so.2"));
}
#[test]
fn elf_libc_refuses_an_interpreter_this_box_does_not_have() {
let f = facts_of(&Builder::default().etype(ET_EXEC).interp("/lib/ld-musl-x86_64.so.1").build());
let coord = Coordinate { path: "bin/gunnar".into(), ..Default::default() };
let r = check(&f, &a_host(), &coord)
.into_iter()
.find(|r| matches!(r, ElfRefusal::LibcInterpreterMissing { .. }))
.expect("elf-libc must fire for a missing interpreter");
let text = r.to_string();
assert!(text.contains("ld-musl"), "{text}");
assert!(text.contains("start and die"), "the sentence must say WHY: {text}");
}
#[test]
fn an_interpreter_the_box_has_is_not_refused() {
let f = facts_of(
&Builder::default().etype(ET_EXEC).interp("/lib64/ld-linux-x86-64.so.2").build(),
);
let coord = Coordinate { path: "bin/x".into(), ..Default::default() };
assert!(!check(&f, &a_host(), &coord).iter().any(|r| r.name() == "elf-libc"));
}
#[test]
fn elf_cpu_refuses_a_level_above_this_cpu() {
let f = facts_of(
&Builder::default()
.note(".note.gnu.property", b"GNU", 5, &property_desc(0xc000_8002, 0b1000))
.build(),
);
let coord = Coordinate { path: "bin/x".into(), ..Default::default() };
let r = check(&f, &a_host(), &coord)
.into_iter()
.find(|r| matches!(r, ElfRefusal::Cpu { .. }))
.expect("elf-cpu must fire");
assert_eq!(r.name(), "elf-cpu");
let text = r.to_string();
assert!(text.contains("x86-64-v4") && text.contains("x86-64-v2"), "{text}");
// it must NOT read as policy — a CPU limit is not a rule somebody chose
assert!(text.contains("host limit, not a policy"), "{text}");
}
#[test]
fn elf_cpu_does_not_refuse_a_level_at_or_below_this_cpu() {
let f = facts_of(
&Builder::default()
.note(".note.gnu.property", b"GNU", 5, &property_desc(0xc000_8002, 0b10))
.build(),
);
let coord = Coordinate { path: "bin/x".into(), ..Default::default() };
assert!(!check(&f, &a_host(), &coord).iter().any(|r| r.name() == "elf-cpu"));
}
#[test]
fn elf_recipe_refuses_a_binary_that_says_nothing_about_how_it_was_built() {
let f = facts_of(&Builder::default().etype(ET_EXEC).build());
let coord = Coordinate { path: "bin/x".into(), ..Default::default() };
let r = check(&f, &a_host(), &coord)
.into_iter()
.find(|r| matches!(r, ElfRefusal::RecipeMissing { .. }))
.expect("elf-recipe must fire");
assert_eq!(r.name(), "elf-recipe");
assert!(r.to_string().contains(".nordisk.buildthing"));
}
#[test]
fn elf_recipe_refuses_when_the_binary_contradicts_the_signed_statement() {
let f = facts_of(&with_receipt(1));
let coord = Coordinate {
path: "bin/gunnar".into(),
expect_toolchain_image_sha256: Some("f29e517c1ba34b920c1234fbd628046d64c0f10".into()),
..Default::default()
};
let r = check(&f, &a_host(), &coord)
.into_iter()
.find(|r| matches!(r, ElfRefusal::RecipeContradicts { field: "toolchain_image_sha256", .. }))
.expect("elf-recipe must fire on a contradiction");
let text = r.to_string();
assert!(text.contains("3d5115ba") && text.contains("f29e517c"), "{text}");
assert!(text.contains("the statement is the authority"), "{text}");
}
#[test]
fn a_recipe_that_agrees_with_the_statement_is_not_refused() {
let f = facts_of(&with_receipt(1));
let coord = Coordinate {
path: "bin/gunnar".into(),
expect_toolchain_image_sha256: Some(
"3d5115baa07d03ca51eb3e44ebb86b4e64e679bd88f38e156ef9da12c35d2143".into(),
),
expect_source_commit: Some("451288a0000000000000000000000000000000aa".into()),
expect_recipe_coordinate: Some("gunnar-build-thing:0.3.1:abc123".into()),
..Default::default()
};
assert!(!check(&f, &a_host(), &coord).iter().any(|r| r.name() == "elf-recipe"));
}
#[test]
fn an_unchecked_toolchain_image_is_recorded_by_name_never_passed_silently() {
// Until BuildProvenance carries the toolchain image (elf-design §6.3(b), an
// owner decision in edda), EVERY artifact is Unchecked — and a check nobody
// can see not running is worse than no check at all.
let f = facts_of(&with_receipt(1));
let coord = Coordinate { path: "bin/gunnar".into(), ..Default::default() };
assert_eq!(crate::refusal::recipe_verdict(&f, &coord), RecipeVerdict::Unchecked);
assert_eq!(RecipeVerdict::Unchecked.word(), "unchecked");
// and it is NOT a refusal
assert!(!check(&f, &a_host(), &coord).iter().any(|r| r.name() == "elf-recipe"));
let checked = Coordinate {
expect_toolchain_image_sha256: Some(
"3d5115baa07d03ca51eb3e44ebb86b4e64e679bd88f38e156ef9da12c35d2143".into(),
),
..coord
};
assert_eq!(crate::refusal::recipe_verdict(&f, &checked), RecipeVerdict::Match);
}
#[test]
fn every_refusal_is_printed_not_only_the_first() {
// A box that has to be rebooted once per problem is a box nobody keeps.
let f = facts_of(
&Builder::default()
.machine(EM_AARCH64)
.etype(ET_EXEC)
.interp("/lib/ld-musl-aarch64.so.1")
.build(),
);
let coord = Coordinate {
path: "target/x86_64-unknown-linux-gnu/release/gunnar".into(),
linkage: Some(Linkage::Static),
..Default::default()
};
let names: Vec<&str> = check(&f, &a_host(), &coord).iter().map(|r| r.name()).collect();
assert!(names.contains(&"elf-machine"), "{names:?}");
assert!(names.contains(&"elf-libc"), "{names:?}");
assert!(names.contains(&"elf-recipe"), "{names:?}");
assert!(names.len() >= 4, "{names:?}");
}
#[test]
fn the_column_and_the_refusal_are_one_rule_seen_twice() {
// arch_lies = 1 in the index and elf-machine at start must never disagree.
let data = Builder::default().machine(EM_AARCH64).build();
let path = "target/x86_64-unknown-linux-gnu/release/gunnar";
let lies = n(&row(path, &data), "arch_lies") == Some(1);
let refused = check(
&facts_of(&data),
&a_host(),
&Coordinate { path: path.into(), ..Default::default() },
)
.iter()
.any(|r| matches!(r, ElfRefusal::Machine { whose: "coordinate", .. }));
assert_eq!(lies, refused);
}
#[test]
fn the_host_detects_itself_without_shelling_out() {
let h = HostFacts::detect();
assert!(!h.machine.is_empty());
// On this lane's box the ELF this test runs in is for this machine.
assert_eq!(h.machine, "x86-64");
}