Skip to content

Snapshot what generate writes, from packages loaded in memory - #151

Merged
crhntr merged 5 commits into
split/5-command-configurationsfrom
split/6-generate-snapshots
Sep 17, 2026
Merged

crhntr merged 5 commits into
split/5-command-configurationsfrom
split/6-generate-snapshots

Conversation

@crhntr

@crhntr crhntr commented Sep 14, 2026

Copy link
Copy Markdown
Member

internal/load/loadtest type checks a package in memory against the official standard library's export data. internal/generate/testdata holds 32 txtar snapshots of generated files, each with its configuration inline, and runs in milliseconds.

Part 6 of 8, stacked on #150. Replaces #145.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UjkpA4fvZ7xQAsY65Beprp

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Three unresolved moderate issues remain in nested-call handling and in-memory loader behavior.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds in-memory package loading and generator snapshot coverage, including standard-library resolution and hydration tests.

Changes:

  • Adds loadtest utilities for in-memory type checking.
  • Adds loader, hydration, configuration, and generated-file tests.
  • Adds 32 generator snapshots and documents the testing workflow.
File summaries
File Summary / review notes
internal/load/stdlib_test.go Tests standard-library type resolution.
internal/load/loadtest/loadtest.go Adds the in-memory package loader. Moderate (2 votes): export-cache paths containing spaces are parsed incorrectly. Moderate (1 vote): subdirectory embedded assets are omitted.
internal/load/loadtest/loadtest_test.go Tests template loading through the loader.
internal/load/hydrate_test.go Tests configuration hydration behavior.
internal/generate/testdata/synthesized_method_note.txtar Snapshot for synthesized method notes.
internal/generate/testdata/status_codes.txtar Status-code generation snapshot.
internal/generate/testdata/sse.txtar SSE generation snapshot.
internal/generate/testdata/sse_messages_without_datastar.txtar SSE message validation snapshot.
internal/generate/testdata/sse_datastar.txtar Datastar SSE snapshot.
internal/generate/testdata/route_without_call.txtar Route-without-call snapshot.
internal/generate/testdata/result_shapes.txtar Result-shape snapshot.
internal/generate/testdata/response_argument.txtar Response-argument snapshot.
internal/generate/testdata/request_body.txtar Request-body snapshot.
internal/generate/testdata/redirect.txtar Redirect snapshot.
internal/generate/testdata/receiver_method_sets.txtar Receiver method-set snapshot.
internal/generate/testdata/path_parameter_types.txtar Path-parameter snapshot.
internal/generate/testdata/nested_calls.txtar Nested-call snapshot. Moderate (2 votes): nested calls execute after invalid argument parsing and should be skipped.
internal/generate/testdata/multipart.txtar Multipart snapshot.
internal/generate/testdata/marshal_json.txtar JSON-marshaling snapshot.
internal/generate/testdata/last_event_id.txtar Last-event-ID snapshot.
internal/generate/testdata/inferred_methods.txtar Inferred-method snapshot.
internal/generate/testdata/form_values.txtar Form-values snapshot.
internal/generate/testdata/form_struct.txtar Form-struct snapshot.
internal/generate/testdata/flag_without_muxt_version.txtar Version-omission snapshot.
internal/generate/testdata/flag_unexported_identifiers.txtar Unexported-identifier snapshot.
internal/generate/testdata/flag_multiple_files.txtar Multiple-file output snapshot.
internal/generate/testdata/flag_logger_path_prefix_middleware.txtar Logger, prefix, and middleware snapshot.
internal/generate/testdata/flag_htmx.txtar HTMX output snapshot.
internal/generate/testdata/flag_custom_names.txtar Custom-name configuration snapshot.
internal/generate/testdata/execute_callback.txtar Execute-callback snapshot.
internal/generate/testdata/err_signals_without_datastar.txtar Datastar validation-error snapshot.
internal/generate/testdata/err_route_paths_method_collision.txtar Route-path collision snapshot.
internal/generate/testdata/err_response_state_with_response_argument.txtar Response-state error snapshot.
internal/generate/testdata/err_resolution.txtar Resolution-error snapshot.
internal/generate/testdata/err_name_errors.txtar Template-name error snapshot.
internal/generate/testdata/err_duplicate_pattern.txtar Duplicate-route error snapshot.
internal/generate/snapshot_test.go Implements snapshot generation and comparison.
internal/generate/generated_test.go Tests generated-file argument discovery.
internal/cli/configurations_test.go Tests parsed command configurations.
CLAUDE.md Documents the snapshot testing workflow.
Review details

Files not reviewed (1)

  • internal/generate/generated_test.go: Generated file

Suppressed comments (1)

