datadog-ci 0.8.2 → 1.0.0.beta1

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -1
  3. data/LICENSE-3rdparty.csv +1 -1
  4. data/README.md +22 -9
  5. data/ext/datadog_cov/datadog_cov.c +192 -0
  6. data/ext/datadog_cov/extconf.rb +18 -0
  7. data/lib/datadog/ci/configuration/settings.rb +1 -1
  8. data/lib/datadog/ci/contrib/cucumber/configuration/settings.rb +0 -15
  9. data/lib/datadog/ci/contrib/cucumber/ext.rb +1 -5
  10. data/lib/datadog/ci/contrib/cucumber/formatter.rb +17 -14
  11. data/lib/datadog/ci/contrib/cucumber/integration.rb +1 -2
  12. data/lib/datadog/ci/contrib/minitest/configuration/settings.rb +0 -15
  13. data/lib/datadog/ci/contrib/minitest/ext.rb +1 -5
  14. data/lib/datadog/ci/contrib/minitest/helpers.rb +1 -2
  15. data/lib/datadog/ci/contrib/minitest/integration.rb +1 -1
  16. data/lib/datadog/ci/contrib/rspec/configuration/settings.rb +0 -15
  17. data/lib/datadog/ci/contrib/rspec/example.rb +18 -20
  18. data/lib/datadog/ci/contrib/rspec/ext.rb +0 -4
  19. data/lib/datadog/ci/contrib/rspec/integration.rb +1 -2
  20. data/lib/datadog/ci/contrib/settings.rb +0 -3
  21. data/lib/datadog/ci/ext/environment/extractor.rb +3 -2
  22. data/lib/datadog/ci/ext/environment/providers/base.rb +1 -1
  23. data/lib/datadog/ci/ext/environment/providers/bitbucket.rb +1 -1
  24. data/lib/datadog/ci/ext/environment/providers/github_actions.rb +3 -2
  25. data/lib/datadog/ci/ext/environment.rb +1 -1
  26. data/lib/datadog/ci/ext/transport.rb +4 -0
  27. data/lib/datadog/ci/itr/coverage/ddcov.rb +14 -0
  28. data/lib/datadog/ci/itr/coverage/event.rb +64 -0
  29. data/lib/datadog/ci/itr/coverage/transport.rb +42 -0
  30. data/lib/datadog/ci/itr/runner.rb +28 -0
  31. data/lib/datadog/ci/test.rb +1 -2
  32. data/lib/datadog/ci/test_visibility/context/global.rb +1 -3
  33. data/lib/datadog/ci/test_visibility/null_recorder.rb +1 -1
  34. data/lib/datadog/ci/test_visibility/recorder.rb +20 -3
  35. data/lib/datadog/ci/test_visibility/serializers/base.rb +1 -1
  36. data/lib/datadog/ci/test_visibility/serializers/factories/test_level.rb +1 -1
  37. data/lib/datadog/ci/test_visibility/serializers/factories/test_suite_level.rb +1 -1
  38. data/lib/datadog/ci/test_visibility/transport.rb +10 -53
  39. data/lib/datadog/ci/transport/api/agentless.rb +8 -1
  40. data/lib/datadog/ci/transport/api/base.rb +23 -0
  41. data/lib/datadog/ci/transport/api/builder.rb +14 -5
  42. data/lib/datadog/ci/transport/api/evp_proxy.rb +8 -0
  43. data/lib/datadog/ci/transport/event_platform_transport.rb +88 -0
  44. data/lib/datadog/ci/transport/http.rb +19 -2
  45. data/lib/datadog/ci/utils/git.rb +2 -2
  46. data/lib/datadog/ci/version.rb +5 -5
  47. metadata +25 -5
  48. data/lib/datadog/ci/utils/url.rb +0 -15
@@ -103,7 +103,7 @@ module Datadog
103
103
  @branch = @tag = nil
104
104
 
105
105
  # @type var branch_or_tag_string: untyped
