Skip to content

Commit 09a9f6e

Browse files
authored
Reorganize and refactor CGP test suite (#246)
* Reorganize and refactor test suite * Revise diff
1 parent d0e3044 commit 09a9f6e

356 files changed

Lines changed: 10443 additions & 14207 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ members = [
3838

3939
"crates/tests/cgp-tests",
4040
"crates/tests/cgp-macro-tests",
41+
"crates/tests/cgp-test-crate-a",
42+
"crates/tests/cgp-test-crate-b",
4143
]
4244

4345
[workspace.package]
@@ -74,6 +76,9 @@ cgp-macro = { version = "0.7.0", path = "./crates/macros/cgp-m
7476
cgp-macro-core = { version = "0.7.0", path = "./crates/macros/cgp-macro-core" }
7577
cgp-macro-lib = { version = "0.7.0", path = "./crates/macros/cgp-macro-lib" }
7678
cgp-macro-test-util = { version = "0.7.0", path = "./crates/macros/cgp-macro-test-util" }
79+
80+
cgp-test-crate-a = { version = "0.7.0", path = "./crates/tests/cgp-test-crate-a" }
81+
cgp-test-crate-b = { version = "0.7.0", path = "./crates/tests/cgp-test-crate-b" }
7782
cgp-macro-test-util-lib = { version = "0.7.0", path = "./crates/macros/cgp-macro-test-util-lib" }
7883
cgp-extra-macro = { version = "0.7.0", path = "./crates/macros/cgp-extra-macro" }
7984
cgp-extra-macro-lib = { version = "0.7.0", path = "./crates/macros/cgp-extra-macro-lib" }

crates/tests/CLAUDE.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# CLAUDE.md — maintaining the CGP test suite
2+
3+
This file governs the test crates under `crates/tests`. Read it before adding,
4+
moving, or refactoring any test here. Invoke the `/cgp` skill first — every test
5+
in this tree is CGP code, and the skill is the authoritative source for CGP
6+
semantics and vocabulary.
7+
8+
The test suite has three jobs, split across crates:
9+
10+
- **`cgp-tests`** is the main suite: realistic example code that must **compile and
11+
run**. A passing test is often just successful compilation, because much of CGP
12+
is compile-time wiring. This is where behavior is verified and where the
13+
user-facing macros are exercised end-to-end.
14+
- **`cgp-macro-tests`** tests the **internals** of the CGP macros by calling the
15+
functions in `cgp-macro-core` directly (parsers, AST types), and is the home for
16+
**failure cases** — inputs CGP should reject, and cases where a macro currently
17+
emits invalid or wrong code.
18+
- **`cgp-test-crate-a` / `cgp-test-crate-b`** are auxiliary packages for
19+
**cross-crate** behavior: whether a downstream crate can extend a namespace or
20+
provide a provider for a component defined elsewhere, under Rust's coherence and
21+
orphan rules.
22+
23+
## Organize by concept, not by construct
24+
25+
Group tests by the **CGP concept or feature** under test, never by the macro that
26+
happens to appear. A single construct such as `delegate_components!` serves many
27+
concepts — basic delegation, `open` dispatch, namespace headers, `UseDelegate`
28+
tables — so a bucket named after the construct mixes unrelated concerns and hides
29+
what is actually being verified. Name each group for the concept: `basic_delegation`,
30+
`abstract_types`, `implicit_arguments`, `namespaces`, `higher_order_providers`, and
31+
so on.
32+
33+
The right granularity is driven by the feature, its implementation complexity, and
34+
how many cases are needed to cover it exhaustively — **not** by mirroring the
35+
concept documents under `docs/concepts/`. The names may coincide, but the split is
36+
chosen for coverage. **When a category accumulates too many test cases to stay
37+
coherent, split it into finer categories** rather than letting it sprawl; prefer
38+
splitting early.
39+
40+
## A test target is a "sub-crate"
41+
42+
Each concept is one **integration test target**, which Cargo compiles as its own
43+
crate — so each concept has its own coherence scope, exactly like a separate crate.
44+
A target is two things:
45+
46+
- an **entrypoint file** `tests/<concept>_tests.rs` — the `_tests` suffix marks it
47+
as the target root; it carries a module doc comment, `#![allow(dead_code)]` when
48+
the target is mostly compile-time wiring, and a single `pub mod <concept>;`;
49+
- a **module directory** `tests/<concept>/` — the clean concept name — whose
50+
`mod.rs` lists the unit-test modules, one `pub mod` per file.
51+
52+
`basic_delegation` is the reference implementation of this layout — copy its shape
53+
when adding a concept.
54+
55+
## One unit test per file
56+
57+
Put each unit test in its own `.rs` file under the concept directory, and make the
58+
file **self-contained**: define its own components, providers, and context types at
59+
module scope. Do **not** separate unrelated units with `#[test]` functions or nested
60+
`mod`s inside one file. CGP tests are dominated by type-level constructs and
61+
compile-time wiring that live at module scope and cannot be isolated by a function
62+
boundary; separate files are the only reliable isolation within a target. A file may
63+
still contain a `#[test]` fn for its runtime assertions, plus the module-scope items
64+
that test exercises.
65+
66+
## Explain what each test covers
67+
68+
Open every test file with a brief comment stating **what behavior it exercises**,
69+
and annotate individual tricky cases inline. Where it helps a reader, link to the
70+
owning reference document (for example `// see docs/reference/macros/cgp_impl.md`).
71+
Tests link **to** the documentation; the documentation never links back to a test
72+
(per `docs/CLAUDE.md`).
73+
74+
## Use macro snapshots sparingly
75+
76+
`cgp-macro-test-util` provides `snapshot_*!` macros (`snapshot_cgp_component!`,
77+
`snapshot_cgp_impl!`, `snapshot_delegate_components!`, …). Each **emits the real
78+
generated code** into the module *and* generates a `#[test]` that asserts a
79+
pretty-printed inline `insta` snapshot of it — so adding or removing a snapshot
80+
never changes the compile/runtime coverage, only the golden assertion. Always keep
81+
the snapshot string **inline** in the file (`@"…"`).
82+
83+
The rule for when to snapshot: **snapshot a macro only in the concept target that
84+
owns that macro's feature; everywhere else invoke the macro plainly.** Concretely,
85+
each macro has one canonical full-expansion snapshot (plus snapshots for its
86+
genuinely distinct variants) in its owning target, and nowhere else:
87+
88+
| Macro | Owning target(s) |
89+
| --- | --- |
90+
| `#[cgp_component]` | `basic_delegation` (+ generic variant in `generic_components`) |
91+
| `#[cgp_impl]` | `basic_delegation` (+ `higher_order_providers`, `implicit_arguments` variants) |
92+
| `#[cgp_type]` | `abstract_types` |
93+
| `#[cgp_getter]` / `#[cgp_auto_getter]` | `getters` |
94+
| `#[cgp_fn]` | `implicit_arguments`, `impl_side_dependencies` |
95+
| `delegate_components!` | `basic_delegation` (basic), `namespaces` (open/namespace), `dispatching` (`UseDelegate`) |
96+
| `check_components!` / `delegate_and_check_components!` | `checking` |
97+
| `cgp_namespace!` | `namespaces` |
98+
| `#[blanket_trait]` | `blanket_traits` |
99+
| `#[derive(HasField)]` / `HasFields` / `CgpData` | `field_access` / `extensible_records` / `extensible_variants` |
100+
101+
When a file uses one of these macros as **incidental scaffolding** — a
102+
`#[cgp_component]` needed to set up a `delegate_components!` test, say — write the
103+
plain macro, not the snapshot form. The expansion is already pinned in the owning
104+
target, and a redundant snapshot only adds golden output that breaks on unrelated
105+
macro changes.
106+
107+
## Adding a failure case (in `cgp-macro-tests`)
108+
109+
CGP will have corner cases it does not yet handle. Do **not** try to fix them inline
110+
while refactoring; capture them as failing-behavior tests instead, in a dedicated
111+
failure-case target:
112+
113+
- **Input that should be rejected** — assert the `cgp-macro-core` parser rejects it,
114+
using the `assert_rejects` helper pattern (see `ident_with_type_params`).
115+
- **A macro that emits invalid Rust** — capture the expanded code as an `insta`
116+
inline snapshot (the snapshot is a *string*, so it compiles even though the code
117+
would not), and add a code comment explaining **why** the output is wrong and
118+
**what the correct output should be**.
119+
120+
Every failure case must also be recorded in the reference document that owns the
121+
construct, under its `## Known issues` section (the heading `docs/CLAUDE.md`
122+
mandates), describing the behavior without referring to the test. Put a link from
123+
the test's comment to that reference document.
124+
125+
## Keep the docs in sync
126+
127+
This suite is one of the four views of CGP's truth, alongside the macro
128+
implementation in `cgp-macro-core`, the reference documents in `docs/reference`, and
129+
the `/cgp` skill (see `docs/CLAUDE.md`). When a test reveals or pins a behavior
130+
worth documenting, update the reference document to explain that behavior directly —
131+
without referring to the test. When you move a test that a reference document's
132+
`## Source` section links to, update the link in the same change.
133+
134+
## Running the suite
135+
136+
```
137+
cargo nextest run -p cgp-tests # the main suite
138+
cargo nextest run -p cgp-macro-tests # macro internals + failures
139+
cargo nextest run --workspace # everything
140+
141+
cargo insta test -p cgp-tests --review # review snapshot diffs
142+
cargo insta test -p cgp-tests --accept # accept intended snapshot changes
143+
```
144+
145+
A snapshot test that fails prints a diff of the generated code; accept it with
146+
`cargo insta` only after confirming the change is intended.
147+
148+
## Migration status
149+
150+
The suite was reorganized from a by-construct layout to this by-concept layout. As
151+
categories grow, keep splitting them per the rule above, and keep expanding failure
152+
coverage in `cgp-macro-tests` and cross-crate coverage in the `cgp-test-crate-*`
153+
packages — these were established with representative cases and are meant to grow.

crates/tests/README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# CGP test suite
2+
3+
This directory holds the test suite for Context-Generic Programming. The tests
4+
are organized **by CGP concept** — basic delegation, abstract types, implicit
5+
arguments, namespaces, and so on — rather than by the macro that implements each
6+
concept, because a single macro (for example `delegate_components!`) serves many
7+
concepts at once. If you are maintaining or extending the suite, read
8+
[CLAUDE.md](CLAUDE.md) first; it is the authoritative guide to the conventions.
9+
This README is the map.
10+
11+
## The crates
12+
13+
The suite is split into three kinds of crate, each with a distinct job.
14+
15+
**`cgp-tests`** is the main suite: realistic example code that must compile and
16+
run. Because much of CGP is compile-time wiring, a test here often passes simply
17+
by compiling. It is also where the user-facing macros are exercised end-to-end and
18+
where the canonical macro-expansion snapshots live.
19+
20+
**`cgp-macro-tests`** tests the macro internals directly against `cgp-macro-core`
21+
(the parsers and AST types), and is the home for **failure cases** — inputs CGP
22+
should reject, and cases where a macro currently emits invalid code.
23+
24+
**`cgp-test-crate-a`** and **`cgp-test-crate-b`** are auxiliary packages for
25+
**cross-crate** behavior. Crate A defines components, a provider, and a namespaced
26+
component; crate B (a downstream crate) wires them, supplies its own provider for a
27+
foreign component, and participates in crate A's namespace — showing that CGP stays
28+
within Rust's coherence and orphan rules across crate boundaries.
29+
30+
## How the tests are laid out
31+
32+
Inside `cgp-tests`, each concept is one **integration test target**, which Cargo
33+
compiles as its own crate (its own coherence scope). A target is an entrypoint file
34+
`tests/<concept>_tests.rs` plus a module directory `tests/<concept>/` holding one
35+
`.rs` file per unit test. Each unit-test file is self-contained: it defines its own
36+
components, providers, and context types at module scope, so the type-level wiring
37+
of one test never leaks into another. `tests/basic_delegation/` is the reference
38+
example of this layout.
39+
40+
The concept targets currently cover: basic delegation, impl-side dependencies,
41+
implicit arguments, higher-order providers, generic components, abstract types,
42+
getters, field access, extensible records, extensible variants, checking,
43+
dispatching, namespaces, handlers, monadic handlers, async and Send bounds, and
44+
blanket traits. This set grows and subdivides over time — when a concept
45+
accumulates too many cases to stay coherent, it is split into finer targets.
46+
47+
`cgp-macro-tests` follows the same target/`_tests.rs` shape: `ident_with_type_params`
48+
for parser corner cases, and the failure-case targets `parser_rejections` and
49+
`invalid_expansion`.
50+
51+
## Snapshots
52+
53+
Many tests assert the exact code a macro generates, using the `snapshot_*!` macros
54+
from `cgp-macro-test-util`. Each such macro emits the real generated code into the
55+
module **and** generates a `#[test]` asserting a pretty-printed inline `insta`
56+
snapshot of it. Snapshots are used deliberately: a macro's expansion is snapshotted
57+
only in the concept target that owns that macro's feature, and written plainly
58+
everywhere else (see [CLAUDE.md](CLAUDE.md) for the ownership rules).
59+
60+
## Running the tests
61+
62+
```
63+
cargo nextest run -p cgp-tests # the main suite
64+
cargo nextest run -p cgp-macro-tests # macro internals + failure cases
65+
cargo nextest run --workspace # everything, including the aux crates
66+
67+
cargo insta test -p cgp-tests --review # review snapshot diffs interactively
68+
cargo insta test -p cgp-tests --accept # accept intended snapshot changes
69+
```
70+
71+
When a snapshot test fails it prints a diff of the generated code; accept the new
72+
output with `cargo insta` only after confirming the change is intended.

crates/tests/cgp-macro-tests/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ keywords = { workspace = true }
1111
[dependencies]
1212
cgp = { workspace = true }
1313
cgp-macro-test-util = { workspace = true }
14+
insta = { version = "1.48.0" }
1415

1516
[dev-dependencies]
1617
cgp-macro-core = { workspace = true }
18+
cgp-macro-lib = { workspace = true }
1719
syn = { version = "2.0.95" }
1820
quote = { version = "1.0.38" }
1921
proc-macro2 = { version = "1.0.92" }

crates/tests/cgp-macro-tests/tests/ident_with_type_params.rs

Lines changed: 0 additions & 1 deletion
This file was deleted.

crates/tests/cgp-macro-tests/tests/ident_with_type_params_tests/mod.rs renamed to crates/tests/cgp-macro-tests/tests/ident_with_type_params/mod.rs

File renamed without changes.

crates/tests/cgp-macro-tests/tests/ident_with_type_params_tests/new_ident_with_type_args.rs renamed to crates/tests/cgp-macro-tests/tests/ident_with_type_params/new_ident_with_type_args.rs

File renamed without changes.

crates/tests/cgp-macro-tests/tests/ident_with_type_params_tests/new_ident_with_type_generics.rs renamed to crates/tests/cgp-macro-tests/tests/ident_with_type_params/new_ident_with_type_generics.rs

File renamed without changes.

crates/tests/cgp-macro-tests/tests/ident_with_type_params_tests/path_with_type_args.rs renamed to crates/tests/cgp-macro-tests/tests/ident_with_type_params/path_with_type_args.rs

File renamed without changes.

0 commit comments

Comments
 (0)