simplecov 0.17.1 → 0.18.0.beta1

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.
@@ -54,3 +54,8 @@ A formatter that prints the coverage of the file under test when you run a singl
54
54
  *by [Yosuke Kabuto](https://github.com/ysksn)*
55
55
 
56
56
  t_wada AA formatter for SimpleCov
57
+
58
+ #### [simplecov-material(https://github.com/chiefpansancolt/simplecov-material)
59
+ *by [Chiefpansancolt](https://github.com/chiefpansancolt)*
60
+
61
+ A Material Designed HTML formatter with clean and easy search of files with a tabular left Navigation.
@@ -2,9 +2,6 @@
2
2
 
3
3
  require "English"
4
4
 
5
- #
6
- # Code coverage for ruby 1.9. Please check out README for a full introduction.
7
- #
8
5
  # Coverage may be inaccurate under JRUBY.
9
6
  if defined?(JRUBY_VERSION) && defined?(JRuby)
10
7
 
@@ -20,6 +17,10 @@ if defined?(JRUBY_VERSION) && defined?(JRuby)
20
17
  ' or set the "debug.fullTrace=true" option in your .jrubyrc'
21
18
  end
22
19
  end
20
+
21
+ #
22
+ # Code coverage for ruby. Please check out README for a full introduction.
23
+ #
23
24
  module SimpleCov
24
25
  class << self
25
26
  attr_accessor :running
@@ -44,35 +45,14 @@ module SimpleCov
44
45
  # Please check out the RDoc for SimpleCov::Configuration to find about available config options
45
46
  #
46
47
  def start(profile = nil, &block)
47
- if SimpleCov.usable?
48
- load_profile(profile) if profile
49
- configure(&block) if block_given?
50
- @result = nil
51
- self.running = true
52
- self.pid = Process.pid
53
- Coverage.start
54
- else
55
- warn "WARNING: SimpleCov is activated, but you're not running Ruby 1.9+ - no coverage analysis will happen"
56
- warn "Starting with SimpleCov 1.0.0, even no-op compatibility with Ruby <= 1.8 will be entirely dropped."
57
- false
58
- end
59
- end
60
-
61
- #
62
- # Finds files that were to be tracked but were not loaded and initializes
63
- # the line-by-line coverage to zero (if relevant) or nil (comments / whitespace etc).
64
- #
65
- def add_not_loaded_files(result)
66
- if tracked_files
67
- result = result.dup
68
- Dir[tracked_files].each do |file|
69
- absolute = File.expand_path(file)
70
-
71
- result[absolute] ||= LinesClassifier.new.classify(File.foreach(absolute))
72
- end
73
- end
48
+ require "coverage"
49
+ load_profile(profile) if profile
50
+ configure(&block) if block_given?
51
+ @result = nil
52
+ self.running = true
53
+ self.pid = Process.pid
74
54
 
75
- result
55
+ start_coverage_measurment
76
56
  end
77
57
 
78
58
  #
@@ -83,9 +63,8 @@ module SimpleCov
83
63
  return @result if result?
84
64
 
85
65
  # Collect our coverage result
86
- if running
87
- @result = SimpleCov::Result.new add_not_loaded_files(Coverage.result)
88
- end
66
+
67
+ process_coverage_result if running
89
68
 
90
69
  # If we're using merging of results, store the current result
91
70
  # first (if there is one), then merge the results and return those
@@ -147,22 +126,6 @@ module SimpleCov
147
126
  load_profile(name)
148
127
  end
149
128
 
150
- #
151
- # Checks whether we're on a proper version of Ruby (likely 1.9+) which
152
- # provides coverage support
153
- #
154
- def usable?
155
- return @usable if defined?(@usable) && !@usable.nil?
156
-
157
- @usable = begin
158
- require "coverage"
159
- require "simplecov/jruby_fix"
160
- true
161
- rescue LoadError
162
- false
163
- end
164
- end
165
-
166
129
  #
167
130
  # Clear out the previously cached .result. Primarily useful in testing
168
131
  #
@@ -206,8 +169,8 @@ module SimpleCov
206
169
 
207
170
  # Force exit with stored status (see github issue #5)
208
171
  # unless it's nil or 0 (see github issue #281)
209
- if exit_status && exit_status > 0
210
- $stderr.printf("SimpleCov failed with exit %d", exit_status)
172
+ if exit_status&.positive?
173
+ $stderr.printf("SimpleCov failed with exit %<exit_status>d\n", :exit_status => exit_status) if print_error_status
211
174
  Kernel.exit exit_status
212
175
  end
213
176
  end
@@ -220,11 +183,9 @@ module SimpleCov
220
183
  def process_result(result, exit_status)
221
184
  return exit_status if exit_status != SimpleCov::ExitCodes::SUCCESS # Existing errors
222
185
 
223
- covered_percent = result.covered_percent.round(2)
186
+ covered_percent = result.covered_percent.floor(2)
224
187
  result_exit_status = result_exit_status(result, covered_percent)
225
- if result_exit_status == SimpleCov::ExitCodes::SUCCESS # No result errors
226
- write_last_run(covered_percent)
227
- end
188
+ write_last_run(covered_percent) if result_exit_status == SimpleCov::ExitCodes::SUCCESS # No result errors
228
189
  final_result_process? ? result_exit_status : SimpleCov::ExitCodes::SUCCESS
229
190
  end
230
191
 
@@ -232,17 +193,30 @@ module SimpleCov
232
193
  #
233
194
  # rubocop:disable Metrics/MethodLength
234
195
  def result_exit_status(result, covered_percent)
235
- covered_percentages = result.covered_percentages.map { |percentage| percentage.round(2) }
196
+ covered_percentages = result.covered_percentages.map { |percentage| percentage.floor(2) }
236
197
  if covered_percent < SimpleCov.minimum_coverage
237
- $stderr.printf("Coverage (%.2f%%) is below the expected minimum coverage (%.2f%%).\n", covered_percent, SimpleCov.minimum_coverage)
198
+ $stderr.printf(
199
+ "Coverage (%<covered>.2f%%) is below the expected minimum coverage (%<minimum_coverage>.2f%%).\n",
200
+ :covered => covered_percent,
201
+ :minimum_coverage => SimpleCov.minimum_coverage
202
+ )
238
203
  SimpleCov::ExitCodes::MINIMUM_COVERAGE
239
204
  elsif covered_percentages.any? { |p| p < SimpleCov.minimum_coverage_by_file }
240
- $stderr.printf("File (%s) is only (%.2f%%) covered. This is below the expected minimum coverage per file of (%.2f%%).\n", result.least_covered_file, covered_percentages.min, SimpleCov.minimum_coverage_by_file)
205
+ $stderr.printf(
206
+ "File (%<file>s) is only (%<least_covered_percentage>.2f%%) covered. This is below the expected minimum coverage per file of (%<min_coverage>.2f%%).\n",
207
+ :file => result.least_covered_file,
208
+ :least_covered_percentage => covered_percentages.min,
209
+ :min_coverage => SimpleCov.minimum_coverage_by_file
210
+ )
241
211
  SimpleCov::ExitCodes::MINIMUM_COVERAGE
242
212
  elsif (last_run = SimpleCov::LastRun.read)
243
213
  coverage_diff = last_run["result"]["covered_percent"] - covered_percent
244
214
  if coverage_diff > SimpleCov.maximum_coverage_drop
245
- $stderr.printf("Coverage has dropped by %.2f%% since the last time (maximum allowed: %.2f%%).\n", coverage_diff, SimpleCov.maximum_coverage_drop)
215
+ $stderr.printf(
216
+ "Coverage has dropped by %<drop_percent>.2f%% since the last time (maximum allowed: %<max_drop>.2f%%).\n",
217
+ :drop_percent => coverage_diff,
218
+ :max_drop => SimpleCov.maximum_coverage_drop
219
+ )
246
220
  SimpleCov::ExitCodes::MAXIMUM_COVERAGE_DROP
247
221
  else
248
222
  SimpleCov::ExitCodes::SUCCESS
@@ -266,6 +240,7 @@ module SimpleCov
266
240
  #
267
241
  def wait_for_other_processes
268
242
  return unless defined?(ParallelTests) && final_result_process?
243
+
269
244
  ParallelTests.wait_for_other_processes_to_finish
270
245
  end
271
246
 
@@ -275,14 +250,117 @@ module SimpleCov
275
250
  def write_last_run(covered_percent)
276
251
  SimpleCov::LastRun.write(:result => {:covered_percent => covered_percent})
277
252
  end
253
+
254
+ private
255
+
256
+ #
257
+ # Trigger Coverage.start depends on given config coverage_criterion
258
+ #
259
+ # With Positive branch it supports all coverage measurement types
260
+ # With Negative branch it supports only line coverage measurement type
261
+ #
262
+ def start_coverage_measurment
263
+ # This blog post gives a good run down of the coverage criterias introduced
264
+ # in Ruby 2.5: https://blog.bigbinary.com/2018/04/11/ruby-2-5-supports-measuring-branch-and-method-coverages.html
265
+ # There is also a nice writeup of the different coverage criteria made in this
266
+ # comment https://github.com/colszowka/simplecov/pull/692#discussion_r281836176 :
267
+ # Ruby < 2.5:
268
+ # https://github.com/ruby/ruby/blob/v1_9_3_374/ext/coverage/coverage.c
269
+ # traditional mode (Array)
270
+ #
271
+ # Ruby 2.5:
272
+ # https://bugs.ruby-lang.org/issues/13901
273
+ # https://github.com/ruby/ruby/blob/v2_5_3/ext/coverage/coverage.c
274
+ # default: traditional/compatible mode (Array)
275
+ # :lines - like traditional mode but using Hash
276
+ # :branches
277
+ # :methods
278
+ # :all - same as lines + branches + methods
279
+ #
280
+ # Ruby >= 2.6:
281
+ # https://bugs.ruby-lang.org/issues/15022
282
+ # https://github.com/ruby/ruby/blob/v2_6_3/ext/coverage/coverage.c
283
+ # default: traditional/compatible mode (Array)
284
+ # :lines - like traditional mode but using Hash
285
+ # :branches
286
+ # :methods
287
+ # :oneshot_lines - can not be combined with lines
288
+ # :all - same as lines + branches + methods
289
+ #
290
+ if branch_coverage?
291
+ Coverage.start(:all)
292
+ else
293
+ Coverage.start
294
+ end
295
+ end
296
+
297
+ #
298
+ # Finds files that were to be tracked but were not loaded and initializes
299
+ # the line-by-line coverage to zero (if relevant) or nil (comments / whitespace etc).
300
+ #
301
+ def add_not_loaded_files(result)
302
+ if tracked_files
303
+ result = result.dup
304
+ Dir[tracked_files].each do |file|
305
+ absolute_path = File.expand_path(file)
306
+ result[absolute_path] ||= SimulateCoverage.call(absolute_path)
307
+ end
308
+ end
309
+
310
+ result
311
+ end
312
+
313
+ #
314
+ # Call steps that handle process coverage result
315
+ #
316
+ # @return [Hash]
317
+ #
318
+ def process_coverage_result
319
+ adapt_coverage_result
320
+ remove_useless_results
321
+ result_with_not_loaded_files
322
+ end
323
+
324
+ #
325
+ # Unite the result so it wouldn't matter what coverage type was called
326
+ #
327
+ # @return [Hash]
328
+ #
329
+ def adapt_coverage_result
330
+ @result = SimpleCov::ResultAdapter.call(Coverage.result)
331
+ end
332
+
333
+ #
334
+ # Filter coverage result
335
+ # The result before filter also has result of coverage for files
336
+ # are not related to the project like loaded gems coverage.
337
+ #
338
+ # @return [Hash]
339
+ #
340
+ def remove_useless_results
341
+ @result = SimpleCov::UselessResultsRemover.call(@result)
342
+ end
343
+
344
+ #
345
+ # Initialize result with files that are not included by coverage
346
+ # and added inside the config block
347
+ #
348
+ # @return [Hash]
349
+ #
350
+ def result_with_not_loaded_files
351
+ @result = SimpleCov::Result.new(add_not_loaded_files(@result))
352
+ end
278
353
  end
279
354
  end
280
355
 
281
356
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__)))
357
+ require "set"
282
358
  require "simplecov/configuration"
