fast_cov 0.3.0 → 0.3.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/ext/fast_cov/fast_cov.c +0 -78
- data/lib/fast_cov/trackers/abstract_tracker.rb +10 -4
- data/lib/fast_cov/trackers/const_get_tracker.rb +1 -2
- data/lib/fast_cov/trackers/factory_bot_tracker.rb +2 -4
- data/lib/fast_cov/trackers/file_tracker.rb +32 -7
- data/lib/fast_cov/trackers/fixture_kit_tracker.rb +54 -0
- data/lib/fast_cov/utils.rb +44 -0
- data/lib/fast_cov/version.rb +1 -1
- data/lib/fast_cov.rb +2 -0
- metadata +31 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8d5eb20089cfe981168592cd5672e1919746a5186c61f151aff87eea7319eb1c
|
|
4
|
+
data.tar.gz: af45acb129fd680beaa1a906248ad1aedf03e10a5d40d02fbeaa0971e9112b44
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 97cdc30f83fb1970d97d6cddc04758463043a85ee81ccd5a22011e0b017a04d8b9b82be90931b42bdf5565ef163ac84cc1544ec43ecae2cc1727041770adfc52
|
|
7
|
+
data.tar.gz: baa28e4ae365439250404b285eb0577a167de1cb907a3c37b33f09d3a99bf53bf4e6ca0ac6fd2ff84360a048f2795de7ca709ffd11c1236b0846ae66a4f4a75a
|
data/ext/fast_cov/fast_cov.c
CHANGED
|
@@ -140,80 +140,6 @@ static void on_line_event(rb_event_flag_t event, VALUE self_data, VALUE self,
|
|
|
140
140
|
record_impacted_file(data, filename);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
// ---- Utils module methods (FastCov::Utils) ------------------------------
|
|
144
|
-
|
|
145
|
-
// Utils.path_within?(path, directory) -> true/false
|
|
146
|
-
// Check if path is within directory, correctly handling:
|
|
147
|
-
// - Trailing slashes on directory
|
|
148
|
-
// - Sibling directories with longer names (e.g., /a/b/c vs /a/b/cd)
|
|
149
|
-
static VALUE utils_path_within(VALUE self, VALUE path, VALUE directory) {
|
|
150
|
-
Check_Type(path, T_STRING);
|
|
151
|
-
Check_Type(directory, T_STRING);
|
|
152
|
-
|
|
153
|
-
// Freeze strings to prevent GC compaction from moving them
|
|
154
|
-
rb_str_freeze(path);
|
|
155
|
-
rb_str_freeze(directory);
|
|
156
|
-
|
|
157
|
-
bool result = fast_cov_is_within_root(
|
|
158
|
-
RSTRING_PTR(path), RSTRING_LEN(path),
|
|
159
|
-
RSTRING_PTR(directory), RSTRING_LEN(directory));
|
|
160
|
-
|
|
161
|
-
return result ? Qtrue : Qfalse;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Utils.relativize_paths(set, root) -> set
|
|
165
|
-
// Mutates set in place: converts absolute paths to relative paths from root.
|
|
166
|
-
// Paths not within root are left unchanged.
|
|
167
|
-
static VALUE utils_relativize_paths(VALUE self, VALUE set, VALUE root) {
|
|
168
|
-
Check_Type(root, T_STRING);
|
|
169
|
-
|
|
170
|
-
// Freeze root to prevent GC from moving it during compaction
|
|
171
|
-
rb_str_freeze(root);
|
|
172
|
-
|
|
173
|
-
const char *root_ptr = RSTRING_PTR(root);
|
|
174
|
-
long root_len = RSTRING_LEN(root);
|
|
175
|
-
|
|
176
|
-
// Normalize: strip trailing slash for offset calculation
|
|
177
|
-
long effective_root_len = root_len;
|
|
178
|
-
if (effective_root_len > 0 && root_ptr[effective_root_len - 1] == '/') {
|
|
179
|
-
effective_root_len--;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Collect paths to transform (can't modify set while iterating)
|
|
183
|
-
VALUE paths = rb_funcall(set, rb_intern("to_a"), 0);
|
|
184
|
-
long num_paths = RARRAY_LEN(paths);
|
|
185
|
-
|
|
186
|
-
for (long i = 0; i < num_paths; i++) {
|
|
187
|
-
VALUE abs_path = rb_ary_entry(paths, i);
|
|
188
|
-
if (!RB_TYPE_P(abs_path, T_STRING)) continue;
|
|
189
|
-
|
|
190
|
-
// Freeze to prevent GC moving it
|
|
191
|
-
rb_str_freeze(abs_path);
|
|
192
|
-
|
|
193
|
-
const char *path_ptr = RSTRING_PTR(abs_path);
|
|
194
|
-
long path_len = RSTRING_LEN(abs_path);
|
|
195
|
-
|
|
196
|
-
// Use proper within_root check
|
|
197
|
-
if (!fast_cov_is_within_root(path_ptr, path_len, root_ptr, root_len)) {
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Calculate offset (skip root + separator)
|
|
202
|
-
long offset = effective_root_len;
|
|
203
|
-
if (offset < path_len && path_ptr[offset] == '/') offset++;
|
|
204
|
-
|
|
205
|
-
// Create relative path
|
|
206
|
-
VALUE rel_path = rb_str_substr(abs_path, offset, path_len - offset);
|
|
207
|
-
|
|
208
|
-
// Delete old path, add new path
|
|
209
|
-
rb_funcall(set, rb_intern("delete"), 1, abs_path);
|
|
210
|
-
rb_funcall(set, rb_intern("add"), 1, rel_path);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
RB_GC_GUARD(paths);
|
|
214
|
-
return set;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
143
|
// ---- Ruby instance methods ----------------------------------------------
|
|
218
144
|
|
|
219
145
|
static VALUE fast_cov_initialize(int argc, VALUE *argv, VALUE self) {
|
|
@@ -366,8 +292,4 @@ void Init_fast_cov(void) {
|
|
|
366
292
|
rb_define_method(cCoverage, "start", fast_cov_start, 0);
|
|
367
293
|
rb_define_method(cCoverage, "stop", fast_cov_stop, 0);
|
|
368
294
|
|
|
369
|
-
// FastCov::Utils module (C-defined methods)
|
|
370
|
-
VALUE mUtils = rb_define_module_under(mFastCov, "Utils");
|
|
371
|
-
rb_define_module_function(mUtils, "path_within?", utils_path_within, 2);
|
|
372
|
-
rb_define_module_function(mUtils, "relativize_paths", utils_relativize_paths, 2);
|
|
373
295
|
}
|
|
@@ -15,12 +15,18 @@ module FastCov
|
|
|
15
15
|
# - threads: true -> record from ALL threads (global tracking)
|
|
16
16
|
# - threads: false -> only record from the thread that called start
|
|
17
17
|
class AbstractTracker
|
|
18
|
+
attr_reader :coverage_map
|
|
19
|
+
|
|
18
20
|
def initialize(coverage_map, **_options)
|
|
19
21
|
@coverage_map = coverage_map
|
|
20
22
|
@files = nil
|
|
21
23
|
@started_thread = nil
|
|
22
24
|
end
|
|
23
25
|
|
|
26
|
+
def root
|
|
27
|
+
coverage_map.root
|
|
28
|
+
end
|
|
29
|
+
|
|
24
30
|
# Public API - called by FastCov framework
|
|
25
31
|
|
|
26
32
|
def start
|
|
@@ -70,12 +76,12 @@ module FastCov
|
|
|
70
76
|
class << self
|
|
71
77
|
attr_accessor :active
|
|
72
78
|
|
|
73
|
-
def record(to: nil)
|
|
79
|
+
def record(path, to: nil)
|
|
74
80
|
return unless active
|
|
75
|
-
return unless
|
|
81
|
+
return unless path
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
active.record(path, to: to)
|
|
83
|
+
to ||= Utils.resolve_caller(caller_locations(1, 20), active.root)
|
|
84
|
+
active.record(path, to: to)
|
|
79
85
|
end
|
|
80
86
|
|
|
81
87
|
def reset
|
|
@@ -23,9 +23,8 @@ module FastCov
|
|
|
23
23
|
|
|
24
24
|
module ConstGetPatch
|
|
25
25
|
def const_get(name, inherit = true)
|
|
26
|
-
source = caller_locations(1, 1).first&.absolute_path
|
|
27
26
|
result = super
|
|
28
|
-
FastCov::ConstGetTracker.record(
|
|
27
|
+
FastCov::ConstGetTracker.record(const_source_location(name, inherit)&.first)
|
|
29
28
|
result
|
|
30
29
|
end
|
|
31
30
|
end
|
|
@@ -12,9 +12,7 @@ module FastCov
|
|
|
12
12
|
# Register via: coverage_map.use(FastCov::FactoryBotTracker)
|
|
13
13
|
class FactoryBotTracker < AbstractTracker
|
|
14
14
|
def install
|
|
15
|
-
|
|
16
|
-
raise LoadError, "FactoryBotTracker requires the factory_bot gem to be installed"
|
|
17
|
-
end
|
|
15
|
+
gem "factory_bot"
|
|
18
16
|
|
|
19
17
|
return if ::FactoryBot.factories.singleton_class.ancestors.include?(RegistryPatch)
|
|
20
18
|
|
|
@@ -42,7 +40,7 @@ module FastCov
|
|
|
42
40
|
next unless block.is_a?(Proc)
|
|
43
41
|
|
|
44
42
|
location = block.source_location
|
|
45
|
-
record
|
|
43
|
+
record(location&.first)
|
|
46
44
|
end
|
|
47
45
|
end
|
|
48
46
|
end
|
|
@@ -4,21 +4,27 @@ require_relative "abstract_tracker"
|
|
|
4
4
|
|
|
5
5
|
module FastCov
|
|
6
6
|
# Tracks files read from disk during coverage (JSON, YAML, .rb templates, etc.)
|
|
7
|
-
# via File.read
|
|
7
|
+
# via File.read, File.open, and YAML load methods.
|
|
8
|
+
#
|
|
9
|
+
# YAML methods are patched separately because Bootsnap's compile cache
|
|
10
|
+
# overrides YAML.load_file/safe_load_file to bypass File.open entirely.
|
|
8
11
|
#
|
|
9
12
|
# Register via: coverage_map.use(FastCov::FileTracker)
|
|
10
13
|
class FileTracker < AbstractTracker
|
|
11
14
|
def install
|
|
12
|
-
|
|
15
|
+
unless File.singleton_class.ancestors.include?(FilePatch)
|
|
16
|
+
File.singleton_class.prepend(FilePatch)
|
|
17
|
+
end
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
if defined?(::YAML) && !::YAML.singleton_class.ancestors.include?(YamlPatch)
|
|
20
|
+
::YAML.singleton_class.prepend(YamlPatch)
|
|
21
|
+
end
|
|
15
22
|
end
|
|
16
23
|
|
|
17
24
|
module FilePatch
|
|
18
25
|
def read(name, *args, **kwargs, &block)
|
|
19
|
-
source = caller_locations(1, 1).first&.absolute_path
|
|
20
26
|
super.tap do
|
|
21
|
-
FastCov::FileTracker.record(
|
|
27
|
+
FastCov::FileTracker.record(File.expand_path(name))
|
|
22
28
|
end
|
|
23
29
|
end
|
|
24
30
|
|
|
@@ -26,9 +32,28 @@ module FastCov
|
|
|
26
32
|
mode = args[0]
|
|
27
33
|
is_read = mode.nil? || (mode.is_a?(String) && mode.start_with?("r")) ||
|
|
28
34
|
(mode.is_a?(Integer) && (mode & (File::WRONLY | File::RDWR)).zero?)
|
|
29
|
-
source = caller_locations(1, 1).first&.absolute_path
|
|
30
35
|
super.tap do
|
|
31
|
-
FastCov::FileTracker.record(
|
|
36
|
+
FastCov::FileTracker.record(File.expand_path(name)) if is_read
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
module YamlPatch
|
|
42
|
+
def load_file(path, *args, **kwargs)
|
|
43
|
+
super.tap do
|
|
44
|
+
FastCov::FileTracker.record(File.expand_path(path))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def safe_load_file(path, *args, **kwargs)
|
|
49
|
+
super.tap do
|
|
50
|
+
FastCov::FileTracker.record(File.expand_path(path))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def unsafe_load_file(path, *args, **kwargs)
|
|
55
|
+
super.tap do
|
|
56
|
+
FastCov::FileTracker.record(File.expand_path(path))
|
|
32
57
|
end
|
|
33
58
|
end
|
|
34
59
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "abstract_tracker"
|
|
4
|
+
|
|
5
|
+
module FastCov
|
|
6
|
+
# Tracks FixtureKit fixture definition files when fixtures are used.
|
|
7
|
+
#
|
|
8
|
+
# Fixture definitions run once during cache generation (before(:context)),
|
|
9
|
+
# then every test replays cached SQL without executing Ruby. This tracker
|
|
10
|
+
# uses FixtureKit's callback hooks to:
|
|
11
|
+
#
|
|
12
|
+
# 1. Connect the fixture file to all source files touched during generation
|
|
13
|
+
# 2. Record the fixture definition file when a test mounts a fixture
|
|
14
|
+
#
|
|
15
|
+
# Requires fixture_kit >= 0.14.0 (Event-based callbacks).
|
|
16
|
+
#
|
|
17
|
+
# Register via: coverage_map.use(FastCov::FixtureKitTracker)
|
|
18
|
+
class FixtureKitTracker < AbstractTracker
|
|
19
|
+
def install
|
|
20
|
+
gem "fixture_kit", ">= 0.14.0"
|
|
21
|
+
|
|
22
|
+
tracker = self
|
|
23
|
+
|
|
24
|
+
FixtureKit.configure do |config|
|
|
25
|
+
# When a fixture is about to be generated, start the coverage map
|
|
26
|
+
# to track what files the fixture definition touches.
|
|
27
|
+
# This runs in before(:context), before the formatter starts coverage.
|
|
28
|
+
config.on_cache_save do |_fixture|
|
|
29
|
+
tracker.coverage_map.start
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# After generation, stop coverage and connect the fixture file
|
|
33
|
+
# to everything it touched.
|
|
34
|
+
config.on_cache_saved do |fixture, _duration|
|
|
35
|
+
files = tracker.coverage_map.stop
|
|
36
|
+
files.each do |file|
|
|
37
|
+
tracker.coverage_map.connect(from: fixture.path, to: file)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# When a test mounts a fixture, record the fixture definition file
|
|
42
|
+
# and any parent fixture files in the chain.
|
|
43
|
+
config.on_cache_mount do |event|
|
|
44
|
+
tracker.class.record(event.path)
|
|
45
|
+
parent = event.fixture.parent
|
|
46
|
+
while parent
|
|
47
|
+
tracker.class.record(parent.definition.path)
|
|
48
|
+
parent = parent.parent
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FastCov
|
|
4
|
+
module Utils
|
|
5
|
+
# Check if path is within directory, correctly handling:
|
|
6
|
+
# - Trailing slashes on directory
|
|
7
|
+
# - Sibling directories with longer names (e.g., /a/b/c vs /a/b/cd)
|
|
8
|
+
def self.path_within?(path, directory)
|
|
9
|
+
dir = directory.end_with?("/") ? directory.chop : directory
|
|
10
|
+
return true if path == dir
|
|
11
|
+
|
|
12
|
+
path.start_with?("#{dir}/")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Mutates set in place: converts absolute paths to relative paths from root.
|
|
16
|
+
# Paths not within root are left unchanged.
|
|
17
|
+
def self.relativize_paths(set, root)
|
|
18
|
+
prefix = root.end_with?("/") ? root : "#{root}/"
|
|
19
|
+
|
|
20
|
+
set.to_a.each do |abs_path|
|
|
21
|
+
next unless abs_path.is_a?(String)
|
|
22
|
+
next unless abs_path.start_with?(prefix) || abs_path == root.chomp("/")
|
|
23
|
+
|
|
24
|
+
set.delete(abs_path)
|
|
25
|
+
set.add(abs_path.delete_prefix(prefix))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
set
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Walk caller locations to find the first frame whose file is within root.
|
|
32
|
+
# Handles indirect calls (e.g., YAML.load_file -> File.open) where the
|
|
33
|
+
# immediate caller is a stdlib/gem file outside the project.
|
|
34
|
+
def self.resolve_caller(locations, root)
|
|
35
|
+
locations.each do |loc|
|
|
36
|
+
path = loc.absolute_path
|
|
37
|
+
next unless path
|
|
38
|
+
|
|
39
|
+
return path if path_within?(path, root)
|
|
40
|
+
end
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/fast_cov/version.rb
CHANGED
data/lib/fast_cov.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "fast_cov/fast_cov.#{RUBY_VERSION}"
|
|
4
4
|
|
|
5
5
|
module FastCov
|
|
6
|
+
autoload :Utils, File.expand_path("fast_cov/utils", __dir__)
|
|
6
7
|
autoload :VERSION, File.expand_path("fast_cov/version", __dir__)
|
|
7
8
|
autoload :ConnectedDependencies, File.expand_path("fast_cov/connected_dependencies", __dir__)
|
|
8
9
|
autoload :CoverageMap, File.expand_path("fast_cov/coverage_map", __dir__)
|
|
@@ -10,5 +11,6 @@ module FastCov
|
|
|
10
11
|
autoload :FileTracker, File.expand_path("fast_cov/trackers/file_tracker", __dir__)
|
|
11
12
|
autoload :FactoryBotTracker, File.expand_path("fast_cov/trackers/factory_bot_tracker", __dir__)
|
|
12
13
|
autoload :ConstGetTracker, File.expand_path("fast_cov/trackers/const_get_tracker", __dir__)
|
|
14
|
+
autoload :FixtureKitTracker, File.expand_path("fast_cov/trackers/fixture_kit_tracker", __dir__)
|
|
13
15
|
autoload :StaticMap, File.expand_path("fast_cov/static_map", __dir__)
|
|
14
16
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fast_cov
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ngan Pham
|
|
@@ -65,6 +65,34 @@ dependencies:
|
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '6.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: fixture_kit
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: 0.14.0
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 0.14.0
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: sqlite3
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
68
96
|
description: A high-performance C extension that tracks which Ruby source files are
|
|
69
97
|
executed during test runs, enabling test impact analysis.
|
|
70
98
|
executables: []
|
|
@@ -91,6 +119,8 @@ files:
|
|
|
91
119
|
- lib/fast_cov/trackers/const_get_tracker.rb
|
|
92
120
|
- lib/fast_cov/trackers/factory_bot_tracker.rb
|
|
93
121
|
- lib/fast_cov/trackers/file_tracker.rb
|
|
122
|
+
- lib/fast_cov/trackers/fixture_kit_tracker.rb
|
|
123
|
+
- lib/fast_cov/utils.rb
|
|
94
124
|
- lib/fast_cov/version.rb
|
|
95
125
|
homepage: https://github.com/Gusto/fast_cov
|
|
96
126
|
licenses:
|