datadog-ci 1.13.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -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/helpers.rb +26 -0
  6. data/lib/datadog/ci/contrib/minitest/runnable.rb +1 -19
  7. data/lib/datadog/ci/contrib/minitest/runner.rb +12 -5
  8. data/lib/datadog/ci/contrib/minitest/test.rb +2 -9
  9. data/lib/datadog/ci/contrib/parallel_tests/cli.rb +84 -0
  10. data/lib/datadog/ci/contrib/parallel_tests/configuration/settings.rb +32 -0
  11. data/lib/datadog/ci/contrib/parallel_tests/ext.rb +16 -0
  12. data/lib/datadog/ci/contrib/parallel_tests/integration.rb +42 -0
  13. data/lib/datadog/ci/contrib/parallel_tests/patcher.rb +24 -0
  14. data/lib/datadog/ci/contrib/rspec/example.rb +7 -0
  15. data/lib/datadog/ci/contrib/rspec/example_group.rb +18 -8
  16. data/lib/datadog/ci/contrib/rspec/helpers.rb +18 -0
  17. data/lib/datadog/ci/contrib/rspec/runner.rb +3 -1
  18. data/lib/datadog/ci/ext/settings.rb +1 -0
  19. data/lib/datadog/ci/ext/test.rb +20 -0
  20. data/lib/datadog/ci/git/local_repository.rb +1 -1
  21. data/lib/datadog/ci/git/tree_uploader.rb +9 -0
  22. data/lib/datadog/ci/readonly_test_module.rb +28 -0
  23. data/lib/datadog/ci/readonly_test_session.rb +31 -0
  24. data/lib/datadog/ci/remote/component.rb +43 -16
  25. data/lib/datadog/ci/span.rb +4 -0
  26. data/lib/datadog/ci/test_management/component.rb +34 -1
  27. data/lib/datadog/ci/test_management/tests_properties.rb +2 -1
  28. data/lib/datadog/ci/test_optimisation/component.rb +38 -33
  29. data/lib/datadog/ci/test_optimisation/skippable_percentage/base.rb +4 -0
  30. data/lib/datadog/ci/test_optimisation/skippable_percentage/calculator.rb +3 -3
  31. data/lib/datadog/ci/test_retries/strategy/retry_new.rb +1 -1
  32. data/lib/datadog/ci/test_session.rb +7 -1
  33. data/lib/datadog/ci/test_suite.rb +18 -0
  34. data/lib/datadog/ci/test_visibility/component.rb +130 -30
  35. data/lib/datadog/ci/test_visibility/context.rb +102 -41
  36. data/lib/datadog/ci/test_visibility/null_component.rb +5 -1
  37. data/lib/datadog/ci/test_visibility/store/{local.rb → fiber_local.rb} +1 -1
  38. data/lib/datadog/ci/test_visibility/store/{global.rb → process.rb} +26 -14
  39. data/lib/datadog/ci/test_visibility/transport.rb +1 -2
  40. data/lib/datadog/ci/transport/http.rb +1 -1
  41. data/lib/datadog/ci/utils/file_storage.rb +57 -0
  42. data/lib/datadog/ci/utils/stateful.rb +52 -0
  43. data/lib/datadog/ci/version.rb +1 -1
  44. data/lib/datadog/ci.rb +5 -4
  45. metadata +28 -5
  46. data/lib/datadog/ci/test_visibility/capabilities.rb +0 -36
@@ -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"
@@ -10,24 +13,35 @@ require_relative "../codeowners/parser"
10
13
  require_relative "../contrib/instrumentation"
11
14
  require_relative "../ext/test"
12
15
  require_relative "../git/local_repository"
16
+ require_relative "../utils/file_storage"
17
+ require_relative "../utils/stateful"
13
18
 
14
19
  require_relative "../worker"
15
20
 
16
21
  module Datadog
17
22
  module CI
18
23
  module TestVisibility
19
- # Common behavior for CI tests
24
+ # Core functionality of the library: tracing tests' execution
20
25
  class Component
21
- attr_reader :test_suite_level_visibility_enabled, :logical_test_session_name, :known_tests, :known_tests_enabled
26
+ include Core::Utils::Forking
27
+ include Datadog::CI::Utils::Stateful
28
+
29
+ FILE_STORAGE_KEY = "test_visibility_component_state"
30
+
31
+ attr_reader :test_suite_level_visibility_enabled, :logical_test_session_name,
32
+ :known_tests, :known_tests_enabled, :context_service_uri, :local_test_suites_mode
22
33
 
23
34
  def initialize(
24
35
  known_tests_client:,
25
36
  test_suite_level_visibility_enabled: false,
26
37
  codeowners: Codeowners::Parser.new(Git::LocalRepository.root).parse,
27
- logical_test_session_name: nil
38
+ logical_test_session_name: nil,
39
+ context_service_uri: nil
28
40
  )
29
41
  @test_suite_level_visibility_enabled = test_suite_level_visibility_enabled
30
- @context = Context.new
42
+
43
+ @context = Context.new(test_visibility_component: self)
44
+
31
45
  @codeowners = codeowners
32
46
  @logical_test_session_name = logical_test_session_name
33
47
 
@@ -36,46 +50,76 @@ module Datadog
36
50
  @known_tests_enabled = false
37
51
  @known_tests_client = known_tests_client
38
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
39
69
  end
40
70
 
41
71
  def configure(library_configuration, test_session)
42
72
  return unless test_suite_level_visibility_enabled
73
+ return unless library_configuration.known_tests_enabled?
43
74
 
44
- if library_configuration.known_tests_enabled?
45
- @known_tests_enabled = true
46
- fetch_known_tests(test_session)
47
- 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
48
80
  end
49
81
 
50
- def start_test_session(service: nil, tags: {}, total_tests_count: 0)
82
+ def start_test_session(service: nil, tags: {}, estimated_total_tests_count: 0, distributed: false, local_test_suites_mode: true)
51
83
  return skip_tracing unless test_suite_level_visibility_enabled
52
84
 
53
- test_session = @context.start_test_session(service: service, tags: tags)
54
- test_session.total_tests_count = total_tests_count
85
+ @local_test_suites_mode = local_test_suites_mode
86
+
87
+ start_drb_service
88
+
89
+ test_session = maybe_remote_context.start_test_session(service: service, tags: tags)
90
+ test_session.estimated_total_tests_count = estimated_total_tests_count
91
+ test_session.distributed = distributed
55
92
 
56
93
  on_test_session_started(test_session)
94
+
57
95
  test_session
58
96
  end
59
97
 
60
98
  def start_test_module(test_module_name, service: nil, tags: {})
61
99
  return skip_tracing unless test_suite_level_visibility_enabled
62
100
 
63
- 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)
64
102
  on_test_module_started(test_module)
103
+
65
104
  test_module
66
105
  end
67
106
 
68
107
  def start_test_suite(test_suite_name, service: nil, tags: {})
69
108
  return skip_tracing unless test_suite_level_visibility_enabled
70
109
 
71
- test_suite = @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)
72
113
  on_test_suite_started(test_suite)
73
114
  test_suite
74
115
  end
75
116
 
76
117
  def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
118
+ test_suite = active_test_suite(test_suite_name)
119
+ tags[Ext::Test::TAG_SUITE] ||= test_suite_name
120
+
77
121
  if block
78
- @context.trace_test(test_name, test_suite_name, service: service, tags: tags) do |test|
122
+ @context.trace_test(test_name, test_suite, service: service, tags: tags) do |test|
79
123
  subscribe_to_after_stop_event(test.tracer_span)
80
124
 
81
125
  on_test_started(test)
@@ -84,7 +128,7 @@ module Datadog
84
128
  res
85
129
  end
86
130
  else
87
- test = @context.trace_test(test_name, test_suite_name, service: service, tags: tags)
131
+ test = @context.trace_test(test_name, test_suite, service: service, tags: tags)
88
132
  subscribe_to_after_stop_event(test.tracer_span)
89
133
  on_test_started(test)
90
134
  test
