metrale_bench/gate/
record_write.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-26: Writing one gate record without overwriting a failing one.
4//!
5//! Record names start `<date>-<sha>`, so a same-day re-run at the same commit
6//! maps to the same name. When that name holds a FAIL, the new record is
7//! written beside it under the re-run name
8//! ([`super::record_path::rerun_path`]). `records_newest_first` orders by
9//! `recorded_at`, so the newest record still decides the gate.
10//!
11//! Owner: bench gate (records).
12//! Invariants:
13//! - `write_record` does not replace a file that holds a FAIL record or that
14//!   does not parse as a record. The check runs just before the write and
15//!   takes no lock.
16
17use std::path::{Path, PathBuf};
18
19use anyhow::{Context, Result, bail};
20
21use super::gate_dir;
22use super::record::{GateRecord, read_record, record_path_for};
23use super::record_path::rerun_path;
24
25/// 2026-09-26: Write one gate record and return its path. Creates the parent
26/// directory; commits nothing. The path is `record_path_for` unless that file
27/// holds a FAIL, in which case it is the re-run name. Errors when the re-run
28/// name holds a FAIL too, or when either file exists but is not a readable
29/// record.
30pub fn write_record(root: &Path, record: &GateRecord) -> Result<PathBuf> {
31    let path = preserving_path(&record_path_for(root, record), record)?;
32    std::fs::create_dir_all(path.parent().expect("record path has a parent")).with_context(
33        || {
34            format!(
35                "creating {}",
36                gate_dir(root, &record.benchmark_id).display()
37            )
38        },
39    )?;
40    let json = serde_json::to_string_pretty(record).context("serializing the gate record")?;
41    std::fs::write(&path, json + "\n").with_context(|| format!("writing {}", path.display()))?;
42    Ok(path)
43}
44
45/// 2026-09-26: `canonical` when it is free or holds a record that is not a
46/// FAIL; the re-run name when it holds a FAIL; an error when the re-run name
47/// holds a FAIL too.
48fn preserving_path(canonical: &Path, record: &GateRecord) -> Result<PathBuf> {
49    if !holds_failure(canonical)? {
50        return Ok(canonical.to_path_buf());
51    }
52    let rerun = rerun_path(canonical, record.recorded_at);
53    if holds_failure(&rerun)? {
54        bail!(
55            "{} and {} both hold FAILING records for this commit; neither is overwritten. \
56             A failing record is evidence — remove one with `git rm` if it must go.",
57            canonical.display(),
58            rerun.display()
59        );
60    }
61    Ok(rerun)
62}
63
64/// 2026-09-26: Whether `path` holds a record whose verdict is FAIL. A missing
65/// file is `false`; a file that exists but does not parse is an error, so it
66/// is not overwritten.
67fn holds_failure(path: &Path) -> Result<bool> {
68    if !path.exists() {
69        return Ok(false);
70    }
71    let existing = read_record(path).with_context(|| {
72        format!(
73            "{} exists but is not a readable gate record, so it is not overwritten",
74            path.display()
75        )
76    })?;
77    Ok(existing.verdict.as_deref() == Some("FAIL"))
78}
79
80#[cfg(test)]
81#[path = "record_write_tests.rs"]
82mod record_write_tests;