View Source Bond.Coverage (Bond v1.16.0)
Contract coverage: a test-time diagnostic that surfaces assertions which are checked but never observed to fail (#56).
The hardest contract bug to catch is the vacuous-but-passing assertion — one that runs at every call, always evaluates true, and so tells you nothing while looking like coverage. The compile-time assertion linter catches the structurally recognisable cases; this is the complementary dynamic signal: an assertion evaluated many times that has never once been false is a candidate for vacuity.
"Never failed" is a weak signal
A correct assertion over correct code, exercised by a passing suite, also never fails — that is what a green suite means. So
⚠ never failedis a prompt, not a verdict: either write a test that makes the assertion fail (proving it can — see the Writing Sound Assertions guide), or delete it. Coverage is an opt-in diagnostic you run deliberately, never an always-on warning.
Enabling
Coverage instrumentation is compile-time opt-in, so a build that does not enable it is byte-for-byte unchanged and pays nothing. Turn it on for the test environment:
# config/test.exs
config :bond, coverage: trueand print the report at the end of the suite by installing the reporter in test/test_helper.exs:
ExUnit.start()
Bond.Coverage.install_reporter()Then mix test prints a per-assertion table of how many times each contract was checked and how
many of those were failures. entries/0 and report/0 can also be read directly at any point.
Install the reporter even if you read
entries/0yourselfCoverage accumulates in an ETS table, and an ETS table dies with the process that created it.
install_reporter/0creates the table up front fromtest/test_helper.exs, whose process outlives the suite. Without that call the table is created lazily by whichever test process evaluates the first contract, and every recorded evaluation is discarded when that test finishes.
Summary
Types
One assertion's accumulated coverage: its identifying metadata plus how many times it was
checked and how many of those checks failed.
Functions
Installs an ExUnit.after_suite/1 callback that prints report/0 once the suite finishes. Call
this in test/test_helper.exs after ExUnit.start().
Records a single evaluation of the assertion described by assertion_info (the map the runtime
check path already carries), classifying it as a failure when result is falsy.
Renders the accumulated coverage as a human-readable table, grouped by module and function.
Assertions that were checked at least once but never failed are flagged ⚠ never failed.
Clears all recorded coverage.
Types
@type entry() :: %{ assertion_id: term(), kind: atom(), label: term(), expression: String.t() | nil, module: module() | nil, function: term(), file: String.t() | nil, line: non_neg_integer() | nil, checked: non_neg_integer(), failed: non_neg_integer() }
One assertion's accumulated coverage: its identifying metadata plus how many times it was
checked and how many of those checks failed.
Functions
@spec entries() :: [entry()]
Returns the accumulated coverage as a list of entry/0, sorted by module, then line.
@spec install_reporter() :: :ok
Installs an ExUnit.after_suite/1 callback that prints report/0 once the suite finishes. Call
this in test/test_helper.exs after ExUnit.start().
Also creates the coverage table, so that it is owned by the process running
test/test_helper.exs rather than by whichever test process happens to evaluate the first
contract. An ETS table dies with its owner, so a table created by a test process takes every
recorded evaluation with it when that test finishes — long before this reporter runs.
Records a single evaluation of the assertion described by assertion_info (the map the runtime
check path already carries), classifying it as a failure when result is falsy.
Called from the coverage-recording variants of the runtime check helpers, which the compiler
emits only when coverage is enabled. Cheap and concurrency-safe: one :ets.update_counter.
@spec report() :: String.t()
Renders the accumulated coverage as a human-readable table, grouped by module and function.
Assertions that were checked at least once but never failed are flagged ⚠ never failed.
@spec reset() :: :ok
Clears all recorded coverage.