datadog-ci 1.0.0 → 1.1.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 +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
|