internal/load/loadtest/loadtest.go:163

  • Subdirectory assets are written but omitted from EmbedFiles, so a fixture using //go:embed partials/*.gohtml cannot be hydrated: check.LoadTemplates will not see the matched template even though go/packages would report it. Keep only root .go files in goPaths, but add non-Go files from subdirectories to embedded, and cover this case in loadtest_test.go.
  • Files reviewed: 40/41 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/generate/testdata/generate/nested_calls.txtar
Comment thread internal/load/loadtest/loadtest.go
@crhntr
crhntr force-pushed the split/5-command-configurations branch from d562b63 to 9f9dc50 Compare September 16, 2026 01:36
@crhntr
crhntr force-pushed the split/6-generate-snapshots branch from 28f15b7 to 2b9cff3 Compare September 16, 2026 01:36
@crhntr
crhntr force-pushed the split/5-command-configurations branch from 9f9dc50 to a822bf1 Compare September 16, 2026 06:07
@crhntr
crhntr force-pushed the split/6-generate-snapshots branch 2 times, most recently from 21633ce to aebc4d8 Compare September 16, 2026 06:22
@crhntr
crhntr force-pushed the split/5-command-configurations branch from a822bf1 to 0deba16 Compare September 16, 2026 06:22
@crhntr
crhntr requested a lite review from Copilot September 16, 2026 06:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Two moderate review findings remain unresolved.

Review details

Files not reviewed (1)

  • internal/generate/generated_test.go: Generated file

Suppressed comments (2)