106
- if branch_or_tag_string && branch_or_tag_string.include?("tags/")
106
+ if branch_or_tag_string&.include?("tags/")
107
107
  @tag = branch_or_tag_string
108
108
  else
109
109
  @branch = branch_or_tag_string
@@ -20,7 +20,7 @@ module Datadog
20
20
  end
21
21
 
22
22
  def pipeline_id
23
- env["BITBUCKET_PIPELINE_UUID"] ? env["BITBUCKET_PIPELINE_UUID"].tr("{}", "") : nil
23
+ env["BITBUCKET_PIPELINE_UUID"]&.tr("{}", "")
24
24
  end
25
25
 
26
26
  def pipeline_name
@@ -2,8 +2,9 @@
2
2
 
3
3
  require "json"
4
4
 
5
+ require "datadog/core/utils/url"
6
+
5
7
  require_relative "base"
6
- require_relative "../../../utils/url"
7
8
 
8
9
  module Datadog
9
10
  module CI
@@ -79,7 +80,7 @@ module Datadog
79
80
  def github_server_url
80
81
  return @github_server_url if defined?(@github_server_url)
81
82
 
82
- @github_server_url ||= Utils::Url.filter_sensitive_info(env["GITHUB_SERVER_URL"])
83
+ @github_server_url ||= Datadog::Core::Utils::Url.filter_basic_auth(env["GITHUB_SERVER_URL"])
83
84
  end
84
85
  end
85
86
  end
@@ -71,7 +71,7 @@ module Datadog
71
71
  return
72
72
  end
73
73
 
74
- unless HEX_NUMBER_REGEXP =~ git_sha
74
+ unless HEX_NUMBER_REGEXP.match?(git_sha)
75
75
  message += " Expected SHA to be a valid HEX number, got #{git_sha}."
76
76
  Datadog.logger.error(message)
77
77
  end
@@ -23,6 +23,9 @@ module Datadog
23
23
  TEST_VISIBILITY_INTAKE_HOST_PREFIX = "citestcycle-intake"
24
24
  TEST_VISIBILITY_INTAKE_PATH = "/api/v2/citestcycle"
25
25
 
26
+ TEST_COVERAGE_INTAKE_HOST_PREFIX = "citestcov-intake"
27
+ TEST_COVERAGE_INTAKE_PATH = "/api/v2/citestcov"
28
+
26
29
  DD_API_HOST_PREFIX = "api"
27
30
  DD_API_SETTINGS_PATH = "/api/v2/libraries/tests/services/setting"
28
31
  DD_API_SETTINGS_TYPE = "ci_app_test_service_libraries_settings"
@@ -35,6 +38,7 @@ module Datadog
35
38
 
36
39
  CONTENT_TYPE_MESSAGEPACK = "application/msgpack"
37
40
  CONTENT_TYPE_JSON = "application/json"
41
+ CONTENT_TYPE_MULTIPART_FORM_DATA = "multipart/form-data"
38
42
  CONTENT_ENCODING_GZIP = "gzip"
39
43
  end
