datadog-ci 1.0.0 → 1.1.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 +18 -2
- data/ext/datadog_cov/datadog_cov.c +115 -85
- data/lib/datadog/ci/configuration/components.rb +3 -2
- data/lib/datadog/ci/configuration/settings.rb +6 -0
- data/lib/datadog/ci/contrib/rspec/example.rb +1 -1
- data/lib/datadog/ci/contrib/rspec/patcher.rb +3 -3
- data/lib/datadog/ci/ext/settings.rb +1 -0
- data/lib/datadog/ci/itr/coverage/writer.rb +3 -1
- data/lib/datadog/ci/itr/runner.rb +11 -2
- data/lib/datadog/ci/transport/adapters/net.rb +138 -0
- data/lib/datadog/ci/transport/api/agentless.rb +2 -2
- data/lib/datadog/ci/transport/api/evp_proxy.rb +1 -1
- data/lib/datadog/ci/transport/http.rb +7 -57
- data/lib/datadog/ci/version.rb +3 -3
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61f6379b9a5ac0c29ff9ee6b1687c1c2add77d6a5031dfd5758eb34a66cc6235
|
4
|
+
data.tar.gz: a795d8d69513925ee5ea6cf113cb54cec3711fe65ae5e928fcdeb8e1454323af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ea51d0baa70ecb4e66fb49712f4f291582ecb04455b2d32ec1328bb8ea3c112c4c25d80e450db542ee4f29408786b09a82b5f94fb1c7ef8c96b98d301e61e9b
|
7
|
+
data.tar.gz: 4f066970c2d9ed93001f35f9096cefcf2a4447a86cf7c1d89435853b1da00389c587924304c7a213e56f58f64b191523d202e34f86da535ded271583881411ac
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.1.0] - 2024-07-01
|
4
|
+
|
5
|
+
### Added
|
6
|
+
* Ignore Webmock automatically when making HTTP calls ([#193][])
|
7
|
+
|
8
|
+
## [1.0.1] - 2024-06-11
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
* multi threaded code coverage support for datadog_cov ([#189][])
|
12
|
+
* code coverage extension fixes and improvements ([#171][])
|
13
|
+
|
3
14
|
## [1.0.0] - 2024-06-06
|
4
15
|
|
5
16
|
|
@@ -261,7 +272,9 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
261
272
|
|
262
273
|
- Ruby versions < 2.7 no longer supported ([#8][])
|
263
274
|
|
264
|
-
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.
|
275
|
+
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.1.0...main
|
276
|
+
[1.1.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.1...v1.1.0
|
277
|
+
[1.0.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0...v1.0.1
|
265
278
|
[1.0.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0.beta6...v1.0.0
|
266
279
|
[1.0.0.beta6]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0.beta5...v1.0.0.beta6
|
267
280
|
[1.0.0.beta5]: https://github.com/DataDog/datadog-ci-rb/compare/v1.0.0.beta4...v1.0.0.beta5
|
@@ -361,6 +374,7 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
361
374
|
[#167]: https://github.com/DataDog/datadog-ci-rb/issues/167
|
362
375
|
[#168]: https://github.com/DataDog/datadog-ci-rb/issues/168
|
363
376
|
[#170]: https://github.com/DataDog/datadog-ci-rb/issues/170
|
377
|
+
[#171]: https://github.com/DataDog/datadog-ci-rb/issues/171
|
364
378
|
[#172]: https://github.com/DataDog/datadog-ci-rb/issues/172
|
365
379
|
[#173]: https://github.com/DataDog/datadog-ci-rb/issues/173
|
366
380
|
[#174]: https://github.com/DataDog/datadog-ci-rb/issues/174
|
@@ -368,4 +382,6 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
368
382
|
[#180]: https://github.com/DataDog/datadog-ci-rb/issues/180
|
369
383
|
[#183]: https://github.com/DataDog/datadog-ci-rb/issues/183
|
370
384
|
[#185]: https://github.com/DataDog/datadog-ci-rb/issues/185
|
371
|
-
[#
|
385
|
+
[#189]: https://github.com/DataDog/datadog-ci-rb/issues/189
|
386
|
+
[#190]: https://github.com/DataDog/datadog-ci-rb/issues/190
|
387
|
+
[#193]: https://github.com/DataDog/datadog-ci-rb/issues/193
|
@@ -1,55 +1,54 @@
|
|
1
1
|
#include <ruby.h>
|
2
2
|
#include <ruby/debug.h>
|
3
3
|
|
4
|
-
|
5
|
-
#define DD_COV_TARGET_FILES 1
|
6
|
-
#define DD_COV_TARGET_LINES 2
|
4
|
+
#define PROFILE_FRAMES_BUFFER_SIZE 1
|
7
5
|
|
8
|
-
|
6
|
+
// threading modes
|
7
|
+
#define SINGLE_THREADED_COVERAGE_MODE 0
|
8
|
+
#define MULTI_THREADED_COVERAGE_MODE 1
|
9
|
+
|
10
|
+
char *ruby_strndup(const char *str, size_t size)
|
9
11
|
{
|
10
|
-
|
11
|
-
{
|
12
|
-
return 0;
|
13
|
-
}
|
12
|
+
char *dup;
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
return 0;
|
19
|
-
}
|
14
|
+
dup = xmalloc(size + 1);
|
15
|
+
memcpy(dup, str, size);
|
16
|
+
dup[size] = '\0';
|
20
17
|
|
21
|
-
|
22
|
-
if (strncmp(c_prefix, str, prefix_len) == 0)
|
23
|
-
{
|
24
|
-
return 1;
|
25
|
-
}
|
26
|
-
else
|
27
|
-
{
|
28
|
-
return 0;
|
29
|
-
}
|
18
|
+
return dup;
|
30
19
|
}
|
31
20
|
|
32
21
|
// Data structure
|
33
22
|
struct dd_cov_data
|
34
23
|
{
|
35
|
-
|
36
|
-
|
37
|
-
|
24
|
+
char *root;
|
25
|
+
long root_len;
|
26
|
+
|
27
|
+
char *ignored_path;
|
28
|
+
long ignored_path_len;
|
29
|
+
|
38
30
|
VALUE coverage;
|
31
|
+
|
32
|
+
uintptr_t last_filename_ptr;
|
33
|
+
|
34
|
+
// for single threaded mode: thread that is being covered
|
35
|
+
VALUE th_covered;
|
36
|
+
|
37
|
+
int threading_mode;
|
39
38
|
};
|
40
39
|
|
41
40
|
static void dd_cov_mark(void *ptr)
|
42
41
|
{
|
43
42
|
struct dd_cov_data *dd_cov_data = ptr;
|
44
43
|
rb_gc_mark_movable(dd_cov_data->coverage);
|
45
|
-
rb_gc_mark_movable(dd_cov_data->
|
46
|
-
rb_gc_mark_movable(dd_cov_data->ignored_path);
|
44
|
+
rb_gc_mark_movable(dd_cov_data->th_covered);
|
47
45
|
}
|
48
46
|
|
49
47
|
static void dd_cov_free(void *ptr)
|
50
48
|
{
|
51
49
|
struct dd_cov_data *dd_cov_data = ptr;
|
52
|
-
|
50
|
+
xfree(dd_cov_data->root);
|
51
|
+
xfree(dd_cov_data->ignored_path);
|
53
52
|
xfree(dd_cov_data);
|
54
53
|
}
|
55
54
|
|
@@ -57,8 +56,7 @@ static void dd_cov_compact(void *ptr)
|
|
57
56
|
{
|
58
57
|
struct dd_cov_data *dd_cov_data = ptr;
|
59
58
|
dd_cov_data->coverage = rb_gc_location(dd_cov_data->coverage);
|
60
|
-
dd_cov_data->
|
61
|
-
dd_cov_data->ignored_path = rb_gc_location(dd_cov_data->ignored_path);
|
59
|
+
dd_cov_data->th_covered = rb_gc_location(dd_cov_data->th_covered);
|
62
60
|
}
|
63
61
|
|
64
62
|
const rb_data_type_t dd_cov_data_type = {
|
@@ -74,10 +72,15 @@ static VALUE dd_cov_allocate(VALUE klass)
|
|
74
72
|
{
|
75
73
|
struct dd_cov_data *dd_cov_data;
|
76
74
|
VALUE obj = TypedData_Make_Struct(klass, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
|
75
|
+
|
77
76
|
dd_cov_data->coverage = rb_hash_new();
|
78
|
-
dd_cov_data->root =
|
79
|
-
dd_cov_data->
|
80
|
-
dd_cov_data->
|
77
|
+
dd_cov_data->root = NULL;
|
78
|
+
dd_cov_data->root_len = 0;
|
79
|
+
dd_cov_data->ignored_path = NULL;
|
80
|
+
dd_cov_data->ignored_path_len = 0;
|
81
|
+
dd_cov_data->last_filename_ptr = 0;
|
82
|
+
dd_cov_data->threading_mode = MULTI_THREADED_COVERAGE_MODE;
|
83
|
+
|
81
84
|
return obj;
|
82
85
|
}
|
83
86
|
|
@@ -85,7 +88,6 @@ static VALUE dd_cov_allocate(VALUE klass)
|
|
85
88
|
static VALUE dd_cov_initialize(int argc, VALUE *argv, VALUE self)
|
86
89
|
{
|
87
90
|
VALUE opt;
|
88
|
-
int mode;
|
89
91
|
|
90
92
|
rb_scan_args(argc, argv, "10", &opt);
|
91
93
|
VALUE rb_root = rb_hash_lookup(opt, ID2SYM(rb_intern("root")));
|
@@ -93,113 +95,141 @@ static VALUE dd_cov_initialize(int argc, VALUE *argv, VALUE self)
|
|
93
95
|
{
|
94
96
|
rb_raise(rb_eArgError, "root is required");
|
95
97
|
}
|
96
|
-
|
97
98
|
VALUE rb_ignored_path = rb_hash_lookup(opt, ID2SYM(rb_intern("ignored_path")));
|
98
99
|
|
99
|
-
VALUE
|
100
|
-
|
100
|
+
VALUE rb_threading_mode = rb_hash_lookup(opt, ID2SYM(rb_intern("threading_mode")));
|
101
|
+
int threading_mode;
|
102
|
+
if (rb_threading_mode == ID2SYM(rb_intern("multi")))
|
101
103
|
{
|
102
|
-
|
104
|
+
threading_mode = MULTI_THREADED_COVERAGE_MODE;
|
103
105
|
}
|
104
|
-
else if (
|
106
|
+
else if (rb_threading_mode == ID2SYM(rb_intern("single")))
|
105
107
|
{
|
106
|
-
|
108
|
+
threading_mode = SINGLE_THREADED_COVERAGE_MODE;
|
107
109
|
}
|
108
110
|
else
|
109
111
|
{
|
110
|
-
rb_raise(rb_eArgError, "mode is invalid");
|
112
|
+
rb_raise(rb_eArgError, "threading mode is invalid");
|
111
113
|
}
|
112
114
|
|
113
115
|
struct dd_cov_data *dd_cov_data;
|
114
116
|
TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
|
115
117
|
|
116
|
-
dd_cov_data->
|
117
|
-
dd_cov_data->
|
118
|
-
dd_cov_data->
|
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))
|
123
|
+
{
|
124
|
+
dd_cov_data->ignored_path_len = RSTRING_LEN(rb_ignored_path);
|
125
|
+
dd_cov_data->ignored_path = ruby_strndup(RSTRING_PTR(rb_ignored_path), dd_cov_data->ignored_path_len);
|
126
|
+
}
|
119
127
|
|
120
128
|
return Qnil;
|
121
129
|
}
|
122
130
|
|
123
|
-
static void
|
131
|
+
static void dd_cov_update_coverage(rb_event_flag_t event, VALUE data, VALUE self, ID id, VALUE klass)
|
124
132
|
{
|
125
133
|
struct dd_cov_data *dd_cov_data;
|
126
134
|
TypedData_Get_Struct(data, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
|
127
135
|
|
128
|
-
const char *
|
129
|
-
|
136
|
+
const char *c_filename = rb_sourcefile();
|
137
|
+
|
138
|
+
// skip if we cover the same file again
|
139
|
+
uintptr_t current_filename_ptr = (uintptr_t)c_filename;
|
140
|
+
if (dd_cov_data->last_filename_ptr == current_filename_ptr)
|
130
141
|
{
|
131
142
|
return;
|
132
143
|
}
|
144
|
+
dd_cov_data->last_filename_ptr = current_filename_ptr;
|
133
145
|
|
134
|
-
|
135
|
-
|
146
|
+
VALUE top_frame;
|
147
|
+
int captured_frames = rb_profile_frames(
|
148
|
+
0 /* stack starting depth */,
|
149
|
+
PROFILE_FRAMES_BUFFER_SIZE,
|
150
|
+
&top_frame,
|
151
|
+
NULL);
|
152
|
+
|
153
|
+
if (captured_frames != PROFILE_FRAMES_BUFFER_SIZE)
|
136
154
|
{
|
137
155
|
return;
|
138
156
|
}
|
139
157
|
|
140
|
-
|
141
|
-
|
142
|
-
if (RTEST(dd_cov_data->ignored_path) && is_prefix(dd_cov_data->ignored_path, filename) == 1)
|
158
|
+
VALUE filename = rb_profile_frame_path(top_frame);
|
159
|
+
if (filename == Qnil)
|
143
160
|
{
|
144
161
|
return;
|
145
162
|
}
|
146
163
|
|
147
|
-
|
148
|
-
|
149
|
-
if (dd_cov_data->
|
164
|
+
char *filename_ptr = RSTRING_PTR(filename);
|
165
|
+
// if the current filename is not located under the root, we skip it
|
166
|
+
if (strncmp(dd_cov_data->root, filename_ptr, dd_cov_data->root_len) != 0)
|
150
167
|
{
|
151
|
-
rb_hash_aset(dd_cov_data->coverage, rb_str_source_file, Qtrue);
|
152
168
|
return;
|
153
169
|
}
|
154
170
|
|
155
|
-
//
|
156
|
-
//
|
157
|
-
|
158
|
-
if (dd_cov_data->mode == DD_COV_TARGET_LINES)
|
171
|
+
// if ignored_path is provided and the current filename is located under the ignored_path, we skip it too
|
172
|
+
// this is useful for ignoring bundled gems location
|
173
|
+
if (dd_cov_data->ignored_path_len != 0 && strncmp(dd_cov_data->ignored_path, filename_ptr, dd_cov_data->ignored_path_len) == 0)
|
159
174
|
{
|
160
|
-
|
161
|
-
if (line_number <= 0)
|
162
|
-
{
|
163
|
-
return;
|
164
|
-
}
|
165
|
-
|
166
|
-
VALUE rb_lines = rb_hash_aref(dd_cov_data->coverage, rb_str_source_file);
|
167
|
-
if (rb_lines == Qnil)
|
168
|
-
{
|
169
|
-
rb_lines = rb_hash_new();
|
170
|
-
rb_hash_aset(dd_cov_data->coverage, rb_str_source_file, rb_lines);
|
171
|
-
}
|
172
|
-
|
173
|
-
rb_hash_aset(rb_lines, INT2FIX(line_number), Qtrue);
|
175
|
+
return;
|
174
176
|
}
|
177
|
+
|
178
|
+
rb_hash_aset(dd_cov_data->coverage, filename, Qtrue);
|
175
179
|
}
|
176
180
|
|
177
181
|
static VALUE dd_cov_start(VALUE self)
|
178
182
|
{
|
179
|
-
// get current thread
|
180
|
-
VALUE thval = rb_thread_current();
|
181
183
|
|
182
|
-
|
183
|
-
|
184
|
+
struct dd_cov_data *dd_cov_data;
|
185
|
+
TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
|
186
|
+
|
187
|
+
if (dd_cov_data->root_len == 0)
|
188
|
+
{
|
189
|
+
rb_raise(rb_eRuntimeError, "root is required");
|
190
|
+
}
|
191
|
+
|
192
|
+
if (dd_cov_data->threading_mode == SINGLE_THREADED_COVERAGE_MODE)
|
193
|
+
{
|
194
|
+
VALUE thval = rb_thread_current();
|
195
|
+
rb_thread_add_event_hook(thval, dd_cov_update_coverage, RUBY_EVENT_LINE, self);
|
196
|
+
dd_cov_data->th_covered = thval;
|
197
|
+
}
|
198
|
+
else
|
199
|
+
{
|
200
|
+
rb_add_event_hook(dd_cov_update_coverage, RUBY_EVENT_LINE, self);
|
201
|
+
}
|
184
202
|
|
185
203
|
return self;
|
186
204
|
}
|
187
205
|
|
188
206
|
static VALUE dd_cov_stop(VALUE self)
|
189
207
|
{
|
190
|
-
// get current thread
|
191
|
-
VALUE thval = rb_thread_current();
|
192
|
-
// remove event hook for the current thread
|
193
|
-
rb_thread_remove_event_hook(thval, dd_cov_update_line_coverage);
|
194
|
-
|
195
208
|
struct dd_cov_data *dd_cov_data;
|
196
209
|
TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
|
197
210
|
|
198
|
-
|
211
|
+
if (dd_cov_data->threading_mode == SINGLE_THREADED_COVERAGE_MODE)
|
212
|
+
{
|
213
|
+
VALUE thval = rb_thread_current();
|
214
|
+
if (!rb_equal(thval, dd_cov_data->th_covered))
|
215
|
+
{
|
216
|
+
rb_raise(rb_eRuntimeError, "Coverage was not started by this thread");
|
217
|
+
}
|
218
|
+
|
219
|
+
rb_thread_remove_event_hook(dd_cov_data->th_covered, dd_cov_update_coverage);
|
220
|
+
dd_cov_data->th_covered = Qnil;
|
221
|
+
}
|
222
|
+
else
|
223
|
+
{
|
224
|
+
rb_remove_event_hook(dd_cov_update_coverage);
|
225
|
+
}
|
226
|
+
|
227
|
+
VALUE res = dd_cov_data->coverage;
|
199
228
|
|
200
229
|
dd_cov_data->coverage = rb_hash_new();
|
230
|
+
dd_cov_data->last_filename_ptr = 0;
|
201
231
|
|
202
|
-
return
|
232
|
+
return res;
|
203
233
|
}
|
204
234
|
|
205
235
|
void Init_datadog_cov(void)
|
@@ -129,7 +129,8 @@ module Datadog
|
|
129
129
|
config_tags: custom_configuration_tags,
|
130
130
|
coverage_writer: coverage_writer,
|
131
131
|
enabled: settings.ci.enabled && settings.ci.itr_enabled,
|
132
|
-
bundle_location: settings.ci.itr_code_coverage_excluded_bundle_path
|
132
|
+
bundle_location: settings.ci.itr_code_coverage_excluded_bundle_path,
|
133
|
+
use_single_threaded_coverage: settings.ci.itr_code_coverage_use_single_threaded_mode
|
133
134
|
)
|
134
135
|
|
135
136
|
git_tree_uploader = Git::TreeUploader.new(api: test_visibility_api)
|
@@ -204,7 +205,7 @@ module Datadog
|
|
204
205
|
end
|
205
206
|
|
206
207
|
def timecop?
|
207
|
-
Gem.loaded_specs.key?("timecop") || defined?(Timecop)
|
208
|
+
Gem.loaded_specs.key?("timecop") || !!defined?(Timecop)
|
208
209
|
end
|
209
210
|
end
|
210
211
|
end
|
@@ -76,6 +76,12 @@ module Datadog
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
|
+
option :itr_code_coverage_use_single_threaded_mode do |o|
|
80
|
+
o.type :bool
|
81
|
+
o.env CI::Ext::Settings::ENV_ITR_CODE_COVERAGE_USE_SINGLE_THREADED_MODE
|
82
|
+
o.default false
|
83
|
+
end
|
84
|
+
|
79
85
|
define_method(:instrument) do |integration_name, options = {}, &block|
|
80
86
|
return unless enabled
|
81
87
|
|
@@ -42,15 +42,15 @@ module Datadog
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def ci_queue?
|
45
|
-
defined?(::RSpec::Queue::Runner)
|
45
|
+
!!defined?(::RSpec::Queue::Runner)
|
46
46
|
end
|
47
47
|
|
48
48
|
def knapsack_pro?
|
49
49
|
knapsack_version = Gem.loaded_specs["knapsack_pro"]&.version
|
50
50
|
|
51
51
|
# additional instrumentation is needed for KnapsackPro version 7 and later
|
52
|
-
defined?(::KnapsackPro) &&
|
53
|
-
knapsack_version && knapsack_version >= Gem::Version.new("7")
|
52
|
+
!!defined?(::KnapsackPro) &&
|
53
|
+
!knapsack_version.nil? && knapsack_version >= Gem::Version.new("7")
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
@@ -13,6 +13,7 @@ module Datadog
|
|
13
13
|
ENV_ITR_ENABLED = "DD_CIVISIBILITY_ITR_ENABLED"
|
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
|
+
ENV_ITR_CODE_COVERAGE_USE_SINGLE_THREADED_MODE = "DD_CIVISIBILITY_ITR_CODE_COVERAGE_USE_SINGLE_THREADED_MODE"
|
16
17
|
|
17
18
|
# Source: https://docs.datadoghq.com/getting_started/site/
|
18
19
|
DD_SITE_ALLOWLIST = %w[
|
@@ -22,6 +22,8 @@ module Datadog
|
|
22
22
|
DEFAULT_BUFFER_MAX_SIZE = 10_000
|
23
23
|
DEFAULT_SHUTDOWN_TIMEOUT = 60
|
24
24
|
|
25
|
+
DEFAULT_INTERVAL = 3
|
26
|
+
|
25
27
|
def initialize(transport:, options: {})
|
26
28
|
@transport = transport
|
27
29
|
|
@@ -32,7 +34,7 @@ module Datadog
|
|
32
34
|
self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_RESTART
|
33
35
|
|
34
36
|
# Workers::IntervalLoop settings
|
35
|
-
self.loop_base_interval = options[:interval]
|
37
|
+
self.loop_base_interval = options[:interval] || DEFAULT_INTERVAL
|
36
38
|
self.loop_back_off_ratio = options[:back_off_ratio] if options.key?(:back_off_ratio)
|
37
39
|
self.loop_back_off_max = options[:back_off_max] if options.key?(:back_off_max)
|
38
40
|
|
@@ -31,7 +31,8 @@ module Datadog
|
|
31
31
|
api: nil,
|
32
32
|
coverage_writer: nil,
|
33
33
|
enabled: false,
|
34
|
-
bundle_location: nil
|
34
|
+
bundle_location: nil,
|
35
|
+
use_single_threaded_coverage: false
|
35
36
|
)
|
36
37
|
@enabled = enabled
|
37
38
|
@api = api
|
@@ -43,6 +44,7 @@ module Datadog
|
|
43
44
|
else
|
44
45
|
bundle_location
|
45
46
|
end
|
47
|
+
@use_single_threaded_coverage = use_single_threaded_coverage
|
46
48
|
|
47
49
|
@test_skipping_enabled = false
|
48
50
|
@code_coverage_enabled = false
|
@@ -186,12 +188,15 @@ module Datadog
|
|
186
188
|
def coverage_collector
|
187
189
|
Thread.current[:dd_coverage_collector] ||= Coverage::DDCov.new(
|
188
190
|
root: Git::LocalRepository.root,
|
189
|
-
ignored_path: @bundle_location
|
191
|
+
ignored_path: @bundle_location,
|
192
|
+
threading_mode: code_coverage_mode
|
190
193
|
)
|
191
194
|
end
|
192
195
|
|
193
196
|
def load_datadog_cov!
|
194
197
|
require "datadog_cov.#{RUBY_VERSION}_#{RUBY_PLATFORM}"
|
198
|
+
|
199
|
+
Datadog.logger.debug("Loaded Datadog code coverage collector, using coverage mode: #{code_coverage_mode}")
|
195
200
|
rescue LoadError => e
|
196
201
|
Datadog.logger.error("Failed to load coverage collector: #{e}. Code coverage will not be collected.")
|
197
202
|
|
@@ -222,6 +227,10 @@ module Datadog
|
|
222
227
|
Datadog.logger.debug { "Found #{@skippable_tests.count} skippable tests." }
|
223
228
|
Datadog.logger.debug { "ITR correlation ID: #{@correlation_id}" }
|
224
229
|
end
|
230
|
+
|
231
|
+
def code_coverage_mode
|
232
|
+
@use_single_threaded_coverage ? :single : :multi
|
233
|
+
end
|
225
234
|
end
|
226
235
|
end
|
227
236
|
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "datadog/core/transport/response"
|
4
|
+
require "datadog/core/transport/ext"
|
5
|
+
|
6
|
+
require_relative "../gzip"
|
7
|
+
require_relative "../../ext/transport"
|
8
|
+
|
9
|
+
module Datadog
|
10
|
+
module CI
|
11
|
+
module Transport
|
12
|
+
module Adapters
|
13
|
+
# Adapter for Net::HTTP
|
14
|
+
class Net
|
15
|
+
attr_reader \
|
16
|
+
:hostname,
|
17
|
+
:port,
|
18
|
+
:timeout,
|
19
|
+
:ssl
|
20
|
+
|
21
|
+
def initialize(hostname:, port:, ssl:, timeout_seconds:)
|
22
|
+
@hostname = hostname
|
23
|
+
@port = port
|
24
|
+
@timeout = timeout_seconds
|
25
|
+
@ssl = ssl
|
26
|
+
end
|
27
|
+
|
28
|
+
def open(&block)
|
29
|
+
req = net_http_client.new(hostname, port)
|
30
|
+
|
31
|
+
req.use_ssl = ssl
|
32
|
+
req.open_timeout = req.read_timeout = timeout
|
33
|
+
|
34
|
+
req.start(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(path:, payload:, headers:, verb:)
|
38
|
+
headers ||= {}
|
39
|
+
# skip tracing for internal DD requests
|
40
|
+
headers[Core::Transport::Ext::HTTP::HEADER_DD_INTERNAL_UNTRACED_REQUEST] = "1"
|
41
|
+
|
42
|
+
if respond_to?(verb)
|
43
|
+
send(verb, path: path, payload: payload, headers: headers)
|
44
|
+
else
|
45
|
+
raise "Unknown HTTP method [#{verb}]"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def post(path:, payload:, headers:)
|
50
|
+
post = ::Net::HTTP::Post.new(path, headers)
|
51
|
+
post.body = payload
|
52
|
+
|
53
|
+
# Connect and send the request
|
54
|
+
http_response = open do |http|
|
55
|
+
http.request(post)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Build and return response
|
59
|
+
Response.new(http_response)
|
60
|
+
end
|
61
|
+
|
62
|
+
class Response
|
63
|
+
include Datadog::Core::Transport::Response
|
64
|
+
|
65
|
+
attr_reader :http_response
|
66
|
+
|
67
|
+
def initialize(http_response)
|
68
|
+
@http_response = http_response
|
69
|
+
end
|
70
|
+
|
71
|
+
def payload
|
72
|
+
return @decompressed_payload if defined?(@decompressed_payload)
|
73
|
+
return http_response.body unless gzipped_content?
|
74
|
+
return http_response.body unless gzipped_body?(http_response.body)
|
75
|
+
|
76
|
+
Datadog.logger.debug("Decompressing gzipped response payload")
|
77
|
+
@decompressed_payload = Gzip.decompress(http_response.body)
|
78
|
+
end
|
79
|
+
|
80
|
+
def header(name)
|
81
|
+
http_response[name]
|
82
|
+
end
|
83
|
+
|
84
|
+
def code
|
85
|
+
http_response.code.to_i
|
86
|
+
end
|
87
|
+
|
88
|
+
def ok?
|
89
|
+
code.between?(200, 299)
|
90
|
+
end
|
91
|
+
|
92
|
+
def unsupported?
|
93
|
+
code == 415
|
94
|
+
end
|
95
|
+
|
96
|
+
def not_found?
|
97
|
+
code == 404
|
98
|
+
end
|
99
|
+
|
100
|
+
def client_error?
|
101
|
+
code.between?(400, 499)
|
102
|
+
end
|
103
|
+
|
104
|
+
def server_error?
|
105
|
+
code.between?(500, 599)
|
106
|
+
end
|
107
|
+
|
108
|
+
def gzipped_content?
|
109
|
+
header(Ext::Transport::HEADER_CONTENT_ENCODING) == Ext::Transport::CONTENT_ENCODING_GZIP
|
110
|
+
end
|
111
|
+
|
112
|
+
def gzipped_body?(body)
|
113
|
+
return false if body.nil? || body.empty?
|
114
|
+
|
115
|
+
# no-dd-sa
|
116
|
+
first_bytes = body[0, 2]
|
117
|
+
return false if first_bytes.nil? || first_bytes.empty?
|
118
|
+
|
119
|
+
first_bytes.b == Ext::Transport::GZIP_MAGIC_NUMBER
|
120
|
+
end
|
121
|
+
|
122
|
+
def inspect
|
123
|
+
"#{super}, http_response:#{http_response}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def net_http_client
|
130
|
+
return ::Net::HTTP unless defined?(WebMock::HttpLibAdapters::NetHttpAdapter::OriginalNetHTTP)
|
131
|
+
|
132
|
+
WebMock::HttpLibAdapters::NetHttpAdapter::OriginalNetHTTP
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -37,7 +37,7 @@ module Datadog
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def citestcov_request(path:, payload:, headers: {}, verb: "post")
|
40
|
-
super
|
40
|
+
super
|
41
41
|
|
42
42
|
perform_request(@citestcov_http, path: path, payload: @citestcov_payload, headers: headers, verb: verb)
|
43
43
|
end
|
@@ -60,7 +60,7 @@ module Datadog
|
|
60
60
|
|
61
61
|
Datadog::CI::Transport::HTTP.new(
|
62
62
|
host: uri.host,
|
63
|
-
port: uri.port,
|
63
|
+
port: uri.port || 80,
|
64
64
|
ssl: uri.scheme == "https" || uri.port == 443,
|
65
65
|
compress: compress
|
66
66
|
)
|
@@ -39,7 +39,7 @@ module Datadog
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def citestcov_request(path:, payload:, headers: {}, verb: "post")
|
42
|
-
super
|
42
|
+
super
|
43
43
|
|
44
44
|
headers[Ext::Transport::HEADER_EVP_SUBDOMAIN] = Ext::Transport::TEST_COVERAGE_INTAKE_HOST_PREFIX
|
45
45
|
|
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "delegate"
|
4
|
-
require "datadog/core/transport/http/adapters/net"
|
5
|
-
require "datadog/core/transport/http/env"
|
6
|
-
require "datadog/core/transport/request"
|
7
4
|
require "socket"
|
8
5
|
|
9
6
|
require_relative "gzip"
|
7
|
+
require_relative "adapters/net"
|
10
8
|
require_relative "../ext/transport"
|
11
9
|
|
12
10
|
module Datadog
|
@@ -24,7 +22,7 @@ module Datadog
|
|
24
22
|
MAX_RETRIES = 3
|
25
23
|
INITIAL_BACKOFF = 1
|
26
24
|
|
27
|
-
def initialize(host:, timeout: DEFAULT_TIMEOUT,
|
25
|
+
def initialize(host:, port:, timeout: DEFAULT_TIMEOUT, ssl: true, compress: false)
|
28
26
|
@host = host
|
29
27
|
@port = port
|
30
28
|
@timeout = timeout
|
@@ -70,7 +68,7 @@ module Datadog
|
|
70
68
|
|
71
69
|
def perform_http_call(path:, payload:, headers:, verb:, retries: MAX_RETRIES, backoff: INITIAL_BACKOFF)
|
72
70
|
adapter.call(
|
73
|
-
|
71
|
+
path: path, payload: payload, headers: headers, verb: verb
|
74
72
|
)
|
75
73
|
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, SocketError, Net::HTTPBadResponse => e
|
76
74
|
Datadog.logger.debug("Failed to send request with #{e} (#{e.message})")
|
@@ -87,65 +85,17 @@ module Datadog
|
|
87
85
|
end
|
88
86
|
end
|
89
87
|
|
90
|
-
def build_env(path:, payload:, headers:, verb:)
|
91
|
-
env = Datadog::Core::Transport::HTTP::Env.new(
|
92
|
-
Datadog::Core::Transport::Request.new
|
93
|
-
)
|
94
|
-
env.body = payload
|
95
|
-
env.path = path
|
96
|
-
env.headers = headers
|
97
|
-
env.verb = verb
|
98
|
-
env
|
99
|
-
end
|
100
|
-
|
101
88
|
def adapter
|
102
|
-
|
103
|
-
|
89
|
+
@adapter ||= Datadog::CI::Transport::Adapters::Net.new(
|
90
|
+
hostname: host, port: port, ssl: ssl, timeout_seconds: timeout
|
91
|
+
)
|
104
92
|
end
|
105
93
|
|
106
|
-
# adds compatibility with Datadog::Tracing transport
|
107
|
-
# provides ungzipping capabilities
|
94
|
+
# adds compatibility with Datadog::Tracing transport
|
108
95
|
class ResponseDecorator < ::SimpleDelegator
|
109
|
-
def payload
|
110
|
-
return @decompressed_payload if defined?(@decompressed_payload)
|
111
|
-
|
112
|
-
if gzipped?(__getobj__.payload)
|
113
|
-
Datadog.logger.debug("Decompressing gzipped response payload")
|
114
|
-
@decompressed_payload = Gzip.decompress(__getobj__.payload)
|
115
|
-
else
|
116
|
-
__getobj__.payload
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
96
|
def trace_count
|
121
97
|
0
|
122
98
|
end
|
123
|
-
|
124
|
-
def gzipped?(payload)
|
125
|
-
return false if payload.nil? || payload.empty?
|
126
|
-
|
127
|
-
# no-dd-sa
|
128
|
-
first_bytes = payload[0, 2]
|
129
|
-
return false if first_bytes.nil? || first_bytes.empty?
|
130
|
-
|
131
|
-
first_bytes.b == Datadog::CI::Ext::Transport::GZIP_MAGIC_NUMBER
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
class AdapterSettings
|
136
|
-
attr_reader :hostname, :port, :ssl, :timeout_seconds
|
137
|
-
|
138
|
-
def initialize(hostname:, port: nil, ssl: true, timeout_seconds: nil)
|
139
|
-
@hostname = hostname
|
140
|
-
@port = port
|
141
|
-
@ssl = ssl
|
142
|
-
@timeout_seconds = timeout_seconds
|
143
|
-
end
|
144
|
-
|
145
|
-
def ==(other)
|
146
|
-
hostname == other.hostname && port == other.port && ssl == other.ssl &&
|
147
|
-
timeout_seconds == other.timeout_seconds
|
148
|
-
end
|
149
99
|
end
|
150
100
|
end
|
151
101
|
end
|
data/lib/datadog/ci/version.rb
CHANGED
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.1.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-
|
11
|
+
date: 2024-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: datadog
|
@@ -160,6 +160,7 @@ files:
|
|
160
160
|
- lib/datadog/ci/test_visibility/serializers/test_v1.rb
|
161
161
|
- lib/datadog/ci/test_visibility/serializers/test_v2.rb
|
162
162
|
- lib/datadog/ci/test_visibility/transport.rb
|
163
|
+
- lib/datadog/ci/transport/adapters/net.rb
|
163
164
|
- lib/datadog/ci/transport/api/agentless.rb
|
164
165
|
- lib/datadog/ci/transport/api/base.rb
|
165
166
|
- lib/datadog/ci/transport/api/builder.rb
|
@@ -201,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
201
202
|
- !ruby/object:Gem::Version
|
202
203
|
version: 2.0.0
|
203
204
|
requirements: []
|
204
|
-
rubygems_version: 3.5.
|
205
|
+
rubygems_version: 3.5.11
|
205
206
|
signing_key:
|
206
207
|
specification_version: 4
|
207
208
|
summary: Datadog CI visibility for your ruby application
|