datadog-ci 1.12.0 → 1.14.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 +27 -2
- data/lib/datadog/ci/configuration/components.rb +27 -15
- data/lib/datadog/ci/configuration/settings.rb +12 -0
- data/lib/datadog/ci/contrib/cucumber/instrumentation.rb +5 -2
- data/lib/datadog/ci/contrib/minitest/helpers.rb +26 -0
- data/lib/datadog/ci/contrib/minitest/runnable.rb +1 -19
- data/lib/datadog/ci/contrib/minitest/runner.rb +9 -5
- data/lib/datadog/ci/contrib/minitest/test.rb +5 -12
- data/lib/datadog/ci/contrib/rspec/example.rb +3 -3
- data/lib/datadog/ci/contrib/rspec/runner.rb +1 -1
- data/lib/datadog/ci/ext/app_types.rb +1 -1
- data/lib/datadog/ci/ext/settings.rb +2 -0
- data/lib/datadog/ci/ext/telemetry.rb +17 -5
- data/lib/datadog/ci/ext/test.rb +42 -4
- data/lib/datadog/ci/ext/transport.rb +6 -0
- data/lib/datadog/ci/remote/component.rb +11 -1
- data/lib/datadog/ci/remote/library_settings.rb +31 -0
- data/lib/datadog/ci/remote/library_settings_client.rb +2 -1
- data/lib/datadog/ci/span.rb +4 -0
- data/lib/datadog/ci/test.rb +67 -9
- data/lib/datadog/ci/test_management/component.rb +61 -0
- data/lib/datadog/ci/test_management/null_component.rb +25 -0
- data/lib/datadog/ci/test_management/tests_properties.rb +128 -0
- data/lib/datadog/ci/test_optimisation/component.rb +9 -30
- data/lib/datadog/ci/test_optimisation/skippable_percentage/base.rb +4 -0
- data/lib/datadog/ci/test_optimisation/skippable_percentage/calculator.rb +3 -3
- data/lib/datadog/ci/test_retries/component.rb +37 -7
- data/lib/datadog/ci/test_retries/driver/base.rb +5 -0
- data/lib/datadog/ci/test_retries/driver/retry_failed.rb +4 -0
- data/lib/datadog/ci/test_retries/driver/retry_flaky_fixed.rb +38 -0
- data/lib/datadog/ci/test_retries/driver/retry_new.rb +2 -7
- data/lib/datadog/ci/test_retries/strategy/retry_flaky_fixed.rb +43 -0
- data/lib/datadog/ci/test_retries/strategy/retry_new.rb +5 -47
- data/lib/datadog/ci/test_session.rb +1 -1
- data/lib/datadog/ci/test_suite.rb +39 -2
- data/lib/datadog/ci/test_visibility/capabilities.rb +36 -0
- data/lib/datadog/ci/test_visibility/component.rb +127 -13
- data/lib/datadog/ci/test_visibility/context.rb +26 -17
- data/lib/datadog/ci/{test_retries/unique_tests_client.rb → test_visibility/known_tests.rb} +10 -10
- data/lib/datadog/ci/test_visibility/null_component.rb +4 -1
- data/lib/datadog/ci/test_visibility/serializers/factories/test_level.rb +1 -1
- data/lib/datadog/ci/test_visibility/store/global.rb +7 -0
- data/lib/datadog/ci/test_visibility/telemetry.rb +11 -2
- data/lib/datadog/ci/test_visibility/transport.rb +15 -2
- data/lib/datadog/ci/version.rb +1 -1
- data/lib/datadog/ci.rb +14 -6
- metadata +25 -5
@@ -62,7 +62,8 @@ module Datadog
|
|
62
62
|
Ext::Telemetry::TAG_EARLY_FLAKE_DETECTION_ENABLED => library_settings.early_flake_detection_enabled?.to_s,
|
63
63
|
Ext::Telemetry::TAG_FLAKY_TEST_RETRIES_ENABLED => library_settings.flaky_test_retries_enabled?.to_s,
|
64
64
|
Ext::Telemetry::TAG_ITR_ENABLED => library_settings.itr_enabled?.to_s,
|
65
|
-
Ext::Telemetry::TAG_REQUIRE_GIT => library_settings.require_git?.to_s
|
65
|
+
Ext::Telemetry::TAG_REQUIRE_GIT => library_settings.require_git?.to_s,
|
66
|
+
Ext::Telemetry::TAG_KNOWN_TESTS_ENABLED => library_settings.known_tests_enabled?.to_s
|
66
67
|
}
|
67
68
|
)
|
68
69
|
|
data/lib/datadog/ci/span.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "drb"
|
4
|
+
|
3
5
|
require "datadog/core/environment/platform"
|
4
6
|
|
5
7
|
require_relative "ext/test"
|
@@ -12,6 +14,8 @@ module Datadog
|
|
12
14
|
#
|
13
15
|
# @public_api
|
14
16
|
class Span
|
17
|
+
include DRb::DRbUndumped
|
18
|
+
|
15
19
|
attr_reader :tracer_span
|
16
20
|
|
17
21
|
def initialize(tracer_span)
|
data/lib/datadog/ci/test.rb
CHANGED
@@ -62,19 +62,37 @@ module Datadog
|
|
62
62
|
get_tag(Ext::Test::TAG_TEST_SESSION_ID)
|
63
63
|
end
|
64
64
|
|
65
|
-
# Returns "true" if the test is skipped by the intelligent test runner.
|
66
|
-
# @return [Boolean] true if the test is skipped by the intelligent test runner, false otherwise.
|
67
|
-
def skipped_by_itr?
|
68
|
-
get_tag(Ext::Test::TAG_ITR_SKIPPED_BY_ITR) == "true"
|
69
|
-
end
|
70
|
-
|
71
65
|
# Returns "true" if test span represents a retry.
|
72
66
|
# @return [Boolean] true if this test is a retry, false otherwise.
|
73
67
|
def is_retry?
|
74
68
|
get_tag(Ext::Test::TAG_IS_RETRY) == "true"
|
75
69
|
end
|
76
70
|
|
77
|
-
#
|
71
|
+
# Returns "true" if this span represents a test that wasn't known to Datadog before.
|
72
|
+
# @return [Boolean] true if this test is a new one, false otherwise.
|
73
|
+
def is_new?
|
74
|
+
get_tag(Ext::Test::TAG_IS_NEW) == "true"
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns "true" if this test is quarantined by Datadog test management.
|
78
|
+
# @return [Boolean] true if this test is quarantined, false otherwise.
|
79
|
+
def quarantined?
|
80
|
+
get_tag(Ext::Test::TAG_IS_QUARANTINED) == "true"
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns "true" if this test is disabled by Datadog test management.
|
84
|
+
# @return [Boolean] true if this test is disabled, false otherwise.
|
85
|
+
def disabled?
|
86
|
+
get_tag(Ext::Test::TAG_IS_TEST_DISABLED) == "true"
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns "true" if this flaky test has fixing attempts (determined by Datadog backend).
|
90
|
+
# @return [Boolean] true if this test is attempted to be fixed.
|
91
|
+
def attempt_to_fix?
|
92
|
+
get_tag(Ext::Test::TAG_IS_ATTEMPT_TO_FIX) == "true"
|
93
|
+
end
|
94
|
+
|
95
|
+
# Marks this test as unskippable by the Test Impact Analysis.
|
78
96
|
# This must be done before the test execution starts.
|
79
97
|
#
|
80
98
|
# Examples of tests that should be unskippable:
|
@@ -88,7 +106,7 @@ module Datadog
|
|
88
106
|
TestOptimisation::Telemetry.itr_unskippable
|
89
107
|
set_tag(Ext::Test::TAG_ITR_UNSKIPPABLE, "true")
|
90
108
|
|
91
|
-
if
|
109
|
+
if skipped_by_test_impact_analysis?
|
92
110
|
clear_tag(Ext::Test::TAG_ITR_SKIPPED_BY_ITR)
|
93
111
|
|
94
112
|
TestOptimisation::Telemetry.itr_forced_run
|
@@ -110,7 +128,13 @@ module Datadog
|
|
110
128
|
def failed!(exception: nil)
|
111
129
|
super
|
112
130
|
|
113
|
-
|
131
|
+
# if we should ignore failures, we consider this test to be passed
|
132
|
+
if should_ignore_failures?
|
133
|
+
# use a special "fail_ignored" status to mark this test as failed but ignored
|
134
|
+
record_test_result(Ext::Test::ExecutionStatsStatus::FAIL_IGNORED)
|
135
|
+
else
|
136
|
+
record_test_result(Ext::Test::Status::FAIL)
|
137
|
+
end
|
114
138
|
end
|
115
139
|
|
116
140
|
# Sets the status of the span to "skip".
|
@@ -148,6 +172,40 @@ module Datadog
|
|
148
172
|
!!test_suite&.any_test_retry_passed?(datadog_test_id)
|
149
173
|
end
|
150
174
|
|
175
|
+
# @internal
|
176
|
+
def all_executions_failed?
|
177
|
+
!!test_suite&.all_executions_failed?(datadog_test_id)
|
178
|
+
end
|
179
|
+
|
180
|
+
# @internal
|
181
|
+
def all_executions_passed?
|
182
|
+
!!test_suite&.all_executions_passed?(datadog_test_id)
|
183
|
+
end
|
184
|
+
|
185
|
+
# @internal
|
186
|
+
def datadog_skip_reason
|
187
|
+
if skipped_by_test_impact_analysis?
|
188
|
+
Ext::Test::SkipReason::TEST_IMPACT_ANALYSIS
|
189
|
+
elsif disabled? || quarantined?
|
190
|
+
Ext::Test::SkipReason::TEST_MANAGEMENT_DISABLED
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# @internal
|
195
|
+
def should_skip?
|
196
|
+
skipped_by_test_impact_analysis? || (disabled? && !attempt_to_fix?)
|
197
|
+
end
|
198
|
+
|
199
|
+
# @internal
|
200
|
+
def should_ignore_failures?
|
201
|
+
quarantined? || disabled? || any_retry_passed?
|
202
|
+
end
|
203
|
+
|
204
|
+
# @internal
|
205
|
+
def skipped_by_test_impact_analysis?
|
206
|
+
get_tag(Ext::Test::TAG_ITR_SKIPPED_BY_ITR) == "true"
|
207
|
+
end
|
208
|
+
|
151
209
|
private
|
152
210
|
|
153
211
|
def record_test_result(datadog_status)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../ext/telemetry"
|
4
|
+
require_relative "../ext/test"
|
5
|
+
require_relative "../utils/telemetry"
|
6
|
+
require_relative "../utils/test_run"
|
7
|
+
|
8
|
+
module Datadog
|
9
|
+
module CI
|
10
|
+
module TestManagement
|
11
|
+
# Test management is a feature that lets people manage their flaky tests in Datadog.
|
12
|
+
# It includes:
|
13
|
+
# - marking test as quarantined causes test to continue running but not failing the build
|
14
|
+
# - marking test as disabled causes test to be skipped
|
15
|
+
# - marking test as "attempted to fix" causes test to be retried many times to confirm that fix worked
|
16
|
+
class Component
|
17
|
+
attr_reader :enabled, :tests_properties
|
18
|
+
|
19
|
+
def initialize(enabled:, tests_properties_client:)
|
20
|
+
@enabled = enabled
|
21
|
+
|
22
|
+
@tests_properties_client = tests_properties_client
|
23
|
+
@tests_properties = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def configure(library_settings, test_session)
|
27
|
+
@enabled &&= library_settings.test_management_enabled?
|
28
|
+
|
29
|
+
return unless @enabled
|
30
|
+
|
31
|
+
test_session.set_tag(Ext::Test::TAG_TEST_MANAGEMENT_ENABLED, "true")
|
32
|
+
|
33
|
+
@tests_properties = @tests_properties_client.fetch(test_session)
|
34
|
+
|
35
|
+
Utils::Telemetry.distribution(
|
36
|
+
Ext::Telemetry::METRIC_TEST_MANAGEMENT_TESTS_RESPONSE_TESTS,
|
37
|
+
@tests_properties.count.to_f
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def tag_test_from_properties(test_span)
|
42
|
+
return unless @enabled
|
43
|
+
|
44
|
+
datadog_test_id = Utils::TestRun.datadog_test_id(test_span.name, test_span.test_suite_name)
|
45
|
+
test_properties = @tests_properties[datadog_test_id]
|
46
|
+
|
47
|
+
if test_properties.nil?
|
48
|
+
Datadog.logger.debug { "Test properties not found for test: #{datadog_test_id}" }
|
49
|
+
return
|
50
|
+
end
|
51
|
+
|
52
|
+
Datadog.logger.debug { "Test properties for test #{datadog_test_id} are: [#{test_properties}]" }
|
53
|
+
|
54
|
+
test_span.set_tag(Ext::Test::TAG_IS_QUARANTINED, "true") if test_properties["quarantined"]
|
55
|
+
test_span.set_tag(Ext::Test::TAG_IS_TEST_DISABLED, "true") if test_properties["disabled"]
|
56
|
+
test_span.set_tag(Ext::Test::TAG_IS_ATTEMPT_TO_FIX, "true") if test_properties["attempt_to_fix"]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,25 @@
|
|
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 TestManagement
|
9
|
+
class NullComponent
|
10
|
+
attr_reader :enabled, :tests_properties
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@enabled = false
|
14
|
+
@tests_properties = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def configure(_, _)
|
18
|
+
end
|
19
|
+
|
20
|
+
def tag_test_from_properties(_)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
require_relative "../ext/telemetry"
|
6
|
+
require_relative "../ext/transport"
|
7
|
+
require_relative "../transport/telemetry"
|
8
|
+
require_relative "../utils/parsing"
|
9
|
+
require_relative "../utils/telemetry"
|
10
|
+
require_relative "../utils/test_run"
|
11
|
+
|
12
|
+
module Datadog
|
13
|
+
module CI
|
14
|
+
module TestManagement
|
15
|
+
# fetches and stores a map of tests to their test management properties from the backend
|
16
|
+
class TestsProperties
|
17
|
+
class Response
|
18
|
+
def initialize(http_response)
|
19
|
+
@http_response = http_response
|
20
|
+
@json = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def ok?
|
24
|
+
resp = @http_response
|
25
|
+
!resp.nil? && resp.ok?
|
26
|
+
end
|
27
|
+
|
28
|
+
def tests
|
29
|
+
tests_map = {}
|
30
|
+
|
31
|
+
payload
|
32
|
+
.fetch("data", {})
|
33
|
+
.fetch("attributes", {})
|
34
|
+
.fetch("modules", {})
|
35
|
+
.each do |_test_module, module_hash|
|
36
|
+
module_hash
|
37
|
+
.fetch("suites", {})
|
38
|
+
.each do |test_suite, suite_hash|
|
39
|
+
suite_hash.fetch("tests", {})
|
40
|
+
.each do |test_name, properties_hash|
|
41
|
+
properties = properties_hash.fetch("properties", {})
|
42
|
+
properties.transform_values! { |v| Utils::Parsing.convert_to_bool(v) }
|
43
|
+
|
44
|
+
tests_map[Utils::TestRun.datadog_test_id(test_name, test_suite)] = properties
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
tests_map
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def payload
|
55
|
+
cached = @json
|
56
|
+
return cached unless cached.nil?
|
57
|
+
|
58
|
+
resp = @http_response
|
59
|
+
return @json = {} if resp.nil? || !ok?
|
60
|
+
|
61
|
+
begin
|
62
|
+
@json = JSON.parse(resp.payload)
|
63
|
+
rescue JSON::ParserError => e
|
64
|
+
Datadog.logger.error(
|
65
|
+
"Failed to parse test management tests response payload: #{e}. Payload was: #{resp.payload}"
|
66
|
+
)
|
67
|
+
@json = {}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(api: nil)
|
73
|
+
@api = api
|
74
|
+
end
|
75
|
+
|
76
|
+
def fetch(test_session)
|
77
|
+
api = @api
|
78
|
+
return {} unless api
|
79
|
+
|
80
|
+
request_payload = payload(test_session)
|
81
|
+
Datadog.logger.debug("Fetching test management tests with request: #{request_payload}")
|
82
|
+
|
83
|
+
http_response = api.api_request(
|
84
|
+
path: Ext::Transport::DD_API_TEST_MANAGEMENT_TESTS_PATH,
|
85
|
+
payload: request_payload
|
86
|
+
)
|
87
|
+
|
88
|
+
CI::Transport::Telemetry.api_requests(
|
89
|
+
Ext::Telemetry::METRIC_TEST_MANAGEMENT_TESTS_REQUEST,
|
90
|
+
1,
|
91
|
+
compressed: http_response.request_compressed
|
92
|
+
)
|
93
|
+
Utils::Telemetry.distribution(Ext::Telemetry::METRIC_TEST_MANAGEMENT_TESTS_REQUEST_MS, http_response.duration_ms)
|
94
|
+
Utils::Telemetry.distribution(
|
95
|
+
Ext::Telemetry::METRIC_TEST_MANAGEMENT_TESTS_RESPONSE_BYTES,
|
96
|
+
http_response.response_size.to_f,
|
97
|
+
{Ext::Telemetry::TAG_RESPONSE_COMPRESSED => http_response.gzipped_content?.to_s}
|
98
|
+
)
|
99
|
+
|
100
|
+
unless http_response.ok?
|
101
|
+
CI::Transport::Telemetry.api_requests_errors(
|
102
|
+
Ext::Telemetry::METRIC_TEST_MANAGEMENT_TESTS_REQUEST_ERRORS,
|
103
|
+
1,
|
104
|
+
error_type: http_response.telemetry_error_type,
|
105
|
+
status_code: http_response.code
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
Response.new(http_response).tests
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def payload(test_session)
|
115
|
+
{
|
116
|
+
"data" => {
|
117
|
+
"id" => Datadog::Core::Environment::Identity.id,
|
118
|
+
"type" => Ext::Transport::DD_API_TEST_MANAGEMENT_TESTS_TYPE,
|
119
|
+
"attributes" => {
|
120
|
+
"repository_url" => test_session.git_repository_url
|
121
|
+
}
|
122
|
+
}
|
123
|
+
}.to_json
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -3,7 +3,6 @@
|
|
3
3
|
require "pp"
|
4
4
|
|
5
5
|
require "datadog/core/telemetry/logging"
|
6
|
-
require "datadog/core/utils/forking"
|
7
6
|
|
8
7
|
require_relative "../ext/test"
|
9
8
|
require_relative "../ext/telemetry"
|
@@ -20,14 +19,11 @@ require_relative "telemetry"
|
|
20
19
|
module Datadog
|
21
20
|
module CI
|
22
21
|
module TestOptimisation
|
23
|
-
#
|
22
|
+
# Test Impact Analysis implementation
|
24
23
|
# Integrates with backend to provide test impact analysis data and
|
25
24
|
# skip tests that are not impacted by the changes
|
26
25
|
class Component
|
27
|
-
include Core::Utils::Forking
|
28
|
-
|
29
26
|
attr_reader :correlation_id, :skippable_tests, :skippable_tests_fetch_error,
|
30
|
-
:skipped_tests_count, :total_tests_count,
|
31
27
|
:enabled, :test_skipping_enabled, :code_coverage_enabled
|
32
28
|
|
33
29
|
def initialize(
|
@@ -61,9 +57,6 @@ module Datadog
|
|
61
57
|
@correlation_id = nil
|
62
58
|
@skippable_tests = Set.new
|
63
59
|
|
64
|
-
@total_tests_count = 0
|
65
|
-
@skipped_tests_count = 0
|
66
|
-
|
67
60
|
@mutex = Mutex.new
|
68
61
|
|
69
62
|
Datadog.logger.debug("TestOptimisation initialized with enabled: #{@enabled}")
|
@@ -155,11 +148,6 @@ module Datadog
|
|
155
148
|
return if !enabled? || !skipping_tests?
|
156
149
|
|
157
150
|
if skippable?(test)
|
158
|
-
if forked?
|
159
|
-
Datadog.logger.warn { "Intelligent test runner is not supported for forking test runners yet" }
|
160
|
-
return
|
161
|
-
end
|
162
|
-
|
163
151
|
test.set_tag(Ext::Test::TAG_ITR_SKIPPED_BY_ITR, "true")
|
164
152
|
|
165
153
|
Datadog.logger.debug { "Marked test as skippable: #{test.datadog_test_id}" }
|
@@ -168,31 +156,22 @@ module Datadog
|
|
168
156
|
end
|
169
157
|
end
|
170
158
|
|
171
|
-
def
|
172
|
-
|
173
|
-
@total_tests_count += 1
|
159
|
+
def on_test_finished(test, context)
|
160
|
+
return if !test.skipped? || !test.skipped_by_test_impact_analysis?
|
174
161
|
|
175
|
-
|
162
|
+
Telemetry.itr_skipped
|
176
163
|
|
177
|
-
|
178
|
-
Datadog.logger.warn { "ITR is not supported for forking test runners yet" }
|
179
|
-
return
|
180
|
-
end
|
181
|
-
|
182
|
-
Telemetry.itr_skipped
|
183
|
-
|
184
|
-
@skipped_tests_count += 1
|
185
|
-
end
|
164
|
+
context.incr_tests_skipped_by_tia_count
|
186
165
|
end
|
187
166
|
|
188
|
-
def write_test_session_tags(test_session)
|
167
|
+
def write_test_session_tags(test_session, skipped_tests_count)
|
189
168
|
return if !enabled?
|
190
169
|
|
191
170
|
Datadog.logger.debug { "Finished optimised session with test skipping enabled: #{@test_skipping_enabled}" }
|
192
|
-
Datadog.logger.debug { "#{
|
171
|
+
Datadog.logger.debug { "#{skipped_tests_count} tests were skipped" }
|
193
172
|
|
194
|
-
test_session.set_tag(Ext::Test::TAG_ITR_TESTS_SKIPPED,
|
195
|
-
test_session.set_tag(Ext::Test::TAG_ITR_TEST_SKIPPING_COUNT,
|
173
|
+
test_session.set_tag(Ext::Test::TAG_ITR_TESTS_SKIPPED, skipped_tests_count.positive?.to_s)
|
174
|
+
test_session.set_tag(Ext::Test::TAG_ITR_TEST_SKIPPING_COUNT, skipped_tests_count)
|
196
175
|
end
|
197
176
|
|
198
177
|
def skippable_tests_count
|
@@ -34,11 +34,11 @@ module Datadog
|
|
34
34
|
return 0.0
|
35
35
|
end
|
36
36
|
|
37
|
-
log("Total tests count: #{
|
38
|
-
log("Skipped tests count: #{
|
37
|
+
log("Total tests count: #{test_visibility.total_tests_count}")
|
38
|
+
log("Skipped tests count: #{test_visibility.tests_skipped_by_tia_count}")
|
39
39
|
validate_test_optimisation_state!
|
40
40
|
|
41
|
-
(
|
41
|
+
(test_visibility.tests_skipped_by_tia_count.to_f / test_visibility.total_tests_count.to_f).floor(2)
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
@@ -7,6 +7,7 @@ require_relative "driver/retry_new"
|
|
7
7
|
require_relative "strategy/no_retry"
|
8
8
|
require_relative "strategy/retry_failed"
|
9
9
|
require_relative "strategy/retry_new"
|
10
|
+
require_relative "strategy/retry_flaky_fixed"
|
10
11
|
|
11
12
|
require_relative "../ext/telemetry"
|
12
13
|
require_relative "../utils/telemetry"
|
@@ -25,23 +26,33 @@ module Datadog
|
|
25
26
|
retry_failed_tests_max_attempts:,
|
26
27
|
retry_failed_tests_total_limit:,
|
27
28
|
retry_new_tests_enabled:,
|
28
|
-
|
29
|
+
retry_flaky_fixed_tests_enabled:,
|
30
|
+
retry_flaky_fixed_tests_max_attempts:
|
29
31
|
)
|
30
32
|
no_retries_strategy = Strategy::NoRetry.new
|
31
33
|
|
32
|
-
retry_failed_strategy = Strategy::RetryFailed.new(
|
34
|
+
@retry_failed_strategy = Strategy::RetryFailed.new(
|
33
35
|
enabled: retry_failed_tests_enabled,
|
34
36
|
max_attempts: retry_failed_tests_max_attempts,
|
35
37
|
total_limit: retry_failed_tests_total_limit
|
36
38
|
)
|
37
39
|
|
38
|
-
retry_new_strategy = Strategy::RetryNew.new(
|
39
|
-
enabled: retry_new_tests_enabled
|
40
|
-
unique_tests_client: unique_tests_client
|
40
|
+
@retry_new_strategy = Strategy::RetryNew.new(
|
41
|
+
enabled: retry_new_tests_enabled
|
41
42
|
)
|
42
43
|
|
43
|
-
|
44
|
-
|
44
|
+
retry_flaky_fixed_strategy = Strategy::RetryFlakyFixed.new(
|
45
|
+
enabled: retry_flaky_fixed_tests_enabled,
|
46
|
+
max_attempts: retry_flaky_fixed_tests_max_attempts
|
47
|
+
)
|
48
|
+
|
49
|
+
# order is important, we apply the first matching strategy
|
50
|
+
@retry_strategies = [
|
51
|
+
retry_flaky_fixed_strategy,
|
52
|
+
@retry_new_strategy,
|
53
|
+
@retry_failed_strategy,
|
54
|
+
no_retries_strategy
|
55
|
+
]
|
45
56
|
@mutex = Mutex.new
|
46
57
|
end
|
47
58
|
|
@@ -82,6 +93,8 @@ module Datadog
|
|
82
93
|
else
|
83
94
|
# after each retry we record the result, the driver will decide if we should retry again
|
84
95
|
current_retry_driver&.record_retry(test_span)
|
96
|
+
|
97
|
+
tag_last_retry(test_span) unless should_retry?
|
85
98
|
end
|
86
99
|
end
|
87
100
|
|
@@ -94,10 +107,27 @@ module Datadog
|
|
94
107
|
self.current_retry_driver = nil
|
95
108
|
end
|
96
109
|
|
110
|
+
def tag_last_retry(test_span)
|
111
|
+
test_span&.set_tag(Ext::Test::TAG_HAS_FAILED_ALL_RETRIES, "true") if test_span&.all_executions_failed?
|
112
|
+
|
113
|
+
# if we are attempting to fix the test and all retries passed, we indicate that the fix might have worked
|
114
|
+
if test_span&.attempt_to_fix? && test_span.all_executions_passed?
|
115
|
+
test_span&.set_tag(Ext::Test::TAG_ATTEMPT_TO_FIX_PASSED, "true")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
97
119
|
def should_retry?
|
98
120
|
!!current_retry_driver&.should_retry?
|
99
121
|
end
|
100
122
|
|
123
|
+
def auto_test_retries_feature_enabled
|
124
|
+
@retry_failed_strategy.enabled
|
125
|
+
end
|
126
|
+
|
127
|
+
def early_flake_detection_feature_enabled
|
128
|
+
@retry_new_strategy.enabled
|
129
|
+
end
|
130
|
+
|
101
131
|
private
|
102
132
|
|
103
133
|
def current_retry_driver
|
@@ -13,11 +13,16 @@ module Datadog
|
|
13
13
|
|
14
14
|
def record_retry(test_span)
|
15
15
|
test_span&.set_tag(Ext::Test::TAG_IS_RETRY, "true")
|
16
|
+
test_span&.set_tag(Ext::Test::TAG_RETRY_REASON, retry_reason)
|
16
17
|
end
|
17
18
|
|
18
19
|
# duration in float seconds
|
19
20
|
def record_duration(duration)
|
20
21
|
end
|
22
|
+
|
23
|
+
def retry_reason
|
24
|
+
"unknown"
|
25
|
+
end
|
21
26
|
end
|
22
27
|
end
|
23
28
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
require_relative "../../ext/test"
|
6
|
+
|
7
|
+
module Datadog
|
8
|
+
module CI
|
9
|
+
module TestRetries
|
10
|
+
module Driver
|
11
|
+
class RetryFlakyFixed < Base
|
12
|
+
attr_reader :max_attempts
|
13
|
+
|
14
|
+
def initialize(max_attempts:)
|
15
|
+
@attempts = 0
|
16
|
+
@max_attempts = max_attempts
|
17
|
+
end
|
18
|
+
|
19
|
+
def should_retry?
|
20
|
+
@attempts < @max_attempts
|
21
|
+
end
|
22
|
+
|
23
|
+
def record_retry(test_span)
|
24
|
+
super
|
25
|
+
|
26
|
+
@attempts += 1
|
27
|
+
|
28
|
+
Datadog.logger.debug { "Retry Attempts [#{@attempts} / #{@max_attempts}]" }
|
29
|
+
end
|
30
|
+
|
31
|
+
def retry_reason
|
32
|
+
Ext::Test::RetryReason::RETRY_FLAKY_FIXED
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -15,8 +15,6 @@ module Datadog
|
|
15
15
|
@attempts = 0
|
16
16
|
# will be changed based on test span duration
|
17
17
|
@max_attempts = 10
|
18
|
-
|
19
|
-
mark_new_test(test_span)
|
20
18
|
end
|
21
19
|
|
22
20
|
def should_retry?
|
@@ -27,7 +25,6 @@ module Datadog
|
|
27
25
|
super
|
28
26
|
|
29
27
|
@attempts += 1
|
30
|
-
mark_new_test(test_span)
|
31
28
|
|
32
29
|
Datadog.logger.debug { "Retry Attempts [#{@attempts} / #{@max_attempts}]" }
|
33
30
|
end
|
@@ -38,10 +35,8 @@ module Datadog
|
|
38
35
|
Datadog.logger.debug { "Recorded test duration of [#{duration}], new Max Attempts value is [#{@max_attempts}]" }
|
39
36
|
end
|
40
37
|
|
41
|
-
|
42
|
-
|
43
|
-
def mark_new_test(test_span)
|
44
|
-
test_span.set_tag(Ext::Test::TAG_IS_NEW, "true")
|
38
|
+
def retry_reason
|
39
|
+
Ext::Test::RetryReason::RETRY_NEW
|
45
40
|
end
|
46
41
|
end
|
47
42
|
end
|