internal/generate/snapshot_test.go:95

  • When a want/<name> file is empty and the generator omits that output, both map lookups yield "", so this comparison treats the missing file as equal and the snapshot passes. Compare map presence as well as content so missing generated files are detected.
				if got[name] != want[name] {

internal/load/loadtest/loadtest.go:165

  • Package is documented as modeling a normal load.Packages result, but this classification puts every root *.go file into GoFiles, including _test.go. A fixture containing tests will therefore type-check test declarations as part of the production package (and can change or break the loaded API), unlike packages.Load with Tests unset; exclude test files from goPaths while still writing them if needed.
  • Files reviewed: 40/41 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@crhntr
crhntr force-pushed the split/6-generate-snapshots branch from aebc4d8 to ed2e17c Compare September 16, 2026 06:38
@crhntr
crhntr force-pushed the split/5-command-configurations branch 2 times, most recently from cc7dcfb to c575f80 Compare September 16, 2026 06:42
@crhntr
crhntr force-pushed the split/6-generate-snapshots branch 2 times, most recently from 452e06d to 2a89249 Compare September 16, 2026 06:58
@crhntr
crhntr force-pushed the split/5-command-configurations branch 2 times, most recently from da4e93d to 336b576 Compare September 16, 2026 07:26
@crhntr
crhntr force-pushed the split/6-generate-snapshots branch 3 times, most recently from 8c594b8 to e0bb019 Compare September 16, 2026 20:30
generate's own tests covered a few percent of it: what a handler looks
like for a form struct, an sse route, a status code in the name or a
redirect in the template was only stated by the integration suite, which
compiles and runs every scratch module and takes over a minute.

internal/load/loadtest builds what a package load returns without loading
the package graph: it writes the files, type checks them in memory, and
imports the official standard library from the export data go list reports
for it, reading it with gcexportdata -- as go/packages does -- and giving
each package the imports go list reports, which is how check finds fmt
behind html/template. The standard library is whichever the go command in
use provides. A test binary pays a few hundred milliseconds once to read
it. TestStandardLibrary states load.StandardLibrary's answers against it,
and TestHydration the order load's hydration reports a missing package, a
missing receiver and a variable that does not evaluate.

internal/generate/testdata/generate/*.txtar holds one case per feature: the
receiver's Go source, the templates, and the files and log lines
generation produces. An archive holds the configuration it generates
with, in its own config.json, beside the command line that parses into it;
nothing outside the archive says what a case is. The directory an archive
is in names the command, so a case runs alone as
-run TestSnapshots/generate/sse. Reading it is
encoding/json/v2, which the module now asks for Go 1.27 to have. TestSnapshots loads the case through loadtest and
load.GenerateSource, as muxt generate does, generates, and compares; the
32 cases cover most of the package. It also fails on a generated file
that imports a package it does not use. go test -run TestSnapshots -update
rewrites the want/ files, and the diff is the review.

A snapshot says what the generator does, not that the result compiles or
serves requests; that stays the integration suite's job.

Assisted-by: Claude:claude-opus-5 gofumpt
@crhntr
crhntr force-pushed the split/6-generate-snapshots branch from e0bb019 to 8f20fbf Compare September 16, 2026 22:07
…mory

muxt check is the command the integration suite runs most, and its
reports -- a field the data type lacks, a template nothing renders, a
route waiting for muxt generate -- were only stated there, behind a
package load per script.

internal/analysis/testdata/<command>/*.txtar holds cases for check, the route
listing, and the template caller and call listings. The directory an
archive is in names the command it runs, so a case runs alone as
-run TestSnapshots/list-template-calls/calls, and the archive holds the
configuration it runs with, in its own config.json, beside the command
line that parses into it. A listing's
configuration needs nothing to read as JSON: regexp.Regexp writes itself
as the text it was compiled from, and internal/configjson says the rest --
a field the command line left alone is null rather than an empty list, and
a member a configuration does not declare is an error. TestSnapshots loads the case through
loadtest and
internal/load's hydration, as the commands do, and compares what the
analysis reports.

Assisted-by: Claude:claude-opus-5 gofumpt
The mutation run's planning -- which templates each ExecuteTemplate call
reaches, with what dot, and which variations apply -- read the loaded
templates, the checker built on them, and the syntax trees it searched
for the string literal a template was written in. So stating any of it
took a module on disk and the go command.

Planning now runs on an input: the source.Package internal/load reads.
loadInput builds one with the go command; planFrom and revisionOf read
nothing else. A template's string literal is found by parsing its file's
text, which planning already holds, instead of searching the loader's
syntax trees. With that, nothing reads load.Templates, and it goes.

internal/mutation/testdata/test-template-mutations/*.txtar snapshots a dry run's report for ten
cases -- a literal template, partials and trims, a template pattern,
skipped mutants, the operand budget, other delimiters, a --diff revision,
and the errors a plan returns -- each loaded through loadtest and planned
in milliseconds. An archive holds the configuration it plans with, in its
own config.json: regexp.Regexp writes itself as the text it was compiled
from, so a pattern reads as the command line wrote it, and a pattern the
command line left alone is null -- "" would be a pattern matching
everything. As in the other two suites, the directory names the command,
so a case runs alone as -run TestSnapshots/test-template-mutations/diff.

CLAUDE.md's recipes for a feature, a bug and an error now start from the
unit test at the layer that owns the behavior, and say what the in-memory
loader cannot stand in for.

Assisted-by: Claude:claude-opus-5 gofumpt
The snapshot archives hold the configuration they run with, and reading
one is encoding/json/v2: it knows how to read a *regexp.Regexp, which v1
could only do through a second struct carrying the pattern's source.
Everywhere else muxt reads or writes JSON went with it, so one library
answers "how does muxt read JSON": the module list, the overlay each
mutant is delivered through, the generated file's import map, and a
command's --format=json result.

Two differences the standard library documents, and the integration suite
insisted on:

- omitempty in v2 omits an empty JSON value -- null, "", [], {} -- and no
  longer a zero number or a false. The fields that meant the latter say
  omitzero now, so a mutant that took no measurable time still reports no
  seconds, and a module listing still leaves out the flags a package does
  not set.

- Reading is case sensitive, and a member the target does not declare is
  an error rather than silence. Every name muxt reads was already written
  by muxt or by the go command, so nothing had to change.

- Writing makes no promise about map order, writes a nil list or map as
  [] or {}, and escapes neither <, >, & nor U+2028 and U+2029, where v1
  sorted map keys, wrote null and escaped them. --format=json is read by
  scripts, so it is written with the options that say v1's choices, and
  TestWriteResultJSON states each one. A listing's import map now writes
  through the encoder it is handed (MarshalJSONTo), so those options reach
  it too, rather than a separate Marshal deciding its order.

The reports and listings are byte for byte what they were.

Assisted-by: Claude:claude-opus-5 gofumpt
A few loops spelled out what the slices and maps packages already name:
gathering a map's keys and sorting them, asking whether a list holds
something, copying a list, and a union of keys de-duplicated by hand.

- slices.Sorted(maps.Keys(m)) where keys were gathered and then sorted:
  the module listing's directories, muxttest.Check's file names, and the
  template source files generation walks. sort is no longer imported
  anywhere in muxt's own code.
- slices.ContainsFunc where a loop or an IndexFunc whose index went unused
  only asked whether something is there: marshalJSON refusing the execute
  callback, and a receiver method already in the interface.
- slices.Clone for File.ImportSpecs' copy; its callers only range over the
  result or take its length.
- sortedKeys in the generate snapshot harness appends every map's keys,
  sorts and compacts, rather than checking Contains before each append.

Loops that find an element to change in place, build a different type,
or return early with more than a yes or no stay as they were. Every
generated file and report is unchanged.

Assisted-by: Claude:claude-opus-5 gofumpt
@crhntr
crhntr merged commit 34652fb into split/5-command-configurations Sep 17, 2026
@crhntr
crhntr deleted the split/6-generate-snapshots branch September 17, 2026 15:58
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.

2 participants