Module: SingleCov

Defined in:
lib/single_cov.rb,
lib/single_cov/version.rb

Constant Summary collapse

COVERAGES =
[]
MAX_OUTPUT =
40
APP_FOLDERS =
["models", "serializers", "helpers", "controllers", "mailers", "views", "jobs", "channels"]
BRANCH_COVERAGE_SUPPORTED =
(RUBY_VERSION >= "2.5.0")
UNCOVERED_COMMENT_MARKER =
"uncovered"
VERSION =
"1.3.2"

Class Method Summary collapse

Class Method Details

.all_covered?(result) ⇒ Boolean

Returns:

  • (Boolean)


24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/single_cov.rb', line 24

def all_covered?(result)
  errors = COVERAGES.map do |file, expected_uncovered|
    if coverage = result["#{root}/#{file}"]
      line_coverage = (coverage.is_a?(Hash) ? coverage.fetch(:lines) : coverage)
      uncovered_lines = line_coverage.each_with_index.map { |c, i| i + 1 if c == 0 }.compact

      branch_coverage = (coverage.is_a?(Hash) && coverage[:branches])
      uncovered_branches = (branch_coverage ? uncovered_branches(branch_coverage, uncovered_lines) : [])

      uncovered = uncovered_lines.concat uncovered_branches
      next if uncovered.size == expected_uncovered

      # ignore lines that are marked as uncovered via comments
      # NOTE: ideally we should also warn when using uncovered but the section is indeed covered
      content = File.readlines(file)
      uncovered.reject! do |line_start, _, _, _|
        content[line_start - 1].include?(UNCOVERED_COMMENT_MARKER)
      end
      next if uncovered.size == expected_uncovered

      # branches are unsorted and added to the end, only sort when displayed
      if branch_coverage
        uncovered.sort_by! { |line_start, char_start, _, _| [line_start, char_start || 0] }
      end

      uncovered.map! do |line_start, char_start, line_end, char_end|
        if char_start # branch coverage
          if line_start == line_end
            "#{file}:#{line_start}:#{char_start}-#{char_end}"
          else # possibly unreachable since branches always seem to be on the same line
            "#{file}:#{line_start}:#{char_start}-#{line_end}:#{char_end}"
          end
        else
          "#{file}:#{line_start}"
        end
      end

      warn_about_bad_coverage(file, expected_uncovered, uncovered)
    else
      warn_about_no_coverage(file)
    end
  end.compact

  return true if errors.empty?

  errors = errors.join("\n").split("\n") # unify arrays with multiline strings
  errors[MAX_OUTPUT..-1] = "... coverage output truncated" if errors.size >= MAX_OUTPUT
  warn errors

  errors.all? { |l| l.end_with?('?') } # ok if we just have warnings
end

.assert_tested(files: glob('{app,lib}/**/*.rb'), tests: default_tests, untested: []) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/single_cov.rb', line 85

def assert_tested(files: glob('{app,lib}/**/*.rb'), tests: default_tests, untested: [])
  missing = files - tests.map { |t| file_under_test(t) }
  fixed = untested - missing
  missing -= untested

  if fixed.any?
    raise "Remove #{fixed.inspect} from untested!"
  elsif missing.any?
    raise missing.map { |f| "missing test for #{f}" }.join("\n")
  end
end

.assert_used(tests: default_tests) ⇒ Object



76
77
78
79
80
81
82
83
# File 'lib/single_cov.rb', line 76

def assert_used(tests: default_tests)
  bad = tests.select do |file|
    File.read(file) !~ /SingleCov.(not_)?covered\!/
  end
  unless bad.empty?
    raise bad.map { |f| "#{f}: needs to use SingleCov.covered!" }.join("\n")
  end
end

.covered!(file: nil, uncovered: 0) ⇒ Object



18
19
20
21
22
# File 'lib/single_cov.rb', line 18

def covered!(file: nil, uncovered: 0)
  file = guess_and_check_covered_file(file)
  COVERAGES << [file, uncovered]
  store_pid
end

.disableObject

use this in forks when using rspec to silence duplicated output



126
127
128
# File 'lib/single_cov.rb', line 126

def disable
  @disabled = true
end

.not_covered!Object



14
15
16
# File 'lib/single_cov.rb', line 14

def not_covered!
  store_pid
end

.rewrite(&block) ⇒ Object

optionally rewrite the file we guessed with a lambda



10
11
12
# File 'lib/single_cov.rb', line 10

def rewrite(&block)
  @rewrite = block
end

.setup(framework, root: nil, branches: BRANCH_COVERAGE_SUPPORTED) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/single_cov.rb', line 97

def setup(framework, root: nil, branches: BRANCH_COVERAGE_SUPPORTED)
  if defined?(SimpleCov)
    raise "Load SimpleCov after SingleCov"
  end
  if branches && !BRANCH_COVERAGE_SUPPORTED
    raise "Branch coverage needs ruby >= 2.5.0"
  end

  @branches = branches
  @root = root

  case framework
  when :minitest
    minitest_should_not_be_running!
    return if minitest_running_subset_of_tests?
  when :rspec
    return if rspec_running_subset_of_tests?
  else
    raise "Unsupported framework #{framework.inspect}"
  end

  start_coverage_recording

  override_at_exit do |status, _exception|
    exit 1 if enabled? && main_process? && status == 0 && !SingleCov.all_covered?(coverage_results)
  end
end