datadog-ci 1.16.0 → 1.18.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -2
  3. data/ext/datadog_ci_native/ci.c +10 -0
  4. data/ext/{datadog_cov → datadog_ci_native}/datadog_cov.c +119 -147
  5. data/ext/datadog_ci_native/datadog_cov.h +3 -0
  6. data/ext/datadog_ci_native/datadog_source_code.c +28 -0
  7. data/ext/datadog_ci_native/datadog_source_code.h +3 -0
  8. data/ext/{datadog_cov → datadog_ci_native}/extconf.rb +1 -1
  9. data/lib/datadog/ci/codeowners/rule.rb +5 -0
  10. data/lib/datadog/ci/configuration/components.rb +17 -5
  11. data/lib/datadog/ci/configuration/settings.rb +6 -0
  12. data/lib/datadog/ci/contrib/knapsack/patcher.rb +1 -3
  13. data/lib/datadog/ci/contrib/knapsack/runner.rb +2 -0
  14. data/lib/datadog/ci/contrib/minitest/runner.rb +1 -0
  15. data/lib/datadog/ci/contrib/minitest/test.rb +24 -9
  16. data/lib/datadog/ci/contrib/parallel_tests/patcher.rb +1 -3
  17. data/lib/datadog/ci/contrib/patcher.rb +4 -0
  18. data/lib/datadog/ci/contrib/rspec/example.rb +14 -7
  19. data/lib/datadog/ci/contrib/rspec/helpers.rb +1 -3
  20. data/lib/datadog/ci/ext/environment/extractor.rb +4 -6
  21. data/lib/datadog/ci/ext/environment/providers/appveyor.rb +5 -0
  22. data/lib/datadog/ci/ext/environment/providers/base.rb +7 -2
  23. data/lib/datadog/ci/ext/environment/providers/bitbucket.rb +6 -0
  24. data/lib/datadog/ci/ext/environment/providers/bitrise.rb +7 -1
  25. data/lib/datadog/ci/ext/environment/providers/buddy.rb +5 -0
  26. data/lib/datadog/ci/ext/environment/providers/github_actions.rb +37 -18
  27. data/lib/datadog/ci/ext/environment/providers/gitlab.rb +13 -1
  28. data/lib/datadog/ci/ext/environment/providers/user_defined_tags.rb +12 -0
  29. data/lib/datadog/ci/ext/git.rb +3 -0
  30. data/lib/datadog/ci/ext/settings.rb +1 -0
  31. data/lib/datadog/ci/ext/telemetry.rb +3 -0
  32. data/lib/datadog/ci/ext/test.rb +5 -1
  33. data/lib/datadog/ci/ext/transport.rb +1 -0
  34. data/lib/datadog/ci/git/base_branch_sha_detection/base.rb +66 -0
  35. data/lib/datadog/ci/git/base_branch_sha_detection/branch_metric.rb +34 -0
  36. data/lib/datadog/ci/git/base_branch_sha_detection/guesser.rb +137 -0
  37. data/lib/datadog/ci/git/base_branch_sha_detection/merge_base_extractor.rb +29 -0
  38. data/lib/datadog/ci/git/base_branch_sha_detector.rb +63 -0
  39. data/lib/datadog/ci/git/cli.rb +56 -0
  40. data/lib/datadog/ci/git/local_repository.rb +131 -118
  41. data/lib/datadog/ci/git/telemetry.rb +14 -0
  42. data/lib/datadog/ci/git/tree_uploader.rb +10 -3
  43. data/lib/datadog/ci/impacted_tests_detection/component.rb +81 -0
  44. data/lib/datadog/ci/remote/component.rb +6 -1
  45. data/lib/datadog/ci/remote/library_settings.rb +8 -0
  46. data/lib/datadog/ci/span.rb +7 -0
  47. data/lib/datadog/ci/test.rb +6 -0
  48. data/lib/datadog/ci/test_management/tests_properties.rb +2 -1
  49. data/lib/datadog/ci/test_optimisation/component.rb +10 -6
  50. data/lib/datadog/ci/test_optimisation/coverage/ddcov.rb +1 -1
  51. data/lib/datadog/ci/test_retries/component.rb +8 -17
  52. data/lib/datadog/ci/test_retries/driver/{retry_new.rb → retry_flake_detection.rb} +1 -1
  53. data/lib/datadog/ci/test_retries/strategy/{retry_new.rb → retry_flake_detection.rb} +4 -4
  54. data/lib/datadog/ci/test_visibility/component.rb +6 -0
  55. data/lib/datadog/ci/test_visibility/telemetry.rb +3 -0
  56. data/lib/datadog/ci/utils/command.rb +116 -0
  57. data/lib/datadog/ci/utils/source_code.rb +31 -0
  58. data/lib/datadog/ci/version.rb +1 -1
  59. metadata +21 -8
