single_cov 1.0.3 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/lib/single_cov.rb +136 -62
 - data/lib/single_cov/version.rb +2 -1
 - metadata +4 -5
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: b1061e2ee0841966f31a082926db0474550414ac8e06f395696152ba3b985aa5
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 836d974a4b39cbbf4b5454334997f60ac4d2afe5aafd4609bc2e04fb57f5a991
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: e30055d56774315c96b907ef0d930c83a4ac921855d0000760ade00d47af0e3394a96853f13f669e4fd797e0d8d2d5aeadbfb1e8a8549f45ca36629a80befae6
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 6e42c196b4cbb0eccb982b3bb942cf1664d8aa99925eabc7c9fb6165d10dd94de36640527556a9209739058501a36e191fc9136f21c903e0aa57307c63b2bd73
         
     | 
    
        data/lib/single_cov.rb
    CHANGED
    
    | 
         @@ -1,63 +1,63 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            module SingleCov
         
     | 
| 
       2 
3 
     | 
    
         
             
              COVERAGES = []
         
     | 
| 
       3 
4 
     | 
    
         
             
              MAX_OUTPUT = 40
         
     | 
| 
       4 
     | 
    
         
            -
               
     | 
| 
       5 
     | 
    
         
            -
               
     | 
| 
      
 5 
     | 
    
         
            +
              RAILS_APP_FOLDERS = ["models", "serializers", "helpers", "controllers", "mailers", "views", "jobs", "channels"]
         
     | 
| 
      
 6 
     | 
    
         
            +
              UNCOVERED_COMMENT_MARKER = /#.*uncovered/
         
     | 
| 
       6 
7 
     | 
    
         | 
| 
       7 
8 
     | 
    
         
             
              class << self
         
     | 
| 
       8 
     | 
    
         
            -
                #  
     | 
| 
      
 9 
     | 
    
         
            +
                # enable coverage reporting: path to output file, changed by forking-test-runner at runtime to combine many reports
         
     | 
| 
      
 10 
     | 
    
         
            +
                attr_accessor :coverage_report
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                # emit only line coverage in coverage report for older coverage systems
         
     | 
| 
      
 13 
     | 
    
         
            +
                attr_accessor :coverage_report_lines
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                # optionally rewrite the matching path single-cov guessed with a lambda
         
     | 
| 
       9 
16 
     | 
    
         
             
                def rewrite(&block)
         
     | 
| 
       10 
17 
     | 
    
         
             
                  @rewrite = block
         
     | 
| 
       11 
18 
     | 
    
         
             
                end
         
     | 
| 
       12 
19 
     | 
    
         | 
| 
      
 20 
     | 
    
         
            +
                # mark a test file as not covering anything to make assert_used pass
         
     | 
| 
       13 
21 
     | 
    
         
             
                def not_covered!
         
     | 
| 
      
 22 
     | 
    
         
            +
                  main_process!
         
     | 
| 
       14 
23 
     | 
    
         
             
                end
         
     | 
| 
       15 
24 
     | 
    
         | 
| 
      
 25 
     | 
    
         
            +
                # mark the file under test as needing coverage
         
     | 
| 
       16 
26 
     | 
    
         
             
                def covered!(file: nil, uncovered: 0)
         
     | 
| 
       17 
     | 
    
         
            -
                  file =  
     | 
| 
      
 27 
     | 
    
         
            +
                  file = ensure_covered_file(file)
         
     | 
| 
       18 
28 
     | 
    
         
             
                  COVERAGES << [file, uncovered]
         
     | 
| 
      
 29 
     | 
    
         
            +
                  main_process!
         
     | 
| 
       19 
30 
     | 
    
         
             
                end
         
     | 
| 
       20 
31 
     | 
    
         | 
| 
       21 
32 
     | 
    
         
             
                def all_covered?(result)
         
     | 
| 
       22 
     | 
    
         
            -
                  errors = COVERAGES. 
     | 
| 
       23 
     | 
    
         
            -
                     
     | 
| 
       24 
     | 
    
         
            -
                      line_coverage = (coverage.is_a?(Hash) ? coverage.fetch(:lines) : coverage)
         
     | 
| 
       25 
     | 
    
         
            -
                      uncovered = line_coverage.each_with_index.map { |c, i| i + 1 if c == 0 }.compact
         
     | 
| 
       26 
     | 
    
         
            -
                      branch_coverage = (coverage.is_a?(Hash) && coverage[:branches])
         
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
                      if branch_coverage
         
     | 
| 
       29 
     | 
    
         
            -
                        uncovered.concat uncovered_branches(file, branch_coverage, uncovered)
         
     | 
| 
       30 
     | 
    
         
            -
                      end
         
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
                      next if uncovered.size == expected_uncovered
         
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
                      # branches are unsorted and added to the end, only sort when necessary
         
     | 
| 
       35 
     | 
    
         
            -
                      if branch_coverage
         
     | 
| 
       36 
     | 
    
         
            -
                        uncovered.sort_by! { |line_start, char_start, line_end, char_end| [line_start, char_start || 0] }
         
     | 
| 
       37 
     | 
    
         
            -
                      end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  errors = COVERAGES.flat_map do |file, expected_uncovered|
         
     | 
| 
      
 34 
     | 
    
         
            +
                    next no_coverage_error(file) unless coverage = result["#{root}/#{file}"]
         
     | 
| 
       38 
35 
     | 
    
         | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
                      end
         
     | 
| 
      
 36 
     | 
    
         
            +
                    uncovered = uncovered(coverage)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    next if uncovered.size == expected_uncovered
         
     | 
| 
       42 
38 
     | 
    
         | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
                     
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
      
 39 
     | 
    
         
            +
                    # ignore lines that are marked as uncovered via comments
         
     | 
| 
      
 40 
     | 
    
         
            +
                    # TODO: warn when using uncovered but the section is indeed covered
         
     | 
| 
      
 41 
     | 
    
         
            +
                    content = File.readlines(file)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    uncovered.reject! do |line_start, _, _, _|
         
     | 
| 
      
 43 
     | 
    
         
            +
                      content[line_start - 1].match?(UNCOVERED_COMMENT_MARKER)
         
     | 
| 
       46 
44 
     | 
    
         
             
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
                    next if uncovered.size == expected_uncovered
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    bad_coverage_error(file, expected_uncovered, uncovered)
         
     | 
| 
       47 
48 
     | 
    
         
             
                  end.compact
         
     | 
| 
       48 
49 
     | 
    
         | 
| 
       49 
50 
     | 
    
         
             
                  return true if errors.empty?
         
     | 
| 
       50 
51 
     | 
    
         | 
| 
       51 
     | 
    
         
            -
                  errors = errors.join("\n").split("\n") # unify arrays with multiline strings
         
     | 
| 
       52 
52 
     | 
    
         
             
                  errors[MAX_OUTPUT..-1] = "... coverage output truncated" if errors.size >= MAX_OUTPUT
         
     | 
| 
       53 
53 
     | 
    
         
             
                  warn errors
         
     | 
| 
       54 
54 
     | 
    
         | 
| 
       55 
     | 
    
         
            -
                  errors.all? { |l|  
     | 
| 
      
 55 
     | 
    
         
            +
                  errors.all? { |l| warning?(l) }
         
     | 
| 
       56 
56 
     | 
    
         
             
                end
         
     | 
| 
       57 
57 
     | 
    
         | 
| 
       58 
58 
     | 
    
         
             
                def assert_used(tests: default_tests)
         
     | 
| 
       59 
59 
     | 
    
         
             
                  bad = tests.select do |file|
         
     | 
| 
       60 
     | 
    
         
            -
                    File.read(file) !~ /SingleCov.(not_)?covered 
     | 
| 
      
 60 
     | 
    
         
            +
                    File.read(file) !~ /SingleCov.(not_)?covered!/
         
     | 
| 
       61 
61 
     | 
    
         
             
                  end
         
     | 
| 
       62 
62 
     | 
    
         
             
                  unless bad.empty?
         
     | 
| 
       63 
63 
     | 
    
         
             
                    raise bad.map { |f| "#{f}: needs to use SingleCov.covered!" }.join("\n")
         
     | 
| 
         @@ -65,7 +65,7 @@ module SingleCov 
     | 
|
| 
       65 
65 
     | 
    
         
             
                end
         
     | 
| 
       66 
66 
     | 
    
         | 
| 
       67 
67 
     | 
    
         
             
                def assert_tested(files: glob('{app,lib}/**/*.rb'), tests: default_tests, untested: [])
         
     | 
| 
       68 
     | 
    
         
            -
                  missing = files - tests.map { |t|  
     | 
| 
      
 68 
     | 
    
         
            +
                  missing = files - tests.map { |t| guess_covered_file(t) }
         
     | 
| 
       69 
69 
     | 
    
         
             
                  fixed = untested - missing
         
     | 
| 
       70 
70 
     | 
    
         
             
                  missing -= untested
         
     | 
| 
       71 
71 
     | 
    
         | 
| 
         @@ -76,13 +76,10 @@ module SingleCov 
     | 
|
| 
       76 
76 
     | 
    
         
             
                  end
         
     | 
| 
       77 
77 
     | 
    
         
             
                end
         
     | 
| 
       78 
78 
     | 
    
         | 
| 
       79 
     | 
    
         
            -
                def setup(framework, root: nil, branches:  
     | 
| 
      
 79 
     | 
    
         
            +
                def setup(framework, root: nil, branches: true)
         
     | 
| 
       80 
80 
     | 
    
         
             
                  if defined?(SimpleCov)
         
     | 
| 
       81 
81 
     | 
    
         
             
                    raise "Load SimpleCov after SingleCov"
         
     | 
| 
       82 
82 
     | 
    
         
             
                  end
         
     | 
| 
       83 
     | 
    
         
            -
                  if branches && !BRANCH_COVERAGE_SUPPORTED
         
     | 
| 
       84 
     | 
    
         
            -
                    raise "Branch coverage needs ruby >= 2.5.0"
         
     | 
| 
       85 
     | 
    
         
            -
                  end
         
     | 
| 
       86 
83 
     | 
    
         | 
| 
       87 
84 
     | 
    
         
             
                  @branches = branches
         
     | 
| 
       88 
85 
     | 
    
         
             
                  @root = root
         
     | 
| 
         @@ -100,7 +97,11 @@ module SingleCov 
     | 
|
| 
       100 
97 
     | 
    
         
             
                  start_coverage_recording
         
     | 
| 
       101 
98 
     | 
    
         | 
| 
       102 
99 
     | 
    
         
             
                  override_at_exit do |status, _exception|
         
     | 
| 
       103 
     | 
    
         
            -
                     
     | 
| 
      
 100 
     | 
    
         
            +
                    if enabled? && main_process? && status == 0
         
     | 
| 
      
 101 
     | 
    
         
            +
                      results = coverage_results
         
     | 
| 
      
 102 
     | 
    
         
            +
                      generate_report results
         
     | 
| 
      
 103 
     | 
    
         
            +
                      exit 1 unless SingleCov.all_covered?(results)
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
       104 
105 
     | 
    
         
             
                  end
         
     | 
| 
       105 
106 
     | 
    
         
             
                end
         
     | 
| 
       106 
107 
     | 
    
         | 
| 
         @@ -111,7 +112,38 @@ module SingleCov 
     | 
|
| 
       111 
112 
     | 
    
         | 
| 
       112 
113 
     | 
    
         
             
                private
         
     | 
| 
       113 
114 
     | 
    
         | 
| 
       114 
     | 
    
         
            -
                def  
     | 
| 
      
 115 
     | 
    
         
            +
                def uncovered(coverage)
         
     | 
| 
      
 116 
     | 
    
         
            +
                  return coverage unless coverage.is_a?(Hash) # just lines
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                  # [nil, 1, 0, 1, 0] -> [3, 5]
         
     | 
| 
      
 119 
     | 
    
         
            +
                  uncovered_lines = coverage.fetch(:lines)
         
     | 
| 
      
 120 
     | 
    
         
            +
                    .each_with_index
         
     | 
| 
      
 121 
     | 
    
         
            +
                    .select { |c, _| c == 0 }
         
     | 
| 
      
 122 
     | 
    
         
            +
                    .map { |_, i| i + 1 }
         
     | 
| 
      
 123 
     | 
    
         
            +
                    .compact
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                  uncovered_branches = uncovered_branches(coverage[:branches] || {})
         
     | 
| 
      
 126 
     | 
    
         
            +
                  uncovered_branches.reject! { |k| uncovered_lines.include?(k[0]) } # remove duplicates
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                  all = uncovered_lines.concat uncovered_branches
         
     | 
| 
      
 129 
     | 
    
         
            +
                  all.sort_by! { |line_start, char_start, _, _| [line_start, char_start || 0] } # branches are unsorted
         
     | 
| 
      
 130 
     | 
    
         
            +
                  all
         
     | 
| 
      
 131 
     | 
    
         
            +
                end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                def enabled?
         
     | 
| 
      
 134 
     | 
    
         
            +
                  (!defined?(@disabled) || !@disabled)
         
     | 
| 
      
 135 
     | 
    
         
            +
                end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                # assuming that the main process will load all the files, we store it's pid
         
     | 
| 
      
 138 
     | 
    
         
            +
                def main_process!
         
     | 
| 
      
 139 
     | 
    
         
            +
                  @main_process_pid = Process.pid
         
     | 
| 
      
 140 
     | 
    
         
            +
                end
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                def main_process?
         
     | 
| 
      
 143 
     | 
    
         
            +
                  (!defined?(@main_process_pid) || @main_process_pid == Process.pid)
         
     | 
| 
      
 144 
     | 
    
         
            +
                end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                def uncovered_branches(coverage)
         
     | 
| 
       115 
147 
     | 
    
         
             
                  # {[branch_id] => {[branch_part] => coverage}} --> {branch_part -> sum-of-coverage}
         
     | 
| 
       116 
148 
     | 
    
         
             
                  sum = Hash.new(0)
         
     | 
| 
       117 
149 
     | 
    
         
             
                  coverage.each_value do |branch|
         
     | 
| 
         @@ -120,9 +152,8 @@ module SingleCov 
     | 
|
| 
       120 
152 
     | 
    
         
             
                    end
         
     | 
| 
       121 
153 
     | 
    
         
             
                  end
         
     | 
| 
       122 
154 
     | 
    
         | 
| 
       123 
     | 
    
         
            -
                  #  
     | 
| 
       124 
     | 
    
         
            -
                  found = sum. 
     | 
| 
       125 
     | 
    
         
            -
                    map { |k, _| [k[0], k[1]+1, k[2], k[3]+1] }
         
     | 
| 
      
 155 
     | 
    
         
            +
                  sum.select! { |_, v| v == 0 } # keep missing coverage
         
     | 
| 
      
 156 
     | 
    
         
            +
                  found = sum.map { |k, _| [k[0], k[1] + 1, k[2], k[3] + 1] }
         
     | 
| 
       126 
157 
     | 
    
         
             
                  found.uniq!
         
     | 
| 
       127 
158 
     | 
    
         
             
                  found
         
     | 
| 
       128 
159 
     | 
    
         
             
                end
         
     | 
| 
         @@ -138,7 +169,12 @@ module SingleCov 
     | 
|
| 
       138 
169 
     | 
    
         
             
                # do not ask for coverage when SimpleCov already does or it conflicts
         
     | 
| 
       139 
170 
     | 
    
         
             
                def coverage_results
         
     | 
| 
       140 
171 
     | 
    
         
             
                  if defined?(SimpleCov) && (result = SimpleCov.instance_variable_get(:@result))
         
     | 
| 
       141 
     | 
    
         
            -
                    result.original_result
         
     | 
| 
      
 172 
     | 
    
         
            +
                    result = result.original_result
         
     | 
| 
      
 173 
     | 
    
         
            +
                    # singlecov 1.18+ puts string "lines" into the result that we cannot read
         
     | 
| 
      
 174 
     | 
    
         
            +
                    if result.each_value.first.is_a?(Hash)
         
     | 
| 
      
 175 
     | 
    
         
            +
                      result = result.transform_values { |v| v.transform_keys(&:to_sym) }
         
     | 
| 
      
 176 
     | 
    
         
            +
                    end
         
     | 
| 
      
 177 
     | 
    
         
            +
                    result
         
     | 
| 
       142 
178 
     | 
    
         
             
                  else
         
     | 
| 
       143 
179 
     | 
    
         
             
                    Coverage.result
         
     | 
| 
       144 
180 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -151,7 +187,7 @@ module SingleCov 
     | 
|
| 
       151 
187 
     | 
    
         
             
                  if @branches
         
     | 
| 
       152 
188 
     | 
    
         
             
                    Coverage.start(lines: true, branches: true)
         
     | 
| 
       153 
189 
     | 
    
         
             
                  else
         
     | 
| 
       154 
     | 
    
         
            -
                    Coverage.start
         
     | 
| 
      
 190 
     | 
    
         
            +
                    Coverage.start(lines: true)
         
     | 
| 
       155 
191 
     | 
    
         
             
                  end
         
     | 
| 
       156 
192 
     | 
    
         
             
                end
         
     | 
| 
       157 
193 
     | 
    
         | 
| 
         @@ -209,7 +245,7 @@ module SingleCov 
     | 
|
| 
       209 
245 
     | 
    
         
             
                end
         
     | 
| 
       210 
246 
     | 
    
         | 
| 
       211 
247 
     | 
    
         
             
                def rspec_running_subset_of_tests?
         
     | 
| 
       212 
     | 
    
         
            -
                  (ARGV & ['-t', '--tag', '-e', '--example']).any? || ARGV.any? { |a| a =~  
     | 
| 
      
 248 
     | 
    
         
            +
                  (ARGV & ['-t', '--tag', '-e', '--example']).any? || ARGV.any? { |a| a =~ /:\d+$|\[[\d:]+\]$/ }
         
     | 
| 
       213 
249 
     | 
    
         
             
                end
         
     | 
| 
       214 
250 
     | 
    
         | 
| 
       215 
251 
     | 
    
         
             
                # code stolen from SimpleCov
         
     | 
| 
         @@ -234,15 +270,13 @@ module SingleCov 
     | 
|
| 
       234 
270 
     | 
    
         
             
                  end
         
     | 
| 
       235 
271 
     | 
    
         
             
                end
         
     | 
| 
       236 
272 
     | 
    
         | 
| 
       237 
     | 
    
         
            -
                def  
     | 
| 
       238 
     | 
    
         
            -
                   
     | 
| 
       239 
     | 
    
         
            -
                    raise "Use paths relative to root."
         
     | 
| 
       240 
     | 
    
         
            -
                  end
         
     | 
| 
      
 273 
     | 
    
         
            +
                def ensure_covered_file(file)
         
     | 
| 
      
 274 
     | 
    
         
            +
                  raise "Use paths relative to project root." if file&.start_with?("/")
         
     | 
| 
       241 
275 
     | 
    
         | 
| 
       242 
276 
     | 
    
         
             
                  if file
         
     | 
| 
       243 
     | 
    
         
            -
                    raise "#{file} does not exist  
     | 
| 
      
 277 
     | 
    
         
            +
                    raise "#{file} does not exist, use paths relative to project root." unless File.exist?("#{root}/#{file}")
         
     | 
| 
       244 
278 
     | 
    
         
             
                  else
         
     | 
| 
       245 
     | 
    
         
            -
                    file =  
     | 
| 
      
 279 
     | 
    
         
            +
                    file = guess_covered_file(caller[1])
         
     | 
| 
       246 
280 
     | 
    
         
             
                    if file.start_with?("/")
         
     | 
| 
       247 
281 
     | 
    
         
             
                      raise "Found file #{file} which is not relative to the root #{root}.\nUse `SingleCov.covered! file: 'target_file.rb'` to set covered file location."
         
     | 
| 
       248 
282 
     | 
    
         
             
                    elsif !File.exist?("#{root}/#{file}")
         
     | 
| 
         @@ -253,21 +287,39 @@ module SingleCov 
     | 
|
| 
       253 
287 
     | 
    
         
             
                  file
         
     | 
| 
       254 
288 
     | 
    
         
             
                end
         
     | 
| 
       255 
289 
     | 
    
         | 
| 
       256 
     | 
    
         
            -
                def  
     | 
| 
       257 
     | 
    
         
            -
                  details = "(#{ 
     | 
| 
       258 
     | 
    
         
            -
                  if expected_uncovered >  
     | 
| 
      
 290 
     | 
    
         
            +
                def bad_coverage_error(file, expected_uncovered, uncovered)
         
     | 
| 
      
 291 
     | 
    
         
            +
                  details = "(#{uncovered.size} current vs #{expected_uncovered} configured)"
         
     | 
| 
      
 292 
     | 
    
         
            +
                  if expected_uncovered > uncovered.size
         
     | 
| 
       259 
293 
     | 
    
         
             
                    if running_single_file?
         
     | 
| 
       260 
     | 
    
         
            -
                      "#{file} has less uncovered lines #{details}, decrement configured uncovered 
     | 
| 
      
 294 
     | 
    
         
            +
                      warning "#{file} has less uncovered lines #{details}, decrement configured uncovered"
         
     | 
| 
       261 
295 
     | 
    
         
             
                    end
         
     | 
| 
       262 
296 
     | 
    
         
             
                  else
         
     | 
| 
       263 
297 
     | 
    
         
             
                    [
         
     | 
| 
       264 
298 
     | 
    
         
             
                      "#{file} new uncovered lines introduced #{details}",
         
     | 
| 
       265 
299 
     | 
    
         
             
                      red("Lines missing coverage:"),
         
     | 
| 
       266 
     | 
    
         
            -
                      * 
     | 
| 
       267 
     | 
    
         
            -
             
     | 
| 
      
 300 
     | 
    
         
            +
                      *uncovered.map do |line_start, char_start, line_end, char_end|
         
     | 
| 
      
 301 
     | 
    
         
            +
                        if char_start # branch coverage
         
     | 
| 
      
 302 
     | 
    
         
            +
                          if line_start == line_end
         
     | 
| 
      
 303 
     | 
    
         
            +
                            "#{file}:#{line_start}:#{char_start}-#{char_end}"
         
     | 
| 
      
 304 
     | 
    
         
            +
                          else # possibly unreachable since branches always seem to be on the same line
         
     | 
| 
      
 305 
     | 
    
         
            +
                            "#{file}:#{line_start}:#{char_start}-#{line_end}:#{char_end}"
         
     | 
| 
      
 306 
     | 
    
         
            +
                          end
         
     | 
| 
      
 307 
     | 
    
         
            +
                        else
         
     | 
| 
      
 308 
     | 
    
         
            +
                          "#{file}:#{line_start}"
         
     | 
| 
      
 309 
     | 
    
         
            +
                        end
         
     | 
| 
      
 310 
     | 
    
         
            +
                      end
         
     | 
| 
      
 311 
     | 
    
         
            +
                    ]
         
     | 
| 
       268 
312 
     | 
    
         
             
                  end
         
     | 
| 
       269 
313 
     | 
    
         
             
                end
         
     | 
| 
       270 
314 
     | 
    
         | 
| 
      
 315 
     | 
    
         
            +
                def warning(msg)
         
     | 
| 
      
 316 
     | 
    
         
            +
                  "#{msg}?"
         
     | 
| 
      
 317 
     | 
    
         
            +
                end
         
     | 
| 
      
 318 
     | 
    
         
            +
             
     | 
| 
      
 319 
     | 
    
         
            +
                def warning?(msg)
         
     | 
| 
      
 320 
     | 
    
         
            +
                  msg.end_with?("?")
         
     | 
| 
      
 321 
     | 
    
         
            +
                end
         
     | 
| 
      
 322 
     | 
    
         
            +
             
     | 
| 
       271 
323 
     | 
    
         
             
                def red(text)
         
     | 
| 
       272 
324 
     | 
    
         
             
                  if $stdin.tty?
         
     | 
| 
       273 
325 
     | 
    
         
             
                    "\e[31m#{text}\e[0m"
         
     | 
| 
         @@ -276,17 +328,17 @@ module SingleCov 
     | 
|
| 
       276 
328 
     | 
    
         
             
                  end
         
     | 
| 
       277 
329 
     | 
    
         
             
                end
         
     | 
| 
       278 
330 
     | 
    
         | 
| 
       279 
     | 
    
         
            -
                def  
     | 
| 
      
 331 
     | 
    
         
            +
                def no_coverage_error(file)
         
     | 
| 
       280 
332 
     | 
    
         
             
                  if $LOADED_FEATURES.include?("#{root}/#{file}")
         
     | 
| 
       281 
333 
     | 
    
         
             
                    # we cannot enforce $LOADED_FEATURES during covered! since it would fail when multiple files are loaded
         
     | 
| 
       282 
     | 
    
         
            -
                    "#{file} was expected to be covered, but was already loaded before  
     | 
| 
      
 334 
     | 
    
         
            +
                    "#{file} was expected to be covered, but was already loaded before coverage started, which makes it uncoverable."
         
     | 
| 
       283 
335 
     | 
    
         
             
                  else
         
     | 
| 
       284 
     | 
    
         
            -
                    "#{file} was expected to be covered, but never loaded."
         
     | 
| 
      
 336 
     | 
    
         
            +
                    "#{file} was expected to be covered, but was never loaded."
         
     | 
| 
       285 
337 
     | 
    
         
             
                  end
         
     | 
| 
       286 
338 
     | 
    
         
             
                end
         
     | 
| 
       287 
339 
     | 
    
         | 
| 
       288 
     | 
    
         
            -
                def  
     | 
| 
       289 
     | 
    
         
            -
                  file =  
     | 
| 
      
 340 
     | 
    
         
            +
                def guess_covered_file(test)
         
     | 
| 
      
 341 
     | 
    
         
            +
                  file = test.dup
         
     | 
| 
       290 
342 
     | 
    
         | 
| 
       291 
343 
     | 
    
         
             
                  # remove caller junk to get nice error messages when something fails
         
     | 
| 
       292 
344 
     | 
    
         
             
                  file.sub!(/\.rb\b.*/, '.rb')
         
     | 
| 
         @@ -304,7 +356,7 @@ module SingleCov 
     | 
|
| 
       304 
356 
     | 
    
         
             
                  end
         
     | 
| 
       305 
357 
     | 
    
         | 
| 
       306 
358 
     | 
    
         
             
                  # rails things live in app
         
     | 
| 
       307 
     | 
    
         
            -
                  file_part[0...0] = if file_part =~ /^(?:#{ 
     | 
| 
      
 359 
     | 
    
         
            +
                  file_part[0...0] = if file_part =~ /^(?:#{RAILS_APP_FOLDERS.map { |f| Regexp.escape(f) }.join('|')})\//
         
     | 
| 
       308 
360 
     | 
    
         
             
                    "app/"
         
     | 
| 
       309 
361 
     | 
    
         
             
                  elsif file_part.start_with?("lib/") # don't add lib twice
         
     | 
| 
       310 
362 
     | 
    
         
             
                    ""
         
     | 
| 
         @@ -313,8 +365,8 @@ module SingleCov 
     | 
|
| 
       313 
365 
     | 
    
         
             
                  end
         
     | 
| 
       314 
366 
     | 
    
         | 
| 
       315 
367 
     | 
    
         
             
                  # remove test extension
         
     | 
| 
       316 
     | 
    
         
            -
                   
     | 
| 
       317 
     | 
    
         
            -
                    raise "Unable to remove test extension from #{file} ... _test.rb and _spec.rb are supported"
         
     | 
| 
      
 368 
     | 
    
         
            +
                  if !file_part.sub!(/_(?:test|spec)\.rb\b.*/, '.rb') && !file_part.sub!(/\/test_/, "/")
         
     | 
| 
      
 369 
     | 
    
         
            +
                    raise "Unable to remove test extension from #{file} ... /test_, _test.rb and _spec.rb are supported"
         
     | 
| 
       318 
370 
     | 
    
         
             
                  end
         
     | 
| 
       319 
371 
     | 
    
         | 
| 
       320 
372 
     | 
    
         
             
                  # put back the subfolder
         
     | 
| 
         @@ -328,5 +380,27 @@ module SingleCov 
     | 
|
| 
       328 
380 
     | 
    
         
             
                def root
         
     | 
| 
       329 
381 
     | 
    
         
             
                  @root ||= (defined?(Bundler) && Bundler.root.to_s.sub(/\/gemfiles$/, '')) || Dir.pwd
         
     | 
| 
       330 
382 
     | 
    
         
             
                end
         
     | 
| 
      
 383 
     | 
    
         
            +
             
     | 
| 
      
 384 
     | 
    
         
            +
                def generate_report(results)
         
     | 
| 
      
 385 
     | 
    
         
            +
                  return unless report = coverage_report
         
     | 
| 
      
 386 
     | 
    
         
            +
             
     | 
| 
      
 387 
     | 
    
         
            +
                  # not a hard dependency for the whole library
         
     | 
| 
      
 388 
     | 
    
         
            +
                  require "json"
         
     | 
| 
      
 389 
     | 
    
         
            +
                  require "fileutils"
         
     | 
| 
      
 390 
     | 
    
         
            +
             
     | 
| 
      
 391 
     | 
    
         
            +
                  used = COVERAGES.map { |f, _| "#{root}/#{f}" }
         
     | 
| 
      
 392 
     | 
    
         
            +
                  covered = results.select { |k, _| used.include?(k) }
         
     | 
| 
      
 393 
     | 
    
         
            +
             
     | 
| 
      
 394 
     | 
    
         
            +
                  if coverage_report_lines
         
     | 
| 
      
 395 
     | 
    
         
            +
                    covered = covered.transform_values { |v| v.is_a?(Hash) ? v.fetch(:lines) : v }
         
     | 
| 
      
 396 
     | 
    
         
            +
                  end
         
     | 
| 
      
 397 
     | 
    
         
            +
             
     | 
| 
      
 398 
     | 
    
         
            +
                  # chose "Minitest" because it is what simplecov uses for reports and "Unit Tests" makes sonarqube break
         
     | 
| 
      
 399 
     | 
    
         
            +
                  data = JSON.pretty_generate(
         
     | 
| 
      
 400 
     | 
    
         
            +
                    "Minitest" => { "coverage" => covered, "timestamp" => Time.now.to_i }
         
     | 
| 
      
 401 
     | 
    
         
            +
                  )
         
     | 
| 
      
 402 
     | 
    
         
            +
                  FileUtils.mkdir_p(File.dirname(report))
         
     | 
| 
      
 403 
     | 
    
         
            +
                  File.write report, data
         
     | 
| 
      
 404 
     | 
    
         
            +
                end
         
     | 
| 
       331 
405 
     | 
    
         
             
              end
         
     | 
| 
       332 
406 
     | 
    
         
             
            end
         
     | 
    
        data/lib/single_cov/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: single_cov
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 1.0 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.6.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Michael Grosser
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date:  
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2020-11-07 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies: []
         
     | 
| 
       13 
13 
     | 
    
         
             
            description: 
         
     | 
| 
       14 
14 
     | 
    
         
             
            email: michael@grosser.it
         
     | 
| 
         @@ -31,15 +31,14 @@ required_ruby_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       31 
31 
     | 
    
         
             
              requirements:
         
     | 
| 
       32 
32 
     | 
    
         
             
              - - ">="
         
     | 
| 
       33 
33 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       34 
     | 
    
         
            -
                  version: 2. 
     | 
| 
      
 34 
     | 
    
         
            +
                  version: 2.5.0
         
     | 
| 
       35 
35 
     | 
    
         
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
       36 
36 
     | 
    
         
             
              requirements:
         
     | 
| 
       37 
37 
     | 
    
         
             
              - - ">="
         
     | 
| 
       38 
38 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       39 
39 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       40 
40 
     | 
    
         
             
            requirements: []
         
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
            rubygems_version: 2.7.6
         
     | 
| 
      
 41 
     | 
    
         
            +
            rubygems_version: 3.1.3
         
     | 
| 
       43 
42 
     | 
    
         
             
            signing_key: 
         
     | 
| 
       44 
43 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       45 
44 
     | 
    
         
             
            summary: Actionable code coverage.
         
     |