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
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
require_relative "../driver/retry_flaky_fixed"
|
6
|
+
|
7
|
+
module Datadog
|
8
|
+
module CI
|
9
|
+
module TestRetries
|
10
|
+
module Strategy
|
11
|
+
# This strategy retries tests that are flaky and were marked as attempted to be fixed in Datadog Test Management UI.
|
12
|
+
class RetryFlakyFixed < Base
|
13
|
+
attr_reader :enabled, :max_attempts
|
14
|
+
|
15
|
+
def initialize(
|
16
|
+
enabled:,
|
17
|
+
max_attempts:
|
18
|
+
)
|
19
|
+
@enabled = enabled
|
20
|
+
@max_attempts = max_attempts
|
21
|
+
end
|
22
|
+
|
23
|
+
def configure(library_settings, test_session)
|
24
|
+
@enabled &&= library_settings.test_management_enabled?
|
25
|
+
@max_attempts = library_settings.attempt_to_fix_retries_count || @max_attempts
|
26
|
+
end
|
27
|
+
|
28
|
+
def covers?(test_span)
|
29
|
+
return false unless @enabled
|
30
|
+
|
31
|
+
!!test_span&.attempt_to_fix?
|
32
|
+
end
|
33
|
+
|
34
|
+
def build_driver(test_span)
|
35
|
+
Datadog.logger.debug { "#{test_span.name} is attempt_to_fix, will be retried" }
|
36
|
+
|
37
|
+
Driver::RetryFlakyFixed.new(max_attempts: max_attempts)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -11,19 +11,13 @@ module Datadog
|
|
11
11
|
class RetryNew < Base
|
12
12
|
DEFAULT_TOTAL_TESTS_COUNT = 100
|
13
13
|
|
14
|
-
attr_reader :enabled, :max_attempts_thresholds, :
|
14
|
+
attr_reader :enabled, :max_attempts_thresholds, :total_limit, :retried_count
|
15
15
|
|
16
|
-
def initialize(
|
17
|
-
enabled:,
|
18
|
-
unique_tests_client:
|
19
|
-
)
|
16
|
+
def initialize(enabled:)
|
20
17
|
@enabled = enabled
|
21
|
-
@unique_tests_set = Set.new
|
22
18
|
# total maximum number of new tests to retry (will be set based on the total number of tests in the session)
|
23
19
|
@total_limit = 0
|
24
20
|
@retried_count = 0
|
25
|
-
|
26
|
-
@unique_tests_client = unique_tests_client
|
27
21
|
end
|
28
22
|
|
29
23
|
def covers?(test_span)
|
@@ -37,11 +31,11 @@ module Datadog
|
|
37
31
|
mark_test_session_faulty(Datadog::CI.active_test_session)
|
38
32
|
end
|
39
33
|
|
40
|
-
@enabled && !test_span.skipped? &&
|
34
|
+
@enabled && !test_span.skipped? && test_span.is_new?
|
41
35
|
end
|
42
36
|
|
43
37
|
def configure(library_settings, test_session)
|
44
|
-
@enabled &&= library_settings.early_flake_detection_enabled?
|
38
|
+
@enabled &&= library_settings.early_flake_detection_enabled? && library_settings.known_tests_enabled?
|
45
39
|
|
46
40
|
return unless @enabled
|
47
41
|
|
@@ -50,7 +44,6 @@ module Datadog
|
|
50
44
|
|
51
45
|
set_max_attempts_thresholds(library_settings)
|
52
46
|
calculate_total_retries_limit(library_settings, test_session)
|
53
|
-
fetch_known_unique_tests(test_session)
|
54
47
|
end
|
55
48
|
|
56
49
|
def build_driver(test_span)
|
@@ -68,20 +61,6 @@ module Datadog
|
|
68
61
|
test_session&.set_tag(Ext::Test::TAG_EARLY_FLAKE_ABORT_REASON, Ext::Test::EARLY_FLAKE_FAULTY)
|
69
62
|
end
|
70
63
|
|
71
|
-
def is_new_test?(test_span)
|
72
|
-
test_id = Utils::TestRun.datadog_test_id(test_span.name, test_span.test_suite_name)
|
73
|
-
|
74
|
-
result = !@unique_tests_set.include?(test_id)
|
75
|
-
|
76
|
-
if result
|
77
|
-
Datadog.logger.debug do
|
78
|
-
"#{test_id} is not found in the unique tests set, it is a new test"
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
result
|
83
|
-
end
|
84
|
-
|
85
64
|
def set_max_attempts_thresholds(library_settings)
|
86
65
|
@max_attempts_thresholds = library_settings.slow_test_retries
|
87
66
|
Datadog.logger.debug do
|
@@ -91,7 +70,7 @@ module Datadog
|
|
91
70
|
|
92
71
|
def calculate_total_retries_limit(library_settings, test_session)
|
93
72
|
percentage_limit = library_settings.faulty_session_threshold
|
94
|
-
tests_count = test_session.
|
73
|
+
tests_count = test_session.estimated_total_tests_count.to_i
|
95
74
|
if tests_count.zero?
|
96
75
|
Datadog.logger.debug do
|
97
76
|
"Total tests count is zero, using default value for the total number of tests: [#{DEFAULT_TOTAL_TESTS_COUNT}]"
|
@@ -104,27 +83,6 @@ module Datadog
|
|
104
83
|
"Retry new tests total limit is [#{@total_limit}] (#{percentage_limit}% of #{tests_count})"
|
105
84
|
end
|
106
85
|
end
|
107
|
-
|
108
|
-
def fetch_known_unique_tests(test_session)
|
109
|
-
@unique_tests_set = @unique_tests_client.fetch_unique_tests(test_session)
|
110
|
-
if @unique_tests_set.empty?
|
111
|
-
@enabled = false
|
112
|
-
mark_test_session_faulty(test_session)
|
113
|
-
|
114
|
-
Datadog.logger.warn(
|
115
|
-
"Disabling early flake detection because there are no known tests (possible reason: no test runs in default branch)"
|
116
|
-
)
|
117
|
-
end
|
118
|
-
|
119
|
-
# report how many unique tests were found
|
120
|
-
Datadog.logger.debug do
|
121
|
-
"Found [#{@unique_tests_set.size}] known unique tests"
|
122
|
-
end
|
123
|
-
Utils::Telemetry.distribution(
|
124
|
-
Ext::Telemetry::METRIC_EFD_UNIQUE_TESTS_RESPONSE_TESTS,
|
125
|
-
@unique_tests_set.size.to_f
|
126
|
-
)
|
127
|
-
end
|
128
86
|
end
|
129
87
|
end
|
130
88
|
end
|
@@ -59,6 +59,24 @@ module Datadog
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
+
# @internal
|
63
|
+
def all_executions_failed?(test_id)
|
64
|
+
synchronize do
|
65
|
+
stats = @execution_stats_per_test[test_id]
|
66
|
+
stats && (stats[Ext::Test::Status::FAIL] > 0 || stats[Ext::Test::ExecutionStatsStatus::FAIL_IGNORED] > 0) &&
|
67
|
+
stats[Ext::Test::Status::PASS] == 0
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# @internal
|
72
|
+
def all_executions_passed?(test_id)
|
73
|
+
synchronize do
|
74
|
+
stats = @execution_stats_per_test[test_id]
|
75
|
+
stats && stats[Ext::Test::Status::PASS] > 0 && stats[Ext::Test::Status::FAIL] == 0 &&
|
76
|
+
stats[Ext::Test::ExecutionStatsStatus::FAIL_IGNORED] == 0
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
62
80
|
# @internal
|
63
81
|
def test_executed?(test_id)
|
64
82
|
synchronize do
|
@@ -66,6 +84,24 @@ module Datadog
|
|
66
84
|
end
|
67
85
|
end
|
68
86
|
|
87
|
+
# @internal
|
88
|
+
def set_expected_tests!(expected_tests)
|
89
|
+
synchronize do
|
90
|
+
return if @expected_tests_set
|
91
|
+
|
92
|
+
@expected_tests_set = Set.new(expected_tests)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# @internal
|
97
|
+
def expected_test_done!(test_name)
|
98
|
+
synchronize do
|
99
|
+
@expected_tests_set.delete(test_name)
|
100
|
+
|
101
|
+
finish if @expected_tests_set.empty?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
69
105
|
private
|
70
106
|
|
71
107
|
def set_status_from_stats!
|
@@ -89,8 +125,9 @@ module Datadog
|
|
89
125
|
end
|
90
126
|
|
91
127
|
def derive_test_status_from_execution_stats(test_execution_stats)
|
92
|
-
# test is passed if it passed at least once
|
93
|
-
if test_execution_stats[Ext::Test::Status::PASS] > 0
|
128
|
+
# test is passed if it passed at least once or it failed but fail was ignored
|
129
|
+
if test_execution_stats[Ext::Test::Status::PASS] > 0 ||
|
130
|
+
test_execution_stats[Ext::Test::ExecutionStatsStatus::FAIL_IGNORED] > 0
|
94
131
|
Ext::Test::Status::PASS
|
95
132
|
# if test was never passed, it is failed if it failed at least once
|
96
133
|
elsif test_execution_stats[Ext::Test::Status::FAIL] > 0
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../ext/test"
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module CI
|
7
|
+
module TestVisibility
|
8
|
+
# Generates internal tags for library capabilities
|
9
|
+
module Capabilities
|
10
|
+
def self.tags
|
11
|
+
tags = {}
|
12
|
+
|
13
|
+
test_optimisation = Datadog::CI.send(:test_optimisation)
|
14
|
+
tags[Ext::Test::LibraryCapabilities::TAG_TEST_IMPACT_ANALYSIS] = test_optimisation.enabled.to_s
|
15
|
+
|
16
|
+
test_management = Datadog::CI.send(:test_management)
|
17
|
+
test_management_tag_value = test_management.enabled.to_s
|
18
|
+
|
19
|
+
[
|
20
|
+
Ext::Test::LibraryCapabilities::TAG_TEST_MANAGEMENT_ATTEMPT_TO_FIX,
|
21
|
+
Ext::Test::LibraryCapabilities::TAG_TEST_MANAGEMENT_QUARANTINE,
|
22
|
+
Ext::Test::LibraryCapabilities::TAG_TEST_MANAGEMENT_DISABLE
|
23
|
+
].each do |tag|
|
24
|
+
tags[tag] = test_management_tag_value
|
25
|
+
end
|
26
|
+
|
27
|
+
test_retries = Datadog::CI.send(:test_retries)
|
28
|
+
tags[Ext::Test::LibraryCapabilities::TAG_AUTO_TEST_RETRIES] = test_retries.auto_test_retries_feature_enabled.to_s
|
29
|
+
tags[Ext::Test::LibraryCapabilities::TAG_EARLY_FLAKE_DETECTION] = test_retries.early_flake_detection_feature_enabled.to_s
|
30
|
+
|
31
|
+
tags
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "drb"
|
3
4
|
require "rbconfig"
|
4
5
|
|
6
|
+
require "datadog/core/utils/forking"
|
7
|
+
|
5
8
|
require_relative "context"
|
6
9
|
require_relative "telemetry"
|
7
10
|
require_relative "total_coverage"
|
@@ -16,26 +19,49 @@ require_relative "../worker"
|
|
16
19
|
module Datadog
|
17
20
|
module CI
|
18
21
|
module TestVisibility
|
19
|
-
#
|
22
|
+
# Core functionality of the library: tracing tests' execution
|
20
23
|
class Component
|
21
|
-
|
24
|
+
include Core::Utils::Forking
|
25
|
+
|
26
|
+
attr_reader :test_suite_level_visibility_enabled, :logical_test_session_name,
|
27
|
+
:known_tests, :known_tests_enabled
|
22
28
|
|
23
29
|
def initialize(
|
30
|
+
known_tests_client:,
|
24
31
|
test_suite_level_visibility_enabled: false,
|
25
32
|
codeowners: Codeowners::Parser.new(Git::LocalRepository.root).parse,
|
26
33
|
logical_test_session_name: nil
|
27
34
|
)
|
28
35
|
@test_suite_level_visibility_enabled = test_suite_level_visibility_enabled
|
36
|
+
|
29
37
|
@context = Context.new
|
38
|
+
|
30
39
|
@codeowners = codeowners
|
31
40
|
@logical_test_session_name = logical_test_session_name
|
41
|
+
|
42
|
+
# "Known tests" feature fetches a list of all tests known to Datadog for this repository
|
43
|
+
# and uses this list to determine if a test is new or not. New tests are marked with "test.is_new" tag.
|
44
|
+
@known_tests_enabled = false
|
45
|
+
@known_tests_client = known_tests_client
|
46
|
+
@known_tests = Set.new
|
32
47
|
end
|
33
48
|
|
34
|
-
def
|
49
|
+
def configure(library_configuration, test_session)
|
50
|
+
return unless test_suite_level_visibility_enabled
|
51
|
+
|
52
|
+
if library_configuration.known_tests_enabled?
|
53
|
+
@known_tests_enabled = true
|
54
|
+
fetch_known_tests(test_session)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def start_test_session(service: nil, tags: {}, estimated_total_tests_count: 0)
|
35
59
|
return skip_tracing unless test_suite_level_visibility_enabled
|
36
60
|
|
61
|
+
start_drb_service
|
62
|
+
|
37
63
|
test_session = @context.start_test_session(service: service, tags: tags)
|
38
|
-
test_session.
|
64
|
+
test_session.estimated_total_tests_count = estimated_total_tests_count
|
39
65
|
|
40
66
|
on_test_session_started(test_session)
|
41
67
|
test_session
|
@@ -52,14 +78,17 @@ module Datadog
|
|
52
78
|
def start_test_suite(test_suite_name, service: nil, tags: {})
|
53
79
|
return skip_tracing unless test_suite_level_visibility_enabled
|
54
80
|
|
55
|
-
test_suite =
|
81
|
+
test_suite = maybe_remote_context.start_test_suite(test_suite_name, service: service, tags: tags)
|
56
82
|
on_test_suite_started(test_suite)
|
57
83
|
test_suite
|
58
84
|
end
|
59
85
|
|
60
86
|
def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
|
87
|
+
test_suite = maybe_remote_context.active_test_suite(test_suite_name)
|
88
|
+
tags[Ext::Test::TAG_SUITE] ||= test_suite_name
|
89
|
+
|
61
90
|
if block
|
62
|
-
@context.trace_test(test_name,
|
91
|
+
@context.trace_test(test_name, test_suite, service: service, tags: tags) do |test|
|
63
92
|
subscribe_to_after_stop_event(test.tracer_span)
|
64
93
|
|
65
94
|
on_test_started(test)
|
@@ -68,7 +97,7 @@ module Datadog
|
|
68
97
|
res
|
69
98
|
end
|
70
99
|
else
|
71
|
-
test = @context.trace_test(test_name,
|
100
|
+
test = @context.trace_test(test_name, test_suite, service: service, tags: tags)
|
72
101
|
subscribe_to_after_stop_event(test.tracer_span)
|
73
102
|
on_test_started(test)
|
74
103
|
test
|
@@ -102,7 +131,7 @@ module Datadog
|
|
102
131
|
end
|
103
132
|
|
104
133
|
def active_test_suite(test_suite_name)
|
105
|
-
|
134
|
+
maybe_remote_context.active_test_suite(test_suite_name)
|
106
135
|
end
|
107
136
|
|
108
137
|
def deactivate_test
|
@@ -130,7 +159,15 @@ module Datadog
|
|
130
159
|
test_suite = active_test_suite(test_suite_name)
|
131
160
|
on_test_suite_finished(test_suite) if test_suite
|
132
161
|
|
133
|
-
|
162
|
+
maybe_remote_context.deactivate_test_suite(test_suite_name)
|
163
|
+
end
|
164
|
+
|
165
|
+
def total_tests_count
|
166
|
+
maybe_remote_context.total_tests_count
|
167
|
+
end
|
168
|
+
|
169
|
+
def tests_skipped_by_tia_count
|
170
|
+
maybe_remote_context.tests_skipped_by_tia_count
|
134
171
|
end
|
135
172
|
|
136
173
|
def itr_enabled?
|
@@ -158,7 +195,8 @@ module Datadog
|
|
158
195
|
# sets logical test session name if none provided by the user
|
159
196
|
override_logical_test_session_name!(test_session) if logical_test_session_name.nil?
|
160
197
|
|
161
|
-
#
|
198
|
+
# Signal Remote::Component to configure the library.
|
199
|
+
# Note that it will call this component back (unfortunate circular dependency).
|
162
200
|
remote.configure(test_session)
|
163
201
|
end
|
164
202
|
|
@@ -173,6 +211,8 @@ module Datadog
|
|
173
211
|
end
|
174
212
|
|
175
213
|
def on_test_started(test)
|
214
|
+
maybe_remote_context.incr_total_tests_count
|
215
|
+
|
176
216
|
# sometimes test suite is not being assigned correctly
|
177
217
|
# fix it by fetching the one single running test suite from the global context
|
178
218
|
fix_test_suite!(test) if test.test_suite_id.nil?
|
@@ -182,12 +222,16 @@ module Datadog
|
|
182
222
|
|
183
223
|
Telemetry.event_created(test)
|
184
224
|
|
225
|
+
mark_test_as_new(test) if new_test?(test)
|
226
|
+
|
227
|
+
test_management.tag_test_from_properties(test)
|
228
|
+
|
185
229
|
test_optimisation.mark_if_skippable(test)
|
186
230
|
test_optimisation.start_coverage(test)
|
187
231
|
end
|
188
232
|
|
189
233
|
def on_test_session_finished(test_session)
|
190
|
-
test_optimisation.write_test_session_tags(test_session)
|
234
|
+
test_optimisation.write_test_session_tags(test_session, maybe_remote_context.tests_skipped_by_tia_count)
|
191
235
|
|
192
236
|
TotalCoverage.extract_lines_pct(test_session)
|
193
237
|
|
@@ -195,6 +239,8 @@ module Datadog
|
|
195
239
|
end
|
196
240
|
|
197
241
|
def on_test_module_finished(test_module)
|
242
|
+
@context.stop_all_test_suites
|
243
|
+
|
198
244
|
Telemetry.event_finished(test_module)
|
199
245
|
end
|
200
246
|
|
@@ -204,7 +250,7 @@ module Datadog
|
|
204
250
|
|
205
251
|
def on_test_finished(test)
|
206
252
|
test_optimisation.stop_coverage(test)
|
207
|
-
test_optimisation.
|
253
|
+
test_optimisation.on_test_finished(test, maybe_remote_context)
|
208
254
|
|
209
255
|
Telemetry.event_finished(test)
|
210
256
|
|
@@ -237,7 +283,7 @@ module Datadog
|
|
237
283
|
def fix_test_suite!(test)
|
238
284
|
return unless test_suite_level_visibility_enabled
|
239
285
|
|
240
|
-
test_suite =
|
286
|
+
test_suite = maybe_remote_context.single_active_test_suite
|
241
287
|
unless test_suite
|
242
288
|
Datadog.logger.debug do
|
243
289
|
"Trying to fix test suite for test [#{test.name}] but no single test suite is running."
|
@@ -287,6 +333,49 @@ module Datadog
|
|
287
333
|
end
|
288
334
|
end
|
289
335
|
|
336
|
+
def new_test?(test_span)
|
337
|
+
return false unless @known_tests_enabled
|
338
|
+
|
339
|
+
test_id = Utils::TestRun.datadog_test_id(test_span.name, test_span.test_suite_name)
|
340
|
+
result = !@known_tests.include?(test_id)
|
341
|
+
|
342
|
+
if result
|
343
|
+
Datadog.logger.debug do
|
344
|
+
"#{test_id} is not found in the known tests set, it is a new test"
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
result
|
349
|
+
end
|
350
|
+
|
351
|
+
def fetch_known_tests(test_session)
|
352
|
+
@known_tests = @known_tests_client.fetch(test_session)
|
353
|
+
|
354
|
+
if @known_tests.empty?
|
355
|
+
@known_tests_enabled = false
|
356
|
+
|
357
|
+
# this adds unfortunate knowledge on EFD from Testvisibility, rethink this
|
358
|
+
test_session&.set_tag(Ext::Test::TAG_EARLY_FLAKE_ABORT_REASON, Ext::Test::EARLY_FLAKE_FAULTY)
|
359
|
+
|
360
|
+
Datadog.logger.warn("Empty set of tests known to Datadog")
|
361
|
+
end
|
362
|
+
|
363
|
+
# report how many known tests were found
|
364
|
+
Datadog.logger.debug do
|
365
|
+
"Found [#{@known_tests.size}] known tests"
|
366
|
+
end
|
367
|
+
Utils::Telemetry.distribution(
|
368
|
+
Ext::Telemetry::METRIC_KNOWN_TESTS_RESPONSE_TESTS,
|
369
|
+
@known_tests.size.to_f
|
370
|
+
)
|
371
|
+
|
372
|
+
@known_tests
|
373
|
+
end
|
374
|
+
|
375
|
+
def mark_test_as_new(test_span)
|
376
|
+
test_span.set_tag(Ext::Test::TAG_IS_NEW, "true")
|
377
|
+
end
|
378
|
+
|
290
379
|
def test_optimisation
|
291
380
|
Datadog.send(:components).test_optimisation
|
292
381
|
end
|
@@ -302,6 +391,31 @@ module Datadog
|
|
302
391
|
def remote
|
303
392
|
Datadog.send(:components).ci_remote
|
304
393
|
end
|
394
|
+
|
395
|
+
def test_management
|
396
|
+
Datadog.send(:components).test_management
|
397
|
+
end
|
398
|
+
|
399
|
+
# DISTRIBUTED RUBY CONTEXT
|
400
|
+
def start_drb_service
|
401
|
+
return if @context_service_uri
|
402
|
+
return if forked?
|
403
|
+
|
404
|
+
@context_service = DRb.start_service("drbunix:", @context)
|
405
|
+
@context_service_uri = @context_service.uri
|
406
|
+
end
|
407
|
+
|
408
|
+
# depending on whether we are in a forked process or not, returns either the global context or its DRbObject
|
409
|
+
def maybe_remote_context
|
410
|
+
return @context unless forked?
|
411
|
+
return @context_client if defined?(@context_client)
|
412
|
+
|
413
|
+
# once per fork we must stop the running DRb server that was copied from the parent process
|
414
|
+
# otherwise, client will be confused thinking it's server which leads to terrible bugs
|
415
|
+
@context_service.stop_service
|
416
|
+
|
417
|
+
@context_client = DRbObject.new_with_uri(@context_service_uri)
|
418
|
+
end
|
305
419
|
end
|
306
420
|
end
|
307
421
|
end
|
@@ -27,9 +27,16 @@ module Datadog
|
|
27
27
|
# Its responsibility includes building domain models for test visibility as well.
|
28
28
|
# Internally it uses Datadog::Tracing module to create spans.
|
29
29
|
class Context
|
30
|
+
attr_reader :total_tests_count, :tests_skipped_by_tia_count
|
31
|
+
|
30
32
|
def initialize
|
31
33
|
@local_context = Store::Local.new
|
32
34
|
@global_context = Store::Global.new
|
35
|
+
|
36
|
+
@mutex = Mutex.new
|
37
|
+
|
38
|
+
@total_tests_count = 0
|
39
|
+
@tests_skipped_by_tia_count = 0
|
33
40
|
end
|
34
41
|
|
35
42
|
def start_test_session(service: nil, tags: {})
|
@@ -66,17 +73,17 @@ module Datadog
|
|
66
73
|
tracer_span = start_datadog_tracer_span(
|
67
74
|
test_suite_name, build_tracing_span_options(service, Ext::AppTypes::TYPE_TEST_SUITE)
|
68
75
|
)
|
69
|
-
set_suite_context(tags,
|
76
|
+
set_suite_context(tags, test_suite: tracer_span)
|
70
77
|
|
71
78
|
build_test_suite(tracer_span, tags)
|
72
79
|
end
|
73
80
|
end
|
74
81
|
|
75
|
-
def trace_test(test_name,
|
82
|
+
def trace_test(test_name, test_suite, service: nil, tags: {}, &block)
|
76
83
|
set_inherited_globals(tags)
|
77
84
|
set_session_context(tags)
|
78
85
|
set_module_context(tags)
|
79
|
-
set_suite_context(tags,
|
86
|
+
set_suite_context(tags, test_suite: test_suite)
|
80
87
|
|
81
88
|
tags[Ext::Test::TAG_NAME] = test_name
|
82
89
|
tags[Ext::Test::TAG_TYPE] ||= Ext::Test::Type::TEST
|
@@ -148,6 +155,10 @@ module Datadog
|
|
148
155
|
@global_context.fetch_single_test_suite
|
149
156
|
end
|
150
157
|
|
158
|
+
def stop_all_test_suites
|
159
|
+
@global_context.stop_all_test_suites
|
160
|
+
end
|
161
|
+
|
151
162
|
def deactivate_test
|
152
163
|
@local_context.deactivate_test
|
153
164
|
end
|
@@ -164,6 +175,14 @@ module Datadog
|
|
164
175
|
@global_context.deactivate_test_suite!(test_suite_name)
|
165
176
|
end
|
166
177
|
|
178
|
+
def incr_total_tests_count
|
179
|
+
@mutex.synchronize { @total_tests_count += 1 }
|
180
|
+
end
|
181
|
+
|
182
|
+
def incr_tests_skipped_by_tia_count
|
183
|
+
@mutex.synchronize { @tests_skipped_by_tia_count += 1 }
|
184
|
+
end
|
185
|
+
|
167
186
|
private
|
168
187
|
|
169
188
|
# BUILDING DOMAIN MODELS
|
@@ -208,10 +227,6 @@ module Datadog
|
|
208
227
|
ci_span.set_tags(@environment_tags)
|
209
228
|
|
210
229
|
ci_span.set_metric(Ext::Test::METRIC_CPU_COUNT, Utils::TestRun.virtual_cpu_count)
|
211
|
-
ci_span.set_tag(
|
212
|
-
Ext::Test::TAG_USER_PROVIDED_TEST_SERVICE,
|
213
|
-
Utils::Configuration.service_name_provided_by_user?.to_s
|
214
|
-
)
|
215
230
|
end
|
216
231
|
|
217
232
|
# PROPAGATING CONTEXT FROM TOP-LEVEL TO THE LOWER LEVELS
|
@@ -236,17 +251,11 @@ module Datadog
|
|
236
251
|
end
|
237
252
|
end
|
238
253
|
|
239
|
-
def set_suite_context(tags,
|
240
|
-
return if
|
254
|
+
def set_suite_context(tags, test_suite: nil)
|
255
|
+
return if test_suite.nil?
|
241
256
|
|
242
|
-
|
243
|
-
|
244
|
-
if test_suite
|
245
|
-
tags[Ext::Test::TAG_TEST_SUITE_ID] = test_suite.id.to_s
|
246
|
-
tags[Ext::Test::TAG_SUITE] = test_suite.name
|
247
|
-
else
|
248
|
-
tags[Ext::Test::TAG_SUITE] = name
|
249
|
-
end
|
257
|
+
tags[Ext::Test::TAG_TEST_SUITE_ID] = test_suite.id.to_s
|
258
|
+
tags[Ext::Test::TAG_SUITE] = test_suite.name
|
250
259
|
end
|
251
260
|
|
252
261
|
# INTERACTIONS WITH TRACING
|
@@ -10,9 +10,9 @@ require_relative "../utils/test_run"
|
|
10
10
|
|
11
11
|
module Datadog
|
12
12
|
module CI
|
13
|
-
module
|
14
|
-
#
|
15
|
-
class
|
13
|
+
module TestVisibility
|
14
|
+
# fetches and stores a list of known tests from the backend
|
15
|
+
class KnownTests
|
16
16
|
class Response
|
17
17
|
def initialize(http_response)
|
18
18
|
@http_response = http_response
|
@@ -66,7 +66,7 @@ module Datadog
|
|
66
66
|
@config_tags = config_tags
|
67
67
|
end
|
68
68
|
|
69
|
-
def
|
69
|
+
def fetch(test_session)
|
70
70
|
api = @api
|
71
71
|
return Set.new unless api
|
72
72
|
|
@@ -78,21 +78,21 @@ module Datadog
|
|
78
78
|
payload: request_payload
|
79
79
|
)
|
80
80
|
|
81
|
-
Transport::Telemetry.api_requests(
|
82
|
-
Ext::Telemetry::
|
81
|
+
CI::Transport::Telemetry.api_requests(
|
82
|
+
Ext::Telemetry::METRIC_KNOWN_TESTS_REQUEST,
|
83
83
|
1,
|
84
84
|
compressed: http_response.request_compressed
|
85
85
|
)
|
86
|
-
Utils::Telemetry.distribution(Ext::Telemetry::
|
86
|
+
Utils::Telemetry.distribution(Ext::Telemetry::METRIC_KNOWN_TESTS_REQUEST_MS, http_response.duration_ms)
|
87
87
|
Utils::Telemetry.distribution(
|
88
|
-
Ext::Telemetry::
|
88
|
+
Ext::Telemetry::METRIC_KNOWN_TESTS_RESPONSE_BYTES,
|
89
89
|
http_response.response_size.to_f,
|
90
90
|
{Ext::Telemetry::TAG_RESPONSE_COMPRESSED => http_response.gzipped_content?.to_s}
|
91
91
|
)
|
92
92
|
|
93
93
|
unless http_response.ok?
|
94
|
-
Transport::Telemetry.api_requests_errors(
|
95
|
-
Ext::Telemetry::
|
94
|
+
CI::Transport::Telemetry.api_requests_errors(
|
95
|
+
Ext::Telemetry::METRIC_KNOWN_TESTS_REQUEST_ERRORS,
|
96
96
|
1,
|
97
97
|
error_type: http_response.telemetry_error_type,
|
98
98
|
status_code: http_response.code
|
@@ -5,7 +5,10 @@ module Datadog
|
|
5
5
|
module TestVisibility
|
6
6
|
# Special test visibility component that does not record anything
|
7
7
|
class NullComponent
|
8
|
-
def
|
8
|
+
def configure(_, _)
|
9
|
+
end
|
10
|
+
|
11
|
+
def start_test_session(service: nil, tags: {}, estimated_total_tests_count: 0)
|
9
12
|
skip_tracing
|
10
13
|
end
|
11
14
|
|
@@ -9,7 +9,7 @@ module Datadog
|
|
9
9
|
module Serializers
|
10
10
|
module Factories
|
11
11
|
# This factory takes care of creating msgpack serializers when test-level visibility is enabled
|
12
|
-
# NOTE: citestcycle is a protocol Datadog uses to submit test execution tracing information to
|
12
|
+
# NOTE: citestcycle is a protocol Datadog uses to submit test execution tracing information to Test Optimization
|
13
13
|
# backend
|
14
14
|
module TestLevel
|
15
15
|
module_function
|