datadog-ci 1.22.0 → 1.23.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: 64b3da927cc62294a16d73045c2585a0d943d81d638b9ac61f95f10b2f69f6b6
4
- data.tar.gz: 72451663fb18398c49bfa61ac43b0317e21792853d2681b44e77239e932d1bfc
3
+ metadata.gz: 2a07d37d208941128fa6c8e31303578021e07a2974e9b5fb27cd0bb7dd64baea
4
+ data.tar.gz: c9e258132d7e8fc0278db80c6659b96a2c2d68ac6f7c7a0f2b6720f98052dc38
5
5
  SHA512:
6
- metadata.gz: 209a3a9d3c6c6081b864231e0c63742f34c5ba64b51c60e879742410a3da16bac7eefc802dd18a23bed30f9cdc4be0282258d51c28fdac560792a91423acaef7
7
- data.tar.gz: 13bb63efb8388ac57a396c299fc5d36f8bda7889914c31cee7eacc409a4bbf9fad70e96e7b01b8ce2cf2139fd467f8f1dda21fe7090e826958440e701abbcfdc
6
+ metadata.gz: 445771d8c3192ed674a30541c9b89444d4ecf3c95728f156cceac42d08c1fb800ca163876546b8cd5fe25dcabe17a2a47471efa51a71d539385795d9264de813
7
+ data.tar.gz: 62a95307714de058754837a753aed50564e8490fdfebc75472a11b464c417ed7020d59cbad18e69b622863e45a3699b5628229cd3b7fcd7781fe2a4d37feb3fb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.23.0] - 2025-10-08
4
+
5
+
6
+ ### Changed
7
+ * In test discovery mode output suiteSourceFile for each test ([#400][])
8
+ * DDTest integration changes ([#407][])
9
+ * Support context propagation from Datadog test runner ([#399][])
10
+
11
+ ### Fixed
12
+ * Fix issue when parallel_tests doesn't track test sessions when using manual instrumentation ([#410][])
13
+
14
+ ## [1.22.1] - 2025-08-25
15
+
16
+ ### Fixed
17
+
18
+ * Fix configuration race condition that caused undefined library behaviour (marking old tests as new as an example) ([#402][])
19
+
3
20
  ## [1.22.0] - 2025-08-12
4
21
 
5
22
  ### Added
@@ -514,7 +531,9 @@ Currently test suite level visibility is not used by our instrumentation: it wil
514
531
 
515
532
  - Ruby versions < 2.7 no longer supported ([#8][])
516
533
 
517
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.22.0...main
534
+ [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.0...main
535
+ [1.23.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.22.1...v1.23.0
536
+ [1.22.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.22.0...v1.22.1
518
537
  [1.22.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.21.1...v1.22.0
519
538
  [1.21.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.21.0...v1.21.1
520
539
  [1.21.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.20.2...v1.21.0
@@ -731,4 +750,9 @@ Currently test suite level visibility is not used by our instrumentation: it wil
731
750
  [#391]: https://github.com/DataDog/datadog-ci-rb/issues/391
732
751
  [#393]: https://github.com/DataDog/datadog-ci-rb/issues/393
733
752
  [#394]: https://github.com/DataDog/datadog-ci-rb/issues/394
734
- [#396]: https://github.com/DataDog/datadog-ci-rb/issues/396
753
+ [#396]: https://github.com/DataDog/datadog-ci-rb/issues/396
754
+ [#399]: https://github.com/DataDog/datadog-ci-rb/issues/399
755
+ [#400]: https://github.com/DataDog/datadog-ci-rb/issues/400
756
+ [#402]: https://github.com/DataDog/datadog-ci-rb/issues/402
757
+ [#407]: https://github.com/DataDog/datadog-ci-rb/issues/407
758
+ [#410]: https://github.com/DataDog/datadog-ci-rb/issues/410
@@ -2,7 +2,6 @@
2
2
 
3
3
  require_relative "../../ext/test"
4
4
  require_relative "ext"
5
- require_relative "helpers"
6
5
 
7
6
  module Datadog
8
7
  module CI
@@ -3,7 +3,6 @@
3
3
  require_relative "../../ext/test"
4
4
  require_relative "../instrumentation"
5
5
  require_relative "ext"
6
- require_relative "helpers"
7
6
 
8
7
  module Datadog
9
8
  module CI
@@ -33,7 +32,8 @@ module Datadog
33
32
 
34
33
  result = super
35
34
  return result unless test_module && test_session
36
- return result if Helpers.parallel_tests?
35
+ # distributed test session must end in the parent process (for RSpec it would be parallel_tests CLI)
36
+ return result if test_session.distributed
37
37
 
38
38
  if result != 0
39
39
  test_module.failed!
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module Ext
6
+ # Constants for integration with DDTest tool: https://github.com/DataDog/ddtest
7
+ module DDTest
8
+ PLAN_FOLDER = ".testoptimization"
9
+ TESTOPTIMIZATION_CACHE_PATH = "#{PLAN_FOLDER}/cache"
10
+
11
+ SETTINGS_FILE_NAME = "settings.json"
12
+ KNOWN_TESTS_FILE_NAME = "known_tests.json"
13
+ TEST_MANAGEMENT_TESTS_FILE_NAME = "test_management_tests.json"
14
+ SKIPPABLE_TESTS_FILE_NAME = "skippable_tests.json"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -6,7 +6,7 @@ module Datadog
6
6
  # Defines constants for test discovery mode
7
7
  module TestDiscovery
8
8
  # Default output path for test discovery mode
9
- DEFAULT_OUTPUT_PATH = "./.dd/test_discovery/tests.json"
9
+ DEFAULT_OUTPUT_PATH = "./#{DDTest::PLAN_FOLDER}/test_discovery/tests.json"
10
10
 
11
11
  # Maximum buffer size before writing to file
12
12
  MAX_BUFFER_SIZE = 10_000
@@ -35,7 +35,7 @@ module Datadog
35
35
  # launch configuration workers
36
36
  configuration_workers.each(&:perform)
37
37
 
38
- # block until all workers are done (or 60 seconds has passed)
38
+ # block until all workers are done
39
39
  configuration_workers.each(&:wait_until_done)
40
40
  end
41
41
 
@@ -54,11 +54,25 @@ module Datadog
54
54
  FILE_STORAGE_KEY
55
55
  end
56
56
 
57
+ def restore_state_from_datadog_test_runner
58
+ Datadog.logger.debug { "Restoring library configuration from DDTest cache" }
59
+
60
+ settings = load_json(Ext::DDTest::SETTINGS_FILE_NAME)
61
+ if settings.nil?
62
+ Datadog.logger.debug { "Restoring library configuration failed, will request again" }
63
+ return false
64
+ end
65
+
66
+ Datadog.logger.debug { "Restored library configuration from DDTest: #{settings}" }
67
+ @library_configuration = LibrarySettings.from_json(settings)
68
+ true
69
+ end
70
+
57
71
  private
58
72
 
59
73
  def fetch_library_configuration(test_session)
60
74
  # In test discovery mode, skip backend fetching and use default settings (everything is disabled)
61
- return @library_configuration = LibrarySettings.new(nil) if @test_discovery_enabled
75
+ return @library_configuration = LibrarySettings.from_http_response(nil) if @test_discovery_enabled
62
76
 
63
77
  # skip backend request if library configuration was loaded by a different process and stored on disk
64
78
  library_configuration_loaded = load_component_state
@@ -14,9 +14,12 @@ module Datadog
14
14
  module Remote
15
15
  # Wrapper around the settings HTTP response
16
16
  class LibrarySettings
17
- def initialize(http_response)
18
- @http_response = http_response
19
- @json = nil
17
+ def self.from_http_response(http_response)
18
+ new(http_response, nil)
19
+ end
20
+
21
+ def self.from_json(json)
22
+ new(nil, json)
20
23
  end
21
24
 
22
25
  def ok?
@@ -148,6 +151,11 @@ module Datadog
148
151
 
149
152
  private
150
153
 
154
+ def initialize(http_response, json)
155
+ @http_response = http_response
156
+ @json = json
157
+ end
158
+
151
159
  def test_management_payload
152
160
  payload.fetch(
153
161
  Ext::Transport::DD_API_SETTINGS_RESPONSE_TEST_MANAGEMENT_KEY,
@@ -25,7 +25,7 @@ module Datadog
25
25
 
26
26
  def fetch(test_session)
27
27
  api = @api
28
- return LibrarySettings.new(nil) unless api
28
+ return LibrarySettings.from_http_response(nil) unless api
29
29
 
30
30
  request_payload = payload(test_session)
31
31
  Datadog.logger.debug("Fetching library settings with request: #{request_payload}")
@@ -51,7 +51,7 @@ module Datadog
51
51
  )
52
52
  end
53
53
 
54
- library_settings = LibrarySettings.new(http_response)
54
+ library_settings = LibrarySettings.from_http_response(http_response)
55
55
 
56
56
  Utils::Telemetry.inc(
57
57
  Ext::Telemetry::METRIC_GIT_REQUESTS_SETTINGS_RESPONSE,
@@ -86,6 +86,7 @@ module Datadog
86
86
  "name" => test.name,
87
87
  "suite" => test.test_suite_name,
88
88
  "sourceFile" => test.source_file,
89
+ "suiteSourceFile" => test.test_suite&.source_file,
89
90
  "fqn" => test.datadog_test_id
90
91
  }
91
92
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../ext/telemetry"
4
4
  require_relative "../ext/test"
5
+ require_relative "../ext/dd_test"
5
6
  require_relative "../utils/stateful"
6
7
  require_relative "../utils/telemetry"
7
8
  require_relative "../utils/test_run"
@@ -35,11 +36,11 @@ module Datadog
35
36
 
36
37
  test_session.set_tag(Ext::Test::TAG_TEST_MANAGEMENT_ENABLED, "true")
37
38
 
38
- # Load component state first, and if successful, skip fetching tests properties
39
- if !load_component_state
40
- @tests_properties = @tests_properties_client.fetch(test_session)
41
- store_component_state if test_session.distributed
42
- end
39
+ return if restore_state_from_datadog_test_runner
40
+ return if load_component_state
41
+
42
+ @tests_properties = @tests_properties_client.fetch(test_session)
43
+ store_component_state if test_session.distributed
43
44
 
44
45
  Utils::Telemetry.distribution(
45
46
  Ext::Telemetry::METRIC_TEST_MANAGEMENT_TESTS_RESPONSE_TESTS,
@@ -74,6 +75,33 @@ module Datadog
74
75
  test_properties.fetch("attempt_to_fix", false)
75
76
  end
76
77
 
78
+ def restore_state_from_datadog_test_runner
79
+ Datadog.logger.debug { "Restoring test management tests from DDTest cache" }
80
+
81
+ test_management_data = load_json(Ext::DDTest::TEST_MANAGEMENT_TESTS_FILE_NAME)
82
+ if test_management_data.nil?
83
+ Datadog.logger.debug { "Restoring test management tests failed, will request again" }
84
+ return false
85
+ end
86
+
87
+ Datadog.logger.debug { "Restored test management tests from DDTest: #{test_management_data}" }
88
+
89
+ # Use the TestsProperties::Response class method to parse the JSON data
90
+ # Wrap the data in the expected backend API format
91
+ wrapped_data = {
92
+ "data" => {
93
+ "attributes" => test_management_data
94
+ }
95
+ }
96
+
97
+ tests_properties_response = TestsProperties::Response.from_json(wrapped_data)
98
+ @tests_properties = tests_properties_response.tests
99
+
100
+ Datadog.logger.debug { "Found [#{@tests_properties.size}] test management tests from context" }
101
+
102
+ true
103
+ end
104
+
77
105
  # Implementation of Stateful interface
78
106
  def serialize_state
79
107
  {
@@ -15,9 +15,12 @@ module Datadog
15
15
  # fetches and stores a map of tests to their test management properties from the backend
16
16
  class TestsProperties
17
17
  class Response
18
- def initialize(http_response)
19
- @http_response = http_response
20
- @json = nil
18
+ def self.from_http_response(http_response)
19
+ new(http_response, nil)
20
+ end
21
+
22
+ def self.from_json(json)
23
+ new(nil, json)
21
24
  end
22
25
 
23
26
  def ok?
@@ -51,6 +54,11 @@ module Datadog
51
54
 
52
55
  private
53
56
 
57
+ def initialize(http_response, json)
58
+ @http_response = http_response
59
+ @json = json
60
+ end
61
+
54
62
  def payload
55
63
  cached = @json
56
64
  return cached unless cached.nil?
@@ -106,7 +114,7 @@ module Datadog
106
114
  )
107
115
  end
108
116
 
109
- Response.new(http_response).tests
117
+ Response.from_http_response(http_response).tests
110
118
  end
111
119
 
112
120
  private
@@ -6,6 +6,7 @@ require "datadog/core/telemetry/logging"
6
6
 
7
7
  require_relative "../ext/test"
8
8
  require_relative "../ext/telemetry"
9
+ require_relative "../ext/dd_test"
9
10
 
10
11
  require_relative "../git/local_repository"
11
12
 
@@ -84,7 +85,11 @@ module Datadog
84
85
  load_datadog_cov! if @code_coverage_enabled
85
86
 
86
87
  # Load component state first, and if successful, skip fetching skippable tests
87
- if skipping_tests? && !load_component_state
88
+ # Also try to restore from DDTest cache if available
89
+ if skipping_tests?
90
+ return if load_component_state
91
+ return if restore_state_from_datadog_test_runner
92
+
88
93
  fetch_skippable_tests(test_session)
89
94
  store_component_state if test_session.distributed
90
95
  end
@@ -210,8 +215,89 @@ module Datadog
210
215
  FILE_STORAGE_KEY
211
216
  end
212
217
 
218
+ def restore_state_from_datadog_test_runner
219
+ Datadog.logger.debug { "Restoring skippable tests from DDTest cache" }
220
+
221
+ skippable_tests_data = load_json(Ext::DDTest::SKIPPABLE_TESTS_FILE_NAME)
222
+ if skippable_tests_data.nil?
223
+ Datadog.logger.debug { "Restoring skippable tests failed, will request again" }
224
+ return false
225
+ end
226
+
227
+ Datadog.logger.debug { "Restored skippable tests from DDTest: #{skippable_tests_data}" }
228
+
229
+ transformed_data = transform_test_runner_data(skippable_tests_data)
230
+
231
+ Datadog.logger.debug { "Skippable tests after transformation: #{transformed_data}" }
232
+
233
+ # Use the Skippable::Response class to parse the transformed data
234
+ skippable_response = Skippable::Response.from_json(transformed_data)
235
+
236
+ @mutex.synchronize do
237
+ @correlation_id = skippable_response.correlation_id
238
+ @skippable_tests = skippable_response.tests
239
+ end
240
+
241
+ Datadog.logger.debug { "Found [#{@skippable_tests.size}] skippable tests from context" }
242
+ Datadog.logger.debug { "ITR correlation ID from context: #{@correlation_id}" }
243
+
244
+ true
245
+ end
246
+
213
247
  private
214
248
 
249
+ # Transforms Test Runner skippable tests data format to the format expected by Skippable::Response
250
+ #
251
+ # Test Runner format:
252
+ # {
253
+ # "correlationId": "abc123",
254
+ # "skippableTests": {
255
+ # "suite_name": {
256
+ # "test_name": [{"suite": "suite_name", "name": "test_name", "parameters": "{...}", "configurations": {}}]
257
+ # }
258
+ # }
259
+ # }
260
+ #
261
+ # Expected format:
262
+ # {
263
+ # "meta": {"correlation_id": "abc123"},
264
+ # "data": [{"type": "test", "attributes": {"suite": "suite_name", "name": "test_name", "parameters": "{...}"}}]
265
+ # }
266
+ def transform_test_runner_data(skippable_tests_data)
267
+ skippable_tests = skippable_tests_data.fetch("skippableTests", {})
268
+
269
+ # Pre-calculate array size for better memory allocation
270
+ total_test_configs = skippable_tests.sum do |_, tests_hash|
271
+ tests_hash.sum { |_, test_configs| test_configs.size }
272
+ end
273
+
274
+ data_array = Array.new(total_test_configs)
275
+ index = 0
276
+
277
+ skippable_tests.each_value do |tests_hash|
278
+ tests_hash.each_value do |test_configs|
279
+ test_configs.each do |test_config|
280
+ data_array[index] = {
281
+ "type" => Ext::Test::ITR_TEST_SKIPPING_MODE,
282
+ "attributes" => {
283
+ "suite" => test_config["suite"],
284
+ "name" => test_config["name"],
285
+ "parameters" => test_config["parameters"]
286
+ }
287
+ }
288
+ index += 1
289
+ end
290
+ end
291
+ end
292
+
293
+ {
294
+ "meta" => {
295
+ "correlation_id" => skippable_tests_data["correlationId"]
296
+ },
297
+ "data" => data_array
298
+ }
299
+ end
300
+
215
301
  def write(event)
216
302
  # skip sending events if writer is not configured
217
303
  @coverage_writer&.write(event)
@@ -14,9 +14,12 @@ module Datadog
14
14
  module TestOptimisation
15
15
  class Skippable
16
16
  class Response
17
- def initialize(http_response)
18
- @http_response = http_response
19
- @json = nil
17
+ def self.from_http_response(http_response)
18
+ new(http_response, nil)
19
+ end
20
+
21
+ def self.from_json(json)
22
+ new(nil, json)
20
23
  end
21
24
 
22
25
  def ok?
@@ -50,6 +53,11 @@ module Datadog
50
53
 
51
54
  private
52
55
 
56
+ def initialize(http_response, json)
57
+ @http_response = http_response
58
+ @json = json
59
+ end
60
+
53
61
  def payload
54
62
  cached = @json
55
63
  return cached unless cached.nil?
@@ -74,7 +82,7 @@ module Datadog
74
82
 
75
83
  def fetch_skippable_tests(test_session)
76
84
  api = @api
77
- return Response.new(nil) unless api
85
+ return Response.from_http_response(nil) unless api
78
86
 
79
87
  request_payload = payload(test_session)
80
88
  Datadog.logger.debug("Fetching skippable tests with request: #{request_payload}")
@@ -105,7 +113,7 @@ module Datadog
105
113
  )
106
114
  end
107
115
 
108
- Response.new(http_response)
116
+ Response.from_http_response(http_response)
109
117
  end
110
118
 
111
119
  private
@@ -6,6 +6,7 @@ require "rbconfig"
6
6
  require "datadog/core/utils/forking"
7
7
 
8
8
  require_relative "context"
9
+ require_relative "known_tests"
9
10
  require_relative "telemetry"
10
11
  require_relative "total_coverage"
11
12
 
@@ -226,6 +227,36 @@ module Datadog
226
227
  (forked? && !@context_service_uri.nil? && !@context_service_uri.empty?) || @is_client_process
227
228
  end
228
229
 
230
+ def restore_state_from_datadog_test_runner
231
+ Datadog.logger.debug { "Restoring known tests from DDTest cache" }
232
+
233
+ known_tests_data = load_json(Ext::DDTest::KNOWN_TESTS_FILE_NAME)
234
+ if known_tests_data.nil?
235
+ Datadog.logger.debug { "Restoring known tests failed, will request again" }
236
+ return false
237
+ end
238
+
239
+ Datadog.logger.debug { "Restored known tests from DDTest: #{known_tests_data}" }
240
+
241
+ # Use the KnownTests class method to parse the JSON data
242
+ known_tests_data = {
243
+ "data" => {
244
+ "attributes" => known_tests_data
245
+ }
246
+ }
247
+
248
+ @known_tests = KnownTests::Response.from_json(known_tests_data).tests
249
+ @known_tests_enabled = !@known_tests.empty?
250
+
251
+ unless @known_tests_enabled
252
+ Datadog.logger.debug("Empty set of known tests from the DDTest cache file")
253
+ end
254
+
255
+ Datadog.logger.debug { "Found [#{@known_tests.size}] known tests from context" }
256
+
257
+ true
258
+ end
259
+
229
260
  private
230
261
 
231
262
  # DOMAIN EVENTS
@@ -398,6 +429,9 @@ module Datadog
398
429
  end
399
430
 
400
431
  def new_test?(test_span)
432
+ # check if @known_tests set is empty again
433
+ # to ensure that we don't tag tests as new unnecessarily
434
+ @known_tests_enabled = false if @known_tests_enabled && @known_tests.empty?
401
435
  return false unless @known_tests_enabled
402
436
 
403
437
  test_id = Utils::TestRun.datadog_test_id(test_span.name, test_span.test_suite_name)
@@ -14,9 +14,12 @@ module Datadog
14
14
  # fetches and stores a list of known tests from the backend
15
15
  class KnownTests
16
16
  class Response
17
- def initialize(http_response)
18
- @http_response = http_response
19
- @json = nil
17
+ def self.from_http_response(http_response)
18
+ new(http_response, nil)
19
+ end
20
+
21
+ def self.from_json(json)
22
+ new(nil, json)
20
23
  end
21
24
 
22
25
  def ok?
@@ -44,6 +47,11 @@ module Datadog
44
47
 
45
48
  private
46
49
 
50
+ def initialize(http_response, json)
51
+ @http_response = http_response
52
+ @json = json
53
+ end
54
+
47
55
  def payload
48
56
  cached = @json
49
57
  return cached unless cached.nil?
@@ -99,7 +107,7 @@ module Datadog
99
107
  )
100
108
  end
101
109
 
102
- Response.new(http_response).tests
110
+ Response.from_http_response(http_response).tests
103
111
  end
104
112
 
105
113
  private
@@ -9,8 +9,6 @@ module Datadog
9
9
  module Store
10
10
  # This context is shared between threads and represents the current test session and test module.
11
11
  class Process
12
- attr_reader :readonly_test_session, :readonly_test_module
13
-
14
12
  def initialize
15
13
  # we are using Monitor instead of Mutex because it is reentrant
16
14
  @mutex = Monitor.new
@@ -52,11 +50,11 @@ module Datadog
52
50
  end
53
51
 
54
52
  def active_test_module
55
- @test_module
53
+ @mutex.synchronize { @test_module }
56
54
  end
57
55
 
58
56
  def active_test_session
59
- @test_session
57
+ @mutex.synchronize { @test_session }
60
58
  end
61
59
 
62
60
  def active_test_suite(test_suite_name)
@@ -82,16 +80,28 @@ module Datadog
82
80
  @mutex.synchronize { @test_suites.delete(test_suite_name) }
83
81
  end
84
82
 
83
+ def readonly_test_session
84
+ @mutex.synchronize { @readonly_test_session }
85
+ end
86
+
87
+ def readonly_test_module
88
+ @mutex.synchronize { @readonly_test_module }
89
+ end
90
+
85
91
  def set_readonly_test_session(remote_test_session)
86
92
  return if remote_test_session.nil?
87
93
 
88
- @readonly_test_session = Datadog::CI::ReadonlyTestSession.new(remote_test_session)
94
+ @mutex.synchronize do
95
+ @readonly_test_session = Datadog::CI::ReadonlyTestSession.new(remote_test_session)
96
+ end
89
97
  end
90
98
 
91
99
  def set_readonly_test_module(remote_test_module)
92
100
  return if remote_test_module.nil?
93
101
 
94
- @readonly_test_module = Datadog::CI::ReadonlyTestModule.new(remote_test_module)
102
+ @mutex.synchronize do
103
+ @readonly_test_module = Datadog::CI::ReadonlyTestModule.new(remote_test_module)
104
+ end
95
105
  end
96
106
  end
97
107
  end
@@ -24,6 +24,21 @@ module Datadog
24
24
  MAX_RETRIES = 3
25
25
  INITIAL_BACKOFF = 1
26
26
  MAX_BACKOFF = 30
27
+ MAX_RETRY_TIME = 50
28
+
29
+ # Errors that should not be retried - fail fast
30
+ NON_RETRIABLE_ERRORS = [
31
+ Timeout::Error, # Don't slow down customers with timeouts
32
+ Errno::EINVAL, # Invalid argument
33
+ Net::HTTPBadResponse # Malformed response - likely persistent issue
34
+ ].freeze
35
+
36
+ # Errors that can be retried - transient network issues
37
+ RETRIABLE_ERRORS = [
38
+ Errno::ECONNRESET, # Connection reset by peer
39
+ EOFError, # Unexpected connection close
40
+ SocketError # DNS/network issues
41
+ ].freeze
27
42
 
28
43
  def initialize(host:, port:, timeout: DEFAULT_TIMEOUT, ssl: true, compress: false)
29
44
  @host = host
@@ -78,7 +93,7 @@ module Datadog
78
93
 
79
94
  private
80
95
 
81
- def perform_http_call(path:, payload:, headers:, verb:, retries: MAX_RETRIES, backoff: INITIAL_BACKOFF)
96
+ def perform_http_call(path:, payload:, headers:, verb:, retries: MAX_RETRIES, backoff: INITIAL_BACKOFF, retry_start_time: Core::Utils::Time.get_time)
82
97
  response = nil
83
98
 
84
99
  begin
@@ -98,12 +113,23 @@ module Datadog
98
113
  else
99
114
  return response
100
115
  end
101
- rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, SocketError, Net::HTTPBadResponse => e
102
- Datadog.logger.debug { "Failed to send request with #{e} (#{e.message})" }
103
-
116
+ rescue *NON_RETRIABLE_ERRORS => e
117
+ Datadog.logger.debug { "Failed to send request with non-retriable error #{e} (#{e.message})" }
118
+ return ErrorResponse.new(e)
119
+ rescue *RETRIABLE_ERRORS => e
120
+ Datadog.logger.debug { "Failed to send request with retriable error #{e} (#{e.message})" }
104
121
  response = ErrorResponse.new(e)
105
122
  end
106
123
 
124
+ # Check if we've exceeded the maximum retry time
125
+ elapsed_time_seconds = Core::Utils::Time.get_time - retry_start_time
126
+ if elapsed_time_seconds >= MAX_RETRY_TIME
127
+ Datadog.logger.debug(
128
+ "Failed to send request to #{path} after #{elapsed_time_seconds.round(2)} seconds (exceeded MAX_RETRY_TIME of #{MAX_RETRY_TIME}s)"
129
+ )
130
+ return response
131
+ end
132
+
107
133
  if retries.positive? && backoff <= MAX_BACKOFF
108
134
  sleep(backoff)
109
135
 
@@ -113,11 +139,12 @@ module Datadog
113
139
  headers: headers,
114
140
  verb: verb,
115
141
  retries: retries - 1,
116
- backoff: backoff * 2
142
+ backoff: backoff * 2,
143
+ retry_start_time: retry_start_time
117
144
  )
118
145
  else
119
- Datadog.logger.error(
120
- "Failed to send request after #{MAX_RETRIES - retries} retries (current backoff value #{backoff})"
146
+ Datadog.logger.debug(
147
+ "Failed to send request to #{path} after #{MAX_RETRIES - retries} retries (current backoff value #{backoff})"
121
148
  )
122
149
 
123
150
  response
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
3
4
  require_relative "file_storage"
5
+ require_relative "../ext/dd_test"
4
6
 
5
7
  module Datadog
6
8
  module CI
@@ -19,6 +21,12 @@ module Datadog
19
21
 
20
22
  # Load component state
21
23
  def load_component_state
24
+ # Check for DDTest cache first
25
+ if Dir.exist?(Ext::DDTest::TESTOPTIMIZATION_CACHE_PATH)
26
+ Datadog.logger.debug { "DDTest cache found" }
27
+ return true if restore_state_from_datadog_test_runner
28
+ end
29
+
22
30
  test_visibility_component = Datadog.send(:components).test_visibility
23
31
  return false unless test_visibility_component.client_process?
24
32
 
@@ -46,6 +54,28 @@ module Datadog
46
54
  def storage_key
47
55
  raise NotImplementedError, "Components must implement #storage_key"
48
56
  end
57
+
58
+ def restore_state_from_datadog_test_runner
59
+ false
60
+ end
61
+
62
+ def load_json(file_name)
63
+ file_path = File.join(Ext::DDTest::TESTOPTIMIZATION_CACHE_PATH, file_name)
64
+
65
+ unless File.exist?(file_path)
66
+ Datadog.logger.debug { "JSON file not found: #{file_path}" }
67
+ return nil
68
+ end
69
+
70
+ content = File.read(file_path)
71
+ JSON.parse(content)
72
+ rescue JSON::ParserError => e
73
+ Datadog.logger.debug { "Failed to parse JSON file #{file_path}: #{e.message}" }
74
+ nil
75
+ rescue => e
76
+ Datadog.logger.debug { "Failed to load JSON file #{file_path}: #{e.message}" }
77
+ nil
78
+ end
49
79
  end
50
80
  end
51
81
  end
@@ -4,7 +4,7 @@ module Datadog
4
4
  module CI
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 22
7
+ MINOR = 23
8
8
  PATCH = 0
9
9
  PRE = nil
10
10
  BUILD = nil
@@ -11,13 +11,12 @@ module Datadog
11
11
  include Datadog::Core::Workers::Async::Thread
12
12
 
13
13
  DEFAULT_SHUTDOWN_TIMEOUT = 60
14
- DEFAULT_WAIT_TIMEOUT = 60
15
14
 
16
15
  def stop(timeout = DEFAULT_SHUTDOWN_TIMEOUT)
17
16
  join(timeout)
18
17
  end
19
18
 
20
- def wait_until_done(timeout = DEFAULT_WAIT_TIMEOUT)
19
+ def wait_until_done(timeout = nil)
21
20
  join(timeout)
22
21
  end
23
22
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog-ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.22.0
4
+ version: 1.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
@@ -143,7 +143,6 @@ files:
143
143
  - lib/datadog/ci/contrib/rspec/example.rb
144
144
  - lib/datadog/ci/contrib/rspec/example_group.rb
145
145
  - lib/datadog/ci/contrib/rspec/ext.rb
146
- - lib/datadog/ci/contrib/rspec/helpers.rb
147
146
  - lib/datadog/ci/contrib/rspec/integration.rb
148
147
  - lib/datadog/ci/contrib/rspec/patcher.rb
149
148
  - lib/datadog/ci/contrib/rspec/runner.rb
@@ -166,6 +165,7 @@ files:
166
165
  - lib/datadog/ci/contrib/simplecov/patcher.rb
167
166
  - lib/datadog/ci/contrib/simplecov/result_extractor.rb
168
167
  - lib/datadog/ci/ext/app_types.rb
168
+ - lib/datadog/ci/ext/dd_test.rb
169
169
  - lib/datadog/ci/ext/environment.rb
170
170
  - lib/datadog/ci/ext/environment/configuration_discrepancy_checker.rb
171
171
  - lib/datadog/ci/ext/environment/extractor.rb
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Datadog
4
- module CI
5
- module Contrib
6
- module RSpec
7
- # Helper methods for RSpec instrumentation
8
- module Helpers
9
- def self.parallel_tests?
10
- !!ENV.fetch("TEST_ENV_NUMBER", nil) && !!ENV.fetch("PARALLEL_TEST_GROUPS", nil)
11
- end
12
- end
13
- end
14
- end
15
- end
16
- end