@@ -4,6 +4,7 @@ require "datadog/core/telemetry/ext"
4
4
 
5
5
  require_relative "../ext/settings"
6
6
  require_relative "../git/tree_uploader"
7
+ require_relative "../impacted_tests_detection/component"
7
8
  require_relative "../logs/component"
8
9
  require_relative "../logs/transport"
9
10
  require_relative "../remote/component"
@@ -36,7 +37,7 @@ module Datadog
36
37
  # Adds CI behavior to Datadog trace components
37
38
  module Components
38
39
  attr_reader :test_visibility, :test_optimisation, :git_tree_upload_worker, :ci_remote, :test_retries,
39
- :test_management, :agentless_logs_submission
40
+ :test_management, :agentless_logs_submission, :impacted_tests_detection
40
41
 
41
42
  def initialize(settings)
42
43
  @test_optimisation = nil
@@ -45,6 +46,7 @@ module Datadog
45
46
  @ci_remote = nil
46
47
  @test_retries = TestRetries::NullComponent.new
47
48
  @test_management = TestManagement::NullComponent.new
49
+ @impacted_tests_detection = nil
48
50
 
49
51
  # Activate CI mode if enabled
50
52
  if settings.ci.enabled
@@ -138,6 +140,8 @@ module Datadog
138
140
  )
139
141
 
140
142
  @agentless_logs_submission = build_agentless_logs_component(settings, test_visibility_api)
143
+
144
+ @impacted_tests_detection = ImpactedTestsDetection::Component.new(enabled: settings.ci.impacted_tests_detection_enabled)
141
145
  end
142
146
 
143
147
  def build_test_optimisation(settings, test_visibility_api)
@@ -237,7 +241,7 @@ module Datadog
237
241
 
238
242
  def build_git_upload_worker(settings, api)
239
243
  if settings.ci.git_metadata_upload_enabled
240
- git_tree_uploader = Git::TreeUploader.new(api: api)
244
+ git_tree_uploader = Git::TreeUploader.new(api: api, force_unshallow: settings.ci.impacted_tests_detection_enabled)
241
245
  Worker.new do |repository_url|
242
246
  git_tree_uploader.call(repository_url)
243
247
  end
@@ -323,13 +327,21 @@ module Datadog
323
327
  settings.telemetry.shutdown_timeout_seconds = 60.0
324
328
 
325
329
  begin
326
- require "datadog/core/telemetry/http/adapters/net"
330
+ require "datadog/core/transport/http/adapters/net"
327
331
 
328
- # patch gem's telemetry transport layer to use Net::HTTP instead of WebMock's Net::HTTP
329
- Core::Telemetry::Http::Adapters::Net.include(CI::Transport::Adapters::TelemetryWebmockSafeAdapter)
332
+ # patch gem's core transport layer to use Net::HTTP instead of WebMock's Net::HTTP
333
+ Core::Transport::HTTP::Adapters::Net.include(CI::Transport::Adapters::TelemetryWebmockSafeAdapter)
330
334
  rescue LoadError, StandardError => e
331
335
  Datadog.logger.warn("Failed to patch Datadog gem's telemetry layer: #{e}")
332
336
  end
337
+
338
+ # for compatibility with old telemetry transport
339
+ begin
340
+ require "datadog/core/telemetry/http/adapters/net"
341
+ Core::Telemetry::Http::Adapters::Net.include(CI::Transport::Adapters::TelemetryWebmockSafeAdapter)
342
+ rescue LoadError, StandardError => e
343
+ Datadog.logger.debug("The old telemetry transport layer is not available: #{e}")
344
+ end
333
345
  end
334
346
 
