single_cov 1.11.0 → 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ffed2c03e29890449010148c9492563266983dd70d0f32e7a600692fa6b6a57
4
- data.tar.gz: a9395fb02ed4dd4f5490cf4faf8a7c8ef3bd8b084081958c3a86fabb3cbbedcf
3
+ metadata.gz: 03abb741a629409eca737e84185cbd61c34a24a65fdb5b4c381d295f00690eed
4
+ data.tar.gz: fee46d0a03e4812f372c375745ff9a02229292bc1c153e028ee7dc46691edfb0
5
5
  SHA512:
6
- metadata.gz: dddfdd5778b9e3c34b92db87d4288970a6a0bd27154d0c40572bf5fa581f92806214827974b55c2612b19f1298bd1d5827072006fb1d131bf5feb36ccca33513
7
- data.tar.gz: 6b82b17a84a018334c140027bab24ef3ac9c08cb0d52973f87d580006d19b0c7cfd3a80b8331b7d487e38af34c8e51f817066170ddee360378587b0b8af03981
6
+ metadata.gz: 52da7209346a1ec927241e0db7390d664ef155a6146642e0895964cbcaa7c6e704ee6dc027591032fcb4727546543de60aa68ddd57f9b7e5f8879b9c98169a73
7
+ data.tar.gz: 3f0ae6f966cb07c608efd71cc8b87787c7506363c8bf2e43c4e334cf88b51122f56d2ad56864afc302b7158339eb4c5f3d8935f8a924c3132e43ab216604f700
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module SingleCov
3
- VERSION = "1.11.0"
3
+ VERSION = "2.1.0"
4
4
  end
data/lib/single_cov.rb CHANGED
@@ -4,6 +4,7 @@ module SingleCov
4
4
  MAX_OUTPUT = Integer(ENV["SINGLE_COV_MAX_OUTPUT"] || "40")
5
5
  RAILS_APP_FOLDERS = ["models", "serializers", "helpers", "controllers", "mailers", "views", "jobs", "channels"]
6
6
  UNCOVERED_COMMENT_MARKER = /#.*uncovered/
7
+ NOCOV_MARKER = /^\s*#\s*:nocov:/ # see https://github.com/simplecov-ruby/simplecov/blob/00376ea2f29ce7a7355b7c2d7d094b666b57cb9b/lib/simplecov/lines_classifier.rb#L16
7
8
  PREFIXES_TO_IGNORE = [] # things to not prefix with lib/ etc
8
9
 
9
10
  class << self
@@ -32,7 +33,7 @@ module SingleCov
32
33
 
33
34
  def all_covered?(result)
34
35
  errors = COVERAGES.flat_map do |file, expected_uncovered|
35
- next no_coverage_error(file) unless coverage = result["#{root}/#{file}"]
36
+ next no_coverage_error(file) unless (coverage = result["#{root}/#{file}"])
36
37
 
37
38
  uncovered = uncovered(coverage)
38
39
  next if uncovered.size == expected_uncovered
@@ -40,8 +41,10 @@ module SingleCov
40
41
  # ignore lines that are marked as uncovered via comments
41
42
  # TODO: warn when using uncovered but the section is indeed covered
42
43
  content = File.readlines("#{root}/#{file}")
44
+ nocov_lines_nums = nocov_line_numbers(content)
43
45
  uncovered.reject! do |line_start, _, _, _, _|
44
- content[line_start - 1].match?(UNCOVERED_COMMENT_MARKER)
46
+ content[line_start - 1].match?(UNCOVERED_COMMENT_MARKER) ||
47
+ nocov_lines_nums.include?(line_start)
45
48
  end
46
49
  next if uncovered.size == expected_uncovered
47
50
 
@@ -119,8 +122,7 @@ module SingleCov
119
122
 
120
123
  case framework
121
124
  when :minitest
122
- minitest_should_not_be_running!
123
- return if minitest_running_subset_of_tests?
125
+ return if minitest_running_subset_of_tests?(ARGV)
124
126
  when :rspec
125
127
  return if rspec_running_subset_of_tests?
126
128
  else
@@ -129,15 +131,35 @@ module SingleCov
129
131
 
130
132
  start_coverage_recording
131
133
 
132
- override_at_exit do |status, _exception|
133
- if enabled? && main_process? && status == 0
134
- results = coverage_results
135
- generate_report results
136
- exit 1 unless SingleCov.all_covered?(results)
134
+ # minitest overrides at_exit, so we need to get into it to execute after it finishes
135
+ # so when using the `minitest` executable or loading minitest before SingleCov the first branch is used
136
+ if defined?(Minitest)
137
+ (class << Minitest; self; end).prepend(Module.new do
138
+ def run(args = [])
139
+ original_args = args.dup # minitest modified them
140
+ result = super
141
+ if result && !SingleCov.send(:minitest_running_subset_of_tests?, original_args)
142
+ return SingleCov.report_at_exit
143
+ end
144
+ result
145
+ end
146
+ end)
147
+ else
148
+ override_at_exit do |status, _exception|
149
+ if main_process? && status == 0 && !report_at_exit
150
+ exit 1
151
+ end
137
152
  end
138
153
  end
139
154
  end
140
155
 
156
+ def report_at_exit
157
+ return unless enabled?
158
+ results = coverage_results
159
+ generate_report results
160
+ SingleCov.all_covered?(results)
161
+ end
162
+
141
163
  # use this in forks when using rspec to silence duplicated output
142
164
  def disable
143
165
  @disabled = true
@@ -159,7 +181,7 @@ module SingleCov
159
181
  end
160
182
 
161
183
  def enabled?
162
- (!defined?(@disabled) || !@disabled)
184
+ !defined?(@disabled) || !@disabled
163
185
  end
164
186
 
165
187
  # assuming that the main process will load all the files, we store it's pid
@@ -168,7 +190,7 @@ module SingleCov
168
190
  end
169
191
 
170
192
  def main_process?
171
- (!defined?(@main_process_pid) || @main_process_pid == Process.pid)
193
+ !defined?(@main_process_pid) || @main_process_pid == Process.pid
172
194
  end
173
195
 
174
196
  # {[branch_id] => {[branch_part] => coverage}} --> uncovered location
@@ -196,6 +218,19 @@ module SingleCov
196
218
  Dir["#{root}/#{pattern}"].map! { |f| f.sub("#{root}/", '') }
197
219
  end
198
220
 
221
+ # @returns [Integer] 1-based line numbers that are inside :nocov: blocks
222
+ def nocov_line_numbers(content)
223
+ inside = false
224
+ content.each_with_index.filter_map do |line, index|
225
+ if line.match?(NOCOV_MARKER)
226
+ inside = !inside # toggle
227
+ nil # line itself cannot be uncovered since it is only a comment
228
+ elsif inside
229
+ index + 1
230
+ end
231
+ end
232
+ end
233
+
199
234
  def indexes(list, find)
200
235
  list.each_with_index.filter_map { |v, i| i if v == find }
201
236
  end
@@ -230,56 +265,26 @@ module SingleCov
230
265
  COVERAGES.size == 1
231
266
  end
232
267
 
233
- # we cannot insert our hooks when minitest is already running
234
- def minitest_should_not_be_running!
235
- return unless defined?(Minitest)
236
- return unless Minitest.class_variable_defined?(:@@installed_at_exit)
237
- return unless Minitest.class_variable_get(:@@installed_at_exit)
238
-
239
- # untested
240
- # https://github.com/rails/rails/pull/26515 rails loads autorun before test
241
- # but it works out for some reason
242
- return if Minitest.extensions.include?('rails')
243
-
244
- # untested
245
- # forking test runner does some hacky acrobatics to fake minitest status
246
- # and the resets it ... works out ok in the end ...
247
- return if faked_by_forking_test_runner?
248
-
249
- # ... but only if it's used with `--merge-coverage` otherwise the coverage reporting is useless
250
- if $0.end_with?("/forking-test-runner")
251
- raise "forking-test-runner only work with single_cov when using --merge-coverage"
252
- end
253
-
254
- raise "Load minitest after setting up SingleCov"
255
- end
256
-
257
- # ForkingTestRunner fakes an initialized minitest to avoid multiple hooks being installed
258
- # so hooks still get added in order https://github.com/grosser/forking_test_runner/pull/4
259
- def faked_by_forking_test_runner?
260
- defined?(Coverage) && Coverage.respond_to?(:capture_coverage!)
261
- end
262
-
263
268
  # do not record or verify when only running selected tests since it would be missing data
264
- def minitest_running_subset_of_tests?
269
+ def minitest_running_subset_of_tests?(argv)
265
270
  # via direct option (ruby test.rb -n /foo/)
266
- (ARGV.map { |a| a.split('=', 2).first } & ['-n', '--name', '-l', '--line']).any? ||
271
+ # or `minitest foo.rb:123` which is translated to -n
272
+ argv.map { |a| a.split('=', 2).first }.intersect?(['-n', '--name', '-l', '--line']) ||
267
273
 
268
- # via testrbl or mtest or rails with direct line number (mtest test.rb:123)
269
- (ARGV.first =~ /:\d+\Z/) ||
274
+ # via testrbl, mtest, or rails with direct line number (mtest test.rb:123)
275
+ (argv.first =~ /:\d+\Z/) ||
270
276
 
271
277
  # via rails test which preloads mintest, removes ARGV and fills options
272
278
  (
273
- defined?(Minitest) &&
274
279
  defined?(Minitest.reporter) &&
275
280
  Minitest.reporter &&
276
281
  (reporter = Minitest.reporter.reporters.first) &&
277
- reporter.options[:filter]
282
+ (reporter.options[:filter] || reporter.options[:include]) # MT 5 vs MT 6
278
283
  )
279
284
  end
280
285
 
281
286
  def rspec_running_subset_of_tests?
282
- (ARGV & ['-t', '--tag', '-e', '--example']).any? || ARGV.any? { |a| a =~ /:\d+$|\[[\d:]+\]$/ }
287
+ ARGV.intersect?(['-t', '--tag', '-e', '--example']) || ARGV.any? { |a| a =~ /:\d+$|\[[\d:]+\]$/ }
283
288
  end
284
289
 
285
290
  # code stolen from SimpleCov
@@ -400,7 +405,7 @@ module SingleCov
400
405
  end
401
406
 
402
407
  # remove test extension
403
- if !file_part.sub!(/_(?:test|spec)\.rb\b.*/, '.rb') && !file_part.sub!(/\/test_/, "/")
408
+ if !file_part.sub!(/_(?:test|spec)\.rb\b.*/, '.rb') && !file_part.sub!('/test_', "/")
404
409
  raise "Unable to remove test extension from #{file} ... /test_, _test.rb and _spec.rb are supported"
405
410
  end
406
411
  end
@@ -418,14 +423,14 @@ module SingleCov
418
423
  end
419
424
 
420
425
  def generate_report(results)
421
- return unless report = coverage_report
426
+ return unless (report = coverage_report)
422
427
 
423
428
  # not a hard dependency for the whole library
424
429
  require "json"
425
430
  require "fileutils"
426
431
 
427
432
  used = COVERAGES.map { |f, _| "#{root}/#{f}" }
428
- covered = results.select { |k, _| used.include?(k) }
433
+ covered = results.slice(*used)
429
434
 
430
435
  if coverage_report_lines
431
436
  covered = covered.transform_values { |v| v.is_a?(Hash) ? v.fetch(:lines) : v }
metadata CHANGED
@@ -1,16 +1,98 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: single_cov
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.11.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-06-03 00:00:00.000000000 Z
12
- dependencies: []
13
- description:
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: bump
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rspec
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: simplecov
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
14
96
  email: michael@grosser.it
15
97
  executables: []
16
98
  extensions: []
@@ -23,7 +105,6 @@ homepage: https://github.com/grosser/single_cov
23
105
  licenses:
24
106
  - MIT
25
107
  metadata: {}
26
- post_install_message:
27
108
  rdoc_options: []
28
109
  require_paths:
29
110
  - lib
@@ -31,15 +112,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
31
112
  requirements:
32
113
  - - ">="
33
114
  - !ruby/object:Gem::Version
34
- version: 2.7.0
115
+ version: 3.2.0
35
116
  required_rubygems_version: !ruby/object:Gem::Requirement
36
117
  requirements:
37
118
  - - ">="
38
119
  - !ruby/object:Gem::Version
39
120
  version: '0'
40
121
  requirements: []
41
- rubygems_version: 3.3.3
42
- signing_key:
122
+ rubygems_version: 3.6.9
43
123
  specification_version: 4
44
124
  summary: Actionable code coverage.
45
125
  test_files: []