123456789_123456789_123456789_123456789_123456789_

Module: SimpleCov::CoverageViolations

Relationships & Source Files
Defined in: lib/simplecov/coverage_violations.rb

Overview

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 round_coverage so downstream consumers don't need to round again.

Class Method Summary

Class Method Details

.compute_drop(criterion, result, last_run) (private)

[ GitHub ]

  
# File 'lib/simplecov/coverage_violations.rb', line 133

def compute_drop(criterion, result, last_run)
  stats_key = SimpleCov.coverage_statistics_key(criterion)
  last_coverage_percent = last_run.dig(:result, stats_key)
  last_coverage_percent ||= last_run.dig(:result, :covered_percent) if stats_key == :line
  return unless last_coverage_percent

  current = percent_for(result, criterion) or return
  (last_coverage_percent - current).floor(10)
end

.effective_per_file_thresholds(file, defaults, overrides) (private)

Walk the overrides in declaration order, merging each one that matches the file's project path into the running effective threshold (so the most-specific or latest-declared override wins per criterion). Returns the defaults Hash unchanged when nothing matches.

[ GitHub ]

  
# File 'lib/simplecov/coverage_violations.rb', line 83

def effective_per_file_thresholds(file, defaults, overrides)
  return defaults if overrides.empty?

  path = file.project_filename
  overrides.reduce(defaults) do |acc, (pattern, criterion_thresholds)|
    path_matches?(path, pattern) ? acc.merge(criterion_thresholds) : acc
  end
end

.file_minimum_violation(file, criterion, expected) (private)

[ GitHub ]

  
# File 'lib/simplecov/coverage_violations.rb', line 104

def file_minimum_violation(file, criterion, expected)
  actual = percent_for(file, criterion) or return
  return unless actual < expected

  {
    criterion: criterion,
    expected: expected,
    actual: actual,
    filename: file.filename,
    project_filename: file.project_filename
  }
end

.group_minimum_violations(group_name, group, minimums) (private)

[ GitHub ]

  
# File 'lib/simplecov/coverage_violations.rb', line 117

def group_minimum_violations(group_name, group, minimums)
  minimums.filter_map do |criterion, expected|
    actual = percent_for(group, criterion) or next
    {group_name: group_name, criterion: criterion, expected: expected, actual: actual} if actual < expected
  end
end

.lookup_group(result, group_name) (private)

[ GitHub ]

  
# File 'lib/simplecov/coverage_violations.rb', line 124

def lookup_group(result, group_name)
  group = result.groups[group_name]
  unless group
    warn "minimum_coverage_by_group: no group named '#{group_name}' exists. " \
         "Available groups: #{result.groups.keys.join(', ')}"
  end
  group
end

.maximum_drop(result, thresholds, last_run: SimpleCov::LastRun.read) ⇒ Array<Hash>

Returns:

  • (Array<Hash>)

    :maximum, :actual where actual is the observed drop (in percentage points) vs. the last run.

[ GitHub ]

  
# File 'lib/simplecov/coverage_violations.rb', line 55

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

.maximum_overall(result, thresholds) ⇒ Array<Hash>

Tolerance: .percent_for floors the actual percent to two decimal places (matching the existing minimum-coverage behavior), so an actual of e.g. 95.4287 is treated as 95.42 — meaning a maximum of 95.42 still passes. See issue #187 for the rationale.

Returns:

  • (Array<Hash>)

    :expected, :actual

[ GitHub ]

  
# File 'lib/simplecov/coverage_violations.rb', line 25

def maximum_overall(result, thresholds)
  thresholds.filter_map do |criterion, expected|
    actual = percent_for(result, criterion) or next
    {criterion: criterion, expected: expected, actual: actual} if actual > expected
  end
end

.minimum_by_file(result, defaults, overrides = {}) ⇒ Array<Hash>

defaults is the criterion-keyed Hash applied to every file. overrides is an ordered Hash<pattern, criterion_thresholds> of per-path overrides; for each file, defaults are merged with every matching override (later wins per criterion, overrides win over defaults).

Returns:

  • (Array<Hash>)

    :expected, :actual, :filename, :project_filename

[ GitHub ]

  
# File 'lib/simplecov/coverage_violations.rb', line 38

def minimum_by_file(result, defaults, overrides = {})
  result.files.flat_map do |file|
    effective = effective_per_file_thresholds(file, defaults, overrides)
    effective.filter_map { |criterion, expected| file_minimum_violation(file, criterion, expected) }
  end
end

.minimum_by_group(result, thresholds) ⇒ Array<Hash>

Returns:

  • (Array<Hash>)

    :criterion, :expected, :actual

[ GitHub ]

  
# File 'lib/simplecov/coverage_violations.rb', line 46

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

.minimum_overall(result, thresholds) ⇒ Array<Hash>

Returns:

  • (Array<Hash>)

    :expected, :actual

[ GitHub ]

  
# File 'lib/simplecov/coverage_violations.rb', line 13

def minimum_overall(result, thresholds)
  thresholds.filter_map do |criterion, expected|
    actual = percent_for(result, criterion) or next
    {criterion: criterion, expected: expected, actual: actual} if actual < expected
  end
end

.path_matches?(project_filename, pattern) ⇒ Boolean (private)

Per-path matching for minimum_coverage_by_file overrides. Strings ending in / are treated as directory prefixes; otherwise they must match project_filename exactly. Regexps are tested via match?. The configuration setter rejects anything other than String/Regexp, so no dead else branch is needed here.

[ GitHub ]

  
# File 'lib/simplecov/coverage_violations.rb', line 97

def path_matches?(project_filename, pattern)
  return project_filename.match?(pattern) if pattern.is_a?(Regexp)
  return project_filename.start_with?(pattern) if pattern.end_with?("/")

  project_filename == pattern
end

.percent_for(stats_source, criterion) (private)

Look up a criterion's percent on any coverage_statistics-bearing object (Result, SourceFile, FileList). Returns nil — and the caller silently skips — when the criterion was configured but not actually measured by the runtime (e.g. minimum_coverage branch: 100 under the "strict" profile on JRuby, where the Coverage module doesn't emit branch data). The config-time raise_if_criterion_disabled check still catches the genuine "forgot to enable the criterion" mistake before we ever get here.

[ GitHub ]

  
# File 'lib/simplecov/coverage_violations.rb', line 74

def percent_for(stats_source, criterion)
  stats = stats_source.coverage_statistics[SimpleCov.coverage_statistics_key(criterion)]
  round(stats.percent) if stats
end

.round(percent) (private)

[ GitHub ]

  
# File 'lib/simplecov/coverage_violations.rb', line 143

def round(percent)
  SimpleCov.round_coverage(percent)
end