coverlint

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 7 Imported by: 0

README

coverlint

codecov

coverlint enforces a minimum Go test coverage percentage for each selected package.

It can run as either:

  • a standalone command-line tool;
  • a module plugin compiled into a custom golangci-lint binary.

The default policy requires 80% coverage per package. Coverage profiles are created in the system temporary directory, loaded into memory, and removed automatically.

coverlint

Requirements

  • Go 1.26.5 or newer;
  • a Go module containing the packages to check;
  • golangci-lint only when using the plugin integration.

Quick start

Standalone CLI

Build the command from this repository:

go build -o ./bin/coverlint ./cmd/coverlint

Check every package under the current module with the default 80% minimum:

./bin/coverlint

Require 85% instead:

./bin/coverlint -min 85
golangci-lint plugin

Build the custom binary described by .custom-gcl.yml:

golangci-lint custom -v

Enable coverlint in .golangci.yml:

version: "2"

linters:
  enable:
    - coverlint
  settings:
    custom:
      coverlint:
        type: module

Run the generated binary, not the standard golangci-lint executable:

./custom-golangci-lint run ./...

With no settings block, the plugin checks ./... and requires 80% coverage for every package.

Package selection and policy matching

coverlint uses two different kinds of patterns. They serve different purposes and are not interchangeable.

Pattern type Examples Purpose
Go package pattern ./..., ./internal/... Selects packages passed to go test and go list.
Import-path glob **/internal/**, **/generated/** Applies an override or exclusion to selected package import paths.

For example:

coverlint \
  -min 75 \
  -override '**/internal/**=85' \
  -exclude '**/generated/**' \
  ./...

Here, ./... selects packages. The quoted glob patterns then match complete import paths such as github.com/acme/project/internal/orders.

Coverage policy

Default minimum

The default minimum is 80%. Set another value with -min or the plugin min setting.

CLI:

coverlint -min 85

Plugin:

settings:
  min: 85

Minimum values must be finite, greater than 0, and at most 100. Decimal values are supported:

coverlint -min 87.5

A package passes when its calculated coverage is equal to or greater than its required minimum.

Package overrides

Overrides assign a different minimum to matching package import paths.

CLI:

coverlint \
  -min 75 \
  -override '**/internal/critical/**=95' \
  -override '**/internal/**=85'

Plugin:

settings:
  min: 75
  overrides:
    - pattern: '**/internal/critical/**'
      min: 95
    - pattern: '**/internal/**'
      min: 85

Overrides are evaluated from top to bottom. The first matching override wins. The default min value is used when no override matches.

Order specific patterns before broad patterns:

overrides:
  - pattern: '**/internal/critical/**'
    min: 95
  - pattern: '**/internal/**'
    min: 85

Reversing those entries would cause the broader **/internal/** rule to match first.

Exclusions

Exclusions skip matching package import paths entirely.

CLI:

coverlint \
  -exclude '**/generated/**' \
  -exclude '**/mocks/**'

Plugin:

settings:
  exclude:
    - '**/generated/**'
    - '**/mocks/**'

Exclusions are evaluated before overrides and the default minimum. Excluded packages count as skipped, not passed or failed.

Glob pattern reference

Policy globs are matched against the entire package import path using / as the separator on every operating system.

Given this import path:

github.com/acme/project/internal/orders

Supported syntax:

Syntax Meaning Example
* Zero or more characters within one path segment. It never crosses /. github.com/acme/*/orders
? Exactly one character within one path segment. **/mock?
[abc] One character from a class. Ranges and negated classes follow Go path.Match syntax. **/[io]rders
** Zero or more complete path segments. It must be a complete segment. **/internal/**

Common patterns:

Glob Matches
** Every package import path.
**/internal/** Any package named internal and all packages below it.
**/generated/** Any package named generated and all packages below it.
github.com/acme/*/api An api package with exactly one segment between acme and api.
github.com/acme/project Only that exact import path.
github.com/acme/project/** The module root and every package below it.

Important rules:

  • matching is anchored to the complete import path;
  • * does not cross a / separator;
  • ** may cross any number of segments, including zero;
  • ** must appear by itself between separators;
  • empty path segments are invalid;
  • use /, not operating-system file separators;
  • quote globs in shell commands to prevent the shell from expanding them before coverlint receives them.

Valid:

**/internal/**
github.com/acme/*/api
**/mock?

