wsb 0.255.6379

Workspace tools for every project under $PRJ: git across submodules, template stamping, GitHub, and the workbench
Documentation
//! What the strip must never say.

use super::*;

/// Sample until the host reports a processor figure, or give up.
///
/// Polled rather than taken after a fixed wait, because the counters update on
/// the KERNEL's schedule: macOS refreshes `HOST_CPU_LOAD_INFO` about twice a
/// second, so a quarter-second window is often byte-identical and yields no
/// figure at all. Measured: four windows in twenty produced nothing.
///
/// A test written as "wait, then demand" therefore passed most of the time,
/// which is the worst thing a test can do. This one asserts what is actually
/// true — that the host does report processor use — and lets the clock take as
/// long as it takes.
fn cpu_within(deadline: std::time::Duration) -> Option<f32> {
    let mut sampler = Sampler::new();
    let give_up = std::time::Instant::now() + deadline;
    let mut spun: u64 = 0;
    loop {
        // Real work, so there is busy time to report and not only idle.
        let until = std::time::Instant::now() + std::time::Duration::from_millis(120);
        while std::time::Instant::now() < until {
            spun = spun.wrapping_add(1).wrapping_mul(2_654_435_761);
        }
        assert!(spun != 1, "the loop must not be optimised away");
        if let Some(cpu) = sampler.sample(Path::new(".")).cpu {
            return Some(cpu);
        }
        if std::time::Instant::now() >= give_up {
            return None;
        }
    }
}

/// TEST9031: a fraction is a fraction, including when there is nothing there.
#[test]
fn test9031_a_fraction_never_divides_by_nothing() {
    let empty = Fraction { used: 0, total: 0 };
    assert_eq!(empty.ratio(), 0.0, "a total of nothing is not an error");
    assert_eq!(empty.free(), 0);

    let half = Fraction {
        used: 4 * 1024,
        total: 8 * 1024,
    };
    assert!((half.ratio() - 0.5).abs() < f32::EPSILON);
    assert_eq!(half.free(), 4 * 1024);

    // More used than there is: a filesystem can report this, and it must not
    // produce a negative amount of free space.
    let over = Fraction {
        used: 10,
        total: 8,
    };
    assert_eq!(over.free(), 0);
}

/// TEST9032: the first sample has no processor figure, and says so.
///
/// Processor use is a delta between two readings. The first one has nothing to
/// compare against, and a graph that started at zero would report an idle
/// machine — a claim, and the wrong one — for as long as it took the second
/// sample to arrive.
///
/// The second reading is taken after WORK, not after a sleep. The counters
/// tick per core at a coarse rate — 120ms of idle on a quiet Mac produced
/// under a hundred ticks and sometimes none — so a test that slept and then
/// demanded a figure was a test that passed most of the time. Busy time is
/// what the counter counts, so the test makes some.
#[test]
fn test9032_the_first_sample_reports_no_processor_figure() {
    let mut sampler = Sampler::new();
    let first = sampler.sample(Path::new("."));
    assert!(
        first.cpu.is_none(),
        "there is no processor figure until there are two readings"
    );

    // The second one has something to compare against.
    //
    // Demanded on BOTH hosts this workspace builds for: Linux keeps the
    // counters in `/proc/stat`, macOS behind `host_statistics64`, and the
    // strip showed only memory and disk on the Mac for as long as this test
    // asked about one of them.
    if cfg!(any(target_os = "linux", target_os = "macos")) {
        let cpu = cpu_within(std::time::Duration::from_secs(5))
            .expect("both hosts keep cumulative processor counters");
        assert!(
            (0.0..=1.0).contains(&cpu),
            "processor use is a fraction, not a percentage: {cpu}"
        );
    }
}

/// TEST9039: the Mac answers for its processor and its GPU.
///
/// A machine that cannot answer shows nothing, which is right — and is also
/// exactly what a broken reader looks like. So the hosts that CAN answer are
/// held to it by name, and this is the test that would have failed while the
/// Mac's strip showed only memory and disk.
#[test]
#[cfg(target_os = "macos")]
fn test9039_macos_answers_for_the_processor_and_the_gpu() {
    let cpu = cpu_within(std::time::Duration::from_secs(5))
        .expect("macOS keeps the counters behind host_statistics");
    assert!((0.0..=1.0).contains(&cpu), "{cpu}");

    let mut sampler = Sampler::new();
    let sample = sampler.sample(Path::new("."));

    // Every Mac has a GPU, and its utilisation is readable without privilege.
    let gpu = sample
        .gpu
        .expect("the accelerator reports its own utilisation");
    assert!((0.0..=1.0).contains(&gpu), "{gpu}");

    // And no VRAM: the GPU shares system memory, so a separate pool would be
    // the same bytes the memory indicator already counts.
    assert!(
        sample.vram.is_none(),
        "unified memory has no separate pool to report"
    );
}

/// TEST9033: every figure this host answers is in range, and the rest are absent.
///
/// Absent is the point: a machine with no discrete GPU has no VRAM, and a
/// graph pinned at the bottom would say "idle" about a thing that is not
/// there.
#[test]
fn test9033_every_answer_is_a_fraction_or_nothing() {
    let mut sampler = Sampler::new();
    let sample = sampler.sample(Path::new("."));

    for (name, fraction) in [
        ("memory", sample.memory),
        ("vram", sample.vram),
        ("disk", sample.disk),
    ] {
        let Some(fraction) = fraction else { continue };
        assert!(fraction.total > 0, "{name} reported a total of nothing");
        assert!(
            (0.0..=1.0).contains(&fraction.ratio()),
            "{name} is out of range: {}",
            fraction.ratio()
        );
    }
    for (name, load) in [("cpu", sample.cpu), ("gpu", sample.gpu)] {
        let Some(load) = load else { continue };
        assert!((0.0..=1.0).contains(&load), "{name} is out of range: {load}");
    }

    // The disk figure is about the volume it was ASKED about, so it must
    // answer for an ordinary path on any host.
    if let Some(disk) = sample.disk {
        assert!(disk.total > 0, "the workspace is on a volume with a size");
    }
}

/// TEST9034: the disk figure is about the volume the path is on.
///
/// The workspace can be on an external disk, and it is that disk a build
/// fills. Reporting the root volume would be an answer to a question nobody
/// asked.
#[test]
fn test9034_the_disk_is_the_one_the_path_is_on() {
    let root = disk(Path::new("/"));
    let temporary = disk(&std::env::temp_dir());
    // Both answer, whatever they answer — a path always has a volume.
    assert!(root.is_some(), "/ is on a volume");
    assert!(temporary.is_some(), "the temp directory is on a volume");
}