Class: SimpleCov::TestTracker
- Inherits:
-
Object
- Object
- SimpleCov::TestTracker
- Defined in:
- lib/simplecov/test_tracker.rb,
lib/simplecov/test_tracker/delta.rb,
lib/simplecov/test_tracker/accessors.rb,
lib/simplecov/test_tracker/constant_watch.rb,
lib/simplecov/test_tracker/framework_hooks.rb
Overview
The test-framework side of per-test tracking: how the wrapper gets installed into RSpec and Minitest, and how each framework's tests are identified. The recording itself lives in test_tracker.rb.
Defined Under Namespace
Modules: Accessors, MinitestRun Classes: ConstantWatch, Delta
Instance Attribute Summary collapse
-
#map ⇒ Object
readonly
Returns the value of attribute map.
Class Method Summary collapse
-
.definition_site(test) ⇒ Object
A method defined in C or through an eval without a filename has no site, and a runner exotic enough to name a method it never defined deserves an id over an exception out of the middle of its run.
- .install_framework_hooks ⇒ Object
-
.install_minitest_hook(test_case = (Minitest::Test))) ⇒ Object
Called by the minitest plugin under minitest 5, and by the constant watch below under minitest 6, whose autorun no longer discovers plugins.
-
.install_minitest_hook_when_loaded(root = Object) ⇒ Object
Installs the Minitest wrapper now when Minitest is already loaded, and otherwise the moment
Minitest::Testis defined. -
.install_rspec_hook(rspec = rspec_module) ⇒ Object
Wraps every RSpec example in a
track_testcall. -
.loaded_const(mod, name) ⇒ Object
The constant when it is genuinely loaded, nil when absent or merely declared for autoload: const_get would force such a load, and installing a coverage hook must never be what requires a test framework.
-
.minitest_test_id(test) ⇒ Object
A Minitest test's identity: the
path:linewhere the test method is defined, relative toSimpleCov.root. -
.reset_rspec_hook! ⇒ Object
without touching the process-wide guard for real.
-
.rspec_example_id(example) ⇒ Object
Relative the way RSpec prints it (
./spec/a_spec.rb:12becomesspec/a_spec.rb:12). -
.rspec_module ⇒ Object
The RSpec this process has, if it has one.
-
.watch_for_minitest_test(minitest) ⇒ Object
An autoload declaration fires
const_addedwithout the module existing yet; that arrangement is left to the plugin, so nil means nothing to watch.
Instance Method Summary collapse
-
#initialize(root_regex: UselessResultsRemover.root_regex, granularity: :test) ⇒ TestTracker
constructor
A new instance of TestTracker.
- #poisoned? ⇒ Boolean
-
#recorded_map(closing: nil) ⇒ Object
nil once poisoned: a poisoned recording must vanish like one that never happened, so the merge's all-or-nothing rule drops the run's map instead of keeping a wrong one.
-
#track(test_id) ⇒ Object
Records the lines the block executed as covered by
test_id's context (the test itself at :test granularity, its file at :file).
Constructor Details
#initialize(root_regex: UselessResultsRemover.root_regex, granularity: :test) ⇒ TestTracker
Returns a new instance of TestTracker.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/simplecov/test_tracker.rb', line 33 def initialize(root_regex: UselessResultsRemover.root_regex, granularity: :test) unless Configuration::TRACK_TESTS_GRANULARITIES.include?(granularity) raise ArgumentError, "unknown granularity #{granularity.inspect}, " \ "expected one of #{Configuration::TRACK_TESTS_GRANULARITIES.inspect}" end @map = ContextMap.new # Only files under the project root are attributed, judged by the same # regex the report's own universe is rooted with. A peek covers every # file the process loaded, every gem included, and diffing all of them on # every test is the cost that would make tracking unaffordable. @delta = Delta.new(root_regex: root_regex) @granularity = granularity @lock = Mutex.new @depth = 0 @poisoned = false end |
Instance Attribute Details
#map ⇒ Object (readonly)
Returns the value of attribute map.
26 27 28 |
# File 'lib/simplecov/test_tracker.rb', line 26 def map @map end |
Class Method Details
.definition_site(test) ⇒ Object
A method defined in C or through an eval without a filename has no site, and a runner exotic enough to name a method it never defined deserves an id over an exception out of the middle of its run.
114 115 116 117 118 |
# File 'lib/simplecov/test_tracker/framework_hooks.rb', line 114 def definition_site(test) test.method(test.name).source_location rescue NameError nil end |
.install_framework_hooks ⇒ Object
9 10 11 12 |
# File 'lib/simplecov/test_tracker/framework_hooks.rb', line 9 def install_framework_hooks install_rspec_hook install_minitest_hook_when_loaded end |
.install_minitest_hook(test_case = (Minitest::Test))) ⇒ Object
Called by the minitest plugin under minitest 5, and by the constant watch below under minitest 6, whose autorun no longer discovers plugins. Prepending a module already in the chain is a no-op, which is the whole of the idempotence.
52 53 54 55 56 |
# File 'lib/simplecov/test_tracker/framework_hooks.rb', line 52 def install_minitest_hook(test_case = (Minitest::Test if defined?(Minitest::Test))) return if test_case.nil? test_case.prepend(MinitestRun) end |
.install_minitest_hook_when_loaded(root = Object) ⇒ Object
Installs the Minitest wrapper now when Minitest is already loaded, and
otherwise the moment Minitest::Test is defined. The deferred half is
what covers minitest 6 under the canonical helper ordering (SimpleCov
first, so coverage sees the app load; minitest after), where no plugin
ever fires. root is Object outside of tests.
63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/simplecov/test_tracker/framework_hooks.rb', line 63 def install_minitest_hook_when_loaded(root = Object) minitest = loaded_const(root, :Minitest) test_case = minitest && loaded_const(minitest, :Test) return install_minitest_hook(test_case) if test_case return watch_for_minitest_test(minitest) if minitest ConstantWatch.new(:Minitest) do # `loaded_const` rather than the block capturing a module: the module # does not exist yet when the watch is armed. watch_for_minitest_test(loaded_const(root, :Minitest)) end.attach(root) end |
.install_rspec_hook(rspec = rspec_module) ⇒ Object
Wraps every RSpec example in a track_test call. Called from
SimpleCov.start_tracking, where RSpec is already loaded when the suite
is an RSpec suite. Installs once per process no matter how often
tracking is restarted.
18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/simplecov/test_tracker/framework_hooks.rb', line 18 def install_rspec_hook(rspec = rspec_module) return unless rspec return if @rspec_hook_installed @rspec_hook_installed = true rspec.configure do |config| # The block runs instance-eval'd in the example group, so everything it # touches must be fully qualified. config.around do |example| SimpleCov.track_test(TestTracker.rspec_example_id(example)) { example.run } end end end |
.loaded_const(mod, name) ⇒ Object
The constant when it is genuinely loaded, nil when absent or merely declared for autoload: const_get would force such a load, and installing a coverage hook must never be what requires a test framework.
94 95 96 97 98 99 |
# File 'lib/simplecov/test_tracker/framework_hooks.rb', line 94 def loaded_const(mod, name) return nil unless mod.const_defined?(name, false) return nil if mod.autoload?(name, false) mod.const_get(name) end |
.minitest_test_id(test) ⇒ Object
A Minitest test's identity: the path:line where the test method is
defined, relative to SimpleCov.root. Falls back to
ClassName#method_name when the method has no source location.
104 105 106 107 108 109 |
# File 'lib/simplecov/test_tracker/framework_hooks.rb', line 104 def minitest_test_id(test) path, line = definition_site(test) return "#{test.class}##{test.name}" unless path "#{path.delete_prefix(File.join(SimpleCov.root, ""))}:#{line}" end |
.reset_rspec_hook! ⇒ Object
without touching the process-wide guard for real.
44 45 46 |
# File 'lib/simplecov/test_tracker/framework_hooks.rb', line 44 def reset_rspec_hook! @rspec_hook_installed = false end |
.rspec_example_id(example) ⇒ Object
Relative the way RSpec prints it (./spec/a_spec.rb:12 becomes
spec/a_spec.rb:12).
78 79 80 |
# File 'lib/simplecov/test_tracker/framework_hooks.rb', line 78 def rspec_example_id(example) example..fetch(:location).delete_prefix("./") end |
.rspec_module ⇒ Object
The RSpec this process has, if it has one. A Minitest-only suite has none, and no example can make this process into one: hiding the RSpec constant takes down the very runner asking the question. simplecov:disable branch — the RSpec-less arm is unreachable from an RSpec-driven suite mutant:disable
37 38 39 |
# File 'lib/simplecov/test_tracker/framework_hooks.rb', line 37 def rspec_module ::RSpec if defined?(::RSpec.configure) end |
.watch_for_minitest_test(minitest) ⇒ Object
An autoload declaration fires const_added without the module existing
yet; that arrangement is left to the plugin, so nil means nothing to
watch.
85 86 87 88 89 |
# File 'lib/simplecov/test_tracker/framework_hooks.rb', line 85 def watch_for_minitest_test(minitest) return unless minitest ConstantWatch.new(:Test) { install_minitest_hook(loaded_const(minitest, :Test)) }.attach(minitest) end |
Instance Method Details
#poisoned? ⇒ Boolean
64 65 66 |
# File 'lib/simplecov/test_tracker.rb', line 64 def poisoned? @poisoned end |
#recorded_map(closing: nil) ⇒ Object
nil once poisoned: a poisoned recording must vanish like one that never
happened, so the merge's all-or-nothing rule drops the run's map instead
of keeping a wrong one. Flushes the open segment, so the answer carries
every finished track. At process exit Coverage.result has already
stopped measurement, so result-building passes the final coverage it just
took as the closing snapshot instead of peeking again.
74 75 76 77 78 79 |
# File 'lib/simplecov/test_tracker.rb', line 74 def recorded_map(closing: nil) return nil if poisoned? flush_segment(closing) @map end |
#track(test_id) ⇒ Object
Records the lines the block executed as covered by test_id's context
(the test itself at :test granularity, its file at :file). A failing
test's lines are still attributed when its segment closes.
54 55 56 57 58 59 60 61 62 |
# File 'lib/simplecov/test_tracker.rb', line 54 def track(test_id) id = context_id(test_id) nested_opening = begin_track(id) begin yield ensure settle(id, nested_opening) end end |