Skip to content

Commit

Permalink
Define minimum coverage by coverage criterion
Browse files Browse the repository at this point in the history
  • Loading branch information
PragTob committed Jan 27, 2020
1 parent 81b1402 commit 69dfd85
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 43 deletions.
22 changes: 20 additions & 2 deletions features/minimum_coverage.feature
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Feature:

When I run `bundle exec rake test`
Then the exit status should not be 0
And the output should contain "Coverage (88.09%) is below the expected minimum coverage (90.00%)."
And the output should contain "Line coverage (88.09%) is below the expected minimum coverage (90.00%)."
And the output should contain "SimpleCov failed with exit 2"

Scenario:
Expand All @@ -31,7 +31,7 @@ Feature:

When I run `bundle exec rake test`
Then the exit status should not be 0
And the output should contain "Coverage (88.09%) is below the expected minimum coverage (88.10%)."
And the output should contain "Line coverage (88.09%) is below the expected minimum coverage (88.10%)."
And the output should contain "SimpleCov failed with exit 2"

Scenario:
Expand All @@ -58,3 +58,21 @@ Feature:

When I run `bundle exec rake test`
Then the exit status should be 0

@branch_coverage
Scenario: Works together with branch coverage and the new criterion announcing both failures
Given SimpleCov for Test/Unit is configured with:
"""
require 'simplecov'
SimpleCov.start do
add_filter 'test.rb'
enable_coverage :branch
minimum_coverage line: 90, branch: 80
end
"""

