datadog-ci 1.1.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61f6379b9a5ac0c29ff9ee6b1687c1c2add77d6a5031dfd5758eb34a66cc6235
4
- data.tar.gz: a795d8d69513925ee5ea6cf113cb54cec3711fe65ae5e928fcdeb8e1454323af
3
+ metadata.gz: ba0975da7283a037f3ea9c70954f2868cf387a0e86608882d2ebefdd097e0cf7
4
+ data.tar.gz: 7842e2e30802411616d987bcbae9be620befa27ba90206a733646a469225f835
5
5
  SHA512:
6
- metadata.gz: 3ea51d0baa70ecb4e66fb49712f4f291582ecb04455b2d32ec1328bb8ea3c112c4c25d80e450db542ee4f29408786b09a82b5f94fb1c7ef8c96b98d301e61e9b
7
- data.tar.gz: 4f066970c2d9ed93001f35f9096cefcf2a4447a86cf7c1d89435853b1da00389c587924304c7a213e56f58f64b191523d202e34f86da535ded271583881411ac
6
+ metadata.gz: 89bce02785793b2c1f5b5438f6c9cfbf63d3ff4cb065788b4884063341bdc23c289e29715f846ce24e5a4ab939c9cdeb05e755a216e92cee6f20b9954462b05f
7
+ data.tar.gz: 6adac19cff9d74fc081d22654a8b0db93152c8c5b6706fee82263e6f219edd96bb473f9595c85418a904110bfcde430b7ddcabd034279cb45d24374a55c5b170
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
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
+
3
8
  ## [1.1.0] - 2024-07-01
4
9
 
5
10
  ### Added
@@ -272,7 +277,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil
272
277
 
