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.
@@ -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 *ignored_path;
47
- long ignored_path_len;
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 constant_references;
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->ignored_path) xfree(data->ignored_path);
81
- if (data->klasses_table) st_free_table(data->klasses_table);
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->ignored_path = NULL;
111
- data->ignored_path_len = 0;
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->constant_references = true;
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->ignored_path,
126
- data->ignored_path_len)) {
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
- // ignored_path: optional, nil if not provided
423
- VALUE rb_ignored_path =
424
- rb_hash_lookup(opt, ID2SYM(rb_intern("ignored_path")));
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->constant_references = constant_references;
445
- data->allocations = allocations;
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 (RTEST(rb_ignored_path)) {
451
- data->ignored_path_len = RSTRING_LEN(rb_ignored_path);
452
- data->ignored_path = fast_cov_ruby_strndup(RSTRING_PTR(rb_ignored_path),
453
- data->ignored_path_len);
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
- if (allocations) {
457
- data->object_allocation_tracepoint = rb_tracepoint_new(
458
- Qnil, RUBY_INTERNAL_EVENT_NEWOBJ, on_newobj_event, (void *)data);
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
- rb_yield(Qnil);
487
- return fast_cov_stop(self);
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);
@@ -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, const char *ignored_path,
18
- long ignored_path_len);
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, const char *ignored_path,
38
- long ignored_path_len) {
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
- if (ignored_path_len > 0 &&
45
- fast_cov_is_within_root(path, path_len, ignored_path, ignored_path_len)) {
46
- return false;
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, ignored_path: ignored_path)
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