datadog-ci 0.8.2 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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