Skip to content

Update SimpleCov packages - #123

Merged
AlexWayfer merged 2 commits into
mainfrom
renovate/simplecov-packages
Aug 16, 2026
Merged

Update SimpleCov packages#123
AlexWayfer merged 2 commits into
mainfrom
renovate/simplecov-packages

Conversation

@renovate

@renovate renovate Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
simplecov (changelog) '~> 0.22.0''~> 1.1.0' age confidence
simplecov-cobertura '~> 3.0''~> 4.0' age confidence

Release Notes

simplecov-ruby/simplecov (simplecov)

v1.1.1

Compare Source

What's Changed

Full Changelog: simplecov-ruby/simplecov@v1.1.0...v1.1.1

v1.1.0

Compare Source

==================

Breaking Changes

  • simplecov report --json now emits {"total": {...}, "groups": {...}} instead of flattening the overall "All Files" entry and configured groups into one object. The old shape silently overwrote the overall totals when a user group was also named All Files; the text report now labels that user section All Files (group) as well.
  • Ungrouped is now reserved for the implicit group of files that match no configured group. Defining an explicit group with that name previously caused SimpleCov to overwrite it during result processing and silently discard its matched files; rename such a group to Other or another distinct label. Group names are also normalized when configured: a Symbol name (group :Models) now means the same group as its String spelling (so group :Ungrouped is rejected like the string form, and a symbol-named group can no longer produce a duplicate JSON key next to a string-named one), and a name that is neither a String nor a Symbol raises SimpleCov::ConfigurationError.
  • The HTML report is now a single self-contained index.html. The viewer's JavaScript and CSS are inlined into the compiled template at build time, and the coverage data is embedded at report time (with < escaped in the payload so embedded source text cannot terminate the surrounding <script> element), so coverage/ contains just index.html and coverage.json. A single file can be mailed, uploaded as a non-zipped GitHub Actions run artifact (actions/upload-artifact with archive: false, viewable directly from the run page), or copied anywhere without sibling files, and the report can no longer be read mid-write in a torn state where index.html, coverage_data.js, and application.js come from different runs — the whole report updates in one atomic rename. The sibling files the formatter previously wrote (coverage_data.js, application.js, application.css, and the three favicon PNGs) are gone; anything scripted against that layout should read coverage.json (the sanctioned data artifact, unchanged) instead of coverage_data.js. Formatting also deletes those six names from the output directory when an earlier version left them there, so an upgraded project's coverage/ doesn't keep a stale coverage_data.js around for simplecov serve to serve. This restores single-file reports to the 1.0 line — the pre-1.0 simplecov-html formatter offered them via the SIMPLECOV_INLINE_ASSETS environment variable, which the 1.0 client-side rendering rewrite dropped — and makes them the default and only mode, with no environment variable or configuration flag. See #​1241.

