datadog-ci 1.25.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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -1
  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/parallel_executor_minitest_6.rb +0 -7
  18. data/lib/datadog/ci/contrib/minitest/test.rb +2 -2
  19. data/lib/datadog/ci/contrib/rspec/example.rb +2 -2
  20. data/lib/datadog/ci/ext/settings.rb +1 -0
  21. data/lib/datadog/ci/source_code/constant_resolver.rb +43 -0
  22. data/lib/datadog/ci/{utils/source_code.rb → source_code/method_inspect.rb} +3 -3
  23. data/lib/datadog/ci/source_code/path_filter.rb +33 -0
  24. data/lib/datadog/ci/source_code/static_dependencies.rb +71 -0
  25. data/lib/datadog/ci/source_code/static_dependencies_extractor.rb +237 -0
  26. data/lib/datadog/ci/test_optimisation/component.rb +34 -4
  27. data/lib/datadog/ci/version.rb +1 -1
  28. metadata +15 -4
  29. data/ext/datadog_ci_native/datadog_source_code.c +0 -28
  30. 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: e68ff85fe19330eb395ad72da915dbad1bf98c02f5c917402da73b4f5ebab745
4
- data.tar.gz: aa30fff0bb9f8349e3c9caa5d9ff77cbdd57b7c1cf8e61fdd19d98ebcb271711
3
+ metadata.gz: b1f81920de6f72669d918ab0ea703312868d380d069df7d17f131c0d64e6925a
4
+ data.tar.gz: 90b37b85ef042fea0b15987d98fa5cf35aad89c599c54ad6ef01d7151dee1386
5
5
  SHA512:
6
- metadata.gz: '0668a4e35ef63218065e169ab00dfe7e7bfc4439c35f6020611d35a2a625dbdb20d38aa3e7edd3a5cf3769e15bd31077717141029223637cabeb34b172069aa8'
7
- data.tar.gz: 119fef9be7bd8ab89c013fa545dd3d09b8013fa6ef79db438e59c12b8fd1acc42734bc9faa8f0f3ff63d28fec0b3480c7f43e89ca10a5ce83ee7b6e7a26b1422
6
+ metadata.gz: 3cd6c023dcd0c239605084ffc9822766ebbde643af95a44bfc2362e8b4a8339cb30ceef649791a1392322de5c0c25ab5d42b80cdbb777ff1f9da1eed717962a7
7
+ data.tar.gz: f5a196eadb710b862e99d59560971ef5dc35bf5ace1a43733558be8bf1f8a467055b61c637d283ec9ed5cd1f511b6c734fb4085e28c518696ed31a0e90fbec14
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
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
+
3
10
  ## [1.25.0] - 2025-12-19
4
11
 
5
12
  ### Added
@@ -571,7 +578,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil
571
578
 
572
579
  - Ruby versions < 2.7 no longer supported ([#8][])
573
580
 
574
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.25.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
575
583
  [1.25.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.24.0...v1.25.0
576
584
  [1.24.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.3...v1.24.0
577
585
  [1.23.3]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.2...v1.23.3
@@ -809,5 +817,6 @@ Currently test suite level visibility is not used by our instrumentation: it wil
809
817
  [#433]: https://github.com/DataDog/datadog-ci-rb/issues/433
810
818
  [#436]: https://github.com/DataDog/datadog-ci-rb/issues/436
811
819
  [#440]: https://github.com/DataDog/datadog-ci-rb/issues/440
820
+ [#442]: https://github.com/DataDog/datadog-ci-rb/issues/442
812
821
  [#444]: https://github.com/DataDog/datadog-ci-rb/issues/444
813
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
@@ -1,12 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../../ext/test"
4
- require_relative "../../git/local_repository"
5
- require_relative "../../utils/source_code"
6
- require_relative "../instrumentation"
7
- require_relative "ext"
8
- require_relative "helpers"
9
-
10
3
  module Datadog
11
4
  module CI
12
5
  module Contrib
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "../../ext/test"
4
4
  require_relative "../../git/local_repository"
5
- require_relative "../../utils/source_code"
5
+ require_relative "../../source_code/method_inspect"
6
6
  require_relative "../instrumentation"
7
7
  require_relative "ext"
8
8
  require_relative "helpers"
@@ -38,7 +38,7 @@ module Datadog
38
38
  # try to find out where test method starts and ends
39
39
  test_method = method(name)
40
40
  source_file, first_line_number = test_method.source_location
41
- last_line_number = Utils::SourceCode.last_line(test_method)
41
+ last_line_number = SourceCode::MethodInspect.last_line(test_method)
42
42
 
43
43
  tags[CI::Ext::Test::TAG_SOURCE_FILE] = Git::LocalRepository.relative_to_root(source_file) if source_file
44
44
  tags[CI::Ext::Test::TAG_SOURCE_START] = first_line_number.to_s if first_line_number
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "../../ext/test"
4
4
  require_relative "../../git/local_repository"
5
- require_relative "../../utils/source_code"
5
+ require_relative "../../source_code/method_inspect"
6
6
  require_relative "../../utils/test_run"
7
7
  require_relative "../instrumentation"
8
8
  require_relative "ext"
@@ -36,7 +36,7 @@ module Datadog
36
36
  CI::Ext::Test::TAG_PARAMETERS => datadog_test_parameters
37
37
  }
38
38
 
39
- end_line = Utils::SourceCode.last_line(@example_block)
39
+ end_line = SourceCode::MethodInspect.last_line(@example_block)
40
40
  tags[CI::Ext::Test::TAG_SOURCE_END] = end_line.to_s if end_line
41
41
 
42
42
  test_retries_component.with_retries do
@@ -29,6 +29,7 @@ module Datadog
29
29
  ENV_TEST_DISCOVERY_MODE_ENABLED = "DD_TEST_OPTIMIZATION_DISCOVERY_ENABLED"
30
30
  ENV_TEST_DISCOVERY_OUTPUT_PATH = "DD_TEST_OPTIMIZATION_DISCOVERY_FILE"
31
31
  ENV_AUTO_INSTRUMENTATION_PROVIDER = "DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER"
32
+ ENV_TIA_STATIC_DEPENDENCIES_TRACKING_ENABLED = "DD_TEST_OPTIMIZATION_TIA_STATIC_DEPS_COVERAGE_ENABLED"
32
33
 
33
34
  # Source: https://docs.datadoghq.com/getting_started/site/
34
35
  DD_SITE_ALLOWLIST = %w[
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module SourceCode
6
+ # ConstantResolver resolves Ruby constant names to their source file locations.
7
+ #
8
+ # This module uses Object.const_source_location to find where a constant is defined.
9
+ # Constants defined in C extensions or built-in Ruby classes have no source location.
10
+ #
11
+ # This module mirrors the C implementation in datadog_common.c (dd_ci_resolve_const_to_file).
12
+ module ConstantResolver
13
+ # Resolve a constant name to its source file path.
14
+ #
15
+ # @param constant_name [String] The fully qualified constant name (e.g., "Foo::Bar::Baz")
16
+ # @return [String, nil] The absolute file path where the constant is defined, or nil if not found
17
+ def self.resolve_path(constant_name)
18
+ return nil unless constant_name.is_a?(String)
19
+ return nil if constant_name.empty?
20
+
21
+ source_location = safely_get_const_source_location(constant_name)
22
+ return nil unless source_location.is_a?(Array) && !source_location.empty?
23
+
24
+ filename = source_location[0]
25
+ return nil unless filename.is_a?(String)
26
+
27
+ filename
28
+ end
29
+
30
+ # Safely get source location for a constant, returning nil on any exception.
31
+ # This handles cases like anonymous classes, C-defined constants, etc.
32
+ #
33
+ # @param constant_name [String] The constant name to look up
34
+ # @return [Array, nil] The [filename, lineno] array or nil
35
+ def self.safely_get_const_source_location(constant_name)
36
+ Object.const_source_location(constant_name)
37
+ rescue
38
+ nil
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Datadog
4
4
  module CI
5
- module Utils
6
- module SourceCode
5
+ module SourceCode
6
+ module MethodInspect
7
7
  begin
8
8
  require "datadog_ci_native.#{RUBY_VERSION}_#{RUBY_PLATFORM}"
9
9
 
@@ -22,7 +22,7 @@ module Datadog
22
22
  return nil unless iseq.is_a?(RubyVM::InstructionSequence)
23
23
  # steep:ignore:end
24
24
 
25
- # this function is implemented in ext/datadog_ci_native/datadog_source_code.c
25
+ # this function is implemented in ext/datadog_ci_native/datadog_method_inspect.c
26
26
  _native_last_line_from_iseq(iseq)
27
27
  end
28
28
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module SourceCode
6
+ # PathFilter determines whether a file path should be included in test impact analysis.
7
+ #
8
+ # A path is included if:
9
+ # - It starts with root_path (prefix match)
10
+ # - It does NOT start with ignored_path (when ignored_path is set)
11
+ #
12
+ # This module mirrors the C implementation in datadog_common.c (dd_ci_is_path_included).
13
+ module PathFilter
14
+ # Check if a file path should be included in analysis.
15
+ #
16
+ # @param path [String] The file path to check
17
+ # @param root_path [String] The root path prefix (required)
18
+ # @param ignored_path [String, nil] Path prefix to exclude (optional)
19
+ # @return [Boolean] true if the path should be included
20
+ def self.included?(path, root_path, ignored_path = nil)
21
+ return false unless path.is_a?(String) && root_path.is_a?(String)
22
+ return false unless path.start_with?(root_path)
23
+
24
+ if ignored_path.is_a?(String) && !ignored_path.empty?
25
+ return false if path.start_with?(ignored_path)
26
+ end
27
+
28
+ true
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "static_dependencies_extractor"
4
+
5
+ module Datadog
6
+ module CI
7
+ module SourceCode
8
+ # ISeqCollector provides native access to Ruby's object space
9
+ # for collecting instruction sequences (ISeqs).
10
+ #
11
+ # @api private
12
+ module ISeqCollector
13
+ STATIC_DEPENDENCIES_EXTRACTION_AVAILABLE = begin
14
+ # We support Ruby >= 3.2 even though technically it is possible to support 3.1
15
+ # The issue is that Ruby 3.1 and earlier doesn't have opt_getconstant_path YARV instruction
16
+ # which makes it a lot harder to parse fully qualified constant access.
17
+ #
18
+ # See the PR https://github.com/DataDog/datadog-ci-rb/pull/442 for more context
19
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.2")
20
+ require "datadog_ci_native.#{RUBY_VERSION}_#{RUBY_PLATFORM}"
21
+ true
22
+ else
23
+ false
24
+ end
25
+ rescue LoadError
26
+ false
27
+ end
28
+
29
+ # Collect all live ISeqs from the Ruby object space.
30
+ # Falls back to empty array if native extension is not available.
31
+ #
32
+ # @return [Array<RubyVM::InstructionSequence>] Array of all live ISeqs
33
+ def self.collect
34
+ return [] unless STATIC_DEPENDENCIES_EXTRACTION_AVAILABLE
35
+
36
+ collect_iseqs
37
+ end
38
+ end
39
+
40
+ module StaticDependencies
41
+ # Populate the static dependencies map by scanning all live ISeqs.
42
+ #
43
+ # @param root_path [String] Only process files under this path
44
+ # @param ignored_path [String, nil] Exclude files under this path
45
+ # @return [Hash{String => Hash{String => Boolean}}] The dependencies map
46
+ def self.populate!(root_path, ignored_path = nil)
47
+ raise ArgumentError, "root_path must be a String and not nil" if root_path.nil? || !root_path.is_a?(String)
48
+
49
+ extractor = StaticDependenciesExtractor.new(root_path, ignored_path)
50
+
51
+ ISeqCollector.collect.each do |iseq|
52
+ extractor.extract(iseq)
53
+ end
54
+
55
+ @dependencies_map = extractor.dependencies_map
56
+ end
57
+
58
+ # Fetch static dependencies for a given file.
59
+ #
60
+ # @param file [String, nil] The file path to look up
61
+ # @return [Hash{String => Boolean}] Dependencies hash or empty hash
62
+ def self.fetch_static_dependencies(file)
63
+ return {} unless @dependencies_map
64
+ return {} if file.nil?
65
+
66
+ @dependencies_map.fetch(file, {})
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "path_filter"
4
+ require_relative "constant_resolver"
5
+
6
+ module Datadog
7
+ module CI
8
+ module SourceCode
9
+ # StaticDependenciesExtractor extracts static constant dependencies from Ruby bytecode.
10
+ #
11
+ # For each ISeq (compiled Ruby code), it:
12
+ # 1. Extracts the source file path
13
+ # 2. Filters by root_path and ignored_path
14
+ # 3. Scans bytecode for constant references
15
+ # 4. Resolves constants to their source file locations
16
+ # 5. Filters dependency paths by root_path and ignored_path
17
+ #
18
+ # @example
19
+ # extractor = StaticDependenciesExtractor.new("/app", "/app/vendor")
20
+ # iseq = RubyVM::InstructionSequence.of(some_method)
21
+ # extractor.extract(iseq)
22
+ # deps = extractor.dependencies_map
23
+ # # => { "/app/foo.rb" => { "/app/bar.rb" => true } }
24
+ #
25
+ class StaticDependenciesExtractor
26
+ # BytecodeScanner scans Ruby bytecode instructions for constant references.
27
+ #
28
+ # This class traverses the ISeq#to_a representation to find:
29
+ # - :getconstant instructions - simple constant references
30
+ # - :opt_getconstant_path instructions - optimized qualified constant paths
31
+ #
32
+ # @api private
33
+ class BytecodeScanner
34
+ # Scan an ISeq body for constant references.
35
+ #
36
+ # @param body [Array] The ISeq body array (last element of ISeq#to_a)
37
+ # @return [Array<String>] Array of constant name strings found in the bytecode
38
+ def scan(body)
39
+ return [] unless body.is_a?(Array)
40
+
41
+ constants = []
42
+ scan_value(body, constants)
43
+ constants
44
+ end
45
+
46
+ # Build a qualified constant name from an array of symbols.
47
+ # e.g., [:Foo, :Bar, :Baz] -> "Foo::Bar::Baz"
48
+ #
49
+ # @param symbol_array [Array<Symbol>] Array of constant name symbols
50
+ # @return [String] The qualified constant path string
51
+ def build_constant_path(symbol_array)
52
+ symbol_array
53
+ .select { |part| part.is_a?(Symbol) }
54
+ .map(&:to_s)
55
+ .join("::")
56
+ end
57
+
58
+ private
59
+
60
+ # Recursively scan a Ruby value for constant references.
61
+ #
62
+ # @param value [Object] Any Ruby value from the ISeq representation
63
+ # @param constants [Array<String>] Accumulator for found constants
64
+ def scan_value(value, constants)
65
+ case value
66
+ when Array
67
+ scan_array(value, constants)
68
+ when Hash
69
+ scan_hash(value, constants)
70
+ end
71
+ end
72
+
73
+ # Scan an array for instructions and nested values.
74
+ #
75
+ # @param arr [Array] Array to scan
76
+ # @param constants [Array<String>] Accumulator for found constants
77
+ def scan_array(arr, constants)
78
+ handle_instruction(arr, constants)
79
+
80
+ arr.each do |elem|
81
+ scan_value(elem, constants)
82
+ end
83
+ end
84
+
85
+ # Scan a hash for constant references in keys and values.
86
+ #
87
+ # @param hash [Hash] Hash to scan
88
+ # @param constants [Array<String>] Accumulator for found constants
89
+ def scan_hash(hash, constants)
90
+ hash.each do |key, val|
91
+ scan_value(key, constants)
92
+ scan_value(val, constants)
93
+ end
94
+ end
95
+
96
+ # Check if an array is a bytecode instruction and handle it.
97
+ # Instructions have the form [:instruction_name, ...args].
98
+ #
99
+ # @param arr [Array] Potential instruction array
100
+ # @param constants [Array<String>] Accumulator for found constants
101
+ def handle_instruction(arr, constants)
102
+ return if arr.size < 2
103
+ return unless arr[0].is_a?(Symbol)
104
+
105
+ case arr[0]
106
+ when :getconstant
107
+ handle_getconstant(arr, constants)
108
+ when :opt_getconstant_path
109
+ handle_opt_getconstant_path(arr, constants)
110
+ end
111
+ end
112
+
113
+ # Handle [:getconstant, :CONST_NAME, ...] instruction.
114
+ #
115
+ # @param instruction [Array] The instruction array
116
+ # @param constants [Array<String>] Accumulator for found constants
117
+ def handle_getconstant(instruction, constants)
118
+ const_name = instruction[1]
119
+ return unless const_name.is_a?(Symbol)
120
+
121
+ constants << const_name.to_s
122
+ end
123
+
124
+ # Handle [:opt_getconstant_path, cache_entry] instruction.
125
+ # The cache entry is an array of symbols: [:Foo, :Bar, :Baz]
126
+ #
127
+ # @param instruction [Array] The instruction array
128
+ # @param constants [Array<String>] Accumulator for found constants
129
+ def handle_opt_getconstant_path(instruction, constants)
130
+ cache_entry = instruction[1]
131
+ return unless cache_entry.is_a?(Array) && !cache_entry.empty?
132
+
133
+ path = build_constant_path(cache_entry)
134
+ constants << path unless path.empty?
135
+ end
136
+ end
137
+
138
+ # @return [Hash{String => Hash{String => Boolean}}] Map of source file to dependencies
139
+ attr_reader :dependencies_map
140
+
141
+ # @return [String] Root path prefix for filtering
142
+ attr_reader :root_path
143
+
144
+ # @return [String, nil] Ignored path prefix for exclusion
145
+ attr_reader :ignored_path
146
+
147
+ # Initialize a new StaticDependenciesExtractor.
148
+ #
149
+ # @param root_path [String] Only process files under this path
150
+ # @param ignored_path [String, nil] Exclude files under this path
151
+ def initialize(root_path, ignored_path = nil)
152
+ @root_path = root_path
153
+ @ignored_path = ignored_path
154
+ @dependencies_map = {}
155
+ @bytecode_scanner = BytecodeScanner.new
156
+ end
157
+
158
+ # Extract constant dependencies from an ISeq.
159
+ #
160
+ # @param iseq [RubyVM::InstructionSequence] The instruction sequence to process
161
+ # @return [void]
162
+ def extract(iseq)
163
+ path = extract_absolute_path(iseq)
164
+ return if path.nil?
165
+ return unless PathFilter.included?(path, root_path, ignored_path)
166
+
167
+ body = extract_body(iseq)
168
+ return if body.nil?
169
+
170
+ deps = get_or_create_deps(path)
171
+ constant_names = @bytecode_scanner.scan(body)
172
+
173
+ constant_names.each do |const_name|
174
+ resolve_and_store_dependency(const_name, deps)
175
+ end
176
+ end
177
+
178
+ # Reset the dependencies map.
179
+ #
180
+ # @return [void]
181
+ def reset
182
+ @dependencies_map = {}
183
+ end
184
+
185
+ private
186
+
187
+ # Extract the absolute path from an ISeq.
188
+ # Returns nil for eval'd code (which has no file).
189
+ #
190
+ # @param iseq [RubyVM::InstructionSequence]
191
+ # @return [String, nil]
192
+ def extract_absolute_path(iseq)
193
+ path = iseq.absolute_path
194
+ return nil unless path.is_a?(String)
195
+
196
+ path
197
+ end
198
+
199
+ # Extract the body array from an ISeq's SimpleDataFormat.
200
+ # The body is the last element of ISeq#to_a.
201
+ #
202
+ # @param iseq [RubyVM::InstructionSequence]
203
+ # @return [Array, nil]
204
+ def extract_body(iseq)
205
+ arr = iseq.to_a
206
+ return nil unless arr.is_a?(Array) && !arr.empty?
207
+
208
+ body = arr[-1]
209
+ return nil unless body.is_a?(Array)
210
+
211
+ body
212
+ end
213
+
214
+ # Get or create dependencies hash for a given path.
215
+ #
216
+ # @param path [String]
217
+ # @return [Hash{String => Boolean}]
218
+ def get_or_create_deps(path)
219
+ @dependencies_map[path] ||= {}
220
+ end
221
+
222
+ # Resolve a constant name to its file and store in dependencies.
223
+ #
224
+ # @param constant_name [String]
225
+ # @param deps [Hash{String => Boolean}]
226
+ # @return [void]
227
+ def resolve_and_store_dependency(constant_name, deps)
228
+ file_path = ConstantResolver.resolve_path(constant_name)
229
+ return if file_path.nil?
230
+ return unless PathFilter.included?(file_path, root_path, ignored_path)
231
+
232
+ deps[file_path] = true
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
@@ -10,6 +10,8 @@ require_relative "../ext/dd_test"
10
10
 
11
11
  require_relative "../git/local_repository"
12
12
 
13
+ require_relative "../source_code/static_dependencies"
14
+
13
15
  require_relative "../utils/parsing"
14
16
  require_relative "../utils/stateful"
15
17
  require_relative "../utils/telemetry"
@@ -40,7 +42,8 @@ module Datadog
40
42
  enabled: false,
41
43
  bundle_location: nil,
42
44
  use_single_threaded_coverage: false,
43
- use_allocation_tracing: true
45
+ use_allocation_tracing: true,
46
+ static_dependencies_tracking_enabled: false
44
47
  )
45
48
  @enabled = enabled
46
49
  @api = api
@@ -54,6 +57,7 @@ module Datadog
54
57
  end
55
58
  @use_single_threaded_coverage = use_single_threaded_coverage
56
59
  @use_allocation_tracing = use_allocation_tracing
60
+ @static_dependencies_tracking_enabled = static_dependencies_tracking_enabled
57
61
 
58
62
  @test_skipping_enabled = false
59
63
  @code_coverage_enabled = false
@@ -82,7 +86,11 @@ module Datadog
82
86
  # we skip tests, not suites
83
87
  test_session.set_tag(Ext::Test::TAG_ITR_TEST_SKIPPING_TYPE, Ext::Test::ITR_TEST_SKIPPING_MODE)
84
88
 
85
- load_datadog_cov! if @code_coverage_enabled
89
+ if @code_coverage_enabled
90
+ load_datadog_cov!
91
+
92
+ populate_static_dependencies_map!
93
+ end
86
94
 
87
95
  # Load component state first, and if successful, skip fetching skippable tests
88
96
  # Also try to restore from DDTest cache if available
@@ -131,11 +139,14 @@ module Datadog
131
139
  return
132
140
  end
133
141
 
142
+ # cucumber's gherkin files are not covered by the code coverage collector - we add them here explicitly
134
143
  test_source_file = test.source_file
135
-
136
- # cucumber's gherkin files are not covered by the code coverage collector
137
144
  ensure_test_source_covered(test_source_file, coverage) unless test_source_file.nil?
138
145
 
146
+ # if we have static dependencies tracking enabled then we can make the coverage
147
+ # more precise by fetching which files we depend on based on constants usage
148
+ enrich_coverage_with_static_dependencies(coverage)
149
+
139
150
  Telemetry.code_coverage_files(coverage.size)
140
151
 
141
152
  event = Coverage::Event.new(
@@ -323,6 +334,25 @@ module Datadog
323
334
  @code_coverage_enabled = false
324
335
  end
325
336
 
337
+ def populate_static_dependencies_map!
338
+ return unless @code_coverage_enabled
339
+ return unless @static_dependencies_tracking_enabled
340
+
341
+ Datadog::CI::SourceCode::StaticDependencies.populate!(Git::LocalRepository.root, @bundle_location)
342
+ end
343
+
344
+ def enrich_coverage_with_static_dependencies(coverage)
345
+ return unless @static_dependencies_tracking_enabled
346
+
347
+ static_dependencies_map = {}
348
+ coverage.keys.each do |file|
349
+ static_dependencies_map.merge!(
350
+ Datadog::CI::SourceCode::StaticDependencies.fetch_static_dependencies(file)
351
+ )
352
+ end
353
+ coverage.merge!(static_dependencies_map)
354
+ end
355
+
326
356
  def ensure_test_source_covered(test_source_file, coverage)
327
357
  absolute_test_source_file_path = File.join(Git::LocalRepository.root, test_source_file)
328
358
  return if coverage.key?(absolute_test_source_file_path)
@@ -4,7 +4,7 @@ module Datadog
4
4
  module CI
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 25
7
+ MINOR = 26
8
8
  PATCH = 0
9
9
  PRE = nil
10
10
  BUILD = nil
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog-ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.25.0
4
+ version: 1.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
@@ -72,11 +72,18 @@ files:
72
72
  - README.md
73
73
  - exe/ddcirb
74
74
  - ext/datadog_ci_native/ci.c
75
+ - ext/datadog_ci_native/datadog_common.c
76
+ - ext/datadog_ci_native/datadog_common.h
75
77
  - ext/datadog_ci_native/datadog_cov.c
76
78
  - ext/datadog_ci_native/datadog_cov.h
77
- - ext/datadog_ci_native/datadog_source_code.c
78
- - ext/datadog_ci_native/datadog_source_code.h
79
+ - ext/datadog_ci_native/datadog_method_inspect.c
80
+ - ext/datadog_ci_native/datadog_method_inspect.h
79
81
  - ext/datadog_ci_native/extconf.rb
82
+ - ext/datadog_ci_native/imemo_helpers.c
83
+ - ext/datadog_ci_native/imemo_helpers.h
84
+ - ext/datadog_ci_native/iseq_collector.c
85
+ - ext/datadog_ci_native/iseq_collector.h
86
+ - ext/datadog_ci_native/ruby_internal.h
80
87
  - lib/datadog/ci.rb
81
88
  - lib/datadog/ci/async_writer.rb
82
89
  - lib/datadog/ci/auto_instrument.rb
@@ -223,6 +230,11 @@ files:
223
230
  - lib/datadog/ci/remote/library_settings_client.rb
224
231
  - lib/datadog/ci/remote/null_component.rb
225
232
  - lib/datadog/ci/remote/slow_test_retries.rb
233
+ - lib/datadog/ci/source_code/constant_resolver.rb
234
+ - lib/datadog/ci/source_code/method_inspect.rb
235
+ - lib/datadog/ci/source_code/path_filter.rb
236
+ - lib/datadog/ci/source_code/static_dependencies.rb
237
+ - lib/datadog/ci/source_code/static_dependencies_extractor.rb
226
238
  - lib/datadog/ci/span.rb
227
239
  - lib/datadog/ci/test.rb
228
240
  - lib/datadog/ci/test_discovery/component.rb
@@ -293,7 +305,6 @@ files:
293
305
  - lib/datadog/ci/utils/git.rb
294
306
  - lib/datadog/ci/utils/parsing.rb
295
307
  - lib/datadog/ci/utils/rum.rb
296
- - lib/datadog/ci/utils/source_code.rb
297
308
  - lib/datadog/ci/utils/stateful.rb
298
309
  - lib/datadog/ci/utils/telemetry.rb
299
310
  - lib/datadog/ci/utils/test_run.rb
@@ -1,28 +0,0 @@
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
- }
@@ -1,3 +0,0 @@
1
- #pragma once
2
-
3
- void Init_datadog_source_code(void);