40
44
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module ITR
6
+ module Coverage
7
+ # Placeholder for code coverage collection
8
+ # Implementation in ext/datadog_cov
9
+ class DDCov
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "msgpack"
4
+
5
+ module Datadog
6
+ module CI
7
+ module ITR
8
+ module Coverage
9
+ class Event
10
+ attr_reader :test_id, :test_suite_id, :test_session_id, :coverage
11
+
12
+ def initialize(test_id:, test_suite_id:, test_session_id:, coverage:)
13
+ @test_id = test_id
14
+ @test_suite_id = test_suite_id
15
+ @test_session_id = test_session_id
16
+ @coverage = coverage
17
+ end
18
+
19
+ def valid?
20
+ valid = true
21
+
22
+ [:test_id, :test_suite_id, :test_session_id, :coverage].each do |key|
23
+ next unless send(key).nil?
24
+
25
+ Datadog.logger.warn("citestcov event is invalid: [#{key}] is nil. Event: #{self}")
26
+ valid = false
27
+ end
28
+
29
+ valid
30
+ end
31
+
32
+ def to_msgpack(packer = nil)
33
+ packer ||= MessagePack::Packer.new
34
+
35
+ packer.write_map_header(4)
36
+
37
+ packer.write("test_session_id")
38
+ packer.write(test_session_id.to_i)
39
+
40
+ packer.write("test_suite_id")
41
+ packer.write(test_suite_id.to_i)
42
+
43
+ packer.write("span_id")
44
+ packer.write(test_id.to_i)
45
+
46
+ files = coverage.keys
47
+ packer.write("files")
48
+ packer.write_array_header(files.size)
49
+
50
+ files.each do |filename|
51
+ packer.write_map_header(1)
52
+ packer.write("filename")
53
+ packer.write(filename)
54
+ end
55
+ end
56
+
57
+ def to_s
58
+ "Coverage::Event[test_id=#{test_id}, test_suite_id=#{test_suite_id}, test_session_id=#{test_session_id}, coverage=#{coverage}]"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "event"
4
+ require_relative "../../transport/event_platform_transport"
5
+
6
+ module Datadog
7
+ module CI
8
+ module ITR
9
+ module Coverage
10
+ class Transport < Datadog::CI::Transport::EventPlatformTransport
11
+ private
12
+
13
+ def send_payload(encoded_payload)
14
+ api.citestcov_request(
15
+ path: Ext::Transport::TEST_COVERAGE_INTAKE_PATH,
16
+ payload: encoded_payload
17
+ )
18
+ end
19
+
20
+ def encode_events(events)
21
+ events.filter_map do |event|
22
+ next unless event.valid?
23
+
24
+ encoded = encoder.encode(event)
25
+ next if event_too_large?(event, encoded)
26
+
27
+ encoded
28
+ end
29
+ end
30
+
31
+ def write_payload_header(packer)
32
+ packer.write_map_header(2)
33
+ packer.write("version")
34
+ packer.write(2)
35
+
36
+ packer.write("coverages")
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -3,6 +3,8 @@
3
3
  require_relative "../ext/test"
4
4
  require_relative "../ext/transport"
5
5
 
6
+ require_relative "../utils/git"
7
+
6
8
  module Datadog
7
9
  module CI
8
10
  module ITR
@@ -41,6 +43,8 @@ module Datadog
41
43
  # we skip tests, not suites
42
44
  test_session.set_tag(Ext::Test::TAG_ITR_TEST_SKIPPING_TYPE, Ext::Test::ITR_TEST_SKIPPING_MODE)
43
45
 
46
+ load_datadog_cov! if @code_coverage_enabled
47
+
44
48
  Datadog.logger.debug("Configured ITR Runner with enabled: #{@enabled}, skipping_tests: #{@test_skipping_enabled}, code_coverage: #{@code_coverage_enabled}")
45
49
  end
46
50
 
@@ -56,8 +60,32 @@ module Datadog
56
60
  @code_coverage_enabled
57
61
  end
58
62
 
63
+ def start_coverage
64
+ return if !enabled? || !code_coverage?
65
+
66
+ coverage_collector&.start
67
+ end
68
+
69
+ def stop_coverage(_test)
70
+ return if !enabled? || !code_coverage?
71
+
72
+ coverage_collector&.stop
73
+ end
74
+
59
75
  private
60
76
 
77
+ def coverage_collector
78
+ Thread.current[:dd_coverage_collector] ||= Coverage::DDCov.new(root: Utils::Git.root)
79
+ end
80
+
81
+ def load_datadog_cov!
82
+ require "datadog_cov.#{RUBY_VERSION}_#{RUBY_PLATFORM}"
83
+ rescue LoadError => e
84
+ Datadog.logger.error("Failed to load coverage collector: #{e}. Code coverage will not be collected.")
85
+
86
+ @code_coverage_enabled = false
87
+ end
88
+
61
89
  def convert_to_bool(value)
62
90
  value.to_s == "true"
63
91
  end
@@ -112,8 +112,7 @@ module Datadog
112
112
  private
113
113
 
114
114
  def record_test_result(datadog_status)
115
- suite = test_suite
116
- suite.record_test_result(datadog_status) if suite
115
+ test_suite&.record_test_result(datadog_status)
117
116
  end
118
117
  end
119
118
  end
@@ -55,9 +55,7 @@ module Datadog
55
55
 
56
56
  def service
57
57
  @mutex.synchronize do
58
- # thank you RBS for this weirdness
59
- test_session = @test_session
60
- test_session.service if test_session
58
+ @test_session&.service
61
59
  end
62
60
  end
63
61
 
@@ -45,7 +45,7 @@ module Datadog
45
45
  private
46
46
 
47
47
  def skip_tracing(block = nil)
48
- block.call(nil) if block
48
+ block&.call(nil)
49
49
  end
50
50
  end
51
51
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "datadog/tracing"
4
+ require "datadog/tracing/contrib/component"
4
5
  require "datadog/tracing/trace_digest"
5
6
 
6
7
  require "rbconfig"
@@ -116,14 +117,22 @@ module Datadog
116
117
  test = build_test(tracer_span, tags)
117
118
 
118
119
  @local_context.activate_test(test) do
119
- block.call(test)
120
+ @itr.start_coverage
121
+
122
+ res = block.call(test)
123
+ on_test_finished(test)
124
+
125
+ res
120
126
  end
121
127
  end
122
128
  else
123
129
  tracer_span = start_datadog_tracer_span(test_name, span_options)
124
130
 
125
131
  test = build_test(tracer_span, tags)
132
+
126
133
  @local_context.activate_test(test)
134
+ @itr.start_coverage
135
+
127
136
  test
128
137
  end
129
138
  end
@@ -168,6 +177,9 @@ module Datadog
168
177
  end
169
178
 
170
179
  def deactivate_test
180
+ test = active_test
181
+ on_test_finished(test) if test
182
+
171
183
  @local_context.deactivate_test
172
184
  end
173
185
 
@@ -198,12 +210,12 @@ module Datadog
198
210
  end
199
211
 
200
212
  def skip_tracing(block = nil)
201
- block.call(nil) if block
213
+ block&.call(nil)
202
214
  end
203
215
 
204
216
  # Sets trace's origin to ciapp-test
205
217
  def set_trace_origin(trace)
206
- trace.origin = Ext::Test::CONTEXT_ORIGIN if trace
218
+ trace&.origin = Ext::Test::CONTEXT_ORIGIN
207
219
  end
208
220
 
209
221
  def build_test_session(tracer_span, tags)
@@ -357,6 +369,11 @@ module Datadog
357
369
  end
358
370
  end
359
371
  end
372
+
373
+ # TODO: use kind of event system to notify about test finished?
374
+ def on_test_finished(test)
375
+ @itr.stop_coverage(test)
376
+ end
360
377
  end
361
378
  end
362
379
  end
@@ -241,7 +241,7 @@ module Datadog
241
241
  end
242
242
 
243
243
  def to_integer(value)
244
- value.to_i if value
244
+ value&.to_i
245
245
  end
246
246
  end
247
247
  end
@@ -8,7 +8,7 @@ module Datadog
8
8
  module TestVisibility
9
9
  module Serializers
10
10
  module Factories
11
- # This factory takes care of creating citestcycle serializers when test-level visibility is enabled
11
+ # This factory takes care of creating msgpack serializers when test-level visibility is enabled
12
12
  # NOTE: citestcycle is a protocol Datadog uses to submit test execution tracing information to CI visibility
13
13
  # backend
14
14
  module TestLevel
@@ -11,7 +11,7 @@ module Datadog
11
11
  module TestVisibility
12
12
  module Serializers
13
13
  module Factories
14
- # This factory takes care of creating citestcycle serializers when test-suite-level visibility is enabled
14
+ # This factory takes care of creating msgpack serializers when test-suite-level visibility is enabled
15
15
  module TestSuiteLevel
16
16
  module_function