335
347
  # When timecop is present:
@@ -151,6 +151,12 @@ module Datadog
151
151
  o.env CI::Ext::Settings::ENV_TEST_VISIBILITY_DRB_SERVER_URI
152
152
  end
153
153
 
154
+ option :impacted_tests_detection_enabled do |o|
155
+ o.type :bool
156
+ o.env CI::Ext::Settings::ENV_IMPACTED_TESTS_DETECTION_ENABLED
157
+ o.default false
158
+ end
159
+
154
160
  define_method(:instrument) do |integration_name, options = {}, &block|
155
161
  return unless enabled
156
162
 
@@ -9,9 +9,7 @@ module Datadog
9
9
  module Patcher
10
10
  include Datadog::CI::Contrib::Patcher
11
11
 
12
- module_function
13
-
14
- def patch
12
+ def self.patch
15
13
  if ::RSpec::Core::Runner.ancestors.include?(::KnapsackPro::Extensions::RSpecExtension::Runner)
16
14
  # knapsack already patched rspec runner
17
15
  require_relative "runner"
@@ -19,6 +19,7 @@ module Datadog
19
19
  return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled]
20
20
  return super unless datadog_configuration[:enabled]
21
21
 
22
+ # @type var test_session: Datadog::CI::TestSession?
22
23
  test_session = test_visibility_component.start_test_session(
23
24
  tags: {
24
25
  CI::Ext::Test::TAG_FRAMEWORK => CI::Contrib::RSpec::Ext::FRAMEWORK,
@@ -27,6 +28,7 @@ module Datadog
27
28
  service: datadog_configuration[:service_name]
28
29
  )
29
30
 
31
+ # @type var test_module: Datadog::CI::TestModule?
30
32
  test_module = test_visibility_component.start_test_module(CI::Contrib::RSpec::Ext::FRAMEWORK)
31
33
 
32
34
  result = super
@@ -38,6 +38,7 @@ module Datadog
38
38
  def run_one_method(klass, method_name)
39
39
  return super unless datadog_configuration[:enabled]
40
40
 
41
+ # @type var result: untyped
41
42
  result = nil
42
43
 
43
44
  test_retries_component.with_retries do
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../../ext/test"
4
4
  require_relative "../../git/local_repository"
5
+ require_relative "../../utils/source_code"
5
6
  require_relative "../instrumentation"
6
7
  require_relative "ext"
7
8
  require_relative "helpers"
@@ -27,20 +28,33 @@ module Datadog
27
28
  end
28
29
 
29
30
  test_suite_name = Helpers.test_suite_name(self.class, name)
30
- source_file, line_number = method(name).source_location
31
+
32
+ # @type var tags : Hash[String, String]
33
+ tags = {
34
+ CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
35
+ CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s
36
+ }
37
+
38
+ # try to find out where test method starts and ends
39
+ test_method = method(name)
40
+ source_file, first_line_number = test_method.source_location
41
+ last_line_number = Utils::SourceCode.last_line(test_method)
42
+
43
+ tags[CI::Ext::Test::TAG_SOURCE_FILE] = Git::LocalRepository.relative_to_root(source_file) if source_file
44
+ tags[CI::Ext::Test::TAG_SOURCE_START] = first_line_number.to_s if first_line_number
45
+ tags[CI::Ext::Test::TAG_SOURCE_END] = last_line_number.to_s if last_line_number
31
46
 
32
47
  test_span = test_visibility_component.trace_test(
33
48
  name,
34
49
  test_suite_name,
35
- tags: {
36
- CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
37
- CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s,
38
- CI::Ext::Test::TAG_SOURCE_FILE => Git::LocalRepository.relative_to_root(source_file),
39
- CI::Ext::Test::TAG_SOURCE_START => line_number.to_s
40
- },
50
+ tags: tags,
41
51
  service: datadog_configuration[:service_name]
42
52
  )
53
+ # Steep type checker doesn't know that we patched Minitest::Test class definition
54
+ #
55
+ # steep:ignore:start
43
56
  test_span&.itr_unskippable! if self.class.dd_suite_unskippable? || self.class.dd_test_unskippable?(name)
57
+ # steep:ignore:end
44
58
  skip(test_span&.datadog_skip_reason) if test_span&.should_skip?
45
59
  end
46
60
 
@@ -100,9 +114,10 @@ module Datadog
100
114
  end
101
115
 
102
116
  def dd_test_unskippable?(test_name)
103
- return false unless @datadog_itr_unskippable_tests
117
+ tests = @datadog_itr_unskippable_tests
118
+ return false unless tests
104
119
 
105
- @datadog_itr_unskippable_tests.include?(test_name)
120
+ tests.include?(test_name)
106
121
  end
107
122
  end
108
123
  end
@@ -11,9 +11,7 @@ module Datadog
11
11
  module Patcher
12
12
  include Datadog::CI::Contrib::Patcher
13
13
 
14
- module_function
15
-
16
- def patch
14
+ def self.patch
17
15
  ::ParallelTests::CLI.include(CLI)
18
16
  end
19
17
  end
@@ -29,9 +29,13 @@ module Datadog
29
29
  return unless defined?(super)
30
30
 
31
31
  patch_only_once.run do
32
+ # There is no way to tell Steep that we prepend these methods to modules that have .patch method
33
+ #
34
+ # steep:ignore:start
32
35
  super.tap do
33
36
  @patch_successful = true
34
37
  end
38
+ # steep:ignore:end
35
39
  rescue => e
36
40
  on_patch_error(e)
37
41
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../../ext/test"
4
4
  require_relative "../../git/local_repository"
5
+ require_relative "../../utils/source_code"
5
6
  require_relative "../../utils/test_run"
6
7
  require_relative "../instrumentation"
7
8
  require_relative "ext"
@@ -26,17 +27,23 @@ module Datadog
26
27
  # don't report test to RSpec::Core::Reporter until retries are done
27
28
  @skip_reporting = true
28
29
 
30
+ # @type var tags : Hash[String, String]
31
+ tags = {
32
+ CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
33
+ CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s,
34
+ CI::Ext::Test::TAG_SOURCE_FILE => Git::LocalRepository.relative_to_root(metadata[:file_path]),
35
+ CI::Ext::Test::TAG_SOURCE_START => metadata[:line_number].to_s,
36
+ CI::Ext::Test::TAG_PARAMETERS => datadog_test_parameters
37
+ }
38
+
39
+ end_line = Utils::SourceCode.last_line(@example_block)
40
+ tags[CI::Ext::Test::TAG_SOURCE_END] = end_line.to_s if end_line
41
+
29
42
  test_retries_component.with_retries do
30
43
  test_visibility_component.trace_test(
31
44
  datadog_test_name,
32
45
  datadog_test_suite_name,
33
- tags: {
34
- CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
35
- CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s,
36
- CI::Ext::Test::TAG_SOURCE_FILE => Git::LocalRepository.relative_to_root(metadata[:file_path]),
37
- CI::Ext::Test::TAG_SOURCE_START => metadata[:line_number].to_s,
38
- CI::Ext::Test::TAG_PARAMETERS => datadog_test_parameters
39
- },
46
+ tags: tags,
40
47
  service: datadog_configuration[:service_name]
41
48
  ) do |test_span|
42
49
  test_span&.itr_unskippable! if datadog_unskippable?
@@ -6,9 +6,7 @@ module Datadog
6
6
  module RSpec
7
7
  # Helper methods for RSpec instrumentation
8
8
  module Helpers
9
- module_function
10
-
11
- def parallel_tests?
9
+ def self.parallel_tests?
12
10
  !!ENV.fetch("TEST_ENV_NUMBER", nil) && !!ENV.fetch("PARALLEL_TEST_GROUPS", nil)
13
11
  end
14
12
  end
@@ -46,14 +46,12 @@ module Datadog
46
46
  Git::TAG_COMMIT_COMMITTER_EMAIL => @provider.git_commit_committer_email,
47
47
  Git::TAG_COMMIT_COMMITTER_NAME => @provider.git_commit_committer_name,
48
48
  Git::TAG_COMMIT_MESSAGE => @provider.git_commit_message,
49
- Git::TAG_COMMIT_SHA => @provider.git_commit_sha
49
+ Git::TAG_COMMIT_SHA => @provider.git_commit_sha,
50
+ Git::TAG_PULL_REQUEST_BASE_BRANCH => @provider.git_pull_request_base_branch,
51
+ Git::TAG_PULL_REQUEST_BASE_BRANCH_SHA => @provider.git_pull_request_base_branch_sha,
52
+ Git::TAG_COMMIT_HEAD_SHA => @provider.git_commit_head_sha
50
53
  }
