datadog-ci 1.24.0 → 1.26.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -2
  3. data/ext/datadog_ci_native/ci.c +5 -3
  4. data/ext/datadog_ci_native/datadog_common.c +64 -0
  5. data/ext/datadog_ci_native/datadog_common.h +60 -0
  6. data/ext/datadog_ci_native/datadog_cov.c +13 -65
  7. data/ext/datadog_ci_native/datadog_method_inspect.c +22 -0
  8. data/ext/datadog_ci_native/datadog_method_inspect.h +4 -0
  9. data/ext/datadog_ci_native/imemo_helpers.c +16 -0
  10. data/ext/datadog_ci_native/imemo_helpers.h +32 -0
  11. data/ext/datadog_ci_native/iseq_collector.c +65 -0
  12. data/ext/datadog_ci_native/iseq_collector.h +6 -0
  13. data/ext/datadog_ci_native/ruby_internal.h +48 -0
  14. data/lib/datadog/ci/configuration/components.rb +2 -1
  15. data/lib/datadog/ci/configuration/settings.rb +6 -0
  16. data/lib/datadog/ci/contrib/minitest/helpers.rb +3 -3
  17. data/lib/datadog/ci/contrib/minitest/integration.rb +1 -1
  18. data/lib/datadog/ci/contrib/minitest/parallel_executor_minitest_6.rb +40 -0
  19. data/lib/datadog/ci/contrib/minitest/patcher.rb +11 -1
  20. data/lib/datadog/ci/contrib/minitest/runnable.rb +0 -5
  21. data/lib/datadog/ci/contrib/minitest/runnable_minitest_6.rb +49 -0
  22. data/lib/datadog/ci/contrib/minitest/runner.rb +8 -2
  23. data/lib/datadog/ci/contrib/minitest/test.rb +5 -5
  24. data/lib/datadog/ci/contrib/rspec/example.rb +2 -2
  25. data/lib/datadog/ci/ext/settings.rb +1 -0
  26. data/lib/datadog/ci/source_code/constant_resolver.rb +43 -0
  27. data/lib/datadog/ci/{utils/source_code.rb → source_code/method_inspect.rb} +3 -3
  28. data/lib/datadog/ci/source_code/path_filter.rb +33 -0
  29. data/lib/datadog/ci/source_code/static_dependencies.rb +71 -0
  30. data/lib/datadog/ci/source_code/static_dependencies_extractor.rb +237 -0
  31. data/lib/datadog/ci/test_optimisation/component.rb +34 -4
  32. data/lib/datadog/ci/test_retries/null_component.rb +1 -2
  33. data/lib/datadog/ci/version.rb +2 -2
  34. metadata +18 -5
  35. data/ext/datadog_ci_native/datadog_source_code.c +0 -28
  36. data/ext/datadog_ci_native/datadog_source_code.h +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30bc86afa5d05d656f9abfa7ffb5ff21d6f9a8fc5edeb5924651de4cdc6df10d
4
- data.tar.gz: c846a97a47a74120d9926350360cefa4ca3fcdadea6819337ad2990f7856e267
3
+ metadata.gz: b1f81920de6f72669d918ab0ea703312868d380d069df7d17f131c0d64e6925a
4
+ data.tar.gz: 90b37b85ef042fea0b15987d98fa5cf35aad89c599c54ad6ef01d7151dee1386
5
5
  SHA512:
6
- metadata.gz: 2e2a5b3efa12f1c64176e1751e86ebf803f8c894aba7c53453abb22732e0ed16a6087292538b2e03f0657dc9433ceba9e452f5899a95ace325a004ee12c37fbc
7
- data.tar.gz: 461912b9cb9f406c4fd1cb9975ece506df7e979ed7b373b46c9e0060977f397b2da63095be353351d74b4273ea3cc1a354c12a4ef9dc7bf75a0a5ed065a16fa3
6
+ metadata.gz: 3cd6c023dcd0c239605084ffc9822766ebbde643af95a44bfc2362e8b4a8339cb30ceef649791a1392322de5c0c25ab5d42b80cdbb777ff1f9da1eed717962a7
7
+ data.tar.gz: f5a196eadb710b862e99d59560971ef5dc35bf5ace1a43733558be8bf1f8a467055b61c637d283ec9ed5cd1f511b6c734fb4085e28c518696ed31a0e90fbec14
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.26.0] - 2026-01-09
4
+
5
+ ========== Changelog ==========
6
+ ### Changed
7
+
8
+ * Track constants usage in each Ruby file as a static dependency to enrich Test Impact Analysis data ([#442][])
9
+
10
+ ## [1.25.0] - 2025-12-19
11
+
12
+ ### Added
13
+ * Minitest 6.0 support ([#446][])
14
+ * Support Ruby 4.0 ([#444][])
15
+
3
16
  ## [1.24.0] - 2025-12-15
4
17
 
5
18
  ### Added
@@ -565,7 +578,9 @@ Currently test suite level visibility is not used by our instrumentation: it wil
565
578
 
566
579
  - Ruby versions < 2.7 no longer supported ([#8][])
567
580
 
568
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.24.0...main
581
+ [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.26.0...main
582
+ [1.26.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.25.0...v1.26.0
583
+ [1.25.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.24.0...v1.25.0
569
584
  [1.24.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.3...v1.24.0
570
585
  [1.23.3]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.2...v1.23.3
571
586
  [1.23.2]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.1...v1.23.2
@@ -801,4 +816,7 @@ Currently test suite level visibility is not used by our instrumentation: it wil
801
816
  [#430]: https://github.com/DataDog/datadog-ci-rb/issues/430
802
817
  [#433]: https://github.com/DataDog/datadog-ci-rb/issues/433
803
818
  [#436]: https://github.com/DataDog/datadog-ci-rb/issues/436
804
- [#440]: https://github.com/DataDog/datadog-ci-rb/issues/440
819
+ [#440]: https://github.com/DataDog/datadog-ci-rb/issues/440
820
+ [#442]: https://github.com/DataDog/datadog-ci-rb/issues/442
821
+ [#444]: https://github.com/DataDog/datadog-ci-rb/issues/444
822
+ [#446]: https://github.com/DataDog/datadog-ci-rb/issues/446
@@ -1,10 +1,12 @@
1
1
  #include "datadog_cov.h"
2
- #include "datadog_source_code.h"
2
+ #include "datadog_method_inspect.h"
3
+ #include "iseq_collector.h"
3
4
 
4
5
  void Init_datadog_ci_native(void) {
5
6
  // Coverage::DDCov
6
7
  Init_datadog_cov();
7
8
 
8
- // Utils::SourceCode
9
- Init_datadog_source_code();
9
+ // SourceCode
10
+ Init_datadog_method_inspect();
11
+ Init_iseq_collector();
10
12
  }
@@ -0,0 +1,64 @@
1
+ #include "datadog_common.h"
2
+ #include <ruby.h>
3
+ #include <string.h>
4
+
5
+ bool dd_ci_is_path_included(const char *path, const char *root_path,
6
+ long root_path_len, const char *ignored_path,
7
+ long ignored_path_len) {
8
+ if (strncmp(root_path, path, root_path_len) != 0) {
9
+ return false;
10
+ }
11
+ if (ignored_path_len > 0 &&
12
+ strncmp(ignored_path, path, ignored_path_len) == 0) {
13
+ return false;
14
+ }
15
+ return true;
16
+ }
17
+
18
+ char *dd_ci_ruby_strndup(const char *str, size_t size) {
19
+ char *dup;
20
+
21
+ dup = xmalloc(size + 1);
22
+ memcpy(dup, str, size);
23
+ dup[size] = '\0';
24
+
25
+ return dup;
26
+ }
27
+
28
+ VALUE dd_ci_rescue_nil(VALUE (*function_to_call_safely)(VALUE),
29
+ VALUE function_to_call_safely_arg) {
30
+ int exception_state;
31
+ // rb_protect sets exception_state to non-zero if an exception occurs
32
+ VALUE result = rb_protect(function_to_call_safely,
33
+ function_to_call_safely_arg, &exception_state);
34
+ if (exception_state != 0) {
35
+ rb_set_errinfo(Qnil); // Clear the exception
36
+ return Qnil;
37
+ }
38
+ return result;
39
+ }
40
+
41
+ VALUE dd_ci_get_const_source_location(VALUE const_name_str) {
42
+ return rb_funcall(rb_cObject, rb_intern("const_source_location"), 1,
43
+ const_name_str);
44
+ }
45
+
46
+ VALUE dd_ci_safely_get_const_source_location(VALUE const_name_str) {
47
+ return dd_ci_rescue_nil(dd_ci_get_const_source_location, const_name_str);
48
+ }
49
+
50
+ VALUE dd_ci_resolve_const_to_file(VALUE const_name_str) {
51
+ VALUE source_location =
52
+ dd_ci_safely_get_const_source_location(const_name_str);
53
+ if (NIL_P(source_location) || !RB_TYPE_P(source_location, T_ARRAY) ||
54
+ RARRAY_LEN(source_location) == 0) {
55
+ return Qnil;
56
+ }
57
+
58
+ VALUE filename = RARRAY_AREF(source_location, 0);
59
+ if (NIL_P(filename) || !RB_TYPE_P(filename, T_STRING)) {
60
+ return Qnil;
61
+ }
62
+
63
+ return filename;
64
+ }
@@ -0,0 +1,60 @@
1
+ #ifndef DATADOG_COMMON_H
2
+ #define DATADOG_COMMON_H
3
+
4
+ #include <ruby.h>
5
+ #include <stdbool.h>
6
+
7
+ /* ---- Path filtering ----------------------------------------------------- */
8
+
9
+ /**
10
+ * Check if a file path is under root_path and not under ignored_path.
11
+ * Returns true if the path should be included, false otherwise.
12
+ *
13
+ * @param path The file path to check
14
+ * @param root_path The root path prefix (required)
15
+ * @param root_path_len Length of root_path
16
+ * @param ignored_path Path prefix to exclude (can be NULL)
17
+ * @param ignored_path_len Length of ignored_path (0 if not set)
18
+ */
19
+ bool dd_ci_is_path_included(const char *path, const char *root_path,
20
+ long root_path_len, const char *ignored_path,
21
+ long ignored_path_len);
22
+
23
+ /* ---- Utility functions -------------------------------------------------- */
24
+
25
+ /**
26
+ * Duplicate a string of given size using Ruby's memory allocator.
27
+ * The returned string is null-terminated.
28
+ */
29
+ char *dd_ci_ruby_strndup(const char *str, size_t size);
30
+
31
+ /**
32
+ * Safe exception handling - equivalent to Ruby's "begin/rescue nil".
33
+ * Calls function_to_call_safely with the given argument and returns Qnil
34
+ * if an exception occurs (clearing the exception state).
35
+ */
36
+ VALUE dd_ci_rescue_nil(VALUE (*function_to_call_safely)(VALUE),
37
+ VALUE function_to_call_safely_arg);
38
+
39
+ /**
40
+ * Get source location for a given constant name string.
41
+ * Calls Object.const_source_location(const_name_str).
42
+ * Returns an array [filename, lineno] or nil if not found.
43
+ */
44
+ VALUE dd_ci_get_const_source_location(VALUE const_name_str);
45
+
46
+ /**
47
+ * Safely get source location for a given constant name string.
48
+ * Returns Qnil if an exception occurs (e.g., for C-defined or anonymous
49
+ * classes).
50
+ */
51
+ VALUE dd_ci_safely_get_const_source_location(VALUE const_name_str);
52
+
53
+ /**
54
+ * Resolve a constant name to its source file path.
55
+ * Returns the filename (String) where the constant is defined, or Qnil if not
56
+ * found.
57
+ */
58
+ VALUE dd_ci_resolve_const_to_file(VALUE const_name_str);
59
+
60
+ #endif /* DATADOG_COMMON_H */
@@ -4,6 +4,8 @@
4
4
 
5
5
  #include <stdbool.h>
6
6
 
7
+ #include "datadog_common.h"
8
+
7
9
  // This is a native extension that collects a list of Ruby files that were
8
10
  // executed during the test run. It is used to optimize the test suite by
9
11
  // running only the tests that are affected by the changes.
@@ -16,35 +18,6 @@ enum threading_mode { single, multi };
16
18
  // functions declarations
17
19
  static void on_newobj_event(VALUE tracepoint_data, void *data);
18
20
 
19
- // utility functions
20
- static char *ruby_strndup(const char *str, size_t size) {
21
- char *dup;
22
-
23
- dup = xmalloc(size + 1);
24
- memcpy(dup, str, size);
25
- dup[size] = '\0';
26
-
27
- return dup;
28
- }
29
-
30
- // Equivalent to Ruby "begin/rescue nil" call, where we call a C function and
31
- // swallow the exception if it occurs - const_source_location often fails with
32
- // exceptions for classes that are defined in C or for anonymous classes.
33
- static VALUE rescue_nil(VALUE (*function_to_call_safely)(VALUE),
34
- VALUE function_to_call_safely_arg) {
35
- int exception_state;
36
- // rb_protect sets exception_state to non-zero if an exception occurs
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) {
42
- return Qnil;
43
- }
44
-
45
- return result;
46
- }
47
-
48
21
  static int mark_key_for_gc_i(st_data_t key, st_data_t _value, st_data_t _data) {
49
22
  VALUE klass = (VALUE)key;
50
23
  // mark klass link for GC as non-movable to avoid changing hashtable's keys
@@ -158,18 +131,9 @@ static VALUE dd_cov_allocate(VALUE klass) {
158
131
  // not in the ignored folder) and adds it to the impacted_files hash.
159
132
  static bool record_impacted_file(struct dd_cov_data *dd_cov_data,
160
133
  VALUE filename) {
161
- char *filename_ptr = RSTRING_PTR(filename);
162
- // if the current filename is not located under the root, we skip it
163
- if (strncmp(dd_cov_data->root, filename_ptr, dd_cov_data->root_len) != 0) {
164
- return false;
165
- }
166
-
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) {
134
+ if (!dd_ci_is_path_included(RSTRING_PTR(filename), dd_cov_data->root,
135
+ dd_cov_data->root_len, dd_cov_data->ignored_path,
136
+ dd_cov_data->ignored_path_len)) {
173
137
  return false;
174
138
  }
175
139
 
@@ -211,25 +175,14 @@ static void on_line_event(rb_event_flag_t event, VALUE data, VALUE self, ID id,
211
175
  record_impacted_file(dd_cov_data, filename);
212
176
  }
213
177
 
214
- // Get source location for a given class name
215
- static VALUE get_source_location(VALUE klass_name) {
216
- return rb_funcall(rb_cObject, rb_intern("const_source_location"), 1,
217
- klass_name);
218
- }
219
-
220
- // Get source location for a given class name and swallow any exceptions
221
- static VALUE safely_get_source_location(VALUE klass_name) {
222
- return rescue_nil(get_source_location, klass_name);
223
- }
224
-
225
178
  // Safely get class name, returns Qnil on any error
226
179
  static VALUE safely_get_class_name(VALUE klass) {
227
- return rescue_nil(rb_class_name, klass);
180
+ return dd_ci_rescue_nil(rb_class_name, klass);
228
181
  }
229
182
 
230
183
  // Safely get module ancestors, returns Qnil on any error
231
184
  static VALUE safely_get_mod_ancestors(VALUE klass) {
232
- return rescue_nil(rb_mod_ancestors, klass);
185
+ return dd_ci_rescue_nil(rb_mod_ancestors, klass);
233
186
  }
234
187
 
235
188
  static bool record_impacted_klass(struct dd_cov_data *dd_cov_data,
@@ -239,14 +192,8 @@ static bool record_impacted_klass(struct dd_cov_data *dd_cov_data,
239
192
  return false;
240
193
  }
241
194
 
242
- VALUE source_location = safely_get_source_location(klass_name);
243
- if (source_location == Qnil || !RB_TYPE_P(source_location, T_ARRAY) ||
244
- RARRAY_LEN(source_location) == 0) {
245
- return false;
246
- }
247
-
248
- VALUE filename = RARRAY_AREF(source_location, 0);
249
- if (filename == Qnil || !RB_TYPE_P(filename, T_STRING)) {
195
+ VALUE filename = dd_ci_resolve_const_to_file(klass_name);
196
+ if (filename == Qnil) {
250
197
  return false;
251
198
  }
252
199
 
@@ -349,12 +296,13 @@ static VALUE dd_cov_initialize(int argc, VALUE *argv, VALUE self) {
349
296
 
350
297
  dd_cov_data->threading_mode = threading_mode;
351
298
  dd_cov_data->root_len = RSTRING_LEN(rb_root);
352
- dd_cov_data->root = ruby_strndup(RSTRING_PTR(rb_root), dd_cov_data->root_len);
299
+ dd_cov_data->root =
300
+ dd_ci_ruby_strndup(RSTRING_PTR(rb_root), dd_cov_data->root_len);
353
301
 
354
302
  if (RTEST(rb_ignored_path)) {
355
303
  dd_cov_data->ignored_path_len = RSTRING_LEN(rb_ignored_path);
356
- dd_cov_data->ignored_path = ruby_strndup(RSTRING_PTR(rb_ignored_path),
357
- dd_cov_data->ignored_path_len);
304
+ dd_cov_data->ignored_path = dd_ci_ruby_strndup(
305
+ RSTRING_PTR(rb_ignored_path), dd_cov_data->ignored_path_len);
358
306
  }
359
307
 
360
308
  if (rb_allocation_tracing_enabled == Qtrue) {
@@ -0,0 +1,22 @@
1
+ #include <ruby.h>
2
+
3
+ #include "ruby_internal.h"
4
+
5
+ static VALUE last_line_from_iseq(VALUE self, VALUE iseqw) {
6
+ const rb_iseq_t *iseq = rb_iseqw_to_iseq(iseqw);
7
+
8
+ int line;
9
+ rb_iseq_code_location(iseq, NULL, NULL, &line, NULL);
10
+
11
+ return INT2NUM(line);
12
+ }
13
+
14
+ void Init_datadog_method_inspect(void) {
15
+ VALUE mDatadog = rb_define_module("Datadog");
16
+ VALUE mCI = rb_define_module_under(mDatadog, "CI");
17
+ VALUE mSourceCode = rb_define_module_under(mCI, "SourceCode");
18
+ VALUE mMethodInspect = rb_define_module_under(mSourceCode, "MethodInspect");
19
+
20
+ rb_define_singleton_method(mMethodInspect, "_native_last_line_from_iseq",
21
+ last_line_from_iseq, 1);
22
+ }
@@ -0,0 +1,4 @@
1
+ #pragma once
2
+
3
+ void Init_datadog_method_inspect(void);
4
+
@@ -0,0 +1,16 @@
1
+ #include "imemo_helpers.h"
2
+ #include "ruby_internal.h"
3
+
4
+ int dd_ci_imemo_type(VALUE imemo) {
5
+ return (RBASIC(imemo)->flags >> FL_USHIFT) & DD_CI_IMEMO_MASK;
6
+ }
7
+
8
+ bool dd_ci_imemo_iseq_p(VALUE v) {
9
+ if (!rb_objspace_internal_object_p(v))
10
+ return false;
11
+ if (!RB_TYPE_P(v, T_IMEMO))
12
+ return false;
13
+ if (dd_ci_imemo_type(v) != DD_CI_IMEMO_TYPE_ISEQ)
14
+ return false;
15
+ return true;
16
+ }
@@ -0,0 +1,32 @@
1
+ #ifndef IMEMO_HELPERS_H
2
+ #define IMEMO_HELPERS_H
3
+
4
+ #include <ruby.h>
5
+ #include <stdbool.h>
6
+
7
+ /*
8
+ Here we are using the same trick that debug gem uses here:
9
+
10
+ https://github.com/ruby/debug/blob/master/ext/debug/iseq_collector.c
11
+
12
+ These functions allow us to check if the VALUE is an ISeq object
13
+ */
14
+
15
+ /*
16
+ * IMEMO (internal memo) helpers for working with Ruby's internal objects.
17
+ */
18
+
19
+ #define DD_CI_IMEMO_TYPE_ISEQ 7
20
+ #define DD_CI_IMEMO_MASK 0x0f
21
+
22
+ /**
23
+ * Get the IMEMO type from flags.
24
+ */
25
+ int dd_ci_imemo_type(VALUE imemo);
26
+
27
+ /**
28
+ * Check if a VALUE is an internal ISeq object.
29
+ */
30
+ bool dd_ci_imemo_iseq_p(VALUE v);
31
+
32
+ #endif /* IMEMO_HELPERS_H */
@@ -0,0 +1,65 @@
1
+ #include <ruby.h>
2
+
3
+ #include "imemo_helpers.h"
4
+ #include "ruby_internal.h"
5
+
6
+ /*
7
+ * Callback for rb_objspace_each_objects.
8
+ *
9
+ * The callback receives a range of object slots [vstart, vend) with given
10
+ * stride. Each slot may contain a live object, a freed slot, or garbage.
11
+ * We check each slot for ISeq objects and add them to the result array.
12
+ *
13
+ * See:
14
+ * https://github.com/ruby/ruby/blob/c99670d6683fec770271d35c2ae082514b1abce3/gc.c#L3550
15
+ */
16
+ static int collect_iseqs_callback(void *vstart, void *vend, size_t stride,
17
+ void *data) {
18
+ VALUE iseqs_array = (VALUE)data;
19
+
20
+ for (VALUE v = (VALUE)vstart; v != (VALUE)vend; v += stride) {
21
+ if (dd_ci_imemo_iseq_p(v)) {
22
+ VALUE iseq = rb_iseqw_new((void *)v);
23
+ rb_ary_push(iseqs_array, iseq);
24
+ }
25
+ }
26
+ return 0;
27
+ }
28
+
29
+ /*
30
+ * ISeqCollector.collect_iseqs
31
+ *
32
+ * Walk all live objects in the Ruby object space and collect all
33
+ * instruction sequences (ISeqs) into an array.
34
+ *
35
+ * @return [Array<RubyVM::InstructionSequence>] Array of all live ISeqs
36
+ *
37
+ * NOTE:
38
+ * - Only sees ISeqs that still exist (top-level file ISeqs might be GC'd).
39
+ * Method ISeqs usually survive longer.
40
+ * - The returned ISeqs include all types: method bodies, class bodies,
41
+ * blocks, etc.
42
+
43
+ * It is very similar to iseq_collector from debug gem:
44
+ * https://github.com/ruby/debug/blob/master/ext/debug/iseq_collector.c
45
+ */
46
+
47
+ static VALUE iseq_collector_collect(VALUE self) {
48
+ VALUE iseqs_array = rb_ary_new();
49
+
50
+ rb_objspace_each_objects(collect_iseqs_callback, (void *)iseqs_array);
51
+
52
+ return iseqs_array;
53
+ }
54
+
55
+ /* ---- Module initialization ---------------------------------------------- */
56
+
57
+ void Init_iseq_collector(void) {
58
+ VALUE mDatadog = rb_define_module("Datadog");
59
+ VALUE mCI = rb_define_module_under(mDatadog, "CI");
60
+ VALUE mSourceCode = rb_define_module_under(mCI, "SourceCode");
61
+ VALUE mISeqCollector = rb_define_module_under(mSourceCode, "ISeqCollector");
62
+
63
+ rb_define_singleton_method(mISeqCollector, "collect_iseqs",
64
+ iseq_collector_collect, 0);
65
+ }
@@ -0,0 +1,6 @@
1
+ #ifndef ISEQ_COLLECTOR_H
2
+ #define ISEQ_COLLECTOR_H
3
+
4
+ void Init_iseq_collector(void);
5
+
6
+ #endif /* ISEQ_COLLECTOR_H */
@@ -0,0 +1,48 @@
1
+ #ifndef RUBY_INTERNAL_H
2
+ #define RUBY_INTERNAL_H
3
+
4
+ #include <ruby.h>
5
+
6
+ /*
7
+ * Ruby MRI internal functions and structures.
8
+ *
9
+ * These are not part of Ruby's public C API and are resolved via dynamic
10
+ * linking against libruby. They may change between Ruby versions.
11
+ */
12
+
13
+ /* ---- ISeq structures and functions -------------------------------------- */
14
+
15
+ typedef struct rb_iseq_struct rb_iseq_t;
16
+
17
+ /**
18
+ * Convert an ISeq wrapper VALUE to internal rb_iseq_t pointer.
19
+ */
20
+ const rb_iseq_t *rb_iseqw_to_iseq(VALUE iseqw);
21
+
22
+ /**
23
+ * Get code location (line/column info) from an ISeq.
24
+ */
25
+ void rb_iseq_code_location(const rb_iseq_t *iseq, int *first_lineno,
26
+ int *first_column, int *last_lineno,
27
+ int *last_column);
28
+
29
+ /**
30
+ * Wrap an internal iseq pointer as RubyVM::InstructionSequence.
31
+ */
32
+ VALUE rb_iseqw_new(const void *iseq);
33
+
34
+ /* ---- Object space functions --------------------------------------------- */
35
+
36
+ /**
37
+ * Check if an object is internal (not visible to Ruby code).
38
+ */
39
+ int rb_objspace_internal_object_p(VALUE obj);
40
+
41
+ /**
42
+ * Iterate over all objects in the Ruby object space.
43
+ */
44
+ void rb_objspace_each_objects(int (*callback)(void *start, void *end,
45
+ size_t stride, void *data),
46
+ void *data);
47
+
48
+ #endif /* RUBY_INTERNAL_H */
@@ -192,7 +192,8 @@ module Datadog
192
192
  enabled: settings.ci.enabled && settings.ci.itr_enabled,
193
193
  bundle_location: settings.ci.itr_code_coverage_excluded_bundle_path,
194
194
  use_single_threaded_coverage: settings.ci.itr_code_coverage_use_single_threaded_mode,
195
- use_allocation_tracing: settings.ci.itr_test_impact_analysis_use_allocation_tracing
195
+ use_allocation_tracing: settings.ci.itr_test_impact_analysis_use_allocation_tracing,
196
+ static_dependencies_tracking_enabled: settings.ci.tia_static_dependencies_tracking_enabled
196
197
  )
197
198
  end
198
199
 
@@ -168,6 +168,12 @@ module Datadog
168
168
  o.env CI::Ext::Settings::ENV_TEST_DISCOVERY_OUTPUT_PATH
169
169
  end
170
170
 
171
+ option :tia_static_dependencies_tracking_enabled do |o|
172
+ o.type :bool
173
+ o.env CI::Ext::Settings::ENV_TIA_STATIC_DEPENDENCIES_TRACKING_ENABLED
174
+ o.default false
175
+ end
176
+
171
177
  define_method(:instrument) do |integration_name, options = {}, &block|
172
178
  return unless enabled
173
179
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../source_code/constant_resolver"
4
+
3
5
  module Datadog
4
6
  module CI
5
7
  module Contrib
@@ -66,9 +68,7 @@ module Datadog
66
68
  def self.extract_source_location_from_class(klass)
67
69
  return [] if klass.nil? || klass.name.nil?
68
70
 
69
- klass.const_source_location(klass.name)
70
- rescue
71
- []
71
+ SourceCode::ConstantResolver.safely_get_const_source_location(klass.name) || []
72
72
  end
73
73
  end
74
74
  end
@@ -18,7 +18,7 @@ module Datadog
18
18
 
19
19
  def loaded?
20
20
  !defined?(::Minitest).nil? && !defined?(::Minitest::Runnable).nil? && !defined?(::Minitest::Test).nil? &&
21
- !defined?(::Minitest::CompositeReporter).nil?
21
+ !defined?(::Minitest::CompositeReporter).nil? && !defined?(::Minitest::Parallel::Executor).nil?
22
22
  end
23
23
 
24
24
  def compatible?
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module Contrib
6
+ module Minitest
7
+ # Lifecycle hooks to instrument Minitest::Test
8
+ module ParallelExecutorMinitest6
9
+ def self.included(base)
10
+ base.prepend(InstanceMethods)
11
+ end
12
+
13
+ module InstanceMethods
14
+ def start
15
+ return super unless datadog_configuration[:enabled]
16
+
17
+ @pool = Array.new(size) {
18
+ Thread.new @queue do |queue|
19
+ Thread.current.abort_on_exception = true
20
+ while (job = queue.pop)
21
+ klass, method, reporter = job
22
+ reporter.synchronize { reporter.prerecord klass, method }
23
+ result = ::Minitest.run_one_method(klass, method)
24
+ reporter.synchronize { reporter.record result }
25
+ end
26
+ end
27
+ }
28
+ end
29
+
30
+ private
31
+
32
+ def datadog_configuration
33
+ Datadog.configuration.ci[:minitest]
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -5,6 +5,9 @@ require_relative "reporter"
5
5
  require_relative "test"
6
6
  require_relative "runnable"
7
7
 
8
+ require_relative "runnable_minitest_6"
9
+ require_relative "parallel_executor_minitest_6"
10
+
8
11
  module Datadog
9
12
  module CI
10
13
  module Contrib
@@ -18,8 +21,15 @@ module Datadog
18
21
  def patch
19
22
  # test session start
20
23
  ::Minitest.include(Runner)
24
+
21
25
  # test suites (when not executed concurrently)
22
- ::Minitest::Runnable.include(Runnable)
26
+ if ::Minitest::Runnable.respond_to?(:run_suite)
27
+ ::Minitest::Runnable.include(RunnableMinitest6)
28
+ ::Minitest::Parallel::Executor.include(ParallelExecutorMinitest6)
29
+ else
30
+ ::Minitest::Runnable.include(Runnable)
31
+ end
32
+
23
33
  # tests; test suites (when executed concurrently)
24
34
  ::Minitest::Test.include(Test)
25
35
  # test session finish
@@ -20,7 +20,6 @@ module Datadog
20
20
  return results unless test_suite
21
21
 
22
22
  test_suite.finish
23
-
24
23
  results
25
24
  end
26
25
 
@@ -29,10 +28,6 @@ module Datadog
29
28
  def datadog_configuration
30
29
  Datadog.configuration.ci[:minitest]
31
30
  end
32
-
33
- def test_visibility_component
34
- Datadog.send(:components).test_visibility
35
- end
36
31
  end
37
32
  end
38
33
  end