datadog-ci 1.1.0 → 1.2.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 +9 -2
- data/ext/datadog_cov/datadog_cov.c +259 -67
- data/lib/datadog/ci/configuration/components.rb +120 -78
- data/lib/datadog/ci/configuration/settings.rb +6 -0
- data/lib/datadog/ci/ext/settings.rb +1 -0
- data/lib/datadog/ci/span.rb +3 -3
- data/lib/datadog/ci/test.rb +1 -1
- data/lib/datadog/ci/test_module.rb +1 -1
- data/lib/datadog/ci/{itr/runner.rb → test_optimisation/component.rb} +13 -10
- data/lib/datadog/ci/{itr → test_optimisation}/coverage/ddcov.rb +1 -1
- data/lib/datadog/ci/{itr → test_optimisation}/coverage/event.rb +1 -1
- data/lib/datadog/ci/{itr → test_optimisation}/coverage/transport.rb +1 -1
- data/lib/datadog/ci/{itr → test_optimisation}/coverage/writer.rb +1 -1
- data/lib/datadog/ci/{itr → test_optimisation}/skippable.rb +1 -1
- data/lib/datadog/ci/test_session.rb +1 -1
- data/lib/datadog/ci/test_suite.rb +1 -1
- data/lib/datadog/ci/test_visibility/{recorder.rb → component.rb} +10 -10
- data/lib/datadog/ci/test_visibility/{null_recorder.rb → null_component.rb} +6 -4
- data/lib/datadog/ci/test_visibility/transport.rb +1 -1
- data/lib/datadog/ci/version.rb +1 -1
- data/lib/datadog/ci.rb +15 -15
- metadata +10 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba0975da7283a037f3ea9c70954f2868cf387a0e86608882d2ebefdd097e0cf7
|
4
|
+
data.tar.gz: 7842e2e30802411616d987bcbae9be620befa27ba90206a733646a469225f835
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89bce02785793b2c1f5b5438f6c9cfbf63d3ff4cb065788b4884063341bdc23c289e29715f846ce24e5a4ab939c9cdeb05e755a216e92cee6f20b9954462b05f
|
7
|
+
data.tar.gz: 6adac19cff9d74fc081d22654a8b0db93152c8c5b6706fee82263e6f219edd96bb473f9595c85418a904110bfcde430b7ddcabd034279cb45d24374a55c5b170
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.2.0] - 2024-07-16
|
4
|
+
|
5
|
+
### Changed
|
6
|
+
* Expand test impact analysis with allocation tracing ([#197][])
|
7
|
+
|
3
8
|
## [1.1.0] - 2024-07-01
|
4
9
|
|
5
10
|
### Added
|
@@ -272,7 +277,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
272
277
|
|
273
278
|
- Ruby versions < 2.7 no longer supported ([#8][])
|
274
279
|
|
275
|
-
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.
|
280
|
+
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.2.0...main
|
281
|
+
[1.2.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.1.0...v1.2.0
|
276
282
|
[1.1.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.1...v1.1.0
|
277
283
|
[1.0.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0...v1.0.1
|
278
284
|
[1.0.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0.beta6...v1.0.0
|
@@ -384,4 +390,5 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
384
390
|
[#185]: https://github.com/DataDog/datadog-ci-rb/issues/185
|
385
391
|
[#189]: https://github.com/DataDog/datadog-ci-rb/issues/189
|
386
392
|
[#190]: https://github.com/DataDog/datadog-ci-rb/issues/190
|
387
|
-
[#193]: https://github.com/DataDog/datadog-ci-rb/issues/193
|
393
|
+
[#193]: https://github.com/DataDog/datadog-ci-rb/issues/193
|
394
|
+
[#197]: https://github.com/DataDog/datadog-ci-rb/issues/197
|
@@ -1,13 +1,26 @@
|
|
1
1
|
#include <ruby.h>
|
2
2
|
#include <ruby/debug.h>
|
3
|
+
#include <ruby/st.h>
|
4
|
+
|
5
|
+
#include <stdbool.h>
|
6
|
+
|
7
|
+
// This is a native extension that collects a list of Ruby files that were executed during the test run.
|
8
|
+
// It is used to optimize the test suite by running only the tests that are affected by the changes.
|
3
9
|
|
4
10
|
#define PROFILE_FRAMES_BUFFER_SIZE 1
|
5
11
|
|
6
12
|
// threading modes
|
7
|
-
|
8
|
-
|
13
|
+
enum threading_mode
|
14
|
+
{
|
15
|
+
single,
|
16
|
+
multi
|
17
|
+
};
|
18
|
+
|
19
|
+
// functions declarations
|
20
|
+
static void on_newobj_event(VALUE tracepoint_data, void *data);
|
9
21
|
|
10
|
-
|
22
|
+
// utility functions
|
23
|
+
static char *ruby_strndup(const char *str, size_t size)
|
11
24
|
{
|
12
25
|
char *dup;
|
13
26
|
|
@@ -18,30 +31,84 @@ char *ruby_strndup(const char *str, size_t size)
|
|
18
31
|
return dup;
|
19
32
|
}
|
20
33
|
|
34
|
+
// Equivalent to Ruby "begin/rescue nil" call, where we call a C function and
|
35
|
+
// swallow the exception if it occurs - const_source_location often fails with
|
36
|
+
// exceptions for classes that are defined in C or for anonymous classes.
|
37
|
+
static VALUE rescue_nil(VALUE (*function_to_call_safely)(VALUE), VALUE function_to_call_safely_arg)
|
38
|
+
{
|
39
|
+
int exception_state;
|
40
|
+
// rb_protect sets exception_state to non-zero if an exception occurs
|
41
|
+
// see https://github.com/ruby/ruby/blob/3219ecf4f659908674f534491d8934ba54e1143d/include/ruby/internal/intern/proc.h#L349
|
42
|
+
VALUE result = rb_protect(function_to_call_safely, function_to_call_safely_arg, &exception_state);
|
43
|
+
if (exception_state != 0)
|
44
|
+
{
|
45
|
+
return Qnil;
|
46
|
+
}
|
47
|
+
|
48
|
+
return result;
|
49
|
+
}
|
50
|
+
|
51
|
+
static int mark_key_for_gc_i(st_data_t key, st_data_t _value, st_data_t _data)
|
52
|
+
{
|
53
|
+
VALUE klass = (VALUE)key;
|
54
|
+
// mark klass link for GC as non-movable to avoid changing hashtable's keys
|
55
|
+
rb_gc_mark(klass);
|
56
|
+
return ST_CONTINUE;
|
57
|
+
}
|
58
|
+
|
21
59
|
// Data structure
|
22
60
|
struct dd_cov_data
|
23
61
|
{
|
62
|
+
// Ruby hash with filenames impacted by the test.
|
63
|
+
VALUE impacted_files;
|
64
|
+
|
65
|
+
// Root is the path to the root folder of the project under test.
|
66
|
+
// Files located outside of the root are ignored.
|
24
67
|
char *root;
|
25
68
|
long root_len;
|
26
69
|
|
70
|
+
// Ignored path contains path to the folder where bundled gems are located if
|
71
|
+
// gems are installed in the project folder.
|
27
72
|
char *ignored_path;
|
28
73
|
long ignored_path_len;
|
29
74
|
|
30
|
-
|
31
|
-
|
75
|
+
// Line tracepoint optimisation: cache last seen filename pointer to avoid
|
76
|
+
// unnecessary string comparison if we stay in the same file.
|
32
77
|
uintptr_t last_filename_ptr;
|
33
78
|
|
79
|
+
// Line tracepoint can work in two modes: single threaded and multi threaded
|
80
|
+
//
|
81
|
+
// In single threaded mode line tracepoint will only cover the thread that started the coverage.
|
82
|
+
// This mode is useful for testing frameworks that run tests in multiple threads.
|
83
|
+
// Do not use single threaded mode for Rails applications unless you know that you
|
84
|
+
// don't run any background threads.
|
85
|
+
//
|
86
|
+
// In multi threaded mode line tracepoint will cover all threads. This mode is enabled by default
|
87
|
+
// and is recommended for most applications.
|
88
|
+
enum threading_mode threading_mode;
|
34
89
|
// for single threaded mode: thread that is being covered
|
35
90
|
VALUE th_covered;
|
36
91
|
|
37
|
-
|
92
|
+
// Allocation tracing is used to track test impact for objects that do not
|
93
|
+
// contain any methods that could be covered by line tracepoint.
|
94
|
+
//
|
95
|
+
// Allocation tracing works only in multi threaded mode.
|
96
|
+
VALUE object_allocation_tracepoint;
|
97
|
+
st_table *klasses_table; // { (VALUE) -> int } hashmap with class names that were covered by allocation during the test run
|
38
98
|
};
|
39
99
|
|
40
100
|
static void dd_cov_mark(void *ptr)
|
41
101
|
{
|
42
102
|
struct dd_cov_data *dd_cov_data = ptr;
|
43
|
-
rb_gc_mark_movable(dd_cov_data->
|
103
|
+
rb_gc_mark_movable(dd_cov_data->impacted_files);
|
44
104
|
rb_gc_mark_movable(dd_cov_data->th_covered);
|
105
|
+
rb_gc_mark_movable(dd_cov_data->object_allocation_tracepoint);
|
106
|
+
|
107
|
+
// if GC starts withing dd_cov_allocate() call, klasses_table might not be initialized yet
|
108
|
+
if (dd_cov_data->klasses_table != NULL)
|
109
|
+
{
|
110
|
+
st_foreach(dd_cov_data->klasses_table, mark_key_for_gc_i, 0);
|
111
|
+
}
|
45
112
|
}
|
46
113
|
|
47
114
|
static void dd_cov_free(void *ptr)
|
@@ -49,17 +116,20 @@ static void dd_cov_free(void *ptr)
|
|
49
116
|
struct dd_cov_data *dd_cov_data = ptr;
|
50
117
|
xfree(dd_cov_data->root);
|
51
118
|
xfree(dd_cov_data->ignored_path);
|
119
|
+
st_free_table(dd_cov_data->klasses_table);
|
52
120
|
xfree(dd_cov_data);
|
53
121
|
}
|
54
122
|
|
55
123
|
static void dd_cov_compact(void *ptr)
|
56
124
|
{
|
57
125
|
struct dd_cov_data *dd_cov_data = ptr;
|
58
|
-
dd_cov_data->
|
126
|
+
dd_cov_data->impacted_files = rb_gc_location(dd_cov_data->impacted_files);
|
59
127
|
dd_cov_data->th_covered = rb_gc_location(dd_cov_data->th_covered);
|
128
|
+
dd_cov_data->object_allocation_tracepoint = rb_gc_location(dd_cov_data->object_allocation_tracepoint);
|
129
|
+
// keys for dd_cov_data->klasses_table are not moved by GC, so we don't need to update them
|
60
130
|
}
|
61
131
|
|
62
|
-
const rb_data_type_t dd_cov_data_type = {
|
132
|
+
static const rb_data_type_t dd_cov_data_type = {
|
63
133
|
.wrap_struct_name = "dd_cov",
|
64
134
|
.function = {
|
65
135
|
.dmark = dd_cov_mark,
|
@@ -71,64 +141,48 @@ const rb_data_type_t dd_cov_data_type = {
|
|
71
141
|
static VALUE dd_cov_allocate(VALUE klass)
|
72
142
|
{
|
73
143
|
struct dd_cov_data *dd_cov_data;
|
74
|
-
VALUE
|
144
|
+
VALUE dd_cov = TypedData_Make_Struct(klass, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
|
75
145
|
|
76
|
-
dd_cov_data->
|
146
|
+
dd_cov_data->impacted_files = rb_hash_new();
|
77
147
|
dd_cov_data->root = NULL;
|
78
148
|
dd_cov_data->root_len = 0;
|
79
149
|
dd_cov_data->ignored_path = NULL;
|
80
150
|
dd_cov_data->ignored_path_len = 0;
|
81
151
|
dd_cov_data->last_filename_ptr = 0;
|
82
|
-
dd_cov_data->threading_mode =
|
152
|
+
dd_cov_data->threading_mode = multi;
|
83
153
|
|
84
|
-
|
85
|
-
|
154
|
+
dd_cov_data->object_allocation_tracepoint = Qnil;
|
155
|
+
// numtable type is needed to store VALUE as a key
|
156
|
+
dd_cov_data->klasses_table = st_init_numtable();
|
86
157
|
|
87
|
-
|
88
|
-
|
89
|
-
{
|
90
|
-
VALUE opt;
|
158
|
+
return dd_cov;
|
159
|
+
}
|
91
160
|
|
92
|
-
|
93
|
-
VALUE rb_root = rb_hash_lookup(opt, ID2SYM(rb_intern("root")));
|
94
|
-
if (!RTEST(rb_root))
|
95
|
-
{
|
96
|
-
rb_raise(rb_eArgError, "root is required");
|
97
|
-
}
|
98
|
-
VALUE rb_ignored_path = rb_hash_lookup(opt, ID2SYM(rb_intern("ignored_path")));
|
161
|
+
// Helper functions (available in C only)
|
99
162
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
{
|
108
|
-
threading_mode = SINGLE_THREADED_COVERAGE_MODE;
|
109
|
-
}
|
110
|
-
else
|
163
|
+
// Checks if the filename is located under the root folder of the project (but not
|
164
|
+
// in the ignored folder) and adds it to the impacted_files hash.
|
165
|
+
static void record_impacted_file(struct dd_cov_data *dd_cov_data, VALUE filename)
|
166
|
+
{
|
167
|
+
char *filename_ptr = RSTRING_PTR(filename);
|
168
|
+
// 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)
|
111
170
|
{
|
112
|
-
|
171
|
+
return;
|
113
172
|
}
|
114
173
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
dd_cov_data->threading_mode = threading_mode;
|
119
|
-
dd_cov_data->root_len = RSTRING_LEN(rb_root);
|
120
|
-
dd_cov_data->root = ruby_strndup(RSTRING_PTR(rb_root), dd_cov_data->root_len);
|
121
|
-
|
122
|
-
if (RTEST(rb_ignored_path))
|
174
|
+
// if ignored_path is provided and the current filename is located under the ignored_path, we skip it too
|
175
|
+
// this is useful for ignoring bundled gems location
|
176
|
+
if (dd_cov_data->ignored_path_len != 0 && strncmp(dd_cov_data->ignored_path, filename_ptr, dd_cov_data->ignored_path_len) == 0)
|
123
177
|
{
|
124
|
-
|
125
|
-
dd_cov_data->ignored_path = ruby_strndup(RSTRING_PTR(rb_ignored_path), dd_cov_data->ignored_path_len);
|
178
|
+
return;
|
126
179
|
}
|
127
180
|
|
128
|
-
|
181
|
+
rb_hash_aset(dd_cov_data->impacted_files, filename, Qtrue);
|
129
182
|
}
|
130
183
|
|
131
|
-
|
184
|
+
// Executed on RUBY_EVENT_LINE event and captures the filename from rb_profile_frames.
|
185
|
+
static void on_line_event(rb_event_flag_t event, VALUE data, VALUE self, ID id, VALUE klass)
|
132
186
|
{
|
133
187
|
struct dd_cov_data *dd_cov_data;
|
134
188
|
TypedData_Get_Struct(data, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
|
@@ -161,54 +215,182 @@ static void dd_cov_update_coverage(rb_event_flag_t event, VALUE data, VALUE self
|
|
161
215
|
return;
|
162
216
|
}
|
163
217
|
|
164
|
-
|
165
|
-
|
166
|
-
|
218
|
+
record_impacted_file(dd_cov_data, filename);
|
219
|
+
}
|
220
|
+
|
221
|
+
// Get source location for a given class name
|
222
|
+
static VALUE get_source_location(VALUE klass_name)
|
223
|
+
{
|
224
|
+
return rb_funcall(rb_cObject, rb_intern("const_source_location"), 1, klass_name);
|
225
|
+
}
|
226
|
+
|
227
|
+
// Get source location for a given class name and swallow any exceptions
|
228
|
+
static VALUE safely_get_source_location(VALUE klass_name)
|
229
|
+
{
|
230
|
+
return rescue_nil(get_source_location, klass_name);
|
231
|
+
}
|
232
|
+
|
233
|
+
// This function is called for each class that was instantiated during the test run.
|
234
|
+
static int process_instantiated_klass(st_data_t key, st_data_t _value, st_data_t data)
|
235
|
+
{
|
236
|
+
VALUE klass = (VALUE)key;
|
237
|
+
struct dd_cov_data *dd_cov_data = (struct dd_cov_data *)data;
|
238
|
+
|
239
|
+
VALUE klass_name = rb_class_name(klass);
|
240
|
+
if (klass_name == Qnil)
|
241
|
+
{
|
242
|
+
return ST_CONTINUE;
|
243
|
+
}
|
244
|
+
|
245
|
+
VALUE source_location = safely_get_source_location(klass_name);
|
246
|
+
if (source_location == Qnil || RARRAY_LEN(source_location) == 0)
|
247
|
+
{
|
248
|
+
return ST_CONTINUE;
|
249
|
+
}
|
250
|
+
|
251
|
+
VALUE filename = RARRAY_AREF(source_location, 0);
|
252
|
+
if (filename == Qnil)
|
253
|
+
{
|
254
|
+
return ST_CONTINUE;
|
255
|
+
}
|
256
|
+
|
257
|
+
record_impacted_file(dd_cov_data, filename);
|
258
|
+
return ST_CONTINUE;
|
259
|
+
}
|
260
|
+
|
261
|
+
// Executed on RUBY_INTERNAL_EVENT_NEWOBJ event and captures the source file for the
|
262
|
+
// allocated object's class.
|
263
|
+
static void on_newobj_event(VALUE tracepoint_data, void *data)
|
264
|
+
{
|
265
|
+
rb_trace_arg_t *tracearg = rb_tracearg_from_tracepoint(tracepoint_data);
|
266
|
+
VALUE new_object = rb_tracearg_object(tracearg);
|
267
|
+
|
268
|
+
// To keep things fast and practical, we only care about objects that extend
|
269
|
+
// either Object or Struct.
|
270
|
+
enum ruby_value_type type = rb_type(new_object);
|
271
|
+
if (type != RUBY_T_OBJECT && type != RUBY_T_STRUCT)
|
167
272
|
{
|
168
273
|
return;
|
169
274
|
}
|
170
275
|
|
171
|
-
|
172
|
-
|
173
|
-
|
276
|
+
VALUE klass = rb_class_of(new_object);
|
277
|
+
if (klass == Qnil || klass == 0)
|
278
|
+
{
|
279
|
+
return;
|
280
|
+
}
|
281
|
+
// Skip anonymous classes starting with "#<Class".
|
282
|
+
// it allows us to skip the source location lookup that will always fail
|
283
|
+
//
|
284
|
+
// rb_mod_name returns nil for anonymous classes
|
285
|
+
if (rb_mod_name(klass) == Qnil)
|
174
286
|
{
|
175
287
|
return;
|
176
288
|
}
|
177
289
|
|
178
|
-
|
290
|
+
struct dd_cov_data *dd_cov_data = (struct dd_cov_data *)data;
|
291
|
+
|
292
|
+
// We use VALUE directly as a key for the hashmap
|
293
|
+
// Ruby itself does it too:
|
294
|
+
// https://github.com/ruby/ruby/blob/94b87084a689a3bc732dcaee744508a708223d6c/ext/objspace/object_tracing.c#L113
|
295
|
+
st_insert(dd_cov_data->klasses_table, (st_data_t)klass, 1);
|
179
296
|
}
|
180
297
|
|
181
|
-
|
298
|
+
// DDCov instance methods available in Ruby
|
299
|
+
static VALUE dd_cov_initialize(int argc, VALUE *argv, VALUE self)
|
182
300
|
{
|
301
|
+
VALUE opt;
|
302
|
+
|
303
|
+
rb_scan_args(argc, argv, "10", &opt);
|
304
|
+
VALUE rb_root = rb_hash_lookup(opt, ID2SYM(rb_intern("root")));
|
305
|
+
if (!RTEST(rb_root))
|
306
|
+
{
|
307
|
+
rb_raise(rb_eArgError, "root is required");
|
308
|
+
}
|
309
|
+
VALUE rb_ignored_path = rb_hash_lookup(opt, ID2SYM(rb_intern("ignored_path")));
|
310
|
+
|
311
|
+
VALUE rb_threading_mode = rb_hash_lookup(opt, ID2SYM(rb_intern("threading_mode")));
|
312
|
+
enum threading_mode threading_mode;
|
313
|
+
if (rb_threading_mode == ID2SYM(rb_intern("multi")))
|
314
|
+
{
|
315
|
+
threading_mode = multi;
|
316
|
+
}
|
317
|
+
else if (rb_threading_mode == ID2SYM(rb_intern("single")))
|
318
|
+
{
|
319
|
+
threading_mode = single;
|
320
|
+
}
|
321
|
+
else
|
322
|
+
{
|
323
|
+
rb_raise(rb_eArgError, "threading mode is invalid");
|
324
|
+
}
|
325
|
+
|
326
|
+
VALUE rb_allocation_tracing_enabled = rb_hash_lookup(opt, ID2SYM(rb_intern("use_allocation_tracing")));
|
327
|
+
if (rb_allocation_tracing_enabled == Qtrue && threading_mode == single)
|
328
|
+
{
|
329
|
+
rb_raise(rb_eArgError, "allocation tracing is not supported in single threaded mode");
|
330
|
+
}
|
183
331
|
|
184
332
|
struct dd_cov_data *dd_cov_data;
|
185
333
|
TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
|
186
334
|
|
335
|
+
dd_cov_data->threading_mode = threading_mode;
|
336
|
+
dd_cov_data->root_len = RSTRING_LEN(rb_root);
|
337
|
+
dd_cov_data->root = ruby_strndup(RSTRING_PTR(rb_root), dd_cov_data->root_len);
|
338
|
+
|
339
|
+
if (RTEST(rb_ignored_path))
|
340
|
+
{
|
341
|
+
dd_cov_data->ignored_path_len = RSTRING_LEN(rb_ignored_path);
|
342
|
+
dd_cov_data->ignored_path = ruby_strndup(RSTRING_PTR(rb_ignored_path), dd_cov_data->ignored_path_len);
|
343
|
+
}
|
344
|
+
|
345
|
+
if (rb_allocation_tracing_enabled == Qtrue)
|
346
|
+
{
|
347
|
+
dd_cov_data->object_allocation_tracepoint = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_NEWOBJ, on_newobj_event, (void *)dd_cov_data);
|
348
|
+
}
|
349
|
+
|
350
|
+
return Qnil;
|
351
|
+
}
|
352
|
+
|
353
|
+
// starts test impact collection, executed before the start of each test
|
354
|
+
static VALUE dd_cov_start(VALUE self)
|
355
|
+
{
|
356
|
+
struct dd_cov_data *dd_cov_data;
|
357
|
+
TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
|
358
|
+
|
187
359
|
if (dd_cov_data->root_len == 0)
|
188
360
|
{
|
189
361
|
rb_raise(rb_eRuntimeError, "root is required");
|
190
362
|
}
|
191
363
|
|
192
|
-
|
364
|
+
// add line tracepoint
|
365
|
+
if (dd_cov_data->threading_mode == single)
|
193
366
|
{
|
194
367
|
VALUE thval = rb_thread_current();
|
195
|
-
rb_thread_add_event_hook(thval,
|
368
|
+
rb_thread_add_event_hook(thval, on_line_event, RUBY_EVENT_LINE, self);
|
196
369
|
dd_cov_data->th_covered = thval;
|
197
370
|
}
|
198
371
|
else
|
199
372
|
{
|
200
|
-
rb_add_event_hook(
|
373
|
+
rb_add_event_hook(on_line_event, RUBY_EVENT_LINE, self);
|
374
|
+
}
|
375
|
+
|
376
|
+
// add object allocation tracepoint
|
377
|
+
if (dd_cov_data->object_allocation_tracepoint != Qnil)
|
378
|
+
{
|
379
|
+
rb_tracepoint_enable(dd_cov_data->object_allocation_tracepoint);
|
201
380
|
}
|
202
381
|
|
203
382
|
return self;
|
204
383
|
}
|
205
384
|
|
385
|
+
// stops test impact collection, executed after the end of each test
|
386
|
+
// returns the hash with impacted files and resets the internal state
|
206
387
|
static VALUE dd_cov_stop(VALUE self)
|
207
388
|
{
|
208
389
|
struct dd_cov_data *dd_cov_data;
|
209
390
|
TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
|
210
391
|
|
211
|
-
|
392
|
+
// stop line tracepoint
|
393
|
+
if (dd_cov_data->threading_mode == single)
|
212
394
|
{
|
213
395
|
VALUE thval = rb_thread_current();
|
214
396
|
if (!rb_equal(thval, dd_cov_data->th_covered))
|
@@ -216,17 +398,27 @@ static VALUE dd_cov_stop(VALUE self)
|
|
216
398
|
rb_raise(rb_eRuntimeError, "Coverage was not started by this thread");
|
217
399
|
}
|
218
400
|
|
219
|
-
rb_thread_remove_event_hook(dd_cov_data->th_covered,
|
401
|
+
rb_thread_remove_event_hook(dd_cov_data->th_covered, on_line_event);
|
220
402
|
dd_cov_data->th_covered = Qnil;
|
221
403
|
}
|
222
404
|
else
|
223
405
|
{
|
224
|
-
rb_remove_event_hook(
|
406
|
+
rb_remove_event_hook(on_line_event);
|
225
407
|
}
|
226
408
|
|
227
|
-
|
409
|
+
// stop object allocation tracepoint
|
410
|
+
if (dd_cov_data->object_allocation_tracepoint != Qnil)
|
411
|
+
{
|
412
|
+
rb_tracepoint_disable(dd_cov_data->object_allocation_tracepoint);
|
413
|
+
}
|
414
|
+
|
415
|
+
// process classes covered by allocation tracing
|
416
|
+
st_foreach(dd_cov_data->klasses_table, process_instantiated_klass, (st_data_t)dd_cov_data);
|
417
|
+
st_clear(dd_cov_data->klasses_table);
|
418
|
+
|
419
|
+
VALUE res = dd_cov_data->impacted_files;
|
228
420
|
|
229
|
-
dd_cov_data->
|
421
|
+
dd_cov_data->impacted_files = rb_hash_new();
|
230
422
|
dd_cov_data->last_filename_ptr = 0;
|
231
423
|
|
232
424
|
return res;
|
@@ -236,8 +428,8 @@ void Init_datadog_cov(void)
|
|
236
428
|
{
|
237
429
|
VALUE mDatadog = rb_define_module("Datadog");
|
238
430
|
VALUE mCI = rb_define_module_under(mDatadog, "CI");
|
239
|
-
VALUE
|
240
|
-
VALUE mCoverage = rb_define_module_under(
|
431
|
+
VALUE mTestOptimisation = rb_define_module_under(mCI, "TestOptimisation");
|
432
|
+
VALUE mCoverage = rb_define_module_under(mTestOptimisation, "Coverage");
|
241
433
|
VALUE cDatadogCov = rb_define_class_under(mCoverage, "DDCov", rb_cObject);
|
242
434
|
|
243
435
|
rb_define_alloc_func(cDatadogCov, dd_cov_allocate);
|
@@ -2,12 +2,12 @@
|
|
2
2
|
|
3
3
|
require_relative "../ext/settings"
|
4
4
|
require_relative "../git/tree_uploader"
|
5
|
-
require_relative "../
|
6
|
-
require_relative "../
|
7
|
-
require_relative "../
|
5
|
+
require_relative "../test_optimisation/component"
|
6
|
+
require_relative "../test_optimisation/coverage/transport"
|
7
|
+
require_relative "../test_optimisation/coverage/writer"
|
8
|
+
require_relative "../test_visibility/component"
|
8
9
|
require_relative "../test_visibility/flush"
|
9
|
-
require_relative "../test_visibility/
|
10
|
-
require_relative "../test_visibility/null_recorder"
|
10
|
+
require_relative "../test_visibility/null_component"
|
11
11
|
require_relative "../test_visibility/serializers/factories/test_level"
|
12
12
|
require_relative "../test_visibility/serializers/factories/test_suite_level"
|
13
13
|
require_relative "../test_visibility/transport"
|
@@ -21,15 +21,15 @@ module Datadog
|
|
21
21
|
module Configuration
|
22
22
|
# Adds CI behavior to Datadog trace components
|
23
23
|
module Components
|
24
|
-
attr_reader :
|
24
|
+
attr_reader :test_visibility, :test_optimisation
|
25
25
|
|
26
26
|
def initialize(settings)
|
27
|
+
@test_optimisation = nil
|
28
|
+
@test_visibility = TestVisibility::NullComponent.new
|
29
|
+
|
27
30
|
# Activate CI mode if enabled
|
28
31
|
if settings.ci.enabled
|
29
32
|
activate_ci!(settings)
|
30
|
-
else
|
31
|
-
@itr = nil
|
32
|
-
@ci_recorder = TestVisibility::NullRecorder.new
|
33
33
|
end
|
34
34
|
|
35
35
|
super
|
@@ -38,8 +38,8 @@ module Datadog
|
|
38
38
|
def shutdown!(replacement = nil)
|
39
39
|
super
|
40
40
|
|
41
|
-
@
|
42
|
-
@
|
41
|
+
@test_visibility&.shutdown!
|
42
|
+
@test_optimisation&.shutdown!
|
43
43
|
end
|
44
44
|
|
45
45
|
def activate_ci!(settings)
|
@@ -53,22 +53,21 @@ module Datadog
|
|
53
53
|
return
|
54
54
|
end
|
55
55
|
|
56
|
-
#
|
56
|
+
# Builds test visibility API layer in agentless or EvP proxy mode
|
57
|
+
test_visibility_api = build_test_visibility_api(settings)
|
58
|
+
# bail out early if api is misconfigured
|
59
|
+
return unless settings.ci.enabled
|
60
|
+
|
61
|
+
# Configure datadog gem for test visibility mode
|
62
|
+
|
57
63
|
# Deactivate telemetry
|
58
64
|
settings.telemetry.enabled = false
|
59
65
|
|
60
|
-
#
|
66
|
+
# Test visibility uses its own remote settings
|
61
67
|
settings.remote.enabled = false
|
62
68
|
|
63
|
-
#
|
64
|
-
|
65
|
-
settings.tracing.trace_id_128_bit_generation_enabled = false
|
66
|
-
|
67
|
-
# Activate underlying tracing test mode
|
68
|
-
settings.tracing.test_mode.enabled = true
|
69
|
-
|
70
|
-
# Choose user defined TraceFlush or default to CI TraceFlush
|
71
|
-
settings.tracing.test_mode.trace_flush = settings.ci.trace_flush || CI::TestVisibility::Flush::Partial.new
|
69
|
+
# startup logs are useless for test visibility and create noise
|
70
|
+
settings.diagnostics.startup_logs.enabled = false
|
72
71
|
|
73
72
|
# When timecop is present, Time.now is mocked and .now_without_mock_time is added on Time to
|
74
73
|
# get the current time without the mock.
|
@@ -81,76 +80,70 @@ module Datadog
|
|
81
80
|
end
|
82
81
|
end
|
83
82
|
|
84
|
-
#
|
85
|
-
settings.diagnostics.startup_logs.enabled = false
|
83
|
+
# Configure Datadog::Tracing module
|
86
84
|
|
87
|
-
#
|
88
|
-
|
89
|
-
|
90
|
-
test_visibility_api = build_test_visibility_api(settings)
|
85
|
+
# No need not use 128-bit trace ids for test visibility,
|
86
|
+
# they are used for OTEL compatibility in Datadog tracer
|
87
|
+
settings.tracing.trace_id_128_bit_generation_enabled = false
|
91
88
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
)
|
89
|
+
# Activate underlying tracing test mode with async worker
|
90
|
+
settings.tracing.test_mode.enabled = true
|
91
|
+
settings.tracing.test_mode.async = true
|
92
|
+
settings.tracing.test_mode.trace_flush = settings.ci.trace_flush || CI::TestVisibility::Flush::Partial.new
|
97
93
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
)
|
104
|
-
writer_options[:shutdown_timeout] = 60
|
105
|
-
writer_options[:buffer_size] = 10_000
|
94
|
+
trace_writer_options = settings.ci.writer_options
|
95
|
+
trace_writer_options[:shutdown_timeout] = 60
|
96
|
+
trace_writer_options[:buffer_size] = 10_000
|
97
|
+
tracing_transport = build_tracing_transport(settings, test_visibility_api)
|
98
|
+
trace_writer_options[:transport] = tracing_transport if tracing_transport
|
106
99
|
|
107
|
-
|
108
|
-
else
|
109
|
-
# only legacy APM protocol is supported, so no test suite level visibility
|
110
|
-
settings.ci.force_test_level_visibility = true
|
100
|
+
settings.tracing.test_mode.writer_options = trace_writer_options
|
111
101
|
|
112
|
-
|
113
|
-
|
114
|
-
end
|
102
|
+
# @type ivar @test_optimisation: Datadog::CI::TestOptimisation::Component
|
103
|
+
@test_optimisation = build_test_optimisation(settings, test_visibility_api)
|
115
104
|
|
116
|
-
|
105
|
+
@test_visibility = TestVisibility::Component.new(
|
106
|
+
test_optimisation: @test_optimisation,
|
107
|
+
test_suite_level_visibility_enabled: !settings.ci.force_test_level_visibility,
|
108
|
+
remote_settings_api: build_remote_settings_client(settings, test_visibility_api),
|
109
|
+
git_tree_upload_worker: build_git_upload_worker(settings, test_visibility_api)
|
110
|
+
)
|
111
|
+
end
|
117
112
|
|
118
|
-
|
113
|
+
def build_test_optimisation(settings, test_visibility_api)
|
114
|
+
if settings.ci.itr_code_coverage_use_single_threaded_mode &&
|
115
|
+
settings.ci.itr_test_impact_analysis_use_allocation_tracing
|
116
|
+
Datadog.logger.warn(
|
117
|
+
"Intelligent test runner: Single threaded coverage mode is incompatible with allocation tracing. " \
|
118
|
+
"Allocation tracing will be disabled. It means that test impact analysis will not be able to detect " \
|
119
|
+
"instantiations of objects in your code, which is important for ActiveRecord models. " \
|
120
|
+
"Please add your app/model folder to the list of tracked files or disable single threaded coverage mode."
|
121
|
+
)
|
119
122
|
|
120
|
-
|
121
|
-
|
122
|
-
dd_env: settings.env,
|
123
|
-
config_tags: custom_configuration_tags
|
124
|
-
)
|
123
|
+
settings.ci.itr_test_impact_analysis_use_allocation_tracing = false
|
124
|
+
end
|
125
125
|
|
126
|
-
|
126
|
+
if RUBY_VERSION.start_with?("3.2.") && RUBY_VERSION < "3.2.3" &&
|
127
|
+
settings.ci.itr_test_impact_analysis_use_allocation_tracing
|
128
|
+
Datadog.logger.warn(
|
129
|
+
"Intelligent test runner: Allocation tracing is not supported in Ruby versions 3.2.0, 3.2.1 and 3.2.2 and will be forcibly " \
|
130
|
+
"disabled. This is due to a VM bug that can lead to crashes (https://bugs.ruby-lang.org/issues/19482). " \
|
131
|
+
"Please update your Ruby version or add your app/model folder to the list of tracked files." \
|
132
|
+
"Set env variable DD_CIVISIBILITY_ITR_TEST_IMPACT_ANALYSIS_USE_ALLOCATION_TRACING to 0 to disable this warning."
|
133
|
+
)
|
134
|
+
settings.ci.itr_test_impact_analysis_use_allocation_tracing = false
|
135
|
+
end
|
136
|
+
|
137
|
+
TestOptimisation::Component.new(
|
127
138
|
api: test_visibility_api,
|
128
139
|
dd_env: settings.env,
|
129
|
-
config_tags:
|
130
|
-
coverage_writer:
|
140
|
+
config_tags: custom_configuration(settings),
|
141
|
+
coverage_writer: build_coverage_writer(settings, test_visibility_api),
|
131
142
|
enabled: settings.ci.enabled && settings.ci.itr_enabled,
|
132
143
|
bundle_location: settings.ci.itr_code_coverage_excluded_bundle_path,
|
133
|
-
use_single_threaded_coverage: settings.ci.itr_code_coverage_use_single_threaded_mode
|
134
|
-
|
135
|
-
|
136
|
-
git_tree_uploader = Git::TreeUploader.new(api: test_visibility_api)
|
137
|
-
git_tree_upload_worker = if settings.ci.git_metadata_upload_enabled
|
138
|
-
Worker.new do |repository_url|
|
139
|
-
git_tree_uploader.call(repository_url)
|
140
|
-
end
|
141
|
-
else
|
142
|
-
DummyWorker.new
|
143
|
-
end
|
144
|
-
|
145
|
-
# CI visibility recorder global instance
|
146
|
-
@ci_recorder = TestVisibility::Recorder.new(
|
147
|
-
test_suite_level_visibility_enabled: !settings.ci.force_test_level_visibility,
|
148
|
-
itr: itr,
|
149
|
-
remote_settings_api: remote_settings_api,
|
150
|
-
git_tree_upload_worker: git_tree_upload_worker
|
144
|
+
use_single_threaded_coverage: settings.ci.itr_code_coverage_use_single_threaded_mode,
|
145
|
+
use_allocation_tracing: settings.ci.itr_test_impact_analysis_use_allocation_tracing
|
151
146
|
)
|
152
|
-
|
153
|
-
@itr = itr
|
154
147
|
end
|
155
148
|
|
156
149
|
def build_test_visibility_api(settings)
|
@@ -179,12 +172,61 @@ module Datadog
|
|
179
172
|
Datadog.logger.debug(
|
180
173
|
"Old agent version detected, no evp_proxy support. Forcing test level visibility mode"
|
181
174
|
)
|
175
|
+
|
176
|
+
# only legacy APM protocol is supported, so no test suite level visibility
|
177
|
+
settings.ci.force_test_level_visibility = true
|
178
|
+
|
179
|
+
# ITR is not supported with APM protocol
|
180
|
+
settings.ci.itr_enabled = false
|
182
181
|
end
|
183
182
|
end
|
184
183
|
|
185
184
|
api
|
186
185
|
end
|
187
186
|
|
187
|
+
def build_tracing_transport(settings, api)
|
188
|
+
return nil if api.nil?
|
189
|
+
|
190
|
+
TestVisibility::Transport.new(
|
191
|
+
api: api,
|
192
|
+
serializers_factory: serializers_factory(settings),
|
193
|
+
dd_env: settings.env
|
194
|
+
)
|
195
|
+
end
|
196
|
+
|
197
|
+
def build_coverage_writer(settings, api)
|
198
|
+
return nil if api.nil?
|
199
|
+
|
200
|
+
TestOptimisation::Coverage::Writer.new(
|
201
|
+
transport: TestOptimisation::Coverage::Transport.new(api: api)
|
202
|
+
)
|
203
|
+
end
|
204
|
+
|
205
|
+
def build_git_upload_worker(settings, api)
|
206
|
+
if settings.ci.git_metadata_upload_enabled
|
207
|
+
git_tree_uploader = Git::TreeUploader.new(api: api)
|
208
|
+
Worker.new do |repository_url|
|
209
|
+
git_tree_uploader.call(repository_url)
|
210
|
+
end
|
211
|
+
else
|
212
|
+
DummyWorker.new
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def build_remote_settings_client(settings, api)
|
217
|
+
Transport::RemoteSettingsApi.new(
|
218
|
+
api: api,
|
219
|
+
dd_env: settings.env,
|
220
|
+
config_tags: custom_configuration(settings)
|
221
|
+
)
|
222
|
+
end
|
223
|
+
|
224
|
+
# fetch custom tags provided by the user in DD_TAGS env var
|
225
|
+
# with prefix test.configuration.
|
226
|
+
def custom_configuration(settings)
|
227
|
+
@custom_configuration ||= Utils::TestRun.custom_configuration(settings.tags)
|
228
|
+
end
|
229
|
+
|
188
230
|
def serializers_factory(settings)
|
189
231
|
if settings.ci.force_test_level_visibility
|
190
232
|
TestVisibility::Serializers::Factories::TestLevel
|
@@ -82,6 +82,12 @@ module Datadog
|
|
82
82
|
o.default false
|
83
83
|
end
|
84
84
|
|
85
|
+
option :itr_test_impact_analysis_use_allocation_tracing do |o|
|
86
|
+
o.type :bool
|
87
|
+
o.env CI::Ext::Settings::ENV_ITR_TEST_IMPACT_ANALYSIS_USE_ALLOCATION_TRACING
|
88
|
+
o.default true
|
89
|
+
end
|
90
|
+
|
85
91
|
define_method(:instrument) do |integration_name, options = {}, &block|
|
86
92
|
return unless enabled
|
87
93
|
|
@@ -14,6 +14,7 @@ module Datadog
|
|
14
14
|
ENV_GIT_METADATA_UPLOAD_ENABLED = "DD_CIVISIBILITY_GIT_METADATA_UPLOAD_ENABLED"
|
15
15
|
ENV_ITR_CODE_COVERAGE_EXCLUDED_BUNDLE_PATH = "DD_CIVISIBILITY_ITR_CODE_COVERAGE_EXCLUDED_BUNDLE_PATH"
|
16
16
|
ENV_ITR_CODE_COVERAGE_USE_SINGLE_THREADED_MODE = "DD_CIVISIBILITY_ITR_CODE_COVERAGE_USE_SINGLE_THREADED_MODE"
|
17
|
+
ENV_ITR_TEST_IMPACT_ANALYSIS_USE_ALLOCATION_TRACING = "DD_CIVISIBILITY_ITR_TEST_IMPACT_ANALYSIS_USE_ALLOCATION_TRACING"
|
17
18
|
|
18
19
|
# Source: https://docs.datadoghq.com/getting_started/site/
|
19
20
|
DD_SITE_ALLOWLIST = %w[
|
data/lib/datadog/ci/span.rb
CHANGED
@@ -202,9 +202,9 @@ module Datadog
|
|
202
202
|
|
203
203
|
private
|
204
204
|
|
205
|
-
# provides access to
|
206
|
-
def
|
207
|
-
Datadog.send(:components).
|
205
|
+
# provides access to the test visibility component for CI models to deactivate themselves
|
206
|
+
def test_visibility
|
207
|
+
Datadog.send(:components).test_visibility
|
208
208
|
end
|
209
209
|
end
|
210
210
|
end
|
data/lib/datadog/ci/test.rb
CHANGED
@@ -16,11 +16,11 @@ require_relative "skippable"
|
|
16
16
|
|
17
17
|
module Datadog
|
18
18
|
module CI
|
19
|
-
module
|
19
|
+
module TestOptimisation
|
20
20
|
# Intelligent test runner implementation
|
21
21
|
# Integrates with backend to provide test impact analysis data and
|
22
22
|
# skip tests that are not impacted by the changes
|
23
|
-
class
|
23
|
+
class Component
|
24
24
|
include Core::Utils::Forking
|
25
25
|
|
26
26
|
attr_reader :correlation_id, :skippable_tests, :skipped_tests_count
|
@@ -32,7 +32,8 @@ module Datadog
|
|
32
32
|
coverage_writer: nil,
|
33
33
|
enabled: false,
|
34
34
|
bundle_location: nil,
|
35
|
-
use_single_threaded_coverage: false
|
35
|
+
use_single_threaded_coverage: false,
|
36
|
+
use_allocation_tracing: true
|
36
37
|
)
|
37
38
|
@enabled = enabled
|
38
39
|
@api = api
|
@@ -45,6 +46,7 @@ module Datadog
|
|
45
46
|
bundle_location
|
46
47
|
end
|
47
48
|
@use_single_threaded_coverage = use_single_threaded_coverage
|
49
|
+
@use_allocation_tracing = use_allocation_tracing
|
48
50
|
|
49
51
|
@test_skipping_enabled = false
|
50
52
|
@code_coverage_enabled = false
|
@@ -57,11 +59,11 @@ module Datadog
|
|
57
59
|
@skipped_tests_count = 0
|
58
60
|
@mutex = Mutex.new
|
59
61
|
|
60
|
-
Datadog.logger.debug("
|
62
|
+
Datadog.logger.debug("TestOptimisation initialized with enabled: #{@enabled}")
|
61
63
|
end
|
62
64
|
|
63
65
|
def configure(remote_configuration, test_session:, git_tree_upload_worker:)
|
64
|
-
Datadog.logger.debug("Configuring
|
66
|
+
Datadog.logger.debug("Configuring TestOptimisation with remote configuration: #{remote_configuration}")
|
65
67
|
|
66
68
|
@enabled = Utils::Parsing.convert_to_bool(
|
67
69
|
remote_configuration.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY, false)
|
@@ -83,7 +85,7 @@ module Datadog
|
|
83
85
|
|
84
86
|
load_datadog_cov! if @code_coverage_enabled
|
85
87
|
|
86
|
-
Datadog.logger.debug("Configured
|
88
|
+
Datadog.logger.debug("Configured TestOptimisation with enabled: #{@enabled}, skipping_tests: #{@test_skipping_enabled}, code_coverage: #{@code_coverage_enabled}")
|
87
89
|
|
88
90
|
fetch_skippable_tests(test_session: test_session, git_tree_upload_worker: git_tree_upload_worker)
|
89
91
|
end
|
@@ -139,7 +141,7 @@ module Datadog
|
|
139
141
|
skippable_test_id = Utils::TestRun.skippable_test_id(test.name, test.test_suite_name, test.parameters)
|
140
142
|
if @skippable_tests.include?(skippable_test_id)
|
141
143
|
if forked?
|
142
|
-
Datadog.logger.warn { "
|
144
|
+
Datadog.logger.warn { "Intelligent test runner is not supported for forking test runners yet" }
|
143
145
|
return
|
144
146
|
end
|
145
147
|
|
@@ -155,7 +157,7 @@ module Datadog
|
|
155
157
|
return if !test.skipped? || !test.skipped_by_itr?
|
156
158
|
|
157
159
|
if forked?
|
158
|
-
Datadog.logger.warn { "
|
160
|
+
Datadog.logger.warn { "Intelligent test runner is not supported for forking test runners yet" }
|
159
161
|
return
|
160
162
|
end
|
161
163
|
|
@@ -167,7 +169,7 @@ module Datadog
|
|
167
169
|
def write_test_session_tags(test_session)
|
168
170
|
return if !enabled?
|
169
171
|
|
170
|
-
Datadog.logger.debug { "Finished
|
172
|
+
Datadog.logger.debug { "Finished optimised session with test skipping enabled: #{@test_skipping_enabled}" }
|
171
173
|
Datadog.logger.debug { "#{@skipped_tests_count} tests were skipped" }
|
172
174
|
|
173
175
|
test_session.set_tag(Ext::Test::TAG_ITR_TESTS_SKIPPED, @skipped_tests_count.positive?.to_s)
|
@@ -189,7 +191,8 @@ module Datadog
|
|
189
191
|
Thread.current[:dd_coverage_collector] ||= Coverage::DDCov.new(
|
190
192
|
root: Git::LocalRepository.root,
|
191
193
|
ignored_path: @bundle_location,
|
192
|
-
threading_mode: code_coverage_mode
|
194
|
+
threading_mode: code_coverage_mode,
|
195
|
+
use_allocation_tracing: @use_allocation_tracing
|
193
196
|
)
|
194
197
|
end
|
195
198
|
|
@@ -28,11 +28,11 @@ module Datadog
|
|
28
28
|
module TestVisibility
|
29
29
|
# Common behavior for CI tests
|
30
30
|
# Note: this class has too many responsibilities and should be split into multiple classes
|
31
|
-
class
|
31
|
+
class Component
|
32
32
|
attr_reader :environment_tags, :test_suite_level_visibility_enabled
|
33
33
|
|
34
34
|
def initialize(
|
35
|
-
|
35
|
+
test_optimisation:,
|
36
36
|
remote_settings_api:,
|
37
37
|
git_tree_upload_worker: DummyWorker.new,
|
38
38
|
test_suite_level_visibility_enabled: false,
|
@@ -46,7 +46,7 @@ module Datadog
|
|
46
46
|
|
47
47
|
@codeowners = codeowners
|
48
48
|
|
49
|
-
@
|
49
|
+
@test_optimisation = test_optimisation
|
50
50
|
@remote_settings_api = remote_settings_api
|
51
51
|
@git_tree_upload_worker = git_tree_upload_worker
|
52
52
|
end
|
@@ -210,7 +210,7 @@ module Datadog
|
|
210
210
|
end
|
211
211
|
|
212
212
|
def itr_enabled?
|
213
|
-
@
|
213
|
+
@test_optimisation.enabled?
|
214
214
|
end
|
215
215
|
|
216
216
|
private
|
@@ -234,7 +234,7 @@ module Datadog
|
|
234
234
|
end
|
235
235
|
end
|
236
236
|
|
237
|
-
@
|
237
|
+
@test_optimisation.configure(
|
238
238
|
remote_configuration.payload,
|
239
239
|
test_session: test_session,
|
240
240
|
git_tree_upload_worker: @git_tree_upload_worker
|
@@ -404,17 +404,17 @@ module Datadog
|
|
404
404
|
|
405
405
|
# TODO: use kind of event system to notify about test finished?
|
406
406
|
def on_test_finished(test)
|
407
|
-
@
|
408
|
-
@
|
407
|
+
@test_optimisation.stop_coverage(test)
|
408
|
+
@test_optimisation.count_skipped_test(test)
|
409
409
|
end
|
410
410
|
|
411
411
|
def on_test_started(test)
|
412
|
-
@
|
413
|
-
@
|
412
|
+
@test_optimisation.mark_if_skippable(test)
|
413
|
+
@test_optimisation.start_coverage(test)
|
414
414
|
end
|
415
415
|
|
416
416
|
def on_test_session_finished(test_session)
|
417
|
-
@
|
417
|
+
@test_optimisation.write_test_session_tags(test_session)
|
418
418
|
end
|
419
419
|
end
|
420
420
|
end
|
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "recorder"
|
4
|
-
|
5
3
|
module Datadog
|
6
4
|
module CI
|
7
5
|
module TestVisibility
|
8
|
-
# Special
|
9
|
-
class
|
6
|
+
# Special test visibility component that does not record anything
|
7
|
+
class NullComponent
|
10
8
|
def start_test_session(service: nil, tags: {})
|
11
9
|
skip_tracing
|
12
10
|
end
|
@@ -45,6 +43,10 @@ module Datadog
|
|
45
43
|
def shutdown!
|
46
44
|
end
|
47
45
|
|
46
|
+
def itr_enabled?
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
48
50
|
private
|
49
51
|
|
50
52
|
def skip_tracing(block = nil)
|
data/lib/datadog/ci/version.rb
CHANGED
data/lib/datadog/ci.rb
CHANGED
@@ -39,7 +39,7 @@ module Datadog
|
|
39
39
|
# @return [Datadog::CI::TestSession] the active, running {Datadog::CI::TestSession}.
|
40
40
|
# @return [nil] if test suite level visibility is disabled or CI mode is disabled.
|
41
41
|
def start_test_session(service: Utils::Configuration.fetch_service_name("test"), tags: {})
|
42
|
-
|
42
|
+
test_visibility.start_test_session(service: service, tags: tags)
|
43
43
|
end
|
44
44
|
|
45
45
|
# The active, unfinished test session.
|
@@ -61,7 +61,7 @@ module Datadog
|
|
61
61
|
# @return [Datadog::CI::TestSession] the active test session
|
62
62
|
# @return [nil] if no test session is active
|
63
63
|
def active_test_session
|
64
|
-
|
64
|
+
test_visibility.active_test_session
|
65
65
|
end
|
66
66
|
|
67
67
|
# Starts a {Datadog::CI::TestModule ci_test_module} that represents a single test module (for most Ruby test frameworks
|
@@ -93,7 +93,7 @@ module Datadog
|
|
93
93
|
# @return [Datadog::CI::TestModule] the active, running {Datadog::CI::TestModule}.
|
94
94
|
# @return [nil] if test suite level visibility is disabled or CI mode is disabled.
|
95
95
|
def start_test_module(test_module_name, service: nil, tags: {})
|
96
|
-
|
96
|
+
test_visibility.start_test_module(test_module_name, service: service, tags: tags)
|
97
97
|
end
|
98
98
|
|
99
99
|
# The active, unfinished test module.
|
@@ -116,7 +116,7 @@ module Datadog
|
|
116
116
|
# @return [Datadog::CI::TestModule] the active test module
|
117
117
|
# @return [nil] if no test module is active
|
118
118
|
def active_test_module
|
119
|
-
|
119
|
+
test_visibility.active_test_module
|
120
120
|
end
|
121
121
|
|
122
122
|
# Starts a {Datadog::CI::TestSuite ci_test_suite} that represents a single test suite.
|
@@ -145,7 +145,7 @@ module Datadog
|
|
145
145
|
# @return [Datadog::CI::TestSuite] the active, running {Datadog::CI::TestSuite}.
|
146
146
|
# @return [nil] if test suite level visibility is disabled or CI mode is disabled.
|
147
147
|
def start_test_suite(test_suite_name, service: nil, tags: {})
|
148
|
-
|
148
|
+
test_visibility.start_test_suite(test_suite_name, service: service, tags: tags)
|
149
149
|
end
|
150
150
|
|
151
151
|
# The active, unfinished test suite.
|
@@ -168,7 +168,7 @@ module Datadog
|
|
168
168
|
# @return [Datadog::CI::TestSuite] the active test suite
|
169
169
|
# @return [nil] if no test suite with given name is active
|
170
170
|
def active_test_suite(test_suite_name)
|
171
|
-
|
171
|
+
test_visibility.active_test_suite(test_suite_name)
|
172
172
|
end
|
173
173
|
|
174
174
|
# Return a {Datadog::CI::Test ci_test} that will trace a test called `test_name`.
|
@@ -222,7 +222,7 @@ module Datadog
|
|
222
222
|
# @yieldparam [Datadog::CI::Test] ci_test the newly created and active [Datadog::CI::Test]
|
223
223
|
# @yieldparam [nil] if CI mode is disabled
|
224
224
|
def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
|
225
|
-
|
225
|
+
test_visibility.trace_test(test_name, test_suite_name, service: service, tags: tags, &block)
|
226
226
|
end
|
227
227
|
|
228
228
|
# Same as {.trace_test} but it does not accept a block.
|
@@ -248,7 +248,7 @@ module Datadog
|
|
248
248
|
# @return [Datadog::CI::Test] the active, unfinished {Datadog::CI::Test}.
|
249
249
|
# @return [nil] if CI mode is disabled.
|
250
250
|
def start_test(test_name, test_suite_name, service: nil, tags: {})
|
251
|
-
|
251
|
+
test_visibility.trace_test(test_name, test_suite_name, service: service, tags: tags)
|
252
252
|
end
|
253
253
|
|
254
254
|
# Trace any custom span inside a test. For example, you could trace:
|
@@ -300,7 +300,7 @@ module Datadog
|
|
300
300
|
)
|
301
301
|
end
|
302
302
|
|
303
|
-
|
303
|
+
test_visibility.trace(span_name, type: type, tags: tags, &block)
|
304
304
|
end
|
305
305
|
|
306
306
|
# The active, unfinished custom (i.e. not test/suite/module/session) span.
|
@@ -326,7 +326,7 @@ module Datadog
|
|
326
326
|
# @return [Datadog::CI::Span] the active span
|
327
327
|
# @return [nil] if no span is active, or if the active span is not a custom span
|
328
328
|
def active_span
|
329
|
-
span =
|
329
|
+
span = test_visibility.active_span
|
330
330
|
span if span && !Ext::AppTypes::CI_SPAN_TYPES.include?(span.type)
|
331
331
|
end
|
332
332
|
|
@@ -352,7 +352,7 @@ module Datadog
|
|
352
352
|
# @return [Datadog::CI::Test] the active test
|
353
353
|
# @return [nil] if no test is active
|
354
354
|
def active_test
|
355
|
-
|
355
|
+
test_visibility.active_test
|
356
356
|
end
|
357
357
|
|
358
358
|
private
|
@@ -361,12 +361,12 @@ module Datadog
|
|
361
361
|
Datadog.send(:components)
|
362
362
|
end
|
363
363
|
|
364
|
-
def
|
365
|
-
components.
|
364
|
+
def test_visibility
|
365
|
+
components.test_visibility
|
366
366
|
end
|
367
367
|
|
368
|
-
def
|
369
|
-
components.
|
368
|
+
def test_optimisation
|
369
|
+
components.test_optimisation
|
370
370
|
end
|
371
371
|
end
|
372
372
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: datadog-ci
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Datadog, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: datadog
|
@@ -134,22 +134,22 @@ files:
|
|
134
134
|
- lib/datadog/ci/git/tree_uploader.rb
|
135
135
|
- lib/datadog/ci/git/upload_packfile.rb
|
136
136
|
- lib/datadog/ci/git/user.rb
|
137
|
-
- lib/datadog/ci/itr/coverage/ddcov.rb
|
138
|
-
- lib/datadog/ci/itr/coverage/event.rb
|
139
|
-
- lib/datadog/ci/itr/coverage/transport.rb
|
140
|
-
- lib/datadog/ci/itr/coverage/writer.rb
|
141
|
-
- lib/datadog/ci/itr/runner.rb
|
142
|
-
- lib/datadog/ci/itr/skippable.rb
|
143
137
|
- lib/datadog/ci/span.rb
|
144
138
|
- lib/datadog/ci/test.rb
|
145
139
|
- lib/datadog/ci/test_module.rb
|
140
|
+
- lib/datadog/ci/test_optimisation/component.rb
|
141
|
+
- lib/datadog/ci/test_optimisation/coverage/ddcov.rb
|
142
|
+
- lib/datadog/ci/test_optimisation/coverage/event.rb
|
143
|
+
- lib/datadog/ci/test_optimisation/coverage/transport.rb
|
144
|
+
- lib/datadog/ci/test_optimisation/coverage/writer.rb
|
145
|
+
- lib/datadog/ci/test_optimisation/skippable.rb
|
146
146
|
- lib/datadog/ci/test_session.rb
|
147
147
|
- lib/datadog/ci/test_suite.rb
|
148
|
+
- lib/datadog/ci/test_visibility/component.rb
|
148
149
|
- lib/datadog/ci/test_visibility/context/global.rb
|
149
150
|
- lib/datadog/ci/test_visibility/context/local.rb
|
150
151
|
- lib/datadog/ci/test_visibility/flush.rb
|
151
|
-
- lib/datadog/ci/test_visibility/
|
152
|
-
- lib/datadog/ci/test_visibility/recorder.rb
|
152
|
+
- lib/datadog/ci/test_visibility/null_component.rb
|
153
153
|
- lib/datadog/ci/test_visibility/serializers/base.rb
|
154
154
|
- lib/datadog/ci/test_visibility/serializers/factories/test_level.rb
|
155
155
|
- lib/datadog/ci/test_visibility/serializers/factories/test_suite_level.rb
|