datadog-ci 1.14.0 → 1.16.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 +35 -2
- data/lib/datadog/ci/async_writer.rb +112 -0
- data/lib/datadog/ci/configuration/components.rb +36 -6
- data/lib/datadog/ci/configuration/settings.rb +17 -0
- data/lib/datadog/ci/contrib/activesupport/configuration/settings.rb +25 -0
- data/lib/datadog/ci/contrib/activesupport/ext.rb +14 -0
- data/lib/datadog/ci/contrib/activesupport/integration.rb +43 -0
- data/lib/datadog/ci/contrib/activesupport/logs_formatter.rb +41 -0
- data/lib/datadog/ci/contrib/activesupport/patcher.rb +50 -0
- data/lib/datadog/ci/contrib/lograge/configuration/settings.rb +25 -0
- data/lib/datadog/ci/contrib/lograge/ext.rb +14 -0
- data/lib/datadog/ci/contrib/lograge/integration.rb +43 -0
- data/lib/datadog/ci/contrib/lograge/log_subscriber.rb +41 -0
- data/lib/datadog/ci/contrib/lograge/patcher.rb +32 -0
- data/lib/datadog/ci/contrib/minitest/runner.rb +4 -1
- data/lib/datadog/ci/contrib/parallel_tests/cli.rb +84 -0
- data/lib/datadog/ci/contrib/parallel_tests/configuration/settings.rb +32 -0
- data/lib/datadog/ci/contrib/parallel_tests/ext.rb +16 -0
- data/lib/datadog/ci/contrib/parallel_tests/integration.rb +42 -0
- data/lib/datadog/ci/contrib/parallel_tests/patcher.rb +23 -0
- data/lib/datadog/ci/contrib/rspec/example.rb +7 -0
- data/lib/datadog/ci/contrib/rspec/example_group.rb +18 -8
- data/lib/datadog/ci/contrib/rspec/helpers.rb +18 -0
- data/lib/datadog/ci/contrib/rspec/runner.rb +2 -0
- data/lib/datadog/ci/contrib/semantic_logger/configuration/settings.rb +25 -0
- data/lib/datadog/ci/contrib/semantic_logger/ext.rb +14 -0
- data/lib/datadog/ci/contrib/semantic_logger/integration.rb +42 -0
- data/lib/datadog/ci/contrib/semantic_logger/logger.rb +32 -0
- data/lib/datadog/ci/contrib/semantic_logger/patcher.rb +32 -0
- data/lib/datadog/ci/ext/settings.rb +3 -0
- data/lib/datadog/ci/ext/test.rb +23 -2
- data/lib/datadog/ci/ext/transport.rb +2 -0
- data/lib/datadog/ci/git/local_repository.rb +1 -1
- data/lib/datadog/ci/git/tree_uploader.rb +9 -0
- data/lib/datadog/ci/logs/component.rb +46 -0
- data/lib/datadog/ci/logs/transport.rb +73 -0
- data/lib/datadog/ci/readonly_test_module.rb +28 -0
- data/lib/datadog/ci/readonly_test_session.rb +31 -0
- data/lib/datadog/ci/remote/component.rb +43 -16
- data/lib/datadog/ci/test.rb +10 -0
- data/lib/datadog/ci/test_management/component.rb +34 -1
- data/lib/datadog/ci/test_management/tests_properties.rb +2 -1
- data/lib/datadog/ci/test_optimisation/component.rb +31 -5
- data/lib/datadog/ci/test_retries/driver/retry_new.rb +1 -1
- data/lib/datadog/ci/test_session.rb +7 -1
- data/lib/datadog/ci/test_visibility/component.rb +82 -28
- data/lib/datadog/ci/test_visibility/context.rb +77 -29
- data/lib/datadog/ci/test_visibility/null_component.rb +7 -1
- data/lib/datadog/ci/test_visibility/store/{local.rb → fiber_local.rb} +1 -1
- data/lib/datadog/ci/test_visibility/store/{global.rb → process.rb} +23 -18
- data/lib/datadog/ci/test_visibility/transport.rb +1 -2
- data/lib/datadog/ci/transport/api/agentless.rb +8 -1
- data/lib/datadog/ci/transport/api/base.rb +4 -0
- data/lib/datadog/ci/transport/api/builder.rb +5 -1
- data/lib/datadog/ci/transport/api/evp_proxy.rb +4 -0
- data/lib/datadog/ci/transport/http.rb +1 -1
- data/lib/datadog/ci/utils/file_storage.rb +57 -0
- data/lib/datadog/ci/utils/stateful.rb +52 -0
- data/lib/datadog/ci/version.rb +1 -1
- data/lib/datadog/ci.rb +7 -3
- metadata +32 -6
- data/lib/datadog/ci/test_optimisation/coverage/writer.rb +0 -116
- data/lib/datadog/ci/test_visibility/capabilities.rb +0 -36
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "test_module"
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module CI
|
7
|
+
# @internal_api
|
8
|
+
class ReadonlyTestModule < TestModule
|
9
|
+
def initialize(test_module)
|
10
|
+
@id = test_module.id
|
11
|
+
@name = test_module.name
|
12
|
+
end
|
13
|
+
attr_reader :id, :name
|
14
|
+
|
15
|
+
def finish
|
16
|
+
raise "ReadonlyTestModule cannot be finished"
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_tag(key, value)
|
20
|
+
raise "ReadonlyTestModule cannot be modified"
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_metric(key, value)
|
24
|
+
raise "ReadonlyTestModule cannot be modified"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "test_session"
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module CI
|
7
|
+
# @internal_api
|
8
|
+
class ReadonlyTestSession < TestSession
|
9
|
+
def initialize(test_session)
|
10
|
+
@id = test_session.id
|
11
|
+
@name = test_session.name
|
12
|
+
@inheritable_tags = test_session.inheritable_tags
|
13
|
+
@service = test_session.service
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :id, :name, :inheritable_tags, :service
|
17
|
+
|
18
|
+
def finish
|
19
|
+
raise "ReadonlyTestSession cannot be finished"
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_tag(key, value)
|
23
|
+
raise "ReadonlyTestSession cannot be modified"
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_metric(key, value)
|
27
|
+
raise "ReadonlyTestSession cannot be modified"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "../worker"
|
4
|
+
require_relative "../utils/stateful"
|
4
5
|
|
5
6
|
module Datadog
|
6
7
|
module CI
|
@@ -8,33 +9,44 @@ module Datadog
|
|
8
9
|
# Remote configuration component.
|
9
10
|
# Responsible for fetching library settings and configuring the library accordingly.
|
10
11
|
class Component
|
12
|
+
include Datadog::CI::Utils::Stateful
|
13
|
+
|
14
|
+
FILE_STORAGE_KEY = "remote_component_state"
|
15
|
+
|
11
16
|
def initialize(library_settings_client:)
|
12
17
|
@library_settings_client = library_settings_client
|
18
|
+
@library_configuration = nil
|
13
19
|
end
|
14
20
|
|
15
21
|
# called on test session start, uses test session info to send configuration request to the backend
|
16
22
|
def configure(test_session)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
23
|
+
# If component state is loaded successfully, skip fetching library configuration
|
24
|
+
unless load_component_state
|
25
|
+
@library_configuration = @library_settings_client.fetch(test_session)
|
26
|
+
# sometimes we can skip code coverage for default branch if there are no changes in the repository
|
27
|
+
# backend needs git metadata uploaded for this test session to check if we can skip code coverage
|
28
|
+
if @library_configuration.require_git?
|
29
|
+
Datadog.logger.debug { "Library configuration endpoint requires git upload to be finished, waiting..." }
|
30
|
+
git_tree_upload_worker.wait_until_done
|
31
|
+
|
32
|
+
Datadog.logger.debug { "Requesting library configuration again..." }
|
33
|
+
@library_configuration = @library_settings_client.fetch(test_session)
|
34
|
+
|
35
|
+
if @library_configuration.require_git?
|
36
|
+
Datadog.logger.debug { "git metadata upload did not complete in time when configuring library" }
|
37
|
+
end
|
29
38
|
end
|
39
|
+
|
40
|
+
# Store component state for distributed test runs
|
41
|
+
store_component_state if test_session.distributed
|
30
42
|
end
|
31
43
|
|
32
44
|
# configure different components in parallel because they might block on HTTP requests
|
33
45
|
configuration_workers = [
|
34
|
-
Worker.new { test_optimisation.configure(library_configuration, test_session) },
|
35
|
-
Worker.new { test_retries.configure(library_configuration, test_session) },
|
36
|
-
Worker.new { test_visibility.configure(library_configuration, test_session) },
|
37
|
-
Worker.new { test_management.configure(library_configuration, test_session) }
|
46
|
+
Worker.new { test_optimisation.configure(@library_configuration, test_session) },
|
47
|
+
Worker.new { test_retries.configure(@library_configuration, test_session) },
|
48
|
+
Worker.new { test_visibility.configure(@library_configuration, test_session) },
|
49
|
+
Worker.new { test_management.configure(@library_configuration, test_session) }
|
38
50
|
]
|
39
51
|
|
40
52
|
# launch configuration workers
|
@@ -44,6 +56,21 @@ module Datadog
|
|
44
56
|
configuration_workers.each(&:wait_until_done)
|
45
57
|
end
|
46
58
|
|
59
|
+
# Implementation of Stateful interface
|
60
|
+
def serialize_state
|
61
|
+
{
|
62
|
+
library_configuration: @library_configuration
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def restore_state(state)
|
67
|
+
@library_configuration = state[:library_configuration]
|
68
|
+
end
|
69
|
+
|
70
|
+
def storage_key
|
71
|
+
FILE_STORAGE_KEY
|
72
|
+
end
|
73
|
+
|
47
74
|
private
|
48
75
|
|
49
76
|
def test_management
|
data/lib/datadog/ci/test.rb
CHANGED
@@ -25,6 +25,10 @@ module Datadog
|
|
25
25
|
# Finishes the current test.
|
26
26
|
# @return [void]
|
27
27
|
def finish
|
28
|
+
if is_retry? && retry_reason.nil?
|
29
|
+
set_tag(Ext::Test::TAG_RETRY_REASON, Ext::Test::RetryReason::RETRY_EXTERNAL)
|
30
|
+
end
|
31
|
+
|
28
32
|
test_visibility.deactivate_test
|
29
33
|
|
30
34
|
super
|
@@ -68,6 +72,12 @@ module Datadog
|
|
68
72
|
get_tag(Ext::Test::TAG_IS_RETRY) == "true"
|
69
73
|
end
|
70
74
|
|
75
|
+
# Returns string with a reason why test was retried
|
76
|
+
# @return [String] retry reason
|
77
|
+
def retry_reason
|
78
|
+
get_tag(Ext::Test::TAG_RETRY_REASON)
|
79
|
+
end
|
80
|
+
|
71
81
|
# Returns "true" if this span represents a test that wasn't known to Datadog before.
|
72
82
|
# @return [Boolean] true if this test is a new one, false otherwise.
|
73
83
|
def is_new?
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative "../ext/telemetry"
|
4
4
|
require_relative "../ext/test"
|
5
|
+
require_relative "../utils/stateful"
|
5
6
|
require_relative "../utils/telemetry"
|
6
7
|
require_relative "../utils/test_run"
|
7
8
|
|
@@ -14,6 +15,10 @@ module Datadog
|
|
14
15
|
# - marking test as disabled causes test to be skipped
|
15
16
|
# - marking test as "attempted to fix" causes test to be retried many times to confirm that fix worked
|
16
17
|
class Component
|
18
|
+
include Datadog::CI::Utils::Stateful
|
19
|
+
|
20
|
+
FILE_STORAGE_KEY = "test_management_component_state"
|
21
|
+
|
17
22
|
attr_reader :enabled, :tests_properties
|
18
23
|
|
19
24
|
def initialize(enabled:, tests_properties_client:)
|
@@ -30,7 +35,11 @@ module Datadog
|
|
30
35
|
|
31
36
|
test_session.set_tag(Ext::Test::TAG_TEST_MANAGEMENT_ENABLED, "true")
|
32
37
|
|
33
|
-
|
38
|
+
# Load component state first, and if successful, skip fetching tests properties
|
39
|
+
if !load_component_state
|
40
|
+
@tests_properties = @tests_properties_client.fetch(test_session)
|
41
|
+
store_component_state if test_session.distributed
|
42
|
+
end
|
34
43
|
|
35
44
|
Utils::Telemetry.distribution(
|
36
45
|
Ext::Telemetry::METRIC_TEST_MANAGEMENT_TESTS_RESPONSE_TESTS,
|
@@ -55,6 +64,30 @@ module Datadog
|
|
55
64
|
test_span.set_tag(Ext::Test::TAG_IS_TEST_DISABLED, "true") if test_properties["disabled"]
|
56
65
|
test_span.set_tag(Ext::Test::TAG_IS_ATTEMPT_TO_FIX, "true") if test_properties["attempt_to_fix"]
|
57
66
|
end
|
67
|
+
|
68
|
+
def attempt_to_fix?(datadog_fqn_test_id)
|
69
|
+
return false unless @enabled
|
70
|
+
|
71
|
+
test_properties = @tests_properties[datadog_fqn_test_id]
|
72
|
+
return false if test_properties.nil?
|
73
|
+
|
74
|
+
test_properties.fetch("attempt_to_fix", false)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Implementation of Stateful interface
|
78
|
+
def serialize_state
|
79
|
+
{
|
80
|
+
tests_properties: @tests_properties
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
def restore_state(state)
|
85
|
+
@tests_properties = state[:tests_properties]
|
86
|
+
end
|
87
|
+
|
88
|
+
def storage_key
|
89
|
+
FILE_STORAGE_KEY
|
90
|
+
end
|
58
91
|
end
|
59
92
|
end
|
60
93
|
end
|
@@ -117,7 +117,8 @@ module Datadog
|
|
117
117
|
"id" => Datadog::Core::Environment::Identity.id,
|
118
118
|
"type" => Ext::Transport::DD_API_TEST_MANAGEMENT_TESTS_TYPE,
|
119
119
|
"attributes" => {
|
120
|
-
"repository_url" => test_session.git_repository_url
|
120
|
+
"repository_url" => test_session.git_repository_url,
|
121
|
+
"commit_message" => test_session.git_commit_message
|
121
122
|
}
|
122
123
|
}
|
123
124
|
}.to_json
|
@@ -10,6 +10,7 @@ require_relative "../ext/telemetry"
|
|
10
10
|
require_relative "../git/local_repository"
|
11
11
|
|
12
12
|
require_relative "../utils/parsing"
|
13
|
+
require_relative "../utils/stateful"
|
13
14
|
require_relative "../utils/telemetry"
|
14
15
|
|
15
16
|
require_relative "coverage/event"
|
@@ -23,6 +24,10 @@ module Datadog
|
|
23
24
|
# Integrates with backend to provide test impact analysis data and
|
24
25
|
# skip tests that are not impacted by the changes
|
25
26
|
class Component
|
27
|
+
include Datadog::CI::Utils::Stateful
|
28
|
+
|
29
|
+
FILE_STORAGE_KEY = "test_optimisation_component_state"
|
30
|
+
|
26
31
|
attr_reader :correlation_id, :skippable_tests, :skippable_tests_fetch_error,
|
27
32
|
:enabled, :test_skipping_enabled, :code_coverage_enabled
|
28
33
|
|
@@ -78,9 +83,13 @@ module Datadog
|
|
78
83
|
|
79
84
|
load_datadog_cov! if @code_coverage_enabled
|
80
85
|
|
81
|
-
|
86
|
+
# Load component state first, and if successful, skip fetching skippable tests
|
87
|
+
if skipping_tests? && !load_component_state
|
88
|
+
fetch_skippable_tests(test_session)
|
89
|
+
store_component_state if test_session.distributed
|
90
|
+
end
|
82
91
|
|
83
|
-
|
92
|
+
Datadog.logger.debug("Configured TestOptimisation with enabled: #{@enabled}, skipping_tests: #{@test_skipping_enabled}, code_coverage: #{@code_coverage_enabled}")
|
84
93
|
end
|
85
94
|
|
86
95
|
def enabled?
|
@@ -138,16 +147,16 @@ module Datadog
|
|
138
147
|
event
|
139
148
|
end
|
140
149
|
|
141
|
-
def skippable?(
|
150
|
+
def skippable?(datadog_test_id)
|
142
151
|
return false if !enabled? || !skipping_tests?
|
143
152
|
|
144
|
-
@skippable_tests.include?(
|
153
|
+
@skippable_tests.include?(datadog_test_id)
|
145
154
|
end
|
146
155
|
|
147
156
|
def mark_if_skippable(test)
|
148
157
|
return if !enabled? || !skipping_tests?
|
149
158
|
|
150
|
-
if skippable?(test)
|
159
|
+
if skippable?(test.datadog_test_id) && !test.attempt_to_fix?
|
151
160
|
test.set_tag(Ext::Test::TAG_ITR_SKIPPED_BY_ITR, "true")
|
152
161
|
|
153
162
|
Datadog.logger.debug { "Marked test as skippable: #{test.datadog_test_id}" }
|
@@ -182,6 +191,23 @@ module Datadog
|
|
182
191
|
@coverage_writer&.stop
|
183
192
|
end
|
184
193
|
|
194
|
+
# Implementation of Stateful interface
|
195
|
+
def serialize_state
|
196
|
+
{
|
197
|
+
correlation_id: @correlation_id,
|
198
|
+
skippable_tests: @skippable_tests
|
199
|
+
}
|
200
|
+
end
|
201
|
+
|
202
|
+
def restore_state(state)
|
203
|
+
@correlation_id = state[:correlation_id]
|
204
|
+
@skippable_tests = state[:skippable_tests]
|
205
|
+
end
|
206
|
+
|
207
|
+
def storage_key
|
208
|
+
FILE_STORAGE_KEY
|
209
|
+
end
|
210
|
+
|
185
211
|
private
|
186
212
|
|
187
213
|
def write(event)
|
@@ -12,7 +12,7 @@ module Datadog
|
|
12
12
|
#
|
13
13
|
# @public_api
|
14
14
|
class TestSession < ConcurrentSpan
|
15
|
-
attr_accessor :estimated_total_tests_count
|
15
|
+
attr_accessor :estimated_total_tests_count, :distributed
|
16
16
|
|
17
17
|
# Finishes the current test session.
|
18
18
|
# @return [void]
|
@@ -46,6 +46,12 @@ module Datadog
|
|
46
46
|
get_tag(Ext::Environment::TAG_JOB_NAME)
|
47
47
|
end
|
48
48
|
|
49
|
+
# Returns the git commit message extracted from the environment.
|
50
|
+
# @return [String] the commit message.
|
51
|
+
def git_commit_message
|
52
|
+
get_tag(Ext::Git::TAG_COMMIT_MESSAGE)
|
53
|
+
end
|
54
|
+
|
49
55
|
def skipping_tests?
|
50
56
|
get_tag(Ext::Test::TAG_ITR_TEST_SKIPPING_ENABLED) == "true"
|
51
57
|
end
|
@@ -13,6 +13,8 @@ require_relative "../codeowners/parser"
|
|
13
13
|
require_relative "../contrib/instrumentation"
|
14
14
|
require_relative "../ext/test"
|
15
15
|
require_relative "../git/local_repository"
|
16
|
+
require_relative "../utils/file_storage"
|
17
|
+
require_relative "../utils/stateful"
|
16
18
|
|
17
19
|
require_relative "../worker"
|
18
20
|
|
@@ -22,19 +24,23 @@ module Datadog
|
|
22
24
|
# Core functionality of the library: tracing tests' execution
|
23
25
|
class Component
|
24
26
|
include Core::Utils::Forking
|
27
|
+
include Datadog::CI::Utils::Stateful
|
28
|
+
|
29
|
+
FILE_STORAGE_KEY = "test_visibility_component_state"
|
25
30
|
|
26
31
|
attr_reader :test_suite_level_visibility_enabled, :logical_test_session_name,
|
27
|
-
:known_tests, :known_tests_enabled
|
32
|
+
:known_tests, :known_tests_enabled, :context_service_uri, :local_test_suites_mode
|
28
33
|
|
29
34
|
def initialize(
|
30
35
|
known_tests_client:,
|
31
36
|
test_suite_level_visibility_enabled: false,
|
32
37
|
codeowners: Codeowners::Parser.new(Git::LocalRepository.root).parse,
|
33
|
-
logical_test_session_name: nil
|
38
|
+
logical_test_session_name: nil,
|
39
|
+
context_service_uri: nil
|
34
40
|
)
|
35
41
|
@test_suite_level_visibility_enabled = test_suite_level_visibility_enabled
|
36
42
|
|
37
|
-
@context = Context.new
|
43
|
+
@context = Context.new(test_visibility_component: self)
|
38
44
|
|
39
45
|
@codeowners = codeowners
|
40
46
|
@logical_test_session_name = logical_test_session_name
|
@@ -44,47 +50,72 @@ module Datadog
|
|
44
50
|
@known_tests_enabled = false
|
45
51
|
@known_tests_client = known_tests_client
|
46
52
|
@known_tests = Set.new
|
53
|
+
|
54
|
+
# this is used for parallel test runners such as parallel_tests
|
55
|
+
if context_service_uri
|
56
|
+
@context_service_uri = context_service_uri
|
57
|
+
@is_client_process = true
|
58
|
+
end
|
59
|
+
|
60
|
+
# This is used for parallel test runners such as parallel_tests.
|
61
|
+
# If true, then test suites are created in the worker process, not the parent.
|
62
|
+
#
|
63
|
+
# The only test runner that requires creating test suites in the remote process is rails test runner because
|
64
|
+
# it splits workload by test, not by test suite.
|
65
|
+
#
|
66
|
+
# Another test runner that splits workload by test is knapsack_pro, but we lack distributed test sessions/test suties
|
67
|
+
# support for that one (as of 2025-03).
|
68
|
+
@local_test_suites_mode = true
|
47
69
|
end
|
48
70
|
|
49
71
|
def configure(library_configuration, test_session)
|
50
72
|
return unless test_suite_level_visibility_enabled
|
73
|
+
return unless library_configuration.known_tests_enabled?
|
51
74
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
75
|
+
@known_tests_enabled = true
|
76
|
+
return if load_component_state
|
77
|
+
|
78
|
+
fetch_known_tests(test_session)
|
79
|
+
store_component_state if test_session.distributed
|
56
80
|
end
|
57
81
|
|
58
|
-
def start_test_session(service: nil, tags: {}, estimated_total_tests_count: 0)
|
82
|
+
def start_test_session(service: nil, tags: {}, estimated_total_tests_count: 0, distributed: false, local_test_suites_mode: true)
|
59
83
|
return skip_tracing unless test_suite_level_visibility_enabled
|
60
84
|
|
85
|
+
@local_test_suites_mode = local_test_suites_mode
|
86
|
+
|
61
87
|
start_drb_service
|
62
88
|
|
63
|
-
test_session =
|
89
|
+
test_session = maybe_remote_context.start_test_session(service: service, tags: tags)
|
64
90
|
test_session.estimated_total_tests_count = estimated_total_tests_count
|
91
|
+
test_session.distributed = distributed
|
65
92
|
|
66
93
|
on_test_session_started(test_session)
|
94
|
+
|
67
95
|
test_session
|
68
96
|
end
|
69
97
|
|
70
98
|
def start_test_module(test_module_name, service: nil, tags: {})
|
71
99
|
return skip_tracing unless test_suite_level_visibility_enabled
|
72
100
|
|
73
|
-
test_module =
|
101
|
+
test_module = maybe_remote_context.start_test_module(test_module_name, service: service, tags: tags)
|
74
102
|
on_test_module_started(test_module)
|
103
|
+
|
75
104
|
test_module
|
76
105
|
end
|
77
106
|
|
78
107
|
def start_test_suite(test_suite_name, service: nil, tags: {})
|
79
108
|
return skip_tracing unless test_suite_level_visibility_enabled
|
80
109
|
|
81
|
-
|
110
|
+
context = @local_test_suites_mode ? @context : maybe_remote_context
|
111
|
+
|
112
|
+
test_suite = context.start_test_suite(test_suite_name, service: service, tags: tags)
|
82
113
|
on_test_suite_started(test_suite)
|
83
114
|
test_suite
|
84
115
|
end
|
85
116
|
|
86
117
|
def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
|
87
|
-
test_suite =
|
118
|
+
test_suite = active_test_suite(test_suite_name)
|
88
119
|
tags[Ext::Test::TAG_SUITE] ||= test_suite_name
|
89
120
|
|
90
121
|
if block
|
@@ -123,14 +154,18 @@ module Datadog
|
|
123
154
|
end
|
124
155
|
|
125
156
|
def active_test_session
|
126
|
-
|
157
|
+
maybe_remote_context.active_test_session
|
127
158
|
end
|
128
159
|
|
129
160
|
def active_test_module
|
130
|
-
|
161
|
+
maybe_remote_context.active_test_module
|
131
162
|
end
|
132
163
|
|
133
164
|
def active_test_suite(test_suite_name)
|
165
|
+
# when fetching test_suite to use as test's context, try local context instance first
|
166
|
+
local_test_suite = @context.active_test_suite(test_suite_name)
|
167
|
+
return local_test_suite if local_test_suite
|
168
|
+
|
134
169
|
maybe_remote_context.active_test_suite(test_suite_name)
|
135
170
|
end
|
136
171
|
|
@@ -159,7 +194,8 @@ module Datadog
|
|
159
194
|
test_suite = active_test_suite(test_suite_name)
|
160
195
|
on_test_suite_finished(test_suite) if test_suite
|
161
196
|
|
162
|
-
|
197
|
+
# deactivation always happens on the same process where test suite is located
|
198
|
+
@context.deactivate_test_suite(test_suite_name)
|
163
199
|
end
|
164
200
|
|
165
201
|
def total_tests_count
|
@@ -178,6 +214,10 @@ module Datadog
|
|
178
214
|
# noop, there is no thread owned by test visibility component
|
179
215
|
end
|
180
216
|
|
217
|
+
def client_process?
|
218
|
+
forked? || @is_client_process
|
219
|
+
end
|
220
|
+
|
181
221
|
private
|
182
222
|
|
183
223
|
# DOMAIN EVENTS
|
@@ -188,10 +228,6 @@ module Datadog
|
|
188
228
|
# finds and instruments additional test libraries that we support (ex: selenium-webdriver)
|
189
229
|
Contrib::Instrumentation.instrument_on_session_start
|
190
230
|
|
191
|
-
# sends internal telemetry events
|
192
|
-
Telemetry.test_session_started(test_session)
|
193
|
-
Telemetry.event_created(test_session)
|
194
|
-
|
195
231
|
# sets logical test session name if none provided by the user
|
196
232
|
override_logical_test_session_name!(test_session) if logical_test_session_name.nil?
|
197
233
|
|
@@ -200,21 +236,22 @@ module Datadog
|
|
200
236
|
remote.configure(test_session)
|
201
237
|
end
|
202
238
|
|
239
|
+
# intentionally empty
|
203
240
|
def on_test_module_started(test_module)
|
204
|
-
Telemetry.event_created(test_module)
|
205
241
|
end
|
206
242
|
|
207
243
|
def on_test_suite_started(test_suite)
|
208
244
|
set_codeowners(test_suite)
|
209
|
-
|
210
|
-
Telemetry.event_created(test_suite)
|
211
245
|
end
|
212
246
|
|
213
247
|
def on_test_started(test)
|
214
248
|
maybe_remote_context.incr_total_tests_count
|
215
249
|
|
216
|
-
#
|
217
|
-
#
|
250
|
+
# Sometimes test suite is not being assigned correctly.
|
251
|
+
# Fix it by fetching the one single running test suite from the process context.
|
252
|
+
#
|
253
|
+
# This is a hack to fix some edge cases that come from some minitest plugins,
|
254
|
+
# especially thoughtbot/shoulda-context.
|
218
255
|
fix_test_suite!(test) if test.test_suite_id.nil?
|
219
256
|
validate_test_suite_level_visibility_correctness(test)
|
220
257
|
|
@@ -236,6 +273,8 @@ module Datadog
|
|
236
273
|
TotalCoverage.extract_lines_pct(test_session)
|
237
274
|
|
238
275
|
Telemetry.event_finished(test_session)
|
276
|
+
|
277
|
+
Utils::FileStorage.cleanup
|
239
278
|
end
|
240
279
|
|
241
280
|
def on_test_module_finished(test_module)
|
@@ -399,7 +438,7 @@ module Datadog
|
|
399
438
|
# DISTRIBUTED RUBY CONTEXT
|
400
439
|
def start_drb_service
|
401
440
|
return if @context_service_uri
|
402
|
-
return if
|
441
|
+
return if client_process?
|
403
442
|
|
404
443
|
@context_service = DRb.start_service("drbunix:", @context)
|
405
444
|
@context_service_uri = @context_service.uri
|
@@ -407,15 +446,30 @@ module Datadog
|
|
407
446
|
|
408
447
|
# depending on whether we are in a forked process or not, returns either the global context or its DRbObject
|
409
448
|
def maybe_remote_context
|
410
|
-
return @context unless
|
449
|
+
return @context unless client_process?
|
411
450
|
return @context_client if defined?(@context_client)
|
412
451
|
|
413
|
-
# once per fork we must stop the running DRb server that was copied from the parent process
|
452
|
+
# at least once per fork we must stop the running DRb server that was copied from the parent process
|
414
453
|
# otherwise, client will be confused thinking it's server which leads to terrible bugs
|
415
|
-
@context_service
|
454
|
+
@context_service&.stop_service
|
416
455
|
|
417
456
|
@context_client = DRbObject.new_with_uri(@context_service_uri)
|
418
457
|
end
|
458
|
+
|
459
|
+
# Implementation of Stateful interface
|
460
|
+
def serialize_state
|
461
|
+
{
|
462
|
+
known_tests: @known_tests
|
463
|
+
}
|
464
|
+
end
|
465
|
+
|
466
|
+
def restore_state(state)
|
467
|
+
@known_tests = state[:known_tests]
|
468
|
+
end
|
469
|
+
|
470
|
+
def storage_key
|
471
|
+
FILE_STORAGE_KEY
|
472
|
+
end
|
419
473
|
end
|
420
474
|
end
|
421
475
|
end
|