51
54
 
52
- # set additional tags if provider needs them
53
- @provider.additional_tags.each do |key, value|
54
- @tags[key] = value
55
- end
56
-
57
55
  # Normalize Git references and filter sensitive data
58
56
  normalize_git!
59
57
  # Expand ~
@@ -83,6 +83,11 @@ module Datadog
83
83
  commit_message
84
84
  end
85
85
 
86
+ def git_pull_request_base_branch
87
+ # from docs: build branch. For Pull Request commits it is base branch PR is merging into
88
+ env["APPVEYOR_REPO_BRANCH"]
89
+ end
90
+
86
91
  private
87
92
 
88
93
  def github_repo_provider?
@@ -96,8 +96,13 @@ module Datadog
96
96
  def git_commit_sha
97
97
  end
98
98
 
99
- def additional_tags
100
- {}
99
+ def git_pull_request_base_branch
100
+ end
101
+
102
+ def git_pull_request_base_branch_sha
103
+ end
104
+
105
+ def git_commit_head_sha
101
106
  end
102
107
 
103
108
  private
@@ -59,6 +59,12 @@ module Datadog
59
59
  env["BITBUCKET_TAG"]
60
60
  end
61
61
 
62
+ def git_pull_request_base_branch
63
+ # from docs: The pull request destination branch (used in combination with BITBUCKET_BRANCH).
64
+ # Only available on a pull request triggered build
65
+ env["BITBUCKET_PR_DESTINATION_BRANCH"]
66
+ end
67
+
62
68
  private
