datadog-ci 1.30.0 → 1.31.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: 129bc549b30318587b435c3836517faa058dac0860f07eca408fc5562186e52f
4
- data.tar.gz: 4a8c2756b9c846a4b5b225c51e0d1b3910e24c550f60adce4b47f968981a2b8d
3
+ metadata.gz: de36af96e9eef5f7bffcfe3a3cb8e80ff08461b8f10795bff6a54e53d340a1c6
4
+ data.tar.gz: 5bdf32ef644ecaac3c57b55f94338359db56e18bea76fec6c40c76fbd1a907bd
5
5
  SHA512:
6
- metadata.gz: ee1ceadbb2c83d09c780bfe517d52dd4a55b5a4a4dc6802fea1e653fe06b308e7eda9a90a420c1ff855b59b27a7ada8253f319f55664ade19cceb5cab79e3c60
7
- data.tar.gz: f297af41a17c77ef5c123ad71a79e92a489218bea1b081d802f414a8496fe7e7ca4da50cde169c290ea3a6c0b189a3987f88f07cdf54bb4c822958527ac26117
6
+ metadata.gz: 702a1219da6c81acc12d6eca8743de08ff97dd5cdf728ff02556bb282673ac913502f49dd4db02bb4ddd119e8102a9ceacc4a081624a48fe256d3f402efa1887
7
+ data.tar.gz: 2470ec968a63e625234fe7cb70e9faadf7ce1ccc5a9cf9f04c668c271b693f6b3a3b38f3b5821e27908f299bf58773122b268decbbf3e65596a63cc39509be04
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.31.0] - 2026-05-20
4
+
5
+ ### Added
6
+
7
+ * Add Test Optimization cache readers for Bazel support and DDTest (test parallelization) ([#511][])
8
+ * Add Jenkins custom parent id env var ([#509][])
9
+
3
10
  ## [1.30.0] - 2026-05-12
4
11
 
5
12
  ### Changed
@@ -634,7 +641,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil
634
641
 
635
642
  - Ruby versions < 2.7 no longer supported ([#8][])
636
643
 
637
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.30.0...main
644
+ [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.31.0...main
645
+ [1.31.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.30.0...v1.31.0
638
646
  [1.30.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.29.0...v1.30.0
639
647
  [1.29.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.28.0...v1.29.0
640
648
  [1.28.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.27.0...v1.28.0
@@ -898,4 +906,6 @@ Currently test suite level visibility is not used by our instrumentation: it wil
898
906
  [#499]: https://github.com/DataDog/datadog-ci-rb/issues/499
899
907
  [#500]: https://github.com/DataDog/datadog-ci-rb/issues/500
900
908
  [#501]: https://github.com/DataDog/datadog-ci-rb/issues/501
901
- [#506]: https://github.com/DataDog/datadog-ci-rb/issues/506
909
+ [#506]: https://github.com/DataDog/datadog-ci-rb/issues/506
910
+ [#509]: https://github.com/DataDog/datadog-ci-rb/issues/509
911
+ [#511]: https://github.com/DataDog/datadog-ci-rb/issues/511
@@ -17,6 +17,8 @@ require_relative "../remote/library_settings_client"
17
17
  require_relative "../test_management/component"
18
18
  require_relative "../test_management/null_component"
19
19
  require_relative "../test_management/tests_properties"
20
+ require_relative "../test_optimization_cache/component"
21
+ require_relative "../test_optimization_cache/null_component"
20
22
  require_relative "../test_impact_analysis/null_component"
21
23
  require_relative "../test_impact_analysis/component"
22
24
  require_relative "../test_impact_analysis/coverage/transport"
@@ -45,7 +47,8 @@ module Datadog
45
47
  # Adds CI behavior to Datadog trace components
46
48
  module Components
47
49
  attr_reader :test_tracing, :test_impact_analysis, :git_tree_upload_worker, :ci_remote, :test_retries,
48
- :test_management, :agentless_logs_submission, :impacted_tests_detection, :test_discovery, :code_coverage
50
+ :test_management, :agentless_logs_submission, :impacted_tests_detection, :test_discovery, :code_coverage,
51
+ :test_optimization_cache
49
52
 
50
53
  def initialize(settings)
51
54
  @test_impact_analysis = TestImpactAnalysis::NullComponent.new
@@ -57,6 +60,7 @@ module Datadog
57
60
  @impacted_tests_detection = ImpactedTestsDetection::NullComponent.new
58
61
  @test_discovery = TestDiscovery::NullComponent.new
59
62
  @code_coverage = CodeCoverage::NullComponent.new
63
+ @test_optimization_cache = TestOptimizationCache::NullComponent.new
60
64
 
61
65
  # Activate CI mode if enabled
62
66
  if settings.ci.enabled
@@ -69,6 +73,7 @@ module Datadog
69
73
  def shutdown!(replacement = nil)
70
74
  super
71
75
 
76
+ @test_optimization_cache&.shutdown!
72
77
  @test_tracing&.shutdown!
73
78
  @test_impact_analysis&.shutdown!
74
79
  @agentless_logs_submission&.shutdown!
@@ -113,6 +118,13 @@ module Datadog
113
118
  )
114
119
  @test_discovery.disable_features_for_test_discovery!(settings)
115
120
 
121
+ @test_optimization_cache = TestOptimizationCache::Component.new(
122
+ manifest_file: settings.ci.test_optimization_cache_manifest_file,
123
+ runfiles_dir: settings.ci.test_optimization_cache_runfiles_dir,
124
+ runfiles_manifest_file: settings.ci.test_optimization_cache_runfiles_manifest_file,
125
+ test_srcdir: settings.ci.test_optimization_cache_test_srcdir
126
+ )
127
+
116
128
  # Configure Datadog::Tracing module
117
129
 
118
130
  # No need not use 128-bit trace ids for test visibility,
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../contrib/instrumentation"
4
4
  require_relative "../ext/settings"
5
+ require_relative "../ext/test_optimization_cache"
5
6
  require_relative "../utils/bundle"
6
7
  require_relative "../utils/parsing"
7
8
 
@@ -180,6 +181,26 @@ module Datadog
180
181
  o.env CI::Ext::Settings::ENV_TEST_DISCOVERY_OUTPUT_PATH
181
182
  end
182
183
 
184
+ option :test_optimization_cache_manifest_file do |o|
185
+ o.type :string, nilable: true
186
+ o.env CI::Ext::TestOptimizationCache::ENV_MANIFEST_FILE
187
+ end
188
+
189
+ option :test_optimization_cache_runfiles_dir do |o|
190
+ o.type :string, nilable: true
191
+ o.env CI::Ext::TestOptimizationCache::ENV_RUNFILES_DIR
192
+ end
193
+
194
+ option :test_optimization_cache_runfiles_manifest_file do |o|
195
+ o.type :string, nilable: true
196
+ o.env CI::Ext::TestOptimizationCache::ENV_RUNFILES_MANIFEST_FILE
197
+ end
198
+
199
+ option :test_optimization_cache_test_srcdir do |o|
200
+ o.type :string, nilable: true
201
+ o.env CI::Ext::TestOptimizationCache::ENV_TEST_SRCDIR
202
+ end
203
+
183
204
  option :tia_static_dependencies_tracking_enabled do |o|
184
205
  o.type :bool
185
206
  o.env CI::Ext::Settings::ENV_TIA_STATIC_DEPENDENCIES_TRACKING_ENABLED
@@ -75,7 +75,8 @@ module Datadog
75
75
 
76
76
  def ci_env_vars
77
77
  {
78
- "DD_CUSTOM_TRACE_ID" => env["DD_CUSTOM_TRACE_ID"]
78
+ "DD_CUSTOM_TRACE_ID" => env["DD_CUSTOM_TRACE_ID"],
79
+ "DD_CUSTOM_PARENT_ID" => env["DD_CUSTOM_PARENT_ID"]
79
80
  }.to_json
80
81
  end
81
82
  end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "test_optimization_cache"
4
+
3
5
  module Datadog
4
6
  module CI
5
7
  module Ext
6
8
  # Defines constants for test discovery mode
7
9
  module TestDiscovery
8
10
  # Default output path for test discovery mode
9
- DEFAULT_OUTPUT_PATH = "./#{DDTest::PLAN_FOLDER}/test_discovery/tests.json"
11
+ DEFAULT_OUTPUT_PATH = "./#{TestOptimizationCache::PLAN_FOLDER}/test_discovery/tests.json"
10
12
 
11
13
  # Maximum buffer size before writing to file
12
14
  MAX_BUFFER_SIZE = 10_000
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module Ext
6
+ # Constants for Test Optimization cache generated by ddtest or Bazel.
7
+ module TestOptimizationCache
8
+ ENV_MANIFEST_FILE = "TEST_OPTIMIZATION_MANIFEST_FILE"
9
+ ENV_RUNFILES_DIR = "RUNFILES_DIR"
10
+ ENV_RUNFILES_MANIFEST_FILE = "RUNFILES_MANIFEST_FILE"
11
+ ENV_TEST_SRCDIR = "TEST_SRCDIR"
12
+
13
+ PLAN_FOLDER = ".testoptimization"
14
+ MANIFEST_FILE_NAME = "manifest.txt"
15
+ SUPPORTED_MANIFEST_VERSION = "1"
16
+
17
+ CACHE_FOLDER_NAME = "cache"
18
+ HTTP_CACHE_FOLDER_NAME = "http"
19
+
20
+ TESTOPTIMIZATION_CACHE_PATH = "#{PLAN_FOLDER}/#{CACHE_FOLDER_NAME}"
21
+ TESTOPTIMIZATION_HTTP_CACHE_PATH = "#{TESTOPTIMIZATION_CACHE_PATH}/#{HTTP_CACHE_FOLDER_NAME}"
22
+
23
+ SETTINGS_FILE_NAME = "settings.json"
24
+ KNOWN_TESTS_FILE_NAME = "known_tests.json"
25
+ TEST_MANAGEMENT_FILE_NAME = "test_management.json"
26
+ LEGACY_TEST_MANAGEMENT_TESTS_FILE_NAME = "test_management_tests.json"
27
+ SKIPPABLE_TESTS_FILE_NAME = "skippable_tests.json"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -10,7 +10,6 @@ require_relative "packfiles"
10
10
 
11
11
  require_relative "../ext/telemetry"
12
12
  require_relative "../utils/telemetry"
13
- require_relative "../utils/test_run"
14
13
 
15
14
  module Datadog
16
15
  module CI
@@ -34,8 +33,8 @@ module Datadog
34
33
  return
35
34
  end
36
35
 
37
- if Utils::TestRun.test_optimization_data_cached?
38
- Datadog.logger.debug("DDTest cache found, git upload already done by DDTest tool, skipping git upload")
36
+ if test_optimization_cache.cache_available?
37
+ Datadog.logger.debug("Test Optimization cache found, git upload already done, skipping git upload")
39
38
  return
40
39
  end
41
40
 
@@ -113,6 +112,10 @@ module Datadog
113
112
  def test_tracing_component
114
113
  Datadog.send(:components).test_tracing
115
114
  end
115
+
116
+ def test_optimization_cache
117
+ Datadog.send(:components).test_optimization_cache
118
+ end
116
119
  end
117
120
  end
118
121
  end
@@ -56,15 +56,15 @@ module Datadog
56
56
  end
57
57
 
58
58
  def restore_state_from_datadog_test_runner
59
- Datadog.logger.debug { "Restoring library configuration from DDTest cache" }
59
+ Datadog.logger.debug { "Restoring library configuration from Test Optimization cache" }
60
60
 
61
- settings = load_json(Ext::DDTest::SETTINGS_FILE_NAME)
61
+ settings = load_cached_settings
62
62
  if settings.nil?
63
63
  Datadog.logger.debug { "Restoring library configuration failed, will request again" }
64
64
  return false
65
65
  end
66
66
 
67
- Datadog.logger.debug { "Restored library configuration from DDTest: #{settings}" }
67
+ Datadog.logger.debug { "Restored library configuration from Test Optimization: #{settings}" }
68
68
  @library_configuration = LibrarySettings.from_json(settings)
69
69
  true
70
70
  end
@@ -75,7 +75,7 @@ module Datadog
75
75
  # In test discovery mode, skip backend fetching and use default settings (everything is disabled)
76
76
  return @library_configuration = LibrarySettings.from_http_response(nil) if @test_discovery_enabled
77
77
 
78
- # skip backend request if library configuration was loaded by a different process and stored on disk
78
+ # skip backend request if library configuration was loaded from external cache or stored on disk
79
79
  library_configuration_loaded = load_component_state
80
80
  return @library_configuration if library_configuration_loaded
81
81
 
@@ -19,7 +19,13 @@ module Datadog
19
19
  end
20
20
 
21
21
  def self.from_json(json)
22
- new(nil, json)
22
+ new(nil, normalize_json(json))
23
+ end
24
+
25
+ def self.normalize_json(json)
26
+ return nil if json.nil?
27
+
28
+ json.dig(*Ext::Transport::DD_API_SETTINGS_RESPONSE_DIG_KEYS) || json
23
29
  end
24
30
 
25
31
  def ok?
@@ -6,7 +6,6 @@ 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"
10
9
 
11
10
  require_relative "../git/local_repository"
12
11
 
@@ -102,11 +101,9 @@ module Datadog
102
101
  populate_static_dependencies_map!
103
102
  end
104
103
 
105
- # Load component state first, and if successful, skip fetching skippable tests
106
- # Also try to restore from DDTest cache if available
104
+ # Load external cache or component state first, and if successful, skip fetching skippable tests
107
105
  if skipping_tests?
108
106
  return if load_component_state
109
- return if restore_state_from_datadog_test_runner
110
107
 
111
108
  fetch_skippable_tests(test_session)
112
109
  store_component_state if test_session.distributed
@@ -333,22 +330,17 @@ module Datadog
333
330
  end
334
331
 
335
332
  def restore_state_from_datadog_test_runner
336
- Datadog.logger.debug { "Restoring skippable tests from DDTest cache" }
333
+ Datadog.logger.debug { "Restoring skippable tests from Test Optimization cache" }
337
334
 
338
- skippable_tests_data = load_json(Ext::DDTest::SKIPPABLE_TESTS_FILE_NAME)
335
+ skippable_tests_data = load_cached_skippable_tests
339
336
  if skippable_tests_data.nil?
340
337
  Datadog.logger.debug { "Restoring skippable tests failed, will request again" }
341
338
  return false
342
339
  end
343
340
 
344
- Datadog.logger.debug { "Restored skippable tests from DDTest: #{skippable_tests_data}" }
341
+ Datadog.logger.debug { "Restored skippable tests from Test Optimization: #{skippable_tests_data}" }
345
342
 
346
- transformed_data = transform_test_runner_data(skippable_tests_data)
347
-
348
- Datadog.logger.debug { "Skippable tests after transformation: #{transformed_data}" }
349
-
350
- # Use the Skippable::Response class to parse the transformed data
351
- skippable_response = Skippable::Response.from_json(transformed_data)
343
+ skippable_response = Skippable::Response.from_json(skippable_tests_data)
352
344
 
353
345
  @mutex.synchronize do
354
346
  @correlation_id = skippable_response.correlation_id
@@ -363,58 +355,6 @@ module Datadog
363
355
 
364
356
  private
365
357
 
366
- # Transforms Test Runner skippable tests data format to the format expected by Skippable::Response
367
- #
368
- # Test Runner format:
369
- # {
370
- # "correlationId": "abc123",
371
- # "skippableTests": {
372
- # "suite_name": {
373
- # "test_name": [{"suite": "suite_name", "name": "test_name", "parameters": "{...}", "configurations": {}}]
374
- # }
375
- # }
376
- # }
377
- #
378
- # Expected format:
379
- # {
380
- # "meta": {"correlation_id": "abc123"},
381
- # "data": [{"type": "test", "attributes": {"suite": "suite_name", "name": "test_name", "parameters": "{...}"}}]
382
- # }
383
- def transform_test_runner_data(skippable_tests_data)
384
- skippable_tests = skippable_tests_data.fetch("skippableTests", {}) || {}
385
-
386
- # Pre-calculate array size for better memory allocation
387
- total_test_configs = skippable_tests.sum do |_, tests_hash|
388
- tests_hash.sum { |_, test_configs| test_configs.size }
389
- end
390
-
391
- data_array = Array.new(total_test_configs)
392
- index = 0
393
-
394
- skippable_tests.each_value do |tests_hash|
395
- tests_hash.each_value do |test_configs|
396
- test_configs.each do |test_config|
397
- data_array[index] = {
398
- "type" => Ext::Test::ITR_TEST_SKIPPING_MODE,
399
- "attributes" => {
400
- "suite" => test_config["suite"],
401
- "name" => test_config["name"],
402
- "parameters" => test_config["parameters"]
403
- }
404
- }
405
- index += 1
406
- end
407
- end
408
- end
409
-
410
- {
411
- "meta" => {
412
- "correlation_id" => skippable_tests_data["correlationId"]
413
- },
414
- "data" => data_array
415
- }
416
- end
417
-
418
358
  def write(event)
419
359
  # skip sending events if writer is not configured
420
360
  @coverage_writer&.write(event)
@@ -2,7 +2,6 @@
2
2
 
3
3
  require_relative "../ext/telemetry"
4
4
  require_relative "../ext/test"
5
- require_relative "../ext/dd_test"
6
5
  require_relative "../utils/stateful"
7
6
  require_relative "../utils/telemetry"
8
7
  require_relative "../utils/test_run"
@@ -36,7 +35,6 @@ module Datadog
36
35
 
37
36
  test_session.set_tag(Ext::Test::TAG_TEST_MANAGEMENT_ENABLED, "true")
38
37
 
39
- return if restore_state_from_datadog_test_runner
40
38
  return if load_component_state
41
39
 
42
40
  @tests_properties = @tests_properties_client.fetch(test_session)
@@ -85,25 +83,17 @@ module Datadog
85
83
  end
86
84
 
87
85
  def restore_state_from_datadog_test_runner
88
- Datadog.logger.debug { "Restoring test management tests from DDTest cache" }
86
+ Datadog.logger.debug { "Restoring test management tests from Test Optimization cache" }
89
87
 
90
- test_management_data = load_json(Ext::DDTest::TEST_MANAGEMENT_TESTS_FILE_NAME)
88
+ test_management_data = load_cached_test_management
91
89
  if test_management_data.nil?
92
90
  Datadog.logger.debug { "Restoring test management tests failed, will request again" }
93
91
  return false
94
92
  end
95
93
 
96
- Datadog.logger.debug { "Restored test management tests from DDTest: #{test_management_data}" }
94
+ Datadog.logger.debug { "Restored test management tests from Test Optimization: #{test_management_data}" }
97
95
 
98
- # Use the TestsProperties::Response class method to parse the JSON data
99
- # Wrap the data in the expected backend API format
100
- wrapped_data = {
101
- "data" => {
102
- "attributes" => test_management_data
103
- }
104
- }
105
-
106
- tests_properties_response = TestsProperties::Response.from_json(wrapped_data)
96
+ tests_properties_response = TestsProperties::Response.from_json(test_management_data)
107
97
  @tests_properties = tests_properties_response.tests
108
98
 
109
99
  Datadog.logger.debug { "Found [#{@tests_properties.size}] test management tests from context" }
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../ext/test_optimization_cache"
4
+ require_relative "locator"
5
+ require_relative "readers/legacy"
6
+ require_relative "readers/missing"
7
+ require_relative "readers/v1"
8
+
9
+ module Datadog
10
+ module CI
11
+ module TestOptimizationCache
12
+ class Component
13
+ READER_BY_MANIFEST_VERSION = {
14
+ Ext::TestOptimizationCache::SUPPORTED_MANIFEST_VERSION => Readers::V1
15
+ }.freeze
16
+
17
+ def initialize(manifest_file:, runfiles_dir:, runfiles_manifest_file:, test_srcdir:)
18
+ @locator = Locator.new(
19
+ manifest_file: manifest_file,
20
+ runfiles_dir: runfiles_dir,
21
+ runfiles_manifest_file: runfiles_manifest_file,
22
+ test_srcdir: test_srcdir
23
+ )
24
+ @reader = build_reader
25
+ end
26
+
27
+ def cache_available?
28
+ @reader.available?
29
+ end
30
+
31
+ def load_settings
32
+ @reader.load_settings
33
+ end
34
+
35
+ def load_known_tests
36
+ @reader.load_known_tests
37
+ end
38
+
39
+ def load_test_management
40
+ @reader.load_test_management
41
+ end
42
+
43
+ def load_skippable_tests
44
+ @reader.load_skippable_tests
45
+ end
46
+
47
+ def shutdown!
48
+ end
49
+
50
+ private
51
+
52
+ def build_reader
53
+ manifest_path = @locator.manifest_path
54
+
55
+ if manifest_path
56
+ version = @locator.manifest_version(manifest_path)
57
+ reader_class = READER_BY_MANIFEST_VERSION[version] if version
58
+ if reader_class
59
+ test_optimization_path = File.dirname(manifest_path)
60
+ reader = reader_class.new(test_optimization_path)
61
+ return reader if reader.available?
62
+
63
+ Datadog.logger.debug do
64
+ "Test Optimization cache settings file not found under #{test_optimization_path}"
65
+ end
66
+ return Readers::Missing.new
67
+ end
68
+
69
+ Datadog.logger.debug do
70
+ "Unsupported Test Optimization cache manifest version #{version.inspect} at #{manifest_path}"
71
+ end
72
+ return Readers::Missing.new
73
+ end
74
+
75
+ return Readers::Legacy.new if Dir.exist?(Ext::TestOptimizationCache::TESTOPTIMIZATION_CACHE_PATH)
76
+
77
+ Readers::Missing.new
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../ext/test_optimization_cache"
4
+
5
+ module Datadog
6
+ module CI
7
+ module TestOptimizationCache
8
+ class Locator
9
+ RUNFILES_MANIFEST_SEPARATOR = " "
10
+
11
+ def initialize(manifest_file:, runfiles_dir:, runfiles_manifest_file:, test_srcdir:)
12
+ @manifest_file = manifest_file
13
+ @runfiles_dir = runfiles_dir
14
+ @runfiles_manifest_file = runfiles_manifest_file
15
+ @test_srcdir = test_srcdir
16
+ @manifest_versions = {}
17
+ end
18
+
19
+ def manifest_path
20
+ return @manifest_path if defined?(@manifest_path)
21
+
22
+ local_manifest = File.join(Ext::TestOptimizationCache::PLAN_FOLDER, Ext::TestOptimizationCache::MANIFEST_FILE_NAME)
23
+ if File.exist?(local_manifest)
24
+ @manifest_path = local_manifest
25
+ return @manifest_path
26
+ end
27
+
28
+ manifest_file = @manifest_file
29
+ if manifest_file && !manifest_file.empty?
30
+ env_path = resolve_bazel_runfile_path(manifest_file)
31
+ if File.exist?(env_path)
32
+ @manifest_path = env_path
33
+ return @manifest_path
34
+ end
35
+ end
36
+
37
+ @manifest_path = nil
38
+ end
39
+
40
+ def manifest_version(manifest_path)
41
+ return @manifest_versions[manifest_path] if @manifest_versions.key?(manifest_path)
42
+
43
+ @manifest_versions[manifest_path] = File.read(manifest_path).delete_prefix("\uFEFF").strip
44
+ rescue => e
45
+ Datadog.logger.debug { "Failed to read Test Optimization cache manifest #{manifest_path}: #{e.message}" }
46
+ @manifest_versions[manifest_path] = nil
47
+ end
48
+
49
+ def resolve_bazel_runfile_path(path)
50
+ return path if File.exist?(path)
51
+
52
+ runfiles_dir = @runfiles_dir
53
+ if runfiles_dir && !runfiles_dir.empty?
54
+ candidate = File.join(runfiles_dir, path)
55
+ return candidate if File.exist?(candidate)
56
+ end
57
+
58
+ manifest_candidate = resolve_bazel_runfile_path_from_manifest(path)
59
+ return manifest_candidate if manifest_candidate
60
+
61
+ test_srcdir = @test_srcdir
62
+ if test_srcdir && !test_srcdir.empty?
63
+ candidate = File.join(test_srcdir, path)
64
+ return candidate if File.exist?(candidate)
65
+ end
66
+
67
+ path
68
+ end
69
+
70
+ # Bazel can provide runfiles through a manifest file instead of a directory tree.
71
+ # Each line maps a logical runfile path to the actual path on disk.
72
+ def resolve_bazel_runfile_path_from_manifest(path)
73
+ runfiles_manifest = @runfiles_manifest_file
74
+ return nil if runfiles_manifest.nil? || runfiles_manifest.empty?
75
+ return nil unless File.exist?(runfiles_manifest)
76
+
77
+ File.foreach(runfiles_manifest) do |line|
78
+ separator_index = line.index(RUNFILES_MANIFEST_SEPARATOR)
79
+ next unless separator_index&.positive?
80
+ next unless line[0...separator_index] == path
81
+
82
+ resolved_path = line[(separator_index + 1)..]
83
+ return resolved_path.strip if resolved_path
84
+ end
85
+
86
+ nil
87
+ rescue => e
88
+ Datadog.logger.debug do
89
+ "Failed to resolve Test Optimization cache manifest from #{runfiles_manifest}: #{e.message}"
90
+ end
91
+ nil
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module TestOptimizationCache
6
+ class NullComponent
7
+ def cache_available?
8
+ false
9
+ end
10
+
11
+ def load_settings
12
+ nil
13
+ end
14
+
15
+ def load_known_tests
16
+ nil
17
+ end
18
+
19
+ def load_test_management
20
+ nil
21
+ end
22
+
23
+ def load_skippable_tests
24
+ nil
25
+ end
26
+
27
+ def shutdown!
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../utils/json"
4
+
5
+ module Datadog
6
+ module CI
7
+ module TestOptimizationCache
8
+ module Readers
9
+ class Base
10
+ def available?
11
+ true
12
+ end
13
+
14
+ def load_settings
15
+ raise NotImplementedError, "#{self.class} must implement #load_settings"
16
+ end
17
+
18
+ def load_known_tests
19
+ raise NotImplementedError, "#{self.class} must implement #load_known_tests"
20
+ end
21
+
22
+ def load_test_management
23
+ raise NotImplementedError, "#{self.class} must implement #load_test_management"
24
+ end
25
+
26
+ def load_skippable_tests
27
+ raise NotImplementedError, "#{self.class} must implement #load_skippable_tests"
28
+ end
29
+
30
+ private
31
+
32
+ def read_json_file(file_path)
33
+ Utils::Json.read_file(file_path)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../ext/test"
4
+ require_relative "../../ext/test_optimization_cache"
5
+ require_relative "base"
6
+
7
+ module Datadog
8
+ module CI
9
+ module TestOptimizationCache
10
+ module Readers
11
+ class Legacy < Base
12
+ def load_settings
13
+ load_legacy_json(Ext::TestOptimizationCache::SETTINGS_FILE_NAME)
14
+ end
15
+
16
+ def load_known_tests
17
+ payload = load_legacy_json(Ext::TestOptimizationCache::KNOWN_TESTS_FILE_NAME)
18
+ backend_response(payload) if payload
19
+ end
20
+
21
+ def load_test_management
22
+ payload = load_legacy_json(Ext::TestOptimizationCache::LEGACY_TEST_MANAGEMENT_TESTS_FILE_NAME)
23
+ backend_response(payload) if payload
24
+ end
25
+
26
+ def load_skippable_tests
27
+ payload = load_legacy_json(Ext::TestOptimizationCache::SKIPPABLE_TESTS_FILE_NAME)
28
+ skippable_tests_response(payload) if payload
29
+ end
30
+
31
+ private
32
+
33
+ def load_legacy_json(file_name)
34
+ read_json_file(File.join(Ext::TestOptimizationCache::TESTOPTIMIZATION_CACHE_PATH, file_name))
35
+ end
36
+
37
+ def backend_response(payload)
38
+ {
39
+ "data" => {
40
+ "attributes" => payload
41
+ }
42
+ }
43
+ end
44
+
45
+ def skippable_tests_response(payload)
46
+ skippable_tests = payload.fetch("skippableTests", {}) || {}
47
+
48
+ data = skippable_tests.each_value.flat_map do |tests_hash|
49
+ tests_hash.each_value.flat_map do |test_configs|
50
+ test_configs.map do |test_config|
51
+ {
52
+ "type" => Ext::Test::ITR_TEST_SKIPPING_MODE,
53
+ "attributes" => {
54
+ "suite" => test_config["suite"],
55
+ "name" => test_config["name"],
56
+ "parameters" => test_config["parameters"]
57
+ }
58
+ }
59
+ end
60
+ end
61
+ end
62
+
63
+ {
64
+ "meta" => {
65
+ "correlation_id" => payload["correlationId"]
66
+ },
67
+ "data" => data
68
+ }
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Datadog
6
+ module CI
7
+ module TestOptimizationCache
8
+ module Readers
9
+ class Missing < Base
10
+ def available?
11
+ false
12
+ end
13
+
14
+ def load_settings
15
+ nil
16
+ end
17
+
18
+ def load_known_tests
19
+ nil
20
+ end
21
+
22
+ def load_test_management
23
+ nil
24
+ end
25
+
26
+ def load_skippable_tests
27
+ nil
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../ext/test_optimization_cache"
4
+ require_relative "base"
5
+
6
+ module Datadog
7
+ module CI
8
+ module TestOptimizationCache
9
+ module Readers
10
+ class V1 < Base
11
+ def initialize(test_optimization_path)
12
+ @test_optimization_path = test_optimization_path
13
+ end
14
+
15
+ def available?
16
+ File.exist?(settings_file_path)
17
+ end
18
+
19
+ def load_settings
20
+ load_http_json(Ext::TestOptimizationCache::SETTINGS_FILE_NAME)
21
+ end
22
+
23
+ def load_known_tests
24
+ load_http_json(Ext::TestOptimizationCache::KNOWN_TESTS_FILE_NAME)
25
+ end
26
+
27
+ def load_test_management
28
+ load_http_json(Ext::TestOptimizationCache::TEST_MANAGEMENT_FILE_NAME)
29
+ end
30
+
31
+ def load_skippable_tests
32
+ load_http_json(Ext::TestOptimizationCache::SKIPPABLE_TESTS_FILE_NAME)
33
+ end
34
+
35
+ private
36
+
37
+ def load_http_json(file_name)
38
+ read_json_file(File.join(http_cache_path, file_name))
39
+ end
40
+
41
+ def settings_file_path
42
+ File.join(http_cache_path, Ext::TestOptimizationCache::SETTINGS_FILE_NAME)
43
+ end
44
+
45
+ def http_cache_path
46
+ File.join(
47
+ @test_optimization_path,
48
+ Ext::TestOptimizationCache::CACHE_FOLDER_NAME,
49
+ Ext::TestOptimizationCache::HTTP_CACHE_FOLDER_NAME
50
+ )
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -225,28 +225,21 @@ module Datadog
225
225
  end
226
226
 
227
227
  def restore_state_from_datadog_test_runner
228
- Datadog.logger.debug { "Restoring known tests from DDTest cache" }
228
+ Datadog.logger.debug { "Restoring known tests from Test Optimization cache" }
229
229
 
230
- known_tests_data = load_json(Ext::DDTest::KNOWN_TESTS_FILE_NAME)
230
+ known_tests_data = load_cached_known_tests
231
231
  if known_tests_data.nil?
232
232
  Datadog.logger.debug { "Restoring known tests failed, will request again" }
233
233
  return false
234
234
  end
235
235
 
236
- Datadog.logger.debug { "Restored known tests from DDTest: #{known_tests_data}" }
237
-
238
- # Use the KnownTests class method to parse the JSON data
239
- known_tests_data = {
240
- "data" => {
241
- "attributes" => known_tests_data
242
- }
243
- }
236
+ Datadog.logger.debug { "Restored known tests from Test Optimization: #{known_tests_data}" }
244
237
 
245
238
  @known_tests = KnownTests::Response.from_json(known_tests_data).tests
246
239
  @known_tests_enabled = !@known_tests.empty?
247
240
 
248
241
  unless @known_tests_enabled
249
- Datadog.logger.debug("Empty set of known tests from the DDTest cache file")
242
+ Datadog.logger.debug("Empty set of known tests from the Test Optimization cache file")
250
243
  end
251
244
 
252
245
  Datadog.logger.debug { "Found [#{@known_tests.size}] known tests from context" }
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Datadog
6
+ module CI
7
+ module Utils
8
+ module Json
9
+ def self.read_file(file_path)
10
+ unless File.exist?(file_path)
11
+ Datadog.logger.debug { "JSON file not found: #{file_path}" }
12
+ return nil
13
+ end
14
+
15
+ JSON.parse(File.read(file_path))
16
+ rescue JSON::ParserError, SystemCallError => e
17
+ Datadog.logger.debug { "Failed to load JSON file #{file_path}: #{e.message}" }
18
+ nil
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
3
  require_relative "file_storage"
5
- require_relative "test_run"
6
- require_relative "../ext/dd_test"
7
4
 
8
5
  module Datadog
9
6
  module CI
@@ -22,9 +19,8 @@ module Datadog
22
19
 
23
20
  # Load component state
24
21
  def load_component_state
25
- # Check for DDTest cache first
26
- if TestRun.test_optimization_data_cached?
27
- Datadog.logger.debug { "DDTest cache found" }
22
+ if test_optimization_cache.cache_available?
23
+ Datadog.logger.debug { "Test Optimization cache found" }
28
24
  return true if restore_state_from_datadog_test_runner
29
25
  end
30
26
 
@@ -60,22 +56,24 @@ module Datadog
60
56
  false
61
57
  end
62
58
 
63
- def load_json(file_name)
64
- file_path = File.join(Ext::DDTest::TESTOPTIMIZATION_CACHE_PATH, file_name)
59
+ def load_cached_settings
60
+ test_optimization_cache.load_settings
61
+ end
65
62
 
66
- unless File.exist?(file_path)
67
- Datadog.logger.debug { "JSON file not found: #{file_path}" }
68
- return nil
69
- end
63
+ def load_cached_known_tests
64
+ test_optimization_cache.load_known_tests
65
+ end
66
+
67
+ def load_cached_test_management
68
+ test_optimization_cache.load_test_management
69
+ end
70
+
71
+ def load_cached_skippable_tests
72
+ test_optimization_cache.load_skippable_tests
73
+ end
70
74
 
71
- content = File.read(file_path)
72
- JSON.parse(content)
73
- rescue JSON::ParserError => e
74
- Datadog.logger.debug { "Failed to parse JSON file #{file_path}: #{e.message}" }
75
- nil
76
- rescue => e
77
- Datadog.logger.debug { "Failed to load JSON file #{file_path}: #{e.message}" }
78
- nil
75
+ def test_optimization_cache
76
+ Datadog.send(:components).test_optimization_cache
79
77
  end
80
78
  end
81
79
  end
@@ -2,8 +2,6 @@
2
2
 
3
3
  require "etc"
4
4
 
5
- require_relative "../ext/dd_test"
6
-
7
5
  module Datadog
8
6
  module CI
9
7
  module Utils
@@ -44,10 +42,6 @@ module Datadog
44
42
 
45
43
  @virtual_cpu_count = ::Etc.nprocessors
46
44
  end
47
-
48
- def self.test_optimization_data_cached?
49
- Dir.exist?(Ext::DDTest::TESTOPTIMIZATION_CACHE_PATH)
50
- end
51
45
  end
52
46
  end
53
47
  end
@@ -4,7 +4,7 @@ module Datadog
4
4
  module CI
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 30
7
+ MINOR = 31
8
8
  PATCH = 0
9
9
  PRE = nil
10
10
  BUILD = nil
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.30.0
4
+ version: 1.31.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
@@ -178,7 +178,6 @@ files:
178
178
  - lib/datadog/ci/contrib/simplecov/report_uploader.rb
179
179
  - lib/datadog/ci/contrib/simplecov/result_extractor.rb
180
180
  - lib/datadog/ci/ext/app_types.rb
181
- - lib/datadog/ci/ext/dd_test.rb
182
181
  - lib/datadog/ci/ext/environment.rb
183
182
  - lib/datadog/ci/ext/environment/configuration_discrepancy_checker.rb
184
183
  - lib/datadog/ci/ext/environment/extractor.rb
@@ -207,6 +206,7 @@ files:
207
206
  - lib/datadog/ci/ext/telemetry.rb
208
207
  - lib/datadog/ci/ext/test.rb
209
208
  - lib/datadog/ci/ext/test_discovery.rb
209
+ - lib/datadog/ci/ext/test_optimization_cache.rb
210
210
  - lib/datadog/ci/ext/transport.rb
211
211
  - lib/datadog/ci/git/base_branch_sha_detection/base.rb
212
212
  - lib/datadog/ci/git/base_branch_sha_detection/branch_metric.rb
@@ -257,6 +257,13 @@ files:
257
257
  - lib/datadog/ci/test_management/null_component.rb
258
258
  - lib/datadog/ci/test_management/tests_properties.rb
259
259
  - lib/datadog/ci/test_module.rb
260
+ - lib/datadog/ci/test_optimization_cache/component.rb
261
+ - lib/datadog/ci/test_optimization_cache/locator.rb
262
+ - lib/datadog/ci/test_optimization_cache/null_component.rb
263
+ - lib/datadog/ci/test_optimization_cache/readers/base.rb
264
+ - lib/datadog/ci/test_optimization_cache/readers/legacy.rb
265
+ - lib/datadog/ci/test_optimization_cache/readers/missing.rb
266
+ - lib/datadog/ci/test_optimization_cache/readers/v1.rb
260
267
  - lib/datadog/ci/test_retries/component.rb
261
268
  - lib/datadog/ci/test_retries/driver/base.rb
262
269
  - lib/datadog/ci/test_retries/driver/no_retry.rb
@@ -307,6 +314,7 @@ files:
307
314
  - lib/datadog/ci/utils/configuration.rb
308
315
  - lib/datadog/ci/utils/file_storage.rb
309
316
  - lib/datadog/ci/utils/git.rb
317
+ - lib/datadog/ci/utils/json.rb
310
318
  - lib/datadog/ci/utils/parsing.rb
311
319
  - lib/datadog/ci/utils/rum.rb
312
320
  - lib/datadog/ci/utils/stateful.rb
@@ -1,18 +0,0 @@
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