simplecov 0.17.1 → 0.18.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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