Invalid:

/internal/**
internal//**
internal/**suffix
internal/[

These are glob patterns, not regular expressions. For example, use **/internal/**, not ^.*/internal/.*$.

CLI reference

Syntax
coverlint [flags] [package-pattern ...]

When no package patterns are provided, ./... is used.

The CLI uses Go's standard flag package, so every flag must appear before the first package pattern.

Correct:

coverlint -min 85 ./internal/...

Incorrect:

coverlint ./internal/... -min 85
Flags
Flag Default Description
-min PERCENT 80 Minimum coverage for selected packages without a matching override.
-override GLOB=MIN none Package-specific minimum. Repeatable; first matching override wins.
-exclude GLOB none Skip matching package import paths. Repeatable.
-test-arg ARG none Pass an additional argument to go test. Repeatable.
-timeout DURATION 10m Maximum duration for coverage collection and package discovery.
-web false Open Go's standard HTML coverage report after evaluation.
-version — Print the version and exit.
-h — Print command help and exit.
-min

Pass the percentage as a number without a % suffix:

coverlint -min 90
coverlint -min 87.5
-override

The format is GLOB=MIN:

coverlint -override '**/payments/**=95'

A % suffix is accepted for an override value:

coverlint -override '**/payments/**=95%'

Repeat the flag for multiple overrides:

coverlint \
  -override '**/critical/**=95' \
  -override '**/internal/**=85'

Because the first match wins, place the most specific override first.

-exclude
coverlint -exclude '**/generated/**'

Repeat the flag for multiple exclusions:

coverlint \
  -exclude '**/generated/**' \
  -exclude '**/mocks/**'
-test-arg

Use one flag for each additional argument passed to go test:

coverlint \
  -test-arg=-race \
  -test-arg=-tags=integration

Use the -test-arg=value form when the value starts with -.

Common examples:

coverlint -test-arg=-short
coverlint -test-arg=-run=TestUnit
coverlint -test-arg=-tags=integration
coverlint -test-arg=-race -timeout=20m

The following arguments are managed by coverlint and are rejected:

  • -coverprofile;
  • --coverprofile;
  • -covermode;
  • --covermode;
  • -count;
  • --count.

-args and -- are also rejected because they would stop Go from seeing the package patterns appended by coverlint.

coverlint always runs tests with -count=1, -covermode=atomic, and its own temporary -coverprofile path.

-timeout

The value uses Go duration syntax:

coverlint -timeout 30s
coverlint -timeout 20m
coverlint -timeout 1h

The timeout covers both go test and go list. It must be greater than zero.

-web

Open Go's standard HTML coverage report:

coverlint -web

After evaluating the policy, coverlint runs the equivalent of:

go tool cover -html=<temporary-profile>

Use this option for local development. It may not work in a headless CI environment.

-version
coverlint -version

CLI examples

Default policy
coverlint

Checks ./... with an 80% minimum.

Selected packages
coverlint -min 85 ./internal/orders/... ./internal/payments/...
Layered package thresholds
coverlint \
  -min 75 \
  -override '**/internal/critical/**=95' \
  -override '**/internal/**=85' \
  ./...
Exclude generated packages
coverlint \
  -min 85 \
  -exclude '**/generated/**' \
  -exclude '**/mocks/**' \
  ./...
Integration tests
coverlint \
  -min 80 \
  -test-arg=-tags=integration \
  -timeout=20m
