# Mutation Testing Baseline
This file tracks mutation-testing coverage per source file. It is the ratchet
target for the multi-wave rollout — every PR should leave the per-file score
≥ the value listed here. New sweeps update the numbers; never drop them
silently.
- **Tool:** `cargo-mutants` (currently `27.0.0`)
- **Config:** [`.cargo/mutants.toml`](../../.cargo/mutants.toml)
- **Baseline date:** 2026-05-01
- **Total mutants generated:** 1030 across 75 source files
## How to read this file
For each file we record one of:
- `swept` — the file has been worked through; counts are real outcomes
(`caught` / `missed` / `unviable` / `timeout`)
- `pending` — only the inventory count is known; no run yet
A "score" is `caught / (caught + missed)`. Unviable mutants (won't compile
because they require a `Default` impl etc.) and timeouts are reported but do
not count against the score. Files with a target lower than 95 % carry an
explicit justification in their row.
## Workflow for a single file
```bash
# 1. Produce the report. --jobs caps how many cargo build+test pipelines run
# in parallel; cargo-mutants warns above 8. Use a lower value if other work
# is competing for the host. The Makefile defaults to 4 and can be
# overridden via the MUTANTS_JOBS env var.
cargo mutants --file <path> --jobs 4
# 2. Inspect outcomes
cat mutants.out/missed.txt
cat mutants.out/unviable.txt
cat mutants.out/timeout.txt
# 3. For each entry in missed.txt:
# a) Add a unit test that fails when the mutation is applied, OR
# b) If the mutation is semantically equivalent, add `// mutants::skip`
# on the offending line with a comment explaining why, OR
# c) If a class of mutants is genuinely out of scope (e.g. log strings,
# Display impls), broaden `exclude_re` in `.cargo/mutants.toml`
#
# 4. Re-run until missed.txt is acceptable. Update this file with the new
# score and bump the "last swept" date.
```
To replay a single missed mutant locally:
```bash
cargo mutants --file <path> --in-place --no-shuffle --line-col <line>:<col>
```
## Sweep order
Modules are tackled in this order (highest leverage first). See the rollout
plan for rationale.
1. **Wave 1** — pure parsers: `url_parser.rs`, `commands/flags.rs`,
`xmlrpc/parsing.rs`, `client/version.rs`
2. **Wave 2** — boundaries: `error.rs`, `config.rs`, `http.rs`,
`tls/verifier.rs`, `tls/tofu.rs`
3. **Wave 3** — `client/auth/**`, `client/{bug,attachment,comment,user,group,
product,component,classification,field,server}.rs`, `client/mod.rs`
4. **Wave 4** — `commands/**`
5. **Wave 5** — `output/**` (adopt `insta` snapshot tests before sweeping)
6. **Wave 6** — `cli/**`, `types/**`, `main.rs`, `lib.rs`
## Per-file inventory
Largest-first. `–` means the wave hasn't started yet.
| src/tls/verifier.rs | 102 | swept | 96 | 0 | 6 | 0 | 100 % | 2026-05-01 |
| src/xmlrpc/parsing.rs | 81 | swept | 57 | 0 | 11 | 0 | 100 % | 2026-05-01 |
| src/types/bug.rs | 77 | swept | 77 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/shared.rs | 49 | swept | 24 | 0 | 4 | 0 | 100 % | 2026-05-01 |
| src/config.rs | 47 | swept | 33 | 0 | 5 | 0 | 100 % | 2026-05-01 |
| src/xmlrpc/client.rs | 42 | swept | 35 | 0 | 7 | 0 | 100 % | 2026-05-01 |
| src/client/mod.rs | 41 | swept | 30 | 0 | 10 | 0 | 100 % | 2026-05-01 |
| src/client/bug.rs | 38 | swept | 28 | 0 | 10 | 0 | 100 % | 2026-05-01 |
| src/tls/tofu.rs | 36 | swept | 28 | 0 | 4 | 0 | 100 % | 2026-05-01 |
| src/output/formatting.rs | 34 | swept | 34 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/xmlrpc/mod.rs | 28 | swept | 21 | 0 | 7 | 0 | 100 % | 2026-05-01 |
| src/url_parser.rs | 25 | swept | 23 | 0 | 2 | 0 | 100 % | 2026-05-01 |
| src/error.rs | 19 | swept | 17 | 0 | 1 | 0 | 100 % | 2026-05-01 |
| src/commands/query.rs | 19 | swept | 18 | 0 | 1 | 0 | 100 % | 2026-05-01 |
| src/commands/config.rs | 18 | swept | 18 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/flags.rs | 17 | swept | 10 | 0 | 7 | 0 | 100 % | 2026-05-01 |
| src/client/version.rs | 17 | swept | 13 | 0 | 4 | 0 | 100 % | 2026-05-01 |
| src/types/common.rs | 16 | swept | 12 | 0 | 4 | 0 | 100 % | 2026-05-01 |
| src/main.rs | 16 | swept | 12 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/attachment.rs | 16 | swept | 16 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/output/query.rs | 13 | swept | 13 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/bug/list.rs | 13 | swept | 13 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/client/group.rs | 13 | swept | 12 | 0 | 1 | 0 | 100 % | 2026-05-01 |
| src/commands/comment.rs | 12 | swept | 7 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/client/attachment.rs | 12 | swept | 10 | 0 | 2 | 0 | 100 % | 2026-05-01 |
| src/http.rs | 11 | swept | 10 | 0 | 2 | 0 | 100 % | 2026-05-01 |
| src/commands/bug/my.rs | 11 | swept | 11 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/client/auth/valid_login.rs | 11 | swept | 8 | 0 | 3 | 0 | 100 % | 2026-05-01 |
| src/output/result_types.rs | 10 | swept | 1 | 0 | 9 | 0 | 100 % | 2026-05-01 |
| src/commands/bug/update.rs | 10 | swept | 10 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/bug/search.rs | 10 | swept | 10 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/client/comment.rs | 10 | swept | 9 | 0 | 1 | 0 | 100 % | 2026-05-01 |
| src/output/config.rs | 9 | swept | 7 | 0 | 2 | 0 | 100 % | 2026-05-01 |
| src/commands/template.rs | 9 | swept | 9 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/client/user.rs | 9 | swept | 4 | 0 | 3 | 0 | 100 % | 2026-05-01 |
| src/xmlrpc/call.rs | 8 | swept | 6 | 0 | 2 | 0 | 100 % | 2026-05-01 |
| src/client/auth/mod.rs | 8 | swept | 6 | 0 | 1 | 0 | 100 % | 2026-05-01 |
| src/output/template.rs | 7 | swept | 7 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/output/product.rs | 7 | swept | 7 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/credentials/keyring.rs | 7 | swept | 5 | 0 | 2 | 0 | 100 % | 2026-05-01 |
| src/client/field.rs | 7 | swept | 4 | 0 | 3 | 0 | 100 % | 2026-05-01 |
| src/output/bug.rs | 6 | swept | 5 | 0 | 1 | 0 | 100 % | 2026-05-01 |
| src/client/product.rs | 6 | swept | 4 | 0 | 2 | 0 | 100 % | 2026-05-01 |
| src/client/auth/whoami.rs | 6 | swept | 4 | 0 | 2 | 0 | 100 % | 2026-05-01 |
| src/types/attachment.rs | 5 | swept | 3 | 0 | 2 | 0 | 100 % | 2026-05-01 |
| src/tls/mod.rs | 5 | swept | 3 | 0 | 2 | 0 | 100 % | 2026-05-01 |
| src/output/user.rs | 5 | swept | 3 | 0 | 2 | 0 | 100 % | 2026-05-01 |
| src/tls/fingerprint.rs | 4 | swept | 4 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/credentials/keyring_stub.rs | 4 | swept | 0 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/user.rs | 4 | swept | 4 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/types/product.rs | 3 | swept | 3 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/field.rs | 3 | swept | 3 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/bug/clone.rs | 3 | swept | 3 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/client/server.rs | 3 | swept | 2 | 0 | 1 | 0 | 100 % | 2026-05-01 |
| src/client/component.rs | 3 | swept | 2 | 0 | 1 | 0 | 100 % | 2026-05-01 |
| src/xmlrpc/fault.rs | 2 | swept | 1 | 0 | 1 | 0 | 100 % | 2026-05-01 |
| src/output/server.rs | 2 | swept | 1 | 0 | 1 | 0 | 100 % | 2026-05-01 |
| src/output/group.rs | 2 | swept | 2 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/output/field.rs | 2 | swept | 2 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/output/classification.rs | 2 | swept | 2 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/types/user.rs | 1 | swept | 0 | 0 | 1 | 0 | 100 % | 2026-05-01 |
| src/output/comment.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/output/attachment.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/lib.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/whoami.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/server.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/product.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/group.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/component.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/classification.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/bug/view.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/bug/mod.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/bug/history.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/commands/bug/create.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
| src/client/classification.rs | 1 | swept | 1 | 0 | 0 | 0 | 100 % | 2026-05-01 |
## Sub-tree totals (informational)
| `src/commands/` | 204 | Includes the `bug/*` split; many small files |
| `src/client/` | 185 | API surface — wiremock-driven tests already exist |
| `src/xmlrpc/` | 161 | Pure parsing — high-leverage |
| `src/tls/` | 147 | Custom verifier; bugs here are silent |
| `src/types/` | 102 | Mostly serde plumbing |
| `src/output/` | 101 | Expect snapshot adoption before sweeping |
| `src/config.rs` | 47 | Single file |
| `src/url_parser.rs` | 25 | Swept (Wave 1) |
| `src/error.rs` | 19 | Single file |
| `src/main.rs` | 16 | Mostly clap glue |
| `src/http.rs` | 11 | Boundary code |
| `src/credentials/` | 11 | Two files |
| `src/lib.rs` | 1 | Dispatch |
## Skip-list audit
Per-file `mutants::skip` attributes are tracked via `make audit-mutant-skips`.
Config-level exclusions in `.cargo/mutants.toml` (review these whenever the
related code changes — they may have rotted):
| `impl .* Display for` / `impl .* Debug for` | all | formatting; covered by snapshot/output tests when needed |
| `skip_to_end` | `src/xmlrpc/parsing.rs` | defensive depth-tracking; equivalent in normal XML-RPC flow (depth never exceeds 1) |
| `is_value_end -> bool with true` | `src/xmlrpc/parsing.rs` | empty `<value></value>` end arm — equivalent under permissive parser |
| `replace == with != in is_value_end` | `src/xmlrpc/parsing.rs` | same equivalence as above |
| `match guard is_value_end(...) with true` | `src/xmlrpc/parsing.rs` | guard call site of the equivalent helper |
| `is_tls_cert_error -> bool with false` | `src/http.rs` | `reqwest::Error` has no public constructor; cannot construct one with both `is_connect()` true and a TLS-keyword chain in unit tests |
| `read_interactive_line` / `confirm_pin` / `prompt_tofu` / `prompt_rotation` returning Ok(None)/Ok(false) | `src/tls/tofu.rs` | Each detects non-terminal stdin and short-circuits to None/false; cargo test stdin is never a terminal, so the mutation matches the test-mode path |
| `warn_security` / `warn_if_path_permissions_too_open` / `Config::warn_on_insecure_permissions` no-ops, plus the bitwise-mode-mask check | `src/config.rs` | Stderr-only side effects; the project has no `capture_stderr` helper, so these can't be observed in unit tests |
| `write_private_file` / `set_private_file_permissions -> Ok(())` | `src/config.rs` | The non-Unix `write_private_file` is dead code on the Linux test platform (`#[cfg(not(unix))]`); the Unix `set_private_file_permissions` is redundant since `write_private_file` already creates with `OpenOptions::mode(0o600)` |
| `delete ! in detect_auth_method` | `src/client/auth/mod.rs` | Tracing-only side effect for non-HTTPS URLs |
| `CodeVisitor>::expecting -> std::fmt::Result` | `src/client/mod.rs` | The visitor's "expected" message is only surfaced on serde failure, which `check_response_status` swallows in favor of `BzrError::HttpStatus` |
| `read_comment_body` / `compose_comment_in_editor` / `TempFile::drop` | `src/commands/comment.rs` | Editor-spawn / tempfile cleanup helpers; only fire when stdin is a terminal, unreachable in unit tests |
| `read_secret_from_prompt_or_env` (non-keyring) | `src/commands/config.rs` | `#[cfg(not(feature = "keyring"))]` — dead code on the default-feature Linux test platform |
| `should_offer_tofu` / `is_pin_mismatch` / `is_issuer_changed` / `classify_and_handle_tls_failure` | `src/commands/shared.rs` | TLS-failure predicates can only return true given a real TLS handshake error from reqwest, which has no public Error constructor |
| `handle_tofu` / `handle_pin_rotation` (whole functions, via `#[cfg_attr(test, mutants::skip)]`) | `src/commands/shared.rs` | TOFU/rotation handlers only fire after a terminal-stdin prompt accepts; cargo-mutants v27 cannot reach `delete field` mutations through `exclude_re`, so the attribute is used instead |
| `main` (whole function, via `#[cfg_attr(test, mutants::skip)]`) | `src/main.rs` | Binary entry point; observing exit codes / stderr requires spawning the compiled binary (e.g. via `assert_cmd`/`escargot`). The pure helpers it delegates to are unit-tested directly. |
| `suppress_stdout` non-unix variants (whole functions, via `#[cfg_attr(test, mutants::skip)]`) | `src/main.rs` | `#[cfg(windows)]` and `#[cfg(not(any(unix, windows)))]` are dead code on the Linux test platform |
| `store` / `retrieve` / `delete` (whole functions, via `#[cfg_attr(test, mutants::skip)]`) | `src/credentials/keyring_stub.rs` | `#[cfg(not(feature = "keyring"))]` — never compiled into the test binary, since `.cargo/mutants.toml` runs with `--all-features` |