Download Latest Version v1.3.0 source code.zip (1.2 MB) Google Add to Preferred Sources
Home / v1.2.0
Name Modified Size InfoDownloads / Week
Parent folder
README.md 2026-09-04 36.6 kB
v1.2.0 source code.tar.gz 2026-09-04 821.5 kB
v1.2.0 source code.zip 2026-09-04 1.2 MB
Totals: 3 Items   2.1 MB 0

SimpleCov 1.2 adds change-focused coverage, per-test attribution, production coverage, and a substantially expanded CLI while keeping the existing configuration API working through deprecation aliases.

Highlights

  • track_tests, simplecov tests, and simplecov affected connect covered lines to the tests that exercised them; the HTML report displays the same attribution and simplecov watch uses it for focused reruns.
  • simplecov patch, per-file baselines, missed-count caps, and coverage history make coverage gates useful for both new changes and legacy codebases.
  • SimpleCov::Production records low-overhead production line coverage, while simplecov dead-code and the bundled reports cross it with test coverage.
  • ActionView templates can be measured with cover_views, including ERB, Haml, and Slim source highlighting.
  • The CLI gains annotated source, report freshness, history, badges, shell completions, a man page, and richer machine-readable output.

Upgrade notes

  • The minimum supported Ruby remains 3.2; JRuby 10 remains supported for line coverage.
  • Successful runs now maintain the bounded coverage/.history.json file by default. Set history_limit 0 to disable it.
  • Existing configuration continues to work, but criterion-specific filters and threshold scopes now prefer coverage(:criterion) { ignore ... } and minimum ..., per: .... The legacy forms warn with their exact replacements, and deprecations :raise can enforce a completed migration.
  • coverage.json advances additively to schema 1.3 for test-context, history, baseline-error, and production data. Previously published versioned schemas remain frozen for pinned consumers.