Race-enabled tests
coverlint \
  -min 80 \
  -test-arg=-race \
  -timeout=20m
Local HTML report
coverlint -min 80 -web
CI script
set -euo pipefail

go build -o ./bin/coverlint ./cmd/coverlint
./bin/coverlint -min 85 ./...

CLI output and exit codes

Coverage violations are written to standard output. Summaries and execution errors are written to standard error.

Example violation:

internal/payments/service.go:1:1: coverage 72.40% is below 85.00% for package "github.com/acme/project/internal/payments" (181/250 statements) (coverlint)

Example success summary:

coverlint: passed (12 checked, 2 skipped)

Example failure summary:

coverlint: failed with 2 issue(s) (12 checked, 2 skipped)
Exit code Meaning
0 Every checked package met its required minimum.
1 At least one package was below its minimum or had no coverage data.
2 Configuration was invalid, tests failed, the timeout expired, or a required command could not run.

golangci-lint module plugin

The plugin is registered under the name coverlint. It must be compiled into a custom golangci-lint binary.

Repository module path

This example repository currently uses the placeholder module path:

github.com/gostafa/coverlint

Replace it with the real repository path before publishing. Then update imports and run:

go mod tidy

Commit the resulting go.sum file.

Local custom build

The included .custom-gcl.yml loads the plugin from the current directory:

version: v2.12.2
name: custom-golangci-lint
plugins:
  - module: github.com/gostafa/coverlint
    path: .

Build the custom binary:

golangci-lint custom -v

Run the plugin using that generated binary:

./custom-golangci-lint run -c .golangci.example.yml ./...

The standard golangci-lint binary does not contain the plugin.

Published plugin build

After publishing the module, reference a released version in .custom-gcl.yml:

version: v2.12.2
name: custom-golangci-lint
plugins:
  - module: github.com/your-org/coverlint
    version: v0.5.0

The plugin is registered from the module root, so an import field is not required.

Minimal linter configuration
version: "2"

linters:
  enable:
    - coverlint
  settings:
    custom:
      coverlint:
        type: module

Defaults:

  • minimum: 80;
  • packages: ./...;
  • timeout: 10m;
  • coverage mode: atomic;
  • no overrides;
  • no exclusions;
  • no additional test arguments.
Complete linter configuration
version: "2"

linters:
  enable:
    - coverlint
  settings:
    custom:
      coverlint:
        type: module
        description: Enforce minimum Go test coverage.
        original-url: github.com/your-org/coverlint
        settings:
          min: 75
          overrides:
            - pattern: '**/internal/critical/**'
              min: 95
            - pattern: '**/internal/**'
              min: 85
          exclude:
            - '**/generated/**'
            - '**/mocks/**'
          packages:
            - ./...
          timeout: 20m
          test-args:
            - -race
            - -tags=integration

Plugin settings reference

Every setting is optional.

Setting Type Default Description
min number 80 Minimum coverage for packages without a matching override.
overrides list of objects empty Ordered package-specific coverage rules. First match wins.
exclude list of strings empty Import-path globs for packages to skip.
packages list of strings ./... Go package patterns passed to go test and go list.
timeout duration string 10m Maximum duration for the complete check.
test-args list of strings empty Additional arguments passed to go test.
min
settings:
  min: 87.5

The value must be finite, greater than 0, and at most 100.

overrides

Each override contains:

Field Type Required Description
pattern string yes Glob matched against the complete package import path.
min number yes Required coverage percentage for the first matching package.
settings:
  min: 75
  overrides:
    - pattern: '**/critical/**'
      min: 95
    - pattern: '**/internal/**'
      min: 85
exclude
settings:
  exclude:
    - '**/generated/**'
    - '**/mocks/**'
packages

These are Go package patterns, not policy globs:

settings:
  packages:
    - ./internal/...
    - ./pkg/...

When the standalone Go API supplies package patterns directly, those patterns take precedence over the configured packages list. For normal plugin use, configure the list under settings.packages.