When I run `bundle exec rake test`
Then the exit status should not be 0
And the output should contain "Line coverage (88.09%) is below the expected minimum coverage (90.00%)."
And the output should contain "Branch coverage (50.00%) is below the expected minimum coverage (80.00%)."
And the output should contain "SimpleCov failed with exit 2"
33 changes: 27 additions & 6 deletions lib/simplecov.rb
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,8 @@ def process_result(result, exit_status)
# rubocop:disable Metrics/MethodLength
def result_exit_status(result, covered_percent)
covered_percentages = result.covered_percentages.map { |percentage| percentage.floor(2) }
if covered_percent < SimpleCov.minimum_coverage
$stderr.printf(
"Coverage (%<covered>.2f%%) is below the expected minimum coverage (%<minimum_coverage>.2f%%).\n",
covered: covered_percent,
minimum_coverage: SimpleCov.minimum_coverage
)
if (minimum_violations = minimum_coverage_violated(result)).any?
report_minimum_violated(minimum_violations)
SimpleCov::ExitCodes::MINIMUM_COVERAGE
elsif covered_percentages.any? { |p| p < SimpleCov.minimum_coverage_by_file }
$stderr.printf(
Expand Down Expand Up @@ -409,6 +405,31 @@ def remove_useless_results
def result_with_not_loaded_files
@result = SimpleCov::Result.new(add_not_loaded_files(@result))
end

def minimum_coverage_violated(result)
coverage_achieved = minimum_coverage.map do |criterion, percent|
{
criterion: criterion,
minimum_expected: percent,
actual: result.coverage_statistics[criterion].percent
}
end

coverage_achieved.select do |achieved|
achieved.fetch(:actual) < achieved.fetch(:minimum_expected)
end
end

def report_minimum_violated(violations)
violations.each do |violation|
$stderr.printf(
"%<criterion>s coverage (%<covered>.2f%%) is below the expected minimum coverage (%<minimum_coverage>.2f%%).\n",
covered: violation.fetch(:actual).floor(2),
minimum_coverage: violation.fetch(:minimum_expected),
criterion: violation.fetch(:criterion).capitalize
)
end
end
end
end

Expand Down
28 changes: 22 additions & 6 deletions lib/simplecov/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,15 @@ def merge_timeout(seconds = nil)
# Default is 0% (disabled)
#
def minimum_coverage(coverage = nil)
minimum_possible_coverage_exceeded("minimum_coverage") if coverage && coverage > 100
@minimum_coverage ||= (coverage || 0).to_f.round(2)
return @minimum_coverage ||= {} unless coverage

coverage = {DEFAULT_COVERAGE_CRITERION => coverage} if coverage.is_a?(Numeric)
coverage.keys.each { |criterion| raise_if_criterion_disabled(criterion) }
coverage.values.each do |percent|
minimum_possible_coverage_exceeded("minimum_coverage") if percent && percent > 100
end

@minimum_coverage = coverage
end

#
Expand Down Expand Up @@ -362,12 +369,21 @@ def coverage_start_arguments_supported?

private

def raise_if_criterion_unsupported(criterion)
raise_criterion_unsupported(criterion) unless SUPPORTED_COVERAGE_CRITERIA.member?(criterion)
def raise_if_criterion_disabled(criterion)
raise_if_criterion_unsupported(criterion)
# rubocop:disable Style/IfUnlessModifier
unless coverage_criterion_enabled?(criterion)
raise "Coverage criterion #{criterion}, is disabled! Please enable it first through enable_coverage #{criterion} (if supported)"
end
# rubocop:enable Style/IfUnlessModifier
end

def raise_criterion_unsupported(criterion)
raise "Unsupported coverage criterion #{criterion}, supported values are #{SUPPORTED_COVERAGE_CRITERIA}"
def raise_if_criterion_unsupported(criterion)
# rubocop:disable Style/IfUnlessModifier
unless SUPPORTED_COVERAGE_CRITERIA.member?(criterion)
raise "Unsupported coverage criterion #{criterion}, supported values are #{SUPPORTED_COVERAGE_CRITERIA}"
end
# rubocop:enable Style/IfUnlessModifier
end

def minimum_possible_coverage_exceeded(coverage_option)
Expand Down
28 changes: 14 additions & 14 deletions lib/simplecov/file_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@ def initialize(files)
@files = files
end

def coverage
@coverage ||= compute_coverage
def coverage_statistics
@coverage_statistics ||= compute_coverage_statistics
end

# Returns the count of lines that have coverage
def covered_lines
coverage[:line]&.covered
coverage_statistics[:line]&.covered
end

# Returns the count of lines that have been missed
def missed_lines
coverage[:line]&.missed
coverage_statistics[:line]&.missed
end

# Returns the count of lines that are not relevant for coverage
Expand Down Expand Up @@ -64,46 +64,46 @@ def least_covered_file

# Returns the overall amount of relevant lines of code across all files in this list
def lines_of_code
coverage[:line]&.total
coverage_statistics[:line]&.total
end

# Computes the coverage based upon lines covered and lines missed
# @return [Float]
def covered_percent
coverage[:line]&.percent
coverage_statistics[:line]&.percent
end

# Computes the strength (hits / line) based upon lines covered and lines missed
# @return [Float]
def covered_strength
coverage[:line]&.strength
coverage_statistics[:line]&.strength
end

# Return total count of branches in all files
def total_branches
coverage[:branch]&.total
coverage_statistics[:branch]&.total
end

# Return total count of covered branches
def covered_branches
coverage[:branch]&.covered
coverage_statistics[:branch]&.covered
end

# Return total count of covered branches
def missed_branches
coverage[:branch]&.missed
coverage_statistics[:branch]&.missed
end

def branch_covered_percent
coverage[:branch]&.percent
coverage_statistics[:branch]&.percent
end

private

def compute_coverage
def compute_coverage_statistics
total_coverage_statistics = @files.each_with_object(line: [], branch: []) do |file, together|
together[:line] << file.coverage[:line]
together[:branch] << file.coverage[:branch] if SimpleCov.branch_coverage?
together[:line] << file.coverage_statistics[:line]
together[:branch] << file.coverage_statistics[:branch] if SimpleCov.branch_coverage?
end

coverage_statistics = {line: CoverageStatistics.from(total_coverage_statistics[:line])}
Expand Down
2 changes: 1 addition & 1 deletion lib/simplecov/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
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_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
Expand Down
20 changes: 10 additions & 10 deletions lib/simplecov/source_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ def src
end
alias source src

def coverage
@coverage ||=
def coverage_statistics
@coverage_statistics ||=
{
**line_coverage,
**branch_coverage
**line_coverage_statistics,
**branch_coverage_statistics
}
end

Expand Down Expand Up @@ -68,7 +68,7 @@ def skipped_lines

# Returns the number of relevant lines (covered + missed)
def lines_of_code
coverage[:line]&.total
coverage_statistics[:line]&.total
end

# Access SimpleCov::SourceFile::Line source lines by line number
Expand All @@ -78,11 +78,11 @@ def line(number)

# The coverage for this file in percent. 0 if the file has no coverage lines
def covered_percent
coverage[:line]&.percent
coverage_statistics[:line]&.percent
end

def covered_strength
coverage[:line]&.strength
coverage_statistics[:line]&.strength
end

def no_lines?
Expand All @@ -104,7 +104,7 @@ def no_branches?
end

def branches_coverage_percent
coverage[:branch]&.percent
coverage_statistics[:branch]&.percent
end

#
Expand Down Expand Up @@ -278,7 +278,7 @@ def build_branch(branch_data, hit_count, condition_start_line)
)
end