17
17
 
@@ -1,27 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "msgpack"
4
- require "uri"
5
-
6
- require "datadog/core/encoding"
7
3
  require "datadog/core/environment/identity"
8
- require "datadog/core/chunker"
9
4
 
10
5
  require_relative "serializers/factories/test_level"
11
6
  require_relative "../ext/transport"
7
+ require_relative "../transport/event_platform_transport"
12
8
 
13
9
  module Datadog
14
10
  module CI
15
11
  module TestVisibility
16
- class Transport
17
- # CI test cycle intake's limit is 5.1MB uncompressed
18
- # We will use a bit more conservative value 5MB
19
- DEFAULT_MAX_PAYLOAD_SIZE = 5 * 1024 * 1024
20
-
21
- attr_reader :serializers_factory,
22
- :api,
23
- :max_payload_size,
24
- :dd_env
12
+ class Transport < Datadog::CI::Transport::EventPlatformTransport
13
+ attr_reader :serializers_factory, :dd_env
25
14
 
26
15
  def initialize(
27
16
  api:,
@@ -29,36 +18,15 @@ module Datadog
29
18
  serializers_factory: Datadog::CI::TestVisibility::Serializers::Factories::TestLevel,
30
19
  max_payload_size: DEFAULT_MAX_PAYLOAD_SIZE
31
20
  )
21
+ super(api: api, max_payload_size: max_payload_size)
22
+
32
23
  @serializers_factory = serializers_factory
33
- @max_payload_size = max_payload_size
34
24
  @dd_env = dd_env
35
- @api = api
36
25
  end
37
26
 
27
+ # this method is needed for compatibility with Datadog::Tracing::Writer that uses this Transport
38
28
  def send_traces(traces)
39
- return [] if traces.nil? || traces.empty?
40
-
41
- Datadog.logger.debug { "Sending #{traces.count} traces..." }
42
-
43
- encoded_events = encode_traces(traces)
44
- if encoded_events.empty?
45
- Datadog.logger.debug { "Empty encoded events list, skipping send" }
46
- return []
47
- end
48
-
49
- responses = []
50
- Datadog::Core::Chunker.chunk_by_size(encoded_events, max_payload_size).map do |chunk|
51
- encoded_payload = pack_events(chunk)
52
- Datadog.logger.debug do
53
- "Send chunk of #{chunk.count} events; payload size #{encoded_payload.size}"
54
- end
55
-
56
- response = send_payload(encoded_payload)
57
-
58
- responses << response
59
- end
60
-
61
- responses
29
+ send_events(traces)
62
30
  end
63
31
 
64
32
  private
@@ -70,15 +38,9 @@ module Datadog
70
38
  )
71
39
  end
72
40
 
73
- def encode_traces(traces)
41
+ def encode_events(traces)
74
42
  traces.flat_map do |trace|
75
- spans = trace.spans
76
- # TODO: remove condition when 1.0 is released
77
- if spans.respond_to?(:filter_map)
78
- spans.filter_map { |span| encode_span(trace, span) }
79
- else
80
- spans.map { |span| encode_span(trace, span) }.reject(&:nil?)
81
- end
43
+ trace.spans.filter_map { |span| encode_span(trace, span) }
82
44
  end
83
45
  end
84
46
 
@@ -107,9 +69,7 @@ module Datadog
107
69
  Datadog::Core::Encoding::MsgpackEncoder
108
70
  end
109
71
 
110
- def pack_events(encoded_events)
111
- packer = MessagePack::Packer.new
112
-
72
+ def write_payload_header(packer)
113
73
  packer.write_map_header(3) # Set header with how many elements in the map
114
74
 
115
75
  packer.write("version")
@@ -137,9 +97,6 @@ module Datadog
137
97
  packer.write(Datadog::CI::VERSION::STRING)
138
98
 
139
99
  packer.write("events")
140
- packer.write_array_header(encoded_events.size)
141
-
142
- (packer.buffer.to_a + encoded_events).join
143
100
  end
144
101
  end
