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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f96afc61b44202e506967dc7b2d6ec956c02561a99ec7e9eae4dc26902688b7
4
- data.tar.gz: f6d66d038f8c5a5632f6c27ed2a3c70d9f85394fca2c237825041cb523063ec4
3
+ metadata.gz: 61f6379b9a5ac0c29ff9ee6b1687c1c2add77d6a5031dfd5758eb34a66cc6235
4
+ data.tar.gz: a795d8d69513925ee5ea6cf113cb54cec3711fe65ae5e928fcdeb8e1454323af
5
5
  SHA512:
6
- metadata.gz: a6a548db92e125ae6fdb23301554efec581f72ca63b9a32384be33162eb90daa79ebf67ccb5834f2347b094f8d131129cd4a6963a817c55beaed83e0cd10b96b
7
- data.tar.gz: e6da7ae7f7607955b206859e8a6c583d65aea03c59d538de2c3c7de41433a0b9942fe48e6d79db9bc7cb6546100b001dfd90b421c7ed392aa6d10e14034ecd08
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.0.0...main
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
- [#190]: https://github.com/DataDog/datadog-ci-rb/issues/190
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
- // constants
5
- #define DD_COV_TARGET_FILES 1
6
- #define DD_COV_TARGET_LINES 2
4
+ #define PROFILE_FRAMES_BUFFER_SIZE 1
7
5
 
8
- static int is_prefix(VALUE prefix, const char *str)
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
- if (prefix == Qnil)
11
- {
12
- return 0;
13
- }
12
+ char *dup;
14
13
 
15
- const char *c_prefix = RSTRING_PTR(prefix);
16
- if (c_prefix == NULL)
17
- {
18
- return 0;
19
- }
14
+ dup = xmalloc(size + 1);
15
+ memcpy(dup, str, size);
16
+ dup[size] = '\0';
20
17
 
21
- long prefix_len = RSTRING_LEN(prefix);
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
- VALUE root;
36
- VALUE ignored_path;
37
- int mode;
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->root);
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->root = rb_gc_location(dd_cov_data->root);
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 = Qnil;
79
- dd_cov_data->ignored_path = Qnil;
80
- dd_cov_data->mode = DD_COV_TARGET_FILES;
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 rb_mode = rb_hash_lookup(opt, ID2SYM(rb_intern("mode")));
100
- if (!RTEST(rb_mode) || rb_mode == ID2SYM(rb_intern("files")))
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
- mode = DD_COV_TARGET_FILES;
104
+ threading_mode = MULTI_THREADED_COVERAGE_MODE;
103
105
  }
104
- else if (rb_mode == ID2SYM(rb_intern("lines")))
106
+ else if (rb_threading_mode == ID2SYM(rb_intern("single")))
105
107
  {
106
- mode = DD_COV_TARGET_LINES;
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->root = rb_root;
117
- dd_cov_data->ignored_path = rb_ignored_path;
118
- dd_cov_data->mode = mode;
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 dd_cov_update_line_coverage(rb_event_flag_t event, VALUE data, VALUE self, ID id, VALUE klass)
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 *filename = rb_sourcefile();
129
- if (filename == NULL)
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
- // if given filename is not located under the root, we skip it
135
- if (is_prefix(dd_cov_data->root, filename) == 0)
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
- // if ignored_path is provided and given filename is located under the ignored_path, we skip it too
141
- // this is useful for ignoring bundled gems location
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
- VALUE rb_str_source_file = rb_str_new2(filename);
148
-
149
- if (dd_cov_data->mode == DD_COV_TARGET_FILES)
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
- // this isn't optimized yet, this is a POC to show that lines coverage is possible
156
- // ITR beta is going to use files coverage, we'll get back to this part when
157
- // we need to implement lines coverage
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
- int line_number = rb_sourceline();
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
- // add event hook
183
- rb_thread_add_event_hook(thval, dd_cov_update_line_coverage, RUBY_EVENT_LINE, self);
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
- VALUE cov = dd_cov_data->coverage;
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 cov;
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
 
@@ -100,7 +100,7 @@ module Datadog
100
100
  end
101
101
 
102
102
  def ci_queue?
103
- defined?(::RSpec::Queue::ExampleExtension) &&
103
+ !!defined?(::RSpec::Queue::ExampleExtension) &&
104
104
  self.class.ancestors.include?(::RSpec::Queue::ExampleExtension)
105
105
  end
106
106
  end
@@ -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] if options.key?(: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(path: path, payload: payload, headers: headers, verb: verb)
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(path: path, payload: payload, headers: headers, verb: verb)
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, port: nil, ssl: true, compress: false)
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
- build_env(path: path, payload: payload, headers: headers, verb: verb)
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
- settings = AdapterSettings.new(hostname: host, port: port, ssl: ssl, timeout_seconds: timeout)
103
- @adapter ||= Datadog::Core::Transport::HTTP::Adapters::Net.new(settings)
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 and
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
@@ -3,9 +3,9 @@
3
3
  module Datadog
4
4
  module CI
5
5
  module VERSION
6
- MAJOR = "1"
7
- MINOR = "0"
8
- PATCH = "0"
6
+ MAJOR = 1
7
+ MINOR = 1
8
+ PATCH = 0
9
9
  PRE = nil
10
10
  BUILD = nil
11
11
  # PRE and BUILD above are modified for dev gems during gem build GHA workflow
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.0.0
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-06-06 00:00:00.000000000 Z
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.9
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