Documentation
¶
Overview ¶
Package staged answers three questions about a change that is staged but not committed: which files it touches, which bytes of them, and which bytes the suite should be run against.
The third is the one that is easy to miss and expensive to get wrong. Mutants come from the staged content, but the test command runs against a whole tree, and a tracked file left dirty in the worktree decides what the suite proves. Measured on a fixture built for it: pointed at the worktree instead of the index, seven of eight verdicts moved — a score of 0.13 against 1.00 for the identical eight mutants of an identical file. A guard that only inspects the staged paths cannot see that, because the file that moved them was never staged.
So the index is materialised and the release is pointed at that. It is not an optimisation and it is not belt-and-braces; it is the difference between measuring the change and measuring the desk it was written on.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type OSRunner ¶
type OSRunner struct{}
OSRunner runs processes with the inherited git addressing removed.
type Repository ¶
type Repository struct {
// contains filtered or unexported fields
}
Repository is a checkout the staged questions can be asked of.
func New ¶
func New(runner Runner, directory string) (*Repository, error)
New binds the questions to a checkout. The root is resolved by git rather than taken on trust, so a command run from a subdirectory means the same thing.
func (*Repository) Files ¶
func (r *Repository) Files(excludedPrefixes []string) ([]string, error)
Files lists the staged Go sources worth mutating: not tests, because they are the oracle rather than the subject, and not anything under a prefix the caller excluded.
func (*Repository) Materialize ¶
func (r *Repository) Materialize() (*Sandbox, error)
Materialize writes the index into a temporary directory.
func (*Repository) RejectPartial ¶
func (r *Repository) RejectPartial(files []string) error
RejectPartial refuses a staged file that also has unstaged edits.
The scope is derived from the index and the mutants are written into a copy of the index, so worktree-only edits to a staged file are content the verdict was never about. Reporting on it as though it were is worse than refusing.
func (*Repository) Root ¶
func (r *Repository) Root() string
Root is the directory every other answer is relative to.
func (*Repository) ScopeOf ¶
func (r *Repository) ScopeOf(files []string) (Scope, error)
ScopeOf converts each staged diff into byte ranges of the staged content.
It fails open rather than guessing. A diff that cannot be parsed, or a range that does not land on index bytes, produces a whole-file scope and says why — mutating too much is a cost, and mutating the wrong bytes is a wrong answer.
type Runner ¶
Runner is the seam over running a process, so the git work can be tested without a repository.
type Sandbox ¶
type Sandbox struct {
Root string
// contains filtered or unexported fields
}
Sandbox is a checkout of the index, and the thing a release must be pointed at.
The measurement that put this here: with a release pointed at the worktree instead, and one tracked file left dirty and unstaged, seven of eight verdicts moved — 0.13 against 1.00 for the identical eight mutants of an identical file. RejectPartial does not cover it, because the file that moved them was never staged and a check over staged paths never looks at it.
`git checkout-index` also happens to be the cheap way to do it: it writes exactly the staged content and nothing else, with no second walk of the tree.
type Scope ¶
Scope is what a staged change justifies mutating.
Ranges is keyed by file and stays keyed by file. A byte offset only means something against the file it was measured in, so a scope that has lost track of which file a range came from makes every file answer to every range: the mutant count then grows as the square of the file count, and the extra ones land in code no diff touched.