datadog-ci 1.0.1 → 1.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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -2
  3. data/ext/datadog_cov/datadog_cov.c +259 -67
  4. data/lib/datadog/ci/configuration/components.rb +121 -79
  5. data/lib/datadog/ci/configuration/settings.rb +6 -0
  6. data/lib/datadog/ci/contrib/rspec/example.rb +1 -1
  7. data/lib/datadog/ci/contrib/rspec/patcher.rb +3 -3
  8. data/lib/datadog/ci/ext/settings.rb +1 -0
  9. data/lib/datadog/ci/span.rb +3 -3
  10. data/lib/datadog/ci/test.rb +1 -1
  11. data/lib/datadog/ci/test_module.rb +1 -1
  12. data/lib/datadog/ci/{itr/runner.rb → test_optimisation/component.rb} +13 -10
  13. data/lib/datadog/ci/{itr → test_optimisation}/coverage/ddcov.rb +1 -1
  14. data/lib/datadog/ci/{itr → test_optimisation}/coverage/event.rb +1 -1
  15. data/lib/datadog/ci/{itr → test_optimisation}/coverage/transport.rb +1 -1
  16. data/lib/datadog/ci/{itr → test_optimisation}/coverage/writer.rb +1 -1
  17. data/lib/datadog/ci/{itr → test_optimisation}/skippable.rb +1 -1
  18. data/lib/datadog/ci/test_session.rb +1 -1
  19. data/lib/datadog/ci/test_suite.rb +1 -1
  20. data/lib/datadog/ci/test_visibility/{recorder.rb → component.rb} +10 -10
  21. data/lib/datadog/ci/test_visibility/{null_recorder.rb → null_component.rb} +6 -4
  22. data/lib/datadog/ci/test_visibility/transport.rb +1 -1
  23. data/lib/datadog/ci/transport/adapters/net.rb +138 -0
  24. data/lib/datadog/ci/transport/api/agentless.rb +2 -2
  25. data/lib/datadog/ci/transport/api/evp_proxy.rb +1 -1
  26. data/lib/datadog/ci/transport/http.rb +7 -57
  27. data/lib/datadog/ci/version.rb +2 -2
  28. data/lib/datadog/ci.rb +15 -15
  29. metadata +12 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73bd9b05aba53e1197420cc0e4a529339b6526e13775467fa326b4949e44043a
4
- data.tar.gz: 7613c476a2a3f2cf28d7dd99dd53da2fa6c921c9a03de18a6997f25e3fe0f27a
3
+ metadata.gz: ba0975da7283a037f3ea9c70954f2868cf387a0e86608882d2ebefdd097e0cf7
4
+ data.tar.gz: 7842e2e30802411616d987bcbae9be620befa27ba90206a733646a469225f835
5
5
  SHA512:
