Skip to content

chore(deps): update dependency simplecov to v1 - #299

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/simplecov-1.x
Open

chore(deps): update dependency simplecov to v1#299
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/simplecov-1.x

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

Release Notes

simplecov-ruby/simplecov (simplecov)

v1.1.1

Compare Source

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

Enhancements

  • The HTML report remembers how a file list was sorted. The selected column and direction persist in localStorage across the All Files and group tables, so reloading a report restores the order it was left in. The preference stores semantic column keys rather than numeric positions, so a report whose enabled criteria differ from the report that stored it falls back to the primary coverage sort instead of sorting by whatever column now happens to sit in that position. Equal values break by filename so the restored order matches the clicked order exactly, and the covered and total columns now sort by their numeric value rather than their comma-grouped digits, which used to rank 1,250 below 999.
  • The report's top bar is now a proper tab strip. Every group tab is outlined, so an unselected group reads as a tab rather than as loose text floating above the panel, while the selected one fills with the panel's own colour and drops its bottom border to merge into it. The colorblind and theme toggles stay unfilled, which keeps the filled shapes in the bar meaningful: the selected tab, and a toggle that is switched on. With more groups than fit, the strip scrolls and both ends fade instead of guillotining whichever tab straddles an edge.
  • The source file view was polished to match. The colorblind and theme controls sit at the same viewport coordinates as the index toolbar, with the scrollbar gutter reserved so opening the dialog cannot slide them sideways. Each legend key shares a row with its line, branch, or method summary, and the generated-at footer is repeated at the bottom of the source dialog. On paper the header returns to document flow, where the toggles and close button are hidden and the legend reads better at full width.
  • The source view no longer prints a coverage glyph in a left gutter on every line, and the legend swatches are plain chips of the colours the source view paints rather than a key to a symbol set. The glyphs stated each line's status without relying on colour, but they read as noise down the left edge of every file. Line numbers keep their per-status background, and the Colorblind toggle does the accessibility work on its own by swapping covered and missed for blue versus orange, the pairing red/green vision cannot separate.
  • The packaged gem no longer ships the schemas/ directory, dropping 32 KB unpacked from every install. Nothing read it at runtime: the JSON formatter points consumers at the canonical schema URL on GitHub, and the schema specs read from the repository.

Bugfixes

  • Coverage checks no longer get skipped under runners that close a worker's output streams before its at_exit hooks run. rspec-conductor closes each worker's pipes as soon as the worker has sent its run summary, and emitting SimpleCov's status line probes Color.enabled?, whose IO#tty? call raised IOError on the closed stream. That exception aborted the remaining exit tasks, so the HTML and JSON reports were written but the minimum and maximum coverage checks silently never ran, and neither .last_run.json nor the .report_stamp deferral marker was written. A closed stream now counts as not a tty, and the status line and the violation report are dropped rather than cancelling the checks that follow. rspec-conductor is also named in the parallel adapter documentation and covered end to end now, since it follows the TEST_ENV_NUMBER and PARALLEL_TEST_GROUPS convention the generic adapter already recognizes. See #​1156.
  • SimpleCov.collate no longer depends on load order for Coverage.line_stub. SimulateCoverage and ResultAdapter both call it without requiring the coverage library. A normal run loads that library while starting tracking, but a collate-only process never starts tracking, so the constant resolved only by luck through a criterion-support check that happened to run first. Any reordering of that path would have turned into a NameError out of SimpleCov.collate, so each file now requires what it uses.

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

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 is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


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

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

Copilot AI review requested due to automatic review settings July 12, 2026 15:10
@renovate
renovate Bot requested a review from a team as a code owner July 12, 2026 15:10
@renovate

renovate Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

⚠️ Artifact update problem

Renovate failed to update an artifact related to this branch. You probably do not want to merge this PR as-is.

♻ Renovate will retry this branch, including artifacts, only when one of the following happens:

  • any of the package files in this branch needs updating, or
  • the branch becomes conflicted, or
  • you click the rebase/retry checkbox if found above, or
  • you rename this PR's title to start with "rebase!" to trigger it manually

The artifact failure details are included below:

File name: Gemfile.lock
Writing lockfile to /tmp/renovate/repos/github/open-feature/ruby-sdk/Gemfile.lock
Fetching gem metadata from https://rubygems.org/...........
Resolving dependencies...

[DEPRECATED] Found x64-mingw32 in lockfile, which is deprecated and will be removed in the future.
Could not find compatible versions

Because simplecov-cobertura >= 2.0.0, < 4.0.0.rc1 depends on simplecov ~> 0.19
  and Gemfile depends on simplecov ~> 1.1.0,
  simplecov-cobertura >= 2.0.0, < 4.0.0.rc1 cannot be used.
So, because Gemfile depends on simplecov-cobertura ~> 3.0,
  version solving has failed.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot can't review bot-authored pull requests automatically. A user with Copilot access can request a review manually.

@github-actions
github-actions Bot enabled auto-merge (squash) July 12, 2026 15:10
@renovate
renovate Bot force-pushed the renovate/simplecov-1.x branch from 6e4c2bb to c6b717e Compare August 11, 2026 00:24
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