63
69
 
64
70
  def url
@@ -47,7 +47,7 @@ module Datadog
47
47
  end
48
48
 
49
49
  def git_branch
50
- env["BITRISEIO_GIT_BRANCH_DEST"] || env["BITRISE_GIT_BRANCH"]
50
+ env["BITRISEIO_PULL_REQUEST_HEAD_BRANCH"] || env["BITRISE_GIT_BRANCH"]
51
51
  end
52
52
 
53
53
  def git_tag
@@ -73,6 +73,12 @@ module Datadog
73
73
  def git_commit_committer_email
74
74
  env["GIT_CLONE_COMMIT_COMMITER_EMAIL"] || env["GIT_CLONE_COMMIT_COMMITER_NAME"]
75
75
  end
76
+
77
+ def git_pull_request_base_branch
78
+ # from docs: Used only with builds triggered by pull requests: the destination/target branch of the pull request that triggered the build.
79
+ # For example, a pull request wants to merge the content of a branch into the branch main. In this case, this Env Var’s value is main.
80
+ env["BITRISEIO_GIT_BRANCH_DEST"]
81
+ end
76
82
  end
77
83
  end
78
84
  end
@@ -65,6 +65,11 @@ module Datadog
65
65
  def git_commit_committer_email
66
66
  env["BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL"]
67
67
  end
68
+
69
+ def git_pull_request_base_branch
70
+ # from docs: The name of the Git BASE branch of the currently run Pull Request
71
+ env["BUDDY_RUN_PR_BASE_BRANCH"]
72
+ end
68
73
  end
69
74
  end
70
75
  end
@@ -76,34 +76,53 @@ module Datadog
76
76
  }.reject { |_, v| v.nil? }.to_json
77
77
  end
78
78
 
79
- def additional_tags
80
- base_ref = env["GITHUB_BASE_REF"]
81
- return {} if base_ref.nil? || base_ref.empty?
79
+ def git_pull_request_base_branch
80
+ return nil if github_event_json.nil?
82
81
 
83
- # @type var result: Hash[String, String]
84
- result = {
85
- Git::TAG_PULL_REQUEST_BASE_BRANCH => base_ref
86
- }
87
-
88
- event_path = env["GITHUB_EVENT_PATH"]
89
- event_json = JSON.parse(File.read(event_path))
82
+ env["GITHUB_BASE_REF"]
83
+ end
90
84
 