def line_coverage
def line_coverage_statistics
{
line: CoverageStatistics.new(
total_strength: lines_strength,
Expand All @@ -288,7 +288,7 @@ def line_coverage
}
end

def branch_coverage
def branch_coverage_statistics
{
branch: CoverageStatistics.new(
covered: covered_branches.size,
Expand Down
42 changes: 42 additions & 0 deletions spec/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
end

describe "#minimum_coverage" do
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)
Expand All @@ -57,6 +61,44 @@
expect(config).to receive(:warn).with("The coverage you set for minimum_coverage is greater than 100%")
config.minimum_coverage(100.01)
end

it "sets the right converage value when called with a number" do
config.minimum_coverage(80)

expect(config.minimum_coverage).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

expect(config.minimum_coverage).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

expect(config.minimum_coverage).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

expect(config.minimum_coverage).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
end.to raise_error(/branch.*disabled/i)
end

it "raises when unknown coverage criteria provided" do
expect do
config.minimum_coverage unknown: 42
end.to raise_error(/unsupported.*unknown/i)
end
end

describe "#minimum_coverage_by_file" do
Expand Down
26 changes: 22 additions & 4 deletions spec/simplecov_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,17 +193,17 @@
end

describe ".process_result" do
context "when minimum coverage is 100%" do
let(:result) { SimpleCov::Result.new({}) }
let(:result) { SimpleCov::Result.new({}) }

context "when minimum coverage is 100%" do
before do
allow(SimpleCov).to receive(:minimum_coverage).and_return(100)
allow(SimpleCov).to receive(:minimum_coverage).and_return(line: 100)
allow(SimpleCov).to receive(:result?).and_return(true)
end

context "when actual coverage is almost 100%" do
before do
allow(result).to receive(:covered_percent).and_return(100 * 32_847.0 / 32_848)
allow(result).to receive(:coverage_statistics).and_return(line: double("statistics", percent: 100 * 32_847.0 / 32_848))
end

it "return SimpleCov::ExitCodes::MINIMUM_COVERAGE" do
Expand All @@ -216,6 +216,9 @@
context "when actual coverage is exactly 100%" do
before do
allow(result).to receive(:covered_percent).and_return(100.0)
allow(result).to receive(:coverage_statistics).and_return(
line: double("statistics", percent: 100.0)
)
allow(result).to receive(:covered_percentages).and_return([])
allow(SimpleCov::LastRun).to receive(:read).and_return(nil)
end
Expand All @@ -227,6 +230,21 @@
end
end
end

context "branch coverage" do
before do
allow(SimpleCov).to receive(:minimum_coverage).and_return(branch: 90)
allow(SimpleCov).to receive(:result?).and_return(true)
end

it "errors out when the coverage is too low" do
allow(result).to receive(:coverage_statistics).and_return(branch: double("statistics", percent: 89.99))

expect(
SimpleCov.process_result(result, SimpleCov::ExitCodes::SUCCESS)
).to eq(SimpleCov::ExitCodes::MINIMUM_COVERAGE)
end
end
end
end

Expand Down

0 comments on commit 69dfd85

Please sign in to comment.