6
- metadata.gz: fc66a07d4ccceb71f18b26d290e387b6b403d7efbb46169ced3db9ad0e67e2f29ec163bdaa43a7f7508c6511ca0c68d99257d32d3a25e62b42e00219d7ef44a1
7
- data.tar.gz: 6fb71c67ecea77fc61238fa7f739192a7670f51d8f4c052d436d2f4d69743f1202f27e27cf84fa76ce74860145d13badb00862c1c03b37d93dc9a33e597afb3d
6
+ metadata.gz: 89bce02785793b2c1f5b5438f6c9cfbf63d3ff4cb065788b4884063341bdc23c289e29715f846ce24e5a4ab939c9cdeb05e755a216e92cee6f20b9954462b05f
7
+ data.tar.gz: 6adac19cff9d74fc081d22654a8b0db93152c8c5b6706fee82263e6f219edd96bb473f9595c85418a904110bfcde430b7ddcabd034279cb45d24374a55c5b170
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.2.0] - 2024-07-16
4
+
5
+ ### Changed
6
+ * Expand test impact analysis with allocation tracing ([#197][])
7
+
8
+ ## [1.1.0] - 2024-07-01
9
+
10
+ ### Added
11
+ * Ignore Webmock automatically when making HTTP calls ([#193][])
12
+
3
13
  ## [1.0.1] - 2024-06-11
4
14
 
5
15
  ### Fixed
@@ -267,7 +277,9 @@ Currently test suite level visibility is not used by our instrumentation: it wil
267
277
 
268
278
  - Ruby versions < 2.7 no longer supported ([#8][])
269
279
 
270
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.1...main
280
+ [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.2.0...main
281
+ [1.2.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.1.0...v1.2.0
282
+ [1.1.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.1...v1.1.0
271
283
  [1.0.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0...v1.0.1
272
284
  [1.0.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0.beta6...v1.0.0
273
285
  [1.0.0.beta6]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0.beta5...v1.0.0.beta6
@@ -377,4 +389,6 @@ Currently test suite level visibility is not used by our instrumentation: it wil
377
389
  [#183]: https://github.com/DataDog/datadog-ci-rb/issues/183
378
390
  [#185]: https://github.com/DataDog/datadog-ci-rb/issues/185
379
391
  [#189]: https://github.com/DataDog/datadog-ci-rb/issues/189
380
- [#190]: https://github.com/DataDog/datadog-ci-rb/issues/190
392
+ [#190]: https://github.com/DataDog/datadog-ci-rb/issues/190
393
+ [#193]: https://github.com/DataDog/datadog-ci-rb/issues/193
394
+ [#197]: https://github.com/DataDog/datadog-ci-rb/issues/197
@@ -1,13 +1,26 @@
1
1
  #include <ruby.h>
2
2
  #include <ruby/debug.h>
3
+ #include <ruby/st.h>
4
+
5
+ #include <stdbool.h>
6
+
7
+ // This is a native extension that collects a list of Ruby files that were executed during the test run.
8
+ // It is used to optimize the test suite by running only the tests that are affected by the changes.
3
9
 
4
10
  #define PROFILE_FRAMES_BUFFER_SIZE 1
5
11
 
6
12
  // threading modes
7
- #define SINGLE_THREADED_COVERAGE_MODE 0
8
- #define MULTI_THREADED_COVERAGE_MODE 1
13
+ enum threading_mode
14
+ {
15
+ single,
16
+ multi
17
+ };
18
+
19
+ // functions declarations
20
+ static void on_newobj_event(VALUE tracepoint_data, void *data);
9
21
 
10
- char *ruby_strndup(const char *str, size_t size)
22
+ // utility functions
23
+ static char *ruby_strndup(const char *str, size_t size)
11
24
  {
12
25
  char *dup;
13
26
 
@@ -18,30 +31,84 @@ char *ruby_strndup(const char *str, size_t size)
18
31
  return dup;
19
32
  }
20
33
 
34
+ // Equivalent to Ruby "begin/rescue nil" call, where we call a C function and
35
+ // swallow the exception if it occurs - const_source_location often fails with
36
+ // exceptions for classes that are defined in C or for anonymous classes.
37
+ static VALUE rescue_nil(VALUE (*function_to_call_safely)(VALUE), VALUE function_to_call_safely_arg)
38
+ {
39
+ int exception_state;
40
+ // rb_protect sets exception_state to non-zero if an exception occurs
41
+ // see https://github.com/ruby/ruby/blob/3219ecf4f659908674f534491d8934ba54e1143d/include/ruby/internal/intern/proc.h#L349
42
+ VALUE result = rb_protect(function_to_call_safely, function_to_call_safely_arg, &exception_state);
43
+ if (exception_state != 0)
44
+ {
45
+ return Qnil;
46
+ }
47
+
48
+ return result;
49
+ }
50
+
51
+ static int mark_key_for_gc_i(st_data_t key, st_data_t _value, st_data_t _data)
52
+ {
53
+ VALUE klass = (VALUE)key;
54
+ // mark klass link for GC as non-movable to avoid changing hashtable's keys
55
+ rb_gc_mark(klass);
56
+ return ST_CONTINUE;
57
+ }
58
+
21
59
  // Data structure
22
60
  struct dd_cov_data
23
61
  {
62
+ // Ruby hash with filenames impacted by the test.
63
+ VALUE impacted_files;
64
+
65
+ // Root is the path to the root folder of the project under test.
66
+ // Files located outside of the root are ignored.
24
67
  char *root;
25
68
  long root_len;
26
69
 
70
+ // Ignored path contains path to the folder where bundled gems are located if
71
+ // gems are installed in the project folder.
27
72
  char *ignored_path;
28
73
  long ignored_path_len;
29
74
 
30
- VALUE coverage;
31
-
75
+ // Line tracepoint optimisation: cache last seen filename pointer to avoid
76
+ // unnecessary string comparison if we stay in the same file.
32
77
  uintptr_t last_filename_ptr;
33
78
 
79
+ // Line tracepoint can work in two modes: single threaded and multi threaded
80
+ //
81
+ // In single threaded mode line tracepoint will only cover the thread that started the coverage.
82
+ // This mode is useful for testing frameworks that run tests in multiple threads.
83
+ // Do not use single threaded mode for Rails applications unless you know that you
84
+ // don't run any background threads.
85
+ //
86
+ // In multi threaded mode line tracepoint will cover all threads. This mode is enabled by default
87
+ // and is recommended for most applications.
88
+ enum threading_mode threading_mode;
34
89
  // for single threaded mode: thread that is being covered
35
90
  VALUE th_covered;
36
91
 
37
- int threading_mode;
92
+ // Allocation tracing is used to track test impact for objects that do not
93
+ // contain any methods that could be covered by line tracepoint.
94
+ //
95
+ // Allocation tracing works only in multi threaded mode.
96
+ VALUE object_allocation_tracepoint;
97
+ st_table *klasses_table; // { (VALUE) -> int } hashmap with class names that were covered by allocation during the test run
38
98
  };
39
99
 
40
100
  static void dd_cov_mark(void *ptr)
41
101
  {
42
102
  struct dd_cov_data *dd_cov_data = ptr;
43
- rb_gc_mark_movable(dd_cov_data->coverage);
103
+ rb_gc_mark_movable(dd_cov_data->impacted_files);
44
104
  rb_gc_mark_movable(dd_cov_data->th_covered);
105
+ rb_gc_mark_movable(dd_cov_data->object_allocation_tracepoint);
106
+
107
+ // if GC starts withing dd_cov_allocate() call, klasses_table might not be initialized yet
108
+ if (dd_cov_data->klasses_table != NULL)
109
+ {
110
+ st_foreach(dd_cov_data->klasses_table, mark_key_for_gc_i, 0);
111
+ }
45
112
  }
46
113
 
47
114
  static void dd_cov_free(void *ptr)
@@ -49,17 +116,20 @@ static void dd_cov_free(void *ptr)
49
116
  struct dd_cov_data *dd_cov_data = ptr;
50
117
  xfree(dd_cov_data->root);
51
118
  xfree(dd_cov_data->ignored_path);
119
+ st_free_table(dd_cov_data->klasses_table);
52
120
  xfree(dd_cov_data);
53
121
  }
54
122
 
55
123
  static void dd_cov_compact(void *ptr)
56
124
  {
57
125
  struct dd_cov_data *dd_cov_data = ptr;
58
- dd_cov_data->coverage = rb_gc_location(dd_cov_data->coverage);
126
+ dd_cov_data->impacted_files = rb_gc_location(dd_cov_data->impacted_files);
59
127
  dd_cov_data->th_covered = rb_gc_location(dd_cov_data->th_covered);
128
+ dd_cov_data->object_allocation_tracepoint = rb_gc_location(dd_cov_data->object_allocation_tracepoint);
129
+ // keys for dd_cov_data->klasses_table are not moved by GC, so we don't need to update them
60
130
  }
61
131
 
62
- const rb_data_type_t dd_cov_data_type = {
132
+ static const rb_data_type_t dd_cov_data_type = {
63
133
  .wrap_struct_name = "dd_cov",
64
134
  .function = {
65
135
  .dmark = dd_cov_mark,
@@ -71,64 +141,48 @@ const rb_data_type_t dd_cov_data_type = {
71
141
  static VALUE dd_cov_allocate(VALUE klass)
72
142
  {
73
143
  struct dd_cov_data *dd_cov_data;
74
- VALUE obj = TypedData_Make_Struct(klass, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
144
+ VALUE dd_cov = TypedData_Make_Struct(klass, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
75
145
 
76
- dd_cov_data->coverage = rb_hash_new();
146
+ dd_cov_data->impacted_files = rb_hash_new();
77
147
  dd_cov_data->root = NULL;
78
148
  dd_cov_data->root_len = 0;
79
149
  dd_cov_data->ignored_path = NULL;
80
150
  dd_cov_data->ignored_path_len = 0;
81
151
  dd_cov_data->last_filename_ptr = 0;
82
- dd_cov_data->threading_mode = MULTI_THREADED_COVERAGE_MODE;
152
+ dd_cov_data->threading_mode = multi;
83
153
 
84
- return obj;
85
- }
154
+ dd_cov_data->object_allocation_tracepoint = Qnil;
155
+ // numtable type is needed to store VALUE as a key
156
+ dd_cov_data->klasses_table = st_init_numtable();
86
157
 
87
- // DDCov methods
88
- static VALUE dd_cov_initialize(int argc, VALUE *argv, VALUE self)
89
- {
90
- VALUE opt;
158
+ return dd_cov;
159
+ }
91
160
 
92
- rb_scan_args(argc, argv, "10", &opt);
93
- VALUE rb_root = rb_hash_lookup(opt, ID2SYM(rb_intern("root")));
94
- if (!RTEST(rb_root))
95
- {
96
- rb_raise(rb_eArgError, "root is required");
97
- }
98
- VALUE rb_ignored_path = rb_hash_lookup(opt, ID2SYM(rb_intern("ignored_path")));
161
+ // Helper functions (available in C only)
99
162
 
100
- VALUE rb_threading_mode = rb_hash_lookup(opt, ID2SYM(rb_intern("threading_mode")));
101
- int threading_mode;
102
- if (rb_threading_mode == ID2SYM(rb_intern("multi")))
103
- {
104
- threading_mode = MULTI_THREADED_COVERAGE_MODE;
105
- }
106
- else if (rb_threading_mode == ID2SYM(rb_intern("single")))
107
- {
108
- threading_mode = SINGLE_THREADED_COVERAGE_MODE;
109
- }
110
- else
163
+ // Checks if the filename is located under the root folder of the project (but not
164
+ // in the ignored folder) and adds it to the impacted_files hash.
165
+ static void record_impacted_file(struct dd_cov_data *dd_cov_data, VALUE filename)
166
+ {
167
+ char *filename_ptr = RSTRING_PTR(filename);
168
+ // if the current filename is not located under the root, we skip it
169
+ if (strncmp(dd_cov_data->root, filename_ptr, dd_cov_data->root_len) != 0)
111
170
  {
112
- rb_raise(rb_eArgError, "threading mode is invalid");
171
+ return;
113
172
  }
114
173
 
115
- struct dd_cov_data *dd_cov_data;
116
- TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
117
-
118
- dd_cov_data->threading_mode = threading_mode;
119
- dd_cov_data->root_len = RSTRING_LEN(rb_root);
120
- dd_cov_data->root = ruby_strndup(RSTRING_PTR(rb_root), dd_cov_data->root_len);
121
-
122
- if (RTEST(rb_ignored_path))
174
+ // if ignored_path is provided and the current filename is located under the ignored_path, we skip it too
175
+ // this is useful for ignoring bundled gems location
176
+ if (dd_cov_data->ignored_path_len != 0 && strncmp(dd_cov_data->ignored_path, filename_ptr, dd_cov_data->ignored_path_len) == 0)
123
177
  {
124
- dd_cov_data->ignored_path_len = RSTRING_LEN(rb_ignored_path);
125
- dd_cov_data->ignored_path = ruby_strndup(RSTRING_PTR(rb_ignored_path), dd_cov_data->ignored_path_len);
178
+ return;
126
179
  }
127
180
 
128
- return Qnil;
181
+ rb_hash_aset(dd_cov_data->impacted_files, filename, Qtrue);
129
182
  }
130
183
 
131
- static void dd_cov_update_coverage(rb_event_flag_t event, VALUE data, VALUE self, ID id, VALUE klass)
184
+ // Executed on RUBY_EVENT_LINE event and captures the filename from rb_profile_frames.
185
+ static void on_line_event(rb_event_flag_t event, VALUE data, VALUE self, ID id, VALUE klass)
132
186
  {
133
187
  struct dd_cov_data *dd_cov_data;
134
188
  TypedData_Get_Struct(data, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
@@ -161,54 +215,182 @@ static void dd_cov_update_coverage(rb_event_flag_t event, VALUE data, VALUE self
161
215
  return;
162
216
  }
163
217
 
164
- char *filename_ptr = RSTRING_PTR(filename);
165
- // if the current filename is not located under the root, we skip it
166
- if (strncmp(dd_cov_data->root, filename_ptr, dd_cov_data->root_len) != 0)
218
+ record_impacted_file(dd_cov_data, filename);
219
+ }
220
+
221
+ // Get source location for a given class name
222
+ static VALUE get_source_location(VALUE klass_name)
223
+ {
224
+ return rb_funcall(rb_cObject, rb_intern("const_source_location"), 1, klass_name);
225
+ }
226
+
227
+ // Get source location for a given class name and swallow any exceptions
228
+ static VALUE safely_get_source_location(VALUE klass_name)
229
+ {
230
+ return rescue_nil(get_source_location, klass_name);
231
+ }
232
+
233
+ // This function is called for each class that was instantiated during the test run.
234
+ static int process_instantiated_klass(st_data_t key, st_data_t _value, st_data_t data)
235
+ {
236
+ VALUE klass = (VALUE)key;
237
+ struct dd_cov_data *dd_cov_data = (struct dd_cov_data *)data;
238
+
239
+ VALUE klass_name = rb_class_name(klass);
240
+ if (klass_name == Qnil)
241
+ {
242
+ return ST_CONTINUE;
243
+ }
244
+
245
+ VALUE source_location = safely_get_source_location(klass_name);
246
+ if (source_location == Qnil || RARRAY_LEN(source_location) == 0)
247
+ {
248
+ return ST_CONTINUE;
249
+ }
250
+
251
+ VALUE filename = RARRAY_AREF(source_location, 0);
252
+ if (filename == Qnil)
253
+ {
254
+ return ST_CONTINUE;
255
+ }
256
+
257
+ record_impacted_file(dd_cov_data, filename);
258
+ return ST_CONTINUE;
259
+ }
260
+
261
+ // Executed on RUBY_INTERNAL_EVENT_NEWOBJ event and captures the source file for the
262
+ // allocated object's class.
263
+ static void on_newobj_event(VALUE tracepoint_data, void *data)
264
+ {
265
+ rb_trace_arg_t *tracearg = rb_tracearg_from_tracepoint(tracepoint_data);
266
+ VALUE new_object = rb_tracearg_object(tracearg);
267
+
268
+ // To keep things fast and practical, we only care about objects that extend
269
+ // either Object or Struct.
270
+ enum ruby_value_type type = rb_type(new_object);
271
+ if (type != RUBY_T_OBJECT && type != RUBY_T_STRUCT)
167
272
  {
168
273
  return;
169
274
  }
170
275
 
171
- // if ignored_path is provided and the current filename is located under the ignored_path, we skip it too
172
- // this is useful for ignoring bundled gems location
173
- if (dd_cov_data->ignored_path_len != 0 && strncmp(dd_cov_data->ignored_path, filename_ptr, dd_cov_data->ignored_path_len) == 0)
276
+ VALUE klass = rb_class_of(new_object);
277
+ if (klass == Qnil || klass == 0)
278
+ {
279
+ return;
280
+ }
281
+ // Skip anonymous classes starting with "#<Class".
282
+ // it allows us to skip the source location lookup that will always fail
283
+ //
284
+ // rb_mod_name returns nil for anonymous classes
285
+ if (rb_mod_name(klass) == Qnil)
174
286
  {
175
287
  return;
176
288
  }
177
289
 
178
- rb_hash_aset(dd_cov_data->coverage, filename, Qtrue);
290
+ struct dd_cov_data *dd_cov_data = (struct dd_cov_data *)data;
291
+
292
+ // We use VALUE directly as a key for the hashmap
293
+ // Ruby itself does it too:
294
+ // https://github.com/ruby/ruby/blob/94b87084a689a3bc732dcaee744508a708223d6c/ext/objspace/object_tracing.c#L113
295
+ st_insert(dd_cov_data->klasses_table, (st_data_t)klass, 1);
179
296
  }
180
297
 
181
- static VALUE dd_cov_start(VALUE self)
298
+ // DDCov instance methods available in Ruby
299
+ static VALUE dd_cov_initialize(int argc, VALUE *argv, VALUE self)
182
300
  {
301
+ VALUE opt;
302
+
303
+ rb_scan_args(argc, argv, "10", &opt);
304
+ VALUE rb_root = rb_hash_lookup(opt, ID2SYM(rb_intern("root")));
305
+ if (!RTEST(rb_root))
306
+ {
307
+ rb_raise(rb_eArgError, "root is required");
308
+ }
309
+ VALUE rb_ignored_path = rb_hash_lookup(opt, ID2SYM(rb_intern("ignored_path")));
310
+
311
+ VALUE rb_threading_mode = rb_hash_lookup(opt, ID2SYM(rb_intern("threading_mode")));
312
+ enum threading_mode threading_mode;
313
+ if (rb_threading_mode == ID2SYM(rb_intern("multi")))
314
+ {
315
+ threading_mode = multi;
316
+ }
317
+ else if (rb_threading_mode == ID2SYM(rb_intern("single")))
318
+ {
319
+ threading_mode = single;
320
+ }
321
+ else
322
+ {
323
+ rb_raise(rb_eArgError, "threading mode is invalid");
324
+ }
325
+
326
+ VALUE rb_allocation_tracing_enabled = rb_hash_lookup(opt, ID2SYM(rb_intern("use_allocation_tracing")));
327
+ if (rb_allocation_tracing_enabled == Qtrue && threading_mode == single)
328
+ {
329
+ rb_raise(rb_eArgError, "allocation tracing is not supported in single threaded mode");
330
+ }
183
331
 
184
332
  struct dd_cov_data *dd_cov_data;
185
333
  TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
186
334
 
335
+ dd_cov_data->threading_mode = threading_mode;
336
+ dd_cov_data->root_len = RSTRING_LEN(rb_root);
337
+ dd_cov_data->root = ruby_strndup(RSTRING_PTR(rb_root), dd_cov_data->root_len);
338
+
339
+ if (RTEST(rb_ignored_path))
340
+ {
341
+ dd_cov_data->ignored_path_len = RSTRING_LEN(rb_ignored_path);
342
+ dd_cov_data->ignored_path = ruby_strndup(RSTRING_PTR(rb_ignored_path), dd_cov_data->ignored_path_len);
343
+ }
344
+
345
+ if (rb_allocation_tracing_enabled == Qtrue)
346
+ {
347
+ dd_cov_data->object_allocation_tracepoint = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_NEWOBJ, on_newobj_event, (void *)dd_cov_data);
348
+ }
349
+
350
+ return Qnil;
351
+ }
352
+
353
+ // starts test impact collection, executed before the start of each test
354
+ static VALUE dd_cov_start(VALUE self)
355
+ {
356
+ struct dd_cov_data *dd_cov_data;
357
+ TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
358
+
187
359
  if (dd_cov_data->root_len == 0)
188
360
  {
189
361
  rb_raise(rb_eRuntimeError, "root is required");
190
362
  }
191
363
 
192
- if (dd_cov_data->threading_mode == SINGLE_THREADED_COVERAGE_MODE)
364
+ // add line tracepoint
365
+ if (dd_cov_data->threading_mode == single)
193
366
  {
194
367
  VALUE thval = rb_thread_current();
195
- rb_thread_add_event_hook(thval, dd_cov_update_coverage, RUBY_EVENT_LINE, self);
368
+ rb_thread_add_event_hook(thval, on_line_event, RUBY_EVENT_LINE, self);
196
369
  dd_cov_data->th_covered = thval;
197
370
  }
198
371
  else
199
372
  {
200
- rb_add_event_hook(dd_cov_update_coverage, RUBY_EVENT_LINE, self);
373
+ rb_add_event_hook(on_line_event, RUBY_EVENT_LINE, self);
374
+ }
375
+
376
+ // add object allocation tracepoint
377
+ if (dd_cov_data->object_allocation_tracepoint != Qnil)
378
+ {
379
+ rb_tracepoint_enable(dd_cov_data->object_allocation_tracepoint);
201
380
  }
202
381
 
203
382
  return self;
204
383
  }
205
384
 
385
+ // stops test impact collection, executed after the end of each test
386
+ // returns the hash with impacted files and resets the internal state
206
387
  static VALUE dd_cov_stop(VALUE self)
207
388
  {
208
389
  struct dd_cov_data *dd_cov_data;
209
390
  TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
210
391
 
211
- if (dd_cov_data->threading_mode == SINGLE_THREADED_COVERAGE_MODE)
392
+ // stop line tracepoint
393
+ if (dd_cov_data->threading_mode == single)
212
394
  {
213
395
  VALUE thval = rb_thread_current();
214
396
  if (!rb_equal(thval, dd_cov_data->th_covered))
@@ -216,17 +398,27 @@ static VALUE dd_cov_stop(VALUE self)
216
398
  rb_raise(rb_eRuntimeError, "Coverage was not started by this thread");
217
399
  }
218
400
 
219
- rb_thread_remove_event_hook(dd_cov_data->th_covered, dd_cov_update_coverage);
401
+ rb_thread_remove_event_hook(dd_cov_data->th_covered, on_line_event);
220
402
  dd_cov_data->th_covered = Qnil;
221
403
  }
222
404
  else
223
405
  {
224
- rb_remove_event_hook(dd_cov_update_coverage);
406
+ rb_remove_event_hook(on_line_event);
225
407
  }
226
408
 
227
- VALUE res = dd_cov_data->coverage;
409
+ // stop object allocation tracepoint
410
+ if (dd_cov_data->object_allocation_tracepoint != Qnil)
411
+ {
412
+ rb_tracepoint_disable(dd_cov_data->object_allocation_tracepoint);
413
+ }
414
+
415
+ // process classes covered by allocation tracing
416
+ st_foreach(dd_cov_data->klasses_table, process_instantiated_klass, (st_data_t)dd_cov_data);
417
+ st_clear(dd_cov_data->klasses_table);
418
+
419
+ VALUE res = dd_cov_data->impacted_files;
228
420
 
229
- dd_cov_data->coverage = rb_hash_new();
421
+ dd_cov_data->impacted_files = rb_hash_new();
230
422
  dd_cov_data->last_filename_ptr = 0;
231
423
 
232
424
  return res;
@@ -236,8 +428,8 @@ void Init_datadog_cov(void)
236
428
  {
237
429
  VALUE mDatadog = rb_define_module("Datadog");
238
430
  VALUE mCI = rb_define_module_under(mDatadog, "CI");
239
- VALUE mITR = rb_define_module_under(mCI, "ITR");
240
- VALUE mCoverage = rb_define_module_under(mITR, "Coverage");
431
+ VALUE mTestOptimisation = rb_define_module_under(mCI, "TestOptimisation");
432
+ VALUE mCoverage = rb_define_module_under(mTestOptimisation, "Coverage");
241
433
  VALUE cDatadogCov = rb_define_class_under(mCoverage, "DDCov", rb_cObject);
242
434
 
243
435
  rb_define_alloc_func(cDatadogCov, dd_cov_allocate);