91
- head_sha = event_json.dig("pull_request", "head", "sha")
92
- result[Git::TAG_COMMIT_HEAD_SHA] = head_sha if head_sha
85
+ def git_pull_request_base_branch_sha
86
+ return nil if git_pull_request_base_branch.nil?
93
87
 
94
- base_sha = event_json.dig("pull_request", "base", "sha")
95
- result[Git::TAG_PULL_REQUEST_BASE_BRANCH_SHA] = base_sha if base_sha
88
+ event_json = github_event_json
89
+ return nil if event_json.nil?
96
90
 
97
- result
91
+ event_json.dig("pull_request", "base", "sha")
98
92
  rescue => e
99
- Datadog.logger.error("Failed to extract additional tags from GitHub Actions: #{e}")
100
- Core::Telemetry::Logger.report(e, description: "Failed to extract additional tags from GitHub Actions")
93
+ Datadog.logger.error("Failed to extract pull request base branch SHA from GitHub Actions: #{e}")
94
+ Core::Telemetry::Logger.report(e, description: "Failed to extract pull request base branch SHA from GitHub Actions")
95
+ nil
96
+ end
101
97
 
102
- {}
98
+ def git_commit_head_sha
99
+ return nil if git_pull_request_base_branch.nil?
100
+
101
+ event_json = github_event_json
102
+ return nil if event_json.nil?
103
+
104
+ event_json.dig("pull_request", "head", "sha")
105
+ rescue => e
106
+ Datadog.logger.error("Failed to extract commit head SHA from GitHub Actions: #{e}")
107
+ Core::Telemetry::Logger.report(e, description: "Failed to extract commit head SHA from GitHub Actions")
108
+ nil
103
109
  end
104
110
 
105
111
  private
106
112
 
113
+ def github_event_json
114
+ return @github_event_json if defined?(@github_event_json)
115
+
116
+ event_path = env["GITHUB_EVENT_PATH"]
117
+ return @github_event_json = nil if event_path.nil? || event_path.empty?
118
+
119
+ @github_event_json = JSON.parse(File.read(event_path))
120
+ rescue => e
121
+ Datadog.logger.error("Failed to parse GitHub event JSON: #{e}")
122
+ Core::Telemetry::Logger.report(e, description: "Failed to parse GitHub event JSON")
123
+ @github_event_json = nil
124
+ end
125
+
107
126
  def github_server_url
108
127
  return @github_server_url if defined?(@github_server_url)
109
128
 
@@ -100,13 +100,25 @@ module Datadog
100
100
  }.to_json
101
101
  end
102
102
 
103
+ def git_pull_request_base_branch
104
+ env["CI_MERGE_REQUEST_TARGET_BRANCH_NAME"]
105
+ end
106
+
107
+ def git_pull_request_base_branch_sha
108
+ env["CI_MERGE_REQUEST_TARGET_BRANCH_SHA"]
109
+ end
110
+
111
+ def git_commit_head_sha
112
+ env["CI_MERGE_REQUEST_SOURCE_BRANCH_SHA"]
113
+ end
114
+
103
115
  private
104
116
 
105
117
  def extract_name_email
106
118
  return @name_email_tuple if defined?(@name_email_tuple)
107
119
 
108
120
  name_and_email_string = env["CI_COMMIT_AUTHOR"]
109
- if name_and_email_string.include?("<") && (match = /^([^<]*)<([^>]*)>$/.match(name_and_email_string))
121
+ if name_and_email_string&.include?("<") && (match = /^([^<]*)<([^>]*)>$/.match(name_and_email_string))
110
122
  name = match[1]
111
123
  name = name.strip if name
112
124
  email = match[2]
@@ -54,6 +54,18 @@ module Datadog
54
54
  def git_commit_committer_date
55
55
  env[Git::ENV_COMMIT_COMMITTER_DATE]
56
56
  end
57
+
58
+ def git_pull_request_base_branch
59
+ env[Git::ENV_PULL_REQUEST_BASE_BRANCH]
60
+ end
61
+
62
+ def git_pull_request_base_branch_sha
63
+ env[Git::ENV_PULL_REQUEST_BASE_BRANCH_SHA]
64
+ end
65
+
66
+ def git_commit_head_sha
67
+ env[Git::ENV_COMMIT_HEAD_SHA]
68
+ end
57
69
  end
