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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -2
- data/ext/datadog_ci_native/ci.c +10 -0
- data/ext/{datadog_cov → datadog_ci_native}/datadog_cov.c +119 -147
- data/ext/datadog_ci_native/datadog_cov.h +3 -0
- data/ext/datadog_ci_native/datadog_source_code.c +28 -0
- data/ext/datadog_ci_native/datadog_source_code.h +3 -0
- data/ext/{datadog_cov → datadog_ci_native}/extconf.rb +1 -1
- data/lib/datadog/ci/codeowners/rule.rb +5 -0
- data/lib/datadog/ci/configuration/components.rb +17 -5
- data/lib/datadog/ci/configuration/settings.rb +6 -0
- data/lib/datadog/ci/contrib/knapsack/patcher.rb +1 -3
- data/lib/datadog/ci/contrib/knapsack/runner.rb +2 -0
- data/lib/datadog/ci/contrib/minitest/runner.rb +1 -0
- data/lib/datadog/ci/contrib/minitest/test.rb +24 -9
- data/lib/datadog/ci/contrib/parallel_tests/patcher.rb +1 -3
- data/lib/datadog/ci/contrib/patcher.rb +4 -0
- data/lib/datadog/ci/contrib/rspec/example.rb +14 -7
- data/lib/datadog/ci/contrib/rspec/helpers.rb +1 -3
- data/lib/datadog/ci/ext/environment/extractor.rb +4 -6
- data/lib/datadog/ci/ext/environment/providers/appveyor.rb +5 -0
- data/lib/datadog/ci/ext/environment/providers/base.rb +7 -2
- data/lib/datadog/ci/ext/environment/providers/bitbucket.rb +6 -0
- data/lib/datadog/ci/ext/environment/providers/bitrise.rb +7 -1
- data/lib/datadog/ci/ext/environment/providers/buddy.rb +5 -0
- data/lib/datadog/ci/ext/environment/providers/github_actions.rb +37 -18
- data/lib/datadog/ci/ext/environment/providers/gitlab.rb +13 -1
- data/lib/datadog/ci/ext/environment/providers/user_defined_tags.rb +12 -0
- data/lib/datadog/ci/ext/git.rb +3 -0
- data/lib/datadog/ci/ext/settings.rb +1 -0
- data/lib/datadog/ci/ext/telemetry.rb +3 -0
- data/lib/datadog/ci/ext/test.rb +5 -1
- data/lib/datadog/ci/ext/transport.rb +1 -0
- data/lib/datadog/ci/git/base_branch_sha_detection/base.rb +66 -0
- data/lib/datadog/ci/git/base_branch_sha_detection/branch_metric.rb +34 -0
- data/lib/datadog/ci/git/base_branch_sha_detection/guesser.rb +137 -0
- data/lib/datadog/ci/git/base_branch_sha_detection/merge_base_extractor.rb +29 -0
- data/lib/datadog/ci/git/base_branch_sha_detector.rb +63 -0
- data/lib/datadog/ci/git/cli.rb +56 -0
- data/lib/datadog/ci/git/local_repository.rb +131 -118
- data/lib/datadog/ci/git/telemetry.rb +14 -0
- data/lib/datadog/ci/git/tree_uploader.rb +10 -3
- data/lib/datadog/ci/impacted_tests_detection/component.rb +81 -0
- data/lib/datadog/ci/remote/component.rb +6 -1
- data/lib/datadog/ci/remote/library_settings.rb +8 -0
- data/lib/datadog/ci/span.rb +7 -0
- data/lib/datadog/ci/test.rb +6 -0
- data/lib/datadog/ci/test_management/tests_properties.rb +2 -1
- data/lib/datadog/ci/test_optimisation/component.rb +10 -6
- data/lib/datadog/ci/test_optimisation/coverage/ddcov.rb +1 -1
- data/lib/datadog/ci/test_retries/component.rb +8 -17
- data/lib/datadog/ci/test_retries/driver/{retry_new.rb → retry_flake_detection.rb} +1 -1
- data/lib/datadog/ci/test_retries/strategy/{retry_new.rb → retry_flake_detection.rb} +4 -4
- data/lib/datadog/ci/test_visibility/component.rb +6 -0
- data/lib/datadog/ci/test_visibility/telemetry.rb +3 -0
- data/lib/datadog/ci/utils/command.rb +116 -0
- data/lib/datadog/ci/utils/source_code.rb +31 -0
- data/lib/datadog/ci/version.rb +1 -1
- metadata +21 -8
@@ -2,8 +2,12 @@
|
|
2
2
|
|
3
3
|
require "open3"
|
4
4
|
require "pathname"
|
5
|
+
require "set"
|
5
6
|
|
6
7
|
require_relative "../ext/telemetry"
|
8
|
+
require_relative "../utils/command"
|
9
|
+
require_relative "base_branch_sha_detector"
|
10
|
+
require_relative "cli"
|
7
11
|
require_relative "telemetry"
|
8
12
|
require_relative "user"
|
9
13
|
|
@@ -11,19 +15,6 @@ module Datadog
|
|
11
15
|
module CI
|
12
16
|
module Git
|
13
17
|
module LocalRepository
|
14
|
-
class GitCommandExecutionError < StandardError
|
15
|
-
attr_reader :output, :command, :status
|
16
|
-
def initialize(message, output:, command:, status:)
|
17
|
-
super(message)
|
18
|
-
|
19
|
-
@output = output
|
20
|
-
@command = command
|
21
|
-
@status = status
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
COMMAND_RETRY_COUNT = 3
|
26
|
-
|
27
18
|
def self.root
|
28
19
|
return @root if defined?(@root)
|
29
20
|
|
@@ -31,7 +22,7 @@ module Datadog
|
|
31
22
|
end
|
32
23
|
|
33
24
|
# ATTENTION: this function is running in a hot path
|
34
|
-
# and
|
25
|
+
# and must be optimized for performance
|
35
26
|
def self.relative_to_root(path)
|
36
27
|
return "" if path.nil?
|
37
28
|
|
@@ -63,7 +54,7 @@ module Datadog
|
|
63
54
|
res = pathname.relative_path_from(root_path).to_s
|
64
55
|
|
65
56
|
unless defined?(@prefix_to_root)
|
66
|
-
@prefix_to_root = res
|
57
|
+
@prefix_to_root = res.gsub(path, "") if res.end_with?(path)
|
67
58
|
end
|
68
59
|
end
|
69
60
|
|
@@ -90,29 +81,30 @@ module Datadog
|
|
90
81
|
|
91
82
|
def self.git_repository_url
|
92
83
|
Telemetry.git_command(Ext::Telemetry::Command::GET_REPOSITORY)
|
84
|
+
# @type var res: String?
|
93
85
|
res = nil
|
94
86
|
|
95
87
|
duration_ms = Core::Utils::Time.measure(:float_millisecond) do
|
96
|
-
res = exec_git_command("
|
88
|
+
res = CLI.exec_git_command(["ls-remote", "--get-url"])
|
97
89
|
end
|
98
90
|
|
99
91
|
Telemetry.git_command_ms(Ext::Telemetry::Command::GET_REPOSITORY, duration_ms)
|
100
92
|
res
|
101
93
|
rescue => e
|
102
94
|
log_failure(e, "git repository url")
|
103
|
-
|
95
|
+
Telemetry.track_error(e, Ext::Telemetry::Command::GET_REPOSITORY)
|
104
96
|
nil
|
105
97
|
end
|
106
98
|
|
107
99
|
def self.git_root
|
108
|
-
exec_git_command("
|
100
|
+
CLI.exec_git_command(["rev-parse", "--show-toplevel"])
|
109
101
|
rescue => e
|
110
102
|
log_failure(e, "git root path")
|
111
103
|
nil
|
112
104
|
end
|
113
105
|
|
114
106
|
def self.git_commit_sha
|
115
|
-
exec_git_command("
|
107
|
+
CLI.exec_git_command(["rev-parse", "HEAD"])
|
116
108
|
rescue => e
|
117
109
|
log_failure(e, "git commit sha")
|
118
110
|
nil
|
@@ -120,29 +112,30 @@ module Datadog
|
|
120
112
|
|
121
113
|
def self.git_branch
|
122
114
|
Telemetry.git_command(Ext::Telemetry::Command::GET_BRANCH)
|
115
|
+
# @type var res: String?
|
123
116
|
res = nil
|
124
117
|
|
125
118
|
duration_ms = Core::Utils::Time.measure(:float_millisecond) do
|
126
|
-
res = exec_git_command("
|
119
|
+
res = CLI.exec_git_command(["rev-parse", "--abbrev-ref", "HEAD"])
|
127
120
|
end
|
128
121
|
|
129
122
|
Telemetry.git_command_ms(Ext::Telemetry::Command::GET_BRANCH, duration_ms)
|
130
123
|
res
|
131
124
|
rescue => e
|
132
125
|
log_failure(e, "git branch")
|
133
|
-
|
126
|
+
Telemetry.track_error(e, Ext::Telemetry::Command::GET_BRANCH)
|
134
127
|
nil
|
135
128
|
end
|
136
129
|
|
137
130
|
def self.git_tag
|
138
|
-
exec_git_command("
|
131
|
+
CLI.exec_git_command(["tag", "--points-at", "HEAD"])
|
139
132
|
rescue => e
|
140
133
|
log_failure(e, "git tag")
|
141
134
|
nil
|
142
135
|
end
|
143
136
|
|
144
137
|
def self.git_commit_message
|
145
|
-
exec_git_command("
|
138
|
+
CLI.exec_git_command(["log", "-n", "1", "--format=%B"])
|
146
139
|
rescue => e
|
147
140
|
log_failure(e, "git commit message")
|
148
141
|
nil
|
@@ -150,7 +143,7 @@ module Datadog
|
|
150
143
|
|
151
144
|
def self.git_commit_users
|
152
145
|
# Get committer and author information in one command.
|
153
|
-
output = exec_git_command("
|
146
|
+
output = CLI.exec_git_command(["show", "-s", "--format=%an\t%ae\t%at\t%cn\t%ce\t%ct"])
|
154
147
|
unless output
|
155
148
|
Datadog.logger.debug(
|
156
149
|
"Unable to read git commit users: git command output is nil"
|
@@ -177,39 +170,42 @@ module Datadog
|
|
177
170
|
def self.git_commits
|
178
171
|
Telemetry.git_command(Ext::Telemetry::Command::GET_LOCAL_COMMITS)
|
179
172
|
|
173
|
+
# @type var output: String?
|
180
174
|
output = nil
|
175
|
+
|
181
176
|
duration_ms = Core::Utils::Time.measure(:float_millisecond) do
|
182
|
-
output = exec_git_command("
|
177
|
+
output = CLI.exec_git_command(["log", "--format=%H", "-n", "1000", "--since=\"1 month ago\""], timeout: CLI::LONG_TIMEOUT)
|
183
178
|
end
|
184
179
|
|
185
180
|
Telemetry.git_command_ms(Ext::Telemetry::Command::GET_LOCAL_COMMITS, duration_ms)
|
186
181
|
|
187
182
|
return [] if output.nil?
|
188
183
|
|
189
|
-
# @type var output: String
|
190
184
|
output.split("\n")
|
191
185
|
rescue => e
|
192
186
|
log_failure(e, "git commits")
|
193
|
-
|
187
|
+
Telemetry.track_error(e, Ext::Telemetry::Command::GET_LOCAL_COMMITS)
|
194
188
|
[]
|
195
189
|
end
|
196
190
|
|
197
191
|
def self.git_commits_rev_list(included_commits:, excluded_commits:)
|
198
192
|
Telemetry.git_command(Ext::Telemetry::Command::GET_OBJECTS)
|
199
|
-
|
200
|
-
|
193
|
+
included_commits_list = filter_invalid_commits(included_commits)
|
194
|
+
excluded_commits_list = filter_invalid_commits(excluded_commits).map { |sha| "^#{sha}" }
|
201
195
|
|
196
|
+
# @type var res: String?
|
202
197
|
res = nil
|
203
198
|
|
204
199
|
duration_ms = Core::Utils::Time.measure(:float_millisecond) do
|
205
|
-
|
206
|
-
"
|
207
|
-
"--objects
|
208
|
-
"--no-object-names
|
209
|
-
"--filter=blob:none
|
210
|
-
"--since=\"1 month ago\"
|
211
|
-
|
212
|
-
|
200
|
+
cmd = [
|
201
|
+
"rev-list",
|
202
|
+
"--objects",
|
203
|
+
"--no-object-names",
|
204
|
+
"--filter=blob:none",
|
205
|
+
"--since=\"1 month ago\""
|
206
|
+
] + excluded_commits_list + included_commits_list
|
207
|
+
|
208
|
+
res = CLI.exec_git_command(cmd, timeout: CLI::LONG_TIMEOUT)
|
213
209
|
end
|
214
210
|
|
215
211
|
Telemetry.git_command_ms(Ext::Telemetry::Command::GET_OBJECTS, duration_ms)
|
@@ -217,7 +213,7 @@ module Datadog
|
|
217
213
|
res
|
218
214
|
rescue => e
|
219
215
|
log_failure(e, "git commits rev list")
|
220
|
-
|
216
|
+
Telemetry.track_error(e, Ext::Telemetry::Command::GET_OBJECTS)
|
221
217
|
nil
|
222
218
|
end
|
223
219
|
|
@@ -232,9 +228,10 @@ module Datadog
|
|
232
228
|
Telemetry.git_command(Ext::Telemetry::Command::PACK_OBJECTS)
|
233
229
|
|
234
230
|
duration_ms = Core::Utils::Time.measure(:float_millisecond) do
|
235
|
-
exec_git_command(
|
236
|
-
"
|
237
|
-
stdin: commit_tree
|
231
|
+
CLI.exec_git_command(
|
232
|
+
["pack-objects", "--compression=9", "--max-pack-size=3m", "#{path}/#{basename}"],
|
233
|
+
stdin: commit_tree,
|
234
|
+
timeout: CLI::LONG_TIMEOUT
|
238
235
|
)
|
239
236
|
end
|
240
237
|
Telemetry.git_command_ms(Ext::Telemetry::Command::PACK_OBJECTS, duration_ms)
|
@@ -242,7 +239,7 @@ module Datadog
|
|
242
239
|
basename
|
243
240
|
rescue => e
|
244
241
|
log_failure(e, "git generate packfiles")
|
245
|
-
|
242
|
+
Telemetry.track_error(e, Ext::Telemetry::Command::PACK_OBJECTS)
|
246
243
|
nil
|
247
244
|
end
|
248
245
|
|
@@ -251,34 +248,31 @@ module Datadog
|
|
251
248
|
res = false
|
252
249
|
|
253
250
|
duration_ms = Core::Utils::Time.measure(:float_millisecond) do
|
254
|
-
res = exec_git_command("
|
251
|
+
res = CLI.exec_git_command(["rev-parse", "--is-shallow-repository"]) == "true"
|
255
252
|
end
|
256
253
|
Telemetry.git_command_ms(Ext::Telemetry::Command::CHECK_SHALLOW, duration_ms)
|
257
254
|
|
258
255
|
res
|
259
256
|
rescue => e
|
260
257
|
log_failure(e, "git shallow clone")
|
261
|
-
|
258
|
+
Telemetry.track_error(e, Ext::Telemetry::Command::CHECK_SHALLOW)
|
262
259
|
false
|
263
260
|
end
|
264
261
|
|
265
262
|
def self.git_unshallow
|
266
263
|
Telemetry.git_command(Ext::Telemetry::Command::UNSHALLOW)
|
264
|
+
# @type var res: String?
|
267
265
|
res = nil
|
268
266
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
unshallow_remotes
|
278
|
-
"$(git rev-parse HEAD)",
|
279
|
-
"$(git rev-parse --abbrev-ref --symbolic-full-name @{upstream})",
|
280
|
-
nil
|
281
|
-
]
|
267
|
+
default_remote = CLI.exec_git_command(["config", "--default", "origin", "--get", "clone.defaultRemoteName"])&.strip
|
268
|
+
head_commit = git_commit_sha
|
269
|
+
upstream_branch = get_upstream_branch
|
270
|
+
|
271
|
+
# Build array of remotes to try, filtering out nil values
|
272
|
+
unshallow_remotes = []
|
273
|
+
unshallow_remotes << head_commit if head_commit
|
274
|
+
unshallow_remotes << upstream_branch if upstream_branch
|
275
|
+
unshallow_remotes << nil # Ensure the loop runs at least once, even if no valid remotes are available. This acts as a fallback mechanism.
|
282
276
|
|
283
277
|
duration_ms = Core::Utils::Time.measure(:float_millisecond) do
|
284
278
|
unshallow_remotes.each do |remote|
|
@@ -286,17 +280,27 @@ module Datadog
|
|
286
280
|
|
287
281
|
res =
|
288
282
|
begin
|
289
|
-
|
290
|
-
|
291
|
-
|
283
|
+
# @type var cmd: Array[String]
|
284
|
+
cmd = [
|
285
|
+
"fetch",
|
286
|
+
"--shallow-since=\"1 month ago\"",
|
287
|
+
"--update-shallow",
|
288
|
+
"--filter=blob:none",
|
289
|
+
"--recurse-submodules=no",
|
290
|
+
default_remote
|
291
|
+
]
|
292
|
+
cmd << remote if remote
|
293
|
+
|
294
|
+
CLI.exec_git_command(cmd, timeout: CLI::UNSHALLOW_TIMEOUT)
|
292
295
|
rescue => e
|
293
296
|
log_failure(e, "git unshallow")
|
294
|
-
|
297
|
+
Telemetry.track_error(e, Ext::Telemetry::Command::UNSHALLOW)
|
295
298
|
unshallowing_errored = true
|
296
299
|
nil
|
297
300
|
end
|
298
301
|
|
299
|
-
break
|
302
|
+
# If the command succeeded, break and return the result
|
303
|
+
break [] if res && !unshallowing_errored
|
300
304
|
end
|
301
305
|
end
|
302
306
|
|
@@ -304,71 +308,80 @@ module Datadog
|
|
304
308
|
res
|
305
309
|
end
|
306
310
|
|
307
|
-
#
|
308
|
-
#
|
309
|
-
|
310
|
-
|
311
|
+
# Returns a Set of normalized file paths changed since the given base_commit.
|
312
|
+
# If base_commit is nil, returns nil. On error, returns nil.
|
313
|
+
def self.get_changed_files_from_diff(base_commit)
|
314
|
+
return nil if base_commit.nil?
|
311
315
|
|
312
|
-
|
313
|
-
commits.filter { |commit| Utils::Git.valid_commit_sha?(commit) }
|
314
|
-
end
|
316
|
+
Datadog.logger.debug { "calculating git diff from base_commit: #{base_commit}" }
|
315
317
|
|
316
|
-
|
317
|
-
# Shell injection is alleviated by making sure that no outside modules call this method.
|
318
|
-
# It is called only internally with static parameters.
|
319
|
-
# no-dd-sa:ruby-security/shell-injection
|
320
|
-
out, status = Open3.capture2e(cmd, stdin_data: stdin)
|
321
|
-
|
322
|
-
if status.nil?
|
323
|
-
retry_count = COMMAND_RETRY_COUNT
|
324
|
-
Datadog.logger.debug { "Opening pipe failed, starting retries..." }
|
325
|
-
while status.nil? && retry_count.positive?
|
326
|
-
# no-dd-sa:ruby-security/shell-injection
|
327
|
-
out, status = Open3.capture2e(cmd, stdin_data: stdin)
|
328
|
-
Datadog.logger.debug { "After retry status is [#{status}]" }
|
329
|
-
retry_count -= 1
|
330
|
-
end
|
331
|
-
end
|
318
|
+
Telemetry.git_command(Ext::Telemetry::Command::DIFF)
|
332
319
|
|
333
|
-
|
334
|
-
|
335
|
-
"Failed to run git command [#{cmd}] with input [#{stdin}] and output [#{out}]",
|
336
|
-
output: out,
|
337
|
-
command: cmd,
|
338
|
-
status: status
|
339
|
-
)
|
340
|
-
end
|
320
|
+
begin
|
321
|
+
# 1. Run the git diff command
|
341
322
|
|
342
|
-
#
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
if Encoding.default_external == Encoding::US_ASCII
|
347
|
-
out = out.force_encoding(Encoding::UTF_8)
|
323
|
+
# @type var output: String?
|
324
|
+
output = nil
|
325
|
+
duration_ms = Core::Utils::Time.measure(:float_millisecond) do
|
326
|
+
output = CLI.exec_git_command(["diff", "-U0", "--word-diff=porcelain", base_commit, "HEAD"], timeout: CLI::LONG_TIMEOUT)
|
348
327
|
end
|
349
|
-
|
328
|
+
Telemetry.git_command_ms(Ext::Telemetry::Command::DIFF, duration_ms)
|
329
|
+
|
330
|
+
Datadog.logger.debug { "git diff output: #{output}" }
|
331
|
+
|
332
|
+
return nil if output.nil?
|
333
|
+
|
334
|
+
# 2. Parse the output to extract which files changed
|
335
|
+
changed_files = Set.new
|
336
|
+
output.each_line do |line|
|
337
|
+
# Match lines like: diff --git a/foo/bar.rb b/foo/bar.rb
|
338
|
+
# This captures git changes on file level
|
339
|
+
match = /^diff --git a\/(?<file>.+?) b\//.match(line)
|
340
|
+
if match && match[:file]
|
341
|
+
changed_file = match[:file]
|
342
|
+
# Normalize to repo root
|
343
|
+
normalized_changed_file = relative_to_root(changed_file)
|
344
|
+
changed_files << normalized_changed_file unless normalized_changed_file.nil? || normalized_changed_file.empty?
|
345
|
+
|
346
|
+
Datadog.logger.debug { "matched changed_file: #{changed_file} from line: #{line}" }
|
347
|
+
Datadog.logger.debug { "normalized_changed_file: #{normalized_changed_file}" }
|
348
|
+
end
|
349
|
+
end
|
350
|
+
changed_files
|
351
|
+
rescue => e
|
352
|
+
Telemetry.track_error(e, Ext::Telemetry::Command::DIFF)
|
353
|
+
log_failure(e, "get changed files from diff")
|
354
|
+
nil
|
355
|
+
end
|
356
|
+
end
|
350
357
|
|
351
|
-
|
358
|
+
# On best effort basis determines the git sha of the most likely
|
359
|
+
# base branch for the current PR.
|
360
|
+
def self.base_commit_sha(base_branch: nil)
|
361
|
+
Telemetry.git_command(Ext::Telemetry::Command::BASE_COMMIT_SHA)
|
352
362
|
|
353
|
-
|
354
|
-
|
363
|
+
BaseBranchShaDetector.base_branch_sha(base_branch)
|
364
|
+
rescue => e
|
365
|
+
Telemetry.track_error(e, Ext::Telemetry::Command::BASE_COMMIT_SHA)
|
366
|
+
log_failure(e, "git base ref")
|
367
|
+
nil
|
368
|
+
end
|
355
369
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
370
|
+
def self.get_upstream_branch
|
371
|
+
CLI.exec_git_command(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"])
|
372
|
+
rescue => e
|
373
|
+
Datadog.logger.debug { "Error getting upstream: #{e}" }
|
374
|
+
nil
|
375
|
+
end
|
361
376
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
end
|
371
|
-
end
|
377
|
+
def self.filter_invalid_commits(commits)
|
378
|
+
commits.filter { |commit| Utils::Git.valid_commit_sha?(commit) }
|
379
|
+
end
|
380
|
+
|
381
|
+
def self.log_failure(e, action)
|
382
|
+
Datadog.logger.debug(
|
383
|
+
"Unable to perform #{action}: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}"
|
384
|
+
)
|
372
385
|
end
|
373
386
|
end
|
374
387
|
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "../utils/telemetry"
|
4
|
+
require_relative "cli"
|
5
|
+
|
3
6
|
module Datadog
|
4
7
|
module CI
|
5
8
|
module Git
|
@@ -21,6 +24,17 @@ module Datadog
|
|
21
24
|
Utils::Telemetry.distribution(Ext::Telemetry::METRIC_GIT_COMMAND_MS, duration_ms, tags_for_command(command))
|
22
25
|
end
|
23
26
|
|
27
|
+
def self.track_error(e, command)
|
28
|
+
case e
|
29
|
+
when Errno::ENOENT
|
30
|
+
git_command_errors(command, executable_missing: true)
|
31
|
+
when CLI::GitCommandExecutionError
|
32
|
+
git_command_errors(command, exit_code: e.status&.to_i)
|
33
|
+
else
|
34
|
+
git_command_errors(command, exit_code: -9000)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
24
38
|
def self.tags_for_command(command)
|
25
39
|
{Ext::Telemetry::TAG_COMMAND => command}
|
26
40
|
end
|
@@ -15,10 +15,11 @@ module Datadog
|
|
15
15
|
module CI
|
16
16
|
module Git
|
17
17
|
class TreeUploader
|
18
|
-
attr_reader :api
|
18
|
+
attr_reader :api, :force_unshallow
|
19
19
|
|
20
|
-
def initialize(api:)
|
20
|
+
def initialize(api:, force_unshallow: false)
|
21
21
|
@api = api
|
22
|
+
@force_unshallow = force_unshallow
|
22
23
|
end
|
23
24
|
|
24
25
|
def call(repository_url)
|
@@ -45,7 +46,13 @@ module Datadog
|
|
45
46
|
# ask the backend for the list of commits it already has
|
46
47
|
known_commits, new_commits = fetch_known_commits_and_split(repository_url, latest_commits)
|
47
48
|
# if all commits are present in the backend, we don't need to upload anything
|
48
|
-
|
49
|
+
|
50
|
+
# We optimize unshallowing process by checking the latest available commits with backend:
|
51
|
+
# if they are already known to backend, then we don't have to unshallow.
|
52
|
+
#
|
53
|
+
# Sometimes we need to unshallow anyway: for impacted tests detection feature for example we need
|
54
|
+
# to calculate git diffs locally. In this case we skip the optimization and always unshallow.
|
55
|
+
if new_commits.empty? && !@force_unshallow
|
49
56
|
Datadog.logger.debug("No new commits to upload")
|
50
57
|
return
|
51
58
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
require_relative "../ext/test"
|
6
|
+
require_relative "../git/local_repository"
|
7
|
+
|
8
|
+
module Datadog
|
9
|
+
module CI
|
10
|
+
module ImpactedTestsDetection
|
11
|
+
class Component
|
12
|
+
def initialize(enabled:)
|
13
|
+
@enabled = enabled
|
14
|
+
@changed_files = Set.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def configure(library_settings, test_session)
|
18
|
+
@enabled &&= library_settings.impacted_tests_enabled?
|
19
|
+
|
20
|
+
return unless @enabled
|
21
|
+
|
22
|
+
# we must unshallow the repository before trying to find base_commit_sha or executing `git diff` command
|
23
|
+
git_tree_upload_worker.wait_until_done
|
24
|
+
|
25
|
+
base_commit_sha = test_session.base_commit_sha || Git::LocalRepository.base_commit_sha
|
26
|
+
if base_commit_sha.nil?
|
27
|
+
Datadog.logger.debug { "Impacted tests detection disabled: base commit not found" }
|
28
|
+
@enabled = false
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
changed_files = Git::LocalRepository.get_changed_files_from_diff(base_commit_sha)
|
33
|
+
if changed_files.nil?
|
34
|
+
Datadog.logger.debug { "Impacted tests detection disabled: could not get changed files" }
|
35
|
+
@enabled = false
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
Datadog.logger.debug do
|
40
|
+
"Impacted tests detection: found #{changed_files.size} changed files"
|
41
|
+
end
|
42
|
+
Datadog.logger.debug do
|
43
|
+
"Impacted tests detection: changed files: #{changed_files.inspect}"
|
44
|
+
end
|
45
|
+
|
46
|
+
@changed_files = changed_files
|
47
|
+
@enabled = true
|
48
|
+
end
|
49
|
+
|
50
|
+
def enabled?
|
51
|
+
@enabled
|
52
|
+
end
|
53
|
+
|
54
|
+
def modified?(test_span)
|
55
|
+
return false unless enabled?
|
56
|
+
|
57
|
+
source_file = test_span.source_file
|
58
|
+
return false if source_file.nil?
|
59
|
+
|
60
|
+
@changed_files.include?(source_file)
|
61
|
+
end
|
62
|
+
|
63
|
+
def tag_modified_test(test_span)
|
64
|
+
return unless modified?(test_span)
|
65
|
+
|
66
|
+
Datadog.logger.debug do
|
67
|
+
"Impacted tests detection: test #{test_span.name} with source file #{test_span.source_file} is modified"
|
68
|
+
end
|
69
|
+
|
70
|
+
test_span.set_tag(Ext::Test::TAG_TEST_IS_MODIFIED, "true")
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def git_tree_upload_worker
|
76
|
+
Datadog.send(:components).git_tree_upload_worker
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -46,7 +46,8 @@ module Datadog
|
|
46
46
|
Worker.new { test_optimisation.configure(@library_configuration, test_session) },
|
47
47
|
Worker.new { test_retries.configure(@library_configuration, test_session) },
|
48
48
|
Worker.new { test_visibility.configure(@library_configuration, test_session) },
|
49
|
-
Worker.new { test_management.configure(@library_configuration, test_session) }
|
49
|
+
Worker.new { test_management.configure(@library_configuration, test_session) },
|
50
|
+
Worker.new { impacted_tests_detection.configure(@library_configuration, test_session) }
|
50
51
|
]
|
51
52
|
|
52
53
|
# launch configuration workers
|
@@ -89,6 +90,10 @@ module Datadog
|
|
89
90
|
Datadog.send(:components).test_retries
|
90
91
|
end
|
91
92
|
|
93
|
+
def impacted_tests_detection
|
94
|
+
Datadog.send(:components).impacted_tests_detection
|
95
|
+
end
|
96
|
+
|
92
97
|
def git_tree_upload_worker
|
93
98
|
Datadog.send(:components).git_tree_upload_worker
|
94
99
|
end
|
@@ -106,6 +106,14 @@ module Datadog
|
|
106
106
|
)
|
107
107
|
end
|
108
108
|
|
109
|
+
def impacted_tests_enabled?
|
110
|
+
return @impacted_tests_enabled if defined?(@impacted_tests_enabled)
|
111
|
+
|
112
|
+
@impacted_tests_enabled = Utils::Parsing.convert_to_bool(
|
113
|
+
payload.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_IMPACTED_TESTS_ENABLED_KEY, false)
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
109
117
|
def slow_test_retries
|
110
118
|
return @slow_test_retries if defined?(@slow_test_retries)
|
111
119
|
|
data/lib/datadog/ci/span.rb
CHANGED
@@ -6,6 +6,7 @@ require "datadog/core/environment/platform"
|
|
6
6
|
|
7
7
|
require_relative "ext/test"
|
8
8
|
require_relative "utils/test_run"
|
9
|
+
require_relative "ext/git"
|
9
10
|
|
10
11
|
module Datadog
|
11
12
|
module CI
|
@@ -164,6 +165,12 @@ module Datadog
|
|
164
165
|
tracer_span.get_tag(Ext::Git::TAG_BRANCH)
|
165
166
|
end
|
166
167
|
|
168
|
+
# Returns the base commit SHA for the pull request, if available.
|
169
|
+
# @return [String, nil] the base commit SHA or nil if not set.
|
170
|
+
def base_commit_sha
|
171
|
+
tracer_span.get_tag(Ext::Git::TAG_PULL_REQUEST_BASE_BRANCH_SHA)
|
172
|
+
end
|
173
|
+
|
167
174
|
# Returns the OS architecture extracted from the environment.
|
168
175
|
# @return [String] OS arch.
|
169
176
|
def os_architecture
|
data/lib/datadog/ci/test.rb
CHANGED
@@ -102,6 +102,12 @@ module Datadog
|
|
102
102
|
get_tag(Ext::Test::TAG_IS_ATTEMPT_TO_FIX) == "true"
|
103
103
|
end
|
104
104
|
|
105
|
+
# Returns "true" if this test is marked as modified (e.g., impacted by code changes).
|
106
|
+
# @return [Boolean] true if this test is modified, false otherwise.
|
107
|
+
def modified?
|
108
|
+
get_tag(Ext::Test::TAG_TEST_IS_MODIFIED) == "true"
|
109
|
+
end
|
110
|
+
|
105
111
|
# Marks this test as unskippable by the Test Impact Analysis.
|
106
112
|
# This must be done before the test execution starts.
|
107
113
|
#
|
@@ -118,7 +118,8 @@ module Datadog
|
|
118
118
|
"type" => Ext::Transport::DD_API_TEST_MANAGEMENT_TESTS_TYPE,
|
119
119
|
"attributes" => {
|
120
120
|
"repository_url" => test_session.git_repository_url,
|
121
|
-
"commit_message" => test_session.git_commit_message
|
121
|
+
"commit_message" => test_session.git_commit_message,
|
122
|
+
"sha" => test_session.git_commit_sha
|
122
123
|
}
|
123
124
|
}
|
124
125
|
}.to_json
|