145
102
  end
@@ -10,10 +10,11 @@ module Datadog
10
10
  class Agentless < Base
11
11
  attr_reader :api_key
12
12
 
13
- def initialize(api_key:, citestcycle_url:, api_url:)
13
+ def initialize(api_key:, citestcycle_url:, api_url:, citestcov_url:)
14
14
  @api_key = api_key
15
15
  @citestcycle_http = build_http_client(citestcycle_url, compress: true)
16
16
  @api_http = build_http_client(api_url, compress: false)
17
+ @citestcov_http = build_http_client(citestcov_url, compress: true)
17
18
  end
18
19
 
19
20
  def citestcycle_request(path:, payload:, headers: {}, verb: "post")
@@ -28,6 +29,12 @@ module Datadog
28
29
  perform_request(@api_http, path: path, payload: payload, headers: headers, verb: verb)
29
30
  end
30
31
 
32
+ def citestcov_request(path:, payload:, headers: {}, verb: "post")
33
+ super(path: path, payload: payload, headers: headers, verb: verb)
34
+
35
+ perform_request(@citestcov_http, path: path, payload: @citestcov_payload, headers: headers, verb: verb)
36
+ end
37
+
31
38
  private
32
39
 
33
40
  def perform_request(http_client, path:, payload:, headers:, verb:)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "securerandom"
4
+
3
5
  require_relative "../../ext/transport"
4
6
 
5
7
  module Datadog
@@ -15,6 +17,27 @@ module Datadog
15
17
  headers[Ext::Transport::HEADER_CONTENT_TYPE] ||= Ext::Transport::CONTENT_TYPE_MESSAGEPACK
16
18
  end
17
19
 
20
+ def citestcov_request(path:, payload:, headers: {}, verb: "post")
21
+ citestcov_request_boundary = ::SecureRandom.uuid
22
+
23
+ headers[Ext::Transport::HEADER_CONTENT_TYPE] ||=
24
+ "#{Ext::Transport::CONTENT_TYPE_MULTIPART_FORM_DATA}; boundary=#{citestcov_request_boundary}"
25
+
26
+ @citestcov_payload = [
27
+ "--#{citestcov_request_boundary}",
28
+ 'Content-Disposition: form-data; name="event"; filename="event.json"',
29
+ "Content-Type: application/json",
30
+ "",
31
+ '{"dummy":true}',
32
+ "--#{citestcov_request_boundary}",
33
+ 'Content-Disposition: form-data; name="coverage1"; filename="coverage1.msgpack"',
34
+ "Content-Type: application/msgpack",
35
+ "",
36
+ payload,
37
+ "--#{citestcov_request_boundary}--"
38
+ ].join("\r\n")
39
+ end
40
+
18
41
  def headers_with_default(headers)
19
42
  request_headers = default_headers
20
43
  request_headers.merge!(headers)
@@ -24,15 +24,24 @@ module Datadog
24
24
  api_url = settings.ci.agentless_url ||
25
25
  "https://#{Ext::Transport::DD_API_HOST_PREFIX}.#{dd_site}:443"
26
26
 
27
- Agentless.new(api_key: settings.api_key, citestcycle_url: citestcycle_url, api_url: api_url)
27
+ citestcov_url = settings.ci.agentless_url ||
28
+ "https://#{Ext::Transport::TEST_COVERAGE_INTAKE_HOST_PREFIX}.#{dd_site}:443"
29
+
30
+ Agentless.new(
31
+ api_key: settings.api_key,
32
+ citestcycle_url: citestcycle_url,
33
+ api_url: api_url,
34
+ citestcov_url: citestcov_url
35
+ )
28
36
  end
29
37
 
30
38
  def self.build_evp_proxy_api(settings)
31
39
  agent_settings = Datadog::Core::Configuration::AgentSettingsResolver.call(settings)
