rspec-tracer 1.2.3 → 2.0.0.pre.2
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 +384 -67
- data/README.md +454 -429
- data/bin/rspec-tracer +15 -0
- data/lib/rspec_tracer/cache/Rakefile +43 -0
- data/lib/rspec_tracer/cli/cache_clear.rb +111 -0
- data/lib/rspec_tracer/cli/cache_info.rb +104 -0
- data/lib/rspec_tracer/cli/doctor.rb +284 -0
- data/lib/rspec_tracer/cli/explain.rb +158 -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 +1196 -3
- data/lib/rspec_tracer/engine.rb +1168 -0
- data/lib/rspec_tracer/example.rb +141 -11
- 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 +436 -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 +239 -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 +130 -0
- data/lib/rspec_tracer/storage/json_backend.rb +884 -0
- data/lib/rspec_tracer/storage/lazy_snapshot.rb +65 -0
- data/lib/rspec_tracer/storage/schema.rb +50 -0
- data/lib/rspec_tracer/storage/serializer/json.rb +41 -0
- data/lib/rspec_tracer/storage/serializer/msgpack.rb +167 -0
- data/lib/rspec_tracer/storage/snapshot.rb +141 -0
- data/lib/rspec_tracer/storage/sqlite_backend.rb +693 -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 +231 -491
- metadata +94 -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
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'snapshot'
|
|
4
|
+
|
|
5
|
+
module RSpecTracer
|
|
6
|
+
module Storage
|
|
7
|
+
# Lazy-reading view returned by `JsonBackend#load_graph`. Presents
|
|
8
|
+
# the same field surface as `Storage::Snapshot` but defers the disk
|
|
9
|
+
# read + deserialization for each field until the caller touches it.
|
|
10
|
+
#
|
|
11
|
+
# Rationale: on a large cache (100 MB+), eager-reading every file
|
|
12
|
+
# at setup dominates warm-run startup (issue #17). Engine
|
|
13
|
+
# at setup touches 10/15 fields; reporters never touch the previous
|
|
14
|
+
# snapshot; third-party tooling often reads one or two. The 3-5
|
|
15
|
+
# fields the Engine does NOT touch at setup (duplicate_examples,
|
|
16
|
+
# reverse_dependency, env_dependency; examples_coverage is deferred
|
|
17
|
+
# to finalize per the seed refactor) save their full disk+parse
|
|
18
|
+
# cost for every run.
|
|
19
|
+
#
|
|
20
|
+
# Duck-types `Snapshot`: every Struct member becomes a method here,
|
|
21
|
+
# `respond_to?` returns true for each, `to_h` materializes the
|
|
22
|
+
# whole thing. Callers doing `prev.send(field)` or `prev.field`
|
|
23
|
+
# work unchanged.
|
|
24
|
+
#
|
|
25
|
+
# Thread-safety: the memoization Hash is not guarded. Engine is
|
|
26
|
+
# single-threaded per run; parallel_tests workers each own their
|
|
27
|
+
# own LazySnapshot instance. If a future caller reads fields from
|
|
28
|
+
# multiple threads, wrap with a Monitor at construct time.
|
|
29
|
+
class LazySnapshot
|
|
30
|
+
LAZY_FIELDS = (Snapshot.members - %i[schema_version run_id]).freeze
|
|
31
|
+
|
|
32
|
+
attr_reader :schema_version, :run_id
|
|
33
|
+
|
|
34
|
+
def initialize(schema_version:, run_id:, reader:)
|
|
35
|
+
@schema_version = schema_version
|
|
36
|
+
@run_id = run_id
|
|
37
|
+
@reader = reader
|
|
38
|
+
@loaded = {}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
LAZY_FIELDS.each do |field|
|
|
42
|
+
define_method(field) do
|
|
43
|
+
return @loaded[field] if @loaded.key?(field)
|
|
44
|
+
|
|
45
|
+
@loaded[field] = @reader.read(field)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Hash view matching `Struct#to_h` so callers composing snapshots
|
|
50
|
+
# (merge pipelines, reporter helpers) get the familiar shape.
|
|
51
|
+
# Forces every field to materialize, so only call this when the
|
|
52
|
+
# full cache is genuinely required.
|
|
53
|
+
def to_h
|
|
54
|
+
Snapshot.members.to_h { |m| [m, public_send(m)] }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Materialize a full eager Snapshot. Use when an API requires the
|
|
58
|
+
# Struct type (Merger input, backends that accept Snapshot on
|
|
59
|
+
# save). Reads every field.
|
|
60
|
+
def to_snapshot
|
|
61
|
+
Snapshot.new(**to_h)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSpecTracer
|
|
4
|
+
# Internal Storage — see {RSpecTracer} for the user-facing surface.
|
|
5
|
+
# @api private
|
|
6
|
+
module Storage
|
|
7
|
+
# Cache schema version and compatibility policy. 1.x shipped caches
|
|
8
|
+
# without any version stamp; 2.0's first migration is simply to
|
|
9
|
+
# start emitting a version and refuse to load anything else.
|
|
10
|
+
#
|
|
11
|
+
# Compatibility rule (from ARCHITECTURE.md):
|
|
12
|
+
# - `CURRENT` is written into every new cache manifest.
|
|
13
|
+
# - `SUPPORTED` is the set of versions this backend is willing
|
|
14
|
+
# to load. For 2.0 there is exactly one supported version.
|
|
15
|
+
# - On mismatch, `load_graph` returns nil after an `info` log
|
|
16
|
+
# line and the run proceeds cold. No in-place migrators.
|
|
17
|
+
#
|
|
18
|
+
# Future version bumps (schema_version 3, 4, ...) add entries to
|
|
19
|
+
# `SUPPORTED` only if the backend can load both shapes. If a
|
|
20
|
+
# change is breaking, `SUPPORTED` resets to `[CURRENT]` and the
|
|
21
|
+
# caller pays one cold run on upgrade - the deal 1.x users already
|
|
22
|
+
# expect for any rspec-tracer version bump.
|
|
23
|
+
module Schema
|
|
24
|
+
# 1.x caches were unstamped; schema_version 2 was the first
|
|
25
|
+
# versioned schema, and 2.0 bumped to 3 when `Snapshot.boot_set`
|
|
26
|
+
# landed. schema_version 4 reshaped the example-identity payload:
|
|
27
|
+
# `example_id` hashes the describe block's *description* (not
|
|
28
|
+
# RSpec's load-order-dependent class name) and excludes line
|
|
29
|
+
# numbers. schema_version 5 closes the remaining gap for unnamed
|
|
30
|
+
# examples (`it { }` / `specify { }` / `example { }`): their
|
|
31
|
+
# `example_id` now derives from an intra-group ordinal instead
|
|
32
|
+
# of RSpec's line-bearing `"example at <path>:<line>"` fallback,
|
|
33
|
+
# so a 4-stamped cache's unnamed-example ids no longer match.
|
|
34
|
+
# Each bump is breaking, so SUPPORTED stays `[CURRENT]` and the
|
|
35
|
+
# caller pays one cold run on upgrade.
|
|
36
|
+
CURRENT = 5
|
|
37
|
+
# Internal constant.
|
|
38
|
+
# @api private
|
|
39
|
+
SUPPORTED = [CURRENT].freeze
|
|
40
|
+
|
|
41
|
+
# True when the caller can load a cache stamped with `version`.
|
|
42
|
+
# nil (an unstamped 1.x cache) is explicitly unsupported -
|
|
43
|
+
# treating nil as "compatible" would defeat the whole point of
|
|
44
|
+
# the version field. The caller logs and falls back to cold run.
|
|
45
|
+
def self.supported?(version)
|
|
46
|
+
SUPPORTED.include?(version)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module RSpecTracer
|
|
6
|
+
# Internal Storage — see {RSpecTracer} for the user-facing surface.
|
|
7
|
+
# @api private
|
|
8
|
+
module Storage
|
|
9
|
+
# Internal Serializer — see {RSpecTracer} for the user-facing surface.
|
|
10
|
+
# @api private
|
|
11
|
+
module Serializer
|
|
12
|
+
# Default serializer for JsonBackend. Produces pretty-printed
|
|
13
|
+
# JSON strings; reads tolerate binary-mode bytes by forcing
|
|
14
|
+
# UTF-8 on decode (preserves the fix for example titles with
|
|
15
|
+
# non-ASCII bytes on US-ASCII-defaulted filesystems).
|
|
16
|
+
#
|
|
17
|
+
# Class-level methods (not module_function) so mutant-rspec can
|
|
18
|
+
# observe mutations through the call path; see the mutation-
|
|
19
|
+
# friendly-modules memo.
|
|
20
|
+
class Json
|
|
21
|
+
# Internal helper for the tracer pipeline.
|
|
22
|
+
# @api private
|
|
23
|
+
def self.extension
|
|
24
|
+
'json'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Internal helper for the tracer pipeline.
|
|
28
|
+
# @api private
|
|
29
|
+
def self.encode(payload)
|
|
30
|
+
::JSON.pretty_generate(payload)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Internal helper for the tracer pipeline.
|
|
34
|
+
# @api private
|
|
35
|
+
def self.decode(bytes)
|
|
36
|
+
::JSON.parse(bytes.dup.force_encoding('UTF-8'))
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'zlib'
|
|
4
|
+
|
|
5
|
+
module RSpecTracer
|
|
6
|
+
# Internal Storage — see {RSpecTracer} for the user-facing surface.
|
|
7
|
+
# @api private
|
|
8
|
+
module Storage
|
|
9
|
+
# Internal Serializer — see {RSpecTracer} for the user-facing surface.
|
|
10
|
+
# @api private
|
|
11
|
+
module Serializer
|
|
12
|
+
# Raised when the caller asks for :msgpack but the msgpack gem
|
|
13
|
+
# is not in the user's bundle. JsonBackend rescues at construct
|
|
14
|
+
# time + falls back to the Json serializer with a warn line,
|
|
15
|
+
# same optional-dep pattern the RedisBackend uses for the redis
|
|
16
|
+
# gem.
|
|
17
|
+
class MsgpackGemNotInstalled < StandardError; end
|
|
18
|
+
|
|
19
|
+
# MessagePack + zlib serializer. msgpack encode is ~2x smaller
|
|
20
|
+
# than pretty JSON on our representative dependency graphs;
|
|
21
|
+
# zlib deflate then buys another ~5x on path-repetitive
|
|
22
|
+
# payloads (dependency.json is the big one - the same path
|
|
23
|
+
# string appears once per example that touched it). Combined
|
|
24
|
+
# ratio matches the ~4-6x claim in remote_cache/archive.rb.
|
|
25
|
+
#
|
|
26
|
+
# Stdlib zlib avoids adding a second gem dep; msgpack itself is
|
|
27
|
+
# the single new dev-group gem. Users who want the backend
|
|
28
|
+
# add `gem 'msgpack'` to their own Gemfile per
|
|
29
|
+
# USER_FACING_SURFACE.md optional-dep convention.
|
|
30
|
+
#
|
|
31
|
+
# The require is lazy so pure-Ruby suites that stay on :json
|
|
32
|
+
# do not pay the msgpack load cost and so the LoadError path
|
|
33
|
+
# is exercisable in unit specs (hide_const-based).
|
|
34
|
+
#
|
|
35
|
+
# Type extensions: Ruby `Time` and `Symbol` values surface in
|
|
36
|
+
# example metadata (RSpec's `execution_result.started_at` is a
|
|
37
|
+
# Time; status values like `:passed` / `:failed` / `:flaky` are
|
|
38
|
+
# Symbols on the in-memory snapshot). The bare msgpack default
|
|
39
|
+
# registry has no `Time` packer (crashes
|
|
40
|
+
# `NoMethodError: undefined method 'to_msgpack'`) and silently
|
|
41
|
+
# coerces `Symbol` to `String` (lossy round-trip). Both
|
|
42
|
+
# behaviors broke the cache on the first run a user followed
|
|
43
|
+
# the 50 MiB warning's `:msgpack` recommendation. Registering
|
|
44
|
+
# `Factory` type extensions for `Time` (ID 0x00, 12-byte
|
|
45
|
+
# seconds+nanoseconds payload) and `Symbol` (ID 0x01, UTF-8
|
|
46
|
+
# string payload) gives lossless round-trip. The factory is
|
|
47
|
+
# memoized so the extension registration is one-shot per
|
|
48
|
+
# process.
|
|
49
|
+
class Msgpack
|
|
50
|
+
# Internal constant — `MessagePack::Factory#register_type`
|
|
51
|
+
# ID for Ruby `Time`. Codepoint chosen from the user-space
|
|
52
|
+
# range (0x00–0x7F per msgpack ext spec); collision-safe with
|
|
53
|
+
# the built-in timestamp ext (ID -1 / 0xFF) since IDs are
|
|
54
|
+
# disjoint.
|
|
55
|
+
# @api private
|
|
56
|
+
TIME_EXTENSION_TYPE = 0x00
|
|
57
|
+
# Internal constant — `MessagePack::Factory#register_type` ID
|
|
58
|
+
# for Ruby `Symbol`. See {TIME_EXTENSION_TYPE} rationale.
|
|
59
|
+
# @api private
|
|
60
|
+
SYMBOL_EXTENSION_TYPE = 0x01
|
|
61
|
+
|
|
62
|
+
# Internal helper for the tracer pipeline.
|
|
63
|
+
# @api private
|
|
64
|
+
def self.extension
|
|
65
|
+
'msgpack.gz'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Internal helper for the tracer pipeline.
|
|
69
|
+
# @api private
|
|
70
|
+
def self.encode(payload)
|
|
71
|
+
::Zlib::Deflate.deflate(factory.pack(payload))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Internal helper for the tracer pipeline.
|
|
75
|
+
# @api private
|
|
76
|
+
def self.decode(bytes)
|
|
77
|
+
factory.unpack(::Zlib::Inflate.inflate(bytes))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Probe used by JsonBackend#initialize to decide whether to
|
|
81
|
+
# fall back to the Json serializer at construct time. True
|
|
82
|
+
# iff `require 'msgpack'` succeeds; false when the gem is
|
|
83
|
+
# missing. Idempotent without an explicit memo because Ruby's
|
|
84
|
+
# `require` short-circuits via `$LOADED_FEATURES` on repeat
|
|
85
|
+
# calls and `defined?(::MessagePack)` cheaply detects the
|
|
86
|
+
# post-load constant. The previous @msgpack_loaded ivar memo
|
|
87
|
+
# was load-bearing for mutation observability: once any prior
|
|
88
|
+
# test tripped the memo, mutations on the require line were
|
|
89
|
+
# observably equivalent.
|
|
90
|
+
def self.available?
|
|
91
|
+
ensure_available!
|
|
92
|
+
true
|
|
93
|
+
rescue MsgpackGemNotInstalled
|
|
94
|
+
false
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class << self
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
# `MessagePack::Factory` with `Time` and `Symbol` type
|
|
101
|
+
# extensions registered. Memoized for the process lifetime;
|
|
102
|
+
# the factory's `register_type` calls are not idempotent
|
|
103
|
+
# (re-registering would double-register the codepoint), so
|
|
104
|
+
# one-shot construction is the simplest correctness
|
|
105
|
+
# contract.
|
|
106
|
+
def factory
|
|
107
|
+
return @factory if defined?(@factory) && @factory
|
|
108
|
+
|
|
109
|
+
ensure_available!
|
|
110
|
+
@factory = build_factory
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# `Time` and `Symbol` packers / unpackers. Packers are
|
|
114
|
+
# called by `factory.pack` whenever a Ruby `Time` /
|
|
115
|
+
# `Symbol` appears at any depth in the payload (top-level
|
|
116
|
+
# value, nested Hash value, Array element). Unpackers are
|
|
117
|
+
# called by `factory.unpack` on the ext-typed bytes.
|
|
118
|
+
# Symbol pack/unpack maps to `to_s` / `to_sym` directly
|
|
119
|
+
# via `Symbol#to_proc`; Time is more involved so it gets
|
|
120
|
+
# named `pack_time` / `unpack_time` helpers below.
|
|
121
|
+
def build_factory
|
|
122
|
+
f = ::MessagePack::Factory.new
|
|
123
|
+
f.register_type(
|
|
124
|
+
TIME_EXTENSION_TYPE, ::Time,
|
|
125
|
+
packer: method(:pack_time),
|
|
126
|
+
unpacker: method(:unpack_time)
|
|
127
|
+
)
|
|
128
|
+
f.register_type(
|
|
129
|
+
SYMBOL_EXTENSION_TYPE, ::Symbol,
|
|
130
|
+
packer: :to_s.to_proc,
|
|
131
|
+
unpacker: :to_sym.to_proc
|
|
132
|
+
)
|
|
133
|
+
f
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Time encoding: 64-bit signed seconds (`tv_sec`) + 32-bit
|
|
137
|
+
# signed nanoseconds (`tv_nsec`), little-endian, 12 bytes
|
|
138
|
+
# total. Round-trip canonicalizes to UTC — rspec-tracer
|
|
139
|
+
# never re-uses the Time as a user-facing wall-clock value
|
|
140
|
+
# (cache entries are compared, not displayed), and UTC
|
|
141
|
+
# makes the on-disk bytes timezone-independent so a cache
|
|
142
|
+
# built in one tz reads back identically in another.
|
|
143
|
+
def pack_time(time)
|
|
144
|
+
[time.tv_sec, time.tv_nsec].pack('q<l<')
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def unpack_time(data)
|
|
148
|
+
sec, nsec = data.unpack('q<l<')
|
|
149
|
+
::Time.at(sec, nsec, :nanosecond).utc
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Internal method on the tracer pipeline.
|
|
153
|
+
# @api private
|
|
154
|
+
def ensure_available!
|
|
155
|
+
return if defined?(::MessagePack)
|
|
156
|
+
|
|
157
|
+
require 'msgpack'
|
|
158
|
+
rescue ::LoadError
|
|
159
|
+
raise MsgpackGemNotInstalled,
|
|
160
|
+
"msgpack gem is not installed; add `gem 'msgpack'` to your Gemfile " \
|
|
161
|
+
'to use the :msgpack serializer.'
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
5
|
+
module RSpecTracer
|
|
6
|
+
# Internal Storage — see {RSpecTracer} for the user-facing surface.
|
|
7
|
+
# @api private
|
|
8
|
+
module Storage
|
|
9
|
+
# Value object returned by `Backend#load_graph` and accepted by
|
|
10
|
+
# `Backend#save_graph`. Bundles every collection that 1.x's
|
|
11
|
+
# report_writer persists plus the schema_version + run_id envelope
|
|
12
|
+
# so a full cache can be reconstructed in one trip.
|
|
13
|
+
#
|
|
14
|
+
# The field layout mirrors 1.x's on-disk contract (see
|
|
15
|
+
# `JsonBackend::FILENAMES`); every field round-trips through
|
|
16
|
+
# `to_h` / `from_h` so the backend can serialize without reaching
|
|
17
|
+
# into struct internals.
|
|
18
|
+
#
|
|
19
|
+
# `examples_coverage` may be `nil` when a caller explicitly loads
|
|
20
|
+
# the cheap header only. The default load is eager - `nil` vs `{}`
|
|
21
|
+
# distinguishes "not yet loaded" from "loaded and empty."
|
|
22
|
+
#
|
|
23
|
+
# Methods are defined on the reopened class body (not inside the
|
|
24
|
+
# Struct.new block) so mutant can introspect them - same pattern
|
|
25
|
+
# as Tracker::Input.
|
|
26
|
+
#
|
|
27
|
+
# `boot_set` - Hash[relative_path => sha256_hex] of every project
|
|
28
|
+
# file loaded before any example runs (spec_helper requires, gem
|
|
29
|
+
# boot, eager autoload). Backs the constants-blind-spot fix: the
|
|
30
|
+
# engine compares this against the previous run's boot_set and ORs
|
|
31
|
+
# any mismatch with WholeSuiteInvalidators when computing the
|
|
32
|
+
# whole_suite_invalidated bool. Per-example loaded-set attribution
|
|
33
|
+
# folds into `dependency` via the existing graph registration path
|
|
34
|
+
# - no separate field.
|
|
35
|
+
#
|
|
36
|
+
# `wsi_snapshot` - Hash[watch_name => sha256_hex] produced by
|
|
37
|
+
# `WholeSuiteInvalidators#digest_snapshot`. Without it, a warm run
|
|
38
|
+
# can't tell whether Gemfile.lock / .ruby-version / .rspec-tracer
|
|
39
|
+
# (or the tracer gem identity) changed since the previous run, and
|
|
40
|
+
# the engine falls back to "first run = invalidate everything" on
|
|
41
|
+
# every warm run. The field is optional in the JSON layout so older
|
|
42
|
+
# caches continue to load (missing wsi.json coerces to `{}`, which
|
|
43
|
+
# compares unequal and triggers one cold re-run - safe fallback,
|
|
44
|
+
# same cost as any other cache miss).
|
|
45
|
+
#
|
|
46
|
+
# `env_snapshot` - Hash[env_name => md5_hex] produced by
|
|
47
|
+
# `Tracker::EnvSnapshot#digest_snapshot`. Covers env-var values
|
|
48
|
+
# declared via the per-example `tracks: { env: ... }` DSL.
|
|
49
|
+
# Without it, a warm run can't tell whether an env-gated example
|
|
50
|
+
# needs to re-run when the env changes. Same optional-in-JSON
|
|
51
|
+
# treatment as wsi_snapshot: missing file coerces to `{}`, no
|
|
52
|
+
# schema_version bump, one cold re-run on upgrade.
|
|
53
|
+
#
|
|
54
|
+
# `env_dependency` - Hash[example_id => Array<env_name>] capturing
|
|
55
|
+
# which env keys each tracked example declared. The per-run
|
|
56
|
+
# `env_snapshot` stores the digest of each key; this map stores
|
|
57
|
+
# the example-to-key attribution that the reporter layer needs to
|
|
58
|
+
# render "which env vars does this example depend on." Without it,
|
|
59
|
+
# reports can't surface env dependencies - Engine's per-run
|
|
60
|
+
# `@tracks_env` map would otherwise be lost at finalize.
|
|
61
|
+
# Same optional-in-JSON treatment as wsi_snapshot / env_snapshot:
|
|
62
|
+
# missing file coerces to `{}`, no schema_version bump.
|
|
63
|
+
#
|
|
64
|
+
# `cache_hit_reason` is a SUITE-LEVEL aggregate. It maps
|
|
65
|
+
# `reason_string => count` (e.g. `{"Files changed" => 12,
|
|
66
|
+
# "No cache" => 5}`) and surfaces "why did each non-skipped
|
|
67
|
+
# example run." JsonBackend persists it as `cache_hit_reason.json`
|
|
68
|
+
# under the per-run dir (same shape as wsi_snapshot / env_snapshot:
|
|
69
|
+
# one file per field; missing file coerces to `{}`, no
|
|
70
|
+
# schema_version bump). SqliteBackend does not persist it (would
|
|
71
|
+
# require a meta-table column / schema bump); SqliteBackend users
|
|
72
|
+
# see the field as `{}` on read until a future enhancement
|
|
73
|
+
# extends the meta table.
|
|
74
|
+
#
|
|
75
|
+
# `filtered_examples` is the PER-EXAMPLE source-of-truth that
|
|
76
|
+
# backs `cache_hit_reason`: a Hash[example_id => reason_string]
|
|
77
|
+
# ("ex_abc" => "Failed previously"). The engine writes this at
|
|
78
|
+
# finalize; `cache_hit_reason` is the values-tally. Persisting
|
|
79
|
+
# both lets the parallel-tests merge collapse per-worker
|
|
80
|
+
# duplicates by id (every worker computes the same hash because
|
|
81
|
+
# the filter walks the global previous-run snapshot) and re-tally
|
|
82
|
+
# at merge time, instead of sum-merging identical per-worker
|
|
83
|
+
# tallies and inflating counts N-fold. Same optional-in-JSON
|
|
84
|
+
# treatment as cache_hit_reason: missing file coerces to `{}`,
|
|
85
|
+
# no schema_version bump, JSON-backend-only surface.
|
|
86
|
+
Snapshot = Struct.new(
|
|
87
|
+
:schema_version,
|
|
88
|
+
:run_id,
|
|
89
|
+
:all_examples,
|
|
90
|
+
:duplicate_examples,
|
|
91
|
+
:interrupted_examples,
|
|
92
|
+
:flaky_examples,
|
|
93
|
+
:failed_examples,
|
|
94
|
+
:pending_examples,
|
|
95
|
+
:skipped_examples,
|
|
96
|
+
:all_files,
|
|
97
|
+
:dependency,
|
|
98
|
+
:reverse_dependency,
|
|
99
|
+
:examples_coverage,
|
|
100
|
+
:boot_set,
|
|
101
|
+
:wsi_snapshot,
|
|
102
|
+
:env_snapshot,
|
|
103
|
+
:env_dependency,
|
|
104
|
+
:cache_hit_reason,
|
|
105
|
+
:filtered_examples,
|
|
106
|
+
keyword_init: true
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Internal Snapshot — see {RSpecTracer} for the user-facing surface.
|
|
110
|
+
# @api private
|
|
111
|
+
class Snapshot
|
|
112
|
+
# Defaults every collection to its 1.x starting shape: Hash for
|
|
113
|
+
# keyed collections, Set for example-id lists. Keeps spec
|
|
114
|
+
# construction terse and prevents accidental nil-deref when a
|
|
115
|
+
# save is composed incrementally.
|
|
116
|
+
def self.empty(schema_version:, run_id:)
|
|
117
|
+
new(
|
|
118
|
+
schema_version: schema_version,
|
|
119
|
+
run_id: run_id,
|
|
120
|
+
all_examples: {},
|
|
121
|
+
duplicate_examples: {},
|
|
122
|
+
interrupted_examples: Set.new,
|
|
123
|
+
flaky_examples: Set.new,
|
|
124
|
+
failed_examples: Set.new,
|
|
125
|
+
pending_examples: Set.new,
|
|
126
|
+
skipped_examples: Set.new,
|
|
127
|
+
all_files: {},
|
|
128
|
+
dependency: {},
|
|
129
|
+
reverse_dependency: {},
|
|
130
|
+
examples_coverage: {},
|
|
131
|
+
boot_set: {},
|
|
132
|
+
wsi_snapshot: {},
|
|
133
|
+
env_snapshot: {},
|
|
134
|
+
env_dependency: {},
|
|
135
|
+
cache_hit_reason: {},
|
|
136
|
+
filtered_examples: {}
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|