@@ -110,15 +154,19 @@ module Datadog
110
154
  end
111
155
 
112
156
  def active_test_session
113
- @context.active_test_session
157
+ maybe_remote_context.active_test_session
114
158
  end
115
159
 
116
160
  def active_test_module
117
- @context.active_test_module
161
+ maybe_remote_context.active_test_module
118
162
  end
119
163
 
120
164
  def active_test_suite(test_suite_name)
121
- @context.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
+
169
+ maybe_remote_context.active_test_suite(test_suite_name)
122
170
  end
123
171
 
124
172
  def deactivate_test
@@ -146,9 +194,18 @@ module Datadog
146
194
  test_suite = active_test_suite(test_suite_name)
147
195
  on_test_suite_finished(test_suite) if test_suite
148
196
 
197
+ # deactivation always happens on the same process where test suite is located
149
198
  @context.deactivate_test_suite(test_suite_name)
150
199
  end
151
200
 
201
+ def total_tests_count
202
+ maybe_remote_context.total_tests_count
203
+ end
204
+
205
+ def tests_skipped_by_tia_count
206
+ maybe_remote_context.tests_skipped_by_tia_count
207
+ end
208
+
152
209
  def itr_enabled?
153
210
  test_optimisation.enabled?
154
211
  end
@@ -157,6 +214,10 @@ module Datadog
157
214
  # noop, there is no thread owned by test visibility component
158
215
  end
159
216
 
217
+ def client_process?
218
+ forked? || @is_client_process
219
+ end
220
+
160
221
  private
161
222
 
162
223
  # DOMAIN EVENTS
@@ -167,10 +228,6 @@ module Datadog
167
228
  # finds and instruments additional test libraries that we support (ex: selenium-webdriver)
168
229
  Contrib::Instrumentation.instrument_on_session_start
169
230
 
170
- # sends internal telemetry events
171
- Telemetry.test_session_started(test_session)
172
- Telemetry.event_created(test_session)
173
-
174
231
  # sets logical test session name if none provided by the user
175
232
  override_logical_test_session_name!(test_session) if logical_test_session_name.nil?
176
233
 
@@ -179,19 +236,22 @@ module Datadog
179
236
  remote.configure(test_session)
180
237
  end
181
238
 
239
+ # intentionally empty
182
240
  def on_test_module_started(test_module)
183
- Telemetry.event_created(test_module)
184
241
  end
185
242
 
186
243
  def on_test_suite_started(test_suite)
187
244
  set_codeowners(test_suite)
188
-
189
- Telemetry.event_created(test_suite)
190
245
  end
191
246
 
192
247
  def on_test_started(test)
193
- # sometimes test suite is not being assigned correctly
194
- # fix it by fetching the one single running test suite from the global context
248
+ maybe_remote_context.incr_total_tests_count
249
+
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.
195
255
  fix_test_suite!(test) if test.test_suite_id.nil?
196
256
  validate_test_suite_level_visibility_correctness(test)
197
257
 
@@ -208,14 +268,18 @@ module Datadog
208
268
  end
209
269
 
210
270
  def on_test_session_finished(test_session)
211
- test_optimisation.write_test_session_tags(test_session)
271
+ test_optimisation.write_test_session_tags(test_session, maybe_remote_context.tests_skipped_by_tia_count)
212
272
 
213
273
  TotalCoverage.extract_lines_pct(test_session)
214
274
 
215
275
  Telemetry.event_finished(test_session)
276
+
277
+ Utils::FileStorage.cleanup
216
278
  end
217
279
 
218
280
  def on_test_module_finished(test_module)
281
+ @context.stop_all_test_suites
282
+
219
283
  Telemetry.event_finished(test_module)
220
284
  end
221
285
 
@@ -225,7 +289,7 @@ module Datadog
225
289
 
226
290
  def on_test_finished(test)
227
291
  test_optimisation.stop_coverage(test)
228
- test_optimisation.count_skipped_test(test)
292
+ test_optimisation.on_test_finished(test, maybe_remote_context)
229
293
 
