gwm-cli 1.0.0

git worktree manager — TUI + CLI, native libgit2, per-repo bootstrap
Documentation
---
title: Tests
description: La matrice cargo test (~1700 tests sur ubuntu / macos / windows), les fichiers de tests d'intégration, la convention de test-sentinelle et la boucle TDD obligatoire.
---

# Tests

`cargo test` exécute la suite complète — **~1700 tests** (1738 marqueurs `#[test]` à partir de la v0.10.0) répartis entre 73 fichiers d'intégration sous `tests/` plus les tests unitaires embarqués dans `src/`. Durée moyenne sur un ordinateur portable récent : ~1 seconde.

La suite s'exécute à chaque push sous forme de matrice sur **`ubuntu-latest`, `macos-latest` et `windows-latest`** (câblée dans `ci.yml`) ; chaque merge dans `dev` est conditionné au passage au vert des trois, et les artefacts de pré-release sont construits à partir du même commit par `pre-release.yml`. Windows a été ajouté à la matrice en v0.8.0-rc.1 ([#112](https://github.com/kbrdn1/gwm-cli/issues/112)).

## le TDD est obligatoire

Aucun code de production n'est intégré sans un test échouant qui a d'abord figé le comportement — c'est une exigence stricte de merge, pas une recommandation (voir [`CLAUDE.md`](https://github.com/kbrdn1/gwm-cli/blob/main/CLAUDE.md)). La boucle est **red → green → refactor** :

1. **Red** — écrire un test échouant qui capture le nouveau comportement ou le bug. L'exécuter ; il DOIT échouer pour la bonne raison (un décalage d'assertion, pas une erreur de compilation sans rapport).
2. **Green** — écrire le minimum de code de production pour le faire passer. Pas de branches ni d'abstractions spéculatives.
3. **Refactor** — nettoyer pendant que les tests sont au vert, en réexécutant la suite complète après chaque étape.

Tout ce qui est observable depuis l'extérieur de la fonction testée compte comme un comportement : une nouvelle sous-commande / un nouveau flag / un nouveau format de sortie CLI → test de bout en bout dans `tests/cli_binary.rs` (et un fichier compagnon par surface — p. ex. `tests/exec_tests.rs`, `tests/clean_tests.rs`, `tests/review_tests.rs`, `tests/statusline_tests.rs`, `tests/daemon_tests.rs`) ; une nouvelle fonction publique → test unitaire dans `tests/<module>_tests.rs` ; une nouvelle étape de bootstrap → test d'intégration dans `tests/bootstrap_tests.rs` ; une opération de worktree libgit2 → `tests/worktree_integration.rs` ; une transition d'état de la TUI → `tests/tui_app_tests.rs` (ou une tranche ciblée `tests/tui_state_*_tests.rs`). « Je l'ai testé manuellement » n'est pas une exception — codifiez le test manuel.

## référence rapide

```bash
cargo test                            # full suite (~1700 tests)
cargo test --test tui_app_tests       # one integration file
cargo test refresh_github             # name-substring filter across the whole suite
cargo test -- --nocapture             # let println! / dbg! through
cargo test --release                  # opt-level=3, same suite
```

Le filtre `--test <name>` se fait par **fichier d'intégration**, pas par module — `cargo test --test tui_app_tests` exécute tout ce qui se trouve dans `tests/tui_app_tests.rs`.

### config déterministe dans les tests

