laboratory

package
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 20, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SetScopeRunnerForTest added in v0.12.0

func SetScopeRunnerForTest(runner commandscope.Runner) func()

SetScopeRunnerForTest replaces the process the scope resolution would start, and returns the restore.

Types

type Laboratory

type Laboratory struct {
	// contains filtered or unexported fields
}

Laboratory runs each mutant in a sandbox, and keeps the sandboxes.

Building one is not cheap: it walks the whole repository and creates a symlink per file, measured at roughly 0.45ms per file. Nothing about a sandbox depends on which mutant will run in it, so rebuilding it for every mutant paid that walk once per mutant rather than once per run.

Sandboxes are pooled rather than shared, so a caller running mutants concurrently draws as many as its peak concurrency and a sequential one — which is what ditto is built for — only ever builds one. The number alive at any instant is therefore the same as when each mutant built its own, which matters because that number is also what an interrupted run leaves behind.

It is also the only place that can learn what the configured test command executes, without paying for it: the baseline run it already makes once per release prints the `go test -json` stream that names every package the command executes, and the sandbox it ran in is the tree to resolve the rest against. See Executes and internal/commandscope.

func New

func New(logger ditto.Logger, testRunner TestRunner, temporaryDirectory TemporaryDirectory) *Laboratory

func (*Laboratory) Executes added in v0.12.0

func (l *Laboratory) Executes(repository ditto.Repository, relativePath string) bool

Executes reports whether the configured test command can execute the package that owns a repository-relative source path.

This is the one question the report cannot answer for itself. Mutants come from files the scope selected and are judged by one command, and when that command does not compile a mutant's package the mutant is not badly tested, it is unmeasured: nothing the command runs can kill it, so it survives, and a score counting it mixes 'your tests missed this' with 'your command cannot see this'. Measured on the report that asked for this: 43 mutants, 20 survivors, 17 of them in one package the command never builds, docs/reports/ditto-mutation-scope.md.

It answers true when the question cannot be answered — a command that is not `go test -json` has no readable package scope, and so does one whose toolchain cannot be asked — because a wrong accusation fails a run that was correct, while a missing one only leaves the caller with what it had before.

The first call is what pays for the baseline on a release that never asks otherwise. There is no second cost: the same sandbox and the same once.

There is no nil check on the scope, and that is deliberate rather than an omission. The baseline always produces one — commandscope.New answers for an empty stream by declining to refuse anything — so a guard here would be unreachable, and a guard no test can distinguish from its absence is dead weight pretending to be defense. Measured: deleting it changed no test.

func (*Laboratory) Test

type TemporaryDirectory

type TemporaryDirectory interface {
	New() string
}

type TestRunner

type TestRunner interface {
	Test(repository ditto.TemporaryRepository) result.Result[string]
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL