datadog-ci 1.21.1 → 1.22.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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -2
  3. data/lib/datadog/ci/configuration/components.rb +13 -2
  4. data/lib/datadog/ci/configuration/settings.rb +11 -0
  5. data/lib/datadog/ci/contrib/instrumentation.rb +0 -6
  6. data/lib/datadog/ci/contrib/minitest/helpers.rb +12 -1
  7. data/lib/datadog/ci/contrib/rspec/integration.rb +10 -1
  8. data/lib/datadog/ci/ext/environment/extractor.rb +2 -0
  9. data/lib/datadog/ci/ext/environment/providers/aws_code_pipeline.rb +4 -0
  10. data/lib/datadog/ci/ext/environment/providers/azure.rb +4 -0
  11. data/lib/datadog/ci/ext/environment/providers/base.rb +6 -0
  12. data/lib/datadog/ci/ext/environment/providers/buildkite.rb +4 -0
  13. data/lib/datadog/ci/ext/environment/providers/circleci.rb +4 -0
  14. data/lib/datadog/ci/ext/environment/providers/github_actions.rb +18 -3
  15. data/lib/datadog/ci/ext/environment/providers/gitlab.rb +12 -0
  16. data/lib/datadog/ci/ext/environment.rb +2 -1
  17. data/lib/datadog/ci/ext/git.rb +1 -0
  18. data/lib/datadog/ci/ext/settings.rb +3 -0
  19. data/lib/datadog/ci/ext/telemetry.rb +1 -0
  20. data/lib/datadog/ci/ext/test.rb +1 -0
  21. data/lib/datadog/ci/ext/test_discovery.rb +16 -0
  22. data/lib/datadog/ci/git/base_branch_sha_detector.rb +1 -15
  23. data/lib/datadog/ci/git/local_repository.rb +41 -3
  24. data/lib/datadog/ci/remote/component.rb +34 -22
  25. data/lib/datadog/ci/test.rb +14 -2
  26. data/lib/datadog/ci/test_discovery/component.rb +131 -0
  27. data/lib/datadog/ci/test_visibility/component.rb +22 -0
  28. data/lib/datadog/ci/test_visibility/telemetry.rb +10 -1
  29. data/lib/datadog/ci/version.rb +2 -2
  30. data/lib/datadog/ci.rb +13 -1
  31. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fea9fced002fe3713119893acd2ab78f7d3f2855dd3eaa3d30c87ed14cd18e37
4
- data.tar.gz: b2bd39e198205855d322e31a17cf5d8ecded29edd2544963c3d5940f68856f45
3
+ metadata.gz: 64b3da927cc62294a16d73045c2585a0d943d81d638b9ac61f95f10b2f69f6b6
4
+ data.tar.gz: 72451663fb18398c49bfa61ac43b0317e21792853d2681b44e77239e932d1bfc
5
5
  SHA512:
6
- metadata.gz: a2807fa13e49deccee138a6e59b3f5a7fb17c64ff0897519cf2b1af48c6724abce003cb726ff82a3b506804bd7f0bf342f1f18a1aeea755cd364ed32d63da0a6
7
- data.tar.gz: 8c625ca146baeeb4901d8614430baf0efcea69e467327e9c8906ae989a23676a5792fef2a10e810606cab4012f2693e293847d00daad7c04c631ab3d0e95f56a
6
+ metadata.gz: 209a3a9d3c6c6081b864231e0c63742f34c5ba64b51c60e879742410a3da16bac7eefc802dd18a23bed30f9cdc4be0282258d51c28fdac560792a91423acaef7
7
+ data.tar.gz: 13bb63efb8388ac57a396c299fc5d36f8bda7889914c31cee7eacc409a4bbf9fad70e96e7b01b8ce2cf2139fd467f8f1dda21fe7090e826958440e701abbcfdc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.22.0] - 2025-08-12
4
+
5
+ ### Added
6
+ * Add pr_number extraction to GitHub Actions provider ([#390][])
7
+ * Add ci.job.id tag to events ([#387][])
8
+ * Test discovery mode - internal, intended for usage with Datadog test runner ([#378][])
9
+
10
+ ### Changed
11
+ * fetch exact head commit instead of unshallowing parent for impacted tests detection ([#393][])
12
+
13
+ ### Fixed
14
+ * Fix: make test_suite_name parameter optional for Datadog::CI.active_test_suite method ([#396][])
15
+ * Fix handling of base branch commit SHA for Github Actions and Gitlab ([#394][])
16
+ * fix Minitest's test suite name when encountering relative source file paths ([#391][])
17
+
3
18
  ## [1.21.1] - 2025-07-22
4
19
 
5
20
  ### Fixed
@@ -499,7 +514,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil
499
514
 
500
515
  - Ruby versions < 2.7 no longer supported ([#8][])
501
516
 
502
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.21.1...main
517
+ [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.22.0...main
518
+ [1.22.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.21.1...v1.22.0
503
519
  [1.21.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.21.0...v1.21.1
504
520
  [1.21.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.20.2...v1.21.0
505
521
  [1.20.2]: https://github.com/DataDog/datadog-ci-rb/compare/v1.20.1...v1.20.2
@@ -707,5 +723,12 @@ Currently test suite level visibility is not used by our instrumentation: it wil
707
723
  [#359]: https://github.com/DataDog/datadog-ci-rb/issues/359
708
724
  [#366]: https://github.com/DataDog/datadog-ci-rb/issues/366
709
725
  [#370]: https://github.com/DataDog/datadog-ci-rb/issues/370
726
+ [#378]: https://github.com/DataDog/datadog-ci-rb/issues/378
710
727
  [#383]: https://github.com/DataDog/datadog-ci-rb/issues/383
711
- [#384]: https://github.com/DataDog/datadog-ci-rb/issues/384
728
+ [#384]: https://github.com/DataDog/datadog-ci-rb/issues/384
729
+ [#387]: https://github.com/DataDog/datadog-ci-rb/issues/387
730
+ [#390]: https://github.com/DataDog/datadog-ci-rb/issues/390
731
+ [#391]: https://github.com/DataDog/datadog-ci-rb/issues/391
732
+ [#393]: https://github.com/DataDog/datadog-ci-rb/issues/393
733
+ [#394]: https://github.com/DataDog/datadog-ci-rb/issues/394
734
+ [#396]: https://github.com/DataDog/datadog-ci-rb/issues/396
@@ -16,6 +16,7 @@ require_relative "../test_optimisation/component"
16
16
  require_relative "../test_optimisation/coverage/transport"
17
17
  require_relative "../test_retries/component"
18
18
  require_relative "../test_retries/null_component"
19
+ require_relative "../test_discovery/component"
19
20
  require_relative "../test_visibility/component"
20
21
  require_relative "../test_visibility/flush"
21
22
  require_relative "../test_visibility/known_tests"
@@ -37,7 +38,7 @@ module Datadog
37
38
  # Adds CI behavior to Datadog trace components
38
39
  module Components
39
40
  attr_reader :test_visibility, :test_optimisation, :git_tree_upload_worker, :ci_remote, :test_retries,
40
- :test_management, :agentless_logs_submission, :impacted_tests_detection
41
+ :test_management, :agentless_logs_submission, :impacted_tests_detection, :test_discovery
41
42
 
42
43
  def initialize(settings)
43
44
  @test_optimisation = nil
@@ -47,6 +48,7 @@ module Datadog
47
48
  @test_retries = TestRetries::NullComponent.new
48
49
  @test_management = TestManagement::NullComponent.new
49
50
  @impacted_tests_detection = nil
51
+ @test_discovery = nil
50
52
 
51
53
  # Activate CI mode if enabled
52
54
  if settings.ci.enabled
@@ -62,6 +64,7 @@ module Datadog
62
64
  @test_visibility&.shutdown!
63
65
  @test_optimisation&.shutdown!
64
66
  @agentless_logs_submission&.shutdown!
67
+ @test_discovery&.shutdown!
65
68
  @git_tree_upload_worker&.stop
66
69
  end
67
70
 
@@ -112,9 +115,17 @@ module Datadog
112
115
 
113
116
  settings.tracing.test_mode.writer_options = trace_writer_options
114
117
 
118
+ # @type ivar @test_discovery: Datadog::CI::TestDiscovery::Component
119
+ @test_discovery = TestDiscovery::Component.new(
120
+ enabled: settings.ci.test_discovery_enabled,
121
+ output_path: settings.ci.test_discovery_output_path
122
+ )
123
+ @test_discovery.disable_features_for_test_discovery!(settings)
124
+
115
125
  @git_tree_upload_worker = build_git_upload_worker(settings, test_visibility_api)
116
126
  @ci_remote = Remote::Component.new(
117
- library_settings_client: build_library_settings_client(settings, test_visibility_api)
127
+ library_settings_client: build_library_settings_client(settings, test_visibility_api),
128
+ test_discovery_enabled: settings.ci.test_discovery_enabled
118
129
  )
119
130
  @test_retries = TestRetries::Component.new(
120
131
  retry_failed_tests_enabled: settings.ci.retry_failed_tests_enabled,
@@ -157,6 +157,17 @@ module Datadog
157
157
  o.default false
158
158
  end
159
159
 
160
+ option :test_discovery_enabled do |o|
161
+ o.type :bool
162
+ o.env Ext::Settings::ENV_TEST_DISCOVERY_MODE_ENABLED
163
+ o.default false
164
+ end
165
+
166
+ option :test_discovery_output_path do |o|
167
+ o.type :string, nilable: true
168
+ o.env CI::Ext::Settings::ENV_TEST_DISCOVERY_OUTPUT_PATH
169
+ end
170
+
160
171
  define_method(:instrument) do |integration_name, options = {}, &block|
161
172
  return unless enabled
162
173
 
@@ -9,7 +9,6 @@ module Datadog
9
9
  class InvalidIntegrationError < StandardError; end
10
10
 
11
11
  @registry = {}
12
- @auto_instrumented = false
13
12
 
14
13
  def self.registry
15
14
  @registry
@@ -19,10 +18,6 @@ module Datadog
19
18
  @registry[integration_name(integration_class)] = integration_class.new
20
19
  end
21
20
 
22
- def self.auto_instrumented?
23
- @auto_instrumented
24
- end
25
-
26
21
  # Auto instrumentation of all integrations.
27
22
  #
28
23
  # Registers a :script_compiled tracepoint to watch for new Ruby files being loaded.
@@ -30,7 +25,6 @@ module Datadog
30
25
  # Only the integrations that are available in the environment are checked.
31
26
  def self.auto_instrument
32
27
  Datadog.logger.debug("Auto instrumenting all integrations...")
33
- @auto_instrumented = true
34
28
 
35
29
  auto_instrumented_integrations = fetch_auto_instrumented_integrations
36
30
  if auto_instrumented_integrations.empty?
@@ -38,7 +38,18 @@ module Datadog
38
38
  source_location, = klass.instance_method(method_name).source_location
39
39
  end
40
40
 
41
- source_file_path = Pathname.new(source_location.to_s).relative_path_from(Pathname.pwd).to_s
41
+ # According to https://github.com/DataDog/datadog-ci-rb/issues/386
42
+ # the source file path coould be relative when using minitest mixins.
43
+ #
44
+ # Note that it doesn't break for test suite source location in .start_test_suite method
45
+ # because it outputs path relative to the repository root.
46
+ #
47
+ # For backwards compatibility, we'll continue to use the relative path from the current
48
+ # working directory for test suite name.
49
+ source_file_path = Pathname.new(source_location.to_s)
50
+ if source_file_path.absolute?
51
+ source_file_path = source_file_path.relative_path_from(Pathname.pwd).to_s
52
+ end
42
53
 
43
54
  "#{klass.name} at #{source_file_path}"
44
55
  end
@@ -32,12 +32,21 @@ module Datadog
32
32
  end
33
33
 
34
34
  def new_configuration
35
- Configuration::Settings.new
35
+ settings = Configuration::Settings.new
36
+ # if we are running in test discovery mode, we are most likely running in dry run - we need to allow it
37
+ settings.dry_run_enabled = true if test_discovery_component&.enabled?
38
+ settings
36
39
  end
37
40
 
38
41
  def patcher
39
42
  Patcher
40
43
  end
44
+
45
+ def test_discovery_component
46
+ components = Datadog.send(:components)
47
+ return nil unless components.respond_to?(:test_discovery)
48
+ components.test_discovery
49
+ end
41
50
  end
42
51
  end
43
52
  end
@@ -23,6 +23,7 @@ module Datadog
23
23
  return @tags if defined?(@tags)
24
24
 
25
25
  @tags = {
26
+ Environment::TAG_JOB_ID => @provider.job_id,
26
27
  Environment::TAG_JOB_NAME => @provider.job_name,
27
28
  Environment::TAG_JOB_URL => @provider.job_url,
28
29
  Environment::TAG_PIPELINE_ID => @provider.pipeline_id,
@@ -51,6 +52,7 @@ module Datadog
51
52
 
52
53
  Git::TAG_PULL_REQUEST_BASE_BRANCH => @provider.git_pull_request_base_branch,
53
54
  Git::TAG_PULL_REQUEST_BASE_BRANCH_SHA => @provider.git_pull_request_base_branch_sha,
55
+ Git::TAG_PULL_REQUEST_BASE_BRANCH_HEAD_SHA => @provider.git_pull_request_base_branch_head_sha,
54
56
  Environment::TAG_PR_NUMBER => @provider.pr_number,
55
57
 
56
58
  Git::TAG_COMMIT_HEAD_SHA => @provider.git_commit_head_sha,
@@ -20,6 +20,10 @@ module Datadog
20
20
  Provider::AWS
21
21
  end
22
22
 
23
+ def job_id
24
+ env["DD_ACTION_EXECUTION_ID"]
25
+ end
26
+
23
27
  def pipeline_id
24
28
  env["DD_PIPELINE_EXECUTION_ID"]
25
29
  end
@@ -20,6 +20,10 @@ module Datadog
20
20
  Provider::AZURE
21
21
  end
22
22
 
23
+ def job_id
24
+ env["SYSTEM_JOBID"]
25
+ end
26
+
23
27
  def pipeline_url
24
28
  return unless url_defined?
25
29
 
@@ -18,6 +18,9 @@ module Datadog
18
18
  @env = env
19
19
  end
20
20
 
21
+ def job_id
22
+ end
23
+
21
24
  def job_name
22
25
  end
23
26
 
@@ -104,6 +107,9 @@ module Datadog
104
107
  def git_pull_request_base_branch_sha
105
108
  end
106
109
 
110
+ def git_pull_request_base_branch_head_sha
111
+ end
112
+
107
113
  def git_commit_head_sha
108
114
  end
109
115
 
@@ -20,6 +20,10 @@ module Datadog
20
20
  Provider::BUILDKITE
21
21
  end
22
22
 
23
+ def job_id
24
+ env["BUILDKITE_JOB_ID"]
25
+ end
26
+
23
27
  def job_url
24
28
  "#{env["BUILDKITE_BUILD_URL"]}##{env["BUILDKITE_JOB_ID"]}"
25
29
  end
@@ -20,6 +20,10 @@ module Datadog
20
20
  Provider::CIRCLECI
21
21
  end
22
22
 
23
+ def job_id
24
+ env["CIRCLE_BUILD_NUM"]
25
+ end
26
+
23
27
  def job_url
24
28
  env["CIRCLE_BUILD_URL"]
25
29
  end
@@ -31,6 +31,10 @@ module Datadog
31
31
  "#{github_server_url}/#{env["GITHUB_REPOSITORY"]}/commit/#{env["GITHUB_SHA"]}/checks"
32
32
  end
33
33
 
34
+ def job_id
35
+ env["GITHUB_JOB"]
36
+ end
37
+
34
38
  def pipeline_id
35
39
  env["GITHUB_RUN_ID"]
36
40
  end
@@ -80,7 +84,7 @@ module Datadog
80
84
  env["GITHUB_BASE_REF"]
81
85
  end
82
86
 
83
- def git_pull_request_base_branch_sha
87
+ def git_pull_request_base_branch_head_sha
84
88
  return nil if git_pull_request_base_branch.nil?
85
89
 
86
90
  event_json = github_event_json
@@ -88,8 +92,8 @@ module Datadog
88
92
 
89
93
  event_json.dig("pull_request", "base", "sha")
90
94
  rescue => e
91
- Datadog.logger.error("Failed to extract pull request base branch SHA from GitHub Actions: #{e}")
92
- Core::Telemetry::Logger.report(e, description: "Failed to extract pull request base branch SHA from GitHub Actions")
95
+ Datadog.logger.error("Failed to extract pull request base branch head SHA from GitHub Actions: #{e}")
96
+ Core::Telemetry::Logger.report(e, description: "Failed to extract pull request base branch head SHA from GitHub Actions")
93
97
  nil
94
98
  end
95
99
 
@@ -106,6 +110,17 @@ module Datadog
106
110
  nil
107
111
  end
108
112
 
113
+ def pr_number
114
+ event_json = github_event_json
115
+ return nil if event_json.nil?
116
+
117
+ event_json["number"]
118
+ rescue => e
119
+ Datadog.logger.error("Failed to extract PR number from GitHub Actions: #{e}")
120
+ Core::Telemetry::Logger.report(e, description: "Failed to extract PR number from GitHub Actions")
121
+ nil
122
+ end
123
+
109
124
  private
110
125
 
111
126
  def github_event_json
@@ -18,6 +18,10 @@ module Datadog
18
18
  Provider::GITLAB
19
19
  end
20
20
 
21
+ def job_id
22
+ env["CI_JOB_ID"]
23
+ end
24
+
21
25
  def job_name
22
26
  env["CI_JOB_NAME"]
23
27
  end
@@ -104,6 +108,14 @@ module Datadog
104
108
  env["CI_MERGE_REQUEST_TARGET_BRANCH_NAME"]
105
109
  end
106
110
 
111
+ def git_pull_request_base_branch_sha
112
+ env["CI_MERGE_REQUEST_DIFF_BASE_SHA"]
113
+ end
114
+
115
+ def git_pull_request_base_branch_head_sha
116
+ env["CI_MERGE_REQUEST_TARGET_BRANCH_SHA"]
117
+ end
118
+
107
119
  def git_commit_head_sha
108
120
  env["CI_MERGE_REQUEST_SOURCE_BRANCH_SHA"]
109
121
  end
@@ -13,6 +13,7 @@ module Datadog
13
13
  module Ext
14
14
  # Defines constants for CI tags
15
15
  module Environment
16
+ TAG_JOB_ID = "ci.job.id"
16
17
  TAG_JOB_NAME = "ci.job.name"
17
18
  TAG_JOB_URL = "ci.job.url"
18
19
  TAG_PIPELINE_ID = "ci.pipeline.id"
@@ -70,7 +71,7 @@ module Datadog
70
71
  # we need to unshallow at least the parent of the current merge commit to be able to extract information
71
72
  # from the real original commit.
72
73
  if tags[Git::TAG_COMMIT_HEAD_SHA]
73
- CI::Git::LocalRepository.git_unshallow(parent_only: true) if CI::Git::LocalRepository.git_shallow_clone?
74
+ CI::Git::LocalRepository.fetch_head_commit_sha(tags[Git::TAG_COMMIT_HEAD_SHA]) if CI::Git::LocalRepository.git_shallow_clone?
74
75
 
75
76
  env[ENV_SPECIAL_KEY_FOR_GIT_COMMIT_HEAD_SHA] = tags[Git::TAG_COMMIT_HEAD_SHA]
76
77
  end
@@ -23,6 +23,7 @@ module Datadog
23
23
  # additional tags that we use for github actions jobs with "pull_request" target
24
24
  TAG_PULL_REQUEST_BASE_BRANCH = "git.pull_request.base_branch"
25
25
  TAG_PULL_REQUEST_BASE_BRANCH_SHA = "git.pull_request.base_branch_sha"
26
+ TAG_PULL_REQUEST_BASE_BRANCH_HEAD_SHA = "git.pull_request.base_branch_head_sha"
26
27
 
27
28
  TAG_COMMIT_HEAD_SHA = "git.commit.head.sha"
28
29
  TAG_COMMIT_HEAD_MESSAGE = "git.commit.head.message"
@@ -26,6 +26,9 @@ module Datadog
26
26
  ENV_AGENTLESS_LOGS_SUBMISSION_ENABLED = "DD_AGENTLESS_LOG_SUBMISSION_ENABLED"
27
27
  ENV_AGENTLESS_LOGS_SUBMISSION_URL = "DD_AGENTLESS_LOG_SUBMISSION_URL"
28
28
  ENV_IMPACTED_TESTS_DETECTION_ENABLED = "DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED"
29
+ ENV_TEST_DISCOVERY_MODE_ENABLED = "DD_TEST_OPTIMIZATION_DISCOVERY_ENABLED"
30
+ ENV_TEST_DISCOVERY_OUTPUT_PATH = "DD_TEST_OPTIMIZATION_DISCOVERY_FILE"
31
+ ENV_AUTO_INSTRUMENTATION_PROVIDER = "DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER"
29
32
 
30
33
  # Source: https://docs.datadoghq.com/getting_started/site/
31
34
  DD_SITE_ALLOWLIST = %w[
@@ -144,6 +144,7 @@ module Datadog
144
144
  PACK_OBJECTS = "pack_objects"
145
145
  DIFF = "diff"
146
146
  BASE_COMMIT_SHA = "base_commit_sha"
147
+ FETCH_HEAD_COMMIT_SHA = "fetch_head_commit_sha"
147
148
  end
148
149
 
149
150
  module Provider
@@ -171,6 +171,7 @@ module Datadog
171
171
  module SkipReason
172
172
  TEST_IMPACT_ANALYSIS = "Skipped by Datadog's Test Impact Analysis"
173
173
  TEST_MANAGEMENT_DISABLED = "Flaky test is disabled by Datadog"
174
+ TEST_DISCOVERY_MODE = "Skipped by Datadog's Test Discovery Mode"
174
175
  end
175
176
  end
176
177
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module Ext
6
+ # Defines constants for test discovery mode
7
+ module TestDiscovery
8
+ # Default output path for test discovery mode
9
+ DEFAULT_OUTPUT_PATH = "./.dd/test_discovery/tests.json"
10
+
11
+ # Maximum buffer size before writing to file
12
+ MAX_BUFFER_SIZE = 10_000
13
+ end
14
+ end
15
+ end
16
+ end
@@ -10,7 +10,7 @@ module Datadog
10
10
  def self.base_branch_sha(base_branch)
11
11
  Datadog.logger.debug { "Base branch: '#{base_branch}'" }
12
12
 
13
- remote_name = get_remote_name
13
+ remote_name = LocalRepository.get_remote_name
14
14
  Datadog.logger.debug { "Remote name: '#{remote_name}'" }
15
15
 
16
16
  source_branch = get_source_branch
@@ -27,20 +27,6 @@ module Datadog
27
27
  strategy.call
28
28
  end
29
29
 
30
- def self.get_remote_name
31
- # Try to find remote from upstream tracking
32
- upstream = LocalRepository.get_upstream_branch
33
-
34
- if upstream
35
- upstream.split("/").first
36
- else
37
- # Fallback to first remote if no upstream is set
38
- first_remote_value = CLI.exec_git_command(["remote"])&.split("\n")&.first
39
- Datadog.logger.debug { "First remote value: '#{first_remote_value}'" }
40
- first_remote_value || "origin"
41
- end
42
- end
43
-
44
30
  def self.get_source_branch
45
31
  source_branch = CLI.exec_git_command(["rev-parse", "--abbrev-ref", "HEAD"])
46
32
  if source_branch.nil?
@@ -268,7 +268,7 @@ module Datadog
268
268
  false
269
269
  end
270
270
 
271
- def self.git_unshallow(parent_only: false)
271
+ def self.git_unshallow
272
272
  Telemetry.git_command(Ext::Telemetry::Command::UNSHALLOW)
273
273
  # @type var res: String?
274
274
  res = nil
@@ -289,11 +289,10 @@ module Datadog
289
289
 
290
290
  res =
291
291
  begin
292
- unshallowing_depth = parent_only ? "--deepen=1" : "--shallow-since=\"1 month ago\""
293
292
  # @type var cmd: Array[String]
294
293
  cmd = [
295
294
  "fetch",
296
- unshallowing_depth,
295
+ "--shallow-since=\"1 month ago\"",
297
296
  "--update-shallow",
298
297
  "--filter=blob:none",
299
298
  "--recurse-submodules=no",
@@ -318,6 +317,32 @@ module Datadog
318
317
  res
319
318
  end
320
319
 
320
+ def self.fetch_head_commit_sha(commit_sha)
321
+ remote = get_remote_name
322
+ # @type var res: String?
323
+ res = nil
324
+
325
+ duration_ms = Core::Utils::Time.measure(:float_millisecond) do
326
+ cmd = [
327
+ "fetch",
328
+ "--update-shallow",
329
+ "--filter=blob:none",
330
+ "--recurse-submodules=no",
331
+ "--no-write-fetch-head",
332
+ remote,
333
+ commit_sha
334
+ ]
335
+ res = CLI.exec_git_command(cmd)
336
+ end
337
+
338
+ Telemetry.git_command_ms(Ext::Telemetry::Command::FETCH_HEAD_COMMIT_SHA, duration_ms)
339
+ res
340
+ rescue => e
341
+ log_failure(e, "git fetch_head_commit_sha")
342
+ Telemetry.track_error(e, Ext::Telemetry::Command::FETCH_HEAD_COMMIT_SHA)
343
+ nil
344
+ end
345
+
321
346
  # Returns a Diff object with relative file paths for files that were changed since the given base_commit.
322
347
  # If base_commit is nil, returns nil. On error, returns nil.
323
348
  def self.get_changes_since(base_commit)
@@ -369,6 +394,19 @@ module Datadog
369
394
  nil
370
395
  end
371
396
 
397
+ def self.get_remote_name
398
+ upstream = LocalRepository.get_upstream_branch
399
+
400
+ if upstream
401
+ upstream.split("/").first
402
+ else
403
+ # Fallback to first remote if no upstream is set
404
+ first_remote_value = CLI.exec_git_command(["remote"])&.split("\n")&.first
405
+ Datadog.logger.debug { "First remote value: '#{first_remote_value}'" }
406
+ first_remote_value || "origin"
407
+ end
408
+ end
409
+
372
410
  def self.filter_invalid_commits(commits)
373
411
  commits.filter { |commit| Utils::Git.valid_commit_sha?(commit) }
374
412
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../worker"
4
4
  require_relative "../utils/stateful"
5
+ require_relative "library_settings"
5
6
 
6
7
  module Datadog
7
8
  module CI
@@ -13,33 +14,14 @@ module Datadog
13
14
 
14
15
  FILE_STORAGE_KEY = "remote_component_state"
15
16
 
16
- def initialize(library_settings_client:)
17
+ def initialize(library_settings_client:, test_discovery_enabled: false)
17
18
  @library_settings_client = library_settings_client
18
- @library_configuration = nil
19
+ @test_discovery_enabled = test_discovery_enabled
19
20
  end
20
21
 
21
22
  # called on test session start, uses test session info to send configuration request to the backend
22
23
  def configure(test_session)
23
- # If component state is loaded successfully, skip fetching library configuration
24
- unless load_component_state
25
- @library_configuration = @library_settings_client.fetch(test_session)
26
- # sometimes we can skip code coverage for default branch if there are no changes in the repository
27
- # backend needs git metadata uploaded for this test session to check if we can skip code coverage
28
- if @library_configuration.require_git?
29
- Datadog.logger.debug { "Library configuration endpoint requires git upload to be finished, waiting..." }
30
- git_tree_upload_worker.wait_until_done
31
-
32
- Datadog.logger.debug { "Requesting library configuration again..." }
33
- @library_configuration = @library_settings_client.fetch(test_session)
34
-
35
- if @library_configuration.require_git?
36
- Datadog.logger.debug { "git metadata upload did not complete in time when configuring library" }
37
- end
38
- end
39
-
40
- # Store component state for distributed test runs
41
- store_component_state if test_session.distributed
42
- end
24
+ fetch_library_configuration(test_session)
43
25
 
44
26
  # configure different components in parallel because they might block on HTTP requests
45
27
  configuration_workers = [
@@ -74,6 +56,36 @@ module Datadog
74
56
 
75
57
  private
76
58
 
59
+ def fetch_library_configuration(test_session)
60
+ # 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
62
+
63
+ # skip backend request if library configuration was loaded by a different process and stored on disk
64
+ library_configuration_loaded = load_component_state
65
+ return @library_configuration if library_configuration_loaded
66
+
67
+ @library_configuration = @library_settings_client.fetch(test_session)
68
+
69
+ # sometimes we can skip code coverage for default branch if there are no changes in the repository
70
+ # backend needs git metadata uploaded for this test session to check if we can skip code coverage
71
+ if @library_configuration.require_git?
72
+ Datadog.logger.debug { "Library configuration endpoint requires git upload to be finished, waiting..." }
73
+ git_tree_upload_worker.wait_until_done
74
+
75
+ Datadog.logger.debug { "Requesting library configuration again..." }
76
+ @library_configuration = @library_settings_client.fetch(test_session)
77
+
78
+ if @library_configuration.require_git?
79
+ Datadog.logger.debug { "git metadata upload did not complete in time when configuring library" }
80
+ end
81
+ end
82
+
83
+ # Store component state for distributed test runs
84
+ store_component_state if test_session.distributed
85
+
86
+ @library_configuration
87
+ end
88
+
77
89
  def test_management
78
90
  Datadog.send(:components).test_management
79
91
  end
@@ -216,7 +216,9 @@ module Datadog
216
216
 
217
217
  # @internal
218
218
  def datadog_skip_reason
219
- if skipped_by_test_impact_analysis?
219
+ if in_test_discovery_mode?
220
+ Ext::Test::SkipReason::TEST_DISCOVERY_MODE
221
+ elsif skipped_by_test_impact_analysis?
220
222
  Ext::Test::SkipReason::TEST_IMPACT_ANALYSIS
221
223
  elsif disabled? || quarantined?
222
224
  Ext::Test::SkipReason::TEST_MANAGEMENT_DISABLED
@@ -225,7 +227,7 @@ module Datadog
225
227
 
226
228
  # @internal
227
229
  def should_skip?
228
- skipped_by_test_impact_analysis? || (disabled? && !attempt_to_fix?)
230
+ in_test_discovery_mode? || skipped_by_test_impact_analysis? || (disabled? && !attempt_to_fix?)
229
231
  end
230
232
 
231
233
  # @internal
@@ -238,6 +240,16 @@ module Datadog
238
240
  get_tag(Ext::Test::TAG_ITR_SKIPPED_BY_ITR) == "true"
239
241
  end
240
242
 
243
+ # @internal
244
+ def in_test_discovery_mode?
245
+ !!@in_test_discovery_mode
246
+ end
247
+
248
+ # @internal
249
+ def mark_test_discovery_mode!
250
+ @in_test_discovery_mode = true
251
+ end
252
+
241
253
  private
242
254
 
243
255
  def record_test_result(datadog_status)
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require_relative "../ext/test"
6
+ require_relative "../ext/test_discovery"
7
+
8
+ module Datadog
9
+ module CI
10
+ module TestDiscovery
11
+ # Test discovery mode component that manages test discovery output and lifecycle
12
+ class Component
13
+ def initialize(
14
+ enabled:,
15
+ output_path:
16
+ )
17
+ @enabled = enabled
18
+ @output_path = output_path
19
+
20
+ @buffer = []
21
+ @buffer_mutex = Mutex.new
22
+ end
23
+
24
+ def configure(library_settings, test_session)
25
+ # This method is noop for this component, it is present for compatibility with other components
26
+ end
27
+
28
+ def enabled?
29
+ @enabled
30
+ end
31
+
32
+ def disable_features_for_test_discovery!(settings)
33
+ return unless @enabled
34
+
35
+ Datadog.logger.debug("ATTENTION! Running in test discovery mode, disabling all features")
36
+
37
+ # in test discovery mode don't send anything to Datadog
38
+ settings.ci.discard_traces = true
39
+
40
+ # Disable all feature flags when in test discovery mode
41
+ settings.telemetry.enabled = false
42
+ settings.ci.itr_enabled = false
43
+ settings.ci.git_metadata_upload_enabled = false
44
+ settings.ci.retry_failed_tests_enabled = false
45
+ settings.ci.retry_new_tests_enabled = false
46
+ settings.ci.test_management_enabled = false
47
+ settings.ci.agentless_logs_submission_enabled = false
48
+ settings.ci.impacted_tests_detection_enabled = false
49
+ end
50
+
51
+ def on_test_session_start
52
+ return unless @enabled
53
+
54
+ if @output_path.nil? || @output_path&.empty?
55
+ @output_path = Ext::TestDiscovery::DEFAULT_OUTPUT_PATH
56
+ end
57
+
58
+ # thanks RBS for this weirdness
59
+ output_path = @output_path
60
+ return unless output_path
61
+
62
+ output_dir = File.dirname(output_path)
63
+ FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
64
+
65
+ Datadog.logger.debug { "Test discovery output path: #{output_path}" }
66
+
67
+ @buffer_mutex.synchronize { @buffer.clear }
68
+ end
69
+
70
+ def on_test_session_end
71
+ return unless @enabled
72
+
73
+ @buffer_mutex.synchronize do
74
+ flush_buffer_unsafe if @buffer.any?
75
+ end
76
+ end
77
+
78
+ def on_test_started(test)
79
+ return unless @enabled
80
+
81
+ # Mark test as being in test discovery mode so it will be skipped
82
+ # even if we are not running in dry run mode.
83
+ test.mark_test_discovery_mode!
84
+
85
+ test_info = {
86
+ "name" => test.name,
87
+ "suite" => test.test_suite_name,
88
+ "sourceFile" => test.source_file,
89
+ "fqn" => test.datadog_test_id
90
+ }
91
+
92
+ Datadog.logger.debug { "Discovered test: #{test_info}" }
93
+
94
+ @buffer_mutex.synchronize do
95
+ @buffer << test_info
96
+
97
+ flush_buffer_unsafe if @buffer.size >= Ext::TestDiscovery::MAX_BUFFER_SIZE
98
+ end
99
+ end
100
+
101
+ def shutdown!
102
+ return unless @enabled
103
+
104
+ @buffer_mutex.synchronize do
105
+ flush_buffer_unsafe if @buffer.any?
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ # Unsafe version - caller must hold @buffer_mutex
112
+ def flush_buffer_unsafe
113
+ return unless @output_path && @buffer.any?
114
+
115
+ output_path = @output_path
116
+ return unless output_path
117
+
118
+ Datadog.logger.debug { "Flushing test discovery buffer with #{@buffer.size} entries to #{output_path}" }
119
+
120
+ File.open(output_path, "a") do |file|
121
+ # disk IO latency is much bigger than time to serialize 10k JSON objects, so we do it in memory and then write to disk
122
+ json_lines = @buffer.map { |test_info| JSON.generate(test_info) }
123
+ file.puts(json_lines)
124
+ end
125
+
126
+ @buffer.clear
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -162,6 +162,10 @@ module Datadog
162
162
  end
163
163
 
164
164
  def active_test_suite(test_suite_name)
165
+ # when test_suite_name is not provided (from public API most likely)
166
+ # we return the single active test suite because most of the time there is only one test suite running
167
+ return single_active_test_suite if test_suite_name.nil?
168
+
165
169
  # when fetching test_suite to use as test's context, try local context instance first
166
170
  local_test_suite = @context.active_test_suite(test_suite_name)
167
171
  return local_test_suite if local_test_suite
@@ -238,6 +242,8 @@ module Datadog
238
242
  # Signal Remote::Component to configure the library.
239
243
  # Note that it will call this component back (unfortunate circular dependency).
240
244
  remote.configure(test_session)
245
+
246
+ test_discovery&.on_test_session_start
241
247
  end
242
248
 
243
249
  # intentionally empty
@@ -273,6 +279,8 @@ module Datadog
273
279
  test_optimisation.start_coverage(test)
274
280
 
275
281
  test_retries.record_test_started(test)
282
+
283
+ test_discovery&.on_test_started(test)
276
284
  end
277
285
 
278
286
  def on_test_session_finished(test_session)
@@ -282,6 +290,8 @@ module Datadog
282
290
 
283
291
  Telemetry.event_finished(test_session)
284
292
 
293
+ test_discovery&.on_test_session_end
294
+
285
295
  Utils::FileStorage.cleanup
286
296
  end
287
297
 
@@ -308,6 +318,14 @@ module Datadog
308
318
  end
309
319
 
310
320
  # HELPERS
321
+ def single_active_test_suite
322
+ # when fetching test_suite to use as test's context, try local context instance first
323
+ local_test_suite = @context.single_active_test_suite
324
+ return local_test_suite if local_test_suite
325
+
326
+ maybe_remote_context.single_active_test_suite
327
+ end
328
+
311
329
  def skip_tracing(block = nil)
312
330
  block&.call(nil)
313
331
  end
@@ -446,6 +464,10 @@ module Datadog
446
464
  Datadog.send(:components).impacted_tests_detection
447
465
  end
448
466
 
467
+ def test_discovery
468
+ Datadog.send(:components).test_discovery
469
+ end
470
+
449
471
  # DISTRIBUTED RUBY CONTEXT
450
472
  def start_drb_service
451
473
  return if @context_service_uri
@@ -3,6 +3,7 @@
3
3
  require_relative "../contrib/instrumentation"
4
4
  require_relative "../ext/app_types"
5
5
  require_relative "../ext/environment"
6
+ require_relative "../ext/settings"
6
7
  require_relative "../ext/telemetry"
7
8
  require_relative "../ext/test"
8
9
  require_relative "../utils/telemetry"
@@ -30,11 +31,15 @@ module Datadog
30
31
  end
31
32
 
32
33
  def self.test_session_started(test_session)
34
+ auto_injected = !ENV[Ext::Settings::ENV_AUTO_INSTRUMENTATION_PROVIDER].nil?
35
+ agentless_logs_enabled = !!agentless_logs_component&.enabled
36
+
33
37
  Utils::Telemetry.inc(
34
38
  Ext::Telemetry::METRIC_TEST_SESSION,
35
39
  1,
36
40
  {
37
- Ext::Telemetry::TAG_AUTO_INJECTED => Contrib::Instrumentation.auto_instrumented?.to_s,
41
+ Ext::Telemetry::TAG_AUTO_INJECTED => auto_injected.to_s,
42
+ Ext::Telemetry::TAG_AGENTLESS_LOG_SUBMISSION_ENABLED => agentless_logs_enabled.to_s,
38
43
  Ext::Telemetry::TAG_PROVIDER => test_session.ci_provider || Ext::Telemetry::Provider::UNSUPPORTED
39
44
  }
40
45
  )
@@ -86,6 +91,10 @@ module Datadog
86
91
  browser_driver = span.get_tag(Ext::Test::TAG_BROWSER_DRIVER)
87
92
  tags[Ext::Telemetry::TAG_BROWSER_DRIVER] = browser_driver if browser_driver
88
93
  end
94
+
95
+ def self.agentless_logs_component
96
+ Datadog.send(:components).agentless_logs_submission
97
+ end
89
98
  end
90
99
  end
91
100
  end
@@ -4,8 +4,8 @@ module Datadog
4
4
  module CI
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 21
8
- PATCH = 1
7
+ MINOR = 22
8
+ PATCH = 0
9
9
  PRE = nil
10
10
  BUILD = nil
11
11
  # PRE and BUILD above are modified for dev gems during gem build GHA workflow
data/lib/datadog/ci.rb CHANGED
@@ -187,9 +187,21 @@ module Datadog
187
187
  # test_suite.finish
188
188
  # ```
189
189
  #
190
+ # Most of the time, there is only one active test suite - except when using minitest with parallel test runner,
191
+ # such as rails built-in test runner.
192
+ #
193
+ # When using RSpec or minitest without parallel test runner, there is only one active test suite, so you can use the following code to fetch it:
194
+ #
195
+ # ```
196
+ # test_suite = Datadog::CI.active_test_suite
197
+ # test_suite.finish
198
+ # ```
199
+ #
200
+ # @param test_suite_name [String?] the name of the test suite to fetch. Optional. When not provided, it assumes that there is a single active test suite and returns it. If there are multiple active test suites and test_suite_name is not provided, it returns nil.
201
+ #
190
202
  # @return [Datadog::CI::TestSuite] the active test suite
191
203
  # @return [nil] if no test suite with given name is active
192
- def active_test_suite(test_suite_name)
204
+ def active_test_suite(test_suite_name = nil)
193
205
  test_visibility.active_test_suite(test_suite_name)
194
206
  end
195
207
 
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.21.1
4
+ version: 1.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
@@ -193,6 +193,7 @@ files:
193
193
  - lib/datadog/ci/ext/settings.rb
194
194
  - lib/datadog/ci/ext/telemetry.rb
195
195
  - lib/datadog/ci/ext/test.rb
196
+ - lib/datadog/ci/ext/test_discovery.rb
196
197
  - lib/datadog/ci/ext/transport.rb
197
198
  - lib/datadog/ci/git/base_branch_sha_detection/base.rb
198
199
  - lib/datadog/ci/git/base_branch_sha_detection/branch_metric.rb
@@ -220,6 +221,7 @@ files:
220
221
  - lib/datadog/ci/remote/slow_test_retries.rb
221
222
  - lib/datadog/ci/span.rb
222
223
  - lib/datadog/ci/test.rb
224
+ - lib/datadog/ci/test_discovery/component.rb
223
225
  - lib/datadog/ci/test_management/component.rb
224
226
  - lib/datadog/ci/test_management/null_component.rb
225
227
  - lib/datadog/ci/test_management/tests_properties.rb
@@ -316,7 +318,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
316
318
  - !ruby/object:Gem::Version
317
319
  version: 2.0.0
318
320
  requirements: []
319
- rubygems_version: 3.6.7
321
+ rubygems_version: 3.6.9
320
322
  specification_version: 4
321
323
  summary: Datadog Test Optimization for your ruby application
322
324
  test_files: []