datadog-ci 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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