Depuis la v0.8.0-rc.5, une config globale au niveau utilisateur dans `~/.config/gwm/config.toml` est fusionnée sous le `.gwm.toml` de chaque repo. Pour garder `cargo test` hermétique, la suite de tests injecte `None` pour la couche globale (les seams `*_layered`), de sorte qu'une exécution ne lise jamais la vraie config globale du contributeur. Définissez `GWM_NO_GLOBAL_CONFIG=1` pour forcer un chargement strictement limité au repo à l'exécution également ([#194](https://github.com/kbrdn1/gwm-cli/issues/194) / [#196](https://github.com/kbrdn1/gwm-cli/issues/196)).

## organisation des tests d'intégration

Tous les tests d'intégration vivent sous `tests/` :

```
tests/
├── common/                       # shared helpers (init_repo, paths_equal)
│
│   # CLI surface
├── cli_binary.rs                # end-to-end assert_cmd coverage of the CLI surface
├── cli_format_tests.rs          # remove / prune / sync plan formatters (alignment, detached HEAD)
├── cli_repo_context_tests.rs    # repo-context resolution in/out of a git repo (strict vs lenient)
├── exec_tests.rs                # gwm exec rollup arithmetic + ✓/✗ aggregation (#313)
├── clean_tests.rs               # gwm clean artefact discovery + human-size formatting (#313)
├── review_tests.rs              # gwm review branch/dir naming contract (#308)
├── review_integration.rs        # gwm review fetch refs/pull/<N>/head + worktree attach (#308)
├── statusline_tests.rs          # gwm statusline one-line render (blank / count / dirty / issue·PR) (#309)
│
│   # daemon + JSON API (#38)
├── daemon_tests.rs              # pure RPC core: handle_line / dispatch (cross-platform)
├── daemon_integration.rs        # socket round-trip (unix + daemon feature only)
├── json_api_tests.rs            # --format=json status/projection for list / doctor / path
│
│   # config + bootstrap + hooks
├── config_tests.rs              # .gwm.toml parsing + write_default + defaults
├── config_cli_tests.rs          # gwm config get/set/unset/list/validate/path/edit
├── config_global_tests.rs       # user-level global config merge (overlay semantics)
├── config_set_at_tests.rs       # set_value_at typed write-back per layer + round-trip
├── presets_tests.rs             # gwm init --preset lookup + alias resolution (#37)
├── bootstrap_tests.rs           # copy / guard / no-symlink / [hooks.*] lifecycle phases
├── bootstrap_when_tests.rs      # `when:` predicate grammar (file/cmd/env/glob + boolean ops)
├── lifecycle_tests.rs           # lifecycle hook execution + command-log recording
├── trust_tests.rs               # TOFU trust ledger hashing + (origin, sha256) entries (#95)
│
│   # GitHub workflow
├── github_tests.rs              # issue/PR linking + trim_git_suffix + URL derivation
├── gitmoji_tests.rs             # gwm commit-prefix (shortcode / unicode / --branch / override)
├── labels_tests.rs              # [[labels]] deterministic colour + diff arithmetic
├── milestones_tests.rs          # [[milestones]] due-date normalisation + diff arithmetic
├── pr_templates_tests.rs        # [pr_template] body rendering + placeholders
├── templating_tests.rs          # placeholder substitution + issue-form markdown rendering
│
│   # worktree ops + history
├── worktree_integration.rs      # git2 add / list / remove / prune
├── sync_tests.rs                # gwm sync fetch + rebase/merge + dirty-tree / no-upstream refusal
├── history_tests.rs             # operation journal IO + gwm history + gwm undo contract
├── command_log_tests.rs         # command-log append ordering + snapshot clone
├── naming_tests.rs              # kebab normalisation + branch validation + parse roundtrip
├── aliases_tests.rs             # [aliases] resolution + shadowing + shell-pipeline refusal
├── doctor_tests.rs              # gwm doctor checks + severity arithmetic
├── hooks_tests.rs               # gwm hooks install commit-msg (force, linked worktree, hooksPath)
├── multiplexer_tests.rs         # gwm tmux / gwm zellij argv construction + $TMUX guard
├── launcher_tests.rs            # [git_tui] / [review] placeholder expansion + base resolution
├── error_tests.rs               # GwmError variants + Display + From impls
├── error_variants_tests.rs      # newer GwmError variants (unborn HEAD, gh JSON parse, …)
│
│   # workspace mode (#36)
├── workspace_tests.rs           # discover child repos + merge_worktrees
├── cli_workspace_tests.rs       # gwm --workspace list / create --repo CLI plumbing
├── tui_workspace_tests.rs       # REPO column + swap-on-navigation in the workspace TUI
│
│   # TUI state + render
├── tui_app_tests.rs             # App state transitions (ratatui-free)
├── tui_chord_tests.rs           # multi-key chord dispatch in list view
├── keymap_tests.rs              # [tui.keys] parsing + chord conflicts + prefix collisions
├── modal_keymap_tests.rs        # [tui.keys.modal.<context>] single-key bindings (#219)
├── palette_tests.rs             # command palette action registry + fuzzy filter
├── theme_tests.rs               # [theme] preset resolution + role overrides + colour parsing
├── tui_footer_tests.rs          # single-line statusline chips + flush-right log
├── tui_header_tests.rs          # single-row header hierarchy + width-driven truncation
├── tui_wt_tree_tests.rs         # Working Tree file-tree model (collapse / cap / glyphs) (#300)
├── tui_ui_helpers_tests.rs      # ellipsize / layout helpers
├── tui_modal_render_tests.rs    # modal overlay rendering against a TestBackend
├── tui_sidebar_render_tests.rs  # sidebar panes rendering (commits / status)
├── tui_theme_audit_tests.rs     # theme role coverage audit across draw sites
├── tui_theme_integration_tests.rs  # resolved theme threaded through draw_* sites
├── tui_state_*_tests.rs         # per-slice state (create_form, filter, confirm, sidebar,
│                                 #   spinner, link_prompt, palette, command_logs,
│                                 #   config_panel, async_task, github_fetch,
│                                 #   pty_overlay (#35), …)
│
│   # packaging / release
├── binstall_metadata_tests.rs   # [package.metadata.binstall] artefact naming
├── homebrew_formula_tests.rs    # Homebrew formula template substitution
├── flake_tests.rs               # Nix flake structure (build, devShell, app, overlay)
├── release_workflow_tests.rs    # release.yml / pre-release.yml tag + publish-token contract
├── precommit_hook_tests.rs      # pre-commit hook scaffolding
└── commit_graph_perf_tests.rs   # commit-graph pipe allocation invariants
```

(Non exhaustif — `ls tests/*.rs` est la liste à jour (73 fichiers à partir de la v0.10.0) ; la matrice les exécute tous.)

## tests-sentinelles

Les tests épinglés pour attraper une régression spécifique sont préfixés d'un commentaire `// regression: <une-ligne>` à l'intérieur du corps du test, afin que l'incident d'origine soit repérable sans `git blame` :

```rust
#[test]
fn trim_git_suffix_handles_trailing_slash() {
  // regression: PR #68 Copilot review — owner/repo.git/ left ".git" in the slug
  assert_eq!(trim_git_suffix("https://github.com/owner/repo.git/"), "owner/repo");
}
```

Les trouver tous :

```bash
grep -rn "regression:" tests/
```

L'hygiène de la suite (couverture sentinelle vs catalogue d'incidents) a été auditée pour la dernière fois dans [`claudedocs/test-audit-0.4.0.md`](https://github.com/kbrdn1/gwm-cli/blob/main/claudedocs/test-audit-0.4.0.md). Relancez l'audit après chaque mineure — la dérive s'accumule vite autour des surfaces du launcher et du linking GitHub.

## tests unitaires

En plus des fichiers d'intégration, `src/` embarque des tests unitaires dans des blocs `#[cfg(test)] mod tests { … }` colocalisés avec le code testé. Ils couvrent :

- Les fonctions pures (normalisation kebab, parsing de slug, arithmétique des sigils)
- Les round-trips serde sur chaque sous-struct `Config`
- Les prédicats internes non exposés sur la surface publique

`cargo test` les exécute aux côtés de la suite d'intégration — ils n'ont pas besoin d'une invocation séparée.

## quoi exécuter avant un push

Le minimum imposé par le hook pre-commit (et par la CI) :

```bash
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test
```

Les trois doivent être au vert. Le hook pre-commit vit dans [`tools/git-hooks/pre-commit`](https://github.com/kbrdn1/gwm-cli/blob/main/tools/git-hooks/pre-commit) ; activez-le avec :

```bash
git config core.hooksPath tools/git-hooks
```

(ou via le bootstrap embarqué si le `.gwm.toml` de votre repo le câble).

## dev shell

Le flake Nix exporte un dev shell épinglé — toolchain Rust (épinglée à la même version `rust-toolchain.toml` utilisée en CI), `rust-analyzer`, `clippy`, `rustfmt`, `cargo-watch`, `cargo-edit` et les dépendances de build de `libgit2` — sans toucher au système hôte :

```bash
nix develop                # drops you in the shell
cargo test                 # everything works out of the box
```

Voir [Intégrations → Homebrew et Nix](/fr/integrations/homebrew-nix#flake-nix) pour la surface complète du flake.

## en lien

- [Contribuer](/fr/development/contributing) — conventions pour ajouter des tests et le format du commentaire de régression
- [Roadmap](/fr/roadmap) — domaines à venir qui nécessiteront une nouvelle couverture de tests
```