283
- SimpleCov.send :extend, SimpleCov::Configuration
359
+ SimpleCov.extend SimpleCov::Configuration
284
360
  require "simplecov/exit_codes"
285
361
  require "simplecov/profiles"
362
+ require "simplecov/source_file/line"
363
+ require "simplecov/source_file/branch"
286
364
  require "simplecov/source_file"
287
365
  require "simplecov/file_list"
288
366
  require "simplecov/result"
@@ -290,13 +368,17 @@ require "simplecov/filter"
290
368
  require "simplecov/formatter"
291
369
  require "simplecov/last_run"
292
370
  require "simplecov/lines_classifier"
293
- require "simplecov/raw_coverage"
294
371
  require "simplecov/result_merger"
295
372
  require "simplecov/command_guesser"
296
373
  require "simplecov/version"
374
+ require "simplecov/result_adapter"
375
+ require "simplecov/combine"
376
+ require "simplecov/combine/branches_combiner"
377
+ require "simplecov/combine/files_combiner"
378
+ require "simplecov/combine/lines_combiner"
379
+ require "simplecov/combine/results_combiner"
380
+ require "simplecov/useless_results_remover"
381
+ require "simplecov/simulate_coverage"
297
382
 
298
383
  # Load default config
299
384
  require "simplecov/defaults" unless ENV["SIMPLECOV_NO_DEFAULTS"]
