Skip to content

codecharter coverage

Run your tests with coverage collection and gate on a minimum line-coverage percentage, configured in .codecharter/config.yml and reported as machine-readable JSON.

codecharter coverage [root] [options]

codecharter coverage finds the test projects under root (the current directory by default), runs them with coverage collection, merges the results, and fails the build when line coverage falls below the required minimum. The same run also reports every uncovered region with its source snippet, so you know exactly which lines are missing a test.

It is the second half of the quality gate: analyze answers "is the code well written", coverage answers "is it actually tested".

Prerequisite

Coverage is collected through the XPlat Code Coverage collector, which needs the coverlet.collector package in every test project:

<PackageReference Include="coverlet.collector" Version="6.*" PrivateAssets="all" />

A test project without it still runs its tests but produces no coverage data. The run names each affected project and prints the exact line to add.

Quick start

# Gate the repository at the configured minimum
codecharter coverage

# Gate one folder at 90%
codecharter coverage src --min-coverage 90

# Reuse the results of a test run you already did
codecharter coverage --skip-tests

# Write the report to a file instead of stdout
codecharter coverage --output-file coverage-report.json

The JSON report goes to stdout, diagnostics and a one-line summary go to stderr. That split keeps codecharter coverage > report.json usable in a pipeline without stripping anything out.

Options

Option Default Description
[root] current directory Directory tree searched for test projects.
--min-coverage <percent> from the configuration Minimum required line coverage (0-100). Overrides the configured value for this run. The number is read locale-independently, so 99.5 is correct everywhere and 99,5 is not.
--results-root <dir> <root>/TestResults/coverage Where test and coverage artifacts are written. A relative path is resolved against the current working directory, not against root.
--skip-tests off Do not run the tests; analyse the coverage files already present under the results root.
--output-file <path> stdout Write the JSON report to this file.
--verbose off Also print informational progress lines to stderr.
--no-color off Disable ANSI color codes.

--skip-tests checks that a coverage file is present for every discovered test project before it trusts the result, so a stale or half-finished run cannot pass the gate unnoticed. If the results directory was produced by a different tool and does not carry the per-project layout, the check falls back to comparing counts and says so.

Configuration

The policy lives in .codecharter/config.yml, so it is committed once and applies to everybody:

coverage:
  minimum-percent: 99.5        # gate threshold (0-100), default 100
  snippet-context-lines: 3     # context lines around each uncovered region
  exclude:                     # files excluded from the metric (globs)
    - "**/*.Designer.cs"
    - "src/Generated/**"

All three keys are repository-wide; unlike the analysis sections, coverage cannot be varied per path scope. Globs are matched case-sensitively against paths relative to the run root, written with forward slashes (**/ spans directories, * stays inside one path segment).

Note that coverage.exclude is separate from the top-level exclude used for analysis. Excluding a folder from rule analysis does not silently drop it from your coverage number, and the other way round.

The effective threshold is resolved per key in this order:

  1. --min-coverage on the command line
  2. .codecharter/config.local.yml (your personal, machine-local overlay)
  3. .codecharter/config.yml (the committed team setting)
  4. the built-in default of 100

The report states which of the four won, so a surprising gate result can always be traced back to its source. Note that a coverage.exclude list in the local overlay replaces the committed list rather than extending it.

You can edit all of this from the command line instead of by hand — see codecharter config.

What counts towards the number

Positional records that only carry data have no behaviour worth testing, so they are left out of the metric. A record is treated as pure data as long as none of its members has a hand-written body: auto-properties (including init) and the generated primary constructor keep it pure, while a computed property, a validating constructor, a method body, an operator, an indexer, or an event puts the whole record back into the gate.

Classes and structs are never excluded this way. The exception is deliberately limited to records, because only a record states the "data, not logic" contract outright.

Everything else you exclude is your decision, through coverage.exclude.

Exit codes

Code Meaning
0 Coverage met the required minimum.
1 Coverage below the required minimum.
2 Tests failed, or the coverage data was incomplete.
3 No coverage data at all.
64 Usage, configuration, or environment error (for example a missing .NET SDK or an unwritable --output-file).

The gate is fail-closed: coverage data that is missing, empty, or unreadable never produces a pass. Exit code 3 says "nothing was measured", it never appears as a green 100%.

The displayed percentage is rounded down, so 100.00% means every measurable line is covered and nothing else. A single uncovered line in a large solution shows as 99.99% and fails a 100% gate, instead of rounding up into a false pass.

The report

The JSON report has three parts:

  • summary — measurable lines, covered lines, the percentage, the required minimum, where that minimum came from, whether the gate passed, and the age of the coverage files that were analysed.
  • testResults — one entry per test project with its exit code (empty when you pass --skip-tests).
  • uncoveredRegions — every contiguous block of never-executed lines, with the file (as an absolute path and relative to the run root), the containing method, the line numbers, and a source snippet.

The relative path is the stable key for CI annotations: it does not depend on where the build agent checked the repository out.

In CI

- name: Coverage gate
  run: codecharter coverage --output-file coverage-report.json
  # Exit code 1 turns the step red when coverage drops below the minimum.

Because the threshold lives in the configuration file, the pipeline definition does not need to change when the team raises the bar.

With an AI assistant

The same gate is available to AI coding assistants as the run_coverage tool of the MCP server. The assistant gets the uncovered regions including file, method, line numbers, and snippet, which is everything it needs to write the missing tests without searching for them first.