datadog-ci 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -2
  3. data/ext/datadog_ci_native/ci.c +10 -0
  4. data/ext/{datadog_cov → datadog_ci_native}/datadog_cov.c +119 -147
  5. data/ext/datadog_ci_native/datadog_cov.h +3 -0
  6. data/ext/datadog_ci_native/datadog_source_code.c +28 -0
  7. data/ext/datadog_ci_native/datadog_source_code.h +3 -0
  8. data/ext/{datadog_cov → datadog_ci_native}/extconf.rb +1 -1
  9. data/lib/datadog/ci/contrib/minitest/test.rb +17 -7
  10. data/lib/datadog/ci/contrib/rspec/example.rb +14 -7
  11. data/lib/datadog/ci/ext/telemetry.rb +1 -2
  12. data/lib/datadog/ci/ext/test.rb +1 -0
  13. data/lib/datadog/ci/git/base_branch_sha_detection/base.rb +66 -0
  14. data/lib/datadog/ci/git/base_branch_sha_detection/branch_metric.rb +34 -0
  15. data/lib/datadog/ci/git/base_branch_sha_detection/guesser.rb +137 -0
  16. data/lib/datadog/ci/git/base_branch_sha_detection/merge_base_extractor.rb +29 -0
  17. data/lib/datadog/ci/git/base_branch_sha_detector.rb +63 -0
  18. data/lib/datadog/ci/git/cli.rb +56 -0
  19. data/lib/datadog/ci/git/local_repository.rb +73 -294
  20. data/lib/datadog/ci/git/telemetry.rb +14 -0
  21. data/lib/datadog/ci/impacted_tests_detection/component.rb +0 -2
  22. data/lib/datadog/ci/test_optimisation/component.rb +10 -6
  23. data/lib/datadog/ci/test_optimisation/coverage/ddcov.rb +1 -1
  24. data/lib/datadog/ci/test_visibility/telemetry.rb +3 -0
  25. data/lib/datadog/ci/utils/command.rb +116 -0
  26. data/lib/datadog/ci/utils/source_code.rb +31 -0
  27. data/lib/datadog/ci/version.rb +1 -1
  28. metadata +16 -5
  29. data/lib/datadog/ci/impacted_tests_detection/telemetry.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d2afc6148658bb41352ad4040c483406594852787e35aa523063a6ae452ec17
4
- data.tar.gz: 3b7f9b07092e33b016b94b25d4aae2b12ff442f458dac6bea7b987adef4ca345
3
+ metadata.gz: 4673e9680749b01863ddde129169fb985f7be2f46ae83da674b856bc70900dc9
4
+ data.tar.gz: 5e0c4f0a055d7a3017dc2984d39b142cfcf9f4bfb39dca5713a18af0481276f6
5
5
  SHA512:
6
- metadata.gz: 7b368e3e3a1c6004307fe834f6f6442f51f7ab538434fb9a34ea42c6fbefa90f7995e54ed35aded0ddfc9e21ff34e70d91e845e9b82009a9e2977f3de9205ff6
7
- data.tar.gz: c56a90e2d3fcc755f9fc224ea8e385c8393a8252487132ca4287377375ac57e839171853391ad9bcb4577a422b0b7c154f32f15c91b69b2b7b17de86aa396ca6
6
+ metadata.gz: 58d120417a247f9e6be958efeea4094e6d3f5dc861df5dbe74428f7968e2dc96156b19d5c3db512f70afa15686997e6b38aae39dbeb8d0bb2f4f19fdb8b524b4
7
+ data.tar.gz: 7d91c8fc4e85bede1f57dc64a151d4ebfb222be88710f958e8a6538ad37e44cedf56c6379fb1476fb472ebf65db57023915100254e180e6087b2c7c0eb7d78ca
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.18.0] - 2025-06-06
4
+
5
+ ### Added
6
+
7
+ * Report end lines for tests ([#327][])
8
+
9
+ ### Changed
10
+
11
+ * Impacted tests detection: improve a script to determine the base branch sha ([#329][])
12
+
3
13
  ## [1.17.0] - 2025-05-19
4
14
 
5
15
  ### Added
@@ -443,7 +453,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil
443
453
 
444
454
  - Ruby versions < 2.7 no longer supported ([#8][])
445
455
 
446
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.17.0...main
456
+ [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.18.0...main
457
+ [1.18.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.17.0...v1.18.0
447
458
  [1.17.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.16.0...v1.17.0
448
459
  [1.16.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.15.0...v1.16.0
449
460
  [1.15.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.14.0...v1.15.0
@@ -634,4 +645,6 @@ Currently test suite level visibility is not used by our instrumentation: it wil
634
645
  [#318]: https://github.com/DataDog/datadog-ci-rb/issues/318
635
646
  [#320]: https://github.com/DataDog/datadog-ci-rb/issues/320
636
647
  [#321]: https://github.com/DataDog/datadog-ci-rb/issues/321
637
- [#323]: https://github.com/DataDog/datadog-ci-rb/issues/323
648
+ [#323]: https://github.com/DataDog/datadog-ci-rb/issues/323
649
+ [#327]: https://github.com/DataDog/datadog-ci-rb/issues/327
650
+ [#329]: https://github.com/DataDog/datadog-ci-rb/issues/329
@@ -0,0 +1,10 @@
1
+ #include "datadog_cov.h"
2
+ #include "datadog_source_code.h"
3
+
4
+ void Init_datadog_ci_native(void) {
5
+ // Coverage::DDCov
6
+ Init_datadog_cov();
7
+
8
+ // Utils::SourceCode
9
+ Init_datadog_source_code();
10
+ }
@@ -4,24 +4,20 @@
4
4
 
5
5
  #include <stdbool.h>
6
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.
7
+ // This is a native extension that collects a list of Ruby files that were
8
+ // executed during the test run. It is used to optimize the test suite by
9
+ // running only the tests that are affected by the changes.
9
10
 
10
11
  #define PROFILE_FRAMES_BUFFER_SIZE 1
11
12
 
12
13
  // threading modes
13
- enum threading_mode
14
- {
15
- single,
16
- multi
17
- };
14
+ enum threading_mode { single, multi };
18
15
 
19
16
  // functions declarations
20
17
  static void on_newobj_event(VALUE tracepoint_data, void *data);
21
18
 
22
19
  // utility functions
23
- static char *ruby_strndup(const char *str, size_t size)
24
- {
20
+ static char *ruby_strndup(const char *str, size_t size) {
25
21
  char *dup;
26
22
 
27
23
  dup = xmalloc(size + 1);
@@ -34,22 +30,22 @@ static char *ruby_strndup(const char *str, size_t size)
34
30
  // Equivalent to Ruby "begin/rescue nil" call, where we call a C function and
35
31
  // swallow the exception if it occurs - const_source_location often fails with
36
32
  // 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
- {
33
+ static VALUE rescue_nil(VALUE (*function_to_call_safely)(VALUE),
34
+ VALUE function_to_call_safely_arg) {
39
35
  int exception_state;
40
36
  // 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
- {
37
+ // see
38
+ // https://github.com/ruby/ruby/blob/3219ecf4f659908674f534491d8934ba54e1143d/include/ruby/internal/intern/proc.h#L349
39
+ VALUE result = rb_protect(function_to_call_safely,
40
+ function_to_call_safely_arg, &exception_state);
41
+ if (exception_state != 0) {
45
42
  return Qnil;
46
43
  }
47
44
 
48
45
  return result;
49
46
  }
50
47
 
51
- static int mark_key_for_gc_i(st_data_t key, st_data_t _value, st_data_t _data)
52
- {
48
+ static int mark_key_for_gc_i(st_data_t key, st_data_t _value, st_data_t _data) {
53
49
  VALUE klass = (VALUE)key;
54
50
  // mark klass link for GC as non-movable to avoid changing hashtable's keys
55
51
  rb_gc_mark(klass);
@@ -57,8 +53,7 @@ static int mark_key_for_gc_i(st_data_t key, st_data_t _value, st_data_t _data)
57
53
  }
58
54
 
59
55
  // Data structure
60
- struct dd_cov_data
61
- {
56
+ struct dd_cov_data {
62
57
  // Ruby hash with filenames impacted by the test.
63
58
  VALUE impacted_files;
64
59
 
@@ -78,13 +73,13 @@ struct dd_cov_data
78
73
 
79
74
  // Line tracepoint can work in two modes: single threaded and multi threaded
80
75
  //
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.
76
+ // In single threaded mode line tracepoint will only cover the thread that
77
+ // started the coverage. This mode is useful for testing frameworks that run
78
+ // tests in multiple threads. Do not use single threaded mode for Rails
79
+ // applications unless you know that you don't run any background threads.
85
80
  //
86
- // In multi threaded mode line tracepoint will cover all threads. This mode is enabled by default
87
- // and is recommended for most applications.
81
+ // In multi threaded mode line tracepoint will cover all threads. This mode is
82
+ // enabled by default and is recommended for most applications.
88
83
  enum threading_mode threading_mode;
89
84
  // for single threaded mode: thread that is being covered
90
85
  VALUE th_covered;
@@ -94,25 +89,24 @@ struct dd_cov_data
94
89
  //
95
90
  // Allocation tracing works only in multi threaded mode.
96
91
  VALUE object_allocation_tracepoint;
97
- st_table *klasses_table; // { (VALUE) -> int } hashmap with class names that were covered by allocation during the test run
92
+ st_table *klasses_table; // { (VALUE) -> int } hashmap with class names that
93
+ // were covered by allocation during the test run
98
94
  };
99
95
 
100
- static void dd_cov_mark(void *ptr)
101
- {
96
+ static void dd_cov_mark(void *ptr) {
102
97
  struct dd_cov_data *dd_cov_data = ptr;
103
98
  rb_gc_mark_movable(dd_cov_data->impacted_files);
104
99
  rb_gc_mark_movable(dd_cov_data->th_covered);
105
100
  rb_gc_mark_movable(dd_cov_data->object_allocation_tracepoint);
106
101
 
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
- {
102
+ // if GC starts withing dd_cov_allocate() call, klasses_table might not be
103
+ // initialized yet
104
+ if (dd_cov_data->klasses_table != NULL) {
110
105
  st_foreach(dd_cov_data->klasses_table, mark_key_for_gc_i, 0);
111
106
  }
112
107
  }
113
108
 
114
- static void dd_cov_free(void *ptr)
115
- {
109
+ static void dd_cov_free(void *ptr) {
116
110
  struct dd_cov_data *dd_cov_data = ptr;
117
111
  xfree(dd_cov_data->root);
118
112
  xfree(dd_cov_data->ignored_path);
@@ -120,28 +114,28 @@ static void dd_cov_free(void *ptr)
120
114
  xfree(dd_cov_data);
121
115
  }
122
116
 
123
- static void dd_cov_compact(void *ptr)
124
- {
117
+ static void dd_cov_compact(void *ptr) {
125
118
  struct dd_cov_data *dd_cov_data = ptr;
126
119
  dd_cov_data->impacted_files = rb_gc_location(dd_cov_data->impacted_files);
127
120
  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
121
+ dd_cov_data->object_allocation_tracepoint =
122
+ rb_gc_location(dd_cov_data->object_allocation_tracepoint);
123
+ // keys for dd_cov_data->klasses_table are not moved by GC, so we don't need
124
+ // to update them
130
125
  }
131
126
 
132
127
  static const rb_data_type_t dd_cov_data_type = {
133
128
  .wrap_struct_name = "dd_cov",
134
- .function = {
135
- .dmark = dd_cov_mark,
136
- .dfree = dd_cov_free,
137
- .dsize = NULL,
138
- .dcompact = dd_cov_compact},
129
+ .function = {.dmark = dd_cov_mark,
130
+ .dfree = dd_cov_free,
131
+ .dsize = NULL,
132
+ .dcompact = dd_cov_compact},
139
133
  .flags = RUBY_TYPED_FREE_IMMEDIATELY};
140
134
 
141
- static VALUE dd_cov_allocate(VALUE klass)
142
- {
135
+ static VALUE dd_cov_allocate(VALUE klass) {
143
136
  struct dd_cov_data *dd_cov_data;
144
- VALUE dd_cov = TypedData_Make_Struct(klass, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
137
+ VALUE dd_cov = TypedData_Make_Struct(klass, struct dd_cov_data,
138
+ &dd_cov_data_type, dd_cov_data);
145
139
 
146
140
  dd_cov_data->impacted_files = rb_hash_new();
147
141
  dd_cov_data->root = NULL;
@@ -160,58 +154,56 @@ static VALUE dd_cov_allocate(VALUE klass)
160
154
 
161
155
  // Helper functions (available in C only)
162
156
 
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
- {
157
+ // Checks if the filename is located under the root folder of the project (but
158
+ // not in the ignored folder) and adds it to the impacted_files hash.
159
+ static void record_impacted_file(struct dd_cov_data *dd_cov_data,
160
+ VALUE filename) {
167
161
  char *filename_ptr = RSTRING_PTR(filename);
168
162
  // 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)
170
- {
163
+ if (strncmp(dd_cov_data->root, filename_ptr, dd_cov_data->root_len) != 0) {
171
164
  return;
172
165
  }
173
166
 
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)
177
- {
167
+ // if ignored_path is provided and the current filename is located under the
168
+ // ignored_path, we skip it too this is useful for ignoring bundled gems
169
+ // location
170
+ if (dd_cov_data->ignored_path_len != 0 &&
171
+ strncmp(dd_cov_data->ignored_path, filename_ptr,
172
+ dd_cov_data->ignored_path_len) == 0) {
178
173
  return;
179
174
  }
180
175
 
181
176
  rb_hash_aset(dd_cov_data->impacted_files, filename, Qtrue);
182
177
  }
183
178
 
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)
186
- {
179
+ // Executed on RUBY_EVENT_LINE event and captures the filename from
180
+ // rb_profile_frames.
181
+ static void on_line_event(rb_event_flag_t event, VALUE data, VALUE self, ID id,
182
+ VALUE klass) {
187
183
  struct dd_cov_data *dd_cov_data;
188
- TypedData_Get_Struct(data, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
184
+ TypedData_Get_Struct(data, struct dd_cov_data, &dd_cov_data_type,
185
+ dd_cov_data);
189
186
 
190
187
  const char *c_filename = rb_sourcefile();
191
188
 
192
189
  // skip if we cover the same file again
193
190
  uintptr_t current_filename_ptr = (uintptr_t)c_filename;
194
- if (dd_cov_data->last_filename_ptr == current_filename_ptr)
195
- {
191
+ if (dd_cov_data->last_filename_ptr == current_filename_ptr) {
196
192
  return;
197
193
  }
198
194
  dd_cov_data->last_filename_ptr = current_filename_ptr;
199
195
 
200
196
  VALUE top_frame;
201
- int captured_frames = rb_profile_frames(
202
- 0 /* stack starting depth */,
203
- PROFILE_FRAMES_BUFFER_SIZE,
204
- &top_frame,
205
- NULL);
206
-
207
- if (captured_frames != PROFILE_FRAMES_BUFFER_SIZE)
208
- {
197
+ int captured_frames =
198
+ rb_profile_frames(0 /* stack starting depth */,
199
+ PROFILE_FRAMES_BUFFER_SIZE, &top_frame, NULL);
200
+
201
+ if (captured_frames != PROFILE_FRAMES_BUFFER_SIZE) {
209
202
  return;
210
203
  }
211
204
 
212
205
  VALUE filename = rb_profile_frame_path(top_frame);
213
- if (filename == Qnil)
214
- {
206
+ if (filename == Qnil) {
215
207
  return;
216
208
  }
217
209
 
@@ -219,38 +211,35 @@ static void on_line_event(rb_event_flag_t event, VALUE data, VALUE self, ID id,
219
211
  }
220
212
 
221
213
  // 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);
214
+ static VALUE get_source_location(VALUE klass_name) {
215
+ return rb_funcall(rb_cObject, rb_intern("const_source_location"), 1,
216
+ klass_name);
225
217
  }
226
218
 
227
219
  // Get source location for a given class name and swallow any exceptions
228
- static VALUE safely_get_source_location(VALUE klass_name)
229
- {
220
+ static VALUE safely_get_source_location(VALUE klass_name) {
230
221
  return rescue_nil(get_source_location, klass_name);
231
222
  }
232
223
 
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
- {
224
+ // This function is called for each class that was instantiated during the test
225
+ // run.
226
+ static int process_instantiated_klass(st_data_t key, st_data_t _value,
227
+ st_data_t data) {
236
228
  VALUE klass = (VALUE)key;
237
229
  struct dd_cov_data *dd_cov_data = (struct dd_cov_data *)data;
238
230
 
239
231
  VALUE klass_name = rb_class_name(klass);
240
- if (klass_name == Qnil)
241
- {
232
+ if (klass_name == Qnil) {
242
233
  return ST_CONTINUE;
243
234
  }
244
235
 
245
236
  VALUE source_location = safely_get_source_location(klass_name);
246
- if (source_location == Qnil || RARRAY_LEN(source_location) == 0)
247
- {
237
+ if (source_location == Qnil || RARRAY_LEN(source_location) == 0) {
248
238
  return ST_CONTINUE;
249
239
  }
250
240
 
251
241
  VALUE filename = RARRAY_AREF(source_location, 0);
252
- if (filename == Qnil || !RB_TYPE_P(filename, T_STRING))
253
- {
242
+ if (filename == Qnil || !RB_TYPE_P(filename, T_STRING)) {
254
243
  return ST_CONTINUE;
255
244
  }
256
245
 
@@ -258,32 +247,28 @@ static int process_instantiated_klass(st_data_t key, st_data_t _value, st_data_t
258
247
  return ST_CONTINUE;
259
248
  }
260
249
 
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
- {
250
+ // Executed on RUBY_INTERNAL_EVENT_NEWOBJ event and captures the source file for
251
+ // the allocated object's class.
252
+ static void on_newobj_event(VALUE tracepoint_data, void *data) {
265
253
  rb_trace_arg_t *tracearg = rb_tracearg_from_tracepoint(tracepoint_data);
266
254
  VALUE new_object = rb_tracearg_object(tracearg);
267
255
 
268
256
  // To keep things fast and practical, we only care about objects that extend
269
257
  // either Object or Struct.
270
258
  enum ruby_value_type type = rb_type(new_object);
271
- if (type != RUBY_T_OBJECT && type != RUBY_T_STRUCT)
272
- {
259
+ if (type != RUBY_T_OBJECT && type != RUBY_T_STRUCT) {
273
260
  return;
274
261
  }
275
262
 
276
263
  VALUE klass = rb_class_of(new_object);
277
- if (klass == Qnil || klass == 0)
278
- {
264
+ if (klass == Qnil || klass == 0) {
279
265
  return;
280
266
  }
281
267
  // Skip anonymous classes starting with "#<Class".
282
268
  // it allows us to skip the source location lookup that will always fail
283
269
  //
284
270
  // rb_mod_name returns nil for anonymous classes
285
- if (rb_mod_name(klass) == Qnil)
286
- {
271
+ if (rb_mod_name(klass) == Qnil) {
287
272
  return;
288
273
  }
289
274
 
@@ -296,86 +281,78 @@ static void on_newobj_event(VALUE tracepoint_data, void *data)
296
281
  }
297
282
 
298
283
  // DDCov instance methods available in Ruby
299
- static VALUE dd_cov_initialize(int argc, VALUE *argv, VALUE self)
300
- {
284
+ static VALUE dd_cov_initialize(int argc, VALUE *argv, VALUE self) {
301
285
  VALUE opt;
302
286
 
303
287
  rb_scan_args(argc, argv, "10", &opt);
304
288
  VALUE rb_root = rb_hash_lookup(opt, ID2SYM(rb_intern("root")));
305
- if (!RTEST(rb_root))
306
- {
289
+ if (!RTEST(rb_root)) {
307
290
  rb_raise(rb_eArgError, "root is required");
308
291
  }
309
- VALUE rb_ignored_path = rb_hash_lookup(opt, ID2SYM(rb_intern("ignored_path")));
292
+ VALUE rb_ignored_path =
293
+ rb_hash_lookup(opt, ID2SYM(rb_intern("ignored_path")));
310
294
 
311
- VALUE rb_threading_mode = rb_hash_lookup(opt, ID2SYM(rb_intern("threading_mode")));
295
+ VALUE rb_threading_mode =
296
+ rb_hash_lookup(opt, ID2SYM(rb_intern("threading_mode")));
312
297
  enum threading_mode threading_mode;
313
- if (rb_threading_mode == ID2SYM(rb_intern("multi")))
314
- {
298
+ if (rb_threading_mode == ID2SYM(rb_intern("multi"))) {
315
299
  threading_mode = multi;
316
- }
317
- else if (rb_threading_mode == ID2SYM(rb_intern("single")))
318
- {
300
+ } else if (rb_threading_mode == ID2SYM(rb_intern("single"))) {
319
301
  threading_mode = single;
320
- }
321
- else
322
- {
302
+ } else {
323
303
  rb_raise(rb_eArgError, "threading mode is invalid");
324
304
  }
325
305
 
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");
306
+ VALUE rb_allocation_tracing_enabled =
307
+ rb_hash_lookup(opt, ID2SYM(rb_intern("use_allocation_tracing")));
308
+ if (rb_allocation_tracing_enabled == Qtrue && threading_mode == single) {
309
+ rb_raise(rb_eArgError,
310
+ "allocation tracing is not supported in single threaded mode");
330
311
  }
331
312
 
332
313
  struct dd_cov_data *dd_cov_data;
333
- TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
314
+ TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type,
315
+ dd_cov_data);
334
316
 
335
317
  dd_cov_data->threading_mode = threading_mode;
336
318
  dd_cov_data->root_len = RSTRING_LEN(rb_root);
337
319
  dd_cov_data->root = ruby_strndup(RSTRING_PTR(rb_root), dd_cov_data->root_len);
338
320
 
339
- if (RTEST(rb_ignored_path))
340
- {
321
+ if (RTEST(rb_ignored_path)) {
341
322
  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);
323
+ dd_cov_data->ignored_path = ruby_strndup(RSTRING_PTR(rb_ignored_path),
324
+ dd_cov_data->ignored_path_len);
343
325
  }
344
326
 
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);
327
+ if (rb_allocation_tracing_enabled == Qtrue) {
328
+ dd_cov_data->object_allocation_tracepoint = rb_tracepoint_new(
329
+ Qnil, RUBY_INTERNAL_EVENT_NEWOBJ, on_newobj_event, (void *)dd_cov_data);
348
330
  }
349
331
 
350
332
  return Qnil;
351
333
  }
352
334
 
353
335
  // starts test impact collection, executed before the start of each test
354
- static VALUE dd_cov_start(VALUE self)
355
- {
336
+ static VALUE dd_cov_start(VALUE self) {
356
337
  struct dd_cov_data *dd_cov_data;
357
- TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
338
+ TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type,
339
+ dd_cov_data);
358
340
 
359
- if (dd_cov_data->root_len == 0)
360
- {
341
+ if (dd_cov_data->root_len == 0) {
361
342
  rb_raise(rb_eRuntimeError, "root is required");
362
343
  }
363
344
 
364
345
  // add line tracepoint
365
- if (dd_cov_data->threading_mode == single)
366
- {
346
+ if (dd_cov_data->threading_mode == single) {
367
347
  VALUE thval = rb_thread_current();
368
348
  rb_thread_add_event_hook(thval, on_line_event, RUBY_EVENT_LINE, self);
369
349
  dd_cov_data->th_covered = thval;
370
- }
371
- else
372
- {
350
+ } else {
373
351
  rb_add_event_hook(on_line_event, RUBY_EVENT_LINE, self);
374
352
  }
375
353
 
376
354
  // add object allocation tracepoint
377
- if (dd_cov_data->object_allocation_tracepoint != Qnil)
378
- {
355
+ if (dd_cov_data->object_allocation_tracepoint != Qnil) {
379
356
  rb_tracepoint_enable(dd_cov_data->object_allocation_tracepoint);
380
357
  }
381
358
 
@@ -384,36 +361,32 @@ static VALUE dd_cov_start(VALUE self)
384
361
 
385
362
  // stops test impact collection, executed after the end of each test
386
363
  // returns the hash with impacted files and resets the internal state
387
- static VALUE dd_cov_stop(VALUE self)
388
- {
364
+ static VALUE dd_cov_stop(VALUE self) {
389
365
  struct dd_cov_data *dd_cov_data;
390
- TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
366
+ TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type,
367
+ dd_cov_data);
391
368
 
392
369
  // stop line tracepoint
393
- if (dd_cov_data->threading_mode == single)
394
- {
370
+ if (dd_cov_data->threading_mode == single) {
395
371
  VALUE thval = rb_thread_current();
396
- if (!rb_equal(thval, dd_cov_data->th_covered))
397
- {
372
+ if (!rb_equal(thval, dd_cov_data->th_covered)) {
398
373
  rb_raise(rb_eRuntimeError, "Coverage was not started by this thread");
399
374
  }
400
375
 
401
376
  rb_thread_remove_event_hook(dd_cov_data->th_covered, on_line_event);
402
377
  dd_cov_data->th_covered = Qnil;
403
- }
404
- else
405
- {
378
+ } else {
406
379
  rb_remove_event_hook(on_line_event);
407
380
  }
408
381
 
409
382
  // stop object allocation tracepoint
410
- if (dd_cov_data->object_allocation_tracepoint != Qnil)
411
- {
383
+ if (dd_cov_data->object_allocation_tracepoint != Qnil) {
412
384
  rb_tracepoint_disable(dd_cov_data->object_allocation_tracepoint);
413
385
  }
414
386
 
415
387
  // process classes covered by allocation tracing
416
- st_foreach(dd_cov_data->klasses_table, process_instantiated_klass, (st_data_t)dd_cov_data);
388
+ st_foreach(dd_cov_data->klasses_table, process_instantiated_klass,
389
+ (st_data_t)dd_cov_data);
417
390
  st_clear(dd_cov_data->klasses_table);
418
391
 
419
392
  VALUE res = dd_cov_data->impacted_files;
@@ -424,8 +397,7 @@ static VALUE dd_cov_stop(VALUE self)
424
397
  return res;
425
398
  }
426
399
 
427
- void Init_datadog_cov(void)
428
- {
400
+ void Init_datadog_cov(void) {
429
401
  VALUE mDatadog = rb_define_module("Datadog");
430
402
  VALUE mCI = rb_define_module_under(mDatadog, "CI");
431
403
  VALUE mTestOptimisation = rb_define_module_under(mCI, "TestOptimisation");
@@ -0,0 +1,3 @@
1
+ #pragma once
2
+
3
+ void Init_datadog_cov(void);
@@ -0,0 +1,28 @@
1
+ #include <ruby.h>
2
+
3
+ // These structs and functions are not exported by MRI because they are part of
4
+ // the internal API. We declare them here to use them via dynamic linking.
5
+ typedef struct rb_iseq_struct rb_iseq_t;
6
+ const rb_iseq_t *rb_iseqw_to_iseq(VALUE iseqw);
7
+ void rb_iseq_code_location(const rb_iseq_t *, int *first_lineno,
8
+ int *first_column, int *last_lineno,
9
+ int *last_column);
10
+
11
+ static VALUE last_line_from_iseq(VALUE self, VALUE iseqw) {
12
+ const rb_iseq_t *iseq = rb_iseqw_to_iseq(iseqw);
13
+
14
+ int line;
15
+ rb_iseq_code_location(iseq, NULL, NULL, &line, NULL);
16
+
17
+ return INT2NUM(line);
18
+ }
19
+
20
+ void Init_datadog_source_code(void) {
21
+ VALUE mDatadog = rb_define_module("Datadog");
22
+ VALUE mCI = rb_define_module_under(mDatadog, "CI");
23
+ VALUE mUtils = rb_define_module_under(mCI, "Utils");
24
+ VALUE mSourceCode = rb_define_module_under(mUtils, "SourceCode");
25
+
26
+ rb_define_singleton_method(mSourceCode, "_native_last_line_from_iseq",
27
+ last_line_from_iseq, 1);
28
+ }
@@ -0,0 +1,3 @@
1
+ #pragma once
2
+
3
+ void Init_datadog_source_code(void);
@@ -13,6 +13,6 @@ require "mkmf"
13
13
  # This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that
14
14
  # the wrong library is never loaded.
15
15
  # When requiring, we need to use the exact same string, including the version and the platform.
16
- EXTENSION_NAME = "datadog_cov.#{RUBY_VERSION}_#{RUBY_PLATFORM}".freeze
16
+ EXTENSION_NAME = "datadog_ci_native.#{RUBY_VERSION}_#{RUBY_PLATFORM}".freeze
17
17
 
18
18
  create_makefile(EXTENSION_NAME)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../../ext/test"
4
4
  require_relative "../../git/local_repository"
5
+ require_relative "../../utils/source_code"
5
6
  require_relative "../instrumentation"
6
7
  require_relative "ext"
7
8
  require_relative "helpers"
@@ -27,17 +28,26 @@ module Datadog
27
28
  end
28
29
 
29
30
  test_suite_name = Helpers.test_suite_name(self.class, name)
30
- source_file, line_number = method(name).source_location
31
+
32
+ # @type var tags : Hash[String, String]
33
+ tags = {
34
+ CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
35
+ CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s
36
+ }
37
+
38
+ # try to find out where test method starts and ends
39
+ test_method = method(name)
40
+ source_file, first_line_number = test_method.source_location
41
+ last_line_number = Utils::SourceCode.last_line(test_method)
42
+
43
+ tags[CI::Ext::Test::TAG_SOURCE_FILE] = Git::LocalRepository.relative_to_root(source_file) if source_file
44
+ tags[CI::Ext::Test::TAG_SOURCE_START] = first_line_number.to_s if first_line_number
45
+ tags[CI::Ext::Test::TAG_SOURCE_END] = last_line_number.to_s if last_line_number
31
46
 
32
47
  test_span = test_visibility_component.trace_test(
33
48
  name,
34
49
  test_suite_name,
35
- tags: {
36
- CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
37
- CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s,
38
- CI::Ext::Test::TAG_SOURCE_FILE => Git::LocalRepository.relative_to_root(source_file),
39
- CI::Ext::Test::TAG_SOURCE_START => line_number.to_s
40
- },
50
+ tags: tags,
41
51
  service: datadog_configuration[:service_name]
42
52
  )
43
53
  # Steep type checker doesn't know that we patched Minitest::Test class definition