bake-test-rust 0.3.0

Reusable Rust test tasks for Bake
Documentation
# Rust Testing Tasks

Add `bake-test-rust` as a dependency of the private `bake/` package and link it
from `bake/src/main.rs`:

```toml
[dependencies]
bake-test-rust = "0.2"
```

```rust,ignore
use bake_test_rust as _;
```

The package registers `test`, `test:coverage`, and `test:external` tasks.
`test` runs `cargo test --workspace --locked`; `test:external` runs Cargo tests
in the selected downstream repositories without `--locked`, because the
lockfiles need to resolve local path patches.

## Coverage

Install `cargo-llvm-cov` and run the Bake coverage task:

```sh
rustup component add llvm-tools-preview --toolchain stable
cargo +stable install cargo-llvm-cov --locked
rustup run stable cargo bake test:coverage
```

Run the task with the same toolchain that has `llvm-tools-preview`. If another
Rust installation such as Homebrew's `cargo` comes first on `PATH`, plain
`cargo bake` may use that compiler and fail to find the Rustup component.

The task calls the optional `test:before` hook once, runs documentation tests
with Cargo, then runs workspace tests under `cargo-llvm-cov`. It reports each
uncovered source region and fails if any measured region remains uncovered.
Regions with identical source spans are merged across function instantiations;
distinct source spans remain separate, including spans on the same line. The
task covers the whole workspace by default; use `--package name` to limit
coverage to one package. Coverage uses the default feature set unless
`--all-features true` or one or more repeatable `--features name` arguments are
supplied. Choose one feature configuration per invocation; the task rejects
combining `--all-features` and `--features`.

### Invariant-only `unreachable!()` calls

Rust checks local type-level impossibilities, such as exhaustive matches over
closed enums. It does not generally prove runtime invariants that depend on
relationships between values, parser behavior, or upstream code. An
`unreachable!()` is a runtime assertion that panics if that invariant is
violated; it is not proof that the branch cannot occur.

Regions inside an `unreachable!()` invocation are excluded from coverage,
including multiline invocations. This exclusion is automatic and does not need
a marker. It applies only to Rust source regions contained within the macro
call; other uncovered code on the same line remains measured. The source scan
ignores strings and comments.

Coverage uses the source regions reported by LLVM without classifying regions
by their source text. A reported region remains measured even if its span
contains only a delimiter or a macro name: LLVM can map executable behavior to
such a span, including a branch outcome mapped to a closing brace. Inspect an
uncovered region and the behavior represented by its mapping; do not exclude it
solely because its span looks syntactic.

If `cargo-llvm-cov` reports a genuinely invariant-only `unreachable!()` call,
include its reason in the panic message:

```rust
_ => unreachable!("Only JSX events can be mismatched here")
```

The macro asserts that the path should not be reached; it does not prove that
the invariant is correct. Use `unreachable!()` only after checking the
invariant against supported inputs. Test valid inputs and the boundary that
establishes the invariant; do not manufacture an impossible private state
solely to execute the panic. All other source-backed regions must reach 100%
coverage.

The canonical GitHub Actions workflow below installs the required Rust
components and coverage tool. The task measures the runner's target and feature
configuration. If a crate contains architecture-specific code, run the same
gate on each supported architecture; each run checks the code compiled for its
target.

The coverage task runs documentation tests but does not include them in the
coverage report; LLVM doctest coverage is still unstable. It uses the JSON
function-region data to enforce 100% of measured source regions. This task
measures source regions, not LLVM's separate experimental branch-coverage
metric.

## Before-test hook

The `test`, `test:coverage`, and `test:external` tasks call the optional project
task `test:before` once before running tests. Use it for resources that need to
be prepared for either local or downstream tests, such as downloading fixtures
or generating assets:

```rust,ignore
#[bake::task(name = "test:before")]
fn before_test(context: &mut bake::Context) -> bake::Result<()> {
    let status = context.command("make").arg("test-assets").status()?;
    if status.success() {
        Ok(())
    } else {
        Err(bake::Error::new(format!("test asset preparation failed: {status}")))
    }
}
```

The hook is optional. If it is not registered, the test tasks continue without
calling it. External checkout and patch preparation happens before the hook;
the hook runs once before Cargo tests begin. If no downstream repositories are
configured, `test:external` returns a message without running the hook.

## External repositories

List selected downstream repositories in the root `Cargo.toml`:

```toml
[[workspace.metadata.bake.test.external]]
repository = "https://github.com/socketry/downstream-project"
branch = "main"
```

For a single-package project, use
`[[package.metadata.bake.test.external]]`. `branch` defaults to `main`. Add
`name = "checkout-name"` when you want the local checkout directory to use a
specific name or avoid a name collision. If the list is empty or absent,
`test:external` reports that no downstream repositories are configured.

The task clones each repository into `external/<name>/` on first use. It keeps
existing Git checkouts and does not fetch, reset, switch branches, or discard
local edits. To update a checkout, run Git commands inside it. If the metadata
repository no longer matches the checkout, choose a new `name` or move the old
directory yourself.

The task adds this workspace's crates.io-publishable packages to the cloned
repository's `[patch.crates-io]` table, using relative paths back to the local
workspace. Existing unrelated manifest content is preserved, and the same
patches are not inserted a second time. If a conflicting patch already exists,
the task stops and asks you to resolve that entry. The patch remains in the
checkout so you can enter it and run `cargo test --workspace` manually to
investigate a failure. Add `/external/` to the consumer repository's
`.gitignore`, as this repository does.

Cargo still checks the downstream dependency's version requirement. If the
local workspace version does not satisfy it, update that requirement in the
checkout before rerunning external tests. The task checks Cargo's resolved
dependency graph and stops if a downstream dependency silently resolves to the
registry version instead of the local patch.

## Canonical GitHub workflows

Use this `test.yml` for Socketry Rust repositories. It runs formatting,
Clippy, documentation tests, and workspace coverage. The coverage task invokes
the optional `test:before` hook and requires 100% coverage of measured source
regions. Passing
`--all-targets true` includes examples and benchmarks in the coverage run. Do
not add a separate `cargo bake test` step to this job; the coverage task runs
the tests itself.

```yaml
name: Test

on:
  push:
  pull_request:
  workflow_dispatch:

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v7
      - uses: actions-rust-lang/setup-rust-toolchain@v2
        with:
          components: clippy, llvm-tools-preview, rustfmt
          # Isolate this source-installed tool from the old prebuilt-action cache.
          cache-shared-key: coverage-cargo-install-v1
      - name: Install Bake launcher
        run: cargo install socketry-cargo-bake --locked
      - name: Install coverage tool
        run: cargo install cargo-llvm-cov --locked
      - run: cargo fmt --all -- --check
      - run: cargo clippy --workspace --all-targets --locked -- -D warnings
      - name: Run tests and require complete source-region coverage
        run: cargo bake --locked test:coverage --all-targets true
```

The standard `test` task remains useful for quick local runs without a coverage
report. CI uses `test:coverage` so it enforces the organization-wide
source-region coverage requirement.

External compatibility testing is optional. Add
`.github/workflows/external.yml` only when the Cargo metadata list contains one
or more selected downstream repositories:

```yaml
name: External Tests

on:
  push:
  pull_request:
  workflow_dispatch:

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v7
      - uses: actions-rust-lang/setup-rust-toolchain@v2
      - name: Install Bake launcher
        run: cargo install socketry-cargo-bake --locked
      - name: Run downstream compatibility tests
        run: cargo bake --locked test:external
```

The external task creates fresh checkouts in the workflow's temporary runner
and applies the local patches before testing. Remove this workflow when the
metadata list becomes empty.