230
294
  Telemetry.event_finished(test)
231
295
 
@@ -258,7 +322,7 @@ module Datadog
258
322
  def fix_test_suite!(test)
259
323
  return unless test_suite_level_visibility_enabled
260
324
 
261
- test_suite = @context.single_active_test_suite
325
+ test_suite = maybe_remote_context.single_active_test_suite
262
326
  unless test_suite
263
327
  Datadog.logger.debug do
264
328
  "Trying to fix test suite for test [#{test.name}] but no single test suite is running."
@@ -370,6 +434,42 @@ module Datadog
370
434
  def test_management
371
435
  Datadog.send(:components).test_management
372
436
  end
437
+
438
+ # DISTRIBUTED RUBY CONTEXT
439
+ def start_drb_service
440
+ return if @context_service_uri
441
+ return if client_process?
442
+
443
+ @context_service = DRb.start_service("drbunix:", @context)
444
+ @context_service_uri = @context_service.uri
445
+ end
446
+
447
+ # depending on whether we are in a forked process or not, returns either the global context or its DRbObject
448
+ def maybe_remote_context
449
+ return @context unless client_process?
450
+ return @context_client if defined?(@context_client)
451
+
452
+ # at least once per fork we must stop the running DRb server that was copied from the parent process
453
+ # otherwise, client will be confused thinking it's server which leads to terrible bugs
454
+ @context_service&.stop_service
455
+
456
+ @context_client = DRbObject.new_with_uri(@context_service_uri)
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
373
473
  end
374
474
  end
375
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"
@@ -27,24 +28,38 @@ module Datadog
27
28
  # Its responsibility includes building domain models for test visibility as well.
28
29
  # Internally it uses Datadog::Tracing module to create spans.
29
30
  class Context
30
- def initialize
31
- @local_context = Store::Local.new
32
- @global_context = Store::Global.new
31
+ attr_reader :total_tests_count, :tests_skipped_by_tia_count
32
+
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
38
+
39
+ @mutex = Mutex.new
40
+
41
+ @total_tests_count = 0
42
+ @tests_skipped_by_tia_count = 0
33
43
  end
34
44
 
35
45
  def start_test_session(service: nil, tags: {})
36
- @global_context.fetch_or_activate_test_session do
46
+ @process_context.fetch_or_activate_test_session do
37
47
  tracer_span = start_datadog_tracer_span(
38
48
  "test.session", build_tracing_span_options(service, Ext::AppTypes::TYPE_TEST_SESSION)
39
49
  )
40
50
  set_session_context(tags, tracer_span)
41
51
 
42
- 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
43
58
  end
44
59
  end
45
60
 
46
61
  def start_test_module(test_module_name, service: nil, tags: {})
47
- @global_context.fetch_or_activate_test_module do
62
+ @process_context.fetch_or_activate_test_module do
48
63
  set_inherited_globals(tags)
49
64
  set_session_context(tags)
50
65
 
@@ -53,12 +68,16 @@ module Datadog
53
68
  )
54
69
  set_module_context(tags, tracer_span)
55
70
 
56
- 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
57
76
  end
58
77
  end
59
78
 
60
79
  def start_test_suite(test_suite_name, service: nil, tags: {})
61
- @global_context.fetch_or_activate_test_suite(test_suite_name) do
80
+ @process_context.fetch_or_activate_test_suite(test_suite_name) do
62
81
  set_inherited_globals(tags)
63
82
  set_session_context(tags)
64
83
  set_module_context(tags)
@@ -66,17 +85,21 @@ module Datadog
66
85
  tracer_span = start_datadog_tracer_span(
67
86
  test_suite_name, build_tracing_span_options(service, Ext::AppTypes::TYPE_TEST_SUITE)
68
87
  )
69
- set_suite_context(tags, span: tracer_span)
88
+ set_suite_context(tags, test_suite: tracer_span)
70
89
 
71
- 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
72
95
  end
73
96
  end
74
97
 
75
- def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
98
+ def trace_test(test_name, test_suite, service: nil, tags: {}, &block)
76
99
  set_inherited_globals(tags)
77
100
  set_session_context(tags)
78
101
  set_module_context(tags)
79
- set_suite_context(tags, name: test_suite_name)
102
+ set_suite_context(tags, test_suite: test_suite)
80
103
 
81
104
  tags[Ext::Test::TAG_NAME] = test_name
82
105
  tags[Ext::Test::TAG_TYPE] ||= Ext::Test::Type::TEST
@@ -93,14 +116,14 @@ module Datadog
93
116
  start_datadog_tracer_span(test_name, span_options) do |tracer_span|
94
117
  test = build_test(tracer_span, tags)
95
118
 
96
- @local_context.activate_test(test) do
119
+ @fiber_local_context.activate_test(test) do
97
120
  block.call(test)
98
121
  end
99
122
  end
100
123
  else
101
124
  tracer_span = start_datadog_tracer_span(test_name, span_options)
102
125
  test = build_test(tracer_span, tags)
103
- @local_context.activate_test(test)
126
+ @fiber_local_context.activate_test(test)
104
127
  test
105
128
  end
106
129
  end
@@ -129,39 +152,51 @@ module Datadog
129
152
  end
130
153
 
131
154
  def active_test
132
- @local_context.active_test
155
+ @fiber_local_context.active_test
133
156
  end
134
157
 
135
158
  def active_test_session
136
- @global_context.active_test_session
159
+ @process_context.active_test_session
137
160
  end
138
161
 
139
162
  def active_test_module
140
- @global_context.active_test_module
163
+ @process_context.active_test_module
141
164
  end
142
165
 
143
166
  def active_test_suite(test_suite_name)
144
- @global_context.active_test_suite(test_suite_name)
167
+ @process_context.active_test_suite(test_suite_name)
145
168
  end
146
169
 
147
170
  def single_active_test_suite
148
- @global_context.fetch_single_test_suite
171
+ @process_context.fetch_single_test_suite
172
+ end
173
+
174
+ def stop_all_test_suites
175
+ @process_context.stop_all_test_suites
149
176
  end
150
177
 
151
178
  def deactivate_test
152
- @local_context.deactivate_test
179
+ @fiber_local_context.deactivate_test
153
180
  end
154
181
 
155
182
  def deactivate_test_session
156
- @global_context.deactivate_test_session!
183
+ @process_context.deactivate_test_session!
157
184
  end
158
185
 
159
186
  def deactivate_test_module
160
- @global_context.deactivate_test_module!
187
+ @process_context.deactivate_test_module!
161
188
  end
162
189
 
163
190
  def deactivate_test_suite(test_suite_name)
164
- @global_context.deactivate_test_suite!(test_suite_name)
191
+ @process_context.deactivate_test_suite!(test_suite_name)
192
+ end
193
+
194
+ def incr_total_tests_count
195
+ @mutex.synchronize { @total_tests_count += 1 }
196
+ end
197
+
198
+ def incr_tests_skipped_by_tia_count
199
+ @mutex.synchronize { @tests_skipped_by_tia_count += 1 }
165
200
  end
166
201
 
167
202
  private
@@ -212,37 +247,32 @@ module Datadog
212
247
 
213
248
  # PROPAGATING CONTEXT FROM TOP-LEVEL TO THE LOWER LEVELS
214
249
  def set_inherited_globals(tags)
215
- # this code achieves the same as @global_context.inheritable_session_tags.merge(tags)
216
- # but without allocating a new hash
217
- @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|
218
252
  tags[key] = value unless tags.key?(key)
219
253
  end
220
254
  end
221
255
 
222
256
  def set_session_context(tags, test_session = nil)
223
- 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
224
259
  tags[Ext::Test::TAG_TEST_SESSION_ID] = test_session.id.to_s if test_session
225
260
  end
226
261
 
227
262
  def set_module_context(tags, test_module = nil)
228
- 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
229
265
  if test_module
230
266
  tags[Ext::Test::TAG_TEST_MODULE_ID] = test_module.id.to_s
231
267
  tags[Ext::Test::TAG_MODULE] = test_module.name