timeout
settings:
  timeout: 20m

The value uses Go duration syntax and must be greater than zero.

test-args
settings:
  test-args:
    - -race
    - -tags=integration

The managed -coverprofile, --coverprofile, -covermode, --covermode, -count, and --count flags are rejected. -args and -- are rejected too.

How coverage is calculated

For each run, coverlint:

  1. resolves configuration and validates every glob;
  2. runs go test -count=1 -covermode=atomic with a temporary coverage profile;
  3. runs go list -json for the same package patterns and forwards build-context test args such as -tags, -modfile, -overlay, -race, -gcflags, -asmflags, and -ldflags;
  4. maps coverage blocks to package import paths;
  5. merges duplicate profile blocks by file and source range, then totals covered and coverable statements for each package;
  6. applies exclusions, then the first matching override, then the default minimum;
  7. reports one diagnostic for each package that violates its policy;
  8. removes temporary coverage files.

Coverage is enforced per package, not as one combined repository-wide percentage.

Only production Go and Cgo source files are used to map packages. Test-only packages, packages whose profile contains no coverable statements, and selected packages with no coverage profile blocks are skipped.

Test failures stop the check before policy evaluation.

Troubleshooting

A glob does not match

Check that the pattern targets the complete import path, not a file-system path or a Go package selector.

Use:

**/internal/**

Not:

./internal/...

The first is a policy glob. The second is a Go package pattern used for package selection.

The shell changes a glob

Quote every CLI glob:

coverlint -exclude '**/generated/**'

Without quotes, some shells may expand *, ?, or character classes before starting coverlint.

An override is ignored

Overrides use first-match precedence. Move the more specific pattern above the broader pattern.

A flag is treated as a package

Place all flags before package patterns:

coverlint -min 85 ./...
Tests fail before coverage is checked

Run the equivalent test command directly and fix the test or build error:

go test -count=1 -covermode=atomic ./...

Include the same values supplied with -test-arg or settings.test-args.

The HTML report does not open

-web depends on go tool cover being able to launch a browser. Avoid it in headless CI environments.

The plugin is not found

Confirm that:

  • the plugin module path in .custom-gcl.yml is correct;
  • golangci-lint custom -v completed successfully;
  • the generated ./custom-golangci-lint binary is the one being executed;
  • coverlint is enabled under linters.enable;
  • the custom linter entry uses type: module.

Development

Build the CLI:

go build -o ./bin/coverlint ./cmd/coverlint

Run static checks:

go vet ./...

Run tests for the CLI and internal packages:

go test ./...

Build and run the custom linter:

make custom-lint

Remove generated binaries:

make clean

License

See LICENSE.

Documentation

Overview

Package coverlint registers the golangci-lint coverlint plugin.

Directories

Path Synopsis
cmd
coverlint command
Command coverlint runs package-level Go coverage policy checks.
Command coverlint runs package-level Go coverage policy checks.
internal
features/coverage
Package coverage exposes the coverlint coverage feature.
Package coverage exposes the coverlint coverage feature.
features/coverage/adapters/gotool
Package gotool adapts the Go command-line toolchain for coverage checks.
Package gotool adapts the Go command-line toolchain for coverage checks.
features/coverage/adapters/text
Package text formats coverage diagnostics.
Package text formats coverage diagnostics.
features/coverage/application
Package application orchestrates coverage collection and policy evaluation.
Package application orchestrates coverage collection and policy evaluation.
features/coverage/config
Package config resolves coverage feature settings into executable requests.
Package config resolves coverage feature settings into executable requests.
features/coverage/domain
Package domain contains coverage policy types and evaluation logic.
Package domain contains coverage policy types and evaluation logic.
features/coverage/ports
Package ports defines coverage feature boundaries.
Package ports defines coverage feature boundaries.

Jump to

Keyboard shortcuts

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