simplecov 1.1.1 → 1.3.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 +4 -4
- data/README.md +148 -5
- data/exe/simplecov +0 -3
- data/lib/minitest/simplecov_plugin.rb +12 -9
- data/lib/simplecov/atomic_file.rb +16 -19
- data/lib/simplecov/autostart.rb +5 -7
- data/lib/simplecov/baseline/parser.rb +58 -0
- data/lib/simplecov/baseline.rb +170 -0
- data/lib/simplecov/cli/affected/changed_files.rb +57 -0
- data/lib/simplecov/cli/affected/selection.rb +117 -0
- data/lib/simplecov/cli/affected.rb +148 -0
- data/lib/simplecov/cli/annotations.rb +166 -0
- data/lib/simplecov/cli/badge/svg.rb +70 -0
- data/lib/simplecov/cli/badge.rb +62 -0
- data/lib/simplecov/cli/clean.rb +23 -24
- data/lib/simplecov/cli/command_helpers.rb +55 -19
- data/lib/simplecov/cli/completions/scripts.rb +114 -0
- data/lib/simplecov/cli/completions.rb +83 -0
- data/lib/simplecov/cli/coverage.rb +25 -28
- data/lib/simplecov/cli/coverage_file.rb +58 -14
- data/lib/simplecov/cli/dead_code/output.rb +100 -0
- data/lib/simplecov/cli/dead_code.rb +139 -0
- data/lib/simplecov/cli/diff/output.rb +45 -0
- data/lib/simplecov/cli/diff.rb +35 -67
- data/lib/simplecov/cli/dotfile.rb +83 -38
- data/lib/simplecov/cli/git.rb +41 -0
- data/lib/simplecov/cli/history/output.rb +135 -0
- data/lib/simplecov/cli/history.rb +67 -0
- data/lib/simplecov/cli/merge.rb +34 -39
- data/lib/simplecov/cli/open.rb +12 -15
- data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
- data/lib/simplecov/cli/patch/output.rb +151 -0
- data/lib/simplecov/cli/patch.rb +175 -0
- data/lib/simplecov/cli/ratchet/output.rb +44 -0
- data/lib/simplecov/cli/ratchet.rb +83 -0
- data/lib/simplecov/cli/report.rb +18 -24
- data/lib/simplecov/cli/run.rb +8 -9
- data/lib/simplecov/cli/serve/report_preparer.rb +7 -6
- data/lib/simplecov/cli/serve/static_file_handler.rb +62 -56
- data/lib/simplecov/cli/serve.rb +27 -24
- data/lib/simplecov/cli/show/annotator.rb +87 -0
- data/lib/simplecov/cli/show/sweep.rb +36 -0
- data/lib/simplecov/cli/show.rb +133 -0
- data/lib/simplecov/cli/status/facts.rb +87 -0
- data/lib/simplecov/cli/status.rb +93 -0
- data/lib/simplecov/cli/tests/redundancy.rb +78 -0
- data/lib/simplecov/cli/tests.rb +148 -0
- data/lib/simplecov/cli/uncovered/misses.rb +33 -0
- data/lib/simplecov/cli/uncovered.rb +58 -34
- data/lib/simplecov/cli/usage.rb +153 -0
- data/lib/simplecov/cli/watch/live_report.rb +73 -0
- data/lib/simplecov/cli/watch/narrator.rb +55 -0
- data/lib/simplecov/cli/watch/poller.rb +45 -0
- data/lib/simplecov/cli/watch/session.rb +134 -0
- data/lib/simplecov/cli/watch/test_plan.rb +43 -0
- data/lib/simplecov/cli/watch.rb +85 -0
- data/lib/simplecov/cli.rb +43 -92
- data/lib/simplecov/color.rb +19 -37
- data/lib/simplecov/combine/branches_combiner.rb +7 -31
- data/lib/simplecov/combine/coverage_accumulator.rb +49 -142
- data/lib/simplecov/combine/identity_interner.rb +10 -13
- data/lib/simplecov/combine/interned_counts.rb +7 -9
- data/lib/simplecov/combine/lines_combiner.rb +20 -44
- data/lib/simplecov/combine/methods_combiner.rb +9 -35
- data/lib/simplecov/combine/results_combiner.rb +4 -21
- data/lib/simplecov/combine.rb +0 -6
- data/lib/simplecov/command_guesser.rb +20 -34
- data/lib/simplecov/configuration/baseline.rb +31 -0
- data/lib/simplecov/configuration/coverage.rb +91 -79
- data/lib/simplecov/configuration/coverage_criteria.rb +43 -54
- data/lib/simplecov/configuration/deprecations.rb +21 -0
- data/lib/simplecov/configuration/eval_coverage.rb +7 -11
- data/lib/simplecov/configuration/filters.rb +40 -73
- data/lib/simplecov/configuration/formatting.rb +74 -83
- data/lib/simplecov/configuration/groups.rb +19 -13
- data/lib/simplecov/configuration/history.rb +44 -0
- data/lib/simplecov/configuration/ignored_entries.rb +31 -22
- data/lib/simplecov/configuration/merging.rb +94 -62
- data/lib/simplecov/configuration/missed_caps.rb +51 -0
- data/lib/simplecov/configuration/production.rb +23 -0
- data/lib/simplecov/configuration/test_tracking.rb +48 -0
- data/lib/simplecov/configuration/thresholds.rb +33 -62
- data/lib/simplecov/configuration/view_coverage.rb +37 -0
- data/lib/simplecov/configuration.rb +70 -76
- data/lib/simplecov/configuration_error.rb +5 -0
- data/lib/simplecov/context_map/union.rb +81 -0
- data/lib/simplecov/context_map.rb +187 -0
- data/lib/simplecov/coverage_json.rb +2 -5
- data/lib/simplecov/coverage_statistics.rb +14 -24
- data/lib/simplecov/coverage_violations.rb +160 -65
- data/lib/simplecov/current_run.rb +61 -0
- data/lib/simplecov/defaults.rb +4 -23
- data/lib/simplecov/deprecation.rb +45 -27
- data/lib/simplecov/directive/erb.rb +44 -0
- data/lib/simplecov/directive/haml.rb +33 -0
- data/lib/simplecov/directive/indented_template.rb +37 -0
- data/lib/simplecov/directive/slim.rb +35 -0
- data/lib/simplecov/directive/template.rb +29 -0
- data/lib/simplecov/directive.rb +47 -62
- data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
- data/lib/simplecov/exit_codes/check.rb +17 -11
- data/lib/simplecov/exit_codes/exit_code_handling.rb +21 -8
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +7 -10
- data/lib/simplecov/exit_codes/maximum_missed_check.rb +26 -0
- data/lib/simplecov/exit_codes/maximum_missed_per_file_check.rb +34 -0
- data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +7 -12
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +11 -10
- data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +7 -9
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +16 -17
- data/lib/simplecov/exit_codes.rb +16 -10
- data/lib/simplecov/exit_handling.rb +94 -59
- data/lib/simplecov/file_list.rb +20 -51
- data/lib/simplecov/filter.rb +43 -66
- data/lib/simplecov/formatter/base.rb +37 -55
- data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
- data/lib/simplecov/formatter/coverage_json_writer.rb +29 -31
- data/lib/simplecov/formatter/html_formatter/public/index.html +15 -9
- data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +74 -17
- data/lib/simplecov/formatter/html_formatter.rb +24 -41
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +50 -16
- data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +43 -29
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +14 -10
- data/lib/simplecov/formatter/json_formatter.rb +7 -13
- data/lib/simplecov/formatter/multi_formatter.rb +5 -11
- data/lib/simplecov/formatter/simple_formatter.rb +2 -8
- data/lib/simplecov/formatter.rb +14 -10
- data/lib/simplecov/group_names.rb +13 -12
- data/lib/simplecov/history.rb +115 -0
- data/lib/simplecov/last_run.rb +6 -9
- data/lib/simplecov/lines_classifier.rb +31 -16
- data/lib/simplecov/load_global_config.rb +7 -7
- data/lib/simplecov/parallel_adapters/base.rb +13 -41
- data/lib/simplecov/parallel_adapters/generic.rb +7 -19
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +21 -42
- data/lib/simplecov/parallel_adapters.rb +24 -55
- data/lib/simplecov/parallel_coordination.rb +39 -49
- data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
- data/lib/simplecov/parallel_result_merger.rb +67 -126
- data/lib/simplecov/process.rb +14 -32
- data/lib/simplecov/production/error.rb +11 -0
- data/lib/simplecov/production/file_sink.rb +130 -0
- data/lib/simplecov/production.rb +249 -0
- data/lib/simplecov/profiles/rails.rb +4 -15
- data/lib/simplecov/profiles/root_filter.rb +0 -4
- data/lib/simplecov/profiles/strict.rb +11 -21
- data/lib/simplecov/profiles.rb +11 -33
- data/lib/simplecov/report_deferral.rb +11 -15
- data/lib/simplecov/report_stamp.rb +6 -9
- data/lib/simplecov/result/filter_config.rb +19 -0
- data/lib/simplecov/result/missing_source_files_reporter.rb +8 -9
- data/lib/simplecov/result/serialization.rb +43 -0
- data/lib/simplecov/result/source_file_builder.rb +7 -12
- data/lib/simplecov/result.rb +74 -117
- data/lib/simplecov/result_adapter.rb +56 -68
- data/lib/simplecov/result_merger/contexts.rb +22 -0
- data/lib/simplecov/result_merger/legacy_format_adapter.rb +4 -8
- data/lib/simplecov/result_merger/resultset_file.rb +20 -27
- data/lib/simplecov/result_merger/resultset_run_identity.rb +23 -25
- data/lib/simplecov/result_merger/resultset_store.rb +23 -14
- data/lib/simplecov/result_merger/unloaded_files.rb +24 -32
- data/lib/simplecov/result_merger.rb +59 -58
- data/lib/simplecov/result_processing.rb +92 -122
- data/lib/simplecov/run_identity.rb +16 -18
- data/lib/simplecov/simulate_coverage.rb +34 -50
- data/lib/simplecov/source_file/branch.rb +8 -33
- data/lib/simplecov/source_file/branch_builder.rb +28 -47
- data/lib/simplecov/source_file/builder_context.rb +13 -13
- data/lib/simplecov/source_file/line.rb +13 -33
- data/lib/simplecov/source_file/line_builder.rb +9 -15
- data/lib/simplecov/source_file/method.rb +3 -7
- data/lib/simplecov/source_file/method_builder.rb +6 -15
- data/lib/simplecov/source_file/ruby_data_parser.rb +59 -66
- data/lib/simplecov/source_file/skip_chunks.rb +29 -29
- data/lib/simplecov/source_file/source_loader.rb +45 -35
- data/lib/simplecov/source_file/statistics.rb +20 -9
- data/lib/simplecov/source_file.rb +21 -51
- data/lib/simplecov/static_coverage_extractor/condition_folding.rb +55 -139
- data/lib/simplecov/static_coverage_extractor/location_conventions.rb +76 -121
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +9 -26
- data/lib/simplecov/static_coverage_extractor/prism_compat.rb +15 -24
- data/lib/simplecov/static_coverage_extractor/value_position.rb +25 -38
- data/lib/simplecov/static_coverage_extractor/visitor.rb +45 -65
- data/lib/simplecov/static_coverage_extractor.rb +43 -75
- data/lib/simplecov/test_tracker/accessors.rb +34 -0
- data/lib/simplecov/test_tracker/constant_watch.rb +49 -0
- data/lib/simplecov/test_tracker/delta.rb +69 -0
- data/lib/simplecov/test_tracker/framework_hooks.rb +130 -0
- data/lib/simplecov/test_tracker.rb +162 -0
- data/lib/simplecov/unloaded_file_injector.rb +18 -31
- data/lib/simplecov/useless_results_remover.rb +7 -6
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov/view_coverage/template_compiler.rb +83 -0
- data/lib/simplecov/view_coverage.rb +55 -0
- data/lib/simplecov-html.rb +1 -0
- data/lib/simplecov.rb +57 -113
- data/lib/simplecov_json_formatter.rb +1 -0
- data/man/simplecov.1 +354 -0
- data/sig/simplecov.rbs +528 -595
- metadata +79 -6
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class TestTracker
|
|
5
|
+
# A one-shot watch that runs a callback the moment a constant of a given name
|
|
6
|
+
# is defined under a host module, via Ruby's `Module#const_added`.
|
|
7
|
+
# `TestTracker` uses it to install the Minitest wrapper in the ordering
|
|
8
|
+
# minitest 6 abandoned plugins for: SimpleCov starts first, as coverage must
|
|
9
|
+
# to see the app load, and minitest is required later, so nothing of
|
|
10
|
+
# minitest's exists yet to hook into.
|
|
11
|
+
#
|
|
12
|
+
# Each watch is its own Module so it can be prepended to the host's singleton
|
|
13
|
+
# class and compose with any `const_added` the host already has. A prepend
|
|
14
|
+
# cannot be removed, so after firing the watch stays in place as a name
|
|
15
|
+
# comparison per constant definition, which is as close to free as Ruby gets.
|
|
16
|
+
class ConstantWatch < Module
|
|
17
|
+
# No `super`: it would forward the implicit block even given explicit
|
|
18
|
+
# arguments, and `Module#initialize` module_evals any block it gets.
|
|
19
|
+
def initialize(name, &on_added)
|
|
20
|
+
@name = name
|
|
21
|
+
@on_added = on_added
|
|
22
|
+
watch = self #: ConstantWatch
|
|
23
|
+
define_method(:const_added) do |added|
|
|
24
|
+
super(added) # steep:ignore UnexpectedPositionalArgument
|
|
25
|
+
watch.notice(added)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# `host.const_added` runs with the host as self, so reaching this watch's
|
|
30
|
+
# state takes the closure. At runtime the `super` inside is the host's next
|
|
31
|
+
# `const_added`; Steep resolves a define_method block's super against the
|
|
32
|
+
# lexically enclosing method instead.
|
|
33
|
+
def attach(host)
|
|
34
|
+
host.singleton_class.prepend(self)
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def notice(added)
|
|
39
|
+
return unless added.equal?(@name)
|
|
40
|
+
|
|
41
|
+
callback = @on_added
|
|
42
|
+
return unless callback
|
|
43
|
+
|
|
44
|
+
@on_added = nil
|
|
45
|
+
callback.call
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class TestTracker
|
|
5
|
+
# The peek-diff arithmetic behind the tracker: which in-root lines grew
|
|
6
|
+
# between two coverage snapshots. This is the part a native
|
|
7
|
+
# `Coverage.supported?(:contexts)` engine would replace outright.
|
|
8
|
+
class Delta
|
|
9
|
+
def initialize(root_regex:)
|
|
10
|
+
@root_regex = root_regex
|
|
11
|
+
@in_root = {} #: Hash[String, bool]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Files outside the root are skipped; a file absent from `before` was loaded
|
|
15
|
+
# by the test itself, so everything it executed is the test's. The root match
|
|
16
|
+
# is memoized, so the regex runs once per file the process ever loads rather
|
|
17
|
+
# than once per file per test.
|
|
18
|
+
def call(before, after)
|
|
19
|
+
changed = {} #: Hash[String, Integer]
|
|
20
|
+
after.each do |path, file_coverage|
|
|
21
|
+
in_root = @in_root.fetch(path) { @in_root[path] = @root_regex.match?(path) }
|
|
22
|
+
next unless in_root
|
|
23
|
+
|
|
24
|
+
bitmap = line_delta(lines_in(before[path]), lines_in(file_coverage))
|
|
25
|
+
changed[path] = bitmap unless bitmap.zero?
|
|
26
|
+
end
|
|
27
|
+
changed
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# A peek's per-file data is a criteria Hash when Coverage was started with a
|
|
33
|
+
# criteria hash, and a bare Array under the older lines-only form. No lines
|
|
34
|
+
# at all, from a branch-only or method-only start, means nothing to diff.
|
|
35
|
+
def lines_in(file_coverage)
|
|
36
|
+
case file_coverage
|
|
37
|
+
when Hash then file_coverage[:lines]
|
|
38
|
+
when Array then file_coverage
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The equality check is the overwhelmingly common case, the test never
|
|
43
|
+
# entering this file: an Array compare is fast C, the per-line loop is not.
|
|
44
|
+
#
|
|
45
|
+
# The bitmap is built as a binary string in one pass, so the
|
|
46
|
+
# arbitrary-precision math happens once per file rather than one bignum OR
|
|
47
|
+
# per executed line. The string reads lowest line first and the number wants
|
|
48
|
+
# it the other way round; the leading zero keeps the parse valid for an
|
|
49
|
+
# all-zero delta without changing any value.
|
|
50
|
+
def line_delta(before_lines, after_lines)
|
|
51
|
+
return 0 unless after_lines
|
|
52
|
+
return 0 if before_lines.eql?(after_lines)
|
|
53
|
+
|
|
54
|
+
bits = +""
|
|
55
|
+
after_lines.each_with_index do |count, index|
|
|
56
|
+
bits << (grew?(count, before_lines&.at(index)) ? "1" : "0")
|
|
57
|
+
end
|
|
58
|
+
Integer("0#{bits.reverse}", 2)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# `previous` is nil both for a non-executable line, whose count is nil too,
|
|
62
|
+
# and for a file the test itself loaded, where every executed line is the
|
|
63
|
+
# test's.
|
|
64
|
+
def grew?(count, previous)
|
|
65
|
+
!count.nil? && count > (previous || 0)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
# The test-framework side of per-test tracking: how the wrapper gets
|
|
5
|
+
# installed into RSpec and Minitest, and how each framework's tests are
|
|
6
|
+
# identified. The recording itself lives in test_tracker.rb.
|
|
7
|
+
class TestTracker
|
|
8
|
+
class << self
|
|
9
|
+
def install_framework_hooks
|
|
10
|
+
install_rspec_hook
|
|
11
|
+
install_minitest_hook_when_loaded
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Wraps every RSpec example in a `track_test` call. Called from
|
|
15
|
+
# `SimpleCov.start_tracking`, where RSpec is already loaded when the suite
|
|
16
|
+
# is an RSpec suite. Installs once per process no matter how often
|
|
17
|
+
# tracking is restarted.
|
|
18
|
+
def install_rspec_hook(rspec = rspec_module)
|
|
19
|
+
return unless rspec
|
|
20
|
+
return if @rspec_hook_installed
|
|
21
|
+
|
|
22
|
+
@rspec_hook_installed = true
|
|
23
|
+
rspec.configure do |config|
|
|
24
|
+
# The block runs instance-eval'd in the example group, so everything it
|
|
25
|
+
# touches must be fully qualified.
|
|
26
|
+
config.around do |example|
|
|
27
|
+
SimpleCov.track_test(TestTracker.rspec_example_id(example)) { example.run }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The RSpec this process has, if it has one. A Minitest-only suite has
|
|
33
|
+
# none, and no example can make this process into one: hiding the RSpec
|
|
34
|
+
# constant takes down the very runner asking the question.
|
|
35
|
+
# simplecov:disable branch — the RSpec-less arm is unreachable from an RSpec-driven suite
|
|
36
|
+
# mutant:disable
|
|
37
|
+
def rspec_module
|
|
38
|
+
::RSpec if defined?(::RSpec.configure)
|
|
39
|
+
end
|
|
40
|
+
# simplecov:enable branch
|
|
41
|
+
|
|
42
|
+
# @api private -- test seam, so specs can exercise installation repeatedly
|
|
43
|
+
# without touching the process-wide guard for real.
|
|
44
|
+
def reset_rspec_hook!
|
|
45
|
+
@rspec_hook_installed = false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Called by the minitest plugin under minitest 5, and by the constant
|
|
49
|
+
# watch below under minitest 6, whose autorun no longer discovers
|
|
50
|
+
# plugins. Prepending a module already in the chain is a no-op, which is
|
|
51
|
+
# the whole of the idempotence.
|
|
52
|
+
def install_minitest_hook(test_case = (Minitest::Test if defined?(Minitest::Test)))
|
|
53
|
+
return if test_case.nil?
|
|
54
|
+
|
|
55
|
+
test_case.prepend(MinitestRun)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Installs the Minitest wrapper now when Minitest is already loaded, and
|
|
59
|
+
# otherwise the moment `Minitest::Test` is defined. The deferred half is
|
|
60
|
+
# what covers minitest 6 under the canonical helper ordering (SimpleCov
|
|
61
|
+
# first, so coverage sees the app load; minitest after), where no plugin
|
|
62
|
+
# ever fires. `root` is Object outside of tests.
|
|
63
|
+
def install_minitest_hook_when_loaded(root = Object)
|
|
64
|
+
minitest = loaded_const(root, :Minitest)
|
|
65
|
+
test_case = minitest && loaded_const(minitest, :Test)
|
|
66
|
+
return install_minitest_hook(test_case) if test_case
|
|
67
|
+
return watch_for_minitest_test(minitest) if minitest
|
|
68
|
+
|
|
69
|
+
ConstantWatch.new(:Minitest) do
|
|
70
|
+
# `loaded_const` rather than the block capturing a module: the module
|
|
71
|
+
# does not exist yet when the watch is armed.
|
|
72
|
+
watch_for_minitest_test(loaded_const(root, :Minitest))
|
|
73
|
+
end.attach(root)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Relative the way RSpec prints it (`./spec/a_spec.rb:12` becomes
|
|
77
|
+
# `spec/a_spec.rb:12`).
|
|
78
|
+
def rspec_example_id(example)
|
|
79
|
+
example.metadata.fetch(:location).delete_prefix("./")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# An autoload declaration fires `const_added` without the module existing
|
|
83
|
+
# yet; that arrangement is left to the plugin, so nil means nothing to
|
|
84
|
+
# watch.
|
|
85
|
+
def watch_for_minitest_test(minitest)
|
|
86
|
+
return unless minitest
|
|
87
|
+
|
|
88
|
+
ConstantWatch.new(:Test) { install_minitest_hook(loaded_const(minitest, :Test)) }.attach(minitest)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The constant when it is genuinely loaded, nil when absent or merely
|
|
92
|
+
# declared for autoload: const_get would force such a load, and installing
|
|
93
|
+
# a coverage hook must never be what requires a test framework.
|
|
94
|
+
def loaded_const(mod, name)
|
|
95
|
+
return nil unless mod.const_defined?(name, false)
|
|
96
|
+
return nil if mod.autoload?(name, false)
|
|
97
|
+
|
|
98
|
+
mod.const_get(name)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# A Minitest test's identity: the `path:line` where the test method is
|
|
102
|
+
# defined, relative to `SimpleCov.root`. Falls back to
|
|
103
|
+
# `ClassName#method_name` when the method has no source location.
|
|
104
|
+
def minitest_test_id(test)
|
|
105
|
+
path, line = definition_site(test)
|
|
106
|
+
return "#{test.class}##{test.name}" unless path
|
|
107
|
+
|
|
108
|
+
"#{path.delete_prefix(File.join(SimpleCov.root, ""))}:#{line}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# A method defined in C or through an eval without a filename has no site,
|
|
112
|
+
# and a runner exotic enough to name a method it never defined deserves
|
|
113
|
+
# an id over an exception out of the middle of its run.
|
|
114
|
+
def definition_site(test)
|
|
115
|
+
test.method(test.name).source_location
|
|
116
|
+
rescue NameError
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Prepended to `Minitest::Test` by `install_minitest_hook`, wrapping each
|
|
122
|
+
# test method run, setup and teardown included: a line only a setup block
|
|
123
|
+
# reaches is still this test's line.
|
|
124
|
+
module MinitestRun
|
|
125
|
+
def run
|
|
126
|
+
SimpleCov.track_test(TestTracker.minitest_test_id(self)) { super }
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
#
|
|
5
|
+
# Records each test as a context in a `ContextMap`. Enabled by the
|
|
6
|
+
# `track_tests` configuration; installed into RSpec by
|
|
7
|
+
# `SimpleCov.start_tracking` and into Minitest by the minitest plugin.
|
|
8
|
+
# Any other runner can wrap its own units of work with
|
|
9
|
+
# `SimpleCov.track_test`.
|
|
10
|
+
#
|
|
11
|
+
# The contract is `track(id) { }`: attribute the lines the block executed to
|
|
12
|
+
# `id`, or abstain (`recorded_map` nil), never guess. How the lines are
|
|
13
|
+
# obtained is this class's private business, and today's engine is a
|
|
14
|
+
# peek-diff: sample `Coverage.peek_result` around the block and take the
|
|
15
|
+
# lines whose execution count grew. If Ruby's Coverage ever grows native
|
|
16
|
+
# contexts the engine is what gets replaced; the contract, the `ContextMap`,
|
|
17
|
+
# and everything downstream stay.
|
|
18
|
+
#
|
|
19
|
+
# Two limits are the peek-diff engine's, not the contract's. It needs
|
|
20
|
+
# per-line execution counts, which is why `track_tests` refuses to run under
|
|
21
|
+
# `:oneshot_line`. And tests overlapping across threads cannot be told
|
|
22
|
+
# apart, because the counters being diffed are process-global.
|
|
23
|
+
#
|
|
24
|
+
#
|
|
25
|
+
class TestTracker
|
|
26
|
+
attr_reader :map
|
|
27
|
+
|
|
28
|
+
# The context id everything since the last boundary belongs to, and the
|
|
29
|
+
# peek that boundary took.
|
|
30
|
+
Segment = Struct.new(:id, :opening)
|
|
31
|
+
private_constant :Segment
|
|
32
|
+
|
|
33
|
+
def initialize(root_regex: UselessResultsRemover.root_regex, granularity: :test)
|
|
34
|
+
unless Configuration::TRACK_TESTS_GRANULARITIES.include?(granularity)
|
|
35
|
+
raise ArgumentError, "unknown granularity #{granularity.inspect}, " \
|
|
36
|
+
"expected one of #{Configuration::TRACK_TESTS_GRANULARITIES.inspect}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@map = ContextMap.new
|
|
40
|
+
# Only files under the project root are attributed, judged by the same
|
|
41
|
+
# regex the report's own universe is rooted with. A peek covers every
|
|
42
|
+
# file the process loaded, every gem included, and diffing all of them on
|
|
43
|
+
# every test is the cost that would make tracking unaffordable.
|
|
44
|
+
@delta = Delta.new(root_regex: root_regex)
|
|
45
|
+
@granularity = granularity
|
|
46
|
+
@lock = Mutex.new
|
|
47
|
+
@depth = 0
|
|
48
|
+
@poisoned = false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Records the lines the block executed as covered by `test_id`'s context
|
|
52
|
+
# (the test itself at :test granularity, its file at :file). A failing
|
|
53
|
+
# test's lines are still attributed when its segment closes.
|
|
54
|
+
def track(test_id)
|
|
55
|
+
id = context_id(test_id)
|
|
56
|
+
nested_opening = begin_track(id)
|
|
57
|
+
begin
|
|
58
|
+
yield
|
|
59
|
+
ensure
|
|
60
|
+
settle(id, nested_opening)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def poisoned?
|
|
65
|
+
@poisoned
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# nil once poisoned: a poisoned recording must vanish like one that never
|
|
69
|
+
# happened, so the merge's all-or-nothing rule drops the run's map instead
|
|
70
|
+
# of keeping a wrong one. Flushes the open segment, so the answer carries
|
|
71
|
+
# every finished track. At process exit `Coverage.result` has already
|
|
72
|
+
# stopped measurement, so result-building passes the final coverage it just
|
|
73
|
+
# took as the closing snapshot instead of peeking again.
|
|
74
|
+
def recorded_map(closing: nil)
|
|
75
|
+
return nil if poisoned?
|
|
76
|
+
|
|
77
|
+
flush_segment(closing)
|
|
78
|
+
@map
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# Same-thread reentrancy is fine, since a line the inner test executed was
|
|
84
|
+
# executed on the outer test's watch too, but a second thread means
|
|
85
|
+
# concurrent tests and `Coverage`'s counters are process-global: their
|
|
86
|
+
# deltas cannot be told apart, so recording shuts off rather than
|
|
87
|
+
# misattribute.
|
|
88
|
+
def begin_track(id)
|
|
89
|
+
outermost = note_entry
|
|
90
|
+
return nil if @poisoned
|
|
91
|
+
return Coverage.peek_result unless outermost
|
|
92
|
+
|
|
93
|
+
open_segment(id)
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def note_entry
|
|
98
|
+
@lock.synchronize do
|
|
99
|
+
if @depth.zero?
|
|
100
|
+
@owner = Thread.current
|
|
101
|
+
elsif !@owner.equal?(Thread.current)
|
|
102
|
+
poison
|
|
103
|
+
end
|
|
104
|
+
@depth += 1
|
|
105
|
+
@depth.equal?(1)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# A nested track records immediately against its own opening peek; an
|
|
110
|
+
# outermost one leaves its segment open for the next boundary. The owner
|
|
111
|
+
# needs no clearing: the next track to find the depth back at zero takes
|
|
112
|
+
# ownership itself.
|
|
113
|
+
def settle(id, nested_opening)
|
|
114
|
+
@map.record(id, @delta.call(nested_opening, Coverage.peek_result)) if nested_opening && !@poisoned
|
|
115
|
+
ensure
|
|
116
|
+
@lock.synchronize { @depth -= 1 }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Peeking is the dominant cost of tracking (the copy covers every file the
|
|
120
|
+
# process loaded, branch and method tables included), so recording settles
|
|
121
|
+
# at segment boundaries: consecutive tracks with one context id share one
|
|
122
|
+
# open segment and pay nothing in between. In-root code that runs between
|
|
123
|
+
# two segments is attributed to the later one, the documented price.
|
|
124
|
+
def open_segment(id)
|
|
125
|
+
return if @segment&.id.eql?(id)
|
|
126
|
+
|
|
127
|
+
boundary = Coverage.peek_result
|
|
128
|
+
close_segment(boundary)
|
|
129
|
+
@segment = Segment.new(id, boundary)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def close_segment(closing)
|
|
133
|
+
segment = @segment
|
|
134
|
+
@map.record(segment.id, @delta.call(segment.opening, closing)) if segment
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def flush_segment(closing)
|
|
138
|
+
return unless @segment
|
|
139
|
+
|
|
140
|
+
close_segment(closing || Coverage.peek_result)
|
|
141
|
+
@segment = nil
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# An id with no `:line` tail (an exotic runner's fallback) stays whole.
|
|
145
|
+
def context_id(test_id)
|
|
146
|
+
return test_id unless @granularity.equal?(:file)
|
|
147
|
+
|
|
148
|
+
path, sep, tail = test_id.rpartition(":")
|
|
149
|
+
(sep.empty? || !tail.match?(/\A\d+\z/)) ? test_id : path
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def poison
|
|
153
|
+
return if @poisoned
|
|
154
|
+
|
|
155
|
+
@poisoned = true
|
|
156
|
+
return unless SimpleCov.print_errors
|
|
157
|
+
|
|
158
|
+
warn "[SimpleCov]: tests are running concurrently in this process (parallel threads), " \
|
|
159
|
+
"so per-test coverage cannot be attributed. Dropping this process's test map."
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -3,46 +3,36 @@
|
|
|
3
3
|
require_relative "simulate_coverage"
|
|
4
4
|
|
|
5
5
|
module SimpleCov
|
|
6
|
-
# Fills in coverage for files that were tracked
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# This is the mechanism; the merge-time policy of *when* to inject and
|
|
11
|
-
# which criteria the simulated files carry lives in
|
|
12
|
-
# `ResultMerger::UnloadedFiles` — easy names to confuse.
|
|
6
|
+
# Fills in coverage for files that were tracked but never loaded, so they
|
|
7
|
+
# count toward the denominators instead of being absent from the report.
|
|
8
|
+
# This is the mechanism; the merge-time policy of when to inject lives in
|
|
9
|
+
# `ResultMerger::UnloadedFiles`.
|
|
13
10
|
#
|
|
14
11
|
# Everything arrives as arguments rather than being read from the SimpleCov
|
|
15
|
-
# singleton
|
|
16
|
-
#
|
|
17
|
-
# configuration: a standalone `collate` never ran `SimpleCov.start`, so it has
|
|
18
|
-
# no `cover` glob of its own and takes the tracked paths from the resultsets
|
|
19
|
-
# instead. See #1250.
|
|
12
|
+
# singleton, because the merge performs this injection on behalf of
|
|
13
|
+
# processes whose configuration it does not necessarily share (#1250).
|
|
20
14
|
module UnloadedFileInjector
|
|
21
|
-
|
|
15
|
+
extend self
|
|
22
16
|
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
17
|
+
# Globs expand relative to `root` rather than `Dir.pwd`: runners that chdir
|
|
18
|
+
# would otherwise silently miss files and produce a different set per
|
|
19
|
+
# environment (#1106).
|
|
20
|
+
#
|
|
27
21
|
# `reject:` are the producing process's path-decidable filters. Paths its
|
|
28
22
|
# own report would exclude must not be recorded as tracked, or a merge that
|
|
29
|
-
# does not share the configuration would simulate them back in.
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
# source file and may consult coverage that does not exist yet, so those
|
|
33
|
-
# still fall to the merging process's filter chain. See #1250.
|
|
23
|
+
# does not share the configuration would simulate them back in. A block
|
|
24
|
+
# filter is handed the source file and may consult coverage that does not
|
|
25
|
+
# exist yet, so those still fall to the merging process's filter chain.
|
|
34
26
|
def discover(globs, root:, reject: [])
|
|
35
27
|
paths = globs.compact
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
.flat_map { |glob| Dir.glob(glob, base: root) }
|
|
29
|
+
.uniq
|
|
30
|
+
.map { |path| File.expand_path(path, root) }
|
|
39
31
|
return paths if reject.empty?
|
|
40
32
|
|
|
41
33
|
paths.reject { |path| rejected?(path, reject) }
|
|
42
34
|
end
|
|
43
35
|
|
|
44
|
-
# `SourceFile` reads source and builds lines lazily, so a path-only filter
|
|
45
|
-
# answers without anything here touching disk.
|
|
46
36
|
def rejected?(path, filters)
|
|
47
37
|
candidate = SourceFile.new(path, NO_COVERAGE_YET, loaded: false)
|
|
48
38
|
filters.any? { |filter| filter.matches?(candidate) }
|
|
@@ -52,12 +42,9 @@ module SimpleCov
|
|
|
52
42
|
NO_COVERAGE_YET = no_coverage_yet.freeze
|
|
53
43
|
private_constant :NO_COVERAGE_YET
|
|
54
44
|
|
|
55
|
-
#
|
|
56
|
-
# Paths that are present are left alone, whoever put them there, which is
|
|
45
|
+
# Paths already present are left alone, whoever put them there, which is
|
|
57
46
|
# what lets this run over resultsets a previous SimpleCov already injected
|
|
58
47
|
# into without double-counting or overwriting real data.
|
|
59
|
-
#
|
|
60
|
-
# Returns the augmented coverage and the set of paths that were added.
|
|
61
48
|
def call(coverage, paths, synthesize:, lines:)
|
|
62
49
|
injected = Set.new
|
|
63
50
|
augmented = coverage.dup
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# run never make it into the formatted result.
|
|
4
|
+
# Drops coverage entries whose paths live outside `SimpleCov.root`, so vendored
|
|
5
|
+
# gems, stdlib files, and anything else touched during the run never make it
|
|
6
|
+
# into the formatted result.
|
|
8
7
|
module UselessResultsRemover
|
|
9
8
|
def self.call(coverage_result)
|
|
10
9
|
coverage_result.select { |path, _coverage| path.match?(root_regex) }
|
|
11
10
|
end
|
|
12
11
|
|
|
13
|
-
# The `/i` flag covers case-insensitive matches on Windows
|
|
12
|
+
# The `/i` flag covers case-insensitive matches on Windows and macOS-HFS+,
|
|
14
13
|
# where the on-disk path's case can differ from `SimpleCov.root`'s.
|
|
15
14
|
def self.root_regex
|
|
16
15
|
root = SimpleCov.root
|
|
17
|
-
return @root_regex if root
|
|
16
|
+
return @root_regex if root.eql?(@root_regex_root)
|
|
18
17
|
|
|
19
18
|
@root_regex_root = root
|
|
20
19
|
@root_regex = /\A#{Regexp.escape(root.chomp(File::SEPARATOR) + File::SEPARATOR)}/i
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
def self.root_regx
|
|
23
|
+
Deprecation.warn("`SimpleCov::UselessResultsRemover.root_regx` is deprecated. " \
|
|
24
|
+
"Replace with `root_regex`.")
|
|
24
25
|
root_regex
|
|
25
26
|
end
|
|
26
27
|
end
|
data/lib/simplecov/version.rb
CHANGED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
module ViewCoverage
|
|
5
|
+
#
|
|
6
|
+
# Compiles a single ActionView template: builds a `Template` for the file on
|
|
7
|
+
# disk and defines its method into a throwaway module, without ever
|
|
8
|
+
# rendering it.
|
|
9
|
+
#
|
|
10
|
+
# Defining the method is the whole trick. `Template#compile` hands the
|
|
11
|
+
# handler's generated Ruby to `module_eval(source, identifier, offset)`,
|
|
12
|
+
# and evaluating a `def` records the body as executable-but-unexecuted
|
|
13
|
+
# rather than running it, so `Coverage` comes away with a zeroed line array
|
|
14
|
+
# and a full set of branch tuples for the template.
|
|
15
|
+
#
|
|
16
|
+
# Never compile a template the suite already rendered. Evaluating the same
|
|
17
|
+
# identifier a second time replaces that file's entry in `Coverage` rather
|
|
18
|
+
# than adding to it, so the recompile would zero the hits the run actually
|
|
19
|
+
# earned and leave a duplicate set of branch tuples behind. `ViewCoverage`
|
|
20
|
+
# filters those out before calling here.
|
|
21
|
+
#
|
|
22
|
+
module TemplateCompiler
|
|
23
|
+
extend self
|
|
24
|
+
|
|
25
|
+
# False in any project that doesn't load ActionView, which is the common
|
|
26
|
+
# case and not an error: view coverage simply has nothing to do there.
|
|
27
|
+
def available?
|
|
28
|
+
!defined?(::ActionView::Template).nil?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Answers whether the compile produced coverage data. A template that
|
|
32
|
+
# doesn't compile can't render either, so the suite has bigger problems
|
|
33
|
+
# than its coverage, but the report is not the place to discover that and
|
|
34
|
+
# a broken view must not take the whole run down.
|
|
35
|
+
def call(path)
|
|
36
|
+
template = build_template(path, File.binread(path))
|
|
37
|
+
return false unless template
|
|
38
|
+
|
|
39
|
+
compile_into_throwaway_module(template)
|
|
40
|
+
true
|
|
41
|
+
rescue StandardError, ScriptError => e
|
|
42
|
+
warn "[SimpleCov]: Skipping #{path}, which did not compile (#{e.class})"
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# ActionView keeps `compile` private, and the module it compiles into is
|
|
47
|
+
# thrown away: what is wanted is the coverage the compile records, not the
|
|
48
|
+
# method it defines.
|
|
49
|
+
#
|
|
50
|
+
# mutant:disable -- `send` and `__send__` are the same call.
|
|
51
|
+
def compile_into_throwaway_module(template)
|
|
52
|
+
template.send(:compile, Module.new)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The format the template renders, taken from the extension in front of the
|
|
56
|
+
# handler's: `show.html.erb` is `:html`. It reaches the handler's codegen
|
|
57
|
+
# (an HTML template escapes interpolations, a text one does not), but
|
|
58
|
+
# never the line structure, so a template named without one compiles fine
|
|
59
|
+
# as nil.
|
|
60
|
+
def format_for(path)
|
|
61
|
+
segments = File.basename(path).split(".")
|
|
62
|
+
return nil if segments.length < 3
|
|
63
|
+
|
|
64
|
+
segments.fetch(-2).to_sym
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The single place ActionView is named. The handler lookup is the one the
|
|
68
|
+
# resolver performs, so a template language the project has registered a
|
|
69
|
+
# handler for compiles here the way it does when rendered. Nil when
|
|
70
|
+
# nothing is registered for the extension, which is how a default glob
|
|
71
|
+
# naming `.haml` behaves in a project that has no Haml: ActionView would
|
|
72
|
+
# hand back the raw handler and the template would report as static text.
|
|
73
|
+
def build_template(path, source)
|
|
74
|
+
extension = File.extname(path).delete_prefix(".")
|
|
75
|
+
handler = ActionView::Template.registered_template_handler(extension)
|
|
76
|
+
return nil unless handler
|
|
77
|
+
|
|
78
|
+
no_locals = [] #: Array[Symbol]
|
|
79
|
+
ActionView::Template.new(source, path, handler, locals: no_locals, format: format_for(path))
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "view_coverage/template_compiler"
|
|
4
|
+
|
|
5
|
+
module SimpleCov
|
|
6
|
+
# Brings ActionView templates into the report, for projects that opt in with
|
|
7
|
+
# `cover_views`.
|
|
8
|
+
#
|
|
9
|
+
# Rendering a template is already enough to measure it: ActionView compiles
|
|
10
|
+
# each one with `module_eval(source, identifier, offset)` where `identifier`
|
|
11
|
+
# is the template's own path, and the offset cancels the `def` line its
|
|
12
|
+
# wrapper adds, so eval coverage attributes the generated code back to the
|
|
13
|
+
# `.erb` file at its own line numbers.
|
|
14
|
+
#
|
|
15
|
+
# A template no test renders is never compiled, so `Coverage` never hears
|
|
16
|
+
# about it and the report omits it entirely. That is the flattering
|
|
17
|
+
# direction to be wrong in, so every template the run didn't reach is
|
|
18
|
+
# compiled here instead, which lands it in the report at 0%.
|
|
19
|
+
module ViewCoverage
|
|
20
|
+
extend self
|
|
21
|
+
|
|
22
|
+
# Called once per process, immediately before `Coverage.result`. It has to
|
|
23
|
+
# happen here rather than at the merge point, where unloaded `.rb` files are
|
|
24
|
+
# injected, because compiling a template needs ActionView and the merging
|
|
25
|
+
# process need not have it loaded. A standalone `simplecov merge` never
|
|
26
|
+
# does.
|
|
27
|
+
def compile_unrendered
|
|
28
|
+
return [] unless enabled?
|
|
29
|
+
|
|
30
|
+
rendered = measured_paths
|
|
31
|
+
discover.reject { |path| rendered.include?(path) }
|
|
32
|
+
.select { |path| TemplateCompiler.call(path) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def enabled?
|
|
36
|
+
SimpleCov.view_coverage? && Coverage.running? && TemplateCompiler.available?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Expanded against `SimpleCov.root` rather than the working directory, and
|
|
40
|
+
# filtered through the same path-only filters the rest of the report honors,
|
|
41
|
+
# so `skip "app/views/admin"` keeps those templates out instead of compiling
|
|
42
|
+
# them only to drop them later.
|
|
43
|
+
def discover
|
|
44
|
+
globs = SimpleCov.view_globs || [] #: Array[String?]
|
|
45
|
+
UnloadedFileInjector.discover(globs, root: SimpleCov.root, reject: SimpleCov.filters.select(&:path_only?))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The files `Coverage` is already holding data for, which for a template
|
|
49
|
+
# means the run rendered it. `peek_result` copies the whole coverage state
|
|
50
|
+
# to answer, so this stays a once-per-process call.
|
|
51
|
+
def measured_paths
|
|
52
|
+
Coverage.peek_result.keys.to_set
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|