One function at a time (--run and --mutagens)
You, or an agent, are killing the survivors of one function. Each try means a new test and another run, and a full run tests every mutant in the file and rewrites the project report each time. --run mutates only the functions you name, --mutagens only the mutagens you pick, and the results go to a separate directory, so the project report stays as it was until you merge them.
Where you start
Take the calc package from the quick start, with one more function below InRange in calc.go, and its test in calc_test.go:
// Percent returns part as a whole percentage of total, rounded down.// It expects total > 0.func Percent(part, total int) int { return part * 100 / total}func TestPercent(t *testing.T) { if got := Percent(1, 4); got != 25 { t.Errorf("Percent(1, 4) = %d, want 25", got) } if got := Percent(3, 3); got != 100 { t.Errorf("Percent(3, 3) = %d, want 100", got) }}A full run now tests 23 mutations, 13 in InRange and 10 in Percent:
gooze run ./...Mutation score: 91.30% (21 killed, 2 survived; 0 cached, 23 tested)
The report shows that both survivors are in InRange, on lines 10 and 13. Percent has none:
PATH │ MUTATIONS │ KILLED │ SURVIVED │ NOT COVERED │ SCORE
───────────────┼───────────┼────────┼──────────┼─────────────┼────────
calc.go │ 23 │ 21 │ 2 │ 0 │ 91.30%
───────────────┼───────────┼────────┼──────────┼─────────────┼────────
TOTAL FILES 1 │ 23 │ 21 │ 2 │ 0 │ 91.30%
Survivors (2):
calc.go:10: survived (comparison): if n < lo { -> if n <= lo {
calc.go:13: survived (comparison): if n > hi { -> if n >= hi {
Mutation score: 91.30% (21 killed, 2 survived)You want to work on InRange alone, without testing Percent's mutants on every try.
Preview the function
Add the first boundary case to the table in TestInRange:
{0, 0, 10, true}, // lower bound is insideThen check what a run limited to InRange would test:
gooze run --estimate --run '^InRange$' ./...Only functions matching ^InRange$. PATH │ MUTATIONS ───────────────┼─────────── calc.go │ 13 ───────────────┼─────────── TOTAL FILES 1 │ 13
The first line names the selection. The count is 13: the mutations of InRange only. The 10 in Percent are left out.
Run it
gooze run --run '^InRange$' ./...Mutation score: 92.31% (12 killed, 1 survived; 0 cached, 13 tested; functions matching ^InRange$) Reports of this run are in .gooze-reports/run; add them to the main reports with gooze report merge.
The score covers only the 13 mutations of InRange, and the end of the line says so (functions matching ^InRange$). The new case killed the survivor on line 10. The next line says where the reports of this run are.
See what still survives
Point gooze report view at the run's directory with -o:
gooze report view -o .gooze-reports/run PATH │ MUTATIONS │ KILLED │ SURVIVED │ NOT COVERED │ SCORE
───────────────┼───────────┼────────┼──────────┼─────────────┼────────
calc.go │ 13 │ 12 │ 1 │ 0 │ 92.31%
───────────────┼───────────┼────────┼──────────┼─────────────┼────────
TOTAL FILES 1 │ 13 │ 12 │ 1 │ 0 │ 92.31%
Survivors (1):
calc.go:13: survived (comparison): if n > hi { -> if n >= hi {
Mutation score: 92.31% (12 killed, 1 survived)One survivor is left, on line 13. The project report hasn't changed; without -o, gooze report view still shows the full run:
gooze report viewMutation score: 91.30% (21 killed, 2 survived)
Write the next test, run again
Add the second boundary case and run the same command:
{10, 0, 10, true}, // upper bound is insidegooze run --run '^InRange$' ./...Mutation score: 100.00% (13 killed, 0 survived; 0 cached, 13 tested; functions matching ^InRange$) Reports of this run are in .gooze-reports/run; add them to the main reports with gooze report merge.
Every narrowed run empties .gooze-reports/run/ first, so it holds only the latest run. Repeat this loop (write a test, run, read the survivors) until the function has no survivors you can kill.
Add the results to the project report
When you're done, fold the run into the main reports:
gooze report mergeMutation score: 100.00% (13 killed, 0 survived)
merge removes run/. What it keeps of the main reports depends on whether the file changed since they were written:
- Unchanged file (same source and test file hashes): the run's results replace only the matching mutations, and the file stays complete.
- Changed or new file: the run's results replace all of the file's results. If the run tested only part of the file, the file is marked partial, and the next full
gooze runre-tests it in full.
Here calc_test.go changed, so calc.go is now partial: the report has the 13 results of InRange, and Percent's 10 are gone until the next full run:
PATH │ MUTATIONS │ KILLED │ SURVIVED │ NOT COVERED │ SCORE ───────────────┼───────────┼────────┼──────────┼─────────────┼───────── calc.go │ 13 │ 13 │ 0 │ 0 │ 100.00% ───────────────┼───────────┼────────┼──────────┼─────────────┼───────── TOTAL FILES 1 │ 13 │ 13 │ 0 │ 0 │ 100.00% No survivors. Mutation score: 100.00% (13 killed, 0 survived)
The next full run re-tests the whole file and gives the project score:
gooze run ./...Mutation score: 100.00% (23 killed, 0 survived; 0 cached, 23 tested)
Unchanged files are skipped
A narrowed run uses the main reports as its cache. A file whose source and test file haven't changed since the main reports is skipped, and it isn't in the run's score. Run --run '^InRange$' again right after that full run, and there is nothing to test:
Mutation score: 100.00% (no mutations; functions matching ^InRange$) Reports of this run are in .gooze-reports/run; add them to the main reports with gooze report merge.
That's why the loop starts with a new test: changing the test file is what puts the function back in the run. To test it without a change, add --no-cache.
How --run matches functions
--run works like go test -run, but on the functions being mutated instead of the tests:
- The pattern is a Go regular expression and isn't anchored:
RangematchesInRange. Write^InRange$for an exact match. - It is matched against the function each mutation is in: its name, and for a method also
Type.Name. Pointer, value and generic receivers all count as the type name, so a methodPushonStack[T]matchesPushandStack.Push. - Code in a closure belongs to the function that contains it.
- Package-level code (
varandconstdeclarations) matches no pattern, so a--runrun never mutates it. - Each mutant still runs all the tests of its own package, never the whole module.
--runis a flag only; there is no config key or environment variable. It works with--estimate, path arguments,-x/--exclude,--mutagensand--diff.
A pattern that doesn't compile exits with code 2:
Running with 1 worker(s) (shard 0/1)
Error: invalid --run pattern "(": error parsing regexp: missing closing ): `(`--run with --shard is rejected: a run of a few functions is small enough for one job.
Error: --run can't be combined with --shard: a function-level run is small, so run it in one job
Only some mutagens (--mutagens)
--mutagens generates only the mutagens you list. The names are the nine that run by default (arithmetic, boolean, numbers, comparison, logical, unary, bitwise, increment, assignment) and the three that run only when you name them (branch, statement, loop, see Mutagens you select), in any case, comma-separated or with the flag repeated. Only the arithmetic and numbers mutants of calc, all of them in Percent:
gooze run --estimate --mutagens arithmetic,numbers ./...Only mutagens arithmetic, numbers. PATH │ MUTATIONS │ CACHE ───────────────┼───────────┼─────────── calc.go │ 10 │ cached ───────────────┼───────────┼─────────── TOTAL FILES 1 │ 10 │ 10 CACHED 10 of 10 mutations are in cached files and will not be re-tested; use --no-cache to re-test everything.
Nothing changed since the full run, so the 10 mutations are cached. Combine it with --run to narrow both ways, for example --run '^InRange$' --mutagens comparison. An unknown name exits with code 2 and lists the valid ones:
Error: unknown mutagen "conditional"; gooze has arithmetic, boolean, numbers, comparison, logical, unary, bitwise, increment, assignment, branch, statement, loop exit code 2
Unlike --run, --mutagens also has a config key, run.mutagens (a list, empty by default, which means the nine defaults), and the environment variable GOOZE_RUN_MUTAGENS. A value there narrows every run, so every run writes to run/ and none updates the main reports. Prefer the flag for one-off runs.
The run directory
Every run with --diff, --run or --mutagens is a narrowed run, and they all follow the same rules:
- Its reports go to a fresh
<output>/run/(by default.gooze-reports/run/), orrun/shard_Nwhen sharded. It never changes the main reports. - It uses the main reports as its cache: an unchanged file is skipped,
--no-cachetests it anyway, and editing its test file makes it re-test. - Its score covers only the selection and names it at the end of the line, followed by a line that points to
run/andgooze report merge. gooze report view -o .gooze-reports/runshows its results and survivors.- The main reports, the cache,
gooze report viewwithout-o, and goozels ignorerun/: they don't read subdirectories of the reports directory. gooze report mergefoldsrun/andrun/shard_*into the main reports (see above) and removesrun/.
Each report file in run/ records what the run selected in a scope field (see Report format).
When not to use them
- Don't gate CI on a
--runor--mutagensscore. It covers only the selection. Gate on a full run, or on a diff run for pull requests. - Don't use
--mutagensto hide a mutagen whose survivors you don't like. If a survivor is an equivalent mutant that no test can kill, mark it with//gooze:ignoreand a reason (see Ignoring code). - Don't push a narrowed run's reports as the OCI baseline. Push after a full run, or after
gooze report merge. - Merge before you read the project score. Until you run
gooze report merge,gooze report viewshows the main reports without the narrowed run. - Don't expect faster tests per mutant.
--runselects fewer mutants; each one still runs the whole package suite.
FAQ
How do I run Gooze on one function only?
Run gooze run --run '^InRange$' ./... with the function's name. The pattern is a Go regular expression, like go test -run, matched against the name of the function each mutation is in (Name, and Type.Name for methods). It is not anchored, so write ^Name$ for an exact match.
Does --run make each mutant faster to test?
--run make each mutant faster to test?No. Every mutant still runs all the tests of its own package (never the whole module). --run only generates fewer mutants, so the run is shorter because there is less to test.
Does a --run or --mutagens run change my project reports?
--run or --mutagens run change my project reports?No. It writes its reports to .gooze-reports/run/ and leaves the main reports as they were. gooze report merge adds them to the main reports and removes run/.
Why does a --run run print no mutations?
--run run print no mutations?The file has stored results in the main reports and neither it nor its test file changed, so the run skips it. Change the test file, or pass --no-cache to test the function anyway.
Which names does --mutagens accept?
--mutagens accept?Every mutagen Gooze has, in any case: the nine that run by default (arithmetic, boolean, numbers, comparison, logical, unary, bitwise, increment, assignment) and the three that run only when named (branch, statement, loop). Separate them with commas or repeat the flag. Any other name exits with code 2.
Can I combine --run with --shard?
--run with --shard?No. Gooze exits with --run can't be combined with --shard: a function-level run is small, so run it in one job. --mutagens and --diff work with --shard.
Should CI gate on the score of a --run or --mutagens run?
--run or --mutagens run?No. That score covers only the selected functions or mutagens, so it says nothing about the rest of the code. Gate on a full run, or on a diff run for pull requests.