Enhancements

  • The HTML report gains a colorblind-friendly mode. A Colorblind toggle next to the Dark toggle swaps the covered/missed pairing (and the coverage bands) for blue versus orange, the standard colorblind-safe pairing, in both themes; the choice persists in localStorage and is applied before first paint. Both toggles report state via aria-pressed.
  • simplecov serve now handles each connection on its own thread with a read timeout, so a stalled connection (browsers routinely open speculative sockets that send no bytes) no longer blocks every other request. It also works on JRuby and TruffleRuby, answers malformed request lines with a 400 instead of an empty response, and prints a bracketed URL for IPv6 hosts.
  • The README was trimmed from 1,617 lines to under 180, with the full documentation moved into topic guides under a new docs/ directory (Configuration, Parallelism, Formatters, CLI, Troubleshooting) alongside the changelogs, contributing guide, and code of conduct, with the issue template tucked into .github/. This changelog now lives at docs/Changelog.md and the gem's changelog_uri metadata follows it. Nothing under docs/ ships in the gem, which also stops packaging the old doc/* link lists. The alternate formatters catalog was rebuilt against RubyGems: twenty formatters join the twelve that were listed, organized by output type.
  • .resultset.json is now written as compact JSON instead of pretty-printed. It is a machine-read cache that every parallel worker rewrites wholesale, and pretty printing nearly doubled the bytes written, read back, and parsed on each store-merge round trip — on a 100,000-file project the file shrinks from 89MB to 51MB and serialization halves. Any JSON parser reads the compact form; pipe it through jq if you need to inspect it by eye.
  • The favicon (a solid square in the overall coverage band's colour) is now drawn by the viewer from the report's own palette instead of shipping as fixed PNGs, so it matches the report's green/yellow/red exactly and follows the light/dark theme, including the in-page toggle.
  • SimpleCov.collate takes a new processes: argument that fans the resultset merge out across that many forked worker processes. This addresses the wall clock of a large CI matrix's collate step, where the collating process reads, parses and folds hundreds of resultsets in sequence and nearly all the time goes into that fold: merging 160 resultsets covering 1,836 files on a 14-core machine took 4.53s at the default processes: 1 and 1.35s across 8 workers. The report is identical either way, not merely equivalent — each worker folds a contiguous slice of the file list and the collating process folds the slices back in order, so the resultsets are visited in the same order a single-process merge visits them. The fan-out lives in a new SimpleCov::ParallelResultMerger, whose absorb_results mirrors ResultMerger.absorb_results, splitting that fold across workers and unioning the tracked paths each one saw. processes defaults to the SIMPLECOV_CONCURRENCY environment variable (1 when unset), so one rake task can serve CI runners of different sizes without being edited, and an explicit argument wins over the variable. It never forks at 1, so existing collate calls are unaffected; it is deliberately not clamped to the core count nor gated on a minimum number of resultsets — only the caller knows what a collate job is allowed to use — asking for more processes than there are result files just gives one file per process, and anything below 1 is taken as 1. Merging falls back to the collating process, with the same report and no error, when the runtime cannot fork (JRuby, TruffleRuby, Windows), when there is only one resultset, or when a worker dies. A benchmarks/collate.rb harness (PROCESSES=N) measures the phases against a saved baseline.

Bugfixes

  • Two concurrent runners sharing a command name no longer lose the later writer's coverage for files both carried. A live result serializes its criterion tables under Ruby's Symbol keys while entries parsed back from .resultset.json carry Strings, and the combiners read only Strings, so the merge that exists to prevent an empty parent process from clobbering a subprocess's data (#​581) silently contributed nothing from the incoming side. Criterion keys are now stringified at serialization time so the stored and live shapes always match.

  • Merging or collating stored resultsets with method coverage enabled no longer crashes on singleton methods defined on instances. def obj.greet records its receiver as the nested inspect form #<Class:#<Object:0x...>>, and the parser that turns JSON-stringified method keys back into tuples stopped at the first closing angle bracket, raising ArgumentError out of the merge. The quoting now handles nested segments.

  • That same clobber-prevention backstop now stands down for failed child runs too. It keyed its freshness check on .last_run.json, which only fully successful runs write, so a Rakefile parent overwrote the child's report exactly when the child's tests or coverage checks had failed and the report mattered most. Formatting now touches a coverage/.report_stamp marker no matter how the run ends, and the backstop accepts either file as evidence of a fresher report.

  • A # simplecov:disable line block around a method no longer silently removes that method from method-coverage totals. The method skip fell back to asking whether all of the method's lines were skipped, so the line-only directive (the README's own example) leaked into the method criterion. Deprecated # :nocov: chunks still exclude methods, now routed explicitly like every other criterion.

  • A directive reason that merely starts with a category name no longer narrows the directive. # simplecov:disable linear algebra reasons parsed as category line with the rest as reason, disabling only line coverage where the documented behavior for unrecognised text is to over-disable everything. The category list now requires a word boundary.

  • Per-group minimums configured with a Symbol group name are enforced again. group :Models normalizes the name to a String but minimum_per_group 95, only: :Models (and the deprecated minimum_coverage_by_group) stored the Symbol untouched, so the check-time lookup missed and warned that group "Models" doesn't exist while listing that very name as available.

  • simplecov diff matches its documentation: --threshold N is inclusive (a file that moved exactly N% is listed), removed files no longer trip --fail-on-drop (deleting a covered file is not a regression), and sub-epsilon float noise no longer fails the gate on a row shown only for its gains.

  • Tracked-but-unloaded files with multi-statement parenthesized conditions no longer synthesize phantom branches. CRuby folds if (1; 2) by its last expression when the compiler can eliminate every leading statement, and the rules differ per version (parse.y eliminates only pure literals, 3.3 also eliminates side-effect-free reads and containers of them, 3.4+ narrows containers to fully static literals). The static extractor now mirrors each compiler exactly, verified against real Coverage output on every supported Ruby.

  • require "simplecov" no longer raises when HOME is set but empty, as some container and CI images do. The global-config loader treats an empty HOME like an unset one.

  • simplecov merge, report, and coverage print one-line errors instead of backtraces on more bad inputs: a directory or unreadable file passed to merge, valid JSON whose total or groups has the wrong type, and a per-file entry that is not an object.

  • An empty or non-numeric PARALLEL_TEST_GROUPS no longer makes the reporting worker expect zero siblings and skip the wait for their results; unusable values now mean one worker, and non-positive values are rejected too.

  • Simulating tracked files became tolerant of unreadable paths: a track_files glob that sweeps up a directory named like a Ruby file or a permission-denied entry now treats it as empty instead of crashing the merge or report step. Resultset files truncated to a single byte now warn like other corruption instead of reading as quietly empty, a hand-edited .last_run.json with a non-numeric percentage no longer raises out of the at_exit hook, and the missing-group notice respects print_errors and survives -W0 like every other enforcement message.

  • coverage :eval, minimum: 100 now explains that thresholds are unsupported for :eval instead of claiming the criterion itself is invalid, and simplecov clean --dry-run counts dotfiles such as .resultset.json in its entry count.

  • Source files containing invalid UTF-8 bytes no longer crash report generation. A file with no encoding magic comment is read as UTF-8, and a stray high-bit byte (a Latin-1 comment, say) previously raised ArgumentError: invalid byte sequence in UTF-8 from the first regex that touched the line — the shebang check or the lines classifier — taking the whole report down. Invalid bytes are now replaced with the Unicode replacement character at load time, so every line leaves the source loader as valid UTF-8 and the rest of the pipeline (classification, JSON embedding, the HTML viewer payload) works from sanitized text.

  • Generated coverage artifacts now share one collision-safe atomic writer. Concurrent threads no longer reuse the same process-ID temporary name, and the JSON formatter and simplecov merge no longer expose partially written documents to readers; existing Unix permission bits and each artifact's historical byte format are preserved.

  • Configuration blocks no longer install temporary method_missing hooks on their caller or copy caller instance variables into SimpleCov. Those hooks leaked DSL commands across threads, broke overlapping and nested evaluations, rejected frozen or immediate-value owners, changed require_relative and binding behavior, and could mask an original exception during cleanup. See the parameterized-block migration under Breaking Changes.

  • Non-final parallel workers now stop after storing their own result instead of reading and caching a partial merge. A single ownership predicate selects the adapter's final process for merging, formatting, threshold checks, and .last_run.json; explicit SimpleCov.collate remains authoritative regardless of worker identity.

  • simplecov serve now builds a missing index.html from coverage.json and fails before binding when neither artifact exists or the JSON is invalid. An existing self-contained report remains usable even if its optional sidecar JSON was later removed or damaged.

  • Coverage JSON consumers now reject malformed syntax, invalid UTF-8, and non-object roots through one shared parser. HTMLFormatter#format_from_json also validates the viewer's required metadata, coverage flags, enabled totals, groups, and source arrays before creating or replacing its output.

  • HTML reports now render correctly when line coverage is disabled. Branch-only and method-only runs use their configured primary criterion for tabs, color bands, sorting, tables, filters, and source summaries instead of crashing while dereferencing absent line statistics.

  • HTML reports now disambiguate source files whose truncated SHA-1 identifiers collide. Existing fragments stay unchanged for non-colliding files, while colliding links receive deterministic suffixes and always open the intended source.

  • SimpleFormatter now prints each file's configured primary coverage percentage instead of always printing line coverage. Branch-, method-, and oneshot-primary reports now match the documented primary-criterion behavior; oneshot coverage correctly reads the normalized line statistics.

  • Frontend builds now use the esbuild binary installed from html_frontend/bun.lock instead of whichever global version happens to be on PATH. CI recompiles the self-contained HTML template and fails on a diff, preventing dependency updates or source changes from leaving the checked-in report asset stale.

  • Frontend asset compilation now fails when esbuild rejects the CSS. The rake helper previously ignored the minifier subprocess's exit status and continued with empty output, allowing a successful build to replace the checked-in report template with a stylesheet-free page.

  • Read-only CLI commands now handle unreadable, malformed, and structurally unusable coverage.json inputs consistently. coverage, report, uncovered, and both inputs to diff return status 1 with one command-specific diagnostic instead of raising a JSON parser backtrace; uncovered no longer mislabels its input errors as simplecov report.

  • Sorting one HTML report group no longer corrupts the next group's first sort. Every table previously shared the same fallback sort-state key because the tables have no ids, so clicking a column already selected in another group reversed unsorted rows while displaying an ascending indicator; sort state is now scoped to each table element.

  • The HTML report now gives the overall file list and configured groups distinct typed identities, so a user-defined group named All Files no longer shares the overall section's DOM id and tab target. Both identically labelled tabs now remain present and open their own file lists.

  • HTML group tabs now remain distinct when one group name contains punctuation and another contains that character's hexadecimal escape spelling (for example, By/group and By_2f_group). Literal underscores are now escaped because underscores delimit encoded characters; previously both names produced the same DOM id and one tab opened the wrong file list.

  • Enabling ordinary line coverage after oneshot-line coverage no longer passes both incompatible modes to Ruby's Coverage.start, which raised RuntimeError: cannot enable lines and oneshot_lines simultaneously. The two modes now replace each other in either direction, with the last request winning, and replacing the active primary criterion resets it to an enabled default.

  • Branch and method tuples are no longer synthesized for code the compiler eliminates. 1.0.2 stopped synthesizing a branch for a constant-folded condition itself (if false, if true, a ternary on a literal), but everything nested inside the dead arm was still visited, so an if false ... end block containing conditionals or method definitions — a common way to disable code — gave a tracked-but-unloaded file tuples Ruby's Coverage never emits: phantom, permanently-missed branches and phantom uncovered methods, the same unmergeable-tuple failure mode as #​1226 / #​1233. The extractor now descends only into the arm the compiler keeps, so a dead arm's entire subtree (nested conditionals, loops, safe navigation, and defs alike) emits nothing, while the live arm's contents — and the surviving elsif chain of a falsy if — are tracked exactly as Coverage tracks them. The folding table also gains the three literals it was missing: __LINE__, __ENCODING__, and a stabby lambda (->) fold as conditions too, while their lookalikes __FILE__ and a lambda call do not and are still tracked. And the fold's paren transparency now matches the compiler's, which is not universal: if (1) folds like if 1, but (nil), ("x"), and (-> {}) keep their real branch the moment parentheses wrap them (for the string, this mismatch predates these changes).

  • A merged report no longer shows 100% branch and method coverage for a tracked file that no process ever loaded. SourceFile::Statistics reports 0% rather than a misleading 100% when a never-loaded file has no branch or method data at all (#​902), but that rule keys off a loaded: flag that only the single-process path ever set: ResultMerger.create_result built its Result without not_loaded_files, so every file in a merged report claimed to have been loaded and the rule could never fire there. The flag isn't serialized into .resultset.json (Result#to_hash writes only coverage and a timestamp), so the merged result now re-derives it from the merged line counts, using the same "did any line execute" signal Combine::FilesCombiner already reconciles on. In practice this surfaced on files with no branches at all, such as a constants file picked up by a cover glob, since #​1059's synthesized tuples already produce 0% for anything containing a conditional. Anything a process did load is unaffected, including under a branch-only or method-only configuration: Coverage reports no line data there, so a simulated file omits it too and the merged report flags nothing rather than mistaking every loaded file for an unloaded one. A file is judged only when it has at least one relevant line, so a loaded file with no executable lines at all (a comment-only constants stub, say) keeps its usual statistics rather than being mistaken for never-loaded — a simulated file carries a 0 on every relevant line, so genuinely unloaded files are still flagged. Reported with an exemplary diagnosis by @​andriytyurnikov. See #​1250.

  • SimpleCov.command_name is no longer decided by an incidental substring of the path to the Ruby interpreter. CommandGuesser matches its framework patterns against "#{$PROGRAM_NAME} #{ARGV.join(' ')}", and those patterns were bare substrings, so a test/ anywhere in that string won. A Ruby installed under a latest/bin directory (the layout mise creates alongside the versioned one) put test/ in the path of every binary run through it, and because test/ is checked before spec/, RSpec and Cucumber suites alike were labelled Unit Tests. The same flaw applied inside the arguments, where rspec spec/greatest/foo_spec.rb was mislabelled for the same reason. The patterns now match only at a path segment boundary, so latest/, contest/, and greatest/ no longer read as test/. Because the command name is the resultset key under merging, a mislabelled suite was filed under the wrong key, letting two different suites merge into each other rather than failing loudly. Reported with an exemplary diagnosis by @​andriytyurnikov. See #​1249.

  • The invoked executable is now consulted before the path patterns, so an rspec or cucumber binary names the framework regardless of what surrounds it on the command line. This is what keeps rspec features reporting as RSpec rather than as Cucumber: an RSpec suite whose examples live in features/ is still an RSpec suite, and previously that case only worked by accident, because the old unanchored spec pattern matched the letters inside the word rspec. Generic runners are deliberately not in the table, so ruby test/integration/foo_test.rb and rake's test loader still fall through to the path patterns that draw the unit, functional, and integration distinction. The executable is read from $PROGRAM_NAME, which is now recorded separately from the flattened command as CommandGuesser.original_program_name, because the space that joins it to ARGV makes a program path containing one (/opt/My Ruby/bin/rspec) impossible to recover afterwards.

  • SimpleCov.formatters = false now opts out of formatting, matching formatter false. Since 1.0.1's input normalization, Array(false) smuggled the false through as a one-element formatter list, so every report printed a "Formatter false failed with NoMethodError" complaint instead of skipping formatting. nil, false, and [] now all mean the same explicit opt-out on both setters.

  • Merging no longer discards branch and method data the resultsets carried just because the merging process does not measure that criterion itself. A merge runs on behalf of the processes that produced the resultsets and does not necessarily share their configuration: simplecov merge only requires the library and never runs SimpleCov.start, and a SimpleCov.collate block need not repeat enable_coverage :branch. Such a process dropped the branch table from every file that appeared in more than one resultset while passing through, untouched, the table of any file that appeared in only one, so the merged output was both lossy and internally inconsistent, and merge_and_store wrote that state back to disk. A criterion is now carried when the merging process measures it or when the data carries it, so nothing measured is lost and a process that does measure a criterion still always gets a table, even an empty one.

Performance

  • HTML formatting with source_in_json false now builds metadata, groups, errors, and per-file statistics once, then derives the source-less coverage.json payload from that result. It previously traversed the entire result and queried Git a second time solely to omit each file's source field.
  • Tracked-but-unloaded files are now simulated once, at the merge point, instead of once per process. inject_unloaded_files skipped only the files the current process had loaded, so every worker in a parallel run simulated nearly the whole project and the merge discarded all but one copy of each. The work now happens in ResultMerger, against the union of what every contributing process loaded, which makes it O(1) in worker count rather than O(N). Over 400 tracked files with 40 of them loaded by no worker, a 16-worker run drops from 6,040 simulations taking 3.15s across the workers to 40 taking 0.024s once, and per-worker resultsets shrink from 13.4MB to 800KB because they no longer each carry a simulated copy of the project. A single process, and any run with merging false, pays exactly what it did before. Each process records the paths it was told to track into its resultset so the merge can do this without needing that process's cover / track_files configuration, which a standalone SimpleCov.collate does not have; resultsets written by earlier versions carry the files their process injected and merge unchanged. Reported with measurements by @​andriytyurnikov. See #​1250.
  • Simulating a tracked-but-unloaded file no longer parses it to synthesize branch and method tuples when neither branch nor method coverage is enabled. Nothing reads those tuples in that case, and the Prism parse that produces them is over half the cost of simulating a file, paid once per tracked file in every process. On a 400 file synthetic project the injection pass drops from 0.526 to 0.222 ms per file for the default line-coverage-only configuration. Suites with branch or method coverage on are unaffected, and the line classification is identical either way. Reported with measurements by @​andriytyurnikov. See #​1250.
  • Classifying the lines of a tracked-but-unloaded file is roughly 10% cheaper, which is what remains of that per-file cost once the synthesis above is skipped — and unlike the synthesis, it is paid under every configuration. LinesClassifier#classify ran the :nocov: regex twice for every line — once to toggle skipping and once inside not_relevant_line?. A marker is always a comment, so the cheaper whitespace-or-comment test now gates the token match, and a line of real code (most of a source file) no longer pays for it at all. Over SimpleCov's own lib/ (104 files, 8,845 lines) the whole simulation pass drops 11% on a line-coverage-only run and 7% with synthesis on. benchmarks/simulate_coverage.rb covers this path, which had no benchmark before — only collate and Result did, so nothing measured the per-process work at exit.
  • Merging resultsets is roughly 40% faster, which on a large parallel CI run is most of what collate spends its time on. Results were folded together pairwise, so every one of the N-1 steps rebuilt the whole accumulated structure — a fresh outer file hash, a fresh lines array for every file, and a fresh branch/method table for every file whose keys were re-interned from their tuples each time. A 160-worker run over ~1,800 files did ~290,000 whole-file rebuilds to produce ~1,800 files of output. Results are now absorbed into an accumulator that owns its state and updates it in place, and the interned branch/method tables become tuple-keyed hashes once, at the end. Resultsets are still read and absorbed one at a time, so the memory ceiling merge_results is careful about is unchanged (peak RSS on the benchmark is identical). On the repository's benchmarks/collate.rb fixture — 160 resultsets, 1,836 files, 147,875 lines, branch coverage on — the merge phase drops from 5.85s to 3.38s and the whole collate from 6.65s to 3.87s. SimpleCov::Combine::FilesCombiner and SimpleCov::Combine.combine are gone, their roles taken by the new SimpleCov::Combine::CoverageAccumulator; both were internal API.

v1.0.3

Compare Source

==================

Bugfixes

  • Generating a report no longer crashes when the coverage universe contains a module that shadows #inspect with an incompatible signature. Rendering a method coverage key's receiver calls to_s, and a singleton class's to_s renders its attached object via #inspect — Liquid's Utils module defines inspect(value, max_depth = 2) as a module_function, so any suite whose report included Liquid's files (typically a vendored bundle under the project root, which is why this surfaced only in CI) raised ArgumentError from the at_exit hook and lost its report. The exposure predates 1.0.2's key normalization, which only moved the call. Rendering now recovers by rebuilding the name from Module#name via bound methods, which user code cannot shadow, falling back to an address form that the existing normalization collapses. The external_at_exit workaround is no longer needed. Reported with an exemplary diagnosis by @​bkuhlmann. See #​1236.
  • Method coverage entries are now aggregated by source location alone, completing the aggregation introduced in 1.0.2 (which keyed on name and location). Ruby records one method entry per defined method, so a builder looping container.each_key { |key| define_method(key) { ... } } produces an entry per generated name, all at the block's location — and every name whose generated wrapper no test happened to call showed as an uncovered method on a line with full line and branch coverage. A source location is the unit a file-based report can express, and regular defs map one location to one name, so they are unaffected. The same identity is used when merging resultsets across processes. This also covers methods copied into refinements via import_methods, which Ruby records once per importing refinement at the shared module's original location, so exercising the method through any refinement now marks the shared definition covered and the skip workaround for shared refinement modules can be dropped. Reported with exemplary diagnoses by @​bkuhlmann. See #​1234 and #​1237.
  • SimpleCov.formatter and SimpleCov.formatters now accept formatter instances in addition to formatter classes, so constructor options can actually be passed — most notably SimpleCov::Formatter::HTMLFormatter.new(silent: true) to suppress the "Coverage report generated" status line. Previously SimpleCov unconditionally called .new on whatever was configured, so passing an instance crashed with NoMethodError at report time. See #​1240.

Performance

  • Fix 5x performance regression on report combining (introduced in 1.0.0 as a result of using Ripper#parse in a hot path) by adding parsed key memoisation to RubyDataParser.call.

v1.0.2

Compare Source

==================

Bugfixes

  • The standalone simplecov CLI's colorizing subcommands (report, uncovered, coverage, diff) no longer crash with NoMethodError: undefined method 'color' when run in a project without a .simplecov file. The CLI deliberately loads only simplecov/cli rather than the full library, so SimpleCov.color was undefined unless a dotfile load had incidentally defined it — and --no-color was the only workaround, since the documented NO_COLOR env var was checked after the line that raised. Color.enabled? now treats missing configuration the same as its :auto default and falls through to NO_COLOR / FORCE_COLOR / TTY detection. Reported with an exemplary diagnosis by @​hasghari. See #​1231.
  • Branch tuples synthesized for tracked-but-unloaded files now match Ruby's Coverage for a safe-navigation call that takes a block. For x&.foo { ... } (and the second link of a chain like x&.foo&.bar { ... }) the extractor keyed the branch on the call node's full source range, which extends through the attached block, while Coverage ends the range at the call itself — so a simulated entry merging with a real one produced a phantom, permanently-missed branch, the same failure mode as the elsif fix in 1.0.1. Reported with an exemplary diagnosis and a suggested fix by @​alexdeng-mp. See #​1233.
  • Prompted by the two reports above, an exhaustive differential audit of StaticCoverageExtractor against Ruby's Coverage — a fuzzing harness that runs thousands of generated programs through both and diffs the branch tuples, now part of the spec suite (opt-in via SIMPLECOV_FUZZ=1) — surfaced and fixed four more mismatches of the same phantom-branch class. Conditions that are compile-time literals (if true, if 1, a ternary on a literal) are folded away by Ruby's compiler and no longer produce synthesized branches (while true still does — loops are not folded). On Ruby 3.3, three legacy conventions now match: the body range of a do-while (begin ... end while), the location of empty branch arms (which on 3.3 depends on whether the construct is in value or void position), and one-line pattern matching (x => pattern / x in pattern), which emits a :case branch on 3.3 and nothing on 3.4+. The audit also caught a crash on Ruby 3.3's stdlib Prism (0.19), which still exposes the else clause of UnlessNode / CaseNode / CaseMatchNode under its pre-1.3 name consequent: the extractor raised internally and silently dropped simulated branch and method data for any file containing unless/else or a case with an empty arm, unless a newer prism gem happened to be installed.
  • As a defensive layer against any future extractor drift, merging now treats an actually-executed file's branch and method data as authoritative: when a resultset that merely tracked a file (with simulated, statically-derived tuples) merges with one from a process that really loaded it, the synthesized tuples are dropped rather than unioned. This contains any undiscovered mismatch to denominator inflation on files no process loaded, instead of phantom misses on fully-covered ones. Line coverage still combines from both sides, so tracked-but-unloaded files keep contributing to the line denominator as before.
  • Method coverage no longer reports phantom uncovered methods for define_method / define_singleton_method blocks defined onto more than one receiver — e.g. a module's included hook defining the same block on every including class. Ruby records one method entry per receiver, all pointing at the same source location, so any receiver whose copy was never called showed as an uncovered method on a line with 100% line coverage. Entries are now aggregated by (name, source location) with hit counts summed, and cross-process merging matches methods on the same source identity rather than on the receiver class. Reported with an exemplary diagnosis by @​bkuhlmann. See #​1234.
  • Branch coverage under enable_coverage :eval no longer inflates denominators or reports phantom missed branches for templates compiled more than once — e.g. hanami-view compiles each template once per view class, and every ERB.new(...).result is a fresh compile. Ruby's Coverage emits a fresh set of branch entries per compile of the same file (nondeterministically through Ruby 4.0, consistently on current ruby master — see https://bugs.ruby-lang.org/issues/22203), each counting only the renders that flowed through that compile, so a side exercised under one compile appeared as a permanently-missed branch in another compile's entry at the same location, and ignore_branches :implicit_else swung the report wildly by stripping only the synthetic-else halves of the duplicates. Duplicated conditions are now aggregated by source location with arm counts summed. Reported with an exemplary diagnosis by @​bkuhlmann. See #​1235.

v1.0.1

Compare Source

==================

Enhancements

  • The gem now ships type signatures under sig/, covering the public API: the configuration DSL (including the criterion-scoped coverage block and the legacy deprecated verbs), the Result / FileList / SourceFile / CoverageStatistics read API that formatter authors consume, the formatter and filter class hierarchies, exit codes, and the ParallelAdapters::Base contract. Internal classes carry repository-only skeleton signatures (sig/internal/, excluded from the gem package) so the entire codebase type-checks under Steep in strict mode, while the shipped signature payload stays small. Signatures are checked with rbs validate and steep check as part of the default rake task. RBS and Steep users no longer need the third-party signatures from ruby/gem_rbs_collection, which cover the 0.22 API and predate 1.0's configuration redesign.

Bugfixes

  • Branch tuples synthesized for tracked-but-unloaded files now match Ruby's Coverage exactly for elsif and for if arms with empty bodies. StaticCoverageExtractor attributed the outer else arm of an elsif to the clause's body rather than the whole clause, and an empty if then-body to the whole node rather than Coverage's zero-width point at the predicate's end. Since resultset merges combine branch arms by their exact location, a simulated entry merging with a real one for the same file (parent and worker under Minitest's parallelize, or RSpec and Minitest suites collated together) produced phantom, permanently-missed branch arms. A new differential spec now pins every branch construct tuple-for-tuple against Ruby's Coverage — which promptly caught that CRuby 3.4 changed several of these conventions, so the extractor now emits whichever shape the running Ruby's Coverage uses (on 3.2/3.3: elsif clause ranges end at the chain's last content rather than the shared end, empty if/else/when bodies fall back to enclosing ranges, and empty while/in bodies collapse to points). Reported with an exemplary diagnosis by @​hasghari. See #​1226.
  • merge_subprocesses no longer silently drops all worker coverage under Minitest's fork-based parallelize(workers: N) (the setup the rails profile exists for). When Minitest's autorun was armed before SimpleCov.start — which is how rails test loads — SimpleCov deferred its report to Minitest.after_run, and forked workers inherited that deferral even though Minitest pins its after_run hook to the parent's pid, so no exit path in the worker ever stored its resultset. Workers now reset the inherited at_exit state on fork and re-arm their own hook, so their resultsets are stored and merged as documented. Reported with an exemplary diagnosis by @​hasghari. See #​1227.
  • Fixed SimpleCov.formatters= raising NoMethodError when given a single formatter instead of an Array — a regression from 0.22.x, where MultiFormatter.new normalized the value internally. This restores the long-documented SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([...]) pattern, in which MultiFormatter.new returns a Class rather than an Array. The regression surfaced in ruby/ruby's CI through net-imap's test helper. Thanks @​koic. See #​1224.
  • Formatter status lines ("Coverage report generated for X") and threshold-enforcement output (violation reports, "SimpleCov failed with exit N") no longer route through Kernel#warn. They still print to stderr, but they are program output rather than Ruby warnings, so Warning.warn hooks — warning trackers and raise-on-warning test setups — no longer intercept them as unaddressable noise, and threshold failure explanations now survive ruby -W0, which previously reduced a failing check to a bare exit code with no explanation. Genuine warnings (deprecations, dropped-file notices, parse failures) still use warn. Suppression remains explicit: silent: true for formatter status lines, print_errors false for enforcement output. Thanks @​viralpraxis. See #​1225.

v1.0.0

Compare Source

==================

First stable release of the 1.0 line. The entries below consolidate release candidates rc1 through rc5 and describe all changes since 0.22.1.

Breaking Changes

  • Dropped support for Ruby 3.1 and JRuby 9.4. The minimum is now Ruby 3.2 (and JRuby 10, which reports RUBY_VERSION 3.4). Ruby 3.1 reached end of life in March 2025, and a recent i18n release calls Fiber[], a Ruby 3.2 API, at load time, so suites that load Rails no longer run on 3.1. Raising required_ruby_version to >= 3.2 also excludes JRuby 9.4, which reports RUBY_VERSION 3.1.x. See #​1171.
  • JSON formatter: group stats changed from { "covered_percent": 80.0 } to full stats shape { "covered": 8, "missed": 2, "total": 10, "percent": 80.0, "strength": 0.0 }. The key covered_percent is renamed to percent.
  • JSON formatter: simplecov_json_formatter gem is now built in. require "simplecov_json_formatter" continues to work via a shim.
  • StringFilter now matches at path-segment boundaries. "lib" matches /lib/ but no longer matches /library/. Use a Regexp filter for substring matching.
  • SourceFile#project_filename now returns a truly relative path with no leading separator (e.g. lib/foo.rb instead of /lib/foo.rb). This also removes the leading / from file path keys in coverage.json and from the filename in minimum_coverage_by_file error messages. Anchored RegexFilters that relied on a leading / (e.g. %r{^/lib/}) should be rewritten (e.g. %r{\Alib/}).
  • Removed docile gem dependency. The SimpleCov.configure block is now evaluated via instance_exec with instance variable proxying.
  • Removed automatic activation of JSONFormatter when the CC_TEST_REPORTER_ID environment variable is set. The default HTMLFormatter now emits coverage.json alongside the HTML report (using JSONFormatter.build_hash to serialize the same payload JSONFormatter writes), so the env-var special case is no longer needed. Because of this, listing JSONFormatter alongside HTMLFormatter is redundant and can be removed.
  • SimpleCov.start now loads the test_frameworks profile by default, which filters paths under test/, spec/, features/, and autotest/. Running the suite always executes 100% of the test files themselves, which inflated the overall percentage and obscured application coverage. To opt back in (e.g. to surface dead test helpers), drop the filter with remove_filter %r{\A(test|features|spec|autotest)/}. See #​816.
  • HTML and JSON formatters now write the "Coverage report generated for X to Y" status line (and the per-criterion totals beneath it) to stderr instead of stdout. The message is a diagnostic, not the program's output, and routing it to stdout polluted pipelines like rspec -f json. Suppress it entirely with silent: true on the formatter; redirect with 2>&1 if you want the old behavior. See #​1060.
  • Under parallel_tests, SimpleCov now waits in the first started process (via ParallelTests.first_process?) rather than the last. This matches the convention parallel_tests's own README recommends for "do something once after all workers finish" hooks, so user code that has its own ParallelTests.wait_for_other_processes_to_finish in an RSpec.after(:suite) (or equivalent) no longer deadlocks against SimpleCov's wait when both pick the same process. As a side benefit, the previous PARALLEL_TEST_GROUPS=1 workaround for last_process?'s "" == "1" mismatch (#​1066) is no longer needed — first_process? handles that case naturally. Migration: the rare project that wired its own wait via ParallelTests.last_process? now hits the symmetric deadlock and must switch to first_process?. See #​922.
  • Removed SimpleCov.coverage_criterion. It was a reader/writer for a value nothing in SimpleCov ever consumed, so it duplicated primary_coverage without affecting any behavior. Use primary_coverage to choose the report's leading criterion (or the coverage :branch, primary: true form).

Deprecations

  • The configuration API has been redesigned around a smaller, more consistent set of verbs. The legacy methods continue to work but each emits a deprecation warning that names its replacement; a future release will remove them. Warnings are deduplicated by call site, so a deprecated method called in a loop or a configuration block re-evaluated once per parallel worker or spec file warns at most once per source location (see #​1204). See the "Migrating from the legacy configuration API" section in the README for the full migration table and a before/after example.
    • add_filterskip (identical matcher grammar; no behavior change)
    • add_groupgroup (identical matcher grammar; no behavior change)
    • track_filescover (cover includes unloaded files like track_files did and restricts the report to the matching set; pass every directory you want reported, e.g. cover "lib/**/*.rb", "app/**/*.rb", to keep the old additive-only behavior)
    • use_mergingmerging (same value)
    • enable_for_subprocessesmerge_subprocesses (same value)
    • enable_coverage_for_evalenable_coverage :eval (folds into the same call that enables :line / :branch / :method)
    • print_error_status (reader) → print_errors (the print_error_status= writer is unaffected for now)
  • Calling SimpleCov.start from .simplecov is deprecated. Coverage tracking still begins for backward compatibility, but a one-time deprecation warning fires pointing the user at moving the call into spec_helper.rb / test_helper.rb; a future release will require the explicit SimpleCov.start from a test helper. The migration goes hand-in-hand with the bugfix below: once SimpleCov.start lives in the test helper, the parent process that auto-loads .simplecov never starts tracking and the empty-report-overwrite scenario can't arise. See #​581.
  • # :nocov: toggle comments (and the configurable SimpleCov.nocov_token / SimpleCov.skip_token) are deprecated in favor of the new # simplecov:disable / # simplecov:enable directives. Each file that still uses # :nocov: emits a one-time deprecation warning to stderr at load time pointing at the recommended replacement, and any call to SimpleCov.nocov_token or SimpleCov.skip_token (getter or setter) likewise warns. The directive will be removed in a future release.
  • SimpleCov::SourceFile#branches_coverage_percent and #methods_coverage_percent are deprecated in favor of the uniform covered_percent(:branch) / covered_percent(:method). covered_percent (and covered_strength) now take a criterion argument (defaulting to :line), so the same call reaches any criterion instead of line being the unprefixed default while branch and method had their own differently-named methods. coverage_statistics also now accepts a criterion (e.g. coverage_statistics(:branch)) to return that one CoverageStatistics rather than the whole Hash.
  • minimum_coverage_by_file and minimum_coverage_by_group are deprecated in favor of the coverage method's minimum_per_file / minimum_per_group verbs. The legacy methods overloaded a single hash to carry both per-criterion defaults and per-path / per-group overrides, with minimum_coverage_by_file further distinguishing Symbol keys (criterion defaults) from String / Regexp keys (path overrides) and accepting either a bare number or a per-criterion hash as the value. The coverage block fixes the criterion so every threshold is a plain percentage with an only: target. The setter form emits a deprecation warning naming the replacement; the no-arg getter (read internally) is unchanged. Replace e.g. minimum_coverage_by_file line: 70, 'app/x.rb' => 100 with coverage(:line) { minimum_per_file 70; minimum_per_file 100, only: 'app/x.rb' }. See the "Per-criterion thresholds with coverage" README section.

Enhancements

  • simplecov uncovered gained --criterion line|branch|method (default line) so the lowest-coverage listing can rank by branch or method coverage, not just line.
  • Added the criterion-first coverage configuration method — a uniform way to configure each coverage criterion (:line, :branch, :method) in one place. coverage :line do minimum 90; minimum_per_file 80; maximum_drop 5 end (or the one-liner coverage :branch, minimum: 80) enables the criterion and declares its thresholds with identical syntax regardless of criterion, because the criterion is fixed by the enclosing call rather than smuggled into the argument as the historical "a bare number means line coverage, every other criterion needs a Hash" special case. Verbs: minimum, maximum, exact, maximum_drop, minimum_per_file (with only: String-path / Regexp overrides), and minimum_per_group. Options: primary: (the report's leading criterion), oneshot: (oneshot-lines mode for :line), and :eval. The flat minimum_coverage family remains as suite-wide sugar. Thresholds feed the same internal stores, so exit-code enforcement is unchanged. See the "Per-criterion thresholds with coverage" section in the README.
  • JSON formatter: coverage.json now carries a top-level $schema field holding the URL of the versioned canonical JSON Schema the document conforms to, plus a human-readable meta.schema_version ("major.minor", currently "1.0"). The versioned canonical lives at schemas/coverage-v1.0.schema.json and is immutable per version, an unversioned convenience alias at schemas/coverage.schema.json always tracks the latest. Downstream tools can validate inputs, generate types, or pin to a known shape, and the document-level $schema makes each payload self-describing. The schema version is independent of the gem version: additive changes bump minor, removals or shape changes bump major and ship as a new schemas/coverage-vX.0.schema.json file so prior-version consumers stay valid. meta.commit carries the git commit SHA the report was generated against (or null outside a git checkout), so tools can recover the exact source from history even when source_in_json false omits the per-file source arrays.
  • Added SimpleCov::ParallelAdapters — a pluggable adapter interface for parallel test runners. SimpleCov's coordination with parallel test runners (deciding which worker does final-result work, waiting for siblings, knowing how many resultsets to expect) now routes through an adapter chain rather than hard-coding the parallel_tests gem's API. Two adapters ship: ParallelTestsAdapter wraps the historical grosser/parallel_tests gem (precise, gem-API-based); GenericAdapter handles any runner that follows the TEST_ENV_NUMBER / PARALLEL_TEST_GROUPS env-var convention without shipping a Ruby API. The practical impact: parallel_rspec (and any similar env-var-only runner) now works out of the box — previously every worker thought it was the "final" one and they clobbered each other's resultsets. Custom runners can register their own adapter via SimpleCov::ParallelAdapters.register MyAdapter, where MyAdapter subclasses SimpleCov::ParallelAdapters::Base and overrides the four contract methods (active?, first_worker?, wait_for_siblings, expected_worker_count). See #​1065.
  • Added SimpleCov.ignore_branches for opting out of synthetic :else branches that Ruby's Coverage library reports for constructs with no literal else keyword — exhaustive case/in pattern matches, case/when without else, ||= / &&=, and if / unless without else. Variadic; only :implicit_else is supported today, with room for future synthetic branch types. Calling it without (or before) enable_coverage :branch is harmless — the setting is stored and applies once branch coverage is enabled. Explicit else arms still count. See #​1033.
  • Added SimpleCov.cover for declaring a positive coverage scope (the long-requested allowlist counterpart to add_filter). Accepts string globs, Regexps, blocks, or arrays of those; multiple calls union. When any cover matcher is configured the report drops every source file that doesn't match at least one of them, and string-

Note

PR body was truncated to here.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate Bot changed the title Update dependency simplecov to v1 Update SimpleCov packages Jul 12, 2026
@renovate
renovate Bot force-pushed the renovate/simplecov-packages branch from 790c221 to ed382d9 Compare July 12, 2026 17:32
@renovate
renovate Bot force-pushed the renovate/simplecov-packages branch from ed382d9 to 2ede8d0 Compare August 11, 2026 02:12
@renovate
renovate Bot force-pushed the renovate/simplecov-packages branch from 2ede8d0 to efc4a90 Compare August 16, 2026 12:09
@renovate

renovate Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

Edited/Blocked Notification

Renovate will not automatically rebase this PR, because it does not recognize the last commit author and assumes somebody else may have edited the PR.

You can manually request rebase by checking the rebase/retry box above.

⚠️ Warning: custom changes will be lost.

@codecov

codecov Bot commented Aug 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (7f3962b) to head (4ea7bb3).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##              main      #123    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           27        13    -14     
  Lines          624       230   -394     
==========================================
- Hits           624       230   -394     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@AlexWayfer
AlexWayfer merged commit 30c8399 into main Aug 16, 2026
10 checks passed
@AlexWayfer
AlexWayfer deleted the renovate/simplecov-packages branch August 16, 2026 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant