Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/nornir-build-thing/tests/receipt_end_to_end.rs

//! **The receipt, through a real compiler and a real linker, and back out.**
//!
//! Everything in `src/receipt.rs`'s own tests is about the code in isolation. The
//! claim that actually matters cannot be tested there:
//!
//! > a build emits a receipt and it lands in `.nordisk.buildthing` of the built
//! > artifact, survives `--release` + `lto` + `strip`, and comes back out by
//! > parsing rather than running.
//!
//! So this file builds real binaries with `cargo` and reads them back. It is slow
//! (two small rustc invocations) and it is the only thing here that is evidence.
//!
//! # The fixtures have no dependencies, on purpose
//!
//! [`emit_source`] output is written straight into the fixture's `main.rs` rather
//! than through a `build.rs` that path-depends on this crate. Two reasons, and the
//! second is the real one:
//!
//! * a fixture with zero dependencies builds `--offline` with no registry, so this
//!   test does not need a network;
//! * the thing under test is **the generated source**, and putting a build script
//!   in front of it would only add a `fs::write` this file cannot see through.
//!   `emit` itself is four lines over `emit_source` and is unit-tested.
//!
//! The builder itself lives in `tests/common/mod.rs`, shared with
//! `history_end_to_end.rs` — the history tests attest the same bytes these tests
//! read receipts out of, so they must be the same bytes.
//!
//! [`emit_source`]: nornir_build_thing::receipt::emit_source

use std::collections::BTreeMap;
use std::process::Command;

mod common;
use common::build_fixture;

use nornir_build_thing::contract::{Coordinate, RECEIPT_SECTION, RECEIPT_VERSION, Receipt};
use nornir_build_thing::receipt::{emit_source, read_receipt, read_section_named};
use nornir_build_thing::receipt_proof::{ReceiptVerdict, audit_artifact};

/// The receipt the fixture carries. `gpu = false` is the design's own example,
/// and it is true of a binary that links nothing at all — which is what makes the
/// proof below a real measurement rather than a tautology.
fn fixture_receipt(gpu: &str) -> Receipt {
    let mut settings = BTreeMap::new();
    settings.insert("feature.gpu".to_string(), gpu.to_string());
    settings.insert("base".to_string(), "tunnr".to_string());
    let mut inputs = BTreeMap::new();
    inputs.insert("source-commit".to_string(), "9f2c1ab".to_string());
    inputs.insert("target".to_string(), "x86_64-unknown-linux-gnu".to_string());
    Receipt {
        receipt_version: RECEIPT_VERSION,
        build_thing: Coordinate::parse("korp-build-thing:0.3.1:a91f4c2e").unwrap(),
        atom: "korp-server".to_string(),
        target: "exe".to_string(),
        settings,
        inputs,
    }
}

