diff --git a/.rubocop.yml b/.rubocop.yml index 8f0f17b2..4b337765 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -188,6 +188,10 @@ Metrics/MethodLength: CountComments: false Max: 12 # TODO: Lower to 10 +Style/Next: + Description: Prefer `if` guards over `next` inside loops for readability. + Enabled: false + Metrics/ModuleLength: Description: Avoid modules longer than 100 lines of code. Max: 300 diff --git a/CHANGELOG.md b/CHANGELOG.md index d99693a6..09fa1a3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,24 @@ Unreleased ========== +## Breaking Changes +* 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. +* Removed `docile` gem dependency. The `SimpleCov.configure` DSL block is now evaluated via `instance_exec` with instance variable proxying. + +## Enhancements +* JSON formatter: added `total` section with aggregate coverage statistics (covered, missed, total, percent, strength) for line, branch, and method coverage. Line stats additionally include `omitted` (count of blank/comment lines, i.e. lines that cannot be covered) +* JSON formatter: per-file output now includes `lines_covered_percent`, and when enabled: `branches_covered_percent`, `methods` array, and `methods_covered_percent` +* JSON formatter: group stats now include full statistics for all enabled coverage types, not just line coverage percent +* JSON formatter: added `silent:` keyword to `JSONFormatter.new` to suppress console output +* Merged `simplecov-html` formatter into the main gem. A backward-compatibility shim ensures `require "simplecov-html"` still works. +* Merged `simplecov_json_formatter` into the main gem. A backward-compatibility shim ensures `require "simplecov_json_formatter"` still works. +* `CommandGuesser` now appends the framework name to parallel test data (e.g. `"RSpec (1/2)"` instead of `"(1/2)"`) + ## Bugfixes * Don't report misleading 100% branch/method coverage for files added via `track_files` that were never loaded. See #902 +* Fix HTML formatter tab bar layout: dark mode toggle no longer wraps onto two lines, and tabs connect seamlessly with the content panel 0.22.1 (2024-09-02) ========== diff --git a/features/step_definitions/json_steps.rb b/features/step_definitions/json_steps.rb index a66f1461..fd94548e 100644 --- a/features/step_definitions/json_steps.rb +++ b/features/step_definitions/json_steps.rb @@ -6,7 +6,12 @@ coverage_hash = json_report.fetch "coverage" directory = Dir.pwd - expect(coverage_hash.fetch("#{directory}/lib/faked_project.rb")).to eq "lines" => [nil, nil, 1, 1, 1, nil, nil, nil, 5, 3, nil, nil, 1] - expect(coverage_hash.fetch("#{directory}/lib/faked_project/some_class.rb")).to eq "lines" => [nil, nil, 1, 1, 1, nil, 1, 2, nil, nil, 1, 1, nil, nil, 1, 1, 1, nil, 0, nil, nil, 0, nil, nil, 1, nil, 1, 0, nil, nil] + faked_project = coverage_hash.fetch("#{directory}/lib/faked_project.rb") + expect(faked_project["lines"]).to eq [nil, nil, 1, 1, 1, nil, nil, nil, 5, 3, nil, nil, 1] + expect(faked_project["lines_covered_percent"]).to be_a(Float) + + some_class = coverage_hash.fetch("#{directory}/lib/faked_project/some_class.rb") + expect(some_class["lines"]).to eq [nil, nil, 1, 1, 1, nil, 1, 2, nil, nil, 1, 1, nil, nil, 1, 1, 1, nil, 0, nil, nil, 0, nil, nil, 1, nil, 1, 0, nil, nil] + expect(some_class["lines_covered_percent"]).to be_a(Float) end end diff --git a/lib/simplecov.rb b/lib/simplecov.rb index b829678d..0430b120 100644 --- a/lib/simplecov.rb +++ b/lib/simplecov.rb @@ -478,6 +478,7 @@ def probably_running_parallel_tests? require_relative "simplecov/configuration" SimpleCov.extend SimpleCov::Configuration require_relative "simplecov/coverage_statistics" +require_relative "simplecov/coverage_violations" require_relative "simplecov/exit_codes" require_relative "simplecov/profiles" require_relative "simplecov/source_file/line" diff --git a/lib/simplecov/coverage_statistics.rb b/lib/simplecov/coverage_statistics.rb index a9666f26..cff423cc 100644 --- a/lib/simplecov/coverage_statistics.rb +++ b/lib/simplecov/coverage_statistics.rb @@ -8,32 +8,35 @@ module SimpleCov # * total - how many things to cover there are (total relevant loc/branches) # * covered - how many of the coverables are hit # * missed - how many of the coverables are missed + # * omitted - how many lines cannot be covered (blank lines/comments); only meaningful for line coverage # * percent - percentage as covered/missed # * strength - average hits per/coverable (will not exist for one shot lines format) class CoverageStatistics - attr_reader :total, :covered, :missed, :strength, :percent + attr_reader :total, :covered, :missed, :omitted, :strength, :percent def self.from(coverage_statistics) - sum_covered, sum_missed, sum_total_strength = - coverage_statistics.reduce([0, 0, 0.0]) do |(covered, missed, total_strength), file_coverage_statistics| + sum_covered, sum_missed, sum_omitted, sum_total_strength = + coverage_statistics.reduce([0, 0, 0, 0.0]) do |(covered, missed, omitted, total_strength), file_coverage_statistics| [ covered + file_coverage_statistics.covered, missed + file_coverage_statistics.missed, + omitted + file_coverage_statistics.omitted, # gotta remultiply with loc because files have different strength and loc # giving them a different "weight" in total total_strength + (file_coverage_statistics.strength * file_coverage_statistics.total) ] end - new(covered: sum_covered, missed: sum_missed, total_strength: sum_total_strength) + new(covered: sum_covered, missed: sum_missed, omitted: sum_omitted, total_strength: sum_total_strength) end # Requires only covered, missed and strength to be initialized. # # Other values are computed by this class. - def initialize(covered:, missed:, total_strength: 0.0, percent: nil) + def initialize(covered:, missed:, omitted: 0, total_strength: 0.0, percent: nil) @covered = covered @missed = missed + @omitted = omitted @total = covered + missed @percent = percent || compute_percent(covered, missed, total) @strength = compute_strength(total_strength, total) diff --git a/lib/simplecov/coverage_violations.rb b/lib/simplecov/coverage_violations.rb new file mode 100644 index 00000000..77a7b354 --- /dev/null +++ b/lib/simplecov/coverage_violations.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +module SimpleCov + # Computes coverage threshold violations for a given result. Shared by + # the exit-code checks and the JSON formatter's `errors` section. + # + # Each method returns an array of violation hashes. All percents are + # rounded via `SimpleCov.round_coverage` so downstream consumers don't + # need to round again. + module CoverageViolations + class << self + # @return [Array] {:criterion, :expected, :actual} + def minimum_overall(result, thresholds) + thresholds.filter_map do |criterion, expected| + actual = round(result.coverage_statistics.fetch(criterion).percent) + {criterion: criterion, expected: expected, actual: actual} if actual < expected + end + end + + # @return [Array] {:criterion, :expected, :actual, :filename, :project_filename} + def minimum_by_file(result, thresholds) + thresholds.flat_map do |criterion, expected| + result.files.filter_map { |file| file_minimum_violation(file, criterion, expected) } + end + end + + # @return [Array] {:group_name, :criterion, :expected, :actual} + def minimum_by_group(result, thresholds) + thresholds.flat_map do |group_name, minimums| + group = lookup_group(result, group_name) + group ? group_minimum_violations(group_name, group, minimums) : [] + end + end + + # @return [Array] {:criterion, :maximum, :actual} where `actual` + # is the observed drop (in percentage points) vs. the last run. + def maximum_drop(result, thresholds, last_run: SimpleCov::LastRun.read) + return [] unless last_run + + thresholds.filter_map do |criterion, maximum| + actual = compute_drop(criterion, result, last_run) + {criterion: criterion, maximum: maximum, actual: actual} if actual && actual > maximum + end + end + + private + + def file_minimum_violation(file, criterion, expected) + actual = round(file.coverage_statistics.fetch(criterion).percent) + return unless actual < expected + + { + criterion: criterion, + expected: expected, + actual: actual, + filename: file.filename, + project_filename: file.project_filename + } + end + + def group_minimum_violations(group_name, group, minimums) + minimums.filter_map do |criterion, expected| + actual = round(group.coverage_statistics.fetch(criterion).percent) + {group_name: group_name, criterion: criterion, expected: expected, actual: actual} if actual < expected + end + end + + def lookup_group(result, group_name) + group = result.groups[group_name] + warn "minimum_coverage_by_group: no group named '#{group_name}' exists. Available groups: #{result.groups.keys.join(', ')}" unless group + group + end + + def compute_drop(criterion, result, last_run) + last_coverage_percent = last_run.dig(:result, criterion) + last_coverage_percent ||= last_run.dig(:result, :covered_percent) if criterion == :line + return unless last_coverage_percent + + current = round(result.coverage_statistics.fetch(criterion).percent) + (last_coverage_percent - current).floor(10) + end + + def round(percent) + SimpleCov.round_coverage(percent) + end + end + end +end diff --git a/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb b/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb index b77f3d50..7484507c 100644 --- a/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +++ b/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb @@ -3,26 +3,22 @@ module SimpleCov module ExitCodes class MaximumCoverageDropCheck - MAX_DROP_ACCURACY = 10 - def initialize(result, maximum_coverage_drop) @result = result @maximum_coverage_drop = maximum_coverage_drop end def failing? - return false unless maximum_coverage_drop && last_run - - coverage_drop_violations.any? + violations.any? end def report - coverage_drop_violations.each do |violation| + violations.each do |violation| $stderr.printf( "%s coverage has dropped by %.2f%% since the last time (maximum allowed: %.2f%%).\n", - criterion: violation[:criterion].capitalize, - drop_percent: SimpleCov.round_coverage(violation[:drop_percent]), - max_drop: violation[:max_drop] + criterion: violation.fetch(:criterion).capitalize, + drop_percent: violation.fetch(:actual), + max_drop: violation.fetch(:maximum) ) end end @@ -33,50 +29,8 @@ def exit_code private - attr_reader :result, :maximum_coverage_drop - - def last_run - return @last_run if defined?(@last_run) - - @last_run = SimpleCov::LastRun.read - end - - def coverage_drop_violations - @coverage_drop_violations ||= - compute_coverage_drop_data.select do |achieved| - achieved.fetch(:max_drop) < achieved.fetch(:drop_percent) - end - end - - def compute_coverage_drop_data - maximum_coverage_drop.map do |criterion, percent| - { - criterion: criterion, - max_drop: percent, - drop_percent: drop_percent(criterion) - } - end - end - - def drop_percent(criterion) - drop = last_coverage(criterion) - - SimpleCov.round_coverage( - result.coverage_statistics.fetch(criterion).percent - ) - - # floats, I tell ya. - # irb(main):001:0* 80.01 - 80.0 - # => 0.010000000000005116 - drop.floor(MAX_DROP_ACCURACY) - end - - def last_coverage(criterion) - last_coverage_percent = last_run[:result][criterion] - - # fallback for old file format - last_coverage_percent = last_run[:result][:covered_percent] if !last_coverage_percent && criterion == :line - - last_coverage_percent || 0 + def violations + @violations ||= SimpleCov::CoverageViolations.maximum_drop(@result, @maximum_coverage_drop) end end end diff --git a/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb b/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb index 80820b9b..e41d74c6 100644 --- a/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +++ b/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb @@ -9,17 +9,17 @@ def initialize(result, minimum_coverage_by_file) end def failing? - minimum_violations.any? + violations.any? end def report - minimum_violations.each do |violation| + violations.each do |violation| $stderr.printf( "%s coverage by file (%.2f%%) is below the expected minimum coverage (%.2f%%) in %s.\n", - covered: SimpleCov.round_coverage(violation.fetch(:actual)), - minimum_coverage: violation.fetch(:minimum_expected), + covered: violation.fetch(:actual), + minimum_coverage: violation.fetch(:expected), criterion: violation.fetch(:criterion).capitalize, - filename: violation.fetch(:filename) + filename: violation.fetch(:project_filename) ) end end @@ -30,27 +30,8 @@ def exit_code private - attr_reader :result, :minimum_coverage_by_file - - def minimum_violations - @minimum_violations ||= - compute_minimum_coverage_data.select do |achieved| - achieved.fetch(:actual) < achieved.fetch(:minimum_expected) - end - end - - def compute_minimum_coverage_data - minimum_coverage_by_file.flat_map do |criterion, expected_percent| - result.files.map do |file| - actual_coverage = file.coverage_statistics.fetch(criterion) - { - criterion: criterion, - minimum_expected: expected_percent, - actual: SimpleCov.round_coverage(actual_coverage.percent), - filename: file.project_filename - } - end - end + def violations + @violations ||= SimpleCov::CoverageViolations.minimum_by_file(@result, @minimum_coverage_by_file) end end end diff --git a/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb b/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb index d424947a..bc6179d6 100644 --- a/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +++ b/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb @@ -9,16 +9,16 @@ def initialize(result, minimum_coverage_by_group) end def failing? - minimum_violations.any? + violations.any? end def report - minimum_violations.each do |violation| + violations.each do |violation| $stderr.printf( "%s coverage by group (%.2f%%) is below the expected minimum coverage (%.2f%%) in %s.\n", group_name: violation.fetch(:group_name), - covered: SimpleCov.round_coverage(violation.fetch(:actual)), - minimum_coverage: violation.fetch(:minimum_expected), + covered: violation.fetch(:actual), + minimum_coverage: violation.fetch(:expected), criterion: violation.fetch(:criterion).capitalize ) end @@ -30,41 +30,8 @@ def exit_code private - attr_reader :result, :minimum_coverage_by_group - - def minimum_violations - @minimum_violations ||= - compute_minimum_coverage_data.select do |achieved| - achieved.fetch(:actual) < achieved.fetch(:minimum_expected) - end - end - - def compute_minimum_coverage_data - minimum_coverage_by_group.flat_map do |group_name, minimum_group_coverage| - group = find_group(group_name) - next [] unless group - - minimum_group_coverage.map do |criterion, expected_percent| - actual_coverage = group.coverage_statistics.fetch(criterion) - minimum_coverage_hash(group_name, criterion, expected_percent, SimpleCov.round_coverage(actual_coverage.percent)) - end - end - end - - def find_group(group_name) - result.groups[group_name] || begin - warn "minimum_coverage_by_group: no group named '#{group_name}' exists. Available groups: #{result.groups.keys.join(', ')}" - nil - end - end - - def minimum_coverage_hash(group_name, criterion, minimum_expected, actual) - { - group_name: group_name, - criterion: criterion, - minimum_expected: minimum_expected, - actual: actual - } + def violations + @violations ||= SimpleCov::CoverageViolations.minimum_by_group(@result, @minimum_coverage_by_group) end end end diff --git a/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb b/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb index ea3a0ea9..c44b3157 100644 --- a/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +++ b/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb @@ -9,15 +9,15 @@ def initialize(result, minimum_coverage) end def failing? - minimum_violations.any? + violations.any? end def report - minimum_violations.each do |violation| + violations.each do |violation| $stderr.printf( "%s coverage (%.2f%%) is below the expected minimum coverage (%.2f%%).\n", - covered: SimpleCov.round_coverage(violation.fetch(:actual)), - minimum_coverage: violation.fetch(:minimum_expected), + covered: violation.fetch(:actual), + minimum_coverage: violation.fetch(:expected), criterion: violation.fetch(:criterion).capitalize ) end @@ -29,24 +29,8 @@ def exit_code private - attr_reader :result, :minimum_coverage - - def minimum_violations - @minimum_violations ||= calculate_minimum_violations - end - - def calculate_minimum_violations - coverage_achieved = minimum_coverage.map do |criterion, percent| - { - criterion: criterion, - minimum_expected: percent, - actual: result.coverage_statistics.fetch(criterion).percent - } - end - - coverage_achieved.select do |achieved| - achieved.fetch(:actual) < achieved.fetch(:minimum_expected) - end + def violations + @violations ||= SimpleCov::CoverageViolations.minimum_overall(@result, @minimum_coverage) end end end diff --git a/lib/simplecov/formatter/html_formatter/coverage_helpers.rb b/lib/simplecov/formatter/html_formatter/coverage_helpers.rb index 9a50c1ea..a2fc0dfe 100644 --- a/lib/simplecov/formatter/html_formatter/coverage_helpers.rb +++ b/lib/simplecov/formatter/html_formatter/coverage_helpers.rb @@ -47,21 +47,17 @@ def coverage_type_summary(type, label, summary, enabled:, **opts) enabled_type_summary(type, label, summary.fetch(type.to_sym), opts) end - def coverage_summary(stats, show_method_toggle: false) + def coverage_summary(source_file, show_method_toggle: false) + stats = source_file.coverage_statistics _summary = { - line: build_stats(stats.fetch(:covered_lines), stats.fetch(:total_lines)), - branch: build_stats(stats.fetch(:covered_branches, 0), stats.fetch(:total_branches, 0)), - method: build_stats(stats.fetch(:covered_methods, 0), stats.fetch(:total_methods, 0)), + line: stats[:line], + branch: stats[:branch], + method: stats[:method], show_method_toggle: show_method_toggle } template("coverage_summary").result(binding) end - def build_stats(covered, total) - pct = total.positive? ? (covered * 100.0 / total) : 100.0 - {covered: covered, total: total, missed: total - covered, pct: pct} - end - private def totals_cell_attrs(type, css) @@ -104,12 +100,12 @@ def append_method_attrs(pairs, source_file) end def enabled_type_summary(type, label, stats, opts) - css = coverage_css_class(stats.fetch(:pct)) - missed = stats.fetch(:missed) + css = coverage_css_class(stats.percent) + missed = stats.missed parts = [ %(
\n #{label}: ), - %(#{Kernel.format('%.2f', stats.fetch(:pct).floor(2))}%), - %( #{stats.fetch(:covered)}/#{stats.fetch(:total)} #{opts.fetch(:suffix, 'covered')}) + %(#{Kernel.format('%.2f', stats.percent.floor(2))}%), + %( #{stats.covered}/#{stats.total} #{opts.fetch(:suffix, 'covered')}) ] parts << missed_summary_html(missed, opts.fetch(:missed_class, "red"), opts.fetch(:toggle, false)) if missed.positive? parts << "\n
" diff --git a/lib/simplecov/formatter/html_formatter/views/source_file.erb b/lib/simplecov/formatter/html_formatter/views/source_file.erb index 48f76c41..94a3f7f5 100644 --- a/lib/simplecov/formatter/html_formatter/views/source_file.erb +++ b/lib/simplecov/formatter/html_formatter/views/source_file.erb @@ -1,11 +1,7 @@

