From 6519bf17a0274325879b330acb0d6dee3ce02e9b Mon Sep 17 00:00:00 2001 From: Jemma Issroff Date: Wed, 7 Oct 2020 15:46:56 -0400 Subject: [PATCH 1/3] Implement branch coverage support for exit status modifiers --- CHANGELOG.md | 6 +++ README.md | 18 +++++--- features/maximum_coverage_drop.feature | 10 ++--- features/refuse_coverage_drop.feature | 8 ++-- lib/simplecov.rb | 5 ++- lib/simplecov/configuration.rb | 35 ++++++++++----- .../exit_codes/maximum_coverage_drop_check.rb | 36 ++++++++++----- .../minimum_coverage_by_file_check.rb | 43 +++++++++++++----- lib/simplecov/file_list.rb | 14 ++++-- lib/simplecov/result.rb | 2 +- spec/configuration_spec.rb | 44 +++++++++---------- 11 files changed, 145 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f43addf1..5958b1e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Unreleased +========== + +## Enhancements +* Can now define the minimum_coverage_by_file, maximum_coverage_drop and refuse_coverage_drop by branch as well as line + 0.20.0 (2020-11-29) ========== diff --git a/README.md b/README.md index f3c5260e..9ec07f49 100644 --- a/README.md +++ b/README.md @@ -787,30 +787,36 @@ to help ensure coverage is relatively consistent, rather than being skewed by pa ```ruby SimpleCov.minimum_coverage_by_file 80 +# same as above (the default is to check line coverage by file) +SimpleCov.minimum_coverage_by_file line: 80 +# check for a minimum line coverage by file of 90% and minimum 80% branch coverage +SimpleCov.minimum_coverage_by_file line: 90, branch: 80 ``` -(not yet supported for branch coverage) - ### Maximum coverage drop You can define the maximum coverage drop percentage at once. SimpleCov will return non-zero if exceeded. ```ruby SimpleCov.maximum_coverage_drop 5 +# same as above (the default is to check line drop) +SimpleCov.maximum_coverage_drop line: 5 +# check for a maximum line drop of 5% and maximum 10% branch drop +SimpleCov.maximum_coverage_drop line: 5, branch: 10 ``` -(not yet supported for branch coverage) - ### Refuse dropping coverage You can also entirely refuse dropping coverage between test runs: ```ruby SimpleCov.refuse_coverage_drop +# same as above (the default is to only refuse line drop) +SimpleCov.refuse_coverage_drop :line +# refuse drop for line and branch +SimpleCov.refuse_coverage_drop :line, :branch ``` -(not yet supported for branch coverage) - ## Using your own formatter You can use your own formatter with: diff --git a/features/maximum_coverage_drop.feature b/features/maximum_coverage_drop.feature index 66a3b4a2..d4cac399 100644 --- a/features/maximum_coverage_drop.feature +++ b/features/maximum_coverage_drop.feature @@ -40,7 +40,7 @@ Feature: """ { "result": { - "covered_percent": 88.09 + "line": 88.09 } } """ @@ -61,7 +61,7 @@ Feature: """ { "result": { - "covered_percent": 88.09 + "line": 88.09 } } """ @@ -84,7 +84,7 @@ Feature: """ { "result": { - "covered_percent": 84.78 + "line": 84.78 } } """ @@ -123,7 +123,7 @@ Feature: """ { "result": { - "covered_percent": 100.0 + "line": 100.0 } } """ @@ -135,7 +135,7 @@ Feature: """ { "result": { - "covered_percent": 100.0 + "line": 100.0 } } """ diff --git a/features/refuse_coverage_drop.feature b/features/refuse_coverage_drop.feature index f4be179b..c25c847a 100644 --- a/features/refuse_coverage_drop.feature +++ b/features/refuse_coverage_drop.feature @@ -24,7 +24,7 @@ Feature: """ { "result": { - "covered_percent": 88.09 + "line": 88.09 } } """ @@ -48,7 +48,7 @@ Feature: """ { "result": { - "covered_percent": 88.09 + "line": 88.09 } } """ @@ -69,7 +69,7 @@ Feature: """ { "result": { - "covered_percent": 88.09 + "line": 88.09 } } """ @@ -92,7 +92,7 @@ Feature: """ { "result": { - "covered_percent": 84.78 + "line": 84.78 } } """ diff --git a/lib/simplecov.rb b/lib/simplecov.rb index 4d4ccfff..5391b163 100644 --- a/lib/simplecov.rb +++ b/lib/simplecov.rb @@ -285,7 +285,10 @@ def wait_for_other_processes # @api private # def write_last_run(result) - SimpleCov::LastRun.write(result: {covered_percent: round_coverage(result.covered_percent)}) + SimpleCov::LastRun.write(result: + result.coverage_statistics.transform_values do |stats| + round_coverage(stats.percent) + end) end # diff --git a/lib/simplecov/configuration.rb b/lib/simplecov/configuration.rb index d04f7544..db47ae9c 100644 --- a/lib/simplecov/configuration.rb +++ b/lib/simplecov/configuration.rb @@ -287,20 +287,22 @@ def merge_timeout(seconds = nil) # # Default is 0% (disabled) # - - # rubocop:disable Metrics/CyclomaticComplexity def minimum_coverage(coverage = nil) return @minimum_coverage ||= {} unless coverage coverage = {DEFAULT_COVERAGE_CRITERION => coverage} if coverage.is_a?(Numeric) + + validate_coverage!(coverage, "minimum_coverage") + + @minimum_coverage = coverage + end + + def validate_coverage!(coverage, coverage_setting) coverage.each_key { |criterion| raise_if_criterion_disabled(criterion) } coverage.each_value do |percent| - minimum_possible_coverage_exceeded("minimum_coverage") if percent && percent > 100 + minimum_possible_coverage_exceeded(coverage_setting) if percent && percent > 100 end - - @minimum_coverage = coverage end - # rubocop:enable Metrics/CyclomaticComplexity # # Defines the maximum coverage drop at once allowed for the testsuite to pass. @@ -309,7 +311,13 @@ def minimum_coverage(coverage = nil) # Default is 100% (disabled) # def maximum_coverage_drop(coverage_drop = nil) - @maximum_coverage_drop ||= (coverage_drop || 100).to_f.round(2) + return @maximum_coverage_drop ||= {} unless coverage_drop + + coverage_drop = {DEFAULT_COVERAGE_CRITERION => coverage_drop} if coverage_drop.is_a?(Numeric) + + validate_coverage!(coverage_drop, "maximum_coverage_drop") + + @maximum_coverage_drop = coverage_drop end # @@ -320,16 +328,21 @@ def maximum_coverage_drop(coverage_drop = nil) # Default is 0% (disabled) # def minimum_coverage_by_file(coverage = nil) - minimum_possible_coverage_exceeded("minimum_coverage_by_file") if coverage && coverage > 100 - @minimum_coverage_by_file ||= (coverage || 0).to_f.round(2) + return @minimum_coverage_by_file ||= {} unless coverage + + coverage = {DEFAULT_COVERAGE_CRITERION => coverage} if coverage.is_a?(Numeric) + + validate_coverage!(coverage, "minimum_coverage_by_file") + + @minimum_coverage_by_file = coverage end # # Refuses any coverage drop. That is, coverage is only allowed to increase. # SimpleCov will return non-zero if the coverage decreases. # - def refuse_coverage_drop - maximum_coverage_drop 0 + def refuse_coverage_drop(criteria = [DEFAULT_COVERAGE_CRITERION]) + maximum_coverage_drop(criteria.map { |c| [c, 0] }.to_h) end # diff --git a/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb b/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb index 4be52044..bd0d195e 100644 --- a/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +++ b/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb @@ -11,15 +11,17 @@ def initialize(result, maximum_coverage_drop) def failing? return false unless maximum_coverage_drop && last_run - coverage_diff > maximum_coverage_drop + coverage_drop_violations.any? end def report - $stderr.printf( - "Coverage has dropped by %.2f%% since the last time (maximum allowed: %.2f%%).\n", - drop_percent: coverage_diff, - max_drop: maximum_coverage_drop - ) + coverage_drop_violations.each do |violation| + $stderr.printf( + "Coverage has dropped by %.2f%% since the last time (maximum allowed: %.2f%%).\n", + drop_percent: SimpleCov.round_coverage(violation[:drop_percent]), + max_drop: violation[:max_drop] + ) + end end def exit_code @@ -36,14 +38,24 @@ def last_run @last_run = SimpleCov::LastRun.read end - def coverage_diff - raise "Trying to access coverage_diff although there is no last run" unless last_run - - @coverage_diff ||= last_run[:result][:covered_percent] - covered_percent + def coverage_drop_violations + @coverage_drop_violations ||= + compute_coverage_drop_violations.select do |achieved| + achieved.fetch(:max_drop) < achieved.fetch(:drop_percent) + end end - def covered_percent - SimpleCov.round_coverage(result.covered_percent) + def compute_coverage_drop_violations + maximum_coverage_drop.map do |criterion, percent| + { + criterion: criterion, + max_drop: percent, + drop_percent: last_run[:result][criterion] - + SimpleCov.round_coverage( + result.coverage_statistics.fetch(criterion).percent + ) + } + end 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 15aef13a..696572f2 100644 --- a/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +++ b/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb @@ -9,16 +9,18 @@ def initialize(result, minimum_coverage_by_file) end def failing? - covered_percentages.any? { |p| p < minimum_coverage_by_file } + minimum_violations.any? end def report - $stderr.printf( - "File (%s) is only (%.2f%%) covered. This is below the expected minimum coverage per file of (%.2f%%).\n", - file: result.least_covered_file, - least_covered_percentage: covered_percentages.min, - min_coverage: minimum_coverage_by_file - ) + minimum_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), + criterion: violation.fetch(:criterion).capitalize + ) + end end def exit_code @@ -29,9 +31,30 @@ def exit_code attr_reader :result, :minimum_coverage_by_file - def covered_percentages - @covered_percentages ||= - result.covered_percentages.map { |percentage| SimpleCov.round_coverage(percentage) } + def coverage_statistics_by_file + @coverage_statistics_by_file ||= + (res = result.coverage_statistics_by_file).each do |criteria, stats| + res[criteria] = stats.map { |stat| SimpleCov.round_coverage(stat.percent) } + end + end + + def minimum_violations + @minimum_violations ||= + compute_minimum_violations.select do |achieved| + achieved.fetch(:actual) < achieved.fetch(:minimum_expected) + end + end + + def compute_minimum_violations + minimum_coverage_by_file.flat_map do |criterion, expected_percent| + coverage_statistics_by_file[criterion].map do |actual_percent| + { + criterion: criterion, + minimum_expected: expected_percent, + actual: actual_percent + } + end + end end end end diff --git a/lib/simplecov/file_list.rb b/lib/simplecov/file_list.rb index d4c9bba9..20f466f7 100644 --- a/lib/simplecov/file_list.rb +++ b/lib/simplecov/file_list.rb @@ -27,6 +27,10 @@ def coverage_statistics @coverage_statistics ||= compute_coverage_statistics end + def coverage_statistics_by_file + @coverage_statistics_by_file ||= compute_coverage_statistics_by_file + end + # Returns the count of lines that have coverage def covered_lines coverage_statistics[:line]&.covered @@ -100,14 +104,16 @@ def branch_covered_percent private - def compute_coverage_statistics - total_coverage_statistics = @files.each_with_object(line: [], branch: []) do |file, together| + def compute_coverage_statistics_by_file + @files.each_with_object(line: [], branch: []) do |file, together| together[:line] << file.coverage_statistics[:line] together[:branch] << file.coverage_statistics[:branch] if SimpleCov.branch_coverage? end + end - coverage_statistics = {line: CoverageStatistics.from(total_coverage_statistics[:line])} - coverage_statistics[:branch] = CoverageStatistics.from(total_coverage_statistics[:branch]) if SimpleCov.branch_coverage? + def compute_coverage_statistics + coverage_statistics = {line: CoverageStatistics.from(coverage_statistics_by_file[:line])} + coverage_statistics[:branch] = CoverageStatistics.from(coverage_statistics_by_file[:branch]) if SimpleCov.branch_coverage? coverage_statistics end end diff --git a/lib/simplecov/result.rb b/lib/simplecov/result.rb index 7eb4f8ff..21dcbaf4 100644 --- a/lib/simplecov/result.rb +++ b/lib/simplecov/result.rb @@ -20,7 +20,7 @@ class Result # Explicitly set the command name that was used for this coverage result. Defaults to SimpleCov.command_name attr_writer :command_name - def_delegators :files, :covered_percent, :covered_percentages, :least_covered_file, :covered_strength, :covered_lines, :missed_lines, :total_branches, :covered_branches, :missed_branches, :coverage_statistics + def_delegators :files, :covered_percent, :covered_percentages, :least_covered_file, :covered_strength, :covered_lines, :missed_lines, :total_branches, :covered_branches, :missed_branches, :coverage_statistics, :coverage_statistics_by_file def_delegator :files, :lines_of_code, :total_lines # Initialize a new SimpleCov::Result from given Coverage.result (a Hash of filenames each containing an array of diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index 3c6eb3ab..b8f5392c 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -47,70 +47,70 @@ end end - describe "#minimum_coverage" do + shared_examples "checks coverage settings" do |coverage_setting| after :each do config.clear_coverage_criteria end it "does not warn you about your usage" do expect(config).not_to receive(:warn) - config.minimum_coverage(100.00) + config.send(coverage_setting, 100.00) end it "warns you about your usage" do - expect(config).to receive(:warn).with("The coverage you set for minimum_coverage is greater than 100%") - config.minimum_coverage(100.01) + expect(config).to receive(:warn).with("The coverage you set for #{coverage_setting} is greater than 100%") + config.send(coverage_setting, 100.01) end it "sets the right coverage value when called with a number" do - config.minimum_coverage(80) + config.send(coverage_setting, 80) - expect(config.minimum_coverage).to eq line: 80 + expect(config.send(coverage_setting)).to eq line: 80 end it "sets the right coverage when called with a hash of just line" do - config.minimum_coverage line: 85.0 + config.send(coverage_setting, {line: 85.0}) - expect(config.minimum_coverage).to eq line: 85.0 + expect(config.send(coverage_setting)).to eq line: 85.0 end it "sets the right coverage when called with a hash of just branch" do config.enable_coverage :branch - config.minimum_coverage branch: 85.0 + config.send(coverage_setting, {branch: 85.0}) - expect(config.minimum_coverage).to eq branch: 85.0 + expect(config.send(coverage_setting)).to eq branch: 85.0 end it "sets the right coverage when called withboth line and branch" do config.enable_coverage :branch - config.minimum_coverage branch: 85.0, line: 95.4 + config.send(coverage_setting, {branch: 85.0, line: 95.4}) - expect(config.minimum_coverage).to eq branch: 85.0, line: 95.4 + expect(config.send(coverage_setting)).to eq branch: 85.0, line: 95.4 end it "raises when trying to set branch coverage but not enabled" do expect do - config.minimum_coverage branch: 42 + config.send(coverage_setting, {branch: 42}) end.to raise_error(/branch.*disabled/i) end it "raises when unknown coverage criteria provided" do expect do - config.minimum_coverage unknown: 42 + config.send(coverage_setting, {unknown: 42}) end.to raise_error(/unsupported.*unknown/i) end end + describe "#minimum_coverage" do + it_behaves_like "checks coverage settings", :minimum_coverage + end + describe "#minimum_coverage_by_file" do - it "does not warn you about your usage" do - expect(config).not_to receive(:warn) - config.minimum_coverage_by_file(100.00) - end + it_behaves_like "checks coverage settings", :minimum_coverage_by_file + end - it "warns you about your usage" do - expect(config).to receive(:warn).with("The coverage you set for minimum_coverage_by_file is greater than 100%") - config.minimum_coverage_by_file(100.01) - end + describe "#maximum_coverage_drop" do + it_behaves_like "checks coverage settings", :maximum_coverage_drop end describe "#coverage_criterion" do From 63ef5066503df91d06e243689bf625cb11e8ee7c Mon Sep 17 00:00:00 2001 From: Jemma Issroff Date: Tue, 15 Dec 2020 09:21:25 -0500 Subject: [PATCH 2/3] Round 1 changes --- CHANGELOG.md | 2 +- features/maximum_coverage_drop.feature | 131 +++++++++++++++++- lib/simplecov/configuration.rb | 17 ++- .../exit_codes/maximum_coverage_drop_check.rb | 18 ++- .../minimum_coverage_by_file_check.rb | 15 +- spec/configuration_spec.rb | 66 ++++++--- 6 files changed, 210 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5958b1e7..12db571a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Unreleased ========== ## Enhancements -* Can now define the minimum_coverage_by_file, maximum_coverage_drop and refuse_coverage_drop by branch as well as line +* Can now define the minimum_coverage_by_file, maximum_coverage_drop and refuse_coverage_drop by branch as well as line coverage 0.20.0 (2020-11-29) ========== diff --git a/features/maximum_coverage_drop.feature b/features/maximum_coverage_drop.feature index d4cac399..532b48b1 100644 --- a/features/maximum_coverage_drop.feature +++ b/features/maximum_coverage_drop.feature @@ -7,7 +7,7 @@ Feature: Background: Given I'm working on the project "faked_project" - Scenario: maximum_coverage_drop configured cam cause spec failure + Scenario: maximum_coverage_drop configured can cause spec failure Given SimpleCov for Test/Unit is configured with: """ require 'simplecov' @@ -34,7 +34,7 @@ Feature: When I run `bundle exec rake test` Then the exit status should not be 0 - And the output should contain "Coverage has dropped by 3.31% since the last time (maximum allowed: 3.14%)." + And the output should contain "Line coverage has dropped by 3.31% since the last time (maximum allowed: 3.14%)." And a file named "coverage/.last_run.json" should exist And the file "coverage/.last_run.json" should contain: """ @@ -139,3 +139,130 @@ Feature: } } """ + Scenario: When the previous last_run file has covered_percent + Given SimpleCov for Test/Unit is configured with: + """ + require 'simplecov' + SimpleCov.start do + add_filter 'test.rb' + end + """ + And the file "coverage/.last_run.json" with: + """ + { + "result": { + "covered_percent": 88.09 + } + } + """ + + When I run `bundle exec rake test` + Then the exit status should be 0 + And a file named "coverage/.last_run.json" should exist + And the file "coverage/.last_run.json" should contain: + """ + { + "result": { + "line": 88.09 + } + } + """ + + Scenario: When the previous last_run file has covered_percent does not update resultset + Given SimpleCov for RSpec is configured with: + """ + require 'simplecov' + SimpleCov.start do + add_group 'Libs', 'lib/faked_project/' + add_filter '/spec/' + maximum_coverage_drop 0 + end + """ + + And a file named "lib/faked_project/missed.rb" with: + """ + class UncoveredSourceCode + def foo + never_reached + rescue => err + but no one cares about invalid ruby here + end + end + """ + + And a file named "spec/failing_spec.rb" with: + """ + require "spec_helper" + describe FakedProject do + it "fails" do + expect(false).to eq(true) + end + end + """ + And the file named "coverage/.last_run.json" with: + """ + { + "result": { + "covered_percent": 100.0 + } + } + """ + + When I run `bundle exec rspec spec` + Then the exit status should be 1 + And a file named "coverage/.last_run.json" should exist + And the file "coverage/.last_run.json" should contain: + """ + { + "result": { + "covered_percent": 100.0 + } + } + """ + + @branch_coverage + Scenario: Works together with branch coverage and line coverage, announcing both failures + Given SimpleCov for Test/Unit is configured with: + """ + require 'simplecov' + SimpleCov.start do + add_filter 'test.rb' + enable_coverage :branch + maximum_coverage_drop line: 0, branch: 0 + end + """ + And a file named "lib/faked_project/missed.rb" with: + """ + class UncoveredSourceCode + def foo + never_reached + rescue => err + but no one cares about invalid ruby here + end + end + """ + + And a file named "spec/failing_spec.rb" with: + """ + require "spec_helper" + describe FakedProject do + it "fails" do + false ? true : expect(false).to eq(true) + end + end + """ + And the file named "coverage/.last_run.json" with: + """ + { + "result": { + "line": 100.0, + "branch": 100.0 + } + } + """ + + When I run `bundle exec rake test` + Then the exit status should not be 0 + And the output should contain "Line coverage has dropped by 15.22% since the last time (maximum allowed: 0.00%)." + And the output should contain "Branch coverage has dropped by 50.00% since the last time (maximum allowed: 0.00%)." + And the output should contain "SimpleCov failed with exit 3" diff --git a/lib/simplecov/configuration.rb b/lib/simplecov/configuration.rb index db47ae9c..b195c76d 100644 --- a/lib/simplecov/configuration.rb +++ b/lib/simplecov/configuration.rb @@ -292,12 +292,12 @@ def minimum_coverage(coverage = nil) coverage = {DEFAULT_COVERAGE_CRITERION => coverage} if coverage.is_a?(Numeric) - validate_coverage!(coverage, "minimum_coverage") + raise_on_invalid_coverage(coverage, "minimum_coverage") @minimum_coverage = coverage end - def validate_coverage!(coverage, coverage_setting) + def raise_on_invalid_coverage(coverage, coverage_setting) coverage.each_key { |criterion| raise_if_criterion_disabled(criterion) } coverage.each_value do |percent| minimum_possible_coverage_exceeded(coverage_setting) if percent && percent > 100 @@ -315,7 +315,7 @@ def maximum_coverage_drop(coverage_drop = nil) coverage_drop = {DEFAULT_COVERAGE_CRITERION => coverage_drop} if coverage_drop.is_a?(Numeric) - validate_coverage!(coverage_drop, "maximum_coverage_drop") + raise_on_invalid_coverage(coverage_drop, "maximum_coverage_drop") @maximum_coverage_drop = coverage_drop end @@ -332,7 +332,7 @@ def minimum_coverage_by_file(coverage = nil) coverage = {DEFAULT_COVERAGE_CRITERION => coverage} if coverage.is_a?(Numeric) - validate_coverage!(coverage, "minimum_coverage_by_file") + raise_on_invalid_coverage(coverage, "minimum_coverage_by_file") @minimum_coverage_by_file = coverage end @@ -341,7 +341,14 @@ def minimum_coverage_by_file(coverage = nil) # Refuses any coverage drop. That is, coverage is only allowed to increase. # SimpleCov will return non-zero if the coverage decreases. # - def refuse_coverage_drop(criteria = [DEFAULT_COVERAGE_CRITERION]) + def refuse_coverage_drop(*criteria) + if criteria.empty? + SUPPORTED_COVERAGE_CRITERIA.each do |coverage_criteria| + enable_coverage(coverage_criteria) + end + criteria = SUPPORTED_COVERAGE_CRITERIA + end + maximum_coverage_drop(criteria.map { |c| [c, 0] }.to_h) end diff --git a/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb b/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb index bd0d195e..4569b41a 100644 --- a/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +++ b/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb @@ -4,6 +4,7 @@ module SimpleCov module ExitCodes class MaximumCoverageDropCheck def initialize(result, maximum_coverage_drop) + puts "REPORTING*****" @result = result @maximum_coverage_drop = maximum_coverage_drop end @@ -17,7 +18,8 @@ def failing? def report coverage_drop_violations.each do |violation| $stderr.printf( - "Coverage has dropped by %.2f%% since the last time (maximum allowed: %.2f%%).\n", + "%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] ) @@ -40,23 +42,31 @@ def last_run def coverage_drop_violations @coverage_drop_violations ||= - compute_coverage_drop_violations.select do |achieved| + compute_coverage_drop_data.select do |achieved| achieved.fetch(:max_drop) < achieved.fetch(:drop_percent) end end - def compute_coverage_drop_violations + def compute_coverage_drop_data maximum_coverage_drop.map do |criterion, percent| { criterion: criterion, max_drop: percent, - drop_percent: last_run[:result][criterion] - + drop_percent: last_coverage(criterion) - SimpleCov.round_coverage( result.coverage_statistics.fetch(criterion).percent ) } end end + + def last_coverage(criterion) + last_run[:result][criterion].tap do |last_coverage_percent| + if !last_coverage_percent && criterion == "line" + return last_run[:result][:covered_percent] + end + end + end 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 696572f2..0837d9b4 100644 --- a/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +++ b/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb @@ -31,27 +31,20 @@ def exit_code attr_reader :result, :minimum_coverage_by_file - def coverage_statistics_by_file - @coverage_statistics_by_file ||= - (res = result.coverage_statistics_by_file).each do |criteria, stats| - res[criteria] = stats.map { |stat| SimpleCov.round_coverage(stat.percent) } - end - end - def minimum_violations @minimum_violations ||= - compute_minimum_violations.select do |achieved| + compute_minimum_coverage_data.select do |achieved| achieved.fetch(:actual) < achieved.fetch(:minimum_expected) end end - def compute_minimum_violations + def compute_minimum_coverage_data minimum_coverage_by_file.flat_map do |criterion, expected_percent| - coverage_statistics_by_file[criterion].map do |actual_percent| + result.coverage_statistics_by_file[criterion].map do |actual_percent| { criterion: criterion, minimum_expected: expected_percent, - actual: actual_percent + actual: SimpleCov.round_coverage(actual_percent) } end end diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index b8f5392c..651869d7 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -47,70 +47,104 @@ end end - shared_examples "checks coverage settings" do |coverage_setting| + shared_examples "setting coverage expectations" do |coverage_setting| after :each do config.clear_coverage_criteria end it "does not warn you about your usage" do expect(config).not_to receive(:warn) - config.send(coverage_setting, 100.00) + config.public_send(coverage_setting, 100.00) end it "warns you about your usage" do expect(config).to receive(:warn).with("The coverage you set for #{coverage_setting} is greater than 100%") - config.send(coverage_setting, 100.01) + config.public_send(coverage_setting, 100.01) end it "sets the right coverage value when called with a number" do - config.send(coverage_setting, 80) + config.public_send(coverage_setting, 80) - expect(config.send(coverage_setting)).to eq line: 80 + expect(config.public_send(coverage_setting)).to eq line: 80 end it "sets the right coverage when called with a hash of just line" do - config.send(coverage_setting, {line: 85.0}) + config.public_send(coverage_setting, {line: 85.0}) - expect(config.send(coverage_setting)).to eq line: 85.0 + expect(config.public_send(coverage_setting)).to eq line: 85.0 end it "sets the right coverage when called with a hash of just branch" do config.enable_coverage :branch - config.send(coverage_setting, {branch: 85.0}) + config.public_send(coverage_setting, {branch: 85.0}) - expect(config.send(coverage_setting)).to eq branch: 85.0 + expect(config.public_send(coverage_setting)).to eq branch: 85.0 end it "sets the right coverage when called withboth line and branch" do config.enable_coverage :branch - config.send(coverage_setting, {branch: 85.0, line: 95.4}) + config.public_send(coverage_setting, {branch: 85.0, line: 95.4}) - expect(config.send(coverage_setting)).to eq branch: 85.0, line: 95.4 + expect(config.public_send(coverage_setting)).to eq branch: 85.0, line: 95.4 end it "raises when trying to set branch coverage but not enabled" do expect do - config.send(coverage_setting, {branch: 42}) + config.public_send(coverage_setting, {branch: 42}) end.to raise_error(/branch.*disabled/i) end it "raises when unknown coverage criteria provided" do expect do - config.send(coverage_setting, {unknown: 42}) + config.public_send(coverage_setting, {unknown: 42}) end.to raise_error(/unsupported.*unknown/i) end end describe "#minimum_coverage" do - it_behaves_like "checks coverage settings", :minimum_coverage + it_behaves_like "setting coverage expectations", :minimum_coverage end describe "#minimum_coverage_by_file" do - it_behaves_like "checks coverage settings", :minimum_coverage_by_file + it_behaves_like "setting coverage expectations", :minimum_coverage_by_file end describe "#maximum_coverage_drop" do - it_behaves_like "checks coverage settings", :maximum_coverage_drop + it_behaves_like "setting coverage expectations", :maximum_coverage_drop + end + + describe "#refuse_coverage_drop" do + after :each do + config.clear_coverage_criteria + end + + it "sets the right coverage value when called with `:line`" do + config.public_send(:refuse_coverage_drop, :line) + + expect(config.public_send(:maximum_coverage_drop)).to eq line: 0 + end + + it "sets the right coverage value when called with `:branch`" do + config.enable_coverage :branch + config.public_send(:refuse_coverage_drop, :branch) + + expect(config.public_send(:maximum_coverage_drop)).to eq branch: 0 + end + + it "sets the right coverage value when called with `:line` and `:branch`" do + config.enable_coverage :branch + config.public_send(:refuse_coverage_drop, :line, :branch) + + expect(config.public_send(:maximum_coverage_drop)). + to eq line: 0, branch: 0 + end + + it "sets the right coverage value when called with no args" do + config.public_send(:refuse_coverage_drop) + + expect(config.public_send(:maximum_coverage_drop)). + to eq line: 0, branch: 0 + end end describe "#coverage_criterion" do From 6722a7c1910f27bac237e991af2b0a1713c59f46 Mon Sep 17 00:00:00 2001 From: Jemma Issroff Date: Sun, 20 Dec 2020 12:27:41 -0500 Subject: [PATCH 3/3] Round 2 changes --- features/refuse_coverage_drop.feature | 2 +- lib/simplecov/configuration.rb | 7 +------ .../exit_codes/maximum_coverage_drop_check.rb | 11 ++++++----- spec/configuration_spec.rb | 2 +- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/features/refuse_coverage_drop.feature b/features/refuse_coverage_drop.feature index c25c847a..acc61a1a 100644 --- a/features/refuse_coverage_drop.feature +++ b/features/refuse_coverage_drop.feature @@ -42,7 +42,7 @@ Feature: When I run `bundle exec rake test` Then the exit status should not be 0 - And the output should contain "Coverage has dropped by 3.31% since the last time (maximum allowed: 0.00%)." + And the output should contain "Line coverage has dropped by 3.31% since the last time (maximum allowed: 0.00%)." And a file named "coverage/.last_run.json" should exist And the file "coverage/.last_run.json" should contain: """ diff --git a/lib/simplecov/configuration.rb b/lib/simplecov/configuration.rb index b195c76d..cb144e9a 100644 --- a/lib/simplecov/configuration.rb +++ b/lib/simplecov/configuration.rb @@ -342,12 +342,7 @@ def minimum_coverage_by_file(coverage = nil) # SimpleCov will return non-zero if the coverage decreases. # def refuse_coverage_drop(*criteria) - if criteria.empty? - SUPPORTED_COVERAGE_CRITERIA.each do |coverage_criteria| - enable_coverage(coverage_criteria) - end - criteria = SUPPORTED_COVERAGE_CRITERIA - end + criteria = coverage_criteria if criteria.empty? maximum_coverage_drop(criteria.map { |c| [c, 0] }.to_h) end diff --git a/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb b/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb index 4569b41a..392ba890 100644 --- a/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +++ b/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb @@ -4,7 +4,6 @@ module SimpleCov module ExitCodes class MaximumCoverageDropCheck def initialize(result, maximum_coverage_drop) - puts "REPORTING*****" @result = result @maximum_coverage_drop = maximum_coverage_drop end @@ -61,10 +60,12 @@ def compute_coverage_drop_data end def last_coverage(criterion) - last_run[:result][criterion].tap do |last_coverage_percent| - if !last_coverage_percent && criterion == "line" - return last_run[:result][:covered_percent] - end + last_coverage_percent = last_run[:result][criterion] + + if !last_coverage_percent && criterion == "line" + last_run[:result][:covered_percent] + else + last_coverage_percent end end end diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index 651869d7..99132073 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -143,7 +143,7 @@ config.public_send(:refuse_coverage_drop) expect(config.public_send(:maximum_coverage_drop)). - to eq line: 0, branch: 0 + to eq line: 0 end end