/// **The whole lane in one test**, because the halves are only meaningful against
/// each other: a reader that always answers `Some` proves nothing, and a writer
/// nobody reads back proves nothing either.
///
/// Five assertions, each with a subject that can go red on its own:
///
/// 1. a stripped release binary carries the receipt, byte-identical;
/// 2. a binary built from the same source **without** the emit carries none —
///    which is the red probe for (1);
/// 3. the receipt still reads out after the ELF is re-stamped as aarch64, i.e.
///    from a binary this host cannot execute, and this host really cannot;
/// 4. an unstripped binary with `gpu = false` and no GPU stack is PROVEN;
/// 5. the same binary with `gpu = true` is DISPROVEN — the red probe for (4).
#[test]
fn a_real_build_carries_its_receipt_and_the_binary_is_measured_against_it() {
    let root = tempfile::tempdir().expect("scratch dir");
    let root = root.path();
    let receipt = fixture_receipt("false");
    let prelude = emit_source(&receipt).expect("generate the section source");

    // (1) A stripped, LTO'd release binary still carries the receipt. Data in a
    // retained section is not a symbol, which is the reason to put it there.
    let with = build_fixture(root, "carries", &prelude, true);
    let read = read_receipt(&with)
        .expect("read the section")
        .expect("the artifact must carry a receipt");
    assert_eq!(read, receipt, "the receipt did not survive the link");

    // The bytes in the section are the bytes the writer produced, not merely
    // something that deserialises to an equal value.
    let raw = read_section_named(&with, RECEIPT_SECTION)
        .expect("parse")
        .expect("section present");
    assert_eq!(
        nornir_build_thing::receipt::decode(&raw).unwrap(),
        receipt,
        "the raw section decodes to the same receipt"
    );

    // (2) THE RED PROBE for (1). Same fixture shape, same flags, no emit. If the
    // reader answered `Some` here, assertion (1) would be measuring nothing.
    let without = build_fixture(root, "carriesnot", "", true);
    assert!(
        read_receipt(&without).expect("parse").is_none(),
        "a binary built without the emit must carry NO receipt — otherwise the \
         reader is not reading anything"
    );

    // (3) A binary this host cannot run. The ELF is re-stamped as aarch64
    // (e_machine = EM_AARCH64) — section headers are untouched, so an object
    // PARSER is unaffected while an exec() is not.
    let alien = root.join("alien");
    let mut bytes = std::fs::read(&with).expect("read the fixture binary");
    assert_eq!(&bytes[0..4], b"\x7fELF", "the fixture is an ELF");
    bytes[18] = 0xb7; // EM_AARCH64, little-endian low byte
    bytes[19] = 0x00;
    std::fs::write(&alien, &bytes).expect("write the alien binary");
    let mut perms = std::fs::metadata(&alien).unwrap().permissions();
    std::os::unix::fs::PermissionsExt::set_mode(&mut perms, 0o755);
    std::fs::set_permissions(&alien, perms).unwrap();

    assert_eq!(
        read_receipt(&alien)
            .expect("parse the alien binary")
            .unwrap(),
        receipt,
        "parsing is not running: an aarch64 artifact must still yield its receipt"
    );
    // …and it really is unrunnable here, so the line above is not a claim about a
    // binary that would have executed fine anyway.
    let ran = Command::new(&alien).output();
    assert!(
        ran.is_err() || !ran.as_ref().unwrap().status.success(),
        "the re-stamped binary was expected to be unexecutable on this host, but it ran"
    );

    // (4) The proof. Unstripped this time, so there IS a symbol table to measure.
    let measurable = build_fixture(root, "measurable", &prelude, false);
    let audit = audit_artifact(&measurable);
    assert!(audit.measured, "an unstripped binary has a symbol table");
    assert!(audit.symbols > 0, "and it is not empty: {}", audit.symbols);
    assert_eq!(
        audit.verdict(),
        ReceiptVerdict::Proven,
        "gpu = false over a binary that links no GPU stack: {:?}",
        audit.lines()
    );

    // (5) THE RED PROBE for (4). Identical binary, one flipped claim.
    let lying = emit_source(&fixture_receipt("true")).expect("generate");
    let liar = build_fixture(root, "liar", &lying, false);
    let audit = audit_artifact(&liar);
    assert_eq!(
        audit.verdict(),
        ReceiptVerdict::Disproven,
        "gpu = true over a binary linking no GPU stack must FAIL: {:?}",
        audit.lines()
    );
    assert!(
        audit.lines().iter().any(|l| l.contains("vacuous")),
        "the failure says why: {:?}",
        audit.lines()
    );
}

/// **The build.rs path, end to end**: `emit_into` writes a file, an `include!`
/// pulls it in, and the receipt comes out of the linked binary.
///
/// This is what `embed_build_receipt!` does — the macro is that `include!` with
/// the OUT_DIR path filled in, and `emit` is `emit_into` with OUT_DIR read from
/// the environment. Exercising it this way needs no build-dependency on this
/// crate, so the fixture still resolves offline.
#[test]
fn the_emitted_file_compiles_in_through_an_include_and_the_receipt_comes_back() {
    let root = tempfile::tempdir().expect("scratch dir");
    let root = root.path();
    let receipt = fixture_receipt("false");

    let out_dir = root.join("out");
    std::fs::create_dir_all(&out_dir).expect("out dir");
    let generated = nornir_build_thing::receipt::emit_into(&out_dir, &receipt).expect("emit");
    assert_eq!(
        generated.file_name().and_then(|s| s.to_str()),
        Some(nornir_build_thing::receipt::GENERATED_FILE),
        "the writer must use the name the macro includes"
    );

    let prelude = format!("include!({:?});", generated.display().to_string());
    let bin = build_fixture(root, "included", &prelude, true);
    assert_eq!(
        read_receipt(&bin).expect("parse").expect("receipt present"),
        receipt,
        "the include! path must land the same bytes in the same section"
    );
}

/// **A stripped binary proves nothing, and must not report that it proved
/// something.**
///
/// This is the blind-guard shape the receipt is most exposed to: with no symbol
/// table every family counts zero, so every `= false` claim in every receipt
/// would pass — by having nothing to look at. Its own test, because it is the one
/// assertion here that is about an absence.
#[test]
fn a_stripped_binary_is_not_measured_and_therefore_does_not_pass() {
    let root = tempfile::tempdir().expect("scratch dir");
    let root = root.path();
    let receipt = fixture_receipt("false");
    let prelude = emit_source(&receipt).expect("generate");

    let stripped = build_fixture(root, "stripped", &prelude, true);
    let audit = audit_artifact(&stripped);
    assert_eq!(
        audit.receipt.as_ref(),
        Some(&receipt),
        "the receipt is still readable — it is data, not a symbol"
    );
    assert!(
        !audit.measured,
        "a stripped binary has no symbol table, so nothing about it was measured"
    );
    assert_eq!(audit.verdict(), ReceiptVerdict::NotMeasured);
    assert!(
        !audit.holds(),
        "gpu = false must NOT pass merely because the binary has nothing to show: {:?}",
        audit.lines()
    );
}