300
-
301
- # Load Rails integration (only for Rails 3, see #113)
302
- require "simplecov/railtie" if defined? Rails::Railtie
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ # Functionally for combining coverage results
5
+ #
6
+ module Combine
7
+ module_function
8
+
9
+ #
10
+ # Combine two coverage based on the given combiner_module.
11
+ #
12
+ # Combiners should always be called throught his interface,
13
+ # as it takes care of short circuting of one of the coverages is nil.
14
+ #
15
+ # @return [Hash]
16
+ def combine(combiner_module, coverage_a, coverage_b)
17
+ return existing_coverage(coverage_a, coverage_b) if empty_coverage?(coverage_a, coverage_b)
18
+
19
+ combiner_module.combine(coverage_a, coverage_b)
20
+ end
21
+
22
+ def empty_coverage?(coverage_a, coverage_b)
23
+ !(coverage_a && coverage_b)
24
+ end
25
+
26
+ def existing_coverage(coverage_a, coverage_b)
27
+ coverage_a || coverage_b
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Combine
5
+ #
6
+ # Combine different branch coverage results on single file.
7
+ #
8
+ # Should be called through `SimpleCov.combine`.
9
+ module BranchesCombiner
10
+ module_function
11
+
12
+ #
13
+ # Return merged branches or the existed branche if other is missing.
14
+ #
15
+ # Branches inside files are always same if they exists, the difference only in coverage count.
16
+ # Branch coverage report for any conditional case is built from hash, it's key is a condition and
17
+ # it's body is a hash << keys from condition and value is coverage rate >>.
18
+ # ex: branches =>{ [:if, 3, 8, 6, 8, 36] => {[:then, 4, 8, 6, 8, 12] => 1, [:else, 5, 8, 6, 8, 36]=>2}, other conditions...}
19
+ # We create copy of result and update it values depending on the combined branches coverage values.
20
+ #
21
+ # @return [Hash]
22
+ #
23
+ def combine(coverage_a, coverage_b)
24
+ coverage_a.merge(coverage_b) do |_condition, branches_inside_a, branches_inside_b|
25
+ branches_inside_a.merge(branches_inside_b) do |_branch, a_count, b_count|
26
+ a_count + b_count
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Combine
5
+ #
6
+ # Handle combining two coverage results for same file
7
+ #
8
+ # Should be called through `SimpleCov.combine`.
9
+ module FilesCombiner
10
+ module_function
11
+
12
+ #
13
+ # Combines the results for 2 coverages of a file.
14
+ #
15
+ # @return [Hash]
16
+ #
17
+ def combine(coverage_a, coverage_b)
18
+ {
19
+ :lines => Combine.combine(LinesCombiner, coverage_a[:lines], coverage_b[:lines]),
20
+ :branches => Combine.combine(BranchesCombiner, coverage_a[:branches], coverage_b[:branches])
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Combine
5
+ #
6
+ # Combine two different lines coverage results on same file
7
+ #
8
+ # Should be called through `SimpleCov.combine`.
9
+ module LinesCombiner
10
+ module_function
11
+
12
+ def combine(coverage_a, coverage_b)
13
+ coverage_a
14
+ .zip(coverage_b)
15
+ .map do |coverage_a_val, coverage_b_val|
16
+ merge_line_coverage(coverage_a_val, coverage_b_val)
17
+ end
18
+ end
19
+
20
+ # Return depends on coverage in a specific line
21
+ #
22
+ # @param [Integer || nil] first_val
23
+ # @param [Integer || nil] second_val
24
+ #
25
+ # Logic:
26
+ #
27
+ # => nil + 0 = nil
28
+ # => nil + nil = nil
29
+ # => int + int = int
30
+ #
31
+ # @return [Integer || nil]
32
+ def merge_line_coverage(first_val, second_val)
33
+ sum = first_val.to_i + second_val.to_i
34
+
35
+ if sum.zero? && (first_val.nil? || second_val.nil?)
36
+ nil
37
+ else
38
+ sum
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Combine
5
+ # There might be reports from different kinds of tests,
6
+ # e.g. RSpec and Cucumber. We need to combine their results
7
+ # into unified one. This class does that.
8
+ # To unite the results on file basis, it leverages
9
+ # the combine of lines and branches inside each file within given results.
10
+ module ResultsCombiner
11
+ module_function
12
+
13
+ #
14
+ # Combine process explanation
15
+ # => ResultCombiner: define all present files between results and start combine on file level.
16
+ # ==> FileCombiner: collect result of next combine levels lines and branches.
17
+ # ===> LinesCombiner: combine lines results.
18
+ # ===> BranchesCombiner: combine branches results.
19
+ #
20
+ # @return [Hash]
21
+ #
22
+ def combine(*results)
23
+ results.reduce({}) do |result, next_result|
24
+ combine_result_sets(result, next_result)
25
+ end
26
+ end
27
+
28
+ #
29
+ # Manage combining results on files level
30
+ #
31
+ # @param [Hash] result_a
32
+ # @param [Hash] result_b
33
+ #
34
+ # @return [Hash]
35
+ #
36
+ def combine_result_sets(result_a, result_b)
37
+ results_files = result_a.keys | result_b.keys
38
+
39
+ results_files.each_with_object({}) do |file_name, combined_results|
40
+ combined_results[file_name] = combine_file_coverage(
41
+ result_a[file_name],
42
+ result_b[file_name]
43
+ )
44
+ end
45
+ end
46
+
47
+ #
48
+ # Combine two files coverage results
49
+ #
50
+ # @param [Hash] coverage_a
51
+ # @param [Hash] coverage_b
52
+ #
53
+ # @return [Hash]
54
+ #
55
+ def combine_file_coverage(coverage_a, coverage_b)
56
+ Combine.combine(Combine::FilesCombiner, coverage_a, coverage_b)
57
+ end
58
+ end
59
+ end
60
+ end