Code Generation
TL;DR
The entire assert/require API (600+ functions) is generated from 76 source assertions in
internal/assertions/. Rungo generate ./...to regenerate everything. Add new assertions by editing source files and adding examples.
Maintaining Generated Code
This repository uses code generation extensively to maintain consistency across assertion packages.
Code Generation Pipeline
graph TD
source["📦 internal/assertions/*.go"]
scanner["🔍 Scanner
go/packages + go/types"]
model["fa:fa-database
Model data structures"]
templates["📝 Templates
Go text/template"]
outputs["📤 Generated Code"]
source --> scanner
scanner --> extract_metadata
extract_metadata --> model
model --> templates
templates --> outputs
subgraph extract_metadata["Extract Metadata"]
direction BT
extract["Extractor"]
comments["godoc comments"] --o extract
examples["examples: values comments"] --o extract
domains["domain tags"] --o extract
sigs["Function signatures"] --o extract
sigs["Other internal annotations comments"] --o extract
end
outputs -.-> assert_package
outputs -.-> require_package
outputs -.-> docs@{shape: documents, label: "docs/doc-site/**/*.md"}
subgraph assert_package
direction BT
assert@{shape: documents, label: "assert/*.go"}
tests_assert["*_test.go files"] --o assert
example_tests_assert["*_examples_test.go files"] --o assert
subgraph not_generated_assert["*not generated*"]
direction LR
docgo_assert@{ shape: document, label: "doc.go" }
adhoc_assert@{ shape: document, label: "*_adhoc*_test.go" }
end
end
subgraph require_package
direction BT
require@{shape: documents, label: "require/*.go"}
tests_require["*_test.go files"] --o require
example_tests_require["*_examples_test.go files"] --o require
subgraph not_generated_require["*not generated*"]
direction LR
docgo_require@{ shape: document, label: "doc.go" }
adhoc_require@{ shape: document, label: "*_adhoc*_test.go" }
end
end
style not_generated_assert fill:#4a9eff,color:#fff
style not_generated_require fill:#4a9eff,color:#fff
The generator scans source code, extracts metadata, builds a model, and applies templates to generate ~800+ functions, tests, and documentation from ~100+ source functions.
Adding a New Assertion
Complete workflow:
Add function to
internal/assertions/<domain>.go:The following example would like go to
string.go, next to theRegexpassertion.Add tests to
internal/assertions/<domain>_test.go: Write comprehensive table-driven tests covering edge cases.Run code generation:
Done! All 8 variants are generated with tests and examples:
assert.StartsWith(t, str, prefix)assert.StartsWithf(t, str, prefix, "msg")a.StartsWith(str, prefix)(forward method)a.StartsWithf(str, prefix, "msg")require.StartsWith(t, str, prefix)require.StartsWithf(t, str, prefix, "msg")r.StartsWith(str, prefix)(forward method)r.StartsWithf(str, prefix, "msg")
How One Function Becomes Eight
graph TD
source["1 Source Function
internal/assertions/Equal()"]
source --> assert_group["assert Package"]
source --> require_group["require Package"]
assert_group --> assert_pkg["assert.Equal(t, a, b)
package-level"]
assert_group --> assert_fmt["assert.Equalf(t, a, b, msg)
formatted variant"]
assert_group --> assert_fwd["a.Equal(a, b)
forward method"]
assert_group --> assert_fwdfmt["a.Equalf(a, b, msg)
forward + formatted"]
require_group --> require_pkg["require.Equal(t, a, b)
package-level (fatal)"]
require_group --> require_fmt["require.Equalf(t, a, b, msg)
formatted variant (fatal)"]
require_group --> require_fwd["r.Equal(a, b)
forward method (fatal)"]
require_group --> require_fwdfmt["r.Equalf(a, b, msg)
forward + formatted (fatal)"]
style source fill:#4a9eff,color:#fff
style assert_group fill:#90ee90,color:#000
style require_group fill:#ffb6c1,color:#000
reflection-based assertions become 8, generic assertions become 4
(plus tests and documentation for each).
Example Annotations Format
The “Examples:” section in doc comments drives test and example generation:
Rules:
- Use valid Go expressions that can be directly inserted into test code
success:andfailure:are required for most assertionspanic:is optional (used for assertions like Panics, YAMLEq)- Multiple examples of the same type are allowed (e.g., multiple
success:lines) - Examples are extracted by the scanner and used to generate:
- Unit tests (success + failure cases)
- Testable examples (success cases only for simplicity)
Example with multiple success cases:
Example-Driven Test Generation
graph LR
doccomment["Doc Comment
with Examples:"]
parser["📖 Example Parser"]
cases["Test Cases
success/failure/panic"]
multiplier["Multiply × 8"]
tests["Generated Tests"]
doccomment --> parser
parser --> cases
cases --> multiplier
multiplier --> pkg_assert["assert package test"]
multiplier --> fmt_assert["assert format test"]
multiplier --> fwd_assert["assert forward test"]
multiplier --> fwdfmt_assert["assert fwd+fmt test"]
multiplier --> pkg_require["require package test"]
multiplier --> fmt_require["require format test"]
multiplier --> fwd_require["require forward test"]
multiplier --> fwdfmt_require["require fwd+fmt test"]
pkg_assert & fmt_assert & fwd_assert & fwdfmt_assert & pkg_require & fmt_require & fwd_require & fwdfmt_require --> tests
style cases fill:orange,color:black;
style multiplier fill:yellow,color:black;
style tests fill:lightgreen,color:black;
Each example in doc comments generates 8 test functions (one per variant), ensuring 100% test coverage of generated code. In addition, the generator produces testable examples (somewhat redundant with “passed” tests) so every function gets a testable example displayed on pkg.go.dev.
Special Cases in Generated Tests
For complex assertions requiring special setup, the test templates support conditional logic. See codegen/internal/generator/templates/assertion_assertions_test.gotmpl for examples of:
- Custom mock selection based on function behavior (mockT vs mockFailNowT)
- Package-specific test helpers (testDataPath, httpOK, etc.)
- Handling functions without test examples (generates
t.Skip())
Some go expressions won’t fit nicely for examples (examples use an external package, e.g. assert_test).
To cover these edge cases, a relocate function map currently rewrites the example values to be used
from an external package. The relocation uses go parsing capabilities. The only hard-coded exception if for PanicFunc.
(see codegen/internal/generator/funcmap.go).
Generator Flags
Current usage with go generate (see doc.go):
Note: Generic functions are planned but not yet implemented.
Verification
After generation, verify:
The 0.5% coverage gap comes from helper functions (non-assertion functions) that don’t have “Examples:” annotations.