datadog-ci 1.14.0 → 1.15.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -2
  3. data/lib/datadog/ci/configuration/components.rb +2 -1
  4. data/lib/datadog/ci/configuration/settings.rb +6 -0
  5. data/lib/datadog/ci/contrib/minitest/runner.rb +4 -1
  6. data/lib/datadog/ci/contrib/parallel_tests/cli.rb +84 -0
  7. data/lib/datadog/ci/contrib/parallel_tests/configuration/settings.rb +32 -0
  8. data/lib/datadog/ci/contrib/parallel_tests/ext.rb +16 -0
  9. data/lib/datadog/ci/contrib/parallel_tests/integration.rb +42 -0
  10. data/lib/datadog/ci/contrib/parallel_tests/patcher.rb +24 -0
  11. data/lib/datadog/ci/contrib/rspec/example.rb +7 -0
  12. data/lib/datadog/ci/contrib/rspec/example_group.rb +18 -8
  13. data/lib/datadog/ci/contrib/rspec/helpers.rb +18 -0
  14. data/lib/datadog/ci/contrib/rspec/runner.rb +2 -0
  15. data/lib/datadog/ci/ext/settings.rb +1 -0
  16. data/lib/datadog/ci/ext/test.rb +20 -0
  17. data/lib/datadog/ci/git/local_repository.rb +1 -1
  18. data/lib/datadog/ci/git/tree_uploader.rb +9 -0
  19. data/lib/datadog/ci/readonly_test_module.rb +28 -0
  20. data/lib/datadog/ci/readonly_test_session.rb +31 -0
  21. data/lib/datadog/ci/remote/component.rb +43 -16
  22. data/lib/datadog/ci/test_management/component.rb +34 -1
  23. data/lib/datadog/ci/test_management/tests_properties.rb +2 -1
  24. data/lib/datadog/ci/test_optimisation/component.rb +31 -5
  25. data/lib/datadog/ci/test_session.rb +7 -1
  26. data/lib/datadog/ci/test_visibility/component.rb +82 -28
  27. data/lib/datadog/ci/test_visibility/context.rb +77 -29
  28. data/lib/datadog/ci/test_visibility/null_component.rb +4 -0
  29. data/lib/datadog/ci/test_visibility/store/{local.rb → fiber_local.rb} +1 -1
  30. data/lib/datadog/ci/test_visibility/store/{global.rb → process.rb} +23 -18
  31. data/lib/datadog/ci/test_visibility/transport.rb +1 -2
  32. data/lib/datadog/ci/transport/http.rb +1 -1
  33. data/lib/datadog/ci/utils/file_storage.rb +57 -0
  34. data/lib/datadog/ci/utils/stateful.rb +52 -0
  35. data/lib/datadog/ci/version.rb +1 -1
  36. data/lib/datadog/ci.rb +4 -3
  37. metadata +14 -5
  38. data/lib/datadog/ci/test_visibility/capabilities.rb +0 -36
@@ -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)
@@ -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
@@ -4,8 +4,9 @@ require "datadog/tracing"
4
4
  require "datadog/tracing/contrib/component"
5
5
  require "datadog/tracing/trace_digest"
6
6
 
7
- require_relative "store/global"
8
- require_relative "store/local"
7
+ require_relative "store/process"
8
+ require_relative "store/fiber_local"
9
+ require_relative "telemetry"
9
10
 
10
11
  require_relative "../ext/app_types"
11
12
  require_relative "../ext/environment"
@@ -29,9 +30,11 @@ module Datadog
29
30
  class Context
30
31
  attr_reader :total_tests_count, :tests_skipped_by_tia_count
31
32
 
32
- def initialize
33
- @local_context = Store::Local.new
34
- @global_context = Store::Global.new
33
+ def initialize(test_visibility_component:)
34
+ @test_visibility_component = test_visibility_component
35
+
36
+ @fiber_local_context = Store::FiberLocal.new
37
+ @process_context = Store::Process.new
35
38
 
36
39
  @mutex = Mutex.new
37
40
 
@@ -40,18 +43,23 @@ module Datadog
40
43
  end
41
44
 
