rspec-tracer 1.2.2 → 2.0.0.pre.1
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 +197 -45
- data/README.md +439 -429
- data/bin/rspec-tracer +15 -0
- data/lib/rspec_tracer/cache/Rakefile +43 -0
- data/lib/rspec_tracer/cli/cache_clear.rb +98 -0
- data/lib/rspec_tracer/cli/cache_info.rb +103 -0
- data/lib/rspec_tracer/cli/doctor.rb +275 -0
- data/lib/rspec_tracer/cli/explain.rb +148 -0
- data/lib/rspec_tracer/cli/report_open.rb +82 -0
- data/lib/rspec_tracer/cli.rb +116 -0
- data/lib/rspec_tracer/configuration.rb +1100 -3
- data/lib/rspec_tracer/engine.rb +1076 -0
- data/lib/rspec_tracer/example.rb +21 -6
- data/lib/rspec_tracer/filter.rb +35 -0
- data/lib/rspec_tracer/line_stub.rb +61 -0
- data/lib/rspec_tracer/load_config.rb +2 -2
- data/lib/rspec_tracer/logger.rb +15 -0
- data/lib/rspec_tracer/rails/README.md +78 -0
- data/lib/rspec_tracer/rails/i18n_tracking.rb +137 -0
- data/lib/rspec_tracer/rails/notifications.rb +263 -0
- data/lib/rspec_tracer/rails/preset.rb +94 -0
- data/lib/rspec_tracer/rails/railtie.rb +22 -0
- data/lib/rspec_tracer/rails.rb +15 -0
- data/lib/rspec_tracer/remote_cache/README.md +140 -0
- data/lib/rspec_tracer/remote_cache/Rakefile +35 -11
- data/lib/rspec_tracer/remote_cache/archive.rb +137 -0
- data/lib/rspec_tracer/remote_cache/backend.rb +73 -0
- data/lib/rspec_tracer/remote_cache/git_ancestry.rb +241 -0
- data/lib/rspec_tracer/remote_cache/local_fs_backend.rb +439 -0
- data/lib/rspec_tracer/remote_cache/redis_backend.rb +554 -0
- data/lib/rspec_tracer/remote_cache/s3_backend.rb +712 -0
- data/lib/rspec_tracer/remote_cache/user_tasks.rb +397 -0
- data/lib/rspec_tracer/remote_cache/validator.rb +40 -62
- data/lib/rspec_tracer/remote_cache.rb +22 -0
- data/lib/rspec_tracer/reporters/README.md +103 -0
- data/lib/rspec_tracer/reporters/base.rb +87 -0
- data/lib/rspec_tracer/reporters/coverage_json_reporter.rb +338 -0
- data/lib/rspec_tracer/reporters/html/.gitignore +19 -0
- data/lib/rspec_tracer/reporters/html/.prettierignore +4 -0
- data/lib/rspec_tracer/reporters/html/.prettierrc.json +9 -0
- data/lib/rspec_tracer/reporters/html/README.md +80 -0
- data/lib/rspec_tracer/reporters/html/dist/assets/index.css +2 -0
- data/lib/rspec_tracer/reporters/html/dist/assets/index.js +1 -0
- data/lib/rspec_tracer/reporters/html/dist/index.html +24 -0
- data/lib/rspec_tracer/reporters/html/eslint.config.js +62 -0
- data/lib/rspec_tracer/reporters/html/package-lock.json +4941 -0
- data/lib/rspec_tracer/reporters/html/package.json +29 -0
- data/lib/rspec_tracer/reporters/html/src/app.jsx +130 -0
- data/lib/rspec_tracer/reporters/html/src/components/AllExamples.jsx +86 -0
- data/lib/rspec_tracer/reporters/html/src/components/DuplicateExamples.jsx +68 -0
- data/lib/rspec_tracer/reporters/html/src/components/ExamplesDependency.jsx +78 -0
- data/lib/rspec_tracer/reporters/html/src/components/FilesDependency.jsx +72 -0
- data/lib/rspec_tracer/reporters/html/src/components/FlakyExamples.jsx +42 -0
- data/lib/rspec_tracer/reporters/html/src/components/ReportTable.jsx +131 -0
- data/lib/rspec_tracer/reporters/html/src/components/SearchBar.jsx +19 -0
- data/lib/rspec_tracer/reporters/html/src/index.html +23 -0
- data/lib/rspec_tracer/reporters/html/src/main.jsx +37 -0
- data/lib/rspec_tracer/reporters/html/src/styles.css +434 -0
- data/lib/rspec_tracer/reporters/html/vite.config.js +42 -0
- data/lib/rspec_tracer/reporters/html_reporter.rb +266 -0
- data/lib/rspec_tracer/reporters/json_reporter.rb +88 -0
- data/lib/rspec_tracer/reporters/payload_builder.rb +235 -0
- data/lib/rspec_tracer/reporters/registry.rb +120 -0
- data/lib/rspec_tracer/reporters/terminal_reporter.rb +264 -0
- data/lib/rspec_tracer/rspec/README.md +73 -0
- data/lib/rspec_tracer/rspec/installation.rb +97 -0
- data/lib/rspec_tracer/rspec/metadata.rb +96 -0
- data/lib/rspec_tracer/rspec/parallel_tests.rb +459 -0
- data/lib/rspec_tracer/rspec/reporter_hook.rb +84 -0
- data/lib/rspec_tracer/rspec/runner_hook.rb +178 -0
- data/lib/rspec_tracer/source_file.rb +24 -7
- data/lib/rspec_tracer/storage/README.md +35 -0
- data/lib/rspec_tracer/storage/backend.rb +68 -0
- data/lib/rspec_tracer/storage/json_backend.rb +866 -0
- data/lib/rspec_tracer/storage/lazy_snapshot.rb +65 -0
- data/lib/rspec_tracer/storage/schema.rb +43 -0
- data/lib/rspec_tracer/storage/serializer/json.rb +41 -0
- data/lib/rspec_tracer/storage/serializer/msgpack.rb +90 -0
- data/lib/rspec_tracer/storage/snapshot.rb +127 -0
- data/lib/rspec_tracer/storage/sqlite_backend.rb +686 -0
- data/lib/rspec_tracer/time_formatter.rb +37 -18
- data/lib/rspec_tracer/tracker/README.md +36 -0
- data/lib/rspec_tracer/tracker/coverage_adapter.rb +174 -0
- data/lib/rspec_tracer/tracker/declared_globs.rb +100 -0
- data/lib/rspec_tracer/tracker/dependency_graph.rb +134 -0
- data/lib/rspec_tracer/tracker/env_matcher.rb +127 -0
- data/lib/rspec_tracer/tracker/env_snapshot.rb +77 -0
- data/lib/rspec_tracer/tracker/example_registry.rb +153 -0
- data/lib/rspec_tracer/tracker/file_digest.rb +61 -0
- data/lib/rspec_tracer/tracker/filter.rb +127 -0
- data/lib/rspec_tracer/tracker/input.rb +99 -0
- data/lib/rspec_tracer/tracker/io_hooks/file.rb +55 -0
- data/lib/rspec_tracer/tracker/io_hooks/io.rb +24 -0
- data/lib/rspec_tracer/tracker/io_hooks/json.rb +23 -0
- data/lib/rspec_tracer/tracker/io_hooks/kernel.rb +26 -0
- data/lib/rspec_tracer/tracker/io_hooks/yaml.rb +38 -0
- data/lib/rspec_tracer/tracker/io_hooks.rb +195 -0
- data/lib/rspec_tracer/tracker/loaded_files_tracker.rb +295 -0
- data/lib/rspec_tracer/tracker/new_file_detector.rb +62 -0
- data/lib/rspec_tracer/tracker/whole_suite_invalidators.rb +96 -0
- data/lib/rspec_tracer/version.rb +4 -1
- data/lib/rspec_tracer.rb +232 -381
- metadata +93 -43
- data/lib/rspec_tracer/cache.rb +0 -207
- data/lib/rspec_tracer/coverage_merger.rb +0 -42
- data/lib/rspec_tracer/coverage_reporter.rb +0 -187
- data/lib/rspec_tracer/coverage_writer.rb +0 -58
- data/lib/rspec_tracer/html_reporter/Rakefile +0 -18
- data/lib/rspec_tracer/html_reporter/assets/javascripts/application.js +0 -56
- data/lib/rspec_tracer/html_reporter/assets/javascripts/libraries/jquery.js +0 -10881
- data/lib/rspec_tracer/html_reporter/assets/javascripts/plugins/datatables.js +0 -15381
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/application.css +0 -196
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/plugins/datatables.css +0 -459
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/plugins/jquery-ui.css +0 -436
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/print.css +0 -92
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/reset.css +0 -265
- data/lib/rspec_tracer/html_reporter/public/application.css +0 -5
- data/lib/rspec_tracer/html_reporter/public/application.js +0 -6
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_asc.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_asc_disabled.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_both.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_desc.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_desc_disabled.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/favicon.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/loading.gif +0 -0
- data/lib/rspec_tracer/html_reporter/reporter.rb +0 -242
- data/lib/rspec_tracer/html_reporter/views/duplicate_examples.erb +0 -34
- data/lib/rspec_tracer/html_reporter/views/examples.erb +0 -58
- data/lib/rspec_tracer/html_reporter/views/examples_dependency.erb +0 -36
- data/lib/rspec_tracer/html_reporter/views/files_dependency.erb +0 -36
- data/lib/rspec_tracer/html_reporter/views/flaky_examples.erb +0 -38
- data/lib/rspec_tracer/html_reporter/views/layout.erb +0 -38
- data/lib/rspec_tracer/remote_cache/aws.rb +0 -176
- data/lib/rspec_tracer/remote_cache/cache.rb +0 -75
- data/lib/rspec_tracer/remote_cache/repo.rb +0 -210
- data/lib/rspec_tracer/report_generator.rb +0 -158
- data/lib/rspec_tracer/report_merger.rb +0 -68
- data/lib/rspec_tracer/report_writer.rb +0 -141
- data/lib/rspec_tracer/reporter.rb +0 -204
- data/lib/rspec_tracer/rspec_reporter.rb +0 -41
- data/lib/rspec_tracer/rspec_runner.rb +0 -56
- data/lib/rspec_tracer/ruby_coverage.rb +0 -9
- data/lib/rspec_tracer/runner.rb +0 -278
data/lib/rspec_tracer.rb
CHANGED
|
@@ -10,73 +10,109 @@ require 'json'
|
|
|
10
10
|
require 'pathname'
|
|
11
11
|
require 'set'
|
|
12
12
|
|
|
13
|
-
require_relative 'rspec_tracer/coverage_merger'
|
|
14
|
-
require_relative 'rspec_tracer/coverage_reporter'
|
|
15
|
-
require_relative 'rspec_tracer/coverage_writer'
|
|
16
13
|
require_relative 'rspec_tracer/defaults'
|
|
14
|
+
require_relative 'rspec_tracer/engine'
|
|
17
15
|
require_relative 'rspec_tracer/example'
|
|
18
|
-
|
|
16
|
+
# Reporters must load before load_config so the Configuration DSL's
|
|
17
|
+
# `add_reporter` can validate symbol names against
|
|
18
|
+
# `Reporters::Registry::BUILT_INS` when a user `.rspec-tracer` calls
|
|
19
|
+
# it at configure time.
|
|
20
|
+
require_relative 'rspec_tracer/reporters/base'
|
|
21
|
+
require_relative 'rspec_tracer/reporters/payload_builder'
|
|
22
|
+
require_relative 'rspec_tracer/reporters/coverage_json_reporter'
|
|
23
|
+
require_relative 'rspec_tracer/reporters/json_reporter'
|
|
24
|
+
require_relative 'rspec_tracer/reporters/terminal_reporter'
|
|
25
|
+
require_relative 'rspec_tracer/reporters/html_reporter'
|
|
26
|
+
require_relative 'rspec_tracer/reporters/registry'
|
|
19
27
|
require_relative 'rspec_tracer/load_config'
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
require_relative 'rspec_tracer/
|
|
26
|
-
require_relative 'rspec_tracer/
|
|
27
|
-
require_relative 'rspec_tracer/runner'
|
|
28
|
+
# RemoteCache is loaded lazily from its Rakefile shim (user-driven),
|
|
29
|
+
# not at gem-load time. The user-facing tasks `rspec_tracer:remote_cache:*`
|
|
30
|
+
# pull in `lib/rspec_tracer/remote_cache.rb` when the user's Rakefile
|
|
31
|
+
# loads the shim. Test-suite runs that never invoke a cache task pay
|
|
32
|
+
# zero load cost for aws/git subshell code.
|
|
33
|
+
require_relative 'rspec_tracer/rspec/installation'
|
|
34
|
+
require_relative 'rspec_tracer/rspec/parallel_tests'
|
|
28
35
|
require_relative 'rspec_tracer/source_file'
|
|
29
36
|
require_relative 'rspec_tracer/time_formatter'
|
|
30
37
|
require_relative 'rspec_tracer/version'
|
|
31
38
|
|
|
39
|
+
# Top-level entry point. Drives the lifecycle:
|
|
40
|
+
#
|
|
41
|
+
# RSpecTracer.start
|
|
42
|
+
# -> RSpec::Installation.install! (prepend RunnerHook + ReporterHook)
|
|
43
|
+
# -> setup_coverage (::Coverage.start unless SimpleCov owns it)
|
|
44
|
+
# -> setup_rails (detect ::Rails::VERSION)
|
|
45
|
+
# -> Engine.new.setup (observers + cache load + filter decisions)
|
|
46
|
+
#
|
|
47
|
+
# at_exit_behavior (installed via `at_exit` elsewhere in the boot
|
|
48
|
+
# flow) runs the finalize stack: Engine#finalize writes the 13-file
|
|
49
|
+
# snapshot via Storage::JsonBackend, Reporters::CoverageJsonReporter
|
|
50
|
+
# writes coverage.json (single owner, replacing the 1.x
|
|
51
|
+
# CoverageReporter + CoverageWriter pair retired in 2.0),
|
|
52
|
+
# ParallelTests#finalize! merges per-worker caches on the last worker.
|
|
32
53
|
module RSpecTracer
|
|
33
54
|
class << self
|
|
55
|
+
# Internal attribute.
|
|
56
|
+
# @api private
|
|
34
57
|
attr_accessor :running, :pid, :no_examples, :duplicate_examples
|
|
35
58
|
|
|
59
|
+
# Boot the tracer. Idempotent — safe to call multiple times in a
|
|
60
|
+
# single process (subsequent calls return without re-installing
|
|
61
|
+
# hooks). Drives the lifecycle:
|
|
62
|
+
#
|
|
63
|
+
# * Installs the RSpec runner / reporter prepend chain.
|
|
64
|
+
# * Starts `::Coverage` unless SimpleCov already owns it.
|
|
65
|
+
# * Detects Rails (memoized in `RSpecTracer.rails?`).
|
|
66
|
+
# * Builds the {RSpecTracer::Engine} and installs observers.
|
|
67
|
+
#
|
|
68
|
+
# Must be called BEFORE any application code loads so the boot
|
|
69
|
+
# set captured by `Coverage.peek_result` is empty. With SimpleCov,
|
|
70
|
+
# call `SimpleCov.start` first; rspec-tracer warns at boot when
|
|
71
|
+
# SimpleCov is loaded but not started.
|
|
72
|
+
#
|
|
73
|
+
# @return [void]
|
|
36
74
|
def start
|
|
75
|
+
return if defined?(@started) && @started
|
|
76
|
+
|
|
37
77
|
RSpecTracer.running = false
|
|
38
78
|
RSpecTracer.pid = Process.pid
|
|
39
|
-
|
|
40
|
-
|
|
79
|
+
@run_started_at = ::Time.now.utc
|
|
80
|
+
@run_monotonic_start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
81
|
+
@started = true
|
|
41
82
|
|
|
42
83
|
RSpecTracer.logger.debug "Started RSpec tracer (pid: #{RSpecTracer.pid})"
|
|
43
84
|
|
|
44
|
-
|
|
45
|
-
initial_setup
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# rubocop:disable Metrics/AbcSize
|
|
49
|
-
def filter_examples
|
|
50
|
-
groups = Set.new
|
|
51
|
-
to_run = Hash.new { |hash, group| hash[group] = [] }
|
|
52
|
-
|
|
53
|
-
RSpec.world.filtered_examples.each_pair do |example_group, examples|
|
|
54
|
-
examples.each do |example|
|
|
55
|
-
tracer_example = RSpecTracer::Example.from(example)
|
|
56
|
-
example_id = tracer_example[:example_id]
|
|
57
|
-
example.metadata[:rspec_tracer_example_id] = example_id
|
|
58
|
-
|
|
59
|
-
if runner.run_example?(example_id)
|
|
60
|
-
run_reason = runner.run_example_reason(example_id)
|
|
61
|
-
tracer_example[:run_reason] = run_reason
|
|
62
|
-
example.metadata[:description] = "#{example.description} (#{run_reason})"
|
|
63
|
-
|
|
64
|
-
to_run[example_group] << example
|
|
65
|
-
groups << example.example_group.parent_groups.last
|
|
66
|
-
|
|
67
|
-
runner.register_example(tracer_example)
|
|
68
|
-
else
|
|
69
|
-
runner.on_example_skipped(example_id)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
runner.deregister_duplicate_examples
|
|
85
|
+
warn_on_simplecov_load_order_mistake
|
|
75
86
|
|
|
76
|
-
|
|
87
|
+
@parallel_tests = RSpecTracer::RSpec::ParallelTests.active?
|
|
88
|
+
RSpecTracer::RSpec::ParallelTests.setup! if parallel_tests?
|
|
89
|
+
initial_setup
|
|
77
90
|
end
|
|
78
|
-
# rubocop:enable Metrics/AbcSize
|
|
79
91
|
|
|
92
|
+
# SimpleCov load-order is part of the documented contract -
|
|
93
|
+
# SimpleCov.start MUST run before RSpecTracer.start when both are
|
|
94
|
+
# used together (see README §SimpleCov interop). When the user
|
|
95
|
+
# has SimpleCov loaded but not started, we'd silently call
|
|
96
|
+
# ::Coverage.start ourselves and SimpleCov's later setup would
|
|
97
|
+
# bolt onto a Coverage already in flight, with the user's
|
|
98
|
+
# add_filter calls applied after rspec-tracer started consuming
|
|
99
|
+
# data. Surface the load-order mistake at start time so the user
|
|
100
|
+
# gets a one-line warning instead of mysteriously-broken
|
|
101
|
+
# coverage output.
|
|
102
|
+
def warn_on_simplecov_load_order_mistake
|
|
103
|
+
return unless defined?(::SimpleCov)
|
|
104
|
+
return if ::SimpleCov.respond_to?(:running) && ::SimpleCov.running
|
|
105
|
+
|
|
106
|
+
RSpecTracer.logger.warn(
|
|
107
|
+
'SimpleCov is loaded but not started. ' \
|
|
108
|
+
'Call SimpleCov.start before RSpecTracer.start so the ' \
|
|
109
|
+
'tracer respects SimpleCov\'s filter chain. See README ' \
|
|
110
|
+
'section "Working with SimpleCov".'
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Internal method on the tracer pipeline.
|
|
115
|
+
# @api private
|
|
80
116
|
def at_exit_behavior
|
|
81
117
|
return unless RSpecTracer.pid == Process.pid && RSpecTracer.running
|
|
82
118
|
|
|
@@ -84,141 +120,69 @@ module RSpecTracer
|
|
|
84
120
|
|
|
85
121
|
run_exit_tasks
|
|
86
122
|
ensure
|
|
87
|
-
|
|
123
|
+
if RSpecTracer::RSpec::ParallelTests.active? &&
|
|
124
|
+
RSpecTracer::RSpec::ParallelTests.last_process?
|
|
125
|
+
RSpecTracer::RSpec::ParallelTests.remove_lock_file!
|
|
126
|
+
end
|
|
88
127
|
|
|
89
128
|
RSpecTracer.running = false
|
|
90
129
|
end
|
|
91
130
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
@examples_traced_files[example_id] = @traced_files
|
|
100
|
-
@traced_files = Set.new
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def runner
|
|
104
|
-
@runner if defined?(@runner)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def coverage_reporter
|
|
108
|
-
@coverage_reporter if defined?(@coverage_reporter)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def report_writer
|
|
112
|
-
@report_writer if defined?(@report_writer)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def coverage_merger
|
|
116
|
-
@coverage_merger if defined?(@coverage_merger)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def report_merger
|
|
120
|
-
@report_merger if defined?(@report_merger)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def trace_point
|
|
124
|
-
@trace_point if defined?(@trace_point)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def traced_files
|
|
128
|
-
@traced_files if defined?(@traced_files)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def examples_traced_files
|
|
132
|
-
@examples_traced_files if defined?(@examples_traced_files)
|
|
131
|
+
# The current {RSpecTracer::Engine} instance, or nil if
|
|
132
|
+
# {.start} hasn't been called yet.
|
|
133
|
+
#
|
|
134
|
+
# @return [RSpecTracer::Engine, nil]
|
|
135
|
+
def engine
|
|
136
|
+
@engine if defined?(@engine)
|
|
133
137
|
end
|
|
134
138
|
|
|
139
|
+
# True if SimpleCov was loaded AND running at the time
|
|
140
|
+
# {.start} was invoked. Determines whether rspec-tracer
|
|
141
|
+
# owns `::Coverage.start` itself (false) or defers to
|
|
142
|
+
# SimpleCov's coverage lifecycle (true).
|
|
143
|
+
#
|
|
144
|
+
# @return [Boolean]
|
|
135
145
|
def simplecov?
|
|
136
146
|
defined?(@simplecov) && @simplecov == true
|
|
137
147
|
end
|
|
138
148
|
|
|
149
|
+
# True if `parallel_tests` is in use (detected via
|
|
150
|
+
# `ParallelTests.active?` at {.start} time). Affects
|
|
151
|
+
# cache + report directory scoping (per-worker dirs)
|
|
152
|
+
# and finalize-time merging.
|
|
153
|
+
#
|
|
154
|
+
# @return [Boolean]
|
|
139
155
|
def parallel_tests?
|
|
140
156
|
defined?(@parallel_tests) && @parallel_tests == true
|
|
141
157
|
end
|
|
142
158
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
RSpec Tracer is not running as it requires debug and object space enabled. Use
|
|
153
|
-
command line options "--debug" and "-X+O" or set the "debug.fullTrace=true" and
|
|
154
|
-
"objectspace.enabled=true" options in your .jrubyrc file. You can also use
|
|
155
|
-
JRUBY_OPTS="--debug -X+O".
|
|
156
|
-
WARN
|
|
157
|
-
|
|
158
|
-
false
|
|
159
|
+
# True if Rails is loaded in this process (detected via
|
|
160
|
+
# `defined?(::Rails::VERSION)` at {.start} time). Memoized;
|
|
161
|
+
# subsequent Rails activations within the same run are not
|
|
162
|
+
# re-detected. Drives the auto-installation of Rails-side
|
|
163
|
+
# observers (template + AR notification subscribers).
|
|
164
|
+
#
|
|
165
|
+
# @return [Boolean]
|
|
166
|
+
def rails?
|
|
167
|
+
defined?(@rails) && @rails == true
|
|
159
168
|
end
|
|
160
169
|
|
|
161
|
-
|
|
162
|
-
unless setup_rspec?
|
|
163
|
-
RSpecTracer.logger.error 'Could not find a running RSpec process'
|
|
170
|
+
private
|
|
164
171
|
|
|
165
|
-
|
|
166
|
-
|
|
172
|
+
# Internal method on the tracer pipeline.
|
|
173
|
+
# @api private
|
|
174
|
+
def initial_setup
|
|
175
|
+
RSpecTracer::RSpec::Installation.install!
|
|
167
176
|
|
|
168
177
|
setup_coverage
|
|
169
|
-
|
|
178
|
+
setup_rails
|
|
170
179
|
|
|
171
|
-
@
|
|
172
|
-
@
|
|
173
|
-
@report_writer = RSpecTracer::ReportWriter.new(RSpecTracer.cache_path, @runner.reporter)
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def parallel_tests_setup
|
|
177
|
-
@parallel_tests = !(ENV.fetch('TEST_ENV_NUMBER', nil) && ENV.fetch('PARALLEL_TEST_GROUPS', nil)).nil?
|
|
178
|
-
|
|
179
|
-
return unless parallel_tests?
|
|
180
|
-
|
|
181
|
-
require 'parallel_tests' unless defined?(ParallelTests)
|
|
182
|
-
|
|
183
|
-
@coverage_merger = RSpecTracer::CoverageMerger.new
|
|
184
|
-
@report_merger = RSpecTracer::ReportMerger.new
|
|
185
|
-
rescue LoadError => e
|
|
186
|
-
RSpecTracer.logger.error "Failed to load parallel tests (Error: #{e.message})"
|
|
187
|
-
ensure
|
|
188
|
-
track_parallel_tests_test_env_number
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def track_parallel_tests_test_env_number
|
|
192
|
-
return unless parallel_tests?
|
|
193
|
-
|
|
194
|
-
File.open(RSpecTracer.lock_file, File::RDWR | File::CREAT, 0o644) do |f|
|
|
195
|
-
f.flock(File::LOCK_EX)
|
|
196
|
-
|
|
197
|
-
test_num = [f.read.to_i, ENV['TEST_ENV_NUMBER'].to_i].max
|
|
198
|
-
|
|
199
|
-
f.rewind
|
|
200
|
-
f.write("#{test_num}\n")
|
|
201
|
-
f.flush
|
|
202
|
-
f.truncate(f.pos)
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def setup_rspec?
|
|
207
|
-
runners = ObjectSpace.each_object(::RSpec::Core::Runner) do |runner|
|
|
208
|
-
runner_clazz = runner.singleton_class
|
|
209
|
-
clazz = RSpecTracer::RSpecRunner
|
|
210
|
-
|
|
211
|
-
runner_clazz.prepend(clazz) unless runner_clazz.ancestors.include?(clazz)
|
|
212
|
-
|
|
213
|
-
reporter_clazz = runner.configuration.reporter.singleton_class
|
|
214
|
-
clazz = RSpecTracer::RSpecReporter
|
|
215
|
-
|
|
216
|
-
reporter_clazz.prepend(clazz) unless reporter_clazz.ancestors.include?(clazz)
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
runners.positive?
|
|
180
|
+
@engine = RSpecTracer::Engine.new(configuration: RSpecTracer)
|
|
181
|
+
@engine.setup
|
|
220
182
|
end
|
|
221
183
|
|
|
184
|
+
# Internal method on the tracer pipeline.
|
|
185
|
+
# @api private
|
|
222
186
|
def setup_coverage
|
|
223
187
|
@simplecov = defined?(SimpleCov) && SimpleCov.running
|
|
224
188
|
|
|
@@ -229,244 +193,131 @@ module RSpecTracer
|
|
|
229
193
|
::Coverage.start
|
|
230
194
|
end
|
|
231
195
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
196
|
+
# Detects Rails by the presence of `::Rails::VERSION`. Users who
|
|
197
|
+
# require `rspec_tracer/rails` transitively load the Railtie (when
|
|
198
|
+
# Rails is also present); this method only sets the flag consumed
|
|
199
|
+
# by `RSpecTracer.rails?`. Safe when Rails is absent - the
|
|
200
|
+
# `defined?` guard returns nil, flag stays false.
|
|
201
|
+
def setup_rails
|
|
202
|
+
@rails = defined?(::Rails::VERSION) && !::Rails::VERSION.nil?
|
|
239
203
|
end
|
|
240
204
|
|
|
205
|
+
# Internal method on the tracer pipeline.
|
|
206
|
+
# @api private
|
|
241
207
|
def run_exit_tasks
|
|
242
208
|
if RSpecTracer.no_examples
|
|
243
209
|
RSpecTracer.logger.info 'Skipped reports generation since all examples were filtered out'
|
|
244
210
|
else
|
|
245
|
-
|
|
211
|
+
snapshot = run_finalize
|
|
212
|
+
# Under parallel_tests, defer reporter emission until
|
|
213
|
+
# last-process finalize merges per-worker snapshots into the
|
|
214
|
+
# top-level cache. Each worker still persists its per-worker
|
|
215
|
+
# snapshot for the merge to consume. Earlier behavior had every
|
|
216
|
+
# worker emit reporters into rspec_tracer_report/parallel_tests_N/
|
|
217
|
+
# and purge_worker_dirs! removed those dirs - leaving the user
|
|
218
|
+
# with no usable terminal/JSON/HTML output. Now reporters fire
|
|
219
|
+
# ONCE at the merged top-level location.
|
|
220
|
+
emit_reporters(snapshot) if snapshot && !parallel_tests?
|
|
246
221
|
end
|
|
247
222
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
run_parallel_tests_exit_tasks
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def generate_reports
|
|
254
|
-
RSpecTracer.logger.debug "RSpec tracer is generating reports (pid: #{RSpecTracer.pid})"
|
|
255
|
-
|
|
256
|
-
process_dependency
|
|
257
|
-
process_coverage
|
|
223
|
+
emit_coverage_json
|
|
258
224
|
|
|
259
|
-
RSpecTracer::
|
|
260
|
-
report_writer.write_report
|
|
261
|
-
RSpecTracer::HTMLReporter::Reporter.new(RSpecTracer.report_path, runner.reporter).generate_report
|
|
225
|
+
RSpecTracer::RSpec::ParallelTests.finalize! if parallel_tests?
|
|
262
226
|
end
|
|
263
227
|
|
|
264
|
-
|
|
228
|
+
# Engine-owned finalize path. Writes the 15-file JSON cache via
|
|
229
|
+
# Storage::JsonBackend. Per-example coverage deltas live on the
|
|
230
|
+
# Engine; 2.0 retired the CoverageReporter mid-flow piece (the
|
|
231
|
+
# legacy `coverage_reporter.generate_final_examples_coverage +
|
|
232
|
+
# merge_coverage(engine.merge_skipped_coverage(...))` is now folded
|
|
233
|
+
# into Reporters::CoverageJsonReporter#generate, which fires from
|
|
234
|
+
# `emit_coverage_json` after this method returns).
|
|
235
|
+
def run_finalize
|
|
265
236
|
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
266
237
|
|
|
267
|
-
|
|
268
|
-
runner.register_deleted_examples
|
|
269
|
-
runner.register_dependency(coverage_reporter.examples_coverage)
|
|
270
|
-
runner.register_traced_dependency(@examples_traced_files)
|
|
238
|
+
snapshot = engine.finalize
|
|
271
239
|
|
|
272
240
|
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
273
241
|
elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
|
274
242
|
|
|
275
|
-
RSpecTracer.logger.debug "RSpec tracer
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
def
|
|
328
|
-
return unless
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
report_dir = File.dirname(RSpecTracer.report_path)
|
|
355
|
-
|
|
356
|
-
RSpecTracer::HTMLReporter::Reporter.new(report_dir, report_merger).generate_report
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
def merge_parallel_tests_coverage_reports
|
|
360
|
-
return unless parallel_tests_executed? && !simplecov?
|
|
361
|
-
|
|
362
|
-
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
363
|
-
|
|
364
|
-
reports_dir = parallel_tests_peer_dirs(File.dirname(RSpecTracer.coverage_path))
|
|
365
|
-
|
|
366
|
-
coverage_merger.merge(reports_dir)
|
|
367
|
-
|
|
368
|
-
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
369
|
-
elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
|
370
|
-
|
|
371
|
-
RSpecTracer.logger.debug "RSpec tracer merged parallel tests coverage reports (took #{elapsed})"
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
def write_parallel_tests_coverage_report
|
|
375
|
-
return unless parallel_tests_executed? && !simplecov?
|
|
376
|
-
|
|
377
|
-
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
378
|
-
|
|
379
|
-
coverage_path = File.dirname(RSpecTracer.coverage_path)
|
|
380
|
-
file_name = File.join(coverage_path, 'coverage.json')
|
|
381
|
-
coverage_writer = RSpecTracer::CoverageWriter.new(file_name, coverage_merger)
|
|
382
|
-
|
|
383
|
-
coverage_writer.write_report
|
|
384
|
-
|
|
385
|
-
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
386
|
-
|
|
387
|
-
coverage_writer.print_stats(ending - starting)
|
|
388
|
-
end
|
|
389
|
-
|
|
390
|
-
def purge_parallel_tests_reports
|
|
391
|
-
return unless parallel_tests_executed?
|
|
392
|
-
|
|
393
|
-
[RSpecTracer.cache_path, RSpecTracer.coverage_path, RSpecTracer.report_path].each do |path|
|
|
394
|
-
parallel_tests_peer_dirs(File.dirname(path)).each do |worker_dir|
|
|
395
|
-
FileUtils.rm_rf(worker_dir)
|
|
396
|
-
end
|
|
397
|
-
end
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
# Returns every `parallel_tests_*` subdirectory directly under
|
|
401
|
-
# `base_path`. Used by the parallel_tests merge + purge paths.
|
|
402
|
-
#
|
|
403
|
-
# Earlier patches iterated `1..ENV['PARALLEL_TEST_GROUPS'].to_i`
|
|
404
|
-
# to construct dir names, but parallel_tests's own runner sets
|
|
405
|
-
# PARALLEL_TEST_GROUPS to the user-requested process count
|
|
406
|
-
# (`Parallel.processor_count` by default), NOT the actual worker
|
|
407
|
-
# count. When num_processes < spawned_worker_count, the upper
|
|
408
|
-
# bound was too small: peer caches with TEST_ENV_NUMBER above the
|
|
409
|
-
# bound were silently dropped from the merge AND left behind by
|
|
410
|
-
# the purge. PR #101's commit message documented this gem
|
|
411
|
-
# behaviour for `last_process?` detection but did not extend the
|
|
412
|
-
# fix to the iteration call-sites; this method closes that gap.
|
|
413
|
-
# Globbing the actual filesystem state is robust to the env
|
|
414
|
-
# discrepancy regardless of how the gem partitions specs.
|
|
415
|
-
def parallel_tests_peer_dirs(base_path)
|
|
416
|
-
Dir.glob(File.join(base_path, 'parallel_tests_*')).select do |path|
|
|
417
|
-
File.directory?(path)
|
|
418
|
-
end
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
def parallel_tests_executed?
|
|
422
|
-
return false unless parallel_tests? && parallel_tests_last_process?
|
|
423
|
-
|
|
424
|
-
ParallelTests.wait_for_other_processes_to_finish
|
|
425
|
-
|
|
426
|
-
true
|
|
427
|
-
end
|
|
428
|
-
|
|
429
|
-
# Elects the worker that performs the per-run merge. Delegates to
|
|
430
|
-
# `::ParallelTests.first_process?`, which returns true iff
|
|
431
|
-
# `TEST_ENV_NUMBER.to_i <= 1` — i.e. for exactly one worker
|
|
432
|
-
# (TEST_ENV_NUMBER == '' or '1'), regardless of how many workers
|
|
433
|
-
# were actually spawned vs. how many CPUs the runner reports.
|
|
434
|
-
#
|
|
435
|
-
# Two previously attempted approaches do NOT work here:
|
|
436
|
-
#
|
|
437
|
-
# 1. The lock-file scheme below (each worker writing its
|
|
438
|
-
# TEST_ENV_NUMBER to `rspec_tracer.lock` via
|
|
439
|
-
# `track_parallel_tests_test_env_number`; last_process picked
|
|
440
|
-
# the max) deadlocked under slow CI: worker 1 could finish
|
|
441
|
-
# its examples before worker 2 even loaded spec_helper,
|
|
442
|
-
# observe itself as the max, and enter
|
|
443
|
-
# `::ParallelTests.wait_for_other_processes_to_finish`
|
|
444
|
-
# concurrently with worker 2's own self-election — both
|
|
445
|
-
# workers then spun on each other's pid.
|
|
446
|
-
#
|
|
447
|
-
# 2. `::ParallelTests.last_process?` compares TEST_ENV_NUMBER
|
|
448
|
-
# against PARALLEL_TEST_GROUPS, which parallel_rspec sets to
|
|
449
|
-
# the CPU-based *intended* process count — NOT the actual
|
|
450
|
-
# worker count. When spec files < CPU count (common), no
|
|
451
|
-
# TEST_ENV_NUMBER ever matches PARALLEL_TEST_GROUPS and the
|
|
452
|
-
# merge is silently skipped.
|
|
453
|
-
#
|
|
454
|
-
# `first_process?` avoids both: set by the parent at spawn,
|
|
455
|
-
# immutable thereafter, and identifies exactly one worker
|
|
456
|
-
# regardless of CPU count. The elected worker still calls
|
|
457
|
-
# `wait_for_other_processes_to_finish` before merging so peer
|
|
458
|
-
# caches are guaranteed on disk.
|
|
459
|
-
#
|
|
460
|
-
# `track_parallel_tests_test_env_number` and the lock-file
|
|
461
|
-
# cleanup in `at_exit_behavior` are retained for backward
|
|
462
|
-
# compatibility with users who observe `rspec_tracer.lock` /
|
|
463
|
-
# set `RSPEC_TRACER_LOCK_FILE`; the file is still written and
|
|
464
|
-
# removed but is no longer consulted.
|
|
465
|
-
def parallel_tests_last_process?
|
|
466
|
-
return false unless parallel_tests?
|
|
467
|
-
return false unless defined?(::ParallelTests)
|
|
468
|
-
|
|
469
|
-
::ParallelTests.first_process?
|
|
243
|
+
RSpecTracer.logger.debug "RSpec tracer persisted cache (took #{elapsed})"
|
|
244
|
+
snapshot
|
|
245
|
+
rescue StandardError => e
|
|
246
|
+
# Graceful-degradation contract per ARCHITECTURE.md
|
|
247
|
+
# section Cache corruption recovery: never propagate storage errors
|
|
248
|
+
# into the user's test suite. Read-only cache_path, disk-full
|
|
249
|
+
# mid-write, permission flips between runs - log and skip
|
|
250
|
+
# report emission. The caller (run_exit_tasks) checks for nil
|
|
251
|
+
# before calling emit_reporters; coverage / parallel_tests
|
|
252
|
+
# finalize paths run independently downstream.
|
|
253
|
+
RSpecTracer.logger.warn(
|
|
254
|
+
"rspec-tracer: cache persistence failed (#{e.class}: #{e.message}); " \
|
|
255
|
+
'skipping report generation. Verify cache_path is writable.'
|
|
256
|
+
)
|
|
257
|
+
nil
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Fire the configured reporters against the persisted
|
|
261
|
+
# Snapshot. Fires per-worker under parallel_tests (same cadence as
|
|
262
|
+
# coverage.json emission); each worker produces its own report.json
|
|
263
|
+
# under its per-worker report_dir. The Registry rescues every
|
|
264
|
+
# reporter individually, so a buggy reporter warns and continues -
|
|
265
|
+
# never propagates a non-zero exit into the user's test suite
|
|
266
|
+
# (graceful degradation contract, same as Storage backends).
|
|
267
|
+
def emit_reporters(snapshot)
|
|
268
|
+
RSpecTracer::Reporters::Registry.emit_all(
|
|
269
|
+
configuration: RSpecTracer,
|
|
270
|
+
snapshot: snapshot,
|
|
271
|
+
report_dir: RSpecTracer.report_path,
|
|
272
|
+
run_metadata: build_run_metadata
|
|
273
|
+
)
|
|
274
|
+
rescue StandardError => e
|
|
275
|
+
RSpecTracer.logger.warn(
|
|
276
|
+
"rspec-tracer: reporter pipeline failed (#{e.class}: #{e.message})"
|
|
277
|
+
)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Internal method on the tracer pipeline.
|
|
281
|
+
# @api private
|
|
282
|
+
def build_run_metadata
|
|
283
|
+
{
|
|
284
|
+
pid: RSpecTracer.pid,
|
|
285
|
+
run_time: run_elapsed_seconds,
|
|
286
|
+
started_at: defined?(@run_started_at) ? @run_started_at : nil,
|
|
287
|
+
cache_path: RSpecTracer.cache_path,
|
|
288
|
+
parallel_tests: RSpecTracer.parallel_tests?,
|
|
289
|
+
rails: RSpecTracer.rails?
|
|
290
|
+
}
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Internal method on the tracer pipeline.
|
|
294
|
+
# @api private
|
|
295
|
+
def run_elapsed_seconds
|
|
296
|
+
return nil unless defined?(@run_monotonic_start) && @run_monotonic_start
|
|
297
|
+
|
|
298
|
+
(::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @run_monotonic_start).round(4)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Dedicated coverage.json firing path, parallel to the
|
|
302
|
+
# report-reporters Registry pipeline. Fires unconditionally - even
|
|
303
|
+
# when no examples ran (matches 1.x where coverage.json gets
|
|
304
|
+
# written with whatever boot-time peek_result returned + filter +
|
|
305
|
+
# stub). The emitter handles SimpleCov interop internally
|
|
306
|
+
# (installs `::Coverage.singleton_class.prepend` shim instead of
|
|
307
|
+
# writing coverage.json when SimpleCov is loaded).
|
|
308
|
+
def emit_coverage_json
|
|
309
|
+
return unless engine
|
|
310
|
+
|
|
311
|
+
RSpecTracer::Reporters::CoverageJsonReporter.new(
|
|
312
|
+
snapshot: nil,
|
|
313
|
+
report_dir: RSpecTracer.report_path,
|
|
314
|
+
run_metadata: build_run_metadata,
|
|
315
|
+
logger: RSpecTracer.logger
|
|
316
|
+
).generate
|
|
317
|
+
rescue StandardError => e
|
|
318
|
+
RSpecTracer.logger.warn(
|
|
319
|
+
"rspec-tracer: coverage.json emit failed (#{e.class}: #{e.message})"
|
|
320
|
+
)
|
|
470
321
|
end
|
|
471
322
|
end
|
|
472
323
|
end
|