58
70
  end
59
71
  end
@@ -36,6 +36,9 @@ module Datadog
36
36
  ENV_COMMIT_COMMITTER_NAME = "DD_GIT_COMMIT_COMMITTER_NAME"
37
37
  ENV_COMMIT_COMMITTER_EMAIL = "DD_GIT_COMMIT_COMMITTER_EMAIL"
38
38
  ENV_COMMIT_COMMITTER_DATE = "DD_GIT_COMMIT_COMMITTER_DATE"
39
+ ENV_PULL_REQUEST_BASE_BRANCH = "DD_GIT_PULL_REQUEST_BASE_BRANCH"
40
+ ENV_PULL_REQUEST_BASE_BRANCH_SHA = "DD_GIT_PULL_REQUEST_BASE_BRANCH_SHA"
41
+ ENV_COMMIT_HEAD_SHA = "DD_GIT_COMMIT_HEAD_SHA"
39
42
  end
40
43
  end
41
44
  end
@@ -25,6 +25,7 @@ module Datadog
25
25
  ENV_TEST_VISIBILITY_DRB_SERVER_URI = "DD_TEST_VISIBILITY_DRB_SERVER_URI"
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
+ ENV_IMPACTED_TESTS_DETECTION_ENABLED = "DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED"
28
29
 
29
30
  # Source: https://docs.datadoghq.com/getting_started/site/
30
31
  DD_SITE_ALLOWLIST = %w[
@@ -90,6 +90,7 @@ module Datadog
90
90
  TAG_IS_QUARANTINED = "is_quarantined"
91
91
  TAG_IS_TEST_DISABLED = "is_disabled"
92
92
  TAG_HAS_FAILED_ALL_RETRIES = "has_failed_all_retries"
93
+ TAG_IS_MODIFIED = "is_modified"
93
94
  # tags for git_requests.settings_response metric
94
95
  TAG_COVERAGE_ENABLED = "coverage_enabled"
95
96
  TAG_ITR_ENABLED = "itr_enabled"
@@ -138,6 +139,8 @@ module Datadog
138
139
  GET_LOCAL_COMMITS = "get_local_commits"
139
140
  GET_OBJECTS = "get_objects"
140
141
  PACK_OBJECTS = "pack_objects"
142
+ DIFF = "diff"
143
+ BASE_COMMIT_SHA = "base_commit_sha"
141
144
  end
142
145
 
143
146
  module Provider
@@ -20,6 +20,7 @@ module Datadog
20
20
  TAG_COMMAND = "test.command"
21
21
  TAG_SOURCE_FILE = "test.source.file"
22
22
  TAG_SOURCE_START = "test.source.start"
23
+ TAG_SOURCE_END = "test.source.end"
23
24
  TAG_CODEOWNERS = "test.codeowners"
24
25
  TAG_PARAMETERS = "test.parameters"
25
26
 
@@ -94,7 +95,7 @@ module Datadog
94
95
  AUTO_TEST_RETRIES_VERSION = "1"
95
96
  TEST_MANAGEMENT_QUARANTINE_VERSION = "1"
96
97
  TEST_MANAGEMENT_DISABLE_VERSION = "1"
97
- TEST_MANAGEMENT_ATTEMPT_TO_FIX_VERSION = "2"
98
+ TEST_MANAGEMENT_ATTEMPT_TO_FIX_VERSION = "4"
98
99
  end
99
100
 
100
101
  # Map of capabilities to their versions
@@ -108,6 +109,9 @@ module Datadog
108
109
  }.freeze
109
110
  end
110
111
 
112
+ # impacted tests detection
113
+ TAG_TEST_IS_MODIFIED = "test.is_modified"
114
+
111
115
  # internal APM tag to mark a span as a test span
112
116
  TAG_SPAN_KIND = "span.kind"
113
117
  SPAN_KIND_TEST = "test"
@@ -48,6 +48,7 @@ module Datadog
48
48
  DD_API_SETTINGS_RESPONSE_TEST_MANAGEMENT_KEY = "test_management"
49
49
  DD_API_SETTINGS_RESPONSE_ATTEMPT_TO_FIX_RETRIES_KEY = "attempt_to_fix_retries"
50
50
  DD_API_SETTINGS_RESPONSE_DEFAULT = {DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY => false}.freeze
51
+ DD_API_SETTINGS_RESPONSE_IMPACTED_TESTS_ENABLED_KEY = "impacted_tests_enabled"
51
52
 
52
53
  DD_API_GIT_SEARCH_COMMITS_PATH = "/api/v2/git/repository/search_commits"
53
54
 
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../cli"
4
+
5
+ module Datadog
6
+ module CI
7
+ module Git
8
+ module BaseBranchShaDetection
9
+ class Base
10
+ attr_reader :remote_name
11
+ attr_reader :source_branch
12
+
13
+ def initialize(remote_name, source_branch)
14
+ @remote_name = remote_name
15
+ @source_branch = source_branch
16
+ end
17
+
18
+ def call
19
+ raise NotImplementedError, "Subclasses must implement #call"
20
+ end
21
+
22
+ protected
23
+
24
+ def merge_base_sha(branch, source_branch)
25
+ CLI.exec_git_command(["merge-base", branch, source_branch], timeout: CLI::LONG_TIMEOUT)&.strip
26
+ rescue CLI::GitCommandExecutionError => e
27
+ Datadog.logger.debug { "Merge base calculation failed for branches '#{branch}' and '#{source_branch}': #{e}" }
28
+ nil
29
+ end
30
+
31
+ def check_and_fetch_branch(branch, remote_name)
32
+ # @type var short_branch_name: String
33
+ short_branch_name = remove_remote_prefix(branch, remote_name)
34
+
35
+ # Check if branch already fetched from remote
36
+ CLI.exec_git_command(["show-ref", "--verify", "--quiet", "refs/remotes/#{remote_name}/#{short_branch_name}"])
37
+ Datadog.logger.debug { "Branch '#{remote_name}/#{short_branch_name}' already fetched from remote, skipping" }
38
+ rescue CLI::GitCommandExecutionError => e
39
+ Datadog.logger.debug { "Branch '#{remote_name}/#{short_branch_name}' doesn't exist locally, checking remote..." }
40
+
41
+ begin
42
+ remote_heads = CLI.exec_git_command(["ls-remote", "--heads", remote_name, short_branch_name])
43
+ if remote_heads.nil? || remote_heads.empty?
44
+ Datadog.logger.debug { "Branch '#{remote_name}/#{short_branch_name}' doesn't exist in remote" }
45
+ return
46
+ end
47
+
48
+ Datadog.logger.debug { "Branch '#{remote_name}/#{short_branch_name}' exists in remote, fetching" }
49
+ CLI.exec_git_command(["fetch", "--depth", "1", remote_name, short_branch_name], timeout: CLI::LONG_TIMEOUT)
50
+ rescue CLI::GitCommandExecutionError => e
51
+ Datadog.logger.debug { "Branch '#{remote_name}/#{short_branch_name}' couldn't be fetched from remote: #{e}" }
52
+ end
53
+ end
54
+
55
+ def remove_remote_prefix(branch_name, remote_name)
56
+ branch_name&.sub(/^#{Regexp.escape(remote_name)}\//, "")
57
+ end
58
+
59
+ def branches_equal?(branch_name, default_branch, remote_name)
60
+ remove_remote_prefix(branch_name, remote_name) == remove_remote_prefix(default_branch, remote_name)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module Git
6
+ module BaseBranchShaDetection
7
+ # Represents metrics for a git branch comparison
8
+ # including how far behind/ahead it is from a source branch
9
+ # and the common base commit SHA
10
+ class BranchMetric
11
+ attr_reader :branch_name, :behind, :ahead, :base_sha
12
+
13
+ def initialize(branch_name:, behind:, ahead:, base_sha:)
14
+ @branch_name = branch_name
15
+ @behind = behind
16
+ @ahead = ahead
17
+ @base_sha = base_sha
18
+ end
19
+
20
+ # Checks if the branch is up to date with the source branch
21
+ def up_to_date?
22
+ @behind == 0 && @ahead == 0
23
+ end
24
+
25
+ # Used for comparison when finding the best branch
26
+ # Lower divergence score is better
27
+ def divergence_score
28
+ @ahead
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end