Documentation
¶
Index ¶
- type BatchLaboratory
- type CommandScope
- type Diagnostic
- func (d *Diagnostic) Address() string
- func (d *Diagnostic) Change() string
- func (d *Diagnostic) Diff(differ gomutatedfile.Differ) string
- func (d *Diagnostic) IsOk() bool
- func (d *Diagnostic) Label() string
- func (d *Diagnostic) Path() string
- func (d *Diagnostic) Reason() verdict.Reason
- func (d *Diagnostic) Unmeasured() bool
- func (d *Diagnostic) Virus() string
- type Ditto
- type Laboratory
- type Logger
- type RefusalError
- type Reporter
- type Repository
- type ScoreCalculator
- type TemporaryRepository
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BatchLaboratory ¶ added in v0.3.0
type BatchLaboratory interface {
TestAll(repository Repository, files []*gomutatedfile.GoMutatedFile) []future.Future[result.Result[string]]
}
BatchLaboratory is handed every mutant of one file together.
One compilation can only serve several mutants if the compiler is given all of them at once, and the gated path needs exactly that. It is a separate interface rather than a change to Laboratory so that every laboratory that exists today goes on being asked one mutant at a time, unchanged.
The batch cannot be assembled lower down. Test returns a future, and a laboratory could in principle buffer and resolve later — but testingtlaboratory awaits inside each subtest, immediately, so a laboratory waiting for mutants that have not been submitted yet would wait forever.
type CommandScope ¶ added in v0.12.0
type CommandScope interface {
Executes(repository Repository, relativePath string) bool
}
CommandScope answers whether the configured test command can execute the package that owns a source path.
It is a separate interface rather than part of Laboratory, for the reason BatchLaboratory is: a laboratory that cannot answer is still a laboratory, and the release then runs exactly what it ran before. An implementation is allowed to answer true because it does not know -- see laboratory.Executes -- and the release treats that as "everything is executed", which is what ditto assumed until it could ask.
type Diagnostic ¶
type Diagnostic struct {
// contains filtered or unexported fields
}
func NewDiagnostic ¶
func NewDiagnostic(res future.Future[result.Result[string]], file *gomutatedfile.GoMutatedFile) *Diagnostic
func NewUnmeasuredDiagnostic ¶ added in v0.12.0
func NewUnmeasuredDiagnostic(file *gomutatedfile.GoMutatedFile) *Diagnostic
NewUnmeasuredDiagnostic is a mutant ditto did not run, because the configured test command does not compile the package that owns it.
Nothing the command runs can kill it, so its survival is not evidence about anybody's tests: the score leaves it out of the numerator and the denominator, exactly as it leaves out a mutant that never compiled, and the report names it instead. Measured on the report that asked for it: 43 mutants, 20 survivors, 17 of them in one package the command never builds, printed as a score of 0.53 against a bar of 0.80. docs/reports/ditto-mutation-scope.md.
It carries the result ditto would have reported before it could tell the difference -- a survivor -- so a consumer that does not know about Unmeasured reads what it read before, and none of them meets a nil.
func (*Diagnostic) Address ¶ added in v0.3.0
func (d *Diagnostic) Address() string
Address and Change are what a survivor is reported as before any diff is rendered: where it is, and what it wrote there.
func (*Diagnostic) Change ¶ added in v0.3.0
func (d *Diagnostic) Change() string
func (*Diagnostic) Diff ¶
func (d *Diagnostic) Diff(differ gomutatedfile.Differ) string
func (*Diagnostic) IsOk ¶
func (d *Diagnostic) IsOk() bool
func (*Diagnostic) Label ¶
func (d *Diagnostic) Label() string
func (*Diagnostic) Path ¶ added in v0.12.0
func (d *Diagnostic) Path() string
Path is the repository-relative source file this mutant came from. The report groups the mutants it could not measure by the directory this names.
func (*Diagnostic) Reason ¶ added in v0.7.0
func (d *Diagnostic) Reason() verdict.Reason
Reason is why this mutant died, and Unknown when ditto was not told.
Ditto recognises a killed mutant by a non-zero exit, which a mutant that never compiled also produces. Measured on internal/schemata/instrument.go: 78 mutants, 50 reported killed, 10 of which did not compile and 1 of which hung until its timeout -- 22% of the kills credited to tests that never ran. See docs/metrics.md, metric 2.
A survivor has no death to explain, so it answers Unknown too. The reason is about a kill.
func (*Diagnostic) Unmeasured ¶ added in v0.12.0
func (d *Diagnostic) Unmeasured() bool
Unmeasured reports a mutant that was never run because the test command does not compile the package that owns it.
Read it BEFORE IsOk. An unmeasured diagnostic is shaped like a survivor -- that is what it would have been, before ditto could tell the difference -- so a consumer that ignores this reads exactly what it read before this existed, which is the graceful half. The shipped reporter does not ignore it.
func (*Diagnostic) Virus ¶ added in v0.7.0
func (d *Diagnostic) Virus() string
Virus names the mutation operator behind this diagnostic, which is the unit a non-viable mutant is fixed in. docs/metrics.md metric 1.
type Ditto ¶
type Ditto struct {
// contains filtered or unexported fields
}
func New ¶
func New(logger Logger, repository Repository, laboratory Laboratory, reporter Reporter, scopes ...CommandScope) *Ditto
func (*Ditto) Release ¶
Release mutates every source file and reports what each mutant did.
Mutants are kept together by the file they came from, because a laboratory that compiles once for a whole file has to receive the file's mutants at once. The order they are reported in is unchanged: sources are walked in order, and each file's mutants in the order its viruses produced them.
A file whose package the test command cannot execute is not run at all: its mutants are recorded as unmeasured, and the report says so. Running them would buy a survivor nobody can kill with a full run of the suite, and the number that came back would be a mixture of two different questions.
type Laboratory ¶
type Laboratory interface {
Test(repository Repository, file *gomutatedfile.GoMutatedFile) future.Future[result.Result[string]]
}
type RefusalError ¶ added in v0.4.0
type RefusalError struct {
// contains filtered or unexported fields
}
RefusalError is the one thing a release says by stopping.
A laboratory that finds the suite already red cannot return that finding: Test hands back a future of a result, and every value in it means something about a mutant. There is no mutant here — the run has not started, and scoring anything would be scoring a suite that tested nothing. So it panics, and the panic is the verdict rather than a defect.
It is a type rather than a string because the two entry points need different things from it. Release lets it through, so a test fails with the message printed as the panic value. Run has to turn it into an error, and recognising it by matching text would tie the entry point to a sentence somebody will reword.
func NewRefusalError ¶ added in v0.4.0
func NewRefusalError(message string) RefusalError
NewRefusalError builds the refusal a laboratory panics with.
func (RefusalError) Error ¶ added in v0.4.0
func (r RefusalError) Error() string
Error is the message, and is what both the panic printer and Run report.
type Reporter ¶
type Reporter interface {
AddDiagnostic(diagnostic *Diagnostic)
Summarize() result.Result[any]
}
type Repository ¶
type Repository interface {
ListGoSourceFiles() []*gosourcefile.GoSourceFile
LinkAllToTemporaryRepository(temporaryPath string) TemporaryRepository
}