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
- type Config
- type OSRunner
- type Range
- type Repository
- func (r *Repository) CopyGenerated(sandbox *Sandbox, generated []string) ([]string, error)
- func (r *Repository) Files(excludedPrefixes []string) ([]string, error)
- func (r *Repository) LoadConfig() (Config, error)
- func (r *Repository) Materialize() (*Sandbox, error)
- func (r *Repository) RejectPartial(files []string) error
- func (r *Repository) Root() string
- func (r *Repository) ScopeOf(files []string) (Scope, error)
- type Runner
- type Sandbox
- type Scope
Constants ¶
const ConfigName = ".ditto.json"
ConfigName is the file a repository uses to name what the sandbox is missing.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶ added in v0.6.0
type Config struct {
// Generated are repository-relative paths copied from the working tree into
// the sandbox after the index is materialised.
//
// Every one of them must be untracked. A tracked path has an index version,
// and letting the working tree's win is exactly the hole the sandbox exists
// to close — so naming one here is refused rather than obeyed.
Generated []string `json:"generated"`
}
Config is what a repository tells ditto about itself.
There is exactly one thing to say, and it exists because of one honest gap: a sandbox is built from the index, and some repositories do not build from their index alone. A generated directory the build needs — an embedded frontend bundle, generated bindings — is on disk and not in git, so the sandbox arrives without it and the package that needs it cannot compile.
Reading the index is not the part to give up: it is what makes a verdict about the change rather than about the desk it was written on, measured at 7 of 8 verdicts moving when a release read the worktree instead. So this does not widen what is read. It names, one path at a time, what git does not carry.
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) CopyGenerated ¶ added in v0.6.0
func (r *Repository) CopyGenerated(sandbox *Sandbox, generated []string) ([]string, error)
CopyGenerated puts the configured paths into a sandbox and reports what it copied, so a run says which of its bytes did not come from the index.
A path that does not exist is refused rather than skipped. It is named because the build needs it, and a sandbox quietly missing it produces the failure this setting was added to prevent.
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) LoadConfig ¶ added in v0.6.0
func (r *Repository) LoadConfig() (Config, error)
LoadConfig reads the repository's configuration. A repository without one is not an error: most do build from their index, and those need to say nothing.
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.