Enhancements

  • The HTML report's source view renders the track_tests recording. A covered line no recorded test executed drains from green to a slate tint, with a "Covered outside tests" legend chip, so coverage produced only by load time, suite setup, or helpers stops passing for tested code at a glance. Every executed line carries a tests badge naming its count, and clicking it opens an inline panel listing the covering tests, the same ids in the same order simplecov tests file:line prints, each one selectable with a click for handing to a runner. Drained lines explain themselves in the same panel instead of listing nothing. The file header's Line coverage row splits its fraction by the same attribution ("Line coverage: 100.00% 21/30 relevant lines covered by tests, 9/30 relevant lines covered outside tests"), the legend's covered chip splits to match (green "Covered by tests" beside the slate "Covered outside tests"), keyboard access rides on real buttons with the report's focus ring, and the panel closes on Escape without closing the source dialog. The file list draws the same distinction: each line coverage bar splits its fill into the share recorded tests produced (in the usual band colour) and a slate share covered only outside them, in the file rows and the live-filtered totals row alike, and sorting by line coverage breaks ties on the by-tests share, so of two files at 100% the one whose coverage rests on its tests ranks above the one warmed by load time. All of it appears only when the report was generated with track_tests enabled, so other reports render exactly as before.
  • simplecov patch reports coverage of just the lines a change touched, the question diff does not answer. Where diff compares two reports and asks whether the overall number moved — which a large or legacy project cannot shift in one pull request — patch reads git diff --unified=0 --merge-base <base>, intersects the added and modified line numbers with the current report, and prints line coverage — and, when the report measured branches or methods, branch and method coverage over the branches and methods those lines carry — for only that change, so a project sitting at 40% can still insist that everything it adds is tested. --minimum N exits non-zero below a floor (every measured criterion must clear it), composing with CI as a per-change gate alongside the existing thresholds; --base selects the ref to diff against (defaulting to the branch origin's HEAD points at, else main; in CI, the target branch or its merge-base); --find-renames follows a moved file instead of counting it as all-new; and --json emits the rows the other read-only commands do. Only files the report already carries are scored, and a line LinesClassifier deems never relevant stays out of the denominator the same way it stays out of a file's total, so a comment-only or whitespace change reads as nothing to cover rather than as a gap. A brand-new file that was never git added appears in no diff yet is still the change's work, so untracked files are scored too, every report-known line of them as new. The diff is anchored at the repository root rather than the working directory, so a run from a subdirectory reports the same change, and changed files resolve against the report by exact path, so a lookalike entry elsewhere in the report can never be scored in a changed file's place. A changed line beyond what the report knows for its file draws a staleness warning instead of silently scoring nothing, a git failure reports git's own words rather than a guess, and a path that matches more than one report entry names its candidates, in coverage and tests too, instead of a bare "no entry". Almost none of this is new machinery: the report already knows which lines are relevant and which were hit, leaving a hunk-header parser and path resolution against the report's root. Prior art is diff-cover, Codecov's patch status, and the undercover gem, which does this for Ruby but needs its own formatter and a git dependency to get there. See [#1262].
  • track_tests recording got a cost model and the levers to control it. Recording now settles at segment boundaries: consecutive tests share their boundary coverage snapshot (one snapshot closes one test's segment and opens the next, attributing any in-between code to the later test), which halves the per-test sampling cost outright, and the project-root check is memoized per file. On top of that, track_tests granularity: :file records one context per test file instead of per test, so the suite pays one snapshot per change of file in run order rather than one per test, and test selection needs no more than file identity anyway. On a real 1,756-test Minitest suite measuring line, branch, and method coverage, the two changes together cut tracking overhead from 52 seconds to 28 at the default granularity and to 13 at :file. The remaining floor is Ruby's own Coverage.peek_result copy, which scales with the criteria the run measures: a millisecond or two under line coverage alone, an order of magnitude more with branch and method tables, which the docs now spell out.
  • simplecov tests answers which tests cover a file or line, from the terminal. Bare, it lists every test recorded under track_tests. With a path it narrows to the tests touching that file, and with path:line to one line. Text output is one test id per line and nothing else on stdout, sorted, so the list can feed a runner directly (simplecov tests lib/foo.rb:42 | xargs bundle exec rspec), with empty answers noted on stderr instead. --json emits a JSON array, and --input picks the report like the other read-only subcommands. It reads the contexts data in coverage.json, so it works on any report generated after track_tests was enabled, and explains what to enable when the recording is missing.
  • simplecov tests --redundant lists the tests whose covered lines other tests also cover, the tests contributing no coverage of their own, computed from the same track_tests recording with no new measurement. The flag composes with the narrowing, so simplecov tests --redundant lib/foo.rb lists the redundant tests among those touching the file. The listed ids are candidates for review rather than a delete list: assertions and mutation-killing power are invisible to coverage, and two tests covering exactly the same lines subsume each other, so both are listed and deleting both would lose the lines. An empty answer over a real recording is good news and says so on stderr.
  • simplecov affected selects the tests that touch changed code. It diffs the working tree against the merge base of a git ref (--base, defaulting to the branch origin's HEAD points at, else main) and HEAD, so uncommitted work counts as part of the change while commits that landed on the base after the branch point do not, includes untracked files, and prints the test files whose recorded tests touch the changed files, so the local inner loop can run simplecov affected --run bundle exec rspec instead of the whole suite. Everything after --run is the runner command, the selection is appended to it, and the exit status is the command's own. The set intersection is the easy half. The hard half is knowing when to distrust the map, because a test map is stale the moment something changes that no test mentions by name, so any changed file outside the tracked set fails open to the full suite, out loud: a changed Gemfile.lock, .simplecov, spec helper, runner configuration, or any file the report has no data for is named on stderr as the trigger, while stdout prints nothing, which a bare runner reads as run everything. Changed or brand-new test files always select themselves, recorded or not, a test file deleted by the change drops out of the answer, and a recorded test whose file no longer exists anywhere else reads as staleness rather than being silently skipped. --json emits the selection with its full_suite verdict and triggers for tooling, and --input picks the report like the other read-only subcommands. Like patch, the diff is anchored at the repository root, so a run from a subdirectory selects over the whole change, with --run starting the runner at that root, and changed files resolve against the report by exact path, so a lookalike entry elsewhere in the report can never stand in for a changed file the report does not carry. Built for the local inner loop first, because a wrong answer in CI is a green build on a broken change. See [#1264]. track_tests in a SimpleCov.start block samples coverage around every RSpec example and Minitest test, and stores the map in .resultset.json under a versioned contexts key beside the merged coverage, exposed as SimpleCov::Result#contexts with a covering(path, line) lookup. The data layer speaks of contexts rather than tests because the mechanism is general and matches the vocabulary a future Coverage library feature would use, while under track_tests every context is one test. Test ids are interned and each test's covered lines are packed into per-file bitmaps, which keeps the naive tests-times-lines storage in hand. Merges union the maps when every merged result recorded one and drop them out loud otherwise, across suites, concurrent runners, parallel workers, and simplecov collate alike, since a partial map would present one worker's tests as the whole run's. The Minitest wrapper installs through the minitest 5 plugin, and under minitest 6, whose autorun no longer discovers plugins, the moment Minitest::Test is defined. Other runners wrap their own units of work with SimpleCov.track_test. Tests running concurrently in threads inside one process cannot be told apart, because coverage counters are process-global, so such a process warns and stores no map rather than a misattributed one, while process-parallel workers each record their own. The serialized map carries a format version, so a future format reads as absent instead of being misread. The map also flows into coverage.json, the durable artifact downstream tools build on, as a document-level contexts array plus per-file hex bitmaps, under coverage.json schema 1.1 (schemas/coverage-v1.1.schema.json, with 1.0 staying frozen for pinned integrations, and the new keys optional so documents without recordings stay minimal). Opt-in because sampling around every test costs run time and the map costs space. Needs regular line coverage, so :oneshot_line is rejected at startup, because a line reports only its first hit ever and every later test's delta would miss it. See [#1263].
  • ActionView templates can be part of the report. cover_views in a SimpleCov.start block turns on eval coverage, which is what measures a template the suite renders, and compiles the templates it never renders so they appear at 0% rather than not at all. Rendering already produced usable data, because ActionView compiles each template with module_eval(source, identifier, offset) where the identifier is the template's own path and the offset cancels the def line its wrapper adds, so the generated code is attributed back to the .erb file at the template's own line numbers and nothing needs remapping. The gap was the other half: a template no test renders is never compiled, so Coverage never hears about it, and the views with no coverage were exactly the ones the report omitted. cover_views defaults to a Rails app's views, takes globs of its own for templates that live elsewhere, and honors skip like any other file in the report. The rails profile gains a matching Views group, which stays empty in a project that has not opted in. Needs CRuby 3.2 or later, which is what eval coverage needs. See [#1265].
  • The source view highlights each file by its own language rather than assuming Ruby. A .erb template used to be handed to the Ruby grammar, which declines to match markup and left the view almost entirely unhighlighted, so templates now go through an ERB grammar that marks up the tags and highlights what is between them as Ruby. It is a small grammar of SimpleCov's own rather than the one highlight.js ships, whose markup is delegated to a full XML grammar that carries a literal `
Source: README.md, updated 2026-09-04