Module: SingleCov

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

Constant Summary collapse

COVERAGES =
[]
MAX_OUTPUT =
40
RAILS_APP_FOLDERS =
["models", "serializers", "helpers", "controllers", "mailers", "views", "jobs", "channels"]
UNCOVERED_COMMENT_MARKER =
/#.*uncovered/
VERSION =
"1.7.0"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.coverage_reportObject

enable coverage reporting: path to output file, changed by forking-test-runner at runtime to combine many reports



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

def coverage_report
  @coverage_report
end

.coverage_report_linesObject

emit only line coverage in coverage report for older coverage systems



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

def coverage_report_lines
  @coverage_report_lines
end

Class Method Details

.all_covered?(result) ⇒ Boolean

Returns:

  • (Boolean)


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
# File 'lib/single_cov.rb', line 32

def all_covered?(result)
  errors = COVERAGES.flat_map do |file, expected_uncovered|
    next no_coverage_error(file) unless coverage = result["#{root}/#{file}"]

    uncovered = uncovered(coverage)
    next if uncovered.size == expected_uncovered

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

    bad_coverage_error(file, expected_uncovered, uncovered)
  end.compact

  return true if errors.empty?

  errors[MAX_OUTPUT..-1] = "... coverage output truncated" if errors.size >= MAX_OUTPUT
  @error_logger.puts errors

  errors.all? { |l| warning?(l) }
end

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



67
68
69
70
71
72
73
74
75
76
77
# File 'lib/single_cov.rb', line 67

def assert_tested(files: glob('{app,lib}/**/*.rb'), tests: default_tests, untested: [])
  missing = files - tests.map { |t| guess_covered_file(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



58
59
60
61
62
63
64
65
# File 'lib/single_cov.rb', line 58

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

mark the file under test as needing coverage



26
27
28
29
30
# File 'lib/single_cov.rb', line 26

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

.disableObject

use this in forks when using rspec to silence duplicated output



111
112
113
# File 'lib/single_cov.rb', line 111

def disable
  @disabled = true
end

.not_covered!Object

mark a test file as not covering anything to make assert_used pass



21
22
23
# File 'lib/single_cov.rb', line 21

def not_covered!
  main_process!
end

.rewrite(&block) ⇒ Object

optionally rewrite the matching path single-cov guessed with a lambda



16
17
18
# File 'lib/single_cov.rb', line 16

def rewrite(&block)
  @rewrite = block
end

.setup(framework, root: nil, branches: true, err: $stderr) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/single_cov.rb', line 79

def setup(framework, root: nil, branches: true, err: $stderr)
  @error_logger = err

  if defined?(SimpleCov)
    raise "Load SimpleCov after SingleCov"
  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|
    if enabled? && main_process? && status == 0
      results = coverage_results
      generate_report results
      exit 1 unless SingleCov.all_covered?(results)
    end
  end
end