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 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