fast_cov 0.4.2 → 0.5.0
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/extconf.rb +3 -2
- data/ext/fast_cov/fast_cov.c +148 -5
- data/ext/fast_cov/fast_cov.h +3 -3
- data/ext/fast_cov/fast_cov_utils.c +6 -6
- data/lib/fast_cov/benchmark/scenarios.rb +14 -0
- data/lib/fast_cov/compiler.rb +6 -3
- data/lib/fast_cov/coverage_map.rb +19 -7
- data/lib/fast_cov/trackers/const_get_tracker.rb +7 -1
- data/lib/fast_cov/trackers/file_tracker.rb +12 -8
- data/lib/fast_cov/trackers/fixture_kit_tracker.rb +2 -0
- data/lib/fast_cov/version.rb +1 -1
- data/lib/fast_cov.rb +5 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f05621dfa93b2807fd7060732bd323ddd407e9af1d80b0857d5d50d6c8667a24
|
|
4
|
+
data.tar.gz: f0cc0a9afd99fddac4e0cc263c0dc5edefb373e5da1b145f12148c2a3640aa39
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8736b3e941eb96518a55ff674429610cb429e790a4fcf67a808d5b5830d3f5110ce443ab18a8e15b657a74a4f3f69734121c8bb924b14b93f0b12eddc00745b2
|
|
7
|
+
data.tar.gz: cc856dc60b6d55b534982b1cb83e6bbf8f5b699ff7f1bbd39b9f96749a119261880ec010243a72cbddb00c535a6e47bd0582546a5288ea6370bfa89a43eea582
|
data/ext/fast_cov/extconf.rb
CHANGED
|
@@ -8,5 +8,6 @@ end
|
|
|
8
8
|
|
|
9
9
|
require "mkmf"
|
|
10
10
|
|
|
11
|
-
#
|
|
12
|
-
|
|
11
|
+
# Tagged with the ABI version so multiple Ruby versions can coexist in
|
|
12
|
+
# development. Must stay in sync with the require in lib/fast_cov.rb.
|
|
13
|
+
create_makefile("fast_cov/fast_cov.#{RbConfig::CONFIG["ruby_version"]}")
|
data/ext/fast_cov/fast_cov.c
CHANGED
|
@@ -17,6 +17,13 @@
|
|
|
17
17
|
static VALUE fast_cov_stop(VALUE self);
|
|
18
18
|
static VALUE fast_cov_yield_block(VALUE _arg);
|
|
19
19
|
|
|
20
|
+
// Seen-set sizing. Power-of-two capacities so lookups mask instead of modulo.
|
|
21
|
+
#define SEEN_INITIAL_CAPACITY 256
|
|
22
|
+
// Grow at 3/4 load. Linear probing degrades sharply past that, and a full
|
|
23
|
+
// table would make the insert probe loop spin forever.
|
|
24
|
+
#define SEEN_LOAD_NUMERATOR 3
|
|
25
|
+
#define SEEN_LOAD_DENOMINATOR 4
|
|
26
|
+
|
|
20
27
|
// ---- Data structure -----------------------------------------------------
|
|
21
28
|
|
|
22
29
|
struct fast_cov_data {
|
|
@@ -29,8 +36,20 @@ struct fast_cov_data {
|
|
|
29
36
|
long *ignored_path_lens;
|
|
30
37
|
long ignored_paths_count;
|
|
31
38
|
|
|
39
|
+
// Two-level cache over source file identity, both keyed on the pointer
|
|
40
|
+
// rb_sourcefile() returns (stable per file, so comparing it is one
|
|
41
|
+
// integer compare instead of a string compare):
|
|
42
|
+
//
|
|
43
|
+
// last_filename_ptr - single slot, hits while execution stays in one file
|
|
44
|
+
// seen_* - open-addressed set of every file seen this session,
|
|
45
|
+
// so alternating between files stays on the fast path
|
|
32
46
|
uintptr_t last_filename_ptr;
|
|
33
47
|
|
|
48
|
+
uintptr_t *seen_ptrs;
|
|
49
|
+
VALUE *seen_paths;
|
|
50
|
+
long seen_capacity;
|
|
51
|
+
long seen_count;
|
|
52
|
+
|
|
34
53
|
bool threads;
|
|
35
54
|
bool started;
|
|
36
55
|
VALUE th_covered;
|
|
@@ -44,8 +63,18 @@ struct fast_cov_data {
|
|
|
44
63
|
|
|
45
64
|
static void fast_cov_mark(void *ptr) {
|
|
46
65
|
struct fast_cov_data *data = ptr;
|
|
66
|
+
long i;
|
|
67
|
+
|
|
47
68
|
rb_gc_mark(data->impacted_files);
|
|
48
69
|
rb_gc_mark(data->th_covered);
|
|
70
|
+
|
|
71
|
+
// Pinning the path strings is what makes the pointer cache sound: it keeps
|
|
72
|
+
// each rb_sourcefile() pointer alive and at a fixed address for the whole
|
|
73
|
+
// session. Without it a freed string's address could be reused by another
|
|
74
|
+
// file, which would read as a cache hit and silently drop that file.
|
|
75
|
+
for (i = 0; i < data->seen_capacity; i++) {
|
|
76
|
+
if (data->seen_ptrs[i]) rb_gc_mark(data->seen_paths[i]);
|
|
77
|
+
}
|
|
49
78
|
}
|
|
50
79
|
|
|
51
80
|
static void fast_cov_free(void *ptr) {
|
|
@@ -59,6 +88,8 @@ static void fast_cov_free(void *ptr) {
|
|
|
59
88
|
xfree(data->ignored_paths);
|
|
60
89
|
}
|
|
61
90
|
if (data->ignored_path_lens) xfree(data->ignored_path_lens);
|
|
91
|
+
if (data->seen_ptrs) xfree(data->seen_ptrs);
|
|
92
|
+
if (data->seen_paths) xfree(data->seen_paths);
|
|
62
93
|
xfree(data);
|
|
63
94
|
}
|
|
64
95
|
|
|
@@ -92,15 +123,111 @@ static VALUE fast_cov_allocate(VALUE klass) {
|
|
|
92
123
|
data->threads = true;
|
|
93
124
|
data->started = false;
|
|
94
125
|
|
|
126
|
+
// Keep seen_capacity at 0 until both arrays are installed: xcalloc can
|
|
127
|
+
// trigger GC, and fast_cov_mark walks seen_capacity entries.
|
|
128
|
+
data->seen_capacity = 0;
|
|
129
|
+
data->seen_count = 0;
|
|
130
|
+
data->seen_ptrs = NULL;
|
|
131
|
+
data->seen_paths = NULL;
|
|
132
|
+
|
|
133
|
+
uintptr_t *seen_ptrs = xcalloc(SEEN_INITIAL_CAPACITY, sizeof(uintptr_t));
|
|
134
|
+
VALUE *seen_paths = xcalloc(SEEN_INITIAL_CAPACITY, sizeof(VALUE));
|
|
135
|
+
data->seen_ptrs = seen_ptrs;
|
|
136
|
+
data->seen_paths = seen_paths;
|
|
137
|
+
data->seen_capacity = SEEN_INITIAL_CAPACITY;
|
|
138
|
+
|
|
95
139
|
return obj;
|
|
96
140
|
}
|
|
97
141
|
|
|
142
|
+
// ---- Seen-set -----------------------------------------------------------
|
|
143
|
+
//
|
|
144
|
+
// Open-addressed set of the rb_sourcefile() pointers seen this session, with
|
|
145
|
+
// the corresponding path string stored alongside so it can be pinned. Linear
|
|
146
|
+
// probing keeps lookups in one cache line for the common case.
|
|
147
|
+
|
|
148
|
+
// The low bits of a pointer carry little entropy (allocations are aligned),
|
|
149
|
+
// so shift them off before masking.
|
|
150
|
+
static inline long seen_slot(uintptr_t filename_ptr, long capacity) {
|
|
151
|
+
return (long)((filename_ptr >> 3) & (uintptr_t)(capacity - 1));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static inline bool seen_include(const struct fast_cov_data *data,
|
|
155
|
+
uintptr_t filename_ptr) {
|
|
156
|
+
long slot = seen_slot(filename_ptr, data->seen_capacity);
|
|
157
|
+
|
|
158
|
+
while (data->seen_ptrs[slot]) {
|
|
159
|
+
if (data->seen_ptrs[slot] == filename_ptr) return true;
|
|
160
|
+
slot = (slot + 1) & (data->seen_capacity - 1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
static void seen_grow(struct fast_cov_data *data) {
|
|
167
|
+
uintptr_t *old_ptrs = data->seen_ptrs;
|
|
168
|
+
VALUE *old_paths = data->seen_paths;
|
|
169
|
+
long old_capacity = data->seen_capacity;
|
|
170
|
+
long new_capacity = old_capacity * 2;
|
|
171
|
+
long i;
|
|
172
|
+
|
|
173
|
+
// Allocate both arrays before installing either. GC can run inside xcalloc,
|
|
174
|
+
// and fast_cov_mark must keep seeing a consistent capacity/arrays triple.
|
|
175
|
+
uintptr_t *new_ptrs = xcalloc(new_capacity, sizeof(uintptr_t));
|
|
176
|
+
VALUE *new_paths = xcalloc(new_capacity, sizeof(VALUE));
|
|
177
|
+
|
|
178
|
+
for (i = 0; i < old_capacity; i++) {
|
|
179
|
+
uintptr_t filename_ptr = old_ptrs[i];
|
|
180
|
+
if (!filename_ptr) continue;
|
|
181
|
+
|
|
182
|
+
long slot = seen_slot(filename_ptr, new_capacity);
|
|
183
|
+
while (new_ptrs[slot]) {
|
|
184
|
+
slot = (slot + 1) & (new_capacity - 1);
|
|
185
|
+
}
|
|
186
|
+
new_ptrs[slot] = filename_ptr;
|
|
187
|
+
new_paths[slot] = old_paths[i];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Install, then free: the struct must never point at freed arrays, since a
|
|
191
|
+
// GC between the two would mark through them.
|
|
192
|
+
data->seen_ptrs = new_ptrs;
|
|
193
|
+
data->seen_paths = new_paths;
|
|
194
|
+
data->seen_capacity = new_capacity;
|
|
195
|
+
|
|
196
|
+
xfree(old_ptrs);
|
|
197
|
+
xfree(old_paths);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
static void seen_add(struct fast_cov_data *data, uintptr_t filename_ptr,
|
|
201
|
+
VALUE path) {
|
|
202
|
+
if (data->seen_count + 1 >
|
|
203
|
+
data->seen_capacity * SEEN_LOAD_NUMERATOR / SEEN_LOAD_DENOMINATOR) {
|
|
204
|
+
seen_grow(data);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
long slot = seen_slot(filename_ptr, data->seen_capacity);
|
|
208
|
+
while (data->seen_ptrs[slot]) {
|
|
209
|
+
if (data->seen_ptrs[slot] == filename_ptr) return;
|
|
210
|
+
slot = (slot + 1) & (data->seen_capacity - 1);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
data->seen_ptrs[slot] = filename_ptr;
|
|
214
|
+
data->seen_paths[slot] = path;
|
|
215
|
+
data->seen_count++;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
static void seen_clear(struct fast_cov_data *data) {
|
|
219
|
+
MEMZERO(data->seen_ptrs, uintptr_t, data->seen_capacity);
|
|
220
|
+
MEMZERO(data->seen_paths, VALUE, data->seen_capacity);
|
|
221
|
+
data->seen_count = 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
98
224
|
// ---- Internal helpers ---------------------------------------------------
|
|
99
225
|
|
|
100
226
|
static bool record_impacted_file(struct fast_cov_data *data, VALUE filename) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
data->
|
|
227
|
+
// RSTRING_LEN is O(1); passing it avoids re-scanning the path with strlen.
|
|
228
|
+
if (!fast_cov_is_path_included(RSTRING_PTR(filename), RSTRING_LEN(filename),
|
|
229
|
+
data->root, data->root_len,
|
|
230
|
+
data->ignored_paths, data->ignored_path_lens,
|
|
104
231
|
data->ignored_paths_count)) {
|
|
105
232
|
return false;
|
|
106
233
|
}
|
|
@@ -127,6 +254,13 @@ static void on_line_event(rb_event_flag_t event, VALUE self_data, VALUE self,
|
|
|
127
254
|
}
|
|
128
255
|
data->last_filename_ptr = current_filename_ptr;
|
|
129
256
|
|
|
257
|
+
// Execution alternates between files constantly (a method in one file
|
|
258
|
+
// calling into another), so the single slot above misses often. Anything
|
|
259
|
+
// already seen this session is resolved here without touching the VM.
|
|
260
|
+
if (seen_include(data, current_filename_ptr)) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
130
264
|
VALUE top_frame;
|
|
131
265
|
if (rb_profile_frames(0, 1, &top_frame, NULL) != 1) {
|
|
132
266
|
return;
|
|
@@ -137,6 +271,9 @@ static void on_line_event(rb_event_flag_t event, VALUE self_data, VALUE self,
|
|
|
137
271
|
return;
|
|
138
272
|
}
|
|
139
273
|
|
|
274
|
+
// Only cache pointers we hold a path string for — the pin in fast_cov_mark
|
|
275
|
+
// is what keeps the pointer valid and unambiguous.
|
|
276
|
+
seen_add(data, current_filename_ptr, filename);
|
|
140
277
|
record_impacted_file(data, filename);
|
|
141
278
|
}
|
|
142
279
|
|
|
@@ -265,16 +402,22 @@ static VALUE fast_cov_stop(VALUE self) {
|
|
|
265
402
|
if (thval != data->th_covered) {
|
|
266
403
|
rb_raise(rb_eRuntimeError, "Coverage was not started by this thread");
|
|
267
404
|
}
|
|
268
|
-
|
|
405
|
+
// Match on the registering object, not just the callback. The plain
|
|
406
|
+
// rb_*_remove_event_hook variants match by function pointer alone, so
|
|
407
|
+
// stopping one Coverage instance would tear down the hooks belonging to
|
|
408
|
+
// every other live instance — silently, since those instances stay
|
|
409
|
+
// `started` and simply stop recording.
|
|
410
|
+
rb_thread_remove_event_hook_with_data(data->th_covered, on_line_event, self);
|
|
269
411
|
data->th_covered = Qnil;
|
|
270
412
|
} else {
|
|
271
|
-
|
|
413
|
+
rb_remove_event_hook_with_data(on_line_event, self);
|
|
272
414
|
}
|
|
273
415
|
|
|
274
416
|
VALUE res = data->impacted_files;
|
|
275
417
|
|
|
276
418
|
data->impacted_files = rb_hash_new();
|
|
277
419
|
data->last_filename_ptr = 0;
|
|
420
|
+
seen_clear(data);
|
|
278
421
|
data->started = false;
|
|
279
422
|
|
|
280
423
|
return res;
|
data/ext/fast_cov/fast_cov.h
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
bool fast_cov_is_within_root(const char *path, long path_len,
|
|
10
10
|
const char *root, long root_len);
|
|
11
11
|
|
|
12
|
-
bool fast_cov_is_path_included(const char *path,
|
|
13
|
-
|
|
14
|
-
long *ignored_path_lens,
|
|
12
|
+
bool fast_cov_is_path_included(const char *path, long path_len,
|
|
13
|
+
const char *root_path, long root_path_len,
|
|
14
|
+
char **ignored_paths, long *ignored_path_lens,
|
|
15
15
|
long ignored_paths_count);
|
|
16
16
|
|
|
17
17
|
/* ---- Utility functions -------------------------------------------------- */
|
|
@@ -18,8 +18,9 @@ bool fast_cov_is_within_root(const char *path, long path_len,
|
|
|
18
18
|
return false;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
// Check prefix match
|
|
22
|
-
|
|
21
|
+
// Check prefix match. memcmp rather than strncmp: both lengths are known,
|
|
22
|
+
// so there is no reason to also scan for a terminator.
|
|
23
|
+
if (memcmp(path, root, (size_t)effective_root_len) != 0) {
|
|
23
24
|
return false;
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -33,11 +34,10 @@ bool fast_cov_is_within_root(const char *path, long path_len,
|
|
|
33
34
|
return path[effective_root_len] == '/';
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
bool fast_cov_is_path_included(const char *path,
|
|
37
|
-
|
|
38
|
-
long *ignored_path_lens,
|
|
37
|
+
bool fast_cov_is_path_included(const char *path, long path_len,
|
|
38
|
+
const char *root_path, long root_path_len,
|
|
39
|
+
char **ignored_paths, long *ignored_path_lens,
|
|
39
40
|
long ignored_paths_count) {
|
|
40
|
-
long path_len = (long)strlen(path);
|
|
41
41
|
long i;
|
|
42
42
|
|
|
43
43
|
if (!fast_cov_is_within_root(path, path_len, root_path, root_path_len)) {
|
|
@@ -48,6 +48,20 @@ module FastCov
|
|
|
48
48
|
cov.stop
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
# Every call crosses calculator.rb -> operations/*.rb and back, so this
|
|
52
|
+
# measures the per-line-event cost of file transitions rather than the
|
|
53
|
+
# start/stop overhead that dominates the scenarios above.
|
|
54
|
+
runner.scenario("Line coverage (cross-file transitions)") do
|
|
55
|
+
cov = FastCov::Coverage.new(root: root_calculator)
|
|
56
|
+
cov.start
|
|
57
|
+
200.times do
|
|
58
|
+
calculator.add(1, 2)
|
|
59
|
+
calculator.subtract(3, 1)
|
|
60
|
+
calculator.multiply(2, 3)
|
|
61
|
+
end
|
|
62
|
+
cov.stop
|
|
63
|
+
end
|
|
64
|
+
|
|
51
65
|
runner.scenario("Rapid start/stop (100x)") do
|
|
52
66
|
cov = FastCov::Coverage.new(root: root_calculator)
|
|
53
67
|
100.times do
|
data/lib/fast_cov/compiler.rb
CHANGED
|
@@ -41,8 +41,11 @@ module FastCov
|
|
|
41
41
|
stored != source_digest
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
# Matches the ABI-version tag used by extconf.rb and lib/fast_cov.rb.
|
|
45
|
+
ABI_VERSION = RbConfig::CONFIG["ruby_version"]
|
|
46
|
+
|
|
44
47
|
def self.extension_exists?
|
|
45
|
-
Dir.glob(File.join(FAST_COV_DIR, "fast_cov.#{
|
|
48
|
+
Dir.glob(File.join(FAST_COV_DIR, "fast_cov.#{ABI_VERSION}.{bundle,so}")).any?
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
def self.source_digest
|
|
@@ -52,8 +55,8 @@ module FastCov
|
|
|
52
55
|
end
|
|
53
56
|
|
|
54
57
|
def self.digest_path
|
|
55
|
-
# Keep
|
|
56
|
-
File.join(FAST_COV_DIR, ".source_digest.#{
|
|
58
|
+
# Keep ABI-specific so we recompile when switching Ruby versions
|
|
59
|
+
File.join(FAST_COV_DIR, ".source_digest.#{ABI_VERSION}")
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
def self.write_digest
|
|
@@ -16,6 +16,7 @@ module FastCov
|
|
|
16
16
|
@connected_dependencies = ConnectedDependencies.new
|
|
17
17
|
@trackers = []
|
|
18
18
|
@native_coverage = nil
|
|
19
|
+
@native_coverage_config = nil
|
|
19
20
|
@started = false
|
|
20
21
|
end
|
|
21
22
|
|
|
@@ -69,11 +70,7 @@ module FastCov
|
|
|
69
70
|
return self if @started
|
|
70
71
|
|
|
71
72
|
begin
|
|
72
|
-
@native_coverage =
|
|
73
|
-
root: normalized_root,
|
|
74
|
-
ignored_paths: normalized_ignored_paths,
|
|
75
|
-
threads: @threads != false
|
|
76
|
-
)
|
|
73
|
+
@native_coverage = native_coverage
|
|
77
74
|
@native_coverage.start
|
|
78
75
|
@trackers.each(&:start)
|
|
79
76
|
@started = true
|
|
@@ -93,7 +90,6 @@ module FastCov
|
|
|
93
90
|
@connected_dependencies.expand(result)
|
|
94
91
|
Utils.relativize_paths(result, normalized_root)
|
|
95
92
|
ensure
|
|
96
|
-
@native_coverage = nil
|
|
97
93
|
@started = false
|
|
98
94
|
end
|
|
99
95
|
|
|
@@ -117,6 +113,20 @@ module FastCov
|
|
|
117
113
|
|
|
118
114
|
private
|
|
119
115
|
|
|
116
|
+
# Reused across cycles: a Coverage instance rebuilds its file cache from
|
|
117
|
+
# scratch on construction, so a fresh one per cycle discards that work.
|
|
118
|
+
def native_coverage
|
|
119
|
+
config = [normalized_root, normalized_ignored_paths, @threads != false]
|
|
120
|
+
return @native_coverage if @native_coverage && @native_coverage_config == config
|
|
121
|
+
|
|
122
|
+
@native_coverage_config = config
|
|
123
|
+
@native_coverage = Coverage.new(
|
|
124
|
+
root: config[0],
|
|
125
|
+
ignored_paths: config[1],
|
|
126
|
+
threads: config[2]
|
|
127
|
+
)
|
|
128
|
+
end
|
|
129
|
+
|
|
120
130
|
def normalized_root
|
|
121
131
|
path = @root&.to_s
|
|
122
132
|
raise ConfigurationError, "root is required" if path.nil? || path.empty?
|
|
@@ -145,10 +155,12 @@ module FastCov
|
|
|
145
155
|
File.absolute_path?(path)
|
|
146
156
|
end
|
|
147
157
|
|
|
158
|
+
# Against root, not Dir.pwd: #stop returns root-relative paths and callers
|
|
159
|
+
# pass them straight back into #connect.
|
|
148
160
|
def normalize_path(path)
|
|
149
161
|
return if path.nil?
|
|
150
162
|
|
|
151
|
-
File.expand_path(path.to_s)
|
|
163
|
+
File.expand_path(path.to_s, normalized_root)
|
|
152
164
|
end
|
|
153
165
|
|
|
154
166
|
def cleanup_failed_start
|
|
@@ -24,7 +24,13 @@ module FastCov
|
|
|
24
24
|
module ConstGetPatch
|
|
25
25
|
def const_get(name, inherit = true)
|
|
26
26
|
result = super
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
# The patch is permanent, so the active check has to precede the
|
|
29
|
+
# lookup — otherwise every const_get in the process pays for it.
|
|
30
|
+
if FastCov::ConstGetTracker.active
|
|
31
|
+
FastCov::ConstGetTracker.record(const_source_location(name, inherit)&.first)
|
|
32
|
+
end
|
|
33
|
+
|
|
28
34
|
result
|
|
29
35
|
end
|
|
30
36
|
end
|
|
@@ -21,19 +21,23 @@ module FastCov
|
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# The patches are permanent, so each checks active before doing any work.
|
|
25
|
+
# Paths go over raw because AbstractTracker#record normalizes them.
|
|
24
26
|
module FilePatch
|
|
25
27
|
def read(name, *args, **kwargs, &block)
|
|
26
28
|
super.tap do
|
|
27
|
-
FastCov::FileTracker.record(
|
|
29
|
+
FastCov::FileTracker.record(name) if FastCov::FileTracker.active
|
|
28
30
|
end
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def open(name, *args, **kwargs, &block)
|
|
32
|
-
mode = args[0]
|
|
33
|
-
is_read = mode.nil? || (mode.is_a?(String) && mode.start_with?("r")) ||
|
|
34
|
-
(mode.is_a?(Integer) && (mode & (File::WRONLY | File::RDWR)).zero?)
|
|
35
34
|
super.tap do
|
|
36
|
-
FastCov::FileTracker.
|
|
35
|
+
next unless FastCov::FileTracker.active
|
|
36
|
+
|
|
37
|
+
mode = args[0]
|
|
38
|
+
is_read = mode.nil? || (mode.is_a?(String) && mode.start_with?("r")) ||
|
|
39
|
+
(mode.is_a?(Integer) && (mode & (File::WRONLY | File::RDWR)).zero?)
|
|
40
|
+
FastCov::FileTracker.record(name) if is_read
|
|
37
41
|
end
|
|
38
42
|
end
|
|
39
43
|
end
|
|
@@ -41,19 +45,19 @@ module FastCov
|
|
|
41
45
|
module YamlPatch
|
|
42
46
|
def load_file(path, *args, **kwargs)
|
|
43
47
|
super.tap do
|
|
44
|
-
FastCov::FileTracker.record(
|
|
48
|
+
FastCov::FileTracker.record(path) if FastCov::FileTracker.active
|
|
45
49
|
end
|
|
46
50
|
end
|
|
47
51
|
|
|
48
52
|
def safe_load_file(path, *args, **kwargs)
|
|
49
53
|
super.tap do
|
|
50
|
-
FastCov::FileTracker.record(
|
|
54
|
+
FastCov::FileTracker.record(path) if FastCov::FileTracker.active
|
|
51
55
|
end
|
|
52
56
|
end
|
|
53
57
|
|
|
54
58
|
def unsafe_load_file(path, *args, **kwargs)
|
|
55
59
|
super.tap do
|
|
56
|
-
FastCov::FileTracker.record(
|
|
60
|
+
FastCov::FileTracker.record(path) if FastCov::FileTracker.active
|
|
57
61
|
end
|
|
58
62
|
end
|
|
59
63
|
end
|
|
@@ -41,6 +41,8 @@ module FastCov
|
|
|
41
41
|
# When a test mounts a fixture, record the fixture definition file
|
|
42
42
|
# and any parent fixture files in the chain.
|
|
43
43
|
config.on_cache_mount do |event|
|
|
44
|
+
next unless tracker.class.active
|
|
45
|
+
|
|
44
46
|
tracker.class.record(event.path)
|
|
45
47
|
parent = event.fixture.parent
|
|
46
48
|
while parent
|
data/lib/fast_cov/version.rb
CHANGED
data/lib/fast_cov.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
|
|
5
|
+
# ABI version ("3.4.0"), not RUBY_VERSION ("3.4.9"): RubyGems keys installed
|
|
6
|
+
# extensions by ABI, so it will not rebuild the gem on a patch-level upgrade.
|
|
7
|
+
require "fast_cov/fast_cov.#{RbConfig::CONFIG["ruby_version"]}"
|
|
4
8
|
|
|
5
9
|
module FastCov
|
|
6
10
|
autoload :Utils, File.expand_path("fast_cov/utils", __dir__)
|