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