Comparing Coverage Across Commits

This is how Codecov determines the code coverage reports to compare and display the amount of coverage change from commit to commit on the Git tree.

Choosing a parent commit

Codecov must choose a parent commit when comparing coverage reports. Having accurate coverage data is crucial. Therefore, by default, Codecov only chooses commits that have successful CI builds.

To illustrate this point, we can review the following commits:

2158

Note: A is the oldest commit and D is the newest.

Order of events

  • Commit A was uploaded. The CI passed for this commit. Coverage did not change.
  • Commit B was uploaded. The CI failed.
  • Commit C was uploaded. The CI failed.
  • Commit D was uploaded. The CI passed and coverage increased.

Codecov will compare A against D because commits B and C failed in CI, and likely have invalid or incomplete coverage data. This results in a +2% change in coverage.

Commits with Failed CI

When coverage runs on your build, it is essential that Codecov recognizes that CI has passed or failed. If CI fails, one or more of the following assumptions can be made:

  1. Not all tests were executed; therefore coverage is incomplete.
  2. Exceptions may call new execution paths, resulting in different coverage metrics.
  3. A failed test could produce different coverage than the same test ran successfully.

Rebase vs Merge Target

There are two techniques to updating pull requests: rebasing and merging target.

Both techniques are respected by Codecov. The pull request base will be updated from a to b, and Codecov will use reports from b when comparing against pull request head (c).

Rebasing

# git diagram - before
```
master a . . b
pull    \ . . c
```

# rebase action
git rebase master

# git diagram - after
```
master a . . b
pull          \ . . c
```

Merging Target

# git diagram - before
```
master a . . b
pull    \ . . c
```

# merging target action
git merge master

# git diagram - after
```
master a . . b
               \
pull    \ . . . mc c
```

Comparing pull request base reports

Pseudo-Comparison

When Codecov generates a comparison for a pull-request, but the pull-request’s base in git does not have coverage information, Codecov will try to find an appropriate substitute (a “pseudo-base”). This is referred to as pseudo-comparison. Pseudo-comparison can be turned off via the Codecov YAML option:

codecov:
  allow_pseudo_compare: False # the default is true

When pseudo-comparison is disallowed and the base commit of a PR has no coverage data, Codecov will render an error in the UI.

Coverage Offsets
Sometimes the pseudo-base chosen by Codecov will not be recent enough to directly substitute for the base commit of a pull-request. In this case, Codecov will try to use the diff between the pseudo-base and the true base to adjust the coverage information of the pseudo-base and account for recent changes. This feature of applying “coverage offsets” to the pseudo-base’s coverage report can also be configured by the Codecov YAML:

codecov:
  allow_coverage_offsets: True # the default is false

Suppose Codecov is configured to pseudo-compare when coverage is missing for a base commit but not to apply coverage offsets when the pseudo-base is too old for direct substitution for the base commit. In that case, Codecov will render an error in the UI, similar to Changes found in between 243277d...143e200 (pseudo...base) which prevent comparing this pull request

Pseudo-Comparison Example

The base commit of the pull request did not upload coverage resulting in Codecov's inability to compare reports.

master . pseudo . . base
pull                  \ . . head
  • pseudo did upload coverage
  • base did not upload coverage
  • head did upload coverage

Examples of the use cases:

  • Base commit skipped CI via [ci skip], use the parent commit as the pseudo commit below.
  • Base commit was a merge commit Merge abc into xyz, use xyz as the pseudo commit below.

Codecov will seek a parent commit and offset the report to get an approximate base report.

# first get the git diff of pseudo...base
pseudo_diff_base = diff(pseudo...base)

# check if you allow offsets (default False)
if yaml["codecov.allow_coverage_offsets"] is False
  if pseudo_diff_base.adjusts_tracked_lines(pseudo.report)
    exit "Missing report base"

# adjust the report with changes in pseudo_diff_base
approx_base_report = pseudo.report.adjust_forward(pseudo_diff_base)

# get the pull diff
pull_diff = diff(base...head)

use compare(approx_base_report...head.report) in pull comment/statuses/etc.