42
45
  def start_test_session(service: nil, tags: {})
43
- @global_context.fetch_or_activate_test_session do
46
+ @process_context.fetch_or_activate_test_session do
44
47
  tracer_span = start_datadog_tracer_span(
45
48
  "test.session", build_tracing_span_options(service, Ext::AppTypes::TYPE_TEST_SESSION)
46
49
  )
47
50
  set_session_context(tags, tracer_span)
48
51
 
49
- build_test_session(tracer_span, tags)
52
+ test_session = build_test_session(tracer_span, tags)
53
+
54
+ Telemetry.test_session_started(test_session)
55
+ Telemetry.event_created(test_session)
56
+
57
+ test_session
50
58
  end
51
59
  end
52
60
 
53
61
  def start_test_module(test_module_name, service: nil, tags: {})
54
- @global_context.fetch_or_activate_test_module do
62
+ @process_context.fetch_or_activate_test_module do
55
63
  set_inherited_globals(tags)
56
64
  set_session_context(tags)
57
65
 
@@ -60,12 +68,16 @@ module Datadog
60
68
  )
61
69
  set_module_context(tags, tracer_span)
62
70
 
63
- build_test_module(tracer_span, tags)
71
+ test_module = build_test_module(tracer_span, tags)
72
+
73
+ Telemetry.event_created(test_module)
74
+
75
+ test_module
64
76
  end
65
77
  end
66
78
 
67
79
  def start_test_suite(test_suite_name, service: nil, tags: {})
68
- @global_context.fetch_or_activate_test_suite(test_suite_name) do
80
+ @process_context.fetch_or_activate_test_suite(test_suite_name) do
69
81
  set_inherited_globals(tags)
70
82
  set_session_context(tags)
71
83
  set_module_context(tags)
@@ -75,7 +87,11 @@ module Datadog
75
87
  )
76
88
  set_suite_context(tags, test_suite: tracer_span)
77
89
 
78
- build_test_suite(tracer_span, tags)
90
+ test_suite = build_test_suite(tracer_span, tags)
91
+
92
+ Telemetry.event_created(test_suite)
93
+
94
+ test_suite
79
95
  end
80
96
  end
81
97
 
@@ -100,14 +116,14 @@ module Datadog
100
116
  start_datadog_tracer_span(test_name, span_options) do |tracer_span|
101
117
  test = build_test(tracer_span, tags)
102
118
 
103
- @local_context.activate_test(test) do
119
+ @fiber_local_context.activate_test(test) do
104
120
  block.call(test)
105
121
  end
106
122
  end
107
123
  else
108
124
  tracer_span = start_datadog_tracer_span(test_name, span_options)
109
125
  test = build_test(tracer_span, tags)
110
- @local_context.activate_test(test)
126
+ @fiber_local_context.activate_test(test)
111
127
  test
112
128
  end
113
129
  end
@@ -136,43 +152,43 @@ module Datadog
136
152
  end
137
153
 
138
154
  def active_test
139
- @local_context.active_test
155
+ @fiber_local_context.active_test
140
156
  end
141
157
 
142
158
  def active_test_session
143
- @global_context.active_test_session
159
+ @process_context.active_test_session
144
160
  end
145
161
 
146
162
  def active_test_module
147
- @global_context.active_test_module
163
+ @process_context.active_test_module
148
164
  end
149
165
 
150
166
  def active_test_suite(test_suite_name)
151
- @global_context.active_test_suite(test_suite_name)
167
+ @process_context.active_test_suite(test_suite_name)
152
168
  end
153
169
 
154
170
  def single_active_test_suite
155
- @global_context.fetch_single_test_suite
171
+ @process_context.fetch_single_test_suite
156
172
  end
157
173
 
158
174
  def stop_all_test_suites
159
- @global_context.stop_all_test_suites
175
+ @process_context.stop_all_test_suites
160
176
  end
161
177
 
162
178
  def deactivate_test
163
- @local_context.deactivate_test
179
+ @fiber_local_context.deactivate_test
164
180
  end
165
181
 
166
182
  def deactivate_test_session
167
- @global_context.deactivate_test_session!
183
+ @process_context.deactivate_test_session!
168
184
  end
