fast_cov 0.3.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a8e169598d457f54b52e872c65d55a9e87164bcac66654d7b21d9497aeba43a
4
- data.tar.gz: 678150e27c7fb664bc5786764b063c1abb75218048f226b39495bc97c012ce75
3
+ metadata.gz: ba4073b332a0c7f0837d5cf954b9ad98353721e6350c24969541819a42a27c65
4
+ data.tar.gz: c85de804a17e0f1bd7b21a370e61dbe0d59dc2bb5ce02d84f827c500d6816b5a
5
5
  SHA512:
6
- metadata.gz: 68e671eab2e3583313b60e408f179006d77aa0d571d1b258b971916b38642fcedae05b8b728ff0588000bd69ada0183a3502b8f6b4e90a37fda3847be3dd6fcd
7
- data.tar.gz: d4531b99a2ca2cfb1a566f63bed51444a91bedfd0356a33d50101f18aafa0020ed40d652199de538cb351c6ac2e5e6a7185c1a3aaef316bc444779767a19ad23
6
+ metadata.gz: f080c26c0a0813a10555885ac162e7662519641501d5d499bebbb1315414ba3469770ac66d95576d6209cc7e9c8b4181fa43c6a729c3d4ccd2c282a1394a86f6
7
+ data.tar.gz: 0ff5f9c3053015028416a2d5190785542abf1bfe0ce6fe2c0d8dae5ab16e555419f5a8b350980119ebf87065b1166b5f700f6131c582c4df9c36b5eb3a0b5f8e
@@ -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
  }
@@ -21,6 +21,10 @@ module FastCov
21
21
  @started_thread = nil
22
22
  end
23
23
 
24
+ def root
25
+ @coverage_map.root
26
+ end
27
+
24
28
  # Public API - called by FastCov framework
25
29
 
26
30
  def start
@@ -70,12 +74,12 @@ module FastCov
70
74
  class << self
71
75
  attr_accessor :active
72
76
 
73
- def record(to: nil)
77
+ def record(path, to: nil)
74
78
  return unless active
75
- return unless block_given?
79
+ return unless path
76
80
 
77
- path = yield
78
- active.record(path, to: to) if path
81
+ to ||= Utils.resolve_caller(caller_locations(1, 20), active.root)
82
+ active.record(path, to: to)
79
83
  end
80
84
 
81
85
  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(to: source) { const_source_location(name, inherit)&.first }
27
+ FastCov::ConstGetTracker.record(const_source_location(name, inherit)&.first)
29
28
  result
30
29
  end
31
30
  end
@@ -42,7 +42,7 @@ module FastCov
42
42
  next unless block.is_a?(Proc)
43
43
 
44
44
  location = block.source_location
45
- record { location&.first }
45
+ record(location&.first)
46
46
  end
47
47
  end
48
48
  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 and File.open.
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
- return if File.singleton_class.ancestors.include?(FilePatch)
15
+ unless File.singleton_class.ancestors.include?(FilePatch)
16
+ File.singleton_class.prepend(FilePatch)
17
+ end
13
18
 
14
- File.singleton_class.prepend(FilePatch)
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(to: source) { File.expand_path(name) }
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(to: source) { File.expand_path(name) } if is_read
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,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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FastCov
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1"
5
5
  end
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__)
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.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ngan Pham
@@ -91,6 +91,7 @@ files:
91
91
  - lib/fast_cov/trackers/const_get_tracker.rb
92
92
  - lib/fast_cov/trackers/factory_bot_tracker.rb
93
93
  - lib/fast_cov/trackers/file_tracker.rb
94
+ - lib/fast_cov/utils.rb
94
95
  - lib/fast_cov/version.rb
95
96
  homepage: https://github.com/Gusto/fast_cov
96
97
  licenses: