diff --git a/issues/a-metal-failure-drops-every-row-its-boot-measured.md b/issues/a-metal-failure-drops-every-row-its-boot-measured.md deleted file mode 100644 index 0dabcc5249e..00000000000 --- a/issues/a-metal-failure-drops-every-row-its-boot-measured.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -status: open -kind: tooling -opened: 2026-09-30 ---- - -# A metal failure drops every row its boot measured - -`tests/common/metal.rs`'s `judge_readbacks` puts boot labels in `failed`, and -`Readback::measured` records a number with no test attached. One red test or -shared member riding a boot therefore keeps every number that boot measured -out of the record, and a number never recorded is never judged. A runner that -carries every test in one session would drop every row of the session for one -red test. - -It already costs rows. On the T14's full run at `0d2dda66f`, `testcases`, -`shared`, `lancase` and `lantalkcase` each carried a known red, and -`tests/metal/lenovo-20w0003amz.toml` holds no row of theirs, so nothing -judges their numbers while those reds stand. - -## Exit condition - -Each number is recorded against its owner, and only that owner fails. diff --git a/issues/the-licence-step-re-locks-std-against-the-registry-on-every-run.md b/issues/the-licence-step-re-locks-std-against-the-registry-on-every-run.md new file mode 100644 index 00000000000..d427d4e47eb --- /dev/null +++ b/issues/the-licence-step-re-locks-std-against-the-registry-on-every-run.md @@ -0,0 +1,28 @@ +--- +status: open +kind: tooling +opened: 2026-10-09 +--- + +# The licence step re-locks the std workspace against the registry on every run, so `host` reds without the network and judges whatever the registry serves that day + +`licence::judge` (`src/licence.rs`) copies the fork's `library/Cargo.lock` +into `target/licence/` and runs `cargo metadata` over the std workspace with +`resolver.lockfile-path` pointing at the copy, with neither `--locked` nor +`--offline`. The copy is stale by design, since bootstrap re-locks it to this +tree's `toyos-abi` and `toyos` versions, so cargo resolves it again and asks +the registry each time. Two things follow. `cargo run -- --ci host` cannot +pass on a machine without the network, though nothing else in it needs one. +And the set of crates whose licences the step judges is not pinned by anything +in the tree: it moves with what the registry answers on the day of the run. + +Evidence: during a network outage on the development machine, the `--ci host` +runs of two branches were each red in this one step, `the licences of what +ships`, 77 of 78 steps green, the step's `cargo metadata` failing to resolve +the registry's host name after its retries; both were green on the same heads +once the registry answered again. Neither branch touched anything the step +reads. + +**Exit**: `the licences of what ships` passes with the network off, shown by a +check that runs it so, and what it resolves for the std workspace is a +function of the tree alone. Owner: the build system (`src/licence.rs`). diff --git a/src/metaltimings.rs b/src/metaltimings.rs index de930057aee..e22a7cf5337 100644 --- a/src/metaltimings.rs +++ b/src/metaltimings.rs @@ -4,9 +4,9 @@ //! One file per machine under [`DIR`], named for its SMBIOS vendor and product, //! written by a run and committed by whoever ran it. A run on a machine with no //! record is recorded and not judged; a name the record lacks is added and not -//! judged, and only off a boot with no failure of its own; a recorded value is -//! never moved by a run, so a slow run cannot become the baseline the next is -//! judged by. A run under a BIOS other than the record's is judged against it, +//! judged, and only where its owner passed, the boot or the row that measured +//! it; a recorded value is never moved by a run, so a slow run cannot become +//! the baseline the next is judged by. A run under a BIOS other than the record's is judged against it, //! fails naming both, and records nothing. Deleting a row is how a number is //! re-recorded, and deleting the file is how a machine is, firmware and all. //! @@ -136,8 +136,8 @@ enum Unread { Refused(String), } -/// One number a run measured, and whether the boot that measured it passed: -/// every reading is judged, and only a passing boot's becomes a record. +/// One number a run measured, and whether its owner passed: every reading is +/// judged, and only a passing owner's becomes a record. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Reading { pub value: u64, @@ -386,9 +386,9 @@ mod tests { ); } - /// **A failed boot's numbers are judged and never become a baseline.** + /// **A failed owner's numbers are judged and never become a baseline.** #[test] - fn a_failing_boots_reading_is_judged_and_not_recorded() { + fn a_failing_owners_reading_is_judged_and_not_recorded() { let machine = t14(BIOS); let record = recorded(&machine, &[("boot.a.complete_ms", 1000)]); let failed = |value| Reading { value, passed: false }; diff --git a/tests/checks.rs b/tests/checks.rs index f9535023c6a..9156ac12a4e 100644 --- a/tests/checks.rs +++ b/tests/checks.rs @@ -872,8 +872,8 @@ mod checks { } #[test] - fn metal_boot_with_a_failure_of_its_own_adds_no_row() { - metal_checks::a_boot_with_a_failure_of_its_own_adds_no_row(); + fn metal_number_fails_with_its_owner_alone() { + metal_checks::a_number_fails_with_its_owner_alone(); } #[test] @@ -882,8 +882,8 @@ mod checks { } #[test] - fn metal_name_two_boots_measured_is_refused() { - metal_checks::a_name_two_boots_measured_is_refused(); + fn metal_name_two_owners_measured_is_refused() { + metal_checks::a_name_two_owners_measured_is_refused(); } #[test] @@ -897,8 +897,8 @@ mod checks { } #[test] - fn metal_failing_shared_member_fails_its_boot() { - metal_checks::a_failing_shared_member_fails_its_boot(); + fn metal_failing_shared_member_fails_itself_alone() { + metal_checks::a_failing_shared_member_fails_itself_alone(); } #[test] diff --git a/tests/checks/metal.rs b/tests/checks/metal.rs index e8454463d16..5670ce65074 100644 --- a/tests/checks/metal.rs +++ b/tests/checks/metal.rs @@ -167,50 +167,93 @@ fn unqualified(b: &[&Readback]) -> Result<(), String> { b[0].measured("latency.p99_us", 16) } +fn unqualified_and_fail(b: &[&Readback]) -> Result<(), String> { + unqualified(b)?; + Err("a planted failure".to_string()) +} + static PASSING: Metal = Metal { arms: &[metal::once("passing", "tests/jobcase", &[], &[])], judge: span }; static FAILING: Metal = Metal { arms: &[metal::once("failing", "tests/jobcase", &[], &[])], judge: span_and_fail, }; +static BESIDE: Metal = Metal { + arms: &[metal::once("failing", "tests/jobcase", &[], &[])], + judge: |b| b[0].measured("beside.failing.us", 9), +}; static REFUSED: Metal = Metal { arms: &[metal::once("refused", "tests/jobcase", &[], &[])], judge: span }; static LATE: Metal = Metal { arms: &[metal::once("late", "tests/jobcase", &[], &[])], judge: span }; +static SKEWED: Metal = + Metal { arms: &[metal::once("skewed", "tests/jobcase", &[], &[])], judge: span }; static ONE: Metal = Metal { arms: &[metal::once("one", "tests/jobcase", &[], &[])], judge: unqualified }; +static ONE_FAILS: Metal = Metal { + arms: &[metal::once("one", "tests/jobcase", &[], &[])], + judge: unqualified_and_fail, +}; static TWO: Metal = Metal { arms: &[metal::once("two", "tests/jobcase", &[], &[])], judge: unqualified }; -/// **The record is one function of the readbacks.** A boot the loop refused, a -/// boot a riding test failed and a boot whose own check failed are each -/// judged, and none adds a row; the one boot with no failure of its own is -/// recorded whole. -pub fn a_boot_with_a_failure_of_its_own_adds_no_row() { +/// **A number is recorded off its owner's verdict and nobody else's.** A row +/// that fails keeps its own number out of the record, and neither its boot's +/// numbers nor those of the row beside it on that boot; a boot whose own check +/// fails keeps its own out, and not those of the row that passed on it, unless +/// the check is of its clocks, which every duration on it was read by; a boot +/// the loop refused measured nothing. +pub fn a_number_fails_with_its_owner_alone() { let dir = toyos_tmpdir::TempDir::new("metal-readbacks"); let root = toyos_tmpdir::TempDir::new("metal-records"); plant(&dir, "passing", PANEL, BOOTED, None); plant(&dir, "failing", PANEL, BOOTED, None); plant(&dir, "refused", PANEL, BOOTED, Some(&Refusal::HungWithoutARecord)); plant(&dir, "late", &expired_at(120_160), &armed(), None); - let readbacks = read(&dir, &["passing", "failing", "refused", "late"]); + // The supervisor's line 10 ms before the spawn it reports. + plant(&dir, "skewed", PANEL, BOOTED, None); + let at = metal::at(&dir, "skewed").join(READBACK_KERNEL); + let kernel = fs::read_to_string(&at).expect("a planted log"); + fs::write(&at, kernel.replacen("0.050 supervisor]", "0.040 supervisor]", 1)).expect("a planted log"); + let readbacks = read(&dir, &["passing", "failing", "refused", "late", "skewed"]); + assert!(readbacks["skewed"].as_ref().expect("a readback").one_clock().is_err()); let why = readbacks["refused"].as_ref().err().expect("the loop's refusal, read back"); assert!(why.contains("never reported: no panic"), "{why}"); - let tests = [("passes", &PASSING), ("fails", &FAILING), ("refused", &REFUSED), ("late", &LATE)]; + let tests = [ + ("passes", &PASSING), + ("fails", &FAILING), + ("beside", &BESIDE), + ("refused", &REFUSED), + ("late", &LATE), + ("skewed", &SKEWED), + ]; let runs: Vec<&(&str, &'static Metal)> = tests.iter().collect(); - assert!(metal::judge_readbacks(&root, &readbacks, &runs, &[]), "three failed boots judged green"); + assert!(metal::judge_readbacks(&root, &readbacks, &runs, &[]), "four failures judged green"); let record = Record::load(&root, &t14()).expect("a readable record").expect("a record"); let names: Vec<&str> = record.measured.keys().map(String::as_str).collect(); assert_eq!( names, [ + "beside.failing.us", + "boot.failing.complete_ms", + "boot.failing.panel_max_us", + "boot.failing.panel_us", "boot.passing.complete_ms", "boot.passing.panel_max_us", "boot.passing.panel_us", + "span.late.us", "span.passing.us" ] ); + // The row's own failure is red with nothing else beside it. + let alone = [("fails", &FAILING)]; + let runs: Vec<&(&str, &'static Metal)> = alone.iter().collect(); + let root = toyos_tmpdir::TempDir::new("metal-records"); + assert!( + metal::judge_readbacks(&root, &read(&dir, &["failing"]), &runs, &[]), + "a failing row judged green" + ); } static HOLED: Metal = @@ -250,20 +293,22 @@ pub fn a_boot_that_lost_parts_of_its_log_judges_no_row() { assert!(!record.measured.keys().any(|name| name.contains("holed")), "{:?}", record.measured); } -/// Two boots under one name are judged on the first reading and recorded off -/// neither. -pub fn a_name_two_boots_measured_is_refused() { +/// Two rows under one name are judged on the first reading and recorded off +/// neither, whether the first of them passed or failed. +pub fn a_name_two_owners_measured_is_refused() { let dir = toyos_tmpdir::TempDir::new("metal-readbacks"); - let root = toyos_tmpdir::TempDir::new("metal-records"); plant(&dir, "one", PANEL, BOOTED, None); plant(&dir, "two", PANEL, BOOTED, None); let readbacks = read(&dir, &["one", "two"]); - let tests = [("one", &ONE), ("two", &TWO)]; - let runs: Vec<&(&str, &'static Metal)> = tests.iter().collect(); - assert!(metal::judge_readbacks(&root, &readbacks, &runs, &[]), "one name twice judged green"); - let record = Record::load(&root, &t14()).expect("a readable record").expect("a record"); - assert!(!record.measured.contains_key("latency.p99_us"), "{:?}", record.measured); - assert_eq!(record.measured.len(), 6, "{:?}", record.measured); + for first in [&ONE, &ONE_FAILS] { + let root = toyos_tmpdir::TempDir::new("metal-records"); + let tests = [("one", first), ("two", &TWO)]; + let runs: Vec<&(&str, &'static Metal)> = tests.iter().collect(); + assert!(metal::judge_readbacks(&root, &readbacks, &runs, &[]), "one name twice judged green"); + let record = Record::load(&root, &t14()).expect("a readable record").expect("a record"); + assert!(!record.measured.contains_key("latency.p99_us"), "{:?}", record.measured); + assert_eq!(record.measured.len(), 6, "{:?}", record.measured); + } } /// `passing`'s four rows under `bios`: its planted readings, with `complete_ms` @@ -325,10 +370,10 @@ pub fn a_run_under_another_bios_fails_and_records_nothing() { assert_eq!(judged_against(&lacking(&bios)), (false, committed(&bios, 1165))); } -/// **A failing shared member fails its boot**: the run is red, and that boot -/// adds no row while the shared boot whose members all passed is recorded -/// whole. -pub fn a_failing_shared_member_fails_its_boot() { +/// **A failing shared member fails itself alone**: the run is red, and the +/// boot it rode records its own numbers as the boot whose members all passed +/// does. +pub fn a_failing_shared_member_fails_itself_alone() { let dir = toyos_tmpdir::TempDir::new("metal-readbacks"); let root = toyos_tmpdir::TempDir::new("metal-records"); let jobs = ["test_rs_std_tls", "test_rs_fs_large_file"]; @@ -360,7 +405,14 @@ pub fn a_failing_shared_member_fails_its_boot() { let names: Vec<&str> = record.measured.keys().map(String::as_str).collect(); assert_eq!( names, - ["boot.passing.complete_ms", "boot.passing.panel_max_us", "boot.passing.panel_us"] + [ + "boot.failing.complete_ms", + "boot.failing.panel_max_us", + "boot.failing.panel_us", + "boot.passing.complete_ms", + "boot.passing.panel_max_us", + "boot.passing.panel_us" + ] ); } diff --git a/tests/common/metal.rs b/tests/common/metal.rs index 21190c3bce2..0c440f498d9 100644 --- a/tests/common/metal.rs +++ b/tests/common/metal.rs @@ -235,7 +235,8 @@ pub struct Readback { pub stick_secs: u64, /// The machine the loop read before the flash. pub machine: Result, - /// What this boot's judges measured, for the machine's record to judge. + /// What was measured on this boot and no owner has taken yet + /// ([`Self::taken`]). numbers: RefCell>, } @@ -462,9 +463,11 @@ impl Readback { }) } - /// One number this boot measured, judged by [`run`] against this machine's - /// record once every judge has spoken. A second value under one name is - /// refused: which of the two a record kept would be nobody's reading. + /// One number measured on this boot. It is its measurer's, the row whose + /// judge called this or the boot itself for what [`judge_readbacks`] reads + /// off every boot, and is judged against this machine's record once every + /// judge has spoken. A second value under one name is refused: which of + /// the two a record kept would be nobody's reading. pub fn measured(&self, name: &str, value: u64) -> Result<(), String> { match self.numbers.borrow_mut().entry(name.to_string()) { Entry::Vacant(slot) => { @@ -479,6 +482,11 @@ impl Readback { } } + /// What was measured on this boot since this was last asked. + fn taken(&self) -> BTreeMap { + std::mem::take(&mut self.numbers.borrow_mut()) + } + /// The job ran and the kernel recorded it exiting cleanly. pub fn job_passed(&self, binary: &str) -> Result<(), String> { match self.exit_code(binary)? { @@ -1005,10 +1013,42 @@ pub fn run( } } +/// What `backs` measured since they were last asked becomes one owner's: judged +/// against the record whatever the owner's verdict, and a new row of it only +/// where the owner `passed`. A name a second owner measures is judged on the +/// first reading and recorded off neither; answers whether one was. +fn own( + measured: &mut BTreeMap, + owner: &str, + passed: bool, + backs: &[&Readback], +) -> bool { + let mut twice = false; + for (name, value) in backs.iter().flat_map(|back| back.taken()) { + match measured.entry(name) { + Entry::Vacant(slot) => { + slot.insert(Reading { value, passed }); + } + Entry::Occupied(mut first) => { + eprintln!(" FAIL {} is measured twice, and {owner} is the second", first.key()); + first.get_mut().passed = false; + twice = true; + } + } + } + twice +} + /// Every verdict a run's readbacks carry, and this machine's record judged by -/// them and added to off the boots that passed: one function of the readbacks, -/// whether the loop wrote them a moment ago or a run long past did. Answers -/// whether anything was red. +/// them: one function of the readbacks, whether the loop wrote them a moment +/// ago or a run long past did. Answers whether anything was red. +/// +/// **A number is its owner's, and fails with its owner alone.** What a boot +/// reads off itself is the boot's, and stands or falls with the boot's own +/// checks; what a row's judge measured is the row's, and stands or falls with +/// that judge, and with the clocks of every boot it rode agreeing +/// ([`Readback::one_clock`]). Neither is moved by another row or a shared +/// member riding the same boot. pub fn judge_readbacks( root: &Path, readbacks: &BTreeMap>, @@ -1016,15 +1056,13 @@ pub fn judge_readbacks( shared: &[SharedBoot], ) -> bool { let mut red = false; - // **A boot with any failure of its own adds no row**, whether the loop, - // the boot's own checks, or a test or member riding it failed. - let mut failed: BTreeSet<&str> = BTreeSet::new(); + let mut measured: BTreeMap = BTreeMap::new(); eprintln!("\n[metal] the boots"); for (label, back) in readbacks { let back = match back { Err(why) => { eprintln!(" FAIL {label}: {why}"); - failed.insert(label); + red = true; continue; } Ok(back) => back, @@ -1072,9 +1110,9 @@ pub fn judge_readbacks( for why in &findings { eprintln!(" FAIL {why}"); } - if !findings.is_empty() { - failed.insert(label); - } + let whole = findings.is_empty(); + let twice = own(&mut measured, &format!("the boot {label}"), whole, &[back]); + red |= twice || !whole; } eprintln!("\n[metal] the tests"); @@ -1093,16 +1131,22 @@ pub fn judge_readbacks( Some(why) => Err(why), None => judge(&owed), }; - match verdict { + // A clock at another rate scales every duration a row measures. No + // other finding of a boot's own touches a row's number while each is + // read off one line that is present: one summed or maximised over the + // log would fall with a log that is not whole too. + let clocked = owed.iter().all(|back| back.one_clock().is_ok()); + let twice = own(&mut measured, name, verdict.is_ok() && clocked, &owed); + match &verdict { + // `own` said so, by name. + Ok(()) if twice => {} Ok(()) => { eprintln!(" PASS {name}"); passed += 1; } - Err(why) => { - eprintln!(" FAIL {name}: {why}"); - failed.extend(arms.iter().map(|arm| arm.boot)); - } + Err(why) => eprintln!(" FAIL {name}: {why}"), } + red |= twice || verdict.is_err(); } let mut members = 0usize; for boot in shared { @@ -1124,7 +1168,7 @@ pub fn judge_readbacks( // bury the four that matter. Err(why) => { eprintln!(" FAIL {job}: {}", why.lines().next().unwrap_or(&why)); - failed.insert(&boot.boot); + red = true; } } } @@ -1135,27 +1179,8 @@ pub fn judge_readbacks( } } } - red |= !failed.is_empty(); eprintln!("\n[metal] the timings"); - let mut measured: BTreeMap = BTreeMap::new(); - for (label, back) in readbacks { - let Ok(back) = back else { continue }; - let passed = !failed.contains(label.as_str()); - for (name, &value) in back.numbers.borrow().iter() { - match measured.entry(name.clone()) { - Entry::Vacant(slot) => { - slot.insert(Reading { value, passed }); - } - // Judged on the first, and recorded off neither. - Entry::Occupied(mut first) => { - eprintln!(" FAIL {name} is measured by two boots, and {label} is the second"); - first.get_mut().passed = false; - red = true; - } - } - } - } let machine = one_machine(readbacks); let record = machine.as_ref().map_err(Clone::clone).and_then(|machine| Record::load(root, machine)); @@ -1178,7 +1203,7 @@ pub fn judge_readbacks( ); } eprintln!( - " {} number(s) on {} {}, BIOS {}; {} past its record, {} off a boot that failed", + " {} number(s) on {} {}, BIOS {}; {} past its record, {} whose owner failed", measured.len(), machine.vendor, machine.product,