Class: SimpleCov::TestTracker

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

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

#mapObject (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_hooksObject



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_moduleObject

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

Returns:

  • (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