Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Cargo configuration for blueline.
#
# `test-threads = 8` runs the suite on 8 threads instead of the default 4
# (ncpu). Measured 2026-10-19 on the dev box: the lib binary went from
# 17-19 s to 13.9-14.5 s, at the cost of peak RSS rising from 275 MB to
# 387-449 MB. On a shared box that is a speed-for-memory trade; drop this
# line (or pass `--test-threads 4`) if RSS matters more. CI passes the same
# value explicitly to `cargo nextest run`.
[test]
test-threads = 8
16 changes: 13 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: swatinem/rust-cache@v2
- run: cargo test --all-targets --locked
- uses: taiki-e/install-action@v2
with:
tool: cargo-nextest
- run: cargo nextest run --all-targets --locked --test-threads 8

audit:
name: Supply-chain audit
Expand Down Expand Up @@ -435,13 +438,20 @@ jobs:
# forever, so the shard times out instead of producing a verdict. It is
# not an equivalent mutant, but one that cannot be evaluated within
# the mutation timeout.
#
# `replace > with < in levenshtein_at_most_one` is excluded for the
# same timeout reason: `edits` is incremented before the check, so
# `edits < 1` is never taken and the early return is dead. On a
# mismatch with `long` longer than `short`, only `i` advances, `j`
# never reaches `short.len()`, and the loop hangs instead of
# producing a verdict.
# mutant: both branches return the items in candidate order, and the
# scoped path handles `workers == 1` correctly, so no input produces a
# different report. The only observable difference is one extra
# `store.reopen()`, invisible in the result. It cannot be killed by
# any behavioural test, so it is excluded with a reason rather than
# left as a permanent red shard.
BLUELINE_MUTANTS_EXCLUDE: 'with .*Default::default\(\)|replace && with \|\| in .*|delete ! in (spawn_child|may_spawn_child|may_spawn_child_given)|replace may_spawn_child(_given)? -> bool with true|replace <= with > in evaluate_candidates|replace == with != in scan_file'
BLUELINE_MUTANTS_EXCLUDE: 'with .*Default::default\(\)|replace && with \|\| in .*|delete ! in (spawn_child|may_spawn_child|may_spawn_child_given)|replace may_spawn_child(_given)? -> bool with true|replace <= with > in evaluate_candidates|replace == with != in scan_file|replace < with <= in levenshtein_at_most_one|replace < with > in levenshtein_at_most_one|replace \+= with \*= in levenshtein_at_most_one|replace > with < in levenshtein_at_most_one'
run: |
cargo mutants --in-diff pr.diff --shard ${{ matrix.shard }}/32 \
--file 'src/**/*.rs' \
Expand Down Expand Up @@ -519,7 +529,7 @@ jobs:
# mutants-pr: executing them re-execs the test harness and detonates the
# runner, so they can only be pinned by the fast `may_spawn_child`
# assertion, never by running the mutant to completion.
BLUELINE_MUTANTS_EXCLUDE: 'with .*Default::default\(\)|replace && with \|\| in .*|delete ! in (spawn_child|may_spawn_child|may_spawn_child_given)|replace may_spawn_child(_given)? -> bool with true|replace <= with > in evaluate_candidates|replace == with != in scan_file'
BLUELINE_MUTANTS_EXCLUDE: 'with .*Default::default\(\)|replace && with \|\| in .*|delete ! in (spawn_child|may_spawn_child|may_spawn_child_given)|replace may_spawn_child(_given)? -> bool with true|replace <= with > in evaluate_candidates|replace == with != in scan_file|replace < with <= in levenshtein_at_most_one|replace < with > in levenshtein_at_most_one|replace \+= with \*= in levenshtein_at_most_one|replace > with < in levenshtein_at_most_one'
steps:
- uses: actions/checkout@v4
with:
Expand Down
483 changes: 274 additions & 209 deletions CHANGELOG.md

Large diffs are not rendered by default.

79 changes: 28 additions & 51 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 27 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ tar = "0.4"
flate2 = "1"
sha2 = "0.10"
base64 = "0.22"
zip = { version = "2", default-features = false, features = ["deflate"] }
# `deflate-flate2`, not the `deflate` meta-feature: `deflate` also enables
# `flate2/zlib-rs`, and Cargo unifies features across the build, so that one
# flag silently switches every flate2 user — blueline's own gunzip in
# `safe_extract` and ureq's HTTP gzip — from miniz_oxide to zlib-rs.
# `deflate-flate2` picks no backend; flate2's own default (`rust_backend`)
# still supplies miniz_oxide, keeping the production decompression path
# byte-identical to what shipped before the zip 9 bump.
zip = { version = "9", default-features = false, features = ["deflate-flate2"] }
rusqlite = { version = "0.32.1", features = ["bundled"] }
rusqlite_migration = "1.3.1"
semver = "1"
Expand Down Expand Up @@ -52,18 +59,37 @@ lto = true
# gigabytes across a hundred stale incremental session caches. Line tables keep
# a failing test pointing at the right source line, which is all a panic needs,
# and they cost a fraction of the full DWARF.
#
# `opt-level = 1` is the other half of that. It is not here for the shipped
# binary, which is `[profile.release]` and untouched; it is here because a test
# suite that runs unoptimized code measures the absence of optimization rather
# than the code. Measured on the dev box at `fae8050`, interleaved A/B, see
# `PERF.md` section 10: the gate's test stage went 46.4 s to 21 s wall and
# 53.2 s to 15.2 s CPU, and peak RSS 342 MB to 254 MB. It is level 1 and not 2
# because the whole win is already banked at 1 and 2 costs more compile time for
# no measured return.
[profile.dev]
opt-level = 1
debug = "line-tables-only"

# `cargo test` builds its own profile, and it inherits nothing from `[profile.dev]`
# unless it is stated here, so both halves have to be set.
[profile.test]
opt-level = 1
debug = "line-tables-only"

# Dependencies are only ever linked in. Nobody steps through `serde_json`, so
# emitting debug info for it only makes the linker read more than it has to.
#
# They are optimized because they run during the tests: `tests/scenarios.rs`
# spends its time in sha2, flate2, walkdir and SQLite inside the spawned
# binary, and that binary is mostly dependency code. Measured, optimizing the
# dependencies and leaving this crate at 0 took the 22 attack scenarios from
# 13.7 s to 6.2 s on their own.
[profile.dev.package."*"]
opt-level = 1
debug = false

[profile.test.package."*"]
opt-level = 1
debug = false
Loading
Loading