232
268
  end
233
269
  end
234
270
 
235
- def set_suite_context(tags, span: nil, name: nil)
236
- return if span.nil? && name.nil?
237
-
238
- test_suite = span || active_test_suite(name)
271
+ def set_suite_context(tags, test_suite: nil)
272
+ return if test_suite.nil?
239
273
 
240
- if test_suite
241
- tags[Ext::Test::TAG_TEST_SUITE_ID] = test_suite.id.to_s
242
- tags[Ext::Test::TAG_SUITE] = test_suite.name
243
- else
244
- tags[Ext::Test::TAG_SUITE] = name
245
- end
274
+ tags[Ext::Test::TAG_TEST_SUITE_ID] = test_suite.id.to_s
275
+ tags[Ext::Test::TAG_SUITE] = test_suite.name
246
276
  end
247
277
 
248
278
  # INTERACTIONS WITH TRACING
@@ -268,11 +298,42 @@ module Datadog
268
298
  end
269
299
 
270
300
  def build_tracing_span_options(service, type, other_options = {})
271
- other_options[:service] = service || @global_context.service
301
+ other_options[:service] = service || test_session_context&.service
272
302
  other_options[:type] = type
273
303
 
274
304
  other_options
275
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
276
337
  end
277
338
  end
278
339
  end
@@ -8,7 +8,7 @@ module Datadog
8
8
  def configure(_, _)
9
9
  end
10
10
 
11
- def start_test_session(service: nil, tags: {}, total_tests_count: 0)
11
+ def start_test_session(service: nil, tags: {}, estimated_total_tests_count: 0)
12
12
  skip_tracing
13
13
  end
14
14
 
@@ -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
 
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../readonly_test_session"
4
+ require_relative "../../readonly_test_module"
5
+
3
6
  module Datadog
4
7
  module CI
5
8
  module TestVisibility
6
9
  module Store
7
10
  # This context is shared between threads and represents the current test session and test module.
8
- class Global
11
+ class Process
12
+ attr_reader :readonly_test_session, :readonly_test_module
13
+
9
14
  def initialize
10
15
  # we are using Monitor instead of Mutex because it is reentrant
11
16
  @mutex = Monitor.new
@@ -13,6 +18,11 @@ module Datadog
13
18
  @test_session = nil
14
19
  @test_module = nil
15
20
  @test_suites = {}
21
+
22
+ # small copies of id, name and some tags: store them in the current process to set session/module context
23
+ # for any spans faster
24
+ @readonly_test_session = nil
25
+ @readonly_test_module = nil
16
26
  end
17
27
 
18
28
  def fetch_or_activate_test_suite(test_suite_name, &block)
@@ -53,20 +63,10 @@ module Datadog
53
63
  @mutex.synchronize { @test_suites[test_suite_name] }
54
64
  end
55
65
 
56
- def service
57
- @mutex.synchronize do
58
- @test_session&.service
59
- end
60
- end
61
-
62
- def inheritable_session_tags
66
+ def stop_all_test_suites
63
67
  @mutex.synchronize do
64
- test_session = @test_session
65
- if test_session
66
- test_session.inheritable_tags
67
- else
68
- {}
69
- end
68
+ @test_suites.each_value(&:finish)
69
+ @test_suites.clear
70
70
  end
71
71
  end
72
72
 
@@ -81,6 +81,18 @@ module Datadog
81
81
  def deactivate_test_suite!(test_suite_name)
82
82
  @mutex.synchronize { @test_suites.delete(test_suite_name) }
83
83
  end
84
+
85
+ def set_readonly_test_session(remote_test_session)
86
+ return if remote_test_session.nil?
87
+
88
+ @readonly_test_session = Datadog::CI::ReadonlyTestSession.new(remote_test_session)
89
+ end
90
+
91
+ def set_readonly_test_module(remote_test_module)
92
+ return if remote_test_module.nil?
93
+
94
+ @readonly_test_module = Datadog::CI::ReadonlyTestModule.new(remote_test_module)
95
+ end
84
96
  end
85
97
  end
86
98
  end