simplecov 0.17.0 → 0.18.3
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/CHANGELOG.md +104 -1
- data/CODE_OF_CONDUCT.md +76 -0
- data/README.md +275 -76
- data/doc/alternate-formatters.md +5 -0
- data/lib/minitest/simplecov_plugin.rb +11 -0
- data/lib/simplecov.rb +235 -62
- data/lib/simplecov/combine.rb +30 -0
- data/lib/simplecov/combine/branches_combiner.rb +32 -0
- data/lib/simplecov/combine/files_combiner.rb +24 -0
- data/lib/simplecov/combine/lines_combiner.rb +43 -0
- data/lib/simplecov/combine/results_combiner.rb +60 -0
- data/lib/simplecov/command_guesser.rb +6 -3
- data/lib/simplecov/configuration.rb +110 -9
- data/lib/simplecov/coverage_statistics.rb +56 -0
- data/lib/simplecov/defaults.rb +4 -5
- data/lib/simplecov/file_list.rb +66 -13
- data/lib/simplecov/filter.rb +2 -1
- data/lib/simplecov/formatter/multi_formatter.rb +2 -2
- data/lib/simplecov/formatter/simple_formatter.rb +4 -4
- data/lib/simplecov/last_run.rb +3 -1
- data/lib/simplecov/lines_classifier.rb +2 -2
- data/lib/simplecov/profiles.rb +9 -7
- data/lib/simplecov/result.rb +39 -6
- data/lib/simplecov/result_adapter.rb +30 -0
- data/lib/simplecov/result_merger.rb +18 -11
- data/lib/simplecov/simulate_coverage.rb +29 -0
- data/lib/simplecov/source_file.rb +272 -126
- data/lib/simplecov/source_file/branch.rb +84 -0
- data/lib/simplecov/source_file/line.rb +72 -0
- data/lib/simplecov/useless_results_remover.rb +16 -0
- data/lib/simplecov/version.rb +1 -1
- metadata +33 -166
- data/lib/simplecov/jruby_fix.rb +0 -44
- data/lib/simplecov/railtie.rb +0 -9
- data/lib/simplecov/railties/tasks.rake +0 -13
- data/lib/simplecov/raw_coverage.rb +0 -41
data/doc/alternate-formatters.md
CHANGED
@@ -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.
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# How minitest plugins. See https://github.com/colszowka/simplecov/pull/756 for why we need this.
|
4
|
+
# https://github.com/seattlerb/minitest#writing-extensions
|
5
|
+
module Minitest
|
6
|
+
def self.plugin_simplecov_init(_options)
|
7
|
+
Minitest.after_run do
|
8
|
+
SimpleCov.at_exit_behavior if SimpleCov.respond_to?(:at_exit_behavior)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/simplecov.rb
CHANGED
@@ -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,49 @@ 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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
48
|
+
require "coverage"
|
49
|
+
initial_setup(profile, &block)
|
50
|
+
@result = nil
|
51
|
+
self.pid = Process.pid
|
52
|
+
|
53
|
+
start_coverage_measurement
|
59
54
|
end
|
60
55
|
|
61
56
|
#
|
62
|
-
#
|
63
|
-
#
|
57
|
+
# Collate a series of SimpleCov result files into a single SimpleCov output.
|
58
|
+
# You can optionally specify configuration with a block:
|
59
|
+
# SimpleCov.collate Dir["simplecov-resultset-*/.resultset.json"]
|
60
|
+
# OR
|
61
|
+
# SimpleCov.collate Dir["simplecov-resultset-*/.resultset.json"], 'rails' # using rails profile
|
62
|
+
# OR
|
63
|
+
# SimpleCov.collate Dir["simplecov-resultset-*/.resultset.json"] do
|
64
|
+
# add_filter 'test'
|
65
|
+
# end
|
66
|
+
# OR
|
67
|
+
# SimpleCov.collate Dir["simplecov-resultset-*/.resultset.json"], 'rails' do
|
68
|
+
# add_filter 'test'
|
69
|
+
# end
|
64
70
|
#
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
71
|
+
# Please check out the RDoc for SimpleCov::Configuration to find about
|
72
|
+
# available config options, or checkout the README for more in-depth
|
73
|
+
# information about coverage collation
|
74
|
+
#
|
75
|
+
def collate(result_filenames, profile = nil, &block)
|
76
|
+
raise "There's no reports to be merged" if result_filenames.empty?
|
77
|
+
|
78
|
+
initial_setup(profile, &block)
|
70
79
|
|
71
|
-
|
80
|
+
results = result_filenames.flat_map do |filename|
|
81
|
+
# Re-create each included instance of SimpleCov::Result from the stored run data.
|
82
|
+
(JSON.parse(File.read(filename)) || {}).map do |command_name, coverage|
|
83
|
+
SimpleCov::Result.from_hash(command_name => coverage)
|
72
84
|
end
|
73
85
|
end
|
74
86
|
|
75
|
-
result
|
87
|
+
# Use the ResultMerger to produce a single, merged result, ready to use.
|
88
|
+
@result = SimpleCov::ResultMerger.merge_and_store(*results)
|
89
|
+
|
90
|
+
run_exit_tasks!
|
76
91
|
end
|
77
92
|
|
78
93
|
#
|
@@ -83,9 +98,8 @@ module SimpleCov
|
|
83
98
|
return @result if result?
|
84
99
|
|
85
100
|
# Collect our coverage result
|
86
|
-
|
87
|
-
|
88
|
-
end
|
101
|
+
|
102
|
+
process_coverage_result if running
|
89
103
|
|
90
104
|
# If we're using merging of results, store the current result
|
91
105
|
# first (if there is one), then merge the results and return those
|
@@ -147,22 +161,6 @@ module SimpleCov
|
|
147
161
|
load_profile(name)
|
148
162
|
end
|
149
163
|
|
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
164
|
#
|
167
165
|
# Clear out the previously cached .result. Primarily useful in testing
|
168
166
|
#
|
@@ -191,11 +189,21 @@ module SimpleCov
|
|
191
189
|
end
|
192
190
|
end
|
193
191
|
|
192
|
+
def at_exit_behavior
|
193
|
+
# If we are in a different process than called start, don't interfere.
|
194
|
+
return if SimpleCov.pid != Process.pid
|
195
|
+
|
196
|
+
# If SimpleCov is no longer running then don't run exit tasks
|
197
|
+
SimpleCov.run_exit_tasks! if SimpleCov.running
|
198
|
+
end
|
199
|
+
|
194
200
|
# @api private
|
195
201
|
#
|
196
202
|
# Called from at_exit block
|
197
203
|
#
|
198
204
|
def run_exit_tasks!
|
205
|
+
set_exit_exception
|
206
|
+
|
199
207
|
exit_status = SimpleCov.exit_status_from_exception
|
200
208
|
|
201
209
|
SimpleCov.at_exit.call
|
@@ -206,8 +214,8 @@ module SimpleCov
|
|
206
214
|
|
207
215
|
# Force exit with stored status (see github issue #5)
|
208
216
|
# unless it's nil or 0 (see github issue #281)
|
209
|
-
if exit_status
|
210
|
-
$stderr.printf("SimpleCov failed with exit
|
217
|
+
if exit_status&.positive?
|
218
|
+
$stderr.printf("SimpleCov failed with exit %<exit_status>d\n", exit_status: exit_status) if print_error_status
|
211
219
|
Kernel.exit exit_status
|
212
220
|
end
|
213
221
|
end
|
@@ -220,11 +228,9 @@ module SimpleCov
|
|
220
228
|
def process_result(result, exit_status)
|
221
229
|
return exit_status if exit_status != SimpleCov::ExitCodes::SUCCESS # Existing errors
|
222
230
|
|
223
|
-
covered_percent = result.covered_percent.
|
231
|
+
covered_percent = result.covered_percent.floor(2)
|
224
232
|
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
|
233
|
+
write_last_run(covered_percent) if result_exit_status == SimpleCov::ExitCodes::SUCCESS # No result errors
|
228
234
|
final_result_process? ? result_exit_status : SimpleCov::ExitCodes::SUCCESS
|
229
235
|
end
|
230
236
|
|
@@ -232,17 +238,26 @@ module SimpleCov
|
|
232
238
|
#
|
233
239
|
# rubocop:disable Metrics/MethodLength
|
234
240
|
def result_exit_status(result, covered_percent)
|
235
|
-
covered_percentages = result.covered_percentages.map { |percentage| percentage.
|
236
|
-
if
|
237
|
-
|
241
|
+
covered_percentages = result.covered_percentages.map { |percentage| percentage.floor(2) }
|
242
|
+
if (minimum_violations = minimum_coverage_violated(result)).any?
|
243
|
+
report_minimum_violated(minimum_violations)
|
238
244
|
SimpleCov::ExitCodes::MINIMUM_COVERAGE
|
239
245
|
elsif covered_percentages.any? { |p| p < SimpleCov.minimum_coverage_by_file }
|
240
|
-
$stderr.printf(
|
246
|
+
$stderr.printf(
|
247
|
+
"File (%<file>s) is only (%<least_covered_percentage>.2f%%) covered. This is below the expected minimum coverage per file of (%<min_coverage>.2f%%).\n",
|
248
|
+
file: result.least_covered_file,
|
249
|
+
least_covered_percentage: covered_percentages.min,
|
250
|
+
min_coverage: SimpleCov.minimum_coverage_by_file
|
251
|
+
)
|
241
252
|
SimpleCov::ExitCodes::MINIMUM_COVERAGE
|
242
253
|
elsif (last_run = SimpleCov::LastRun.read)
|
243
|
-
coverage_diff = last_run[
|
254
|
+
coverage_diff = last_run[:result][:covered_percent] - covered_percent
|
244
255
|
if coverage_diff > SimpleCov.maximum_coverage_drop
|
245
|
-
$stderr.printf(
|
256
|
+
$stderr.printf(
|
257
|
+
"Coverage has dropped by %<drop_percent>.2f%% since the last time (maximum allowed: %<max_drop>.2f%%).\n",
|
258
|
+
drop_percent: coverage_diff,
|
259
|
+
max_drop: SimpleCov.maximum_coverage_drop
|
260
|
+
)
|
246
261
|
SimpleCov::ExitCodes::MAXIMUM_COVERAGE_DROP
|
247
262
|
else
|
248
263
|
SimpleCov::ExitCodes::SUCCESS
|
@@ -257,7 +272,8 @@ module SimpleCov
|
|
257
272
|
# @api private
|
258
273
|
#
|
259
274
|
def final_result_process?
|
260
|
-
|
275
|
+
# checking for ENV["TEST_ENV_NUMBER"] to determine if the tess are being run in parallel
|
276
|
+
!defined?(ParallelTests) || !ENV["TEST_ENV_NUMBER"] || ParallelTests.number_of_running_processes <= 1
|
261
277
|
end
|
262
278
|
|
263
279
|
#
|
@@ -265,6 +281,7 @@ module SimpleCov
|
|
265
281
|
#
|
266
282
|
def wait_for_other_processes
|
267
283
|
return unless defined?(ParallelTests) && final_result_process?
|
284
|
+
|
268
285
|
ParallelTests.wait_for_other_processes_to_finish
|
269
286
|
end
|
270
287
|
|
@@ -272,16 +289,168 @@ module SimpleCov
|
|
272
289
|
# @api private
|
273
290
|
#
|
274
291
|
def write_last_run(covered_percent)
|
275
|
-
SimpleCov::LastRun.write(:
|
292
|
+
SimpleCov::LastRun.write(result: {covered_percent: covered_percent})
|
293
|
+
end
|
294
|
+
|
295
|
+
private
|
296
|
+
|
297
|
+
def initial_setup(profile, &block)
|
298
|
+
load_profile(profile) if profile
|
299
|
+
configure(&block) if block_given?
|
300
|
+
self.running = true
|
301
|
+
end
|
302
|
+
|
303
|
+
#
|
304
|
+
# Trigger Coverage.start depends on given config coverage_criterion
|
305
|
+
#
|
306
|
+
# With Positive branch it supports all coverage measurement types
|
307
|
+
# With Negative branch it supports only line coverage measurement type
|
308
|
+
#
|
309
|
+
def start_coverage_measurement
|
310
|
+
# This blog post gives a good run down of the coverage criterias introduced
|
311
|
+
# in Ruby 2.5: https://blog.bigbinary.com/2018/04/11/ruby-2-5-supports-measuring-branch-and-method-coverages.html
|
312
|
+
# There is also a nice writeup of the different coverage criteria made in this
|
313
|
+
# comment https://github.com/colszowka/simplecov/pull/692#discussion_r281836176 :
|
314
|
+
# Ruby < 2.5:
|
315
|
+
# https://github.com/ruby/ruby/blob/v1_9_3_374/ext/coverage/coverage.c
|
316
|
+
# traditional mode (Array)
|
317
|
+
#
|
318
|
+
# Ruby 2.5:
|
319
|
+
# https://bugs.ruby-lang.org/issues/13901
|
320
|
+
# https://github.com/ruby/ruby/blob/v2_5_3/ext/coverage/coverage.c
|
321
|
+
# default: traditional/compatible mode (Array)
|
322
|
+
# :lines - like traditional mode but using Hash
|
323
|
+
# :branches
|
324
|
+
# :methods
|
325
|
+
# :all - same as lines + branches + methods
|
326
|
+
#
|
327
|
+
# Ruby >= 2.6:
|
328
|
+
# https://bugs.ruby-lang.org/issues/15022
|
329
|
+
# https://github.com/ruby/ruby/blob/v2_6_3/ext/coverage/coverage.c
|
330
|
+
# default: traditional/compatible mode (Array)
|
331
|
+
# :lines - like traditional mode but using Hash
|
332
|
+
# :branches
|
333
|
+
# :methods
|
334
|
+
# :oneshot_lines - can not be combined with lines
|
335
|
+
# :all - same as lines + branches + methods
|
336
|
+
#
|
337
|
+
if coverage_start_arguments_supported?
|
338
|
+
start_coverage_with_criteria
|
339
|
+
else
|
340
|
+
Coverage.start
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def start_coverage_with_criteria
|
345
|
+
start_arguments = coverage_criteria.map do |criterion|
|
346
|
+
[lookup_corresponding_ruby_coverage_name(criterion), true]
|
347
|
+
end.to_h
|
348
|
+
|
349
|
+
Coverage.start(start_arguments)
|
350
|
+
end
|
351
|
+
|
352
|
+
CRITERION_TO_RUBY_COVERAGE = {
|
353
|
+
branch: :branches,
|
354
|
+
line: :lines
|
355
|
+
}.freeze
|
356
|
+
def lookup_corresponding_ruby_coverage_name(criterion)
|
357
|
+
CRITERION_TO_RUBY_COVERAGE.fetch(criterion)
|
358
|
+
end
|
359
|
+
|
360
|
+
#
|
361
|
+
# Finds files that were to be tracked but were not loaded and initializes
|
362
|
+
# the line-by-line coverage to zero (if relevant) or nil (comments / whitespace etc).
|
363
|
+
#
|
364
|
+
def add_not_loaded_files(result)
|
365
|
+
if tracked_files
|
366
|
+
result = result.dup
|
367
|
+
Dir[tracked_files].each do |file|
|
368
|
+
absolute_path = File.expand_path(file)
|
369
|
+
result[absolute_path] ||= SimulateCoverage.call(absolute_path)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
result
|
374
|
+
end
|
375
|
+
|
376
|
+
#
|
377
|
+
# Call steps that handle process coverage result
|
378
|
+
#
|
379
|
+
# @return [Hash]
|
380
|
+
#
|
381
|
+
def process_coverage_result
|
382
|
+
adapt_coverage_result
|
383
|
+
remove_useless_results
|
384
|
+
result_with_not_loaded_files
|
385
|
+
end
|
386
|
+
|
387
|
+
#
|
388
|
+
# Unite the result so it wouldn't matter what coverage type was called
|
389
|
+
#
|
390
|
+
# @return [Hash]
|
391
|
+
#
|
392
|
+
def adapt_coverage_result
|
393
|
+
@result = SimpleCov::ResultAdapter.call(Coverage.result)
|
394
|
+
end
|
395
|
+
|
396
|
+
#
|
397
|
+
# Filter coverage result
|
398
|
+
# The result before filter also has result of coverage for files
|
399
|
+
# are not related to the project like loaded gems coverage.
|
400
|
+
#
|
401
|
+
# @return [Hash]
|
402
|
+
#
|
403
|
+
def remove_useless_results
|
404
|
+
@result = SimpleCov::UselessResultsRemover.call(@result)
|
405
|
+
end
|
406
|
+
|
407
|
+
#
|
408
|
+
# Initialize result with files that are not included by coverage
|
409
|
+
# and added inside the config block
|
410
|
+
#
|
411
|
+
# @return [Hash]
|
412
|
+
#
|
413
|
+
def result_with_not_loaded_files
|
414
|
+
@result = SimpleCov::Result.new(add_not_loaded_files(@result))
|
415
|
+
end
|
416
|
+
|
417
|
+
def minimum_coverage_violated(result)
|
418
|
+
coverage_achieved = minimum_coverage.map do |criterion, percent|
|
419
|
+
{
|
420
|
+
criterion: criterion,
|
421
|
+
minimum_expected: percent,
|
422
|
+
actual: result.coverage_statistics[criterion].percent
|
423
|
+
}
|
424
|
+
end
|
425
|
+
|
426
|
+
coverage_achieved.select do |achieved|
|
427
|
+
achieved.fetch(:actual) < achieved.fetch(:minimum_expected)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def report_minimum_violated(violations)
|
432
|
+
violations.each do |violation|
|
433
|
+
$stderr.printf(
|
434
|
+
"%<criterion>s coverage (%<covered>.2f%%) is below the expected minimum coverage (%<minimum_coverage>.2f%%).\n",
|
435
|
+
covered: violation.fetch(:actual).floor(2),
|
436
|
+
minimum_coverage: violation.fetch(:minimum_expected),
|
437
|
+
criterion: violation.fetch(:criterion).capitalize
|
438
|
+
)
|
439
|
+
end
|
276
440
|
end
|
277
441
|
end
|
278
442
|
end
|
279
443
|
|
280
444
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__)))
|
445
|
+
require "set"
|
446
|
+
require "forwardable"
|
281
447
|
require "simplecov/configuration"
|
282
|
-
SimpleCov.
|
448
|
+
SimpleCov.extend SimpleCov::Configuration
|
449
|
+
require "simplecov/coverage_statistics"
|
283
450
|
require "simplecov/exit_codes"
|
284
451
|
require "simplecov/profiles"
|
452
|
+
require "simplecov/source_file/line"
|
453
|
+
require "simplecov/source_file/branch"
|
285
454
|
require "simplecov/source_file"
|
286
455
|
require "simplecov/file_list"
|
287
456
|
require "simplecov/result"
|
@@ -289,13 +458,17 @@ require "simplecov/filter"
|
|
289
458
|
require "simplecov/formatter"
|
290
459
|
require "simplecov/last_run"
|
291
460
|
require "simplecov/lines_classifier"
|
292
|
-
require "simplecov/raw_coverage"
|
293
461
|
require "simplecov/result_merger"
|
294
462
|
require "simplecov/command_guesser"
|
295
463
|
require "simplecov/version"
|
464
|
+
require "simplecov/result_adapter"
|
465
|
+
require "simplecov/combine"
|
466
|
+
require "simplecov/combine/branches_combiner"
|
467
|
+
require "simplecov/combine/files_combiner"
|
468
|
+
require "simplecov/combine/lines_combiner"
|
469
|
+
require "simplecov/combine/results_combiner"
|
470
|
+
require "simplecov/useless_results_remover"
|
471
|
+
require "simplecov/simulate_coverage"
|
296
472
|
|
297
473
|
# Load default config
|
298
474
|
require "simplecov/defaults" unless ENV["SIMPLECOV_NO_DEFAULTS"]
|
299
|
-
|
300
|
-
# Load Rails integration (only for Rails 3, see #113)
|
301
|
-
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 through this interface,
|
13
|
+
# as it takes care of short-circuiting 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
|