Documentation
¶
Overview ¶
Package covergate parses Go coverage profiles and enforces a ratchet: coverage may rise freely but may never fall below a recorded baseline.
It is designed for repositories that want to lock in the coverage they already have before starting a large refactor, without committing to an absolute target such as 100 percent.
Typical use from a magefile:
report, err := covergate.Report("coverage.out", covergate.Options{
Exclude: []string{"**/gen/**"},
})
if err != nil {
return err
}
return covergate.Check(report, "coverage-baseline.json", covergate.CheckOptions{})
Index ¶
- Constants
- Variables
- func Check(r Report, b Baseline, opts CheckOptions) error
- func FormatReport(r Report) string
- func Gate(c Config) error
- func Record(c Config, note string) error
- func SaveBaseline(name string, b Baseline) error
- type Baseline
- type Block
- type CheckError
- type CheckOptions
- type Config
- type Delta
- type Options
- type ProfileData
- type Regression
- type Report
- type Stats
Constants ¶
const DefaultBaselineFile = "coverage-baseline.json"
DefaultBaselineFile is the conventional baseline location for a repository.
const ScopeTotal = "total"
ScopeTotal is the scope name used for repository-wide coverage, as opposed to a package import path.
Variables ¶
var ErrNoBaseline = errors.New("coverage baseline not found")
ErrNoBaseline is returned when a baseline file does not exist.
Functions ¶
func Check ¶
func Check(r Report, b Baseline, opts CheckOptions) error
Check enforces the ratchet: every scope in the baseline must still meet its recorded floor, and by default any package new since the baseline must meet the baseline total.
It returns a *CheckError when coverage regressed, so callers can inspect the individual regressions with errors.As.
func FormatReport ¶
FormatReport renders a human-readable summary, worst packages first.
func Gate ¶
Gate parses the profile and enforces the ratchet against the recorded baseline. It is the function a magefile's coverage target should call.
If no baseline exists yet, Gate fails with instructions rather than silently passing, so an unrecorded repository cannot appear to be protected.
func Record ¶
Record measures the current profile and writes it as the new baseline.
It refuses to overwrite a baseline recorded on another platform. Coverage is measured per platform, so re-recording on a developer machine would replace the numbers CI can actually reach with numbers only that machine can reach, and the gate would then fail for everyone else.
func SaveBaseline ¶
SaveBaseline writes a baseline file atomically, with a trailing newline so it stays diff-friendly.
Types ¶
type Baseline ¶
type Baseline struct {
// Total is the minimum acceptable overall percentage.
Total float64 `json:"total"`
// Packages maps a package import path to its minimum percentage.
Packages map[string]float64 `json:"packages"`
// Exclude records the glob patterns used when the baseline was written,
// so a later check cannot silently widen them.
Exclude []string `json:"exclude,omitempty"`
// Mode records the "go test -covermode" counter mode the baseline was
// measured with. Percentages are not comparable across modes, so a later
// check measured in a different mode is rejected.
Mode string `json:"mode,omitempty"`
// OS records the GOOS the baseline was measured on. Platform-specific
// branches are unreachable, and therefore uncovered, on every other
// platform, so percentages are not comparable across operating systems.
OS string `json:"os,omitempty"`
// Note is free-form context for humans reading the file.
Note string `json:"note,omitempty"`
}
Baseline is the recorded coverage floor for a repository.
func BaselineFrom ¶
BaselineFrom derives a baseline from a report, keeping the report's own exclude patterns for later verification.
func LoadBaseline ¶
LoadBaseline reads a baseline file. It returns ErrNoBaseline if the file is absent, which callers can treat as "record one now".
The path comes from build tooling rather than untrusted input.
type Block ¶
type Block struct {
// File is the import-path-qualified file name, for example
// "github.com/jongio/azd-core/env/load.go".
File string
// Statements is the number of statements in the block.
Statements int
// Count is the number of times the block was executed.
Count int
}
Block is a single counted region from a Go coverage profile.
type CheckError ¶
type CheckError struct {
Regressions []Regression
}
CheckError reports one or more coverage regressions.
func (*CheckError) Error ¶
func (e *CheckError) Error() string
type CheckOptions ¶
type CheckOptions struct {
// Tolerance is the percentage drop allowed before a regression is
// reported. It absorbs rounding noise from non-deterministic test
// selection. Zero means any drop fails.
Tolerance float64
// IgnoreNewPackages disables the "new package below total" rule. By
// default a package absent from the baseline must at least meet the
// baseline total, so new code cannot dilute overall coverage.
IgnoreNewPackages bool
// AllowExcludeDrift permits the report's exclude patterns to differ from
// those recorded in the baseline. By default a mismatch fails, because
// widening exclusions is the easiest way to fake a passing gate.
AllowExcludeDrift bool
// AllowModeDrift permits the profile's counter mode to differ from the
// mode the baseline was recorded with. By default a mismatch fails,
// because percentages are not comparable across modes.
AllowModeDrift bool
// AllowOSDrift permits the check to run on a different GOOS than the
// baseline was recorded on. By default a mismatch fails, because
// platform-specific code is unreachable, and so uncovered, elsewhere.
AllowOSDrift bool
}
CheckOptions tunes ratchet enforcement.
type Config ¶
type Config struct {
// Profile is the coverage profile to read. Defaults to "coverage.out".
Profile string
// BaselineFile is where the recorded floor lives. Defaults to
// DefaultBaselineFile.
BaselineFile string
// Exclude holds glob patterns for code that should not count, such as
// generated sources.
Exclude []string
// Check tunes ratchet enforcement.
Check CheckOptions
// SkipOnForeignOS downgrades a platform mismatch from a failure to a
// visible notice, so a developer on a platform other than the one the
// baseline was recorded on still gets a coverage report instead of a
// hard stop. Enforcement then belongs to CI, which runs on the recording
// platform. Leave it false wherever the gate is authoritative.
SkipOnForeignOS bool
// Out receives human-readable progress. Defaults to os.Stdout.
Out io.Writer
}
Config describes a repository's coverage gate.
type Delta ¶
type Delta struct {
// Scope is "total" or a package import path.
Scope string
// Baseline is the recorded floor.
Baseline float64
// Current is the measured value.
Current float64
// New reports whether the scope is absent from the baseline.
New bool
}
Delta is a change in coverage for one scope, in either direction.
func Improvements ¶
Improvements lists scopes that now exceed their baseline by at least minDelta, so a caller can prompt the user to re-record and lock in the gain.
type Options ¶
type Options struct {
// Exclude holds slash-separated glob patterns matched against each
// block's file path. Matching blocks are dropped before any percentage
// is computed. Use this for generated code, for example "**/gen/**".
Exclude []string
}
Options controls how a coverage profile is aggregated into a Report.
type ProfileData ¶
type ProfileData struct {
// Mode is the value of the profile's "mode:" header, such as "atomic".
Mode string
// Blocks holds every counted region in the profile.
Blocks []Block
}
ProfileData is a parsed coverage profile: the counter mode declared in its header plus the blocks it contains.
The mode matters to the ratchet because "go test -covermode" materially changes the reported percentage. Atomic counters survive concurrent updates that "set" and "count" mode lose, so the same tests can report several points higher under atomic. Comparing a baseline recorded in one mode against a profile measured in another produces meaningless deltas.
func ParseProfile ¶
func ParseProfile(r io.Reader) (ProfileData, error)
ParseProfile reads a Go coverage profile produced by "go test -coverprofile".
The leading "mode:" line is required, matching the format that "go tool cover" itself accepts. Blank lines are ignored.
func ParseProfileFile ¶
func ParseProfileFile(name string) (ProfileData, error)
ParseProfileFile reads and parses a coverage profile from disk.
The path comes from build tooling rather than untrusted input, so it is opened directly.
type Regression ¶
type Regression Delta
Regression is a coverage drop below the recorded floor.
func (Regression) String ¶
func (r Regression) String() string
type Report ¶
type Report struct {
// Total is coverage across every retained block.
Total Stats `json:"total"`
// Packages maps a package import path to its coverage.
Packages map[string]Stats `json:"packages"`
// Mode is the counter mode of the profile this report came from, such as
// "atomic". Aggregate leaves it empty; Profile fills it in from the
// profile header.
Mode string `json:"mode,omitempty"`
// OS is the GOOS the profile was measured on. Aggregate leaves it empty;
// Profile fills it in from the running process, because a profile is only
// ever produced by tests running on the current platform.
OS string `json:"os,omitempty"`
// Excluded is the number of blocks dropped by the Exclude patterns.
Excluded int `json:"-"`
// contains filtered or unexported fields
}
Report is the aggregated coverage of one profile.
func Profile ¶
Profile parses a coverage profile file and aggregates it in one step, carrying the profile's counter mode and the measuring platform onto the report.
func (Report) ExcludePatterns ¶
ExcludePatterns returns the glob patterns used to build this report.
func (Report) PackageNames ¶
PackageNames returns the report's package paths in sorted order.