datadog-ci 1.25.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -2
- data/ext/datadog_ci_native/ci.c +5 -3
- data/ext/datadog_ci_native/datadog_common.c +64 -0
- data/ext/datadog_ci_native/datadog_common.h +60 -0
- data/ext/datadog_ci_native/datadog_cov.c +13 -65
- data/ext/datadog_ci_native/datadog_method_inspect.c +22 -0
- data/ext/datadog_ci_native/datadog_method_inspect.h +4 -0
- data/ext/datadog_ci_native/imemo_helpers.c +16 -0
- data/ext/datadog_ci_native/imemo_helpers.h +32 -0
- data/ext/datadog_ci_native/iseq_collector.c +65 -0
- data/ext/datadog_ci_native/iseq_collector.h +6 -0
- data/ext/datadog_ci_native/ruby_internal.h +48 -0
- data/lib/datadog/ci/code_coverage/component.rb +55 -0
- data/lib/datadog/ci/code_coverage/null_component.rb +24 -0
- data/lib/datadog/ci/code_coverage/transport.rb +66 -0
- data/lib/datadog/ci/configuration/components.rb +19 -2
- data/lib/datadog/ci/configuration/settings.rb +26 -2
- data/lib/datadog/ci/contrib/minitest/helpers.rb +3 -3
- data/lib/datadog/ci/contrib/minitest/parallel_executor_minitest_6.rb +0 -7
- data/lib/datadog/ci/contrib/minitest/test.rb +3 -3
- data/lib/datadog/ci/contrib/rspec/example.rb +50 -10
- data/lib/datadog/ci/contrib/rspec/example_group.rb +63 -31
- data/lib/datadog/ci/contrib/simplecov/ext.rb +2 -0
- data/lib/datadog/ci/contrib/simplecov/patcher.rb +2 -0
- data/lib/datadog/ci/contrib/simplecov/report_uploader.rb +59 -0
- data/lib/datadog/ci/ext/environment/providers/github_actions.rb +65 -2
- data/lib/datadog/ci/ext/environment.rb +10 -0
- data/lib/datadog/ci/ext/settings.rb +3 -0
- data/lib/datadog/ci/ext/telemetry.rb +5 -0
- data/lib/datadog/ci/ext/test.rb +0 -5
- data/lib/datadog/ci/ext/transport.rb +4 -0
- data/lib/datadog/ci/git/cli.rb +59 -1
- data/lib/datadog/ci/remote/component.rb +6 -1
- data/lib/datadog/ci/remote/library_settings.rb +8 -0
- data/lib/datadog/ci/source_code/constant_resolver.rb +43 -0
- data/lib/datadog/ci/{utils/source_code.rb → source_code/method_inspect.rb} +3 -3
- data/lib/datadog/ci/source_code/path_filter.rb +33 -0
- data/lib/datadog/ci/source_code/static_dependencies.rb +71 -0
- data/lib/datadog/ci/source_code/static_dependencies_extractor.rb +237 -0
- data/lib/datadog/ci/test.rb +27 -18
- data/lib/datadog/ci/test_management/component.rb +9 -0
- data/lib/datadog/ci/test_management/null_component.rb +8 -0
- data/lib/datadog/ci/test_optimisation/component.rb +202 -20
- data/lib/datadog/ci/test_optimisation/null_component.rb +19 -5
- data/lib/datadog/ci/test_suite.rb +16 -21
- data/lib/datadog/ci/test_visibility/component.rb +1 -2
- data/lib/datadog/ci/test_visibility/known_tests.rb +59 -6
- data/lib/datadog/ci/transport/api/agentless.rb +8 -1
- data/lib/datadog/ci/transport/api/base.rb +21 -0
- data/lib/datadog/ci/transport/api/builder.rb +5 -1
- data/lib/datadog/ci/transport/api/evp_proxy.rb +8 -0
- data/lib/datadog/ci/version.rb +1 -1
- metadata +19 -4
- data/ext/datadog_ci_native/datadog_source_code.c +0 -28
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 47e2dae93f942112cea718e6772f7c586214d97a4d3fb9d8ecf9643826c4e9a0
|
|
4
|
+
data.tar.gz: 80558ee95a33818f80f937981b9c0533c8e638444ac181a31683ea22d53c7ad7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d442d8921f4ba7fd8ae372544f518edeaac97a6a4b89aa52d2cc8e6ff8035e3131d61104fc4bce7030634970fc2cd52a13d929944ad08497e7abfcf174634ccb
|
|
7
|
+
data.tar.gz: 4f58631b0792099fdff630a65c10f162e3c933ac2e5c32b53addbaebc4533daf4c87ae7112b69abbd4cf113257a4ac1cc58dc59fb2f8b63184ba3f9abb1aec8d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.27.0] - 2026-01-22
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
* Add context-level code coverage tracking for RSpec to improve Test Impact Analysis correctness ([#460][])
|
|
9
|
+
* Add automatic uploading of code coverage reports to Datadog Code Coverage product ([#456][])
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
* Use pagination for known tests API ([#455][]) ([#458][])
|
|
14
|
+
* GitHub Actions: extract numeric job ID for accurate job URLs ([#457][])
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
* RSpec: take into account Test Management disabled tests when deciding to skip context hooks ([#463][])
|
|
19
|
+
* Fix for attempt-to-fix flow: don't mask failures for flaky tests being fixed ([#464][])
|
|
20
|
+
* Pass -c safe.directory to all git commands to fix dubious ownership errors ([#454][])
|
|
21
|
+
* fix: rename DD_TRACE_CI_ENABLED to DD_CIVISIBILITY_ENABLED with backwards compatibility ([#453][])
|
|
22
|
+
|
|
23
|
+
## [1.26.0] - 2026-01-09
|
|
24
|
+
|
|
25
|
+
========== Changelog ==========
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
* Track constants usage in each Ruby file as a static dependency to enrich Test Impact Analysis data ([#442][])
|
|
29
|
+
|
|
3
30
|
## [1.25.0] - 2025-12-19
|
|
4
31
|
|
|
5
32
|
### Added
|
|
@@ -571,7 +598,9 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
|
571
598
|
|
|
572
599
|
- Ruby versions < 2.7 no longer supported ([#8][])
|
|
573
600
|
|
|
574
|
-
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.
|
|
601
|
+
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.27.0...main
|
|
602
|
+
[1.27.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.26.0...v1.27.0
|
|
603
|
+
[1.26.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.25.0...v1.26.0
|
|
575
604
|
[1.25.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.24.0...v1.25.0
|
|
576
605
|
[1.24.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.3...v1.24.0
|
|
577
606
|
[1.23.3]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.2...v1.23.3
|
|
@@ -809,5 +838,15 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
|
809
838
|
[#433]: https://github.com/DataDog/datadog-ci-rb/issues/433
|
|
810
839
|
[#436]: https://github.com/DataDog/datadog-ci-rb/issues/436
|
|
811
840
|
[#440]: https://github.com/DataDog/datadog-ci-rb/issues/440
|
|
841
|
+
[#442]: https://github.com/DataDog/datadog-ci-rb/issues/442
|
|
812
842
|
[#444]: https://github.com/DataDog/datadog-ci-rb/issues/444
|
|
813
|
-
[#446]: https://github.com/DataDog/datadog-ci-rb/issues/446
|
|
843
|
+
[#446]: https://github.com/DataDog/datadog-ci-rb/issues/446
|
|
844
|
+
[#453]: https://github.com/DataDog/datadog-ci-rb/issues/453
|
|
845
|
+
[#454]: https://github.com/DataDog/datadog-ci-rb/issues/454
|
|
846
|
+
[#455]: https://github.com/DataDog/datadog-ci-rb/issues/455
|
|
847
|
+
[#456]: https://github.com/DataDog/datadog-ci-rb/issues/456
|
|
848
|
+
[#457]: https://github.com/DataDog/datadog-ci-rb/issues/457
|
|
849
|
+
[#458]: https://github.com/DataDog/datadog-ci-rb/issues/458
|
|
850
|
+
[#460]: https://github.com/DataDog/datadog-ci-rb/issues/460
|
|
851
|
+
[#463]: https://github.com/DataDog/datadog-ci-rb/issues/463
|
|
852
|
+
[#464]: https://github.com/DataDog/datadog-ci-rb/issues/464
|
data/ext/datadog_ci_native/ci.c
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#include "datadog_cov.h"
|
|
2
|
-
#include "
|
|
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
|
-
//
|
|
9
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
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
|
|
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
|
|
243
|
-
if (
|
|
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 =
|
|
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 =
|
|
357
|
-
|
|
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,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,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 */
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../ext/environment"
|
|
4
|
+
require_relative "transport"
|
|
5
|
+
|
|
6
|
+
module Datadog
|
|
7
|
+
module CI
|
|
8
|
+
module CodeCoverage
|
|
9
|
+
# CodeCoverage component is responsible for uploading code coverage reports
|
|
10
|
+
# to Datadog's Code Coverage product.
|
|
11
|
+
class Component
|
|
12
|
+
COVERAGE_REPORT_TYPE = "coverage_report"
|
|
13
|
+
|
|
14
|
+
attr_reader :enabled
|
|
15
|
+
|
|
16
|
+
def initialize(enabled:, transport:)
|
|
17
|
+
@enabled = enabled
|
|
18
|
+
@transport = transport
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def configure(library_configuration)
|
|
22
|
+
@enabled &&= library_configuration.coverage_report_upload_enabled?
|
|
23
|
+
|
|
24
|
+
Datadog.logger.debug do
|
|
25
|
+
"[#{self.class.name}] Configured with enabled=#{@enabled}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def upload(serialized_report:, format:)
|
|
30
|
+
return unless @enabled
|
|
31
|
+
return if serialized_report.nil?
|
|
32
|
+
|
|
33
|
+
Datadog.logger.debug { "[#{self.class.name}] Uploading coverage report..." }
|
|
34
|
+
|
|
35
|
+
event = build_event(format)
|
|
36
|
+
|
|
37
|
+
@transport.send_coverage_report(event: event, coverage_report: serialized_report)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def shutdown!
|
|
41
|
+
# noop - transport is synchronous
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def build_event(format)
|
|
47
|
+
{
|
|
48
|
+
"type" => COVERAGE_REPORT_TYPE,
|
|
49
|
+
"format" => format
|
|
50
|
+
}.merge(Ext::Environment.tags(ENV))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module CI
|
|
5
|
+
module CodeCoverage
|
|
6
|
+
class NullComponent
|
|
7
|
+
attr_reader :enabled
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@enabled = false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def configure(library_configuration)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def upload(serialized_report:, format:)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def shutdown!
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|