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