Skip to content

Snapshot Testing for CGP Proc Macros#236

Merged
soareschen merged 31 commits into
mainfrom
macro-snapshot-test
Jun 19, 2026
Merged

Snapshot Testing for CGP Proc Macros#236
soareschen merged 31 commits into
mainfrom
macro-snapshot-test

Conversation

@soareschen

@soareschen soareschen commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

AI Summary

This PR introduces a complete snapshot testing infrastructure for the CGP procedural macros, and then migrates the existing macro test suite to use it. The goal is to pin down the exact code that macros such as #[cgp_component], #[cgp_impl], #[cgp_getter], #[cgp_fn], and delegate_components! generate, so that any unintended change to the generated output is caught as a reviewable diff rather than slipping through silently.

The change is almost entirely additive with respect to the production macros. It adds two new crates and a large volume of test conversions, but it does not alter the runtime behavior of the CGP macros themselves. The only production code touched is a small refactor that promotes an existing helper function to a shared, public location so the new tooling can reuse it.

High Level Concepts

The core idea is that each CGP macro now has a matching snapshot_*! companion macro. When you wrap a normal macro invocation inside its snapshot companion, two things happen at once. First, the companion macro emits the real generated code into the surrounding module, exactly as the underlying macro would, so the generated traits, structs, and implementations remain live and usable. Second, it generates a #[test] function that captures a pretty printed string of that same generated code and asserts it against an inline snapshot using the insta crate.

Because the real code is still emitted, migrating an existing test to a snapshot macro never removes any compile time or runtime coverage. It only layers a snapshot assertion on top of whatever the test was already verifying. This is why the migration could be applied broadly across the suite without weakening the existing checks.

The snapshots themselves are produced by parsing the generated token stream, stripping the internal ::cgp::macro_prelude:: qualifier so the output reads as ordinary Rust, and then formatting it with prettyplease. Because the snapshot tooling calls directly into the real macro logic in cgp-macro-lib, the captured output is guaranteed to match what the production macros generate.

Structural Changes

Two new crates

The PR adds a pair of crates under crates/macros, following the same split that the production macros already use. The crate cgp-macro-test-util is the proc macro shell, containing only thin #[proc_macro] entry points that forward to the implementation crate. The crate cgp-macro-test-util-lib is a normal library crate that holds the actual logic, which means the parsing and snapshot machinery can be unit tested without the restrictions imposed on proc macro crates.

The implementation crate is organized into three areas. The entrypoints module contains one function per snapshot macro. The types module contains the syn parsers, including MacroSnapshot for the trailing test block, AttributeMacroSnapshot for attribute style macros such as #[cgp_component], and StatementMacroSnapshot for statement style macros such as delegate_components!. The functions module contains helpers for parsing attributes and for pretty formatting.

The family of snapshot macros

The following snapshot macros are provided, each wrapping the production macro of the corresponding name: snapshot_cgp_component!, snapshot_cgp_impl!, snapshot_cgp_provider!, snapshot_cgp_new_provider!, snapshot_cgp_auto_getter!, snapshot_cgp_getter!, snapshot_cgp_fn!, snapshot_cgp_type!, snapshot_delegate_components!, snapshot_check_components!, snapshot_delegate_and_check_components!, and snapshot_cgp_namespace!. Each accepts the same invocation forms as the macro it wraps, since the body is forwarded verbatim to the real macro logic.

Refactor in cgp-macro-core

The helper strip_macro_prelude, which removes the internal prelude qualifier from a token stream, previously existed as a private function inside parse_internal.rs. It has been moved into a new strip module and made public so that the snapshot tooling can reuse the identical logic when formatting its output. This is a pure relocation of existing code with no behavioral change, and the original call site continues to use it through the new public path.

Workspace and manifest changes

The two new crates are registered as workspace members and given workspace dependency entries in the root Cargo.toml. The cgp-tests crate now depends on insta and on cgp-macro-test-util, and it drops its const_format dependency, which is no longer needed by the migrated tests. A few minor manifest cleanups are included as well, such as removing now redundant crate descriptions and an unnecessary extern crate proc_macro declaration.

Test suite migration

The bulk of the diff is the migration of the existing macro tests in cgp-tests to the new snapshot macros. This spans roughly eighty test files across the getter, component, function, namespace, preset, handler, and extensible data test groups. Two migration patterns are used. Tests that used a macro at module level are simply wrapped in the matching snapshot macro with a test block appended. Tests that defined CGP components inside a test function are lifted into an inner module, since the generated #[test] function cannot be nested inside another function. Purely compile time tests become an inner module holding the snapshot macro, while tests with runtime assertions keep their original test function and reference the components from the inner module.

Documentation

A detailed README.md is added to the cgp-macro-test-util crate. It explains the motivation for snapshot testing the macros, the layout of the two crates, the full list of available snapshot macros, the anatomy of a snapshot invocation, the workflow for filling in snapshots with cargo insta, and the guidance for migrating existing tests.

Impacts of the Changes

The generated output of every wrapped CGP macro is now pinned by golden snapshots. Any future change to a macro that alters its generated code will fail the corresponding snapshot test and surface a readable diff, so such changes become deliberate and reviewable rather than silent.

The cgp-tests crate gains new build dependencies on insta and cgp-macro-test-util, and it no longer pulls in const_format. The overall workspace now builds two additional crates.

The function strip_macro_prelude becomes part of the public surface of cgp-macro-core, exported from its functions module, where previously it was private. Consumers of that crate can now rely on it.

The migrated tests retain all of their previous compile time and runtime coverage, because the snapshot macros re-emit the real generated code. The restructuring of some tests into inner modules changes how those tests are organized, but it does not reduce what they verify.

The production CGP macros are unchanged in behavior. Apart from the relocation of the prelude stripping helper, no logic in cgp-macro, cgp-macro-lib, or cgp-macro-core is modified, so this PR carries no runtime risk for downstream users of the macros.

Maintainers gain a documented and repeatable workflow for capturing and reviewing macro output, including the ability to accept intended changes in a single step with cargo insta, which lowers the cost of evolving the macro internals with confidence.

@soareschen soareschen merged commit 138f365 into main Jun 19, 2026
5 checks passed
@soareschen soareschen deleted the macro-snapshot-test branch June 19, 2026 21:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant