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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -2
  3. data/lib/datadog/ci/async_writer.rb +112 -0
  4. data/lib/datadog/ci/configuration/components.rb +36 -6
  5. data/lib/datadog/ci/configuration/settings.rb +17 -0
  6. data/lib/datadog/ci/contrib/activesupport/configuration/settings.rb +25 -0
  7. data/lib/datadog/ci/contrib/activesupport/ext.rb +14 -0
  8. data/lib/datadog/ci/contrib/activesupport/integration.rb +43 -0
  9. data/lib/datadog/ci/contrib/activesupport/logs_formatter.rb +41 -0
  10. data/lib/datadog/ci/contrib/activesupport/patcher.rb +50 -0
  11. data/lib/datadog/ci/contrib/lograge/configuration/settings.rb +25 -0
  12. data/lib/datadog/ci/contrib/lograge/ext.rb +14 -0
  13. data/lib/datadog/ci/contrib/lograge/integration.rb +43 -0
  14. data/lib/datadog/ci/contrib/lograge/log_subscriber.rb +41 -0
  15. data/lib/datadog/ci/contrib/lograge/patcher.rb +32 -0
  16. data/lib/datadog/ci/contrib/minitest/runner.rb +4 -1
  17. data/lib/datadog/ci/contrib/parallel_tests/cli.rb +84 -0
  18. data/lib/datadog/ci/contrib/parallel_tests/configuration/settings.rb +32 -0
  19. data/lib/datadog/ci/contrib/parallel_tests/ext.rb +16 -0
  20. data/lib/datadog/ci/contrib/parallel_tests/integration.rb +42 -0
  21. data/lib/datadog/ci/contrib/parallel_tests/patcher.rb +23 -0
  22. data/lib/datadog/ci/contrib/rspec/example.rb +7 -0
  23. data/lib/datadog/ci/contrib/rspec/example_group.rb +18 -8
  24. data/lib/datadog/ci/contrib/rspec/helpers.rb +18 -0
  25. data/lib/datadog/ci/contrib/rspec/runner.rb +2 -0
  26. data/lib/datadog/ci/contrib/semantic_logger/configuration/settings.rb +25 -0
  27. data/lib/datadog/ci/contrib/semantic_logger/ext.rb +14 -0
  28. data/lib/datadog/ci/contrib/semantic_logger/integration.rb +42 -0
  29. data/lib/datadog/ci/contrib/semantic_logger/logger.rb +32 -0
  30. data/lib/datadog/ci/contrib/semantic_logger/patcher.rb +32 -0
  31. data/lib/datadog/ci/ext/settings.rb +3 -0
  32. data/lib/datadog/ci/ext/test.rb +23 -2
  33. data/lib/datadog/ci/ext/transport.rb +2 -0
  34. data/lib/datadog/ci/git/local_repository.rb +1 -1
  35. data/lib/datadog/ci/git/tree_uploader.rb +9 -0
  36. data/lib/datadog/ci/logs/component.rb +46 -0
  37. data/lib/datadog/ci/logs/transport.rb +73 -0
  38. data/lib/datadog/ci/readonly_test_module.rb +28 -0
  39. data/lib/datadog/ci/readonly_test_session.rb +31 -0
  40. data/lib/datadog/ci/remote/component.rb +43 -16
  41. data/lib/datadog/ci/test.rb +10 -0
  42. data/lib/datadog/ci/test_management/component.rb +34 -1
  43. data/lib/datadog/ci/test_management/tests_properties.rb +2 -1
  44. data/lib/datadog/ci/test_optimisation/component.rb +31 -5
  45. data/lib/datadog/ci/test_retries/driver/retry_new.rb +1 -1
  46. data/lib/datadog/ci/test_session.rb +7 -1
  47. data/lib/datadog/ci/test_visibility/component.rb +82 -28
  48. data/lib/datadog/ci/test_visibility/context.rb +77 -29
  49. data/lib/datadog/ci/test_visibility/null_component.rb +7 -1
  50. data/lib/datadog/ci/test_visibility/store/{local.rb → fiber_local.rb} +1 -1
  51. data/lib/datadog/ci/test_visibility/store/{global.rb → process.rb} +23 -18
  52. data/lib/datadog/ci/test_visibility/transport.rb +1 -2
  53. data/lib/datadog/ci/transport/api/agentless.rb +8 -1
  54. data/lib/datadog/ci/transport/api/base.rb +4 -0
  55. data/lib/datadog/ci/transport/api/builder.rb +5 -1
  56. data/lib/datadog/ci/transport/api/evp_proxy.rb +4 -0
  57. data/lib/datadog/ci/transport/http.rb +1 -1
  58. data/lib/datadog/ci/utils/file_storage.rb +57 -0
  59. data/lib/datadog/ci/utils/stateful.rb +52 -0
  60. data/lib/datadog/ci/version.rb +1 -1
  61. data/lib/datadog/ci.rb +7 -3
  62. metadata +32 -6
  63. data/lib/datadog/ci/test_optimisation/coverage/writer.rb +0 -116
  64. 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
- library_configuration = @library_settings_client.fetch(test_session)
18
- # sometimes we can skip code coverage for default branch if there are no changes in the repository
19
- # backend needs git metadata uploaded for this test session to check if we can skip code coverage
20
- if library_configuration.require_git?
21
- Datadog.logger.debug { "Library configuration endpoint requires git upload to be finished, waiting..." }
22
- git_tree_upload_worker.wait_until_done
23
-
24
- Datadog.logger.debug { "Requesting library configuration again..." }
25
- library_configuration = @library_settings_client.fetch(test_session)
26
-
27
- if library_configuration.require_git?
28
- Datadog.logger.debug { "git metadata upload did not complete in time when configuring library" }
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
@@ -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
- @tests_properties = @tests_properties_client.fetch(test_session)
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
- Datadog.logger.debug("Configured TestOptimisation with enabled: #{@enabled}, skipping_tests: #{@test_skipping_enabled}, code_coverage: #{@code_coverage_enabled}")
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
- fetch_skippable_tests(test_session)
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?(test)
150
+ def skippable?(datadog_test_id)
142
151
  return false if !enabled? || !skipping_tests?
143
152
 
144
- @skippable_tests.include?(test.datadog_test_id)
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)
@@ -36,7 +36,7 @@ module Datadog
36
36
  end
37
37
 
38
38
  def retry_reason
39
- Ext::Test::RetryReason::RETRY_NEW
39
+ Ext::Test::RetryReason::RETRY_DETECT_FLAKY
40
40
  end
41
41
  end
42
42
  end
@@ -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
- if library_configuration.known_tests_enabled?
53
- @known_tests_enabled = true
54
- fetch_known_tests(test_session)
55
- end
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 = @context.start_test_session(service: service, tags: tags)
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 = @context.start_test_module(test_module_name, service: service, tags: tags)
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
- test_suite = maybe_remote_context.start_test_suite(test_suite_name, service: service, tags: tags)
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 = maybe_remote_context.active_test_suite(test_suite_name)
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
- @context.active_test_session
157
+ maybe_remote_context.active_test_session
127
158
  end
128
159
 
129
160
  def active_test_module
130
- @context.active_test_module
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
- maybe_remote_context.deactivate_test_suite(test_suite_name)
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
- # sometimes test suite is not being assigned correctly
217
- # fix it by fetching the one single running test suite from the global context
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 forked?
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 forked?
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.stop_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