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

nordisk/dwarves

ci/verbs/tests/coverage_verb.rs

//! The nightly coverage step (OSSF-NEXT, silver prep): `ci-verbs coverage` runs
//! `cargo llvm-cov` over a checkout's chosen packages and writes the numbers the CI
//! status page shows: the totals (lines, functions, regions) and one row per crate.

use std::path::Path;

use ci_verbs::coverage;
use serde_json::json;

fn export() -> serde_json::Value {
    // the shape `cargo llvm-cov --json --summary-only` writes (llvm-cov export 2.0.1)
    let f = |name: &str, lc: u64, ln: u64, fc: u64, fnn: u64| {
        json!({ "filename": name, "summary": {
            "lines": { "count": ln, "covered": lc, "percent": 100.0 * lc as f64 / ln as f64 },
            "functions": { "count": fnn, "covered": fc, "percent": 100.0 * fc as f64 / fnn as f64 },
            "regions": { "count": ln * 2, "covered": lc * 2, "percent": 100.0 * lc as f64 / ln as f64 }
        }})
    };
    json!({
        "type": "llvm.coverage.json.export",
        "version": "2.0.1",
        "data": [{
            "files": [
                f("/src/gunnar/crates/gunnar-wire/src/pkt.rs", 90, 100, 9, 10),
                f("/src/gunnar/crates/gunnar-wire/src/pack.rs", 30, 100, 3, 10),
                f("/src/gunnar/crates/gunnar-crypt/src/lib.rs", 80, 100, 8, 10),
                f("/src/gunnar/xtask/src/main.rs", 0, 50, 0, 5),
                f("/home/u/.cargo/registry/src/x/lib.rs", 1, 1, 1, 1)
            ],
            "totals": {
                "lines": { "count": 350, "covered": 200, "percent": 57.142857 },
                "functions": { "count": 35, "covered": 20, "percent": 57.142857 },
                "regions": { "count": 700, "covered": 400, "percent": 57.142857 }
            }
        }]
    })
}

#[test]
fn the_totals_and_one_row_per_crate_come_from_the_export() {
    let e = export();
    let t = coverage::totals(&e).unwrap();
    assert_eq!((t.lines.covered, t.lines.count), (200, 350));
    assert_eq!((t.functions.covered, t.functions.count), (20, 35));
    assert_eq!((t.regions.covered, t.regions.count), (400, 700));
    assert!((t.lines.percent() - 57.14).abs() < 0.01);

    let rows = coverage::per_crate(&e, Path::new("/src/gunnar"));
    let names: Vec<&str> = rows.iter().map(|r| r.name.as_str()).collect();
    assert_eq!(names, ["crates/gunnar-crypt", "crates/gunnar-wire", "xtask"], "files outside the checkout are not a crate of it");
    let wire = &rows[1];
    assert_eq!((wire.lines.covered, wire.lines.count), (120, 200));
    assert!(coverage::totals(&json!({ "data": [] })).is_err(), "an empty export is refused");
}

#[test]
fn the_command_is_cargo_llvm_cov_over_the_named_packages() {
    let a = coverage::args(&["gunnar-wire".into(), "gunnar-crypt".into()], &[], Path::new("/o/llvm-cov-export.data"));
    let line = a.join(" ");
    assert!(line.starts_with("llvm-cov --json --summary-only --output-path /o/llvm-cov-export.data"), "{line}");
    assert!(line.contains("-p gunnar-wire -p gunnar-crypt"), "{line}");
    assert!(!line.contains("--workspace"), "{line}");
    let all = coverage::args(&[], &["metered".into()], Path::new("/o/x")).join(" ");
    assert!(all.contains("--workspace") && all.contains("--features metered"), "{all}");
}

#[test]
fn a_skipped_test_is_handed_to_every_test_binary() {
    let a = coverage::args_skipping(&[], &[], &["needs_podman".into(), "needs_a_tag".into()], Path::new("/o/x"));
    let line = a.join(" ");
    assert!(line.ends_with("--workspace -- --skip needs_podman --skip needs_a_tag"), "{line}");
    let none = coverage::args_skipping(&[], &[], &[], Path::new("/o/x"));
    assert_eq!(none, coverage::args(&[], &[], Path::new("/o/x")), "no skip, no `--`");
}

#[test]
fn the_page_shows_the_numbers_and_a_floor_turns_a_drop_red() {
    let e = export();
    let t = coverage::totals(&e).unwrap();
    let rows = coverage::per_crate(&e, Path::new("/src/gunnar"));
    let md = coverage::summary("gunnar", "abcdef0123456789", &t, &rows, Some(50.0));
    assert!(md.contains("**57.1 %** of lines"), "{md}");
    assert!(md.contains("| crates/gunnar-wire | 60.0 % (120 / 200) |"), "{md}");
    assert!(md.contains("`abcdef01`"), "{md}");
    assert!(coverage::below_floor(&t, Some(50.0)).is_none());
    let why = coverage::below_floor(&t, Some(80.0)).expect("57 % is under an 80 % floor");
    assert!(why.contains("80"), "{why}");
    assert!(coverage::below_floor(&t, None).is_none(), "no floor, never red for the number");
}

#[test]
fn the_verb_says_usage_and_names_a_missing_cargo_llvm_cov() {
    assert_eq!(coverage::verb(&[]), 2);
    let d = std::env::temp_dir().join(format!("ci-verbs-cov-{}", std::process::id()));
    std::fs::create_dir_all(&d).unwrap();
    let args: Vec<String> = ["--checkout", d.to_str().unwrap(), "--out", d.join("out").to_str().unwrap(), "--cargo", "/nonexistent/cargo"]
        .iter()
        .map(|s| s.to_string())
        .collect();
    assert_eq!(coverage::verb(&args), 1);
    let md = std::fs::read_to_string(d.join("out/summary.md")).unwrap();
    assert!(md.contains("cargo llvm-cov"), "the page says what could not run: {md}");
    let _ = std::fs::remove_dir_all(d);
}

#[test]
fn a_crate_is_the_nearest_directory_with_a_manifest() {
    // Holger keeps crates at server/lib and server/inspect: one row each, not one "server"
    let d = std::env::temp_dir().join(format!("ci-verbs-cov-crates-{}", std::process::id()));
    for c in ["server/lib", "server/inspect", "traits"] {
        std::fs::create_dir_all(d.join(c).join("src")).unwrap();
        std::fs::write(d.join(c).join("Cargo.toml"), "[package]\n").unwrap();
    }
    let f = |p: &str| json!({ "filename": d.join(p).display().to_string(), "summary": { "lines": { "count": 10, "covered": 5 } } });
    let e = json!({ "data": [{ "files": [f("server/lib/src/a.rs"), f("server/inspect/src/b.rs"), f("traits/src/c.rs")], "totals": {} }] });
    let names: Vec<String> = coverage::per_crate(&e, &d).into_iter().map(|r| r.name).collect();
    assert_eq!(names, ["server/inspect", "server/lib", "traits"]);
    let _ = std::fs::remove_dir_all(d);
}