fast_cov 0.1.6 → 0.2.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/README.md +63 -228
- data/ext/fast_cov/fast_cov.c +85 -283
- data/ext/fast_cov/fast_cov.h +3 -14
- data/ext/fast_cov/fast_cov_utils.c +11 -53
- data/lib/fast_cov/benchmark/scenarios.rb +1 -33
- data/lib/fast_cov/connected_dependencies.rb +37 -0
- data/lib/fast_cov/coverage_map.rb +175 -0
- data/lib/fast_cov/trackers/abstract_tracker.rb +26 -24
- data/lib/fast_cov/trackers/const_get_tracker.rb +5 -7
- data/lib/fast_cov/trackers/factory_bot_tracker.rb +4 -5
- data/lib/fast_cov/trackers/file_tracker.rb +11 -4
- data/lib/fast_cov/version.rb +1 -1
- data/lib/fast_cov.rb +7 -10
- metadata +3 -19
- data/lib/fast_cov/configuration.rb +0 -71
- data/lib/fast_cov/constant_extractor.rb +0 -53
- data/lib/fast_cov/singleton.rb +0 -44
- data/lib/fast_cov/trackers/coverage_tracker.rb +0 -28
data/ext/fast_cov/fast_cov.c
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#include <ruby.h>
|
|
2
2
|
#include <ruby/version.h>
|
|
3
3
|
#include <ruby/debug.h>
|
|
4
|
-
#include <ruby/st.h>
|
|
5
4
|
|
|
6
5
|
#include <stdbool.h>
|
|
7
6
|
|
|
@@ -12,28 +11,11 @@
|
|
|
12
11
|
// Tracks which source files are executed during a test run by hooking into
|
|
13
12
|
// Ruby VM events. Designed for test impact analysis.
|
|
14
13
|
|
|
15
|
-
#define MAX_CONST_RESOLUTION_ROUNDS 10
|
|
16
|
-
|
|
17
14
|
// threads: true = multi-threaded (global hook), false = single-threaded (per-thread hook)
|
|
18
15
|
|
|
19
|
-
// Constant resolution via Ruby helper (FastCov::ConstantExtractor)
|
|
20
|
-
static VALUE cConstantExtractor;
|
|
21
|
-
static ID id_extract;
|
|
22
|
-
static ID id_keys;
|
|
23
|
-
|
|
24
|
-
// Cache infrastructure
|
|
25
|
-
VALUE fast_cov_cache_hash; // process-level cache (non-static for access from utils)
|
|
26
|
-
static ID id_clear;
|
|
27
|
-
static ID id_merge_bang;
|
|
28
|
-
|
|
29
16
|
// Forward declarations
|
|
30
17
|
static VALUE fast_cov_stop(VALUE self);
|
|
31
|
-
|
|
32
|
-
static int mark_key_for_gc_i(st_data_t key, st_data_t _value,
|
|
33
|
-
st_data_t _data) {
|
|
34
|
-
rb_gc_mark((VALUE)key);
|
|
35
|
-
return ST_CONTINUE;
|
|
36
|
-
}
|
|
18
|
+
static VALUE fast_cov_yield_block(VALUE _arg);
|
|
37
19
|
|
|
38
20
|
// ---- Data structure -----------------------------------------------------
|
|
39
21
|
|
|
@@ -43,18 +25,15 @@ struct fast_cov_data {
|
|
|
43
25
|
char *root;
|
|
44
26
|
long root_len;
|
|
45
27
|
|
|
46
|
-
char
|
|
47
|
-
long
|
|
28
|
+
char **ignored_paths;
|
|
29
|
+
long *ignored_path_lens;
|
|
30
|
+
long ignored_paths_count;
|
|
48
31
|
|
|
49
32
|
uintptr_t last_filename_ptr;
|
|
50
33
|
|
|
51
34
|
bool threads;
|
|
52
|
-
bool
|
|
53
|
-
bool allocations;
|
|
35
|
+
bool started;
|
|
54
36
|
VALUE th_covered;
|
|
55
|
-
|
|
56
|
-
VALUE object_allocation_tracepoint;
|
|
57
|
-
st_table *klasses_table;
|
|
58
37
|
};
|
|
59
38
|
|
|
60
39
|
// ---- GC callbacks -------------------------------------------------------
|
|
@@ -67,18 +46,19 @@ static void fast_cov_mark(void *ptr) {
|
|
|
67
46
|
struct fast_cov_data *data = ptr;
|
|
68
47
|
rb_gc_mark(data->impacted_files);
|
|
69
48
|
rb_gc_mark(data->th_covered);
|
|
70
|
-
rb_gc_mark(data->object_allocation_tracepoint);
|
|
71
|
-
|
|
72
|
-
if (data->klasses_table != NULL) {
|
|
73
|
-
st_foreach(data->klasses_table, mark_key_for_gc_i, 0);
|
|
74
|
-
}
|
|
75
49
|
}
|
|
76
50
|
|
|
77
51
|
static void fast_cov_free(void *ptr) {
|
|
78
52
|
struct fast_cov_data *data = ptr;
|
|
53
|
+
long i;
|
|
79
54
|
if (data->root) xfree(data->root);
|
|
80
|
-
if (data->
|
|
81
|
-
|
|
55
|
+
if (data->ignored_paths) {
|
|
56
|
+
for (i = 0; i < data->ignored_paths_count; i++) {
|
|
57
|
+
xfree(data->ignored_paths[i]);
|
|
58
|
+
}
|
|
59
|
+
xfree(data->ignored_paths);
|
|
60
|
+
}
|
|
61
|
+
if (data->ignored_path_lens) xfree(data->ignored_path_lens);
|
|
82
62
|
xfree(data);
|
|
83
63
|
}
|
|
84
64
|
|
|
@@ -101,19 +81,16 @@ static VALUE fast_cov_allocate(VALUE klass) {
|
|
|
101
81
|
// Qfalse, not Qnil — and marking Qfalse can confuse Ruby 3.4's GC.
|
|
102
82
|
data->impacted_files = Qnil;
|
|
103
83
|
data->th_covered = Qnil;
|
|
104
|
-
data->object_allocation_tracepoint = Qnil;
|
|
105
|
-
data->klasses_table = NULL;
|
|
106
84
|
|
|
107
85
|
data->impacted_files = rb_hash_new();
|
|
108
86
|
data->root = NULL;
|
|
109
87
|
data->root_len = 0;
|
|
110
|
-
data->
|
|
111
|
-
data->
|
|
88
|
+
data->ignored_paths = NULL;
|
|
89
|
+
data->ignored_path_lens = NULL;
|
|
90
|
+
data->ignored_paths_count = 0;
|
|
112
91
|
data->last_filename_ptr = 0;
|
|
113
92
|
data->threads = true;
|
|
114
|
-
data->
|
|
115
|
-
data->allocations = true;
|
|
116
|
-
data->klasses_table = st_init_numtable();
|
|
93
|
+
data->started = false;
|
|
117
94
|
|
|
118
95
|
return obj;
|
|
119
96
|
}
|
|
@@ -122,8 +99,9 @@ static VALUE fast_cov_allocate(VALUE klass) {
|
|
|
122
99
|
|
|
123
100
|
static bool record_impacted_file(struct fast_cov_data *data, VALUE filename) {
|
|
124
101
|
if (!fast_cov_is_path_included(RSTRING_PTR(filename), data->root,
|
|
125
|
-
data->root_len, data->
|
|
126
|
-
data->
|
|
102
|
+
data->root_len, data->ignored_paths,
|
|
103
|
+
data->ignored_path_lens,
|
|
104
|
+
data->ignored_paths_count)) {
|
|
127
105
|
return false;
|
|
128
106
|
}
|
|
129
107
|
|
|
@@ -131,6 +109,8 @@ static bool record_impacted_file(struct fast_cov_data *data, VALUE filename) {
|
|
|
131
109
|
return true;
|
|
132
110
|
}
|
|
133
111
|
|
|
112
|
+
static VALUE fast_cov_yield_block(VALUE _arg) { return rb_yield(Qnil); }
|
|
113
|
+
|
|
134
114
|
// ---- Line event callback ------------------------------------------------
|
|
135
115
|
|
|
136
116
|
static void on_line_event(rb_event_flag_t event, VALUE self_data, VALUE self,
|
|
@@ -160,156 +140,6 @@ static void on_line_event(rb_event_flag_t event, VALUE self_data, VALUE self,
|
|
|
160
140
|
record_impacted_file(data, filename);
|
|
161
141
|
}
|
|
162
142
|
|
|
163
|
-
// ---- Allocation tracing helpers -----------------------------------------
|
|
164
|
-
|
|
165
|
-
static bool record_impacted_klass(struct fast_cov_data *data, VALUE klass) {
|
|
166
|
-
VALUE klass_name = fast_cov_rescue_nil(rb_class_name, klass);
|
|
167
|
-
if (klass_name == Qnil) {
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
VALUE filename = fast_cov_resolve_const_to_file(klass_name);
|
|
172
|
-
if (filename == Qnil) {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return record_impacted_file(data, filename);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
static int each_instantiated_klass(st_data_t key, st_data_t _value,
|
|
180
|
-
st_data_t cb_data) {
|
|
181
|
-
VALUE klass = (VALUE)key;
|
|
182
|
-
struct fast_cov_data *data = (struct fast_cov_data *)cb_data;
|
|
183
|
-
|
|
184
|
-
VALUE ancestors = fast_cov_rescue_nil(rb_mod_ancestors, klass);
|
|
185
|
-
if (ancestors == Qnil || !RB_TYPE_P(ancestors, T_ARRAY)) {
|
|
186
|
-
return ST_CONTINUE;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
long len = RARRAY_LEN(ancestors);
|
|
190
|
-
for (long i = 0; i < len; i++) {
|
|
191
|
-
VALUE mod = rb_ary_entry(ancestors, i);
|
|
192
|
-
if (mod == Qnil) {
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
record_impacted_klass(data, mod);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return ST_CONTINUE;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// ---- Newobj event callback ----------------------------------------------
|
|
202
|
-
|
|
203
|
-
static void on_newobj_event(VALUE tracepoint_data, void *raw_data) {
|
|
204
|
-
rb_trace_arg_t *tracearg = rb_tracearg_from_tracepoint(tracepoint_data);
|
|
205
|
-
VALUE new_object = rb_tracearg_object(tracearg);
|
|
206
|
-
|
|
207
|
-
enum ruby_value_type type = rb_type(new_object);
|
|
208
|
-
if (type != RUBY_T_OBJECT && type != RUBY_T_STRUCT) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
VALUE klass = rb_class_of(new_object);
|
|
213
|
-
#if RUBY_API_VERSION_MAJOR < 4
|
|
214
|
-
// Ruby 3.x: rb_class_of can return 0 during NEWOBJ for some allocations
|
|
215
|
-
if (klass == Qnil || klass == 0) {
|
|
216
|
-
#else
|
|
217
|
-
if (klass == Qnil) {
|
|
218
|
-
#endif
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
if (rb_mod_name(klass) == Qnil) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
struct fast_cov_data *data = (struct fast_cov_data *)raw_data;
|
|
226
|
-
st_insert(data->klasses_table, (st_data_t)klass, 1);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// ---- Constant reference resolution (cached) -----------------------------
|
|
230
|
-
|
|
231
|
-
// Parse file with Prism and extract constant names.
|
|
232
|
-
static VALUE extract_const_names_body(VALUE filename) {
|
|
233
|
-
return rb_funcall(cConstantExtractor, id_extract, 1, filename);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Returns an array of constant name strings for a file, using the cache.
|
|
237
|
-
// Cache is keyed by filename only - once parsed, results are cached for
|
|
238
|
-
// the lifetime of the process. No digest/mtime validation needed since
|
|
239
|
-
// files don't change during a test suite run.
|
|
240
|
-
static VALUE get_const_refs_for_file(VALUE filename) {
|
|
241
|
-
VALUE const_refs_hash =
|
|
242
|
-
rb_hash_lookup(fast_cov_cache_hash, ID2SYM(rb_intern("const_refs")));
|
|
243
|
-
|
|
244
|
-
// Cache hit: return cached refs
|
|
245
|
-
VALUE cached = rb_hash_lookup(const_refs_hash, filename);
|
|
246
|
-
if (cached != Qnil) {
|
|
247
|
-
return cached;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Cache miss: parse with Prism and extract constant names
|
|
251
|
-
int exception_state;
|
|
252
|
-
VALUE const_names =
|
|
253
|
-
rb_protect(extract_const_names_body, filename, &exception_state);
|
|
254
|
-
if (exception_state != 0) {
|
|
255
|
-
rb_set_errinfo(Qnil);
|
|
256
|
-
return Qnil;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Store in cache (filename -> refs)
|
|
260
|
-
rb_hash_aset(const_refs_hash, filename, const_names);
|
|
261
|
-
|
|
262
|
-
return const_names;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
static void resolve_constant_references(struct fast_cov_data *data) {
|
|
266
|
-
VALUE seen_consts = rb_hash_new();
|
|
267
|
-
VALUE processed_files = rb_hash_new();
|
|
268
|
-
|
|
269
|
-
for (int round = 0; round < MAX_CONST_RESOLUTION_ROUNDS; round++) {
|
|
270
|
-
VALUE keys = rb_funcall(data->impacted_files, id_keys, 0);
|
|
271
|
-
long num_keys = RARRAY_LEN(keys);
|
|
272
|
-
int found_new_file = 0;
|
|
273
|
-
|
|
274
|
-
for (long i = 0; i < num_keys; i++) {
|
|
275
|
-
VALUE filename = rb_ary_entry(keys, i);
|
|
276
|
-
|
|
277
|
-
if (rb_hash_lookup(processed_files, filename) != Qnil) {
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
rb_hash_aset(processed_files, filename, Qtrue);
|
|
281
|
-
|
|
282
|
-
VALUE const_names = get_const_refs_for_file(filename);
|
|
283
|
-
if (NIL_P(const_names) || !RB_TYPE_P(const_names, T_ARRAY)) {
|
|
284
|
-
continue;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
long num_refs = RARRAY_LEN(const_names);
|
|
288
|
-
for (long j = 0; j < num_refs; j++) {
|
|
289
|
-
VALUE const_name = rb_ary_entry(const_names, j);
|
|
290
|
-
|
|
291
|
-
if (rb_hash_lookup(seen_consts, const_name) != Qnil) {
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
rb_hash_aset(seen_consts, const_name, Qtrue);
|
|
295
|
-
|
|
296
|
-
VALUE resolved_file = fast_cov_resolve_const_to_file(const_name);
|
|
297
|
-
if (NIL_P(resolved_file)) {
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (record_impacted_file(data, resolved_file)) {
|
|
302
|
-
found_new_file = 1;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (!found_new_file) {
|
|
308
|
-
break;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
143
|
// ---- Utils module methods (FastCov::Utils) ------------------------------
|
|
314
144
|
|
|
315
145
|
// Utils.path_within?(path, directory) -> true/false
|
|
@@ -384,28 +214,6 @@ static VALUE utils_relativize_paths(VALUE self, VALUE set, VALUE root) {
|
|
|
384
214
|
return set;
|
|
385
215
|
}
|
|
386
216
|
|
|
387
|
-
// ---- Cache module methods (FastCov::Cache) ------------------------------
|
|
388
|
-
|
|
389
|
-
static VALUE cache_get_data(VALUE self) { return fast_cov_cache_hash; }
|
|
390
|
-
|
|
391
|
-
static VALUE cache_set_data(VALUE self, VALUE new_cache) {
|
|
392
|
-
if (!RB_TYPE_P(new_cache, T_HASH)) {
|
|
393
|
-
rb_raise(rb_eTypeError, "cache data must be a Hash");
|
|
394
|
-
}
|
|
395
|
-
rb_funcall(fast_cov_cache_hash, id_clear, 0);
|
|
396
|
-
rb_funcall(fast_cov_cache_hash, id_merge_bang, 1, new_cache);
|
|
397
|
-
return fast_cov_cache_hash;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
static VALUE cache_clear(VALUE self) {
|
|
401
|
-
rb_funcall(fast_cov_cache_hash, id_clear, 0);
|
|
402
|
-
rb_hash_aset(fast_cov_cache_hash, ID2SYM(rb_intern("const_refs")),
|
|
403
|
-
rb_hash_new());
|
|
404
|
-
rb_hash_aset(fast_cov_cache_hash, ID2SYM(rb_intern("const_locations")),
|
|
405
|
-
rb_hash_new());
|
|
406
|
-
return Qnil;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
217
|
// ---- Ruby instance methods ----------------------------------------------
|
|
410
218
|
|
|
411
219
|
static VALUE fast_cov_initialize(int argc, VALUE *argv, VALUE self) {
|
|
@@ -418,44 +226,63 @@ static VALUE fast_cov_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
418
226
|
if (!RTEST(rb_root)) {
|
|
419
227
|
rb_root = rb_funcall(rb_cDir, rb_intern("pwd"), 0);
|
|
420
228
|
}
|
|
229
|
+
Check_Type(rb_root, T_STRING);
|
|
421
230
|
|
|
422
|
-
//
|
|
423
|
-
VALUE
|
|
424
|
-
rb_hash_lookup(opt, ID2SYM(rb_intern("
|
|
231
|
+
// ignored_paths: optional array, [] if not provided
|
|
232
|
+
VALUE rb_ignored_paths =
|
|
233
|
+
rb_hash_lookup(opt, ID2SYM(rb_intern("ignored_paths")));
|
|
234
|
+
if (!NIL_P(rb_ignored_paths)) {
|
|
235
|
+
Check_Type(rb_ignored_paths, T_ARRAY);
|
|
236
|
+
}
|
|
425
237
|
|
|
426
238
|
// threads: true (multi) or false (single), defaults to true
|
|
427
239
|
VALUE rb_threads = rb_hash_lookup(opt, ID2SYM(rb_intern("threads")));
|
|
428
240
|
bool threads = (rb_threads != Qfalse);
|
|
429
241
|
|
|
430
|
-
// constant_references: defaults to true
|
|
431
|
-
VALUE rb_const_refs =
|
|
432
|
-
rb_hash_lookup(opt, ID2SYM(rb_intern("constant_references")));
|
|
433
|
-
bool constant_references = (rb_const_refs != Qfalse);
|
|
434
|
-
|
|
435
|
-
// allocations: defaults to true
|
|
436
|
-
VALUE rb_allocations =
|
|
437
|
-
rb_hash_lookup(opt, ID2SYM(rb_intern("allocations")));
|
|
438
|
-
bool allocations = (rb_allocations != Qfalse);
|
|
439
|
-
|
|
440
242
|
struct fast_cov_data *data;
|
|
441
243
|
TypedData_Get_Struct(self, struct fast_cov_data, &fast_cov_data_type, data);
|
|
442
244
|
|
|
245
|
+
char *root = fast_cov_ruby_strndup(RSTRING_PTR(rb_root), RSTRING_LEN(rb_root));
|
|
246
|
+
|
|
443
247
|
data->threads = threads;
|
|
444
|
-
data->
|
|
445
|
-
data->
|
|
248
|
+
if (data->root) xfree(data->root);
|
|
249
|
+
data->root = root;
|
|
446
250
|
data->root_len = RSTRING_LEN(rb_root);
|
|
447
|
-
data->root =
|
|
448
|
-
fast_cov_ruby_strndup(RSTRING_PTR(rb_root), data->root_len);
|
|
449
251
|
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
data->
|
|
453
|
-
|
|
252
|
+
if (data->ignored_paths) {
|
|
253
|
+
long i;
|
|
254
|
+
for (i = 0; i < data->ignored_paths_count; i++) {
|
|
255
|
+
xfree(data->ignored_paths[i]);
|
|
256
|
+
}
|
|
257
|
+
xfree(data->ignored_paths);
|
|
258
|
+
data->ignored_paths = NULL;
|
|
259
|
+
}
|
|
260
|
+
if (data->ignored_path_lens) {
|
|
261
|
+
xfree(data->ignored_path_lens);
|
|
262
|
+
data->ignored_path_lens = NULL;
|
|
454
263
|
}
|
|
264
|
+
data->ignored_paths_count = 0;
|
|
265
|
+
|
|
266
|
+
if (!NIL_P(rb_ignored_paths) && RARRAY_LEN(rb_ignored_paths) > 0) {
|
|
267
|
+
long i;
|
|
268
|
+
long ignored_paths_count = RARRAY_LEN(rb_ignored_paths);
|
|
455
269
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
270
|
+
for (i = 0; i < ignored_paths_count; i++) {
|
|
271
|
+
Check_Type(rb_ary_entry(rb_ignored_paths, i), T_STRING);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
data->ignored_paths_count = ignored_paths_count;
|
|
275
|
+
data->ignored_paths = xcalloc(data->ignored_paths_count, sizeof(char *));
|
|
276
|
+
data->ignored_path_lens = xcalloc(data->ignored_paths_count, sizeof(long));
|
|
277
|
+
|
|
278
|
+
for (i = 0; i < data->ignored_paths_count; i++) {
|
|
279
|
+
VALUE rb_ignored_path = rb_ary_entry(rb_ignored_paths, i);
|
|
280
|
+
|
|
281
|
+
data->ignored_path_lens[i] = RSTRING_LEN(rb_ignored_path);
|
|
282
|
+
data->ignored_paths[i] =
|
|
283
|
+
fast_cov_ruby_strndup(RSTRING_PTR(rb_ignored_path),
|
|
284
|
+
data->ignored_path_lens[i]);
|
|
285
|
+
}
|
|
459
286
|
}
|
|
460
287
|
|
|
461
288
|
return Qnil;
|
|
@@ -469,6 +296,13 @@ static VALUE fast_cov_start(VALUE self) {
|
|
|
469
296
|
rb_raise(rb_eRuntimeError, "root is required");
|
|
470
297
|
}
|
|
471
298
|
|
|
299
|
+
if (data->started) {
|
|
300
|
+
if (rb_block_given_p()) {
|
|
301
|
+
rb_raise(rb_eRuntimeError, "Coverage is already started");
|
|
302
|
+
}
|
|
303
|
+
return self;
|
|
304
|
+
}
|
|
305
|
+
|
|
472
306
|
if (!data->threads) {
|
|
473
307
|
VALUE thval = rb_thread_current();
|
|
474
308
|
rb_thread_add_event_hook(thval, on_line_event, RUBY_EVENT_LINE, self);
|
|
@@ -476,15 +310,17 @@ static VALUE fast_cov_start(VALUE self) {
|
|
|
476
310
|
} else {
|
|
477
311
|
rb_add_event_hook(on_line_event, RUBY_EVENT_LINE, self);
|
|
478
312
|
}
|
|
479
|
-
|
|
480
|
-
if (data->object_allocation_tracepoint != Qnil) {
|
|
481
|
-
rb_tracepoint_enable(data->object_allocation_tracepoint);
|
|
482
|
-
}
|
|
313
|
+
data->started = true;
|
|
483
314
|
|
|
484
315
|
// Block form: start { ... } runs the block then returns stop result
|
|
485
316
|
if (rb_block_given_p()) {
|
|
486
|
-
|
|
487
|
-
|
|
317
|
+
int exception_state = 0;
|
|
318
|
+
rb_protect(fast_cov_yield_block, Qnil, &exception_state);
|
|
319
|
+
VALUE result = fast_cov_stop(self);
|
|
320
|
+
if (exception_state != 0) {
|
|
321
|
+
rb_jump_tag(exception_state);
|
|
322
|
+
}
|
|
323
|
+
return result;
|
|
488
324
|
}
|
|
489
325
|
|
|
490
326
|
return self;
|
|
@@ -494,6 +330,10 @@ static VALUE fast_cov_stop(VALUE self) {
|
|
|
494
330
|
struct fast_cov_data *data;
|
|
495
331
|
TypedData_Get_Struct(self, struct fast_cov_data, &fast_cov_data_type, data);
|
|
496
332
|
|
|
333
|
+
if (!data->started) {
|
|
334
|
+
return rb_hash_new();
|
|
335
|
+
}
|
|
336
|
+
|
|
497
337
|
if (!data->threads) {
|
|
498
338
|
VALUE thval = rb_thread_current();
|
|
499
339
|
if (thval != data->th_covered) {
|
|
@@ -505,23 +345,11 @@ static VALUE fast_cov_stop(VALUE self) {
|
|
|
505
345
|
rb_remove_event_hook(on_line_event);
|
|
506
346
|
}
|
|
507
347
|
|
|
508
|
-
if (data->object_allocation_tracepoint != Qnil) {
|
|
509
|
-
rb_tracepoint_disable(data->object_allocation_tracepoint);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
if (data->allocations) {
|
|
513
|
-
st_foreach(data->klasses_table, each_instantiated_klass, (st_data_t)data);
|
|
514
|
-
st_clear(data->klasses_table);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (data->constant_references) {
|
|
518
|
-
resolve_constant_references(data);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
348
|
VALUE res = data->impacted_files;
|
|
522
349
|
|
|
523
350
|
data->impacted_files = rb_hash_new();
|
|
524
351
|
data->last_filename_ptr = 0;
|
|
352
|
+
data->started = false;
|
|
525
353
|
|
|
526
354
|
return res;
|
|
527
355
|
}
|
|
@@ -529,28 +357,8 @@ static VALUE fast_cov_stop(VALUE self) {
|
|
|
529
357
|
// ---- Init ---------------------------------------------------------------
|
|
530
358
|
|
|
531
359
|
void Init_fast_cov(void) {
|
|
532
|
-
id_extract = rb_intern("extract");
|
|
533
|
-
id_keys = rb_intern("keys");
|
|
534
|
-
id_clear = rb_intern("clear");
|
|
535
|
-
id_merge_bang = rb_intern("merge!");
|
|
536
|
-
|
|
537
|
-
rb_require("fast_cov/constant_extractor");
|
|
538
|
-
|
|
539
|
-
// Initialize process-level cache
|
|
540
|
-
fast_cov_cache_hash = rb_hash_new();
|
|
541
|
-
rb_gc_register_address(&fast_cov_cache_hash);
|
|
542
|
-
rb_hash_aset(fast_cov_cache_hash, ID2SYM(rb_intern("const_refs")),
|
|
543
|
-
rb_hash_new());
|
|
544
|
-
rb_hash_aset(fast_cov_cache_hash, ID2SYM(rb_intern("const_locations")),
|
|
545
|
-
rb_hash_new());
|
|
546
|
-
|
|
547
360
|
VALUE mFastCov = rb_define_module("FastCov");
|
|
548
361
|
|
|
549
|
-
// FastCov::ConstantExtractor must be loaded before the C extension
|
|
550
|
-
cConstantExtractor =
|
|
551
|
-
rb_const_get(mFastCov, rb_intern("ConstantExtractor"));
|
|
552
|
-
rb_gc_register_address(&cConstantExtractor);
|
|
553
|
-
|
|
554
362
|
VALUE cCoverage = rb_define_class_under(mFastCov, "Coverage", rb_cObject);
|
|
555
363
|
|
|
556
364
|
rb_define_alloc_func(cCoverage, fast_cov_allocate);
|
|
@@ -558,12 +366,6 @@ void Init_fast_cov(void) {
|
|
|
558
366
|
rb_define_method(cCoverage, "start", fast_cov_start, 0);
|
|
559
367
|
rb_define_method(cCoverage, "stop", fast_cov_stop, 0);
|
|
560
368
|
|
|
561
|
-
// FastCov::Cache module (C-defined methods)
|
|
562
|
-
VALUE mCache = rb_define_module_under(mFastCov, "Cache");
|
|
563
|
-
rb_define_module_function(mCache, "data", cache_get_data, 0);
|
|
564
|
-
rb_define_module_function(mCache, "data=", cache_set_data, 1);
|
|
565
|
-
rb_define_module_function(mCache, "clear", cache_clear, 0);
|
|
566
|
-
|
|
567
369
|
// FastCov::Utils module (C-defined methods)
|
|
568
370
|
VALUE mUtils = rb_define_module_under(mFastCov, "Utils");
|
|
569
371
|
rb_define_module_function(mUtils, "path_within?", utils_path_within, 2);
|
data/ext/fast_cov/fast_cov.h
CHANGED
|
@@ -4,29 +4,18 @@
|
|
|
4
4
|
#include <ruby.h>
|
|
5
5
|
#include <stdbool.h>
|
|
6
6
|
|
|
7
|
-
/* ---- Cache -------------------------------------------------------------- */
|
|
8
|
-
|
|
9
|
-
extern VALUE fast_cov_cache_hash;
|
|
10
|
-
|
|
11
7
|
/* ---- Path filtering ----------------------------------------------------- */
|
|
12
8
|
|
|
13
9
|
bool fast_cov_is_within_root(const char *path, long path_len,
|
|
14
10
|
const char *root, long root_len);
|
|
15
11
|
|
|
16
12
|
bool fast_cov_is_path_included(const char *path, const char *root_path,
|
|
17
|
-
long root_path_len,
|
|
18
|
-
long
|
|
13
|
+
long root_path_len, char **ignored_paths,
|
|
14
|
+
long *ignored_path_lens,
|
|
15
|
+
long ignored_paths_count);
|
|
19
16
|
|
|
20
17
|
/* ---- Utility functions -------------------------------------------------- */
|
|
21
18
|
|
|
22
19
|
char *fast_cov_ruby_strndup(const char *str, size_t size);
|
|
23
20
|
|
|
24
|
-
VALUE fast_cov_rescue_nil(VALUE (*fn)(VALUE), VALUE arg);
|
|
25
|
-
|
|
26
|
-
VALUE fast_cov_get_const_source_location(VALUE const_name_str);
|
|
27
|
-
|
|
28
|
-
VALUE fast_cov_safely_get_const_source_location(VALUE const_name_str);
|
|
29
|
-
|
|
30
|
-
VALUE fast_cov_resolve_const_to_file(VALUE const_name_str);
|
|
31
|
-
|
|
32
21
|
#endif /* FAST_COV_H */
|
|
@@ -34,17 +34,23 @@ bool fast_cov_is_within_root(const char *path, long path_len,
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
bool fast_cov_is_path_included(const char *path, const char *root_path,
|
|
37
|
-
long root_path_len,
|
|
38
|
-
long
|
|
37
|
+
long root_path_len, char **ignored_paths,
|
|
38
|
+
long *ignored_path_lens,
|
|
39
|
+
long ignored_paths_count) {
|
|
39
40
|
long path_len = (long)strlen(path);
|
|
41
|
+
long i;
|
|
40
42
|
|
|
41
43
|
if (!fast_cov_is_within_root(path, path_len, root_path, root_path_len)) {
|
|
42
44
|
return false;
|
|
43
45
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
for (i = 0; i < ignored_paths_count; i++) {
|
|
48
|
+
if (fast_cov_is_within_root(path, path_len, ignored_paths[i],
|
|
49
|
+
ignored_path_lens[i])) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
47
52
|
}
|
|
53
|
+
|
|
48
54
|
return true;
|
|
49
55
|
}
|
|
50
56
|
|
|
@@ -54,51 +60,3 @@ char *fast_cov_ruby_strndup(const char *str, size_t size) {
|
|
|
54
60
|
dup[size] = '\0';
|
|
55
61
|
return dup;
|
|
56
62
|
}
|
|
57
|
-
|
|
58
|
-
VALUE fast_cov_rescue_nil(VALUE (*fn)(VALUE), VALUE arg) {
|
|
59
|
-
int exception_state;
|
|
60
|
-
VALUE result = rb_protect(fn, arg, &exception_state);
|
|
61
|
-
if (exception_state != 0) {
|
|
62
|
-
rb_set_errinfo(Qnil);
|
|
63
|
-
return Qnil;
|
|
64
|
-
}
|
|
65
|
-
return result;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
VALUE fast_cov_get_const_source_location(VALUE const_name_str) {
|
|
69
|
-
return rb_funcall(rb_cObject, rb_intern("const_source_location"), 1,
|
|
70
|
-
const_name_str);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
VALUE fast_cov_safely_get_const_source_location(VALUE const_name_str) {
|
|
74
|
-
return fast_cov_rescue_nil(fast_cov_get_const_source_location,
|
|
75
|
-
const_name_str);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
VALUE fast_cov_resolve_const_to_file(VALUE const_name_str) {
|
|
79
|
-
// Check cache first
|
|
80
|
-
VALUE const_locations_hash =
|
|
81
|
-
rb_hash_lookup(fast_cov_cache_hash, ID2SYM(rb_intern("const_locations")));
|
|
82
|
-
VALUE cached = rb_hash_lookup(const_locations_hash, const_name_str);
|
|
83
|
-
if (cached != Qnil) {
|
|
84
|
-
return cached;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Cache miss - resolve via Object.const_source_location
|
|
88
|
-
VALUE source_location =
|
|
89
|
-
fast_cov_safely_get_const_source_location(const_name_str);
|
|
90
|
-
if (NIL_P(source_location) || !RB_TYPE_P(source_location, T_ARRAY) ||
|
|
91
|
-
RARRAY_LEN(source_location) == 0) {
|
|
92
|
-
return Qnil;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
VALUE filename = RARRAY_AREF(source_location, 0);
|
|
96
|
-
if (NIL_P(filename) || !RB_TYPE_P(filename, T_STRING)) {
|
|
97
|
-
return Qnil;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Cache the result
|
|
101
|
-
rb_hash_aset(const_locations_hash, const_name_str, filename);
|
|
102
|
-
|
|
103
|
-
return filename;
|
|
104
|
-
}
|
|
@@ -5,7 +5,6 @@ module FastCov
|
|
|
5
5
|
module Scenarios
|
|
6
6
|
def self.register(runner, fixtures_dir:)
|
|
7
7
|
root_calculator = File.join(fixtures_dir, "calculator")
|
|
8
|
-
root_app = File.join(fixtures_dir, "app")
|
|
9
8
|
root_all = fixtures_dir
|
|
10
9
|
ignored_path = File.join(fixtures_dir, "vendor")
|
|
11
10
|
|
|
@@ -42,44 +41,13 @@ module FastCov
|
|
|
42
41
|
end
|
|
43
42
|
|
|
44
43
|
runner.scenario("Line coverage (with ignored_path)") do
|
|
45
|
-
cov = FastCov::Coverage.new(root: root_all,
|
|
44
|
+
cov = FastCov::Coverage.new(root: root_all, ignored_paths: [ignored_path])
|
|
46
45
|
cov.start
|
|
47
46
|
calculator.add(1, 2)
|
|
48
47
|
calculator.subtract(3, 1)
|
|
49
48
|
cov.stop
|
|
50
49
|
end
|
|
51
50
|
|
|
52
|
-
runner.scenario("Allocation tracing") do
|
|
53
|
-
cov = FastCov::Coverage.new(root: root_app, allocations: true)
|
|
54
|
-
cov.start
|
|
55
|
-
MyModel.new
|
|
56
|
-
User.new("test", "test@test.com")
|
|
57
|
-
DynamicModel.new
|
|
58
|
-
cov.stop
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
runner.scenario("Constant resolution (cold cache)") do
|
|
62
|
-
FastCov::Cache.clear
|
|
63
|
-
cov = FastCov::Coverage.new(root: root_calculator)
|
|
64
|
-
cov.start
|
|
65
|
-
ConstantReader.new.operations
|
|
66
|
-
calculator.add(1, 2)
|
|
67
|
-
cov.stop
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
runner.scenario("Constant resolution (warm cache)") do
|
|
71
|
-
warm = FastCov::Coverage.new(root: root_calculator)
|
|
72
|
-
warm.start
|
|
73
|
-
ConstantReader.new.operations
|
|
74
|
-
warm.stop
|
|
75
|
-
|
|
76
|
-
cov = FastCov::Coverage.new(root: root_calculator)
|
|
77
|
-
cov.start
|
|
78
|
-
ConstantReader.new.operations
|
|
79
|
-
calculator.add(1, 2)
|
|
80
|
-
cov.stop
|
|
81
|
-
end
|
|
82
|
-
|
|
83
51
|
runner.scenario("Rapid start/stop (100x)") do
|
|
84
52
|
cov = FastCov::Coverage.new(root: root_calculator)
|
|
85
53
|
100.times do
|