datadog-ci 1.17.0 → 1.19.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 +28 -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/contrib/minitest/test.rb +17 -7
- data/lib/datadog/ci/contrib/rspec/example.rb +14 -7
- data/lib/datadog/ci/ext/environment/providers/appveyor.rb +1 -1
- data/lib/datadog/ci/ext/environment/providers/buildkite.rb +4 -0
- data/lib/datadog/ci/ext/environment/providers/gitlab.rb +0 -4
- data/lib/datadog/ci/ext/telemetry.rb +1 -2
- data/lib/datadog/ci/ext/test.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/changed_lines.rb +109 -0
- data/lib/datadog/ci/git/cli.rb +56 -0
- data/lib/datadog/ci/git/diff.rb +90 -0
- data/lib/datadog/ci/git/local_repository.rb +94 -321
- data/lib/datadog/ci/git/telemetry.rb +14 -0
- data/lib/datadog/ci/impacted_tests_detection/component.rb +15 -11
- data/lib/datadog/ci/test.rb +16 -0
- 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_visibility/telemetry.rb +3 -0
- data/lib/datadog/ci/utils/command.rb +117 -0
- data/lib/datadog/ci/utils/source_code.rb +31 -0
- data/lib/datadog/ci/version.rb +1 -1
- metadata +18 -5
- data/lib/datadog/ci/impacted_tests_detection/telemetry.rb +0 -16
@@ -1,10 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "set"
|
4
|
-
|
5
3
|
require_relative "../ext/test"
|
6
4
|
require_relative "../git/local_repository"
|
7
|
-
require_relative "telemetry"
|
8
5
|
|
9
6
|
module Datadog
|
10
7
|
module CI
|
@@ -12,7 +9,7 @@ module Datadog
|
|
12
9
|
class Component
|
13
10
|
def initialize(enabled:)
|
14
11
|
@enabled = enabled
|
15
|
-
@
|
12
|
+
@git_diff = Git::Diff.new
|
16
13
|
end
|
17
14
|
|
18
15
|
def configure(library_settings, test_session)
|
@@ -30,21 +27,21 @@ module Datadog
|
|
30
27
|
return
|
31
28
|
end
|
32
29
|
|
33
|
-
|
34
|
-
if
|
30
|
+
git_diff = Git::LocalRepository.get_changes_since(base_commit_sha)
|
31
|
+
if git_diff.empty?
|
35
32
|
Datadog.logger.debug { "Impacted tests detection disabled: could not get changed files" }
|
36
33
|
@enabled = false
|
37
34
|
return
|
38
35
|
end
|
39
36
|
|
40
37
|
Datadog.logger.debug do
|
41
|
-
"Impacted tests detection: found #{
|
38
|
+
"Impacted tests detection: found #{git_diff.size} changed files"
|
42
39
|
end
|
43
40
|
Datadog.logger.debug do
|
44
|
-
"Impacted tests detection: changed files: #{
|
41
|
+
"Impacted tests detection: changed files: #{git_diff.inspect}"
|
45
42
|
end
|
46
43
|
|
47
|
-
@
|
44
|
+
@git_diff = git_diff
|
48
45
|
@enabled = true
|
49
46
|
end
|
50
47
|
|
@@ -58,7 +55,15 @@ module Datadog
|
|
58
55
|
source_file = test_span.source_file
|
59
56
|
return false if source_file.nil?
|
60
57
|
|
61
|
-
|
58
|
+
# convert to relative path without leading slash
|
59
|
+
# @type var source_file: String
|
60
|
+
source_file = source_file[1..] if source_file.start_with?("/")
|
61
|
+
|
62
|
+
result = @git_diff.lines_changed?(source_file, start_line: test_span.start_line, end_line: test_span.end_line)
|
63
|
+
Datadog.logger.debug do
|
64
|
+
"Impacted tests detection: test #{test_span.name} with source file #{source_file} is modified: #{result}"
|
65
|
+
end
|
66
|
+
result
|
62
67
|
end
|
63
68
|
|
64
69
|
def tag_modified_test(test_span)
|
@@ -69,7 +74,6 @@ module Datadog
|
|
69
74
|
end
|
70
75
|
|
71
76
|
test_span.set_tag(Ext::Test::TAG_TEST_IS_MODIFIED, "true")
|
72
|
-
Telemetry.impacted_test_detected
|
73
77
|
end
|
74
78
|
|
75
79
|
private
|
data/lib/datadog/ci/test.rb
CHANGED
@@ -66,6 +66,22 @@ module Datadog
|
|
66
66
|
get_tag(Ext::Test::TAG_TEST_SESSION_ID)
|
67
67
|
end
|
68
68
|
|
69
|
+
# Returns the starting line number of the test in the source file.
|
70
|
+
# @return [Integer] the starting line number
|
71
|
+
# @return [nil] if the starting line is not available
|
72
|
+
def start_line
|
73
|
+
line = get_tag(Ext::Test::TAG_SOURCE_START)
|
74
|
+
line&.to_i
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the ending line number of the test in the source file.
|
78
|
+
# @return [Integer] the ending line number
|
79
|
+
# @return [nil] if the ending line is not available
|
80
|
+
def end_line
|
81
|
+
line = get_tag(Ext::Test::TAG_SOURCE_END)
|
82
|
+
line&.to_i
|
83
|
+
end
|
84
|
+
|
69
85
|
# Returns "true" if test span represents a retry.
|
70
86
|
# @return [Boolean] true if this test is a retry, false otherwise.
|
71
87
|
def is_retry?
|
@@ -150,7 +150,7 @@ module Datadog
|
|
150
150
|
def skippable?(datadog_test_id)
|
151
151
|
return false if !enabled? || !skipping_tests?
|
152
152
|
|
153
|
-
@skippable_tests.include?(datadog_test_id)
|
153
|
+
@mutex.synchronize { @skippable_tests.include?(datadog_test_id) }
|
154
154
|
end
|
155
155
|
|
156
156
|
def mark_if_skippable(test)
|
@@ -200,8 +200,10 @@ module Datadog
|
|
200
200
|
end
|
201
201
|
|
202
202
|
def restore_state(state)
|
203
|
-
@
|
204
|
-
|
203
|
+
@mutex.synchronize do
|
204
|
+
@correlation_id = state[:correlation_id]
|
205
|
+
@skippable_tests = state[:skippable_tests]
|
206
|
+
end
|
205
207
|
end
|
206
208
|
|
207
209
|
def storage_key
|
@@ -225,7 +227,7 @@ module Datadog
|
|
225
227
|
end
|
226
228
|
|
227
229
|
def load_datadog_cov!
|
228
|
-
require "
|
230
|
+
require "datadog_ci_native.#{RUBY_VERSION}_#{RUBY_PLATFORM}"
|
229
231
|
|
230
232
|
Datadog.logger.debug("Loaded Datadog code coverage collector, using coverage mode: #{code_coverage_mode}")
|
231
233
|
rescue LoadError => e
|
@@ -253,8 +255,10 @@ module Datadog
|
|
253
255
|
.fetch_skippable_tests(test_session)
|
254
256
|
@skippable_tests_fetch_error = skippable_response.error_message unless skippable_response.ok?
|
255
257
|
|
256
|
-
@
|
257
|
-
|
258
|
+
@mutex.synchronize do
|
259
|
+
@correlation_id = skippable_response.correlation_id
|
260
|
+
@skippable_tests = skippable_response.tests
|
261
|
+
end
|
258
262
|
|
259
263
|
Datadog.logger.debug { "Fetched skippable tests: \n #{@skippable_tests}" }
|
260
264
|
Datadog.logger.debug { "Found #{@skippable_tests.count} skippable tests." }
|
@@ -75,6 +75,9 @@ module Datadog
|
|
75
75
|
tags[Ext::Telemetry::TAG_IS_TEST_DISABLED] = "true" if span.get_tag(Ext::Test::TAG_IS_TEST_DISABLED)
|
76
76
|
tags[Ext::Telemetry::TAG_HAS_FAILED_ALL_RETRIES] = "true" if span.get_tag(Ext::Test::TAG_HAS_FAILED_ALL_RETRIES)
|
77
77
|
|
78
|
+
# impacted tests tags
|
79
|
+
tags[Ext::Telemetry::TAG_IS_MODIFIED] = "true" if span.get_tag(Ext::Test::TAG_TEST_IS_MODIFIED)
|
80
|
+
|
78
81
|
tags
|
79
82
|
end
|
80
83
|
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
require "datadog/core/utils/time"
|
5
|
+
|
6
|
+
module Datadog
|
7
|
+
module CI
|
8
|
+
module Utils
|
9
|
+
# Provides a way to call external commands with timeout
|
10
|
+
module Command
|
11
|
+
DEFAULT_TIMEOUT = 10 # seconds
|
12
|
+
BUFFER_SIZE = 1024
|
13
|
+
|
14
|
+
OPEN_STDIN_RETRY_COUNT = 3
|
15
|
+
|
16
|
+
# Executes a command with optional timeout and stdin data
|
17
|
+
#
|
18
|
+
# @param command [Array<String>] Command to execute.
|
19
|
+
# @param stdin_data [String, nil] Data to write to stdin
|
20
|
+
# @param timeout [Integer] Maximum execution time in seconds
|
21
|
+
# @return [Array<String, Process::Status?>] Output and exit status
|
22
|
+
#
|
23
|
+
# @example Safe usage with array (recommended)
|
24
|
+
# Command.exec_command(["git", "log", "-n", "1"])
|
25
|
+
#
|
26
|
+
#
|
27
|
+
def self.exec_command(command, stdin_data: nil, timeout: DEFAULT_TIMEOUT)
|
28
|
+
output = +""
|
29
|
+
exit_value = nil
|
30
|
+
timeout_reached = false
|
31
|
+
|
32
|
+
begin
|
33
|
+
start = Core::Utils::Time.get_time
|
34
|
+
|
35
|
+
_, stderrout, thread = popen_with_stdin(command, stdin_data: stdin_data)
|
36
|
+
pid = thread[:pid]
|
37
|
+
|
38
|
+
# wait for output and read from stdout/stderr
|
39
|
+
while (Core::Utils::Time.get_time - start) < timeout
|
40
|
+
# wait for data to appear in stderrout channel
|
41
|
+
# maximum wait time 100ms
|
42
|
+
Kernel.select([stderrout], [], [], 0.1)
|
43
|
+
|
44
|
+
begin
|
45
|
+
output << stderrout.read_nonblock(1024)
|
46
|
+
rescue IO::WaitReadable
|
47
|
+
rescue EOFError
|
48
|
+
# we're done here, we return from this cycle when we processed the whole output of the command
|
49
|
+
break
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if (Core::Utils::Time.get_time - start) > timeout
|
54
|
+
timeout_reached = true
|
55
|
+
end
|
56
|
+
|
57
|
+
if thread.alive?
|
58
|
+
begin
|
59
|
+
Process.kill("TERM", pid)
|
60
|
+
rescue
|
61
|
+
# Process already terminated
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
thread.join(1)
|
66
|
+
exit_value = thread.value
|
67
|
+
rescue Errno::EPIPE
|
68
|
+
return ["Error writing to stdin", nil]
|
69
|
+
ensure
|
70
|
+
stderrout&.close
|
71
|
+
end
|
72
|
+
|
73
|
+
# we read command's output as binary so now we need to set an appropriate encoding for the result
|
74
|
+
encoding = Encoding.default_external
|
75
|
+
|
76
|
+
# Sometimes Encoding.default_external is somehow set to US-ASCII which breaks
|
77
|
+
# commit messages with UTF-8 characters like emojis
|
78
|
+
# We force output's encoding to be UTF-8 in this case
|
79
|
+
# This is safe to do as UTF-8 is compatible with US-ASCII
|
80
|
+
if Encoding.default_external == Encoding::US_ASCII
|
81
|
+
encoding = Encoding::UTF_8
|
82
|
+
end
|
83
|
+
|
84
|
+
output.force_encoding(encoding)
|
85
|
+
output.strip! # There's always a "\n" at the end of the command output
|
86
|
+
|
87
|
+
if timeout_reached && output.empty?
|
88
|
+
output = "Command timed out after #{timeout} seconds"
|
89
|
+
end
|
90
|
+
|
91
|
+
[output, exit_value]
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.popen_with_stdin(command, stdin_data: nil, retries_left: OPEN_STDIN_RETRY_COUNT)
|
95
|
+
stdin = nil
|
96
|
+
result = Open3.popen2e(*command)
|
97
|
+
stdin = result.first
|
98
|
+
|
99
|
+
# write input to stdin
|
100
|
+
begin
|
101
|
+
stdin.write(stdin_data) if stdin_data
|
102
|
+
rescue Errno::EPIPE => e
|
103
|
+
if retries_left > 0
|
104
|
+
return popen_with_stdin(command, stdin_data: stdin_data, retries_left: retries_left - 1)
|
105
|
+
else
|
106
|
+
raise e
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
result
|
111
|
+
ensure
|
112
|
+
stdin&.close
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module CI
|
5
|
+
module Utils
|
6
|
+
module SourceCode
|
7
|
+
begin
|
8
|
+
require "datadog_ci_native.#{RUBY_VERSION}_#{RUBY_PLATFORM}"
|
9
|
+
|
10
|
+
LAST_LINE_AVAILABLE = true
|
11
|
+
rescue LoadError
|
12
|
+
LAST_LINE_AVAILABLE = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.last_line(target)
|
16
|
+
return nil if target.nil?
|
17
|
+
return nil unless LAST_LINE_AVAILABLE
|
18
|
+
|
19
|
+
# Ruby has outdated RBS for RubyVM::InstructionSequence where method `of` is not defined
|
20
|
+
# steep:ignore:start
|
21
|
+
iseq = RubyVM::InstructionSequence.of(target)
|
22
|
+
return nil unless iseq.is_a?(RubyVM::InstructionSequence)
|
23
|
+
# steep:ignore:end
|
24
|
+
|
25
|
+
# this function is implemented in ext/datadog_ci_native/datadog_source_code.c
|
26
|
+
_native_last_line_from_iseq(iseq)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/datadog/ci/version.rb
CHANGED
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.
|
4
|
+
version: 1.19.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Datadog, Inc.
|
@@ -60,7 +60,7 @@ email:
|
|
60
60
|
executables:
|
61
61
|
- ddcirb
|
62
62
|
extensions:
|
63
|
-
- ext/
|
63
|
+
- ext/datadog_ci_native/extconf.rb
|
64
64
|
extra_rdoc_files: []
|
65
65
|
files:
|
66
66
|
- CHANGELOG.md
|
@@ -71,8 +71,12 @@ files:
|
|
71
71
|
- NOTICE
|
72
72
|
- README.md
|
73
73
|
- exe/ddcirb
|
74
|
-
- ext/
|
75
|
-
- ext/datadog_cov
|
74
|
+
- ext/datadog_ci_native/ci.c
|
75
|
+
- ext/datadog_ci_native/datadog_cov.c
|
76
|
+
- ext/datadog_ci_native/datadog_cov.h
|
77
|
+
- ext/datadog_ci_native/datadog_source_code.c
|
78
|
+
- ext/datadog_ci_native/datadog_source_code.h
|
79
|
+
- ext/datadog_ci_native/extconf.rb
|
76
80
|
- lib/datadog/ci.rb
|
77
81
|
- lib/datadog/ci/async_writer.rb
|
78
82
|
- lib/datadog/ci/auto_instrument.rb
|
@@ -187,6 +191,14 @@ files:
|
|
187
191
|
- lib/datadog/ci/ext/telemetry.rb
|
188
192
|
- lib/datadog/ci/ext/test.rb
|
189
193
|
- lib/datadog/ci/ext/transport.rb
|
194
|
+
- lib/datadog/ci/git/base_branch_sha_detection/base.rb
|
195
|
+
- lib/datadog/ci/git/base_branch_sha_detection/branch_metric.rb
|
196
|
+
- lib/datadog/ci/git/base_branch_sha_detection/guesser.rb
|
197
|
+
- lib/datadog/ci/git/base_branch_sha_detection/merge_base_extractor.rb
|
198
|
+
- lib/datadog/ci/git/base_branch_sha_detector.rb
|
199
|
+
- lib/datadog/ci/git/changed_lines.rb
|
200
|
+
- lib/datadog/ci/git/cli.rb
|
201
|
+
- lib/datadog/ci/git/diff.rb
|
190
202
|
- lib/datadog/ci/git/local_repository.rb
|
191
203
|
- lib/datadog/ci/git/packfiles.rb
|
192
204
|
- lib/datadog/ci/git/search_commits.rb
|
@@ -195,7 +207,6 @@ files:
|
|
195
207
|
- lib/datadog/ci/git/upload_packfile.rb
|
196
208
|
- lib/datadog/ci/git/user.rb
|
197
209
|
- lib/datadog/ci/impacted_tests_detection/component.rb
|
198
|
-
- lib/datadog/ci/impacted_tests_detection/telemetry.rb
|
199
210
|
- lib/datadog/ci/logs/component.rb
|
200
211
|
- lib/datadog/ci/logs/transport.rb
|
201
212
|
- lib/datadog/ci/readonly_test_module.rb
|
@@ -265,11 +276,13 @@ files:
|
|
265
276
|
- lib/datadog/ci/transport/http.rb
|
266
277
|
- lib/datadog/ci/transport/telemetry.rb
|
267
278
|
- lib/datadog/ci/utils/bundle.rb
|
279
|
+
- lib/datadog/ci/utils/command.rb
|
268
280
|
- lib/datadog/ci/utils/configuration.rb
|
269
281
|
- lib/datadog/ci/utils/file_storage.rb
|
270
282
|
- lib/datadog/ci/utils/git.rb
|
271
283
|
- lib/datadog/ci/utils/parsing.rb
|
272
284
|
- lib/datadog/ci/utils/rum.rb
|
285
|
+
- lib/datadog/ci/utils/source_code.rb
|
273
286
|
- lib/datadog/ci/utils/stateful.rb
|
274
287
|
- lib/datadog/ci/utils/telemetry.rb
|
275
288
|
- lib/datadog/ci/utils/test_run.rb
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "../ext/telemetry"
|
4
|
-
require_relative "../utils/telemetry"
|
5
|
-
|
6
|
-
module Datadog
|
7
|
-
module CI
|
8
|
-
module ImpactedTestsDetection
|
9
|
-
module Telemetry
|
10
|
-
def self.impacted_test_detected
|
11
|
-
Utils::Telemetry.inc(Ext::Telemetry::METRIC_IMPACTED_TESTS_IS_MODIFIED, 1)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|