A Golang mutation testing tool inspired by TMNT "ooze" mutagen.
Gooze helps you measure test suite quality by introducing small, controlled mutations
into your Go source and running tests to see which changes are caught. It supports
Go path patterns (like ./...) for fast targeting and can parallelize mutation runs
to speed up larger projects.
Choosing a tool? See COMPARISON.md.
Install the latest gooze binary to your Go bin directory.
go install gooze.dev/pkg/gooze@latestgooze.dev/pkg/gooze is a vanity import path. For this to work, https://gooze.dev/pkg/gooze?go-get=1 must serve a go-import meta tag pointing at this repo (template: docs/pkg/gooze/index.html).
gooze run [paths...] Run mutation testing
run --estimate Preview files + applicable mutation counts (no tests run)
run --coverage-profile Skip mutations on uncovered lines using a Go coverage profile
gooze report view View previously generated reports
report merge Merge sharded reports into one directory
report push <reference> Push reports to an OCI registry as an artifact
report pull <reference> Pull reports from an OCI registry
gooze config init Generate a default gooze.yaml
gooze version Show version information
Run gooze <command> --help (e.g. gooze report --help) for full flags.
Preview which files will be mutated and how many mutations apply, without running tests.
gooze run --estimate ./...Execute mutation testing across the target paths. With no paths, run defaults
to ./... (the current module, recursively).
gooze run # same as: gooze run ./...
gooze run ./pkg/...Pass a Go coverage profile and gooze will report any mutation on a line that the
profile shows as uncovered as not_covered — immediately, without running its
tests. This speeds up runs (uncovered mutations can never be killed) and makes
test gaps explicit. not_covered counts as a survivor in the mutation score and
is tallied separately (not_covered_mutations in _index.yaml).
go test -coverprofile=coverage.out ./...
gooze run --coverage-profile coverage.out ./...Gooze supports a configuration file (.gooze.yml) for persistent settings, reducing the need to specify options repeatedly on the command line. Place the file in the root of your project or specify its location with the --config flag.
Example .gooze.yml:
output: .gooze-reports
parallel: 4
exclude:
- '^vendor/'
- '^mock_'
shard: 0/3Supported Options:
output: Directory for mutation reports (default:.gooze-reports).parallel: Number of parallel workers for mutation testing.exclude: List of regex patterns to exclude files.shard: Shard index and total (e.g.,0/3for shard 0 of 3).no_cache: Boolean to disable caching (default:false).
Gooze also reads configuration from environment variables. Keys are mapped from config keys by:
- Prefixing with
GOOZE_ - Uppercasing
- Replacing
.and-with_
Flag precedence is generally: CLI flags → env vars → config file → defaults. For logging specifically, --verbose and --log-output override env/config.
| Config key | Env var | Type | Default | Notes |
|---|---|---|---|---|
output |
GOOZE_OUTPUT |
string | .gooze-reports |
Reports output directory |
no-cache |
GOOZE_NO_CACHE |
bool | false |
When true, disables incremental cache |
paths.exclude |
GOOZE_PATHS_EXCLUDE |
string list | [] |
Comma-separated (e.g. ^vendor/,^mock_) |
run.parallel |
GOOZE_RUN_PARALLEL |
int | 1 |
Worker count for run |
run.mutation_timeout |
GOOZE_RUN_MUTATION_TIMEOUT |
int | 120 |
Per-mutation timeout (seconds) (also --mutation-timeout) |
run.coverage_profile |
GOOZE_RUN_COVERAGE_PROFILE |
string | "" |
Go coverage profile path; mutations on uncovered lines become not_covered (also --coverage-profile) |
log.filename |
GOOZE_LOG_FILENAME |
string | .gooze.log |
Log file path (also settable via --log-output) |
log.verbose |
GOOZE_LOG_VERBOSE |
bool | false |
When true, forces debug logging (also --verbose) |
log.level |
GOOZE_LOG_LEVEL |
string/int | info |
debug, info, warn, error (or numeric slog level) |
log.max_size |
GOOZE_LOG_MAX_SIZE |
int | 10 |
MiB before rotation |
log.max_backups |
GOOZE_LOG_MAX_BACKUPS |
int | 3 |
Number of rotated files to keep |
log.max_age |
GOOZE_LOG_MAX_AGE |
int | 28 |
Days to keep old logs |
log.compress |
GOOZE_LOG_COMPRESS |
bool | true |
Gzip rotated logs |
Usage:
- Gooze automatically loads
.gooze.ymlif present in the current directory. - Override specific options via command-line flags (e.g.,
gooze run --output custom-dir ./...).
Tip: Use
.gooze.ymlto standardize settings across your team and CI pipelines.
By default, Gooze writes mutation reports to .gooze-reports (override with -o/--output).
- One YAML file per report:
<hash>.yaml - An index file:
_index.yaml
View the last run:
gooze report viewOr point it at an explicit directory:
gooze report view -o .gooze-reportsGooze supports incremental mutation testing by caching results and skipping unchanged files (use --no-cache to ignore the cache and re-test everything).
How it works:
- After running tests, Gooze stores mutation results in the reports directory (default
.gooze-reports/, configurable with-o) with source file hashes - On subsequent runs, Gooze checks each source file:
- If source or test file content changed → re-run mutations
- If mutator versions changed → re-run mutations
- Otherwise → skip (use cached results)
Example
First run populates the cache:
gooze run ./...Make a small change to a single file:
echo "// comment" >> main.goSecond run re-tests only affected sources and reuses cached results for everything else:
gooze run ./...To ignore the cache and force re-testing everything:
gooze run --no-cache ./...Cache invalidation triggers:
- Source file content hash changed
- Test file content hash changed
- Mutator version changed (e.g., after upgrading Gooze)
- Source file deleted
Store and retrieve mutation reports as OCI artifacts in any registry — built in
via gooze report push / pull (powered by ORAS), so no
external oras or tar steps are needed.
push/pull operate on the reports directory (-o/--output, default
.gooze-reports). Flags: --plain-http (non-TLS registry), --insecure (skip
TLS verification).
Authentication. For registries that require login, set
GOOZE_REGISTRY_USERNAME and GOOZE_REGISTRY_PASSWORD. The password may be a
token/PAT — e.g. for GHCR use your GitHub username with a PAT (or GITHUB_TOKEN
in CI):
export GOOZE_REGISTRY_USERNAME="$USER"
export GOOZE_REGISTRY_PASSWORD="$GITHUB_TOKEN"
gooze report push ghcr.io/your-org/your-repo/gooze-reports:mainIf unset, gooze falls back to the Docker credential store (whatever
docker login saved).
Push reports:
gooze run -o .gooze-reports ./...
gooze report push ghcr.io/your-org/your-repo/gooze-reports:mainPull and view reports:
gooze report pull ghcr.io/your-org/your-repo/gooze-reports:main
gooze report viewIncremental testing in CI — restore the baseline, run, publish:
gooze report pull ghcr.io/your-org/your-repo/gooze-reports:main # restore baseline
gooze run ./... # only changed sources re-tested
gooze report push ghcr.io/your-org/your-repo/gooze-reports:main # publish updated reportsBenefits:
- Reuse cached results across CI runs
- Speed up branch testing by reusing main branch results
- Version and track mutation test results alongside code
- Share baseline reports across team members
When sharding is enabled (-s/--shard INDEX/TOTAL), reports are written to shard subdirectories:
<output>/shard_0/<output>/shard_1/- ...
Example distributed run (3 shards) and merge:
gooze run -o .gooze-reports -s 0/3 ./...
gooze run -o .gooze-reports -s 1/3 ./...
gooze run -o .gooze-reports -s 2/3 ./...
gooze report merge -o .gooze-reports
gooze report view -o .gooze-reportsWith parallel workers:
gooze run -p 4 ./...Exclude files by regex (repeatable):
gooze run -x '^vendor/' -x '^mock_' ./...Tips:
- Use
gooze run --estimateto preview the files and mutation counts before running tests.- Use
--parallelto reduce total runtime on multi-core machines.- Use
-x/--excludeto skip files by regex (path or base name).
Gooze automatically selects the UI based on whether output is a TTY:
- Interactive TUI: Used when running in a terminal.
- Simple/CI UI: Used when output is redirected or in CI.
To skip the interactive UI, pipe output (e.g., gooze run ./... | cat).
Skip generating mutations by placing a single annotation: //gooze:ignore.
You can optionally provide a comma-separated list of mutagen names, e.g. //gooze:ignore arithmetic,comparison.
Mutagen names match the labels shown in output, e.g. arithmetic, comparison, numbers, boolean, logical, unary, branch, statement, loop.
Scope is determined by where the annotation appears:
- File: if the annotation appears before the
packagedeclaration (typically the first line), it applies to the whole file. - Function / method: if the annotation is immediately above a
funcdeclaration, it applies to that entire function/method. - Line: if the annotation appears on its own line directly above a statement, or as a trailing comment on the same line, it applies only to that line.
Examples:
//gooze:ignore arithmetic,comparison
package main
//gooze:ignore
func main() {
x := 1 + 2 //gooze:ignore numbers
if x > 0 { //gooze:ignore comparison
println(x)
}
//gooze:ignore
y := x + 1
_ = y
}- Boolean Literal
- Numbers
- Unary / Negation
- Arithmetic
- Comparison / Relational
- Logical Operators
- Branch (if/else removal, condition inversion, switch case removal)
- Statement (statement deletion: assignments, expressions, defer, go, send)
- Loop (boundary conditions, loop body removal, break/continue removal)
- Core Logic
- Return Value
- Conditional
- Complex Expression
- Slice
- Map
- Pointer & Memory
- Interface / Type Assertion
- Function Signature / Parameter
- Type System & Interfaces
- Global State & Initialization
- Go-Specific Error Handling
- Concurrency & Channels
- Annotation Skipping: Support
//gooze:ignoreto skip file/function/line, optionally per mutagen (Medium) - Custom Exec Hook: Support custom test runner commands similar to
go-mutesting --exec(High) - Function Selection: Allow mutating specific functions/methods via regex (High)
- Timeouts: Per-mutation execution budgets to prevent infinite loops (Medium)
- Config File: Support
.gooze.ymlfor persistent configuration (Medium)
- Run only matching
*_test.gofiles for each mutated source file - Reduces test execution time by running relevant tests only
-
--parallelflag for concurrent mutation testing - Sharding support for distributed execution across multiple machines
- Compatible with parallel execution within shards
- Automatic report merging from multiple shards (
gooze report merge)
- Incremental testing: cache and reuse results for unchanged files
- Per-file mutation reports for granular analysis
- Index file with summary (
_index.yaml) - OCI artifact integration with automated push/pull workflows
- GitHub Actions workflow templates
- GitLab CI pipeline configuration
