Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mikhliuk-k committed Aug 29, 2024
1 parent c7102e4 commit 37f41fc
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 5 deletions.
4 changes: 2 additions & 2 deletions lib/simplecov.rb
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,11 @@ def process_result(result)
end

# @api private
CoverageLimits = Struct.new(:minimum_coverage, :minimum_coverage_by_file, :maximum_coverage_drop, keyword_init: true)
CoverageLimits = Struct.new(:minimum_coverage, :minimum_coverage_by_file, :minimum_coverage_by_group, :maximum_coverage_drop, keyword_init: true)
def result_exit_status(result)
coverage_limits = CoverageLimits.new(
minimum_coverage: minimum_coverage, minimum_coverage_by_file: minimum_coverage_by_file,
maximum_coverage_drop: maximum_coverage_drop
minimum_coverage_by_group: minimum_coverage_by_group, maximum_coverage_drop: maximum_coverage_drop
)

ExitCodes::ExitCodeHandling.call(result, coverage_limits: coverage_limits)
Expand Down
19 changes: 19 additions & 0 deletions lib/simplecov/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,25 @@ def minimum_coverage_by_file(coverage = nil)
@minimum_coverage_by_file = coverage
end

#
# Defines the minimum coverage per group required for the testsuite to pass.
# SimpleCov will return non-zero if the current coverage of the least covered group
# is below this threshold.
#
# Default is 0% (disabled)
#
def minimum_coverage_by_group(coverage = nil)
return @minimum_coverage_by_group ||= {} unless coverage

@minimum_coverage_by_group = coverage.dup.transform_values do |group_coverage|
group_coverage = {primary_coverage => group_coverage} if group_coverage.is_a?(Numeric)

raise_on_invalid_coverage(group_coverage, "minimum_coverage_by_group")

group_coverage
end
end

#
# Refuses any coverage drop. That is, coverage is only allowed to increase.
# SimpleCov will return non-zero if the coverage decreases.
Expand Down
1 change: 1 addition & 0 deletions lib/simplecov/exit_codes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ module ExitCodes
require_relative "exit_codes/exit_code_handling"
require_relative "exit_codes/maximum_coverage_drop_check"
require_relative "exit_codes/minimum_coverage_by_file_check"
require_relative "exit_codes/minimum_coverage_by_group_check"
require_relative "exit_codes/minimum_overall_coverage_check"
1 change: 1 addition & 0 deletions lib/simplecov/exit_codes/exit_code_handling.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def coverage_checks(result, coverage_limits)
[
MinimumOverallCoverageCheck.new(result, coverage_limits.minimum_coverage),
MinimumCoverageByFileCheck.new(result, coverage_limits.minimum_coverage_by_file),
MinimumCoverageByGroupCheck.new(result, coverage_limits.minimum_coverage_by_group),
MaximumCoverageDropCheck.new(result, coverage_limits.maximum_coverage_drop)
]
end
Expand Down
62 changes: 62 additions & 0 deletions lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

module SimpleCov
module ExitCodes
class MinimumCoverageByGroupCheck
def initialize(result, minimum_coverage_by_group)
@result = result
@minimum_coverage_by_group = minimum_coverage_by_group
end

def failing?
minimum_violations.any?
end

def report
minimum_violations.each do |violation|
$stderr.printf(
"%<criterion>s coverage by group %<group_name> (%<covered>.2f%%) is below the expected minimum coverage (%<minimum_coverage>.2f%%).\n",
group_name: violation.fetch(:group_name),
covered: SimpleCov.round_coverage(violation.fetch(:actual)),
minimum_coverage: violation.fetch(:minimum_expected),
criterion: violation.fetch(:criterion).capitalize
)
end
end

def exit_code
SimpleCov::ExitCodes::MINIMUM_COVERAGE
end

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_data = []

minimum_coverage_by_group.each do |criterion, expected_percent|
result.coverage_statistics_by_group.each do |group_name, group_statistics|
group_statistics.fetch(criterion).each do |actual_coverage|
minimum_coverage_data << {
group_name: group_name,
criterion: criterion,
minimum_expected: expected_percent,
actual: SimpleCov.round_coverage(actual_coverage.percent)
}
end
end
end

minimum_coverage_data
end
end
end
end
19 changes: 19 additions & 0 deletions lib/simplecov/file_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ def coverage_statistics_by_file
@coverage_statistics_by_file ||= compute_coverage_statistics_by_file
end

def coverage_statistics_by_group
@coverage_statistics_by_group ||= coverage_statistics_by_group
end

# Returns the count of lines that have coverage
def covered_lines
coverage_statistics[:line]&.covered
Expand Down Expand Up @@ -111,6 +115,21 @@ def compute_coverage_statistics_by_file
end
end

def compute_coverage_statistics_by_group
by_group_coverage = {}

@group.each_with_object do |group_name, files|
group_coverage = files.each_with_object(line: [], branch: []) do |file, together|
together[:line] << file.coverage_statistics.fetch(:line)
together[:branch] << file.coverage_statistics.fetch(:branch) if SimpleCov.branch_coverage?
end

by_group_coverage[group_name] = group_coverage
end

by_group_coverage
end

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?
Expand Down
5 changes: 4 additions & 1 deletion lib/simplecov/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ 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, :coverage_statistics_by_file
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, :coverage_statistics_by_group

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
71 changes: 69 additions & 2 deletions spec/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@
end
end

shared_examples "setting coverage expectations" do |coverage_setting|
shared_examples "setting coverage expectations" do |coverage_setting, coverage_decorator|
after do
config.clear_coverage_criteria
end

it "does not warn you about your usage" do
expect(config).not_to receive(:warn)
config.public_send(coverage_setting, 100.00)
config.public_send(coverage_setting, coverage_decorator.call(100.00))
end

it "warns you about your usage" do
Expand Down Expand Up @@ -124,6 +124,73 @@
it_behaves_like "setting coverage expectations", :minimum_coverage_by_file
end

describe "#minimum_coverage_by_group" do
after do
config.clear_coverage_criteria
end

it "does not warn you about your usage" do
expect(config).not_to receive(:warn)
config.minimum_coverage_by_group('Test Group 1' => 100.00)
end

it "warns you about your usage" do
expect(config).to receive(:warn).with("The coverage you set for minimum_coverage_by_group is greater than 100%")
config.minimum_coverage_by_group('Test Group 1' => 100.01)
end

it "sets the right coverage value when called with a number" do
config.minimum_coverage_by_group('Test Group 1' => 80)

expect(config.minimum_coverage_by_group).to eq { 'Test Group 1' => { line: 80 } }
end

it "sets the right coverage when called with a hash of just line" do
config.minimum_coverage_by_group('Test Group 1' => { line: 85.0 })

expect(config.mini mum_coverage_by_group).to eq { 'Test Group 1' => { 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_by_group({ branch: 85.0 })

expect(config.minimum_coverage_by_group).to eq branch: 85.0
end

it "sets the right coverage when called with both line and branch" do
config.enable_coverage :branch
config.minimum_coverage_by_group('Test Group 1' => { branch: 85.0, line: 95.4 })

expect(config.minimum_coverage_by_group).to eq { 'Test Group 1' => { branch: 85.0, line: 95.4 } }
end

it "raises when trying to set branch coverage but not enabled" do
expect do
config.minimum_coverage_by_group { 'Test Group 1' => { branch: 42 } }
end.to raise_error(/branch.*disabled/i)
end

it "raises when unknown coverage criteria provided" do
expect do
config.minimum_coverage_by_group({ 'Test Group 1' => { unknown: 42 } })
end.to raise_error(/unsupported.*unknown/i)
end

context "when primary coverage is set" do
before do
config.enable_coverage :branch
config.primary_coverage :branch
end

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

expect(config.minimum_coverage_by_group).to eq { 'Test Group 1' => { branch: 80 } }
end
end
end

describe "#maximum_coverage_drop" do
it_behaves_like "setting coverage expectations", :maximum_coverage_drop
end
Expand Down
41 changes: 41 additions & 0 deletions spec/exit_codes/minimum_coverage_by_group_check_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

require "helper"

RSpec.describe SimpleCov::ExitCodes::MinimumCoverageByGroupCheck do
subject { described_class.new(result, minimum_coverage_by_group) }

let(:result) { instance_double(SimpleCov::Result, coverage_statistics_by_file: stats) }
let(:stats) {
{
'Test Group 1' => {
line: [SimpleCov::CoverageStatistics.new(covered: 8, missed: 2)],
branch: SimpleCov::CoverageStatistics.new(covered: 8, missed: 2)
}
}
}

context "everything exactly ok" do
let(:minimum_coverage) { { 'Test Group 1' => {line: 80.0} } }

it { is_expected.not_to be_failing }
end

context "coverage violated" do
let(:minimum_coverage) { { 'Test Group 1' => {line: 90.0} } }

it { is_expected.to be_failing }
end

context "coverage slightly violated" do
let(:minimum_coverage) { { 'Test Group 1' => {line: 80.01} } }

it { is_expected.to be_failing }
end

context "one criterion violated" do
let(:minimum_coverage) { { 'Test Group 1' => {line: 80.0, branch: 90.0} } }

it { is_expected.to be_failing }
end
end

0 comments on commit 37f41fc

Please sign in to comment.