<%= shortened_filename source_file %>

- <%= coverage_summary({ - covered_lines: source_file.covered_lines.count, total_lines: source_file.covered_lines.count + source_file.missed_lines.count, - covered_branches: branch_coverage? ? source_file.covered_branches.count : 0, total_branches: branch_coverage? ? source_file.total_branches.count : 0, - covered_methods: method_coverage? ? source_file.covered_methods.count : 0, total_methods: method_coverage? ? source_file.methods.count : 0 - }, show_method_toggle: method_coverage? && source_file.missed_methods.any?) %> + <%= coverage_summary(source_file, show_method_toggle: method_coverage? && source_file.missed_methods.any?) %> <%- if method_coverage? && source_file.missed_methods.any? -%> "), "Expected closing
, got: ...#{result[-30..]}" @@ -1012,7 +918,7 @@ def test_coverage_type_summary_ends_with_closing_div def test_coverage_type_summary_contains_label f = new_formatter_with(branch: false, method: false) - summary = {line: {pct: 100.0, covered: 100, total: 100, missed: 0}} + summary = line_summary(pct: 100.0, covered: 100, total: 100, missed: 0) result = f.send(:coverage_type_summary, "line", "Line coverage", summary, enabled: true) assert_includes result, "Line coverage:" @@ -1020,7 +926,7 @@ def test_coverage_type_summary_contains_label def test_coverage_type_summary_with_missed_includes_missed_count f = new_formatter_with(branch: false, method: false) - summary = {line: {pct: 80.0, covered: 80, total: 100, missed: 20}} + summary = line_summary(pct: 80.0, covered: 80, total: 100, missed: 20) result = f.send(:coverage_type_summary, "line", "Line coverage", summary, enabled: true) assert_includes result, "20 missed" @@ -1028,7 +934,7 @@ def test_coverage_type_summary_with_missed_includes_missed_count def test_coverage_type_summary_no_missed_when_zero f = new_formatter_with(branch: false, method: false) - summary = {line: {pct: 100.0, covered: 100, total: 100, missed: 0}} + summary = line_summary(pct: 100.0, covered: 100, total: 100, missed: 0) result = f.send(:coverage_type_summary, "line", "Line coverage", summary, enabled: true) refute_includes result, "missed" @@ -1036,7 +942,7 @@ def test_coverage_type_summary_no_missed_when_zero def test_coverage_type_summary_missed_uses_default_red_class f = new_formatter_with(branch: false, method: false) - summary = {line: {pct: 80.0, covered: 80, total: 100, missed: 20}} + summary = line_summary(pct: 80.0, covered: 80, total: 100, missed: 20) result = f.send(:coverage_type_summary, "line", "Line coverage", summary, enabled: true) assert_includes result, 'class="red"' @@ -1044,7 +950,7 @@ def test_coverage_type_summary_missed_uses_default_red_class def test_coverage_type_summary_missed_uses_custom_missed_class f = new_formatter_with(branch: false, method: false) - summary = {line: {pct: 80.0, covered: 80, total: 100, missed: 20}} + summary = line_summary(pct: 80.0, covered: 80, total: 100, missed: 20) result = f.send(:coverage_type_summary, "line", "Line coverage", summary, enabled: true, missed_class: "missed-branch-text") assert_includes result, 'class="missed-branch-text"' @@ -1053,7 +959,7 @@ def test_coverage_type_summary_missed_uses_custom_missed_class def test_coverage_type_summary_toggle_false_uses_span f = new_formatter_with(branch: false, method: false) - summary = {line: {pct: 80.0, covered: 80, total: 100, missed: 20}} + summary = line_summary(pct: 80.0, covered: 80, total: 100, missed: 20) result = f.send(:coverage_type_summary, "line", "Line coverage", summary, enabled: true, toggle: false) assert_includes result, ",) @@ -1086,7 +992,7 @@ def test_coverage_type_summary_missed_includes_comma_separator def test_coverage_type_summary_type_appears_in_enabled_div_class f = new_formatter_with(branch: false, method: false) - summary = {branch: {pct: 75.0, covered: 15, total: 20, missed: 5}} + summary = {branch: SimpleCov::CoverageStatistics.new(covered: 15, missed: 5, percent: 75.0)} result = f.send(:coverage_type_summary, "branch", "Branch coverage", summary, enabled: true) assert_includes result, "t-branch-summary" @@ -1229,31 +1135,34 @@ def new_formatter_with(branch: nil, method: nil) def render_summary(covered_lines:, total_lines:) f = SimpleCov::Formatter::HTMLFormatter.new - f.send(:coverage_summary, zero_stats(covered_lines: covered_lines, total_lines: total_lines)) + f.send(:coverage_summary, stub_source_file_with_stats(covered_lines: covered_lines, total_lines: total_lines)) end def full_stats - { - covered_lines: 80, total_lines: 100, - covered_branches: 10, total_branches: 20, - covered_methods: 5, total_methods: 10 - } + stub_source_file_with_stats(covered_lines: 80, total_lines: 100, covered_branches: 10, total_branches: 20, covered_methods: 5, total_methods: 10) end def zero_stats(covered_lines: 0, total_lines: 0) - { - covered_lines: covered_lines, total_lines: total_lines, - covered_branches: 0, total_branches: 0, - covered_methods: 0, total_methods: 0 - } + stub_source_file_with_stats(covered_lines: covered_lines, total_lines: total_lines) end def method_stats - { - covered_lines: 80, total_lines: 100, - covered_branches: 0, total_branches: 0, - covered_methods: 5, total_methods: 10 + stub_source_file_with_stats(covered_lines: 80, total_lines: 100, covered_methods: 5, total_methods: 10) + end + + def stub_source_file_with_stats(covered_lines: 0, total_lines: 0, covered_branches: 0, total_branches: 0, covered_methods: 0, total_methods: 0) # rubocop:disable Metrics/ParameterLists + stats = { + line: SimpleCov::CoverageStatistics.new(covered: covered_lines, missed: total_lines - covered_lines), + branch: SimpleCov::CoverageStatistics.new(covered: covered_branches, missed: total_branches - covered_branches), + method: SimpleCov::CoverageStatistics.new(covered: covered_methods, missed: total_methods - covered_methods) } + obj = Object.new + obj.define_singleton_method(:coverage_statistics) { stats } + obj + end + + def line_summary(pct:, covered:, missed:, **) + {line: SimpleCov::CoverageStatistics.new(covered: covered, missed: missed, percent: pct)} end def make_method_stub(start_line, end_line)