32
- negotiation = Datadog::Core::Remote::Negotiation.new(settings, agent_settings)
33
-
34
- # temporary, remove this when patch will be accepted in Core to make logging configurable
35
- negotiation.instance_variable_set(:@logged, {no_config_endpoint: true})
40
+ negotiation = Datadog::Core::Remote::Negotiation.new(
41
+ settings,
42
+ agent_settings,
43
+ suppress_logging: {no_config_endpoint: true}
44
+ )
36
45
 
37
46
  evp_proxy_path_prefix = Ext::Transport::EVP_PROXY_PATH_PREFIXES.find do |path_prefix|
38
47
  negotiation.endpoint?(path_prefix)
@@ -38,6 +38,14 @@ module Datadog
38
38
  perform_request(@agent_api_http, path: path, payload: payload, headers: headers, verb: verb)
39
39
  end
40
40
 
41
+ def citestcov_request(path:, payload:, headers: {}, verb: "post")
42
+ super(path: path, payload: payload, headers: headers, verb: verb)
43
+
44
+ headers[Ext::Transport::HEADER_EVP_SUBDOMAIN] = Ext::Transport::TEST_COVERAGE_INTAKE_HOST_PREFIX
45
+
46
+ perform_request(@agent_intake_http, path: path, payload: @citestcov_payload, headers: headers, verb: verb)
47
+ end
48
+
41
49
  private
42
50
 
43
51
  def perform_request(http_client, path:, payload:, headers:, verb:)
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "msgpack"
4
+
5
+ require "datadog/core/encoding"
6
+ require "datadog/core/chunker"
7
+
8
+ module Datadog
9
+ module CI
10
+ module Transport
11
+ class EventPlatformTransport
12
+ DEFAULT_MAX_PAYLOAD_SIZE = 5 * 1024 * 1024
13
+
14
+ attr_reader :api,
15
+ :max_payload_size
16
+
17
+ def initialize(api:, max_payload_size: DEFAULT_MAX_PAYLOAD_SIZE)
18
+ @api = api
19
+ @max_payload_size = max_payload_size
20
+ end
21
+
22
+ def send_events(events)
23
+ return [] if events.nil? || events.empty?
24
+
25
+ Datadog.logger.debug { "[#{self.class.name}] Sending #{events.count} events..." }
26
+
27
+ encoded_events = encode_events(events)
28
+ if encoded_events.empty?
29
+ Datadog.logger.debug { "[#{self.class.name}] Empty encoded events list, skipping send" }
30
+ return []
31
+ end
32
+
33
+ responses = []
34
+
35
+ Datadog::Core::Chunker.chunk_by_size(encoded_events, max_payload_size).map do |chunk|
36
+ encoded_payload = pack_events(chunk)
37
+ Datadog.logger.debug do
38
+ "[#{self.class.name}] Send chunk of #{chunk.count} events; payload size #{encoded_payload.size}"
39
+ end
40
+
41
+ response = send_payload(encoded_payload)
42
+
43
+ responses << response
44
+ end
45
+
46
+ responses
47
+ end
48
+
49
+ private
50
+
51
+ def encoder
52
+ Datadog::Core::Encoding::MsgpackEncoder
53
+ end
54
+
55
+ def pack_events(encoded_events)
56
+ packer = MessagePack::Packer.new
57
+
58
+ write_payload_header(packer)
59
+
60
+ packer.write_array_header(encoded_events.count)
61
+ (packer.buffer.to_a + encoded_events).join
62
+ end
63
+
64
+ def event_too_large?(event, encoded_event)
65
+ return false unless encoded_event.size > max_payload_size
66
+
67
+ # This single event is too large, we can't flush it
68
+ Datadog.logger.warn("[#{self.class.name}] Dropping coverage event. Payload too large: '#{event.inspect}'")
69
+ Datadog.logger.warn(encoded_event)
70
+
71
+ true
72
+ end
73
+
74
+ def send_payload(encoded_payload)
75
+ raise NotImplementedError
76
+ end
77
+
78
+ def encode_events(events)
79
+ raise NotImplementedError
80
+ end
81
+
82
+ def write_payload_header(packer)
83
+ raise NotImplementedError
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end