---
name: test-corpus
description: The test_documents submodule — a bucket-fetched fixture corpus that is not committed, the read_test_fixture helper that keeps a missing fixture out of the build, why a git worktree is not a valid A/B control, and the submodule push order. Load before running the Rust test suite on a fresh clone, setting up an A/B control, adding a fixture-backed test, or diagnosing "missing fixture" failures.
---

# Test corpus

`test_documents` is a git submodule (`.gitmodules` → `xberg-io/test_documents`). Its binary
fixtures — 654 objects, ~581 MiB per `test_documents/corpus.lock.json` — are **not committed**.
They are materialised from a public GCS bucket over anonymous HTTPS, with no credentials and no
SDK.

```bash
python3 test_documents/scripts/fetch_corpus.py                   # everything
python3 test_documents/scripts/fetch_corpus.py --include 'pdf/**'
```

Fetched files are gitignored inside the submodule, so neither the submodule nor the
superproject goes dirty. CI does the same thing with `xberg-io/actions/fetch-test-documents@v1`,
narrowing with `include:` where the job's fixture surface is known (see `ci-rust.yaml`).

## Without the fetch

Tests that read from `test_documents` fail with missing-file errors, not assertion failures.
Read the message before concluding the suite regressed.

## Never `include_bytes!` a corpus fixture

Baking corpus bytes in at compile time turns a missing fixture into a **build** failure for
the whole crate — this is what broke the workspace-wide clippy run in CI. Use the canonical
helper instead:

```rust
// crates/xberg/src/utils/mod.rs — #[cfg(test)], pub(crate)
let Some(bytes) = crate::utils::read_test_fixture("images/test.heic") else { return; };
```

`read_test_fixture` prints a greppable `SKIP: fixture … not available` line naming the missing
path and returns `None`. It lives in `utils` deliberately: `utils` compiles unconditionally,
while `extraction::image` and `extraction::email` are feature-gated.

A `None` is **"not run"**, never "passed".

## A git worktree is not a valid control

`git worktree add` does not populate submodules, so a control worktree has an **empty**
`test_documents`. Every fixture-guarded test skips silently and the run reports green — a
vacuous control inverts the verdict. The tell is the runtime: `finished in 0.00s` means the
binary did nothing.

Two more worktree traps: a relative `[patch.crates-io]` path dependency (the root
`Cargo.toml` currently patches `liter-llm` to `../liter-llm/…`) resolves relative to the
*worktree*, not the main checkout; and a shared `CARGO_TARGET_DIR` will thrash.

Working setup:

```bash
git worktree add /tmp/ctl <ref> --detach
rmdir /tmp/ctl/test_documents && ln -sfn <main-tree>/test_documents /tmp/ctl/test_documents
CARGO_TARGET_DIR=/tmp/ctl-target cargo test ...
```

When the goal is only build isolation, prefer a dedicated `CARGO_TARGET_DIR` in the main
checkout over a worktree — it avoids all three traps.

## Pushing a corpus change

Push the **submodule commit before** the superproject gitlink. A local-only submodule commit
builds and tests green on the machine that made it and turns every CI workflow red at
checkout with `Fetched in submodule path 'test_documents', but it did not contain <sha>`.
Check with `git branch -r --contains <sha>` inside the submodule.

## Not the same thing as `fixtures/`

`fixtures/` at the repo root is committed and safe to `include_bytes!`. Only
`test_documents/` is bucket-fetched.