169
185
 
170
186
  def deactivate_test_module
171
- @global_context.deactivate_test_module!
187
+ @process_context.deactivate_test_module!
172
188
  end
173
189
 
174
190
  def deactivate_test_suite(test_suite_name)
175
- @global_context.deactivate_test_suite!(test_suite_name)
191
+ @process_context.deactivate_test_suite!(test_suite_name)
176
192
  end
177
193
 
178
194
  def incr_total_tests_count
@@ -231,20 +247,21 @@ module Datadog
231
247
 
232
248
  # PROPAGATING CONTEXT FROM TOP-LEVEL TO THE LOWER LEVELS
233
249
  def set_inherited_globals(tags)
234
- # this code achieves the same as @global_context.inheritable_session_tags.merge(tags)
235
- # but without allocating a new hash
236
- @global_context.inheritable_session_tags.each do |key, value|
250
+ # Copy inheritable tags from the test session context to the provided tags
251
+ test_session_context&.inheritable_tags&.each do |key, value|
237
252
  tags[key] = value unless tags.key?(key)
238
253
  end
239
254
  end
240
255
 
241
256
  def set_session_context(tags, test_session = nil)
242
- test_session ||= active_test_session
257
+ # we need to call TestVisibility::Component here because active test session might be remote
258
+ test_session ||= test_session_context
243
259
  tags[Ext::Test::TAG_TEST_SESSION_ID] = test_session.id.to_s if test_session
244
260
  end
245
261
 
246
262
  def set_module_context(tags, test_module = nil)
247
- test_module ||= active_test_module
263
+ # we need to call TestVisibility::Component here because active test module might be remote
264
+ test_module ||= test_module_context
248
265
  if test_module
249
266
  tags[Ext::Test::TAG_TEST_MODULE_ID] = test_module.id.to_s
250
267
  tags[Ext::Test::TAG_MODULE] = test_module.name
@@ -281,11 +298,42 @@ module Datadog
281
298
  end
282
299
 
283
300
  def build_tracing_span_options(service, type, other_options = {})
284
- other_options[:service] = service || @global_context.service
301
+ other_options[:service] = service || test_session_context&.service
285
302
  other_options[:type] = type
286
303
 
287
304
  other_options
288
305
  end
306
+
307
+ # one of:
308
+ # 1. Currrent test session from the Store::Process
309
+ # 2. Readonly copy of the remote test session (if test session was started by a parent process and local copy was created)
310
+ # 3. Remote test session as DRb::DRbObject link (in this case also local copy will be created)
311
+ def test_session_context
312
+ local_test_session = @process_context.active_test_session
313
+ return local_test_session if local_test_session
314
+
315
+ local_readonly_test_session = @process_context.readonly_test_session
316
+ return local_readonly_test_session if local_readonly_test_session
317
+
318
+ remote_test_session = @test_visibility_component.active_test_session
319
+ @process_context.set_readonly_test_session(remote_test_session)
320
+
321
+ remote_test_session
322
+ end
323
+
324
+ # works similar to test_session_context
325
+ def test_module_context
326
+ local_test_module = @process_context.active_test_module
327
+ return local_test_module if local_test_module
328
+
329
+ local_readonly_test_module = @process_context.readonly_test_module
330
+ return local_readonly_test_module if local_readonly_test_module
331
+
332
+ remote_test_module = @test_visibility_component.active_test_module
333
+ @process_context.set_readonly_test_module(remote_test_module)
334
+
335
+ remote_test_module
336
+ end
289
337
  end
290
338
  end
291
339
  end
@@ -63,6 +63,10 @@ module Datadog
63
63
  def logical_test_session_name
64
64
  end
65
65
 
66
+ def client_process?
67
+ false
68
+ end
69
+
66
70
  private
67
71
 
68
72
  def skip_tracing(block = nil)
@@ -4,7 +4,7 @@ module Datadog
4
4
  module CI
5
5
  module TestVisibility
6
6
  module Store
7
- class Local
7
+ class FiberLocal
8
8
  def initialize
9
9
  @key = :datadog_ci_active_test
10
10