273
278
  - Ruby versions < 2.7 no longer supported ([#8][])
274
279
 
275
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.1.0...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
276
282
  [1.1.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.1...v1.1.0
277
283
  [1.0.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0...v1.0.1
278
284
  [1.0.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0.beta6...v1.0.0
@@ -384,4 +390,5 @@ Currently test suite level visibility is not used by our instrumentation: it wil
384
390
  [#185]: https://github.com/DataDog/datadog-ci-rb/issues/185
385
391
  [#189]: https://github.com/DataDog/datadog-ci-rb/issues/189
386
392
  [#190]: https://github.com/DataDog/datadog-ci-rb/issues/190
387
- [#193]: https://github.com/DataDog/datadog-ci-rb/issues/193
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);
@@ -2,12 +2,12 @@
2
2
 
3
3
  require_relative "../ext/settings"
4
4
  require_relative "../git/tree_uploader"
5
- require_relative "../itr/runner"
6
- require_relative "../itr/coverage/transport"
7
- require_relative "../itr/coverage/writer"
5
+ require_relative "../test_optimisation/component"
6
+ require_relative "../test_optimisation/coverage/transport"
7
+ require_relative "../test_optimisation/coverage/writer"
8
+ require_relative "../test_visibility/component"
8
9
  require_relative "../test_visibility/flush"
9
- require_relative "../test_visibility/recorder"
10
- require_relative "../test_visibility/null_recorder"
10
+ require_relative "../test_visibility/null_component"
11
11
  require_relative "../test_visibility/serializers/factories/test_level"
12
12
  require_relative "../test_visibility/serializers/factories/test_suite_level"
13
13
  require_relative "../test_visibility/transport"
@@ -21,15 +21,15 @@ module Datadog
21
21
  module Configuration
22
22
  # Adds CI behavior to Datadog trace components
23
23
  module Components
24
- attr_reader :ci_recorder, :itr
24
+ attr_reader :test_visibility, :test_optimisation
25
25
 
26
26
  def initialize(settings)
27
+ @test_optimisation = nil
28
+ @test_visibility = TestVisibility::NullComponent.new
29
+
27
30
  # Activate CI mode if enabled
28
31
  if settings.ci.enabled
29
32
  activate_ci!(settings)
30
- else
31
- @itr = nil
32
- @ci_recorder = TestVisibility::NullRecorder.new
33
33
  end
34
34
 
35
35
  super
@@ -38,8 +38,8 @@ module Datadog
38
38
  def shutdown!(replacement = nil)
39
39
  super
40
40
 
41
- @ci_recorder&.shutdown!
42
- @itr&.shutdown!
41
+ @test_visibility&.shutdown!
42
+ @test_optimisation&.shutdown!
43
43
  end
44
44
 
45
45
  def activate_ci!(settings)
@@ -53,22 +53,21 @@ module Datadog
53
53
  return
54
54
  end
55
55
 
56
- # Configure ddtrace library for CI visibility mode
56
+ # Builds test visibility API layer in agentless or EvP proxy mode
57
+ test_visibility_api = build_test_visibility_api(settings)
58
+ # bail out early if api is misconfigured
59
+ return unless settings.ci.enabled
60
+
61
+ # Configure datadog gem for test visibility mode
62
+
57
63
  # Deactivate telemetry
58
64
  settings.telemetry.enabled = false
59
65
 
60
- # Deactivate remote configuration
66
+ # Test visibility uses its own remote settings
61
67
  settings.remote.enabled = false
62
68
 
63
- # do not use 128-bit trace ids for CI visibility
64
- # they are used for OTEL compatibility in Datadog tracer
65
- settings.tracing.trace_id_128_bit_generation_enabled = false
66
-
67
- # Activate underlying tracing test mode
68
- settings.tracing.test_mode.enabled = true
69
-
70
- # Choose user defined TraceFlush or default to CI TraceFlush
71
- settings.tracing.test_mode.trace_flush = settings.ci.trace_flush || CI::TestVisibility::Flush::Partial.new
69
+ # startup logs are useless for test visibility and create noise
70
+ settings.diagnostics.startup_logs.enabled = false
72
71
 
73
72
  # When timecop is present, Time.now is mocked and .now_without_mock_time is added on Time to
74
73
  # get the current time without the mock.
@@ -81,76 +80,70 @@ module Datadog
81
80
  end
82
81
  end
83
82
 
84
- # startup logs are useless for CI visibility and create noise
85
- settings.diagnostics.startup_logs.enabled = false
83
+ # Configure Datadog::Tracing module
86
84
 
87
- # transport creation
88
- writer_options = settings.ci.writer_options
89
- coverage_writer = nil
90
- test_visibility_api = build_test_visibility_api(settings)
85
+ # No need not use 128-bit trace ids for test visibility,
86
+ # they are used for OTEL compatibility in Datadog tracer
87
+ settings.tracing.trace_id_128_bit_generation_enabled = false
91
88
 
92
- if test_visibility_api
93
- # setup writer for code coverage payloads
94
- coverage_writer = ITR::Coverage::Writer.new(
95
- transport: ITR::Coverage::Transport.new(api: test_visibility_api)
96
- )
89
+ # Activate underlying tracing test mode with async worker
90
+ settings.tracing.test_mode.enabled = true
91
+ settings.tracing.test_mode.async = true
92
+ settings.tracing.test_mode.trace_flush = settings.ci.trace_flush || CI::TestVisibility::Flush::Partial.new
97
93
 
98
- # configure tracing writer to send traces to CI visibility backend
99
- writer_options[:transport] = TestVisibility::Transport.new(
100
- api: test_visibility_api,
101
- serializers_factory: serializers_factory(settings),
102
- dd_env: settings.env
103
- )
104
- writer_options[:shutdown_timeout] = 60
105
- writer_options[:buffer_size] = 10_000
94
+ trace_writer_options = settings.ci.writer_options
95
+ trace_writer_options[:shutdown_timeout] = 60
96
+ trace_writer_options[:buffer_size] = 10_000
97
+ tracing_transport = build_tracing_transport(settings, test_visibility_api)
98
+ trace_writer_options[:transport] = tracing_transport if tracing_transport
106
99
 
107
- settings.tracing.test_mode.async = true
108
- else
109
- # only legacy APM protocol is supported, so no test suite level visibility
110
- settings.ci.force_test_level_visibility = true
100
+ settings.tracing.test_mode.writer_options = trace_writer_options
111
101
 
112
- # ITR is not supported with APM protocol
113
- settings.ci.itr_enabled = false
114
- end
102
+ # @type ivar @test_optimisation: Datadog::CI::TestOptimisation::Component
103
+ @test_optimisation = build_test_optimisation(settings, test_visibility_api)
115
104
 
116
- settings.tracing.test_mode.writer_options = writer_options
105
+ @test_visibility = TestVisibility::Component.new(
106
+ test_optimisation: @test_optimisation,
107
+ test_suite_level_visibility_enabled: !settings.ci.force_test_level_visibility,
108
+ remote_settings_api: build_remote_settings_client(settings, test_visibility_api),
109
+ git_tree_upload_worker: build_git_upload_worker(settings, test_visibility_api)
110
+ )
111
+ end
117
112
 
118
- custom_configuration_tags = Utils::TestRun.custom_configuration(settings.tags)
113
+ def build_test_optimisation(settings, test_visibility_api)
114
+ if settings.ci.itr_code_coverage_use_single_threaded_mode &&
115
+ settings.ci.itr_test_impact_analysis_use_allocation_tracing
116
+ Datadog.logger.warn(
117
+ "Intelligent test runner: Single threaded coverage mode is incompatible with allocation tracing. " \
118
+ "Allocation tracing will be disabled. It means that test impact analysis will not be able to detect " \
119
+ "instantiations of objects in your code, which is important for ActiveRecord models. " \
120
+ "Please add your app/model folder to the list of tracked files or disable single threaded coverage mode."
121
+ )
119
122
 
120
- remote_settings_api = Transport::RemoteSettingsApi.new(
121
- api: test_visibility_api,
122
- dd_env: settings.env,
123
- config_tags: custom_configuration_tags
124
- )
123
+ settings.ci.itr_test_impact_analysis_use_allocation_tracing = false
124
+ end
125
125
 
126
- itr = ITR::Runner.new(
126
+ if RUBY_VERSION.start_with?("3.2.") && RUBY_VERSION < "3.2.3" &&
127
+ settings.ci.itr_test_impact_analysis_use_allocation_tracing
128
+ Datadog.logger.warn(
129
+ "Intelligent test runner: Allocation tracing is not supported in Ruby versions 3.2.0, 3.2.1 and 3.2.2 and will be forcibly " \
130
+ "disabled. This is due to a VM bug that can lead to crashes (https://bugs.ruby-lang.org/issues/19482). " \
131
+ "Please update your Ruby version or add your app/model folder to the list of tracked files." \
132
+ "Set env variable DD_CIVISIBILITY_ITR_TEST_IMPACT_ANALYSIS_USE_ALLOCATION_TRACING to 0 to disable this warning."
133
+ )
134
+ settings.ci.itr_test_impact_analysis_use_allocation_tracing = false
135
+ end
136
+
137
+ TestOptimisation::Component.new(
127
138
  api: test_visibility_api,
128
139
  dd_env: settings.env,
129
- config_tags: custom_configuration_tags,
130
- coverage_writer: coverage_writer,
140
+ config_tags: custom_configuration(settings),
141
+ coverage_writer: build_coverage_writer(settings, test_visibility_api),
131
142
  enabled: settings.ci.enabled && settings.ci.itr_enabled,
132
143
  bundle_location: settings.ci.itr_code_coverage_excluded_bundle_path,
133
- use_single_threaded_coverage: settings.ci.itr_code_coverage_use_single_threaded_mode
134
- )
135
-
136
- git_tree_uploader = Git::TreeUploader.new(api: test_visibility_api)
137
- git_tree_upload_worker = if settings.ci.git_metadata_upload_enabled
138
- Worker.new do |repository_url|
139
- git_tree_uploader.call(repository_url)
140
- end
141
- else
142
- DummyWorker.new
143
- end
144
-
145
- # CI visibility recorder global instance
146
- @ci_recorder = TestVisibility::Recorder.new(
147
- test_suite_level_visibility_enabled: !settings.ci.force_test_level_visibility,
148
- itr: itr,
149
- remote_settings_api: remote_settings_api,
150
- git_tree_upload_worker: git_tree_upload_worker
144
+ use_single_threaded_coverage: settings.ci.itr_code_coverage_use_single_threaded_mode,
145
+ use_allocation_tracing: settings.ci.itr_test_impact_analysis_use_allocation_tracing
151
146
  )
152
-
153
- @itr = itr
154
147
  end
155
148
 
156
149
  def build_test_visibility_api(settings)
@@ -179,12 +172,61 @@ module Datadog
179
172
  Datadog.logger.debug(
180
173
  "Old agent version detected, no evp_proxy support. Forcing test level visibility mode"
181
174
  )
175
+
176
+ # only legacy APM protocol is supported, so no test suite level visibility
177
+ settings.ci.force_test_level_visibility = true
178
+
179
+ # ITR is not supported with APM protocol
180
+ settings.ci.itr_enabled = false
182
181
  end
183
182
  end
184
183
 
185
184
  api
186
185
  end
187
186
 
187
+ def build_tracing_transport(settings, api)
188
+ return nil if api.nil?
189
+
190
+ TestVisibility::Transport.new(
191
+ api: api,
192
+ serializers_factory: serializers_factory(settings),
193
+ dd_env: settings.env
194
+ )
195
+ end
196
+
197
+ def build_coverage_writer(settings, api)
198
+ return nil if api.nil?
199
+
200
+ TestOptimisation::Coverage::Writer.new(
201
+ transport: TestOptimisation::Coverage::Transport.new(api: api)
202
+ )
203
+ end
204
+
205
+ def build_git_upload_worker(settings, api)
206
+ if settings.ci.git_metadata_upload_enabled
207
+ git_tree_uploader = Git::TreeUploader.new(api: api)
208
+ Worker.new do |repository_url|
209
+ git_tree_uploader.call(repository_url)
210
+ end
211
+ else
212
+ DummyWorker.new
213
+ end
214
+ end
215
+
216
+ def build_remote_settings_client(settings, api)
217
+ Transport::RemoteSettingsApi.new(
218
+ api: api,
219
+ dd_env: settings.env,
220
+ config_tags: custom_configuration(settings)
221
+ )
222
+ end
223
+
224
+ # fetch custom tags provided by the user in DD_TAGS env var
225
+ # with prefix test.configuration.
226
+ def custom_configuration(settings)
227
+ @custom_configuration ||= Utils::TestRun.custom_configuration(settings.tags)
228
+ end
229
+
188
230
  def serializers_factory(settings)
189
231
  if settings.ci.force_test_level_visibility
190
232
  TestVisibility::Serializers::Factories::TestLevel
@@ -82,6 +82,12 @@ module Datadog
82
82
  o.default false
83
83
  end
84
84
 
85
+ option :itr_test_impact_analysis_use_allocation_tracing do |o|
86
+ o.type :bool
87
+ o.env CI::Ext::Settings::ENV_ITR_TEST_IMPACT_ANALYSIS_USE_ALLOCATION_TRACING
88
+ o.default true
89
+ end
90
+
85
91
  define_method(:instrument) do |integration_name, options = {}, &block|
86
92
  return unless enabled
87
93
 
@@ -14,6 +14,7 @@ module Datadog
14
14
  ENV_GIT_METADATA_UPLOAD_ENABLED = "DD_CIVISIBILITY_GIT_METADATA_UPLOAD_ENABLED"
15
15
  ENV_ITR_CODE_COVERAGE_EXCLUDED_BUNDLE_PATH = "DD_CIVISIBILITY_ITR_CODE_COVERAGE_EXCLUDED_BUNDLE_PATH"
16
16
  ENV_ITR_CODE_COVERAGE_USE_SINGLE_THREADED_MODE = "DD_CIVISIBILITY_ITR_CODE_COVERAGE_USE_SINGLE_THREADED_MODE"
17
+ ENV_ITR_TEST_IMPACT_ANALYSIS_USE_ALLOCATION_TRACING = "DD_CIVISIBILITY_ITR_TEST_IMPACT_ANALYSIS_USE_ALLOCATION_TRACING"
17
18
 
18
19
  # Source: https://docs.datadoghq.com/getting_started/site/
19
20
  DD_SITE_ALLOWLIST = %w[
@@ -202,9 +202,9 @@ module Datadog
202
202
 
203
203
  private
204
204
 
205
- # provides access to global CI recorder for CI models to deactivate themselves
206
- def recorder
207
- Datadog.send(:components).ci_recorder
205
+ # provides access to the test visibility component for CI models to deactivate themselves
206
+ def test_visibility
207
+ Datadog.send(:components).test_visibility
208
208
  end
209
209
  end
210
210
  end
@@ -19,7 +19,7 @@ module Datadog
19
19
  # Finishes the current test.
20
20
  # @return [void]
21
21
  def finish
22
- recorder.deactivate_test
22
+ test_visibility.deactivate_test
23
23
 
24
24
  super
25
25
  end
@@ -14,7 +14,7 @@ module Datadog
14
14
  # Finishes this test module.
15
15
  # @return [void]
16
16
  def finish
17
- recorder.deactivate_test_module
17
+ test_visibility.deactivate_test_module
18
18
 
19
19
  super
20
20
  end
@@ -16,11 +16,11 @@ require_relative "skippable"
16
16
 
17
17
  module Datadog
18
18
  module CI
19
- module ITR
19
+ module TestOptimisation
20
20
  # Intelligent test runner implementation
21
21
  # Integrates with backend to provide test impact analysis data and
22
22
  # skip tests that are not impacted by the changes
23
- class Runner
23
+ class Component
24
24
  include Core::Utils::Forking
25
25
 
26
26
  attr_reader :correlation_id, :skippable_tests, :skipped_tests_count
@@ -32,7 +32,8 @@ module Datadog
32
32
  coverage_writer: nil,
33
33
  enabled: false,
34
34
  bundle_location: nil,
35
- use_single_threaded_coverage: false
35
+ use_single_threaded_coverage: false,
36
+ use_allocation_tracing: true
36
37
  )
37
38
  @enabled = enabled
38
39
  @api = api
@@ -45,6 +46,7 @@ module Datadog
45
46
  bundle_location
46
47
  end
47
48
  @use_single_threaded_coverage = use_single_threaded_coverage
49
+ @use_allocation_tracing = use_allocation_tracing
48
50
 
49
51
  @test_skipping_enabled = false
50
52
  @code_coverage_enabled = false
@@ -57,11 +59,11 @@ module Datadog
57
59
  @skipped_tests_count = 0
58
60
  @mutex = Mutex.new
59
61
 
60
- Datadog.logger.debug("ITR Runner initialized with enabled: #{@enabled}")
62
+ Datadog.logger.debug("TestOptimisation initialized with enabled: #{@enabled}")
61
63
  end
62
64
 
63
65
  def configure(remote_configuration, test_session:, git_tree_upload_worker:)
64
- Datadog.logger.debug("Configuring ITR Runner with remote configuration: #{remote_configuration}")
66
+ Datadog.logger.debug("Configuring TestOptimisation with remote configuration: #{remote_configuration}")
65
67
 
66
68
  @enabled = Utils::Parsing.convert_to_bool(
67
69
  remote_configuration.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY, false)
@@ -83,7 +85,7 @@ module Datadog
83
85
 
84
86
  load_datadog_cov! if @code_coverage_enabled
85
87
 
86
- Datadog.logger.debug("Configured ITR Runner with enabled: #{@enabled}, skipping_tests: #{@test_skipping_enabled}, code_coverage: #{@code_coverage_enabled}")
88
+ Datadog.logger.debug("Configured TestOptimisation with enabled: #{@enabled}, skipping_tests: #{@test_skipping_enabled}, code_coverage: #{@code_coverage_enabled}")
87
89
 
88
90
  fetch_skippable_tests(test_session: test_session, git_tree_upload_worker: git_tree_upload_worker)
89
91
  end
@@ -139,7 +141,7 @@ module Datadog
139
141
  skippable_test_id = Utils::TestRun.skippable_test_id(test.name, test.test_suite_name, test.parameters)
140
142
  if @skippable_tests.include?(skippable_test_id)
141
143
  if forked?
142
- Datadog.logger.warn { "ITR is not supported for forking test runners yet" }
144
+ Datadog.logger.warn { "Intelligent test runner is not supported for forking test runners yet" }
143
145
  return
144
146
  end
145
147
 
@@ -155,7 +157,7 @@ module Datadog
155
157
  return if !test.skipped? || !test.skipped_by_itr?
156
158
 
157
159
  if forked?
158
- Datadog.logger.warn { "ITR is not supported for forking test runners yet" }
160
+ Datadog.logger.warn { "Intelligent test runner is not supported for forking test runners yet" }
159
161
  return
160
162
  end
161
163
 
@@ -167,7 +169,7 @@ module Datadog
167
169
  def write_test_session_tags(test_session)
168
170
  return if !enabled?
169
171
 
170
- Datadog.logger.debug { "Finished ITR session with test skipping enabled: #{@test_skipping_enabled}" }
172
+ Datadog.logger.debug { "Finished optimised session with test skipping enabled: #{@test_skipping_enabled}" }
171
173
  Datadog.logger.debug { "#{@skipped_tests_count} tests were skipped" }
172
174
 
173
175
  test_session.set_tag(Ext::Test::TAG_ITR_TESTS_SKIPPED, @skipped_tests_count.positive?.to_s)
@@ -189,7 +191,8 @@ module Datadog
189
191
  Thread.current[:dd_coverage_collector] ||= Coverage::DDCov.new(
190
192
  root: Git::LocalRepository.root,
191
193
  ignored_path: @bundle_location,
192
- threading_mode: code_coverage_mode
194
+ threading_mode: code_coverage_mode,
195
+ use_allocation_tracing: @use_allocation_tracing
193
196
  )
194
197
  end
195
198
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Datadog
4
4
  module CI
5
- module ITR
5
+ module TestOptimisation
6
6
  module Coverage
7
7
  # Placeholder for code coverage collection
8
8
  # Implementation in ext/datadog_cov
@@ -6,7 +6,7 @@ require_relative "../../git/local_repository"
6
6
 
7
7
  module Datadog
8
8
  module CI
9
- module ITR
9
+ module TestOptimisation
10
10
  module Coverage
11
11
  class Event
12
12
  attr_reader :test_id, :test_suite_id, :test_session_id, :coverage
@@ -5,7 +5,7 @@ require_relative "../../transport/event_platform_transport"
5
5
 
6
6
  module Datadog
7
7
  module CI
8
- module ITR
8
+ module TestOptimisation
9
9
  module Coverage
10
10
  class Transport < Datadog::CI::Transport::EventPlatformTransport
11
11
  private
@@ -11,7 +11,7 @@ require "datadog/core/environment/ext"
11
11
 
12
12
  module Datadog
13
13
  module CI
14
- module ITR
14
+ module TestOptimisation
15
15
  module Coverage
16
16
  class Writer
17
17
  include Core::Workers::Queue
@@ -8,7 +8,7 @@ require_relative "../utils/test_run"
8
8
 
9
9
  module Datadog
10
10
  module CI
11
- module ITR
11
+ module TestOptimisation
12
12
  class Skippable
13
13
  class Response
14
14
  def initialize(http_response)
@@ -15,7 +15,7 @@ module Datadog
15
15
  # Finishes the current test session.
16
16
  # @return [void]
17
17
  def finish
18
- recorder.deactivate_test_session
18
+ test_visibility.deactivate_test_session
19
19
 
20
20
  super
21
21
  end
@@ -26,7 +26,7 @@ module Datadog
26
26
  # we try to derive test suite status from execution stats if no status was set explicitly
27
27
  set_status_from_stats! if undefined?
28
28
 
29
- recorder.deactivate_test_suite(name)
29
+ test_visibility.deactivate_test_suite(name)
30
30
 
31
31
  super
32
32
  end
@@ -28,11 +28,11 @@ module Datadog
28
28
  module TestVisibility
29
29
  # Common behavior for CI tests
30
30
  # Note: this class has too many responsibilities and should be split into multiple classes
31
- class Recorder
31
+ class Component
32
32
  attr_reader :environment_tags, :test_suite_level_visibility_enabled
33
33
 
34
34
  def initialize(
35
- itr:,
35
+ test_optimisation:,
36
36
  remote_settings_api:,
37
37
  git_tree_upload_worker: DummyWorker.new,
38
38
  test_suite_level_visibility_enabled: false,
@@ -46,7 +46,7 @@ module Datadog
46
46
 
47
47
  @codeowners = codeowners
48
48
 
49
- @itr = itr
49
+ @test_optimisation = test_optimisation
50
50
  @remote_settings_api = remote_settings_api
51
51
  @git_tree_upload_worker = git_tree_upload_worker
52
52
  end
@@ -210,7 +210,7 @@ module Datadog
210
210
  end
211
211
 
212
212
  def itr_enabled?
213
- @itr.enabled?
213
+ @test_optimisation.enabled?
214
214
  end
215
215
 
216
216
  private
@@ -234,7 +234,7 @@ module Datadog
234
234
  end
235
235
  end
236
236
 
237
- @itr.configure(
237
+ @test_optimisation.configure(
238
238
  remote_configuration.payload,
239
239
  test_session: test_session,
240
240
  git_tree_upload_worker: @git_tree_upload_worker
@@ -404,17 +404,17 @@ module Datadog
404
404
 
405
405
  # TODO: use kind of event system to notify about test finished?
406
406
  def on_test_finished(test)
407
- @itr.stop_coverage(test)
408
- @itr.count_skipped_test(test)
407
+ @test_optimisation.stop_coverage(test)
408
+ @test_optimisation.count_skipped_test(test)
409
409
  end
410
410
 
411
411
  def on_test_started(test)
412
- @itr.mark_if_skippable(test)
413
- @itr.start_coverage(test)
412
+ @test_optimisation.mark_if_skippable(test)
413
+ @test_optimisation.start_coverage(test)
414
414
  end
415
415
 
416
416
  def on_test_session_finished(test_session)
417
- @itr.write_test_session_tags(test_session)
417
+ @test_optimisation.write_test_session_tags(test_session)
418
418
  end
419
419
  end
420
420
  end
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "recorder"
4
-
5
3
  module Datadog
6
4
  module CI
7
5
  module TestVisibility
8
- # Special recorder that does not record anything
9
- class NullRecorder
6
+ # Special test visibility component that does not record anything
7
+ class NullComponent
10
8
  def start_test_session(service: nil, tags: {})
11
9
  skip_tracing
12
10
  end
@@ -45,6 +43,10 @@ module Datadog
45
43
  def shutdown!
46
44
  end
47
45
 
46
+ def itr_enabled?
47
+ false
48
+ end
49
+
48
50
  private
49
51
 
50
52
  def skip_tracing(block = nil)
@@ -100,7 +100,7 @@ module Datadog
100
100
  end
101
101
 
102
102
  def itr
103
- @itr ||= Datadog::CI.send(:itr_runner)
103
+ @test_optimisation ||= Datadog::CI.send(:test_optimisation)
104
104
  end
105
105
  end
106
106
  end
@@ -4,7 +4,7 @@ module Datadog
4
4
  module CI
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 1
7
+ MINOR = 2
8
8
  PATCH = 0
9
9
  PRE = nil
10
10
  BUILD = nil
data/lib/datadog/ci.rb CHANGED
@@ -39,7 +39,7 @@ module Datadog
39
39
  # @return [Datadog::CI::TestSession] the active, running {Datadog::CI::TestSession}.
40
40
  # @return [nil] if test suite level visibility is disabled or CI mode is disabled.
41
41
  def start_test_session(service: Utils::Configuration.fetch_service_name("test"), tags: {})
42
- recorder.start_test_session(service: service, tags: tags)
42
+ test_visibility.start_test_session(service: service, tags: tags)
43
43
  end
44
44
 
45
45
  # The active, unfinished test session.
@@ -61,7 +61,7 @@ module Datadog
61
61
  # @return [Datadog::CI::TestSession] the active test session
62
62
  # @return [nil] if no test session is active
63
63
  def active_test_session
64
- recorder.active_test_session
64
+ test_visibility.active_test_session
65
65
  end
66
66
 
67
67
  # Starts a {Datadog::CI::TestModule ci_test_module} that represents a single test module (for most Ruby test frameworks
@@ -93,7 +93,7 @@ module Datadog
93
93
  # @return [Datadog::CI::TestModule] the active, running {Datadog::CI::TestModule}.
94
94
  # @return [nil] if test suite level visibility is disabled or CI mode is disabled.
95
95
  def start_test_module(test_module_name, service: nil, tags: {})
96
- recorder.start_test_module(test_module_name, service: service, tags: tags)
96
+ test_visibility.start_test_module(test_module_name, service: service, tags: tags)
97
97
  end
98
98
 
99
99
  # The active, unfinished test module.
@@ -116,7 +116,7 @@ module Datadog
116
116
  # @return [Datadog::CI::TestModule] the active test module
117
117
  # @return [nil] if no test module is active
118
118
  def active_test_module
119
- recorder.active_test_module
119
+ test_visibility.active_test_module
120
120
  end
121
121
 
122
122
  # Starts a {Datadog::CI::TestSuite ci_test_suite} that represents a single test suite.
@@ -145,7 +145,7 @@ module Datadog
145
145
  # @return [Datadog::CI::TestSuite] the active, running {Datadog::CI::TestSuite}.
146
146
  # @return [nil] if test suite level visibility is disabled or CI mode is disabled.
147
147
  def start_test_suite(test_suite_name, service: nil, tags: {})
148
- recorder.start_test_suite(test_suite_name, service: service, tags: tags)
148
+ test_visibility.start_test_suite(test_suite_name, service: service, tags: tags)
149
149
  end
150
150
 
151
151
  # The active, unfinished test suite.
@@ -168,7 +168,7 @@ module Datadog
168
168
  # @return [Datadog::CI::TestSuite] the active test suite
169
169
  # @return [nil] if no test suite with given name is active
170
170
  def active_test_suite(test_suite_name)
171
- recorder.active_test_suite(test_suite_name)
171
+ test_visibility.active_test_suite(test_suite_name)
172
172
  end
173
173
 
174
174
  # Return a {Datadog::CI::Test ci_test} that will trace a test called `test_name`.
@@ -222,7 +222,7 @@ module Datadog
222
222
  # @yieldparam [Datadog::CI::Test] ci_test the newly created and active [Datadog::CI::Test]
223
223
  # @yieldparam [nil] if CI mode is disabled
224
224
  def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
225
- recorder.trace_test(test_name, test_suite_name, service: service, tags: tags, &block)
225
+ test_visibility.trace_test(test_name, test_suite_name, service: service, tags: tags, &block)
226
226
  end
227
227
 
228
228
  # Same as {.trace_test} but it does not accept a block.
@@ -248,7 +248,7 @@ module Datadog
248
248
  # @return [Datadog::CI::Test] the active, unfinished {Datadog::CI::Test}.
249
249
  # @return [nil] if CI mode is disabled.
250
250
  def start_test(test_name, test_suite_name, service: nil, tags: {})
251
- recorder.trace_test(test_name, test_suite_name, service: service, tags: tags)
251
+ test_visibility.trace_test(test_name, test_suite_name, service: service, tags: tags)
252
252
  end
253
253
 
254
254
  # Trace any custom span inside a test. For example, you could trace:
@@ -300,7 +300,7 @@ module Datadog
300
300
  )
301
301
  end
302
302
 
303
- recorder.trace(span_name, type: type, tags: tags, &block)
303
+ test_visibility.trace(span_name, type: type, tags: tags, &block)
304
304
  end
305
305
 
306
306
  # The active, unfinished custom (i.e. not test/suite/module/session) span.
@@ -326,7 +326,7 @@ module Datadog
326
326
  # @return [Datadog::CI::Span] the active span
327
327
  # @return [nil] if no span is active, or if the active span is not a custom span
328
328
  def active_span
329
- span = recorder.active_span
329
+ span = test_visibility.active_span
330
330
  span if span && !Ext::AppTypes::CI_SPAN_TYPES.include?(span.type)
331
331
  end
332
332
 
@@ -352,7 +352,7 @@ module Datadog
352
352
  # @return [Datadog::CI::Test] the active test
353
353
  # @return [nil] if no test is active
354
354
  def active_test
355
- recorder.active_test
355
+ test_visibility.active_test
356
356
  end
357
357
 
358
358
  private
@@ -361,12 +361,12 @@ module Datadog
361
361
  Datadog.send(:components)
362
362
  end
363
363
 
364
- def recorder
365
- components.ci_recorder
364
+ def test_visibility
365
+ components.test_visibility
366
366
  end
367
367
 
368
- def itr_runner
369
- components.itr
368
+ def test_optimisation
369
+ components.test_optimisation
370
370
  end
371
371
  end
372
372
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog-ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-01 00:00:00.000000000 Z
11
+ date: 2024-07-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: datadog
@@ -134,22 +134,22 @@ files:
134
134
  - lib/datadog/ci/git/tree_uploader.rb
135
135
  - lib/datadog/ci/git/upload_packfile.rb
136
136
  - lib/datadog/ci/git/user.rb
137
- - lib/datadog/ci/itr/coverage/ddcov.rb
138
- - lib/datadog/ci/itr/coverage/event.rb
139
- - lib/datadog/ci/itr/coverage/transport.rb
140
- - lib/datadog/ci/itr/coverage/writer.rb
141
- - lib/datadog/ci/itr/runner.rb
142
- - lib/datadog/ci/itr/skippable.rb
143
137
  - lib/datadog/ci/span.rb
144
138
  - lib/datadog/ci/test.rb
145
139
  - lib/datadog/ci/test_module.rb
140
+ - lib/datadog/ci/test_optimisation/component.rb
141
+ - lib/datadog/ci/test_optimisation/coverage/ddcov.rb
142
+ - lib/datadog/ci/test_optimisation/coverage/event.rb
143
+ - lib/datadog/ci/test_optimisation/coverage/transport.rb
144
+ - lib/datadog/ci/test_optimisation/coverage/writer.rb
145
+ - lib/datadog/ci/test_optimisation/skippable.rb
146
146
  - lib/datadog/ci/test_session.rb
147
147
  - lib/datadog/ci/test_suite.rb
148
+ - lib/datadog/ci/test_visibility/component.rb
148
149
  - lib/datadog/ci/test_visibility/context/global.rb
149
150
  - lib/datadog/ci/test_visibility/context/local.rb
150
151
  - lib/datadog/ci/test_visibility/flush.rb
151
- - lib/datadog/ci/test_visibility/null_recorder.rb
152
- - lib/datadog/ci/test_visibility/recorder.rb
152
+ - lib/datadog/ci/test_visibility/null_component.rb
153
153
  - lib/datadog/ci/test_visibility/serializers/base.rb
154
154
  - lib/datadog/ci/test_visibility/serializers/factories/test_level.rb
155
155
  - lib/datadog/ci/test_visibility/serializers/factories/test_suite_level.rb