datadog-ci 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|