Class: Datadog::CI::TestTracing::Component

Inherits:
Object
  • Object
show all
Includes:
Core::Utils::Forking, Utils::Stateful
Defined in:
lib/datadog/ci/test_tracing/component.rb

Overview

Core functionality of the library: tracing tests' execution

Constant Summary collapse

FILE_STORAGE_KEY =
"test_tracing_component_state"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils::Stateful

#load_cached_known_tests, #load_cached_settings, #load_cached_skippable_tests, #load_cached_test_management, #load_component_state, #store_component_state, #test_optimization_cache

Constructor Details

#initialize(known_tests_client:, codeowners: Codeowners::Parser.new(Git::LocalRepository.root).parse, logical_test_session_name: nil, runtime_tags_overrides: {}, context_service_uri: nil, trace_setup_teardown_enabled: false) ⇒ Component

Returns a new instance of Component.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/datadog/ci/test_tracing/component.rb', line 49

def initialize(
  known_tests_client:,
  codeowners: Codeowners::Parser.new(Git::LocalRepository.root).parse,
  logical_test_session_name: nil,
  runtime_tags_overrides: {},
  context_service_uri: nil,
  trace_setup_teardown_enabled: false
)
  @context = Context.new(test_tracing_component: self, runtime_tags_overrides: runtime_tags_overrides)

  @codeowners = codeowners
  @logical_test_session_name = logical_test_session_name
  @trace_setup_teardown_enabled = trace_setup_teardown_enabled

  # "Known tests" feature fetches a list of all tests known to Datadog for this repository
  # and uses this list to determine if a test is new or not. New tests are marked with "test.is_new" tag.
  @known_tests_enabled = false
  @known_tests_client = known_tests_client
  @known_tests = Set.new

  # this is used for parallel test runners such as parallel_tests
  if context_service_uri
    @context_service_uri = context_service_uri
    @is_client_process = true
  end

  # This is used for parallel test runners such as parallel_tests.
  # If true, then test suites are created in the worker process, not the parent.
  #
  # The only test runner that requires creating test suites in the remote process is rails test runner because
  # it splits workload by test, not by test suite.
  #
  # Another test runner that splits workload by test is knapsack_pro, but we lack distributed test sessions/test suties
  # support for that one (as of 2025-03).
  @local_test_suites_mode = true
end

Instance Attribute Details

#context_service_uri ⇒ Object (readonly)

Returns the value of attribute context_service_uri.



35
36
37
# File 'lib/datadog/ci/test_tracing/component.rb', line 35

def context_service_uri
  @context_service_uri
end

#known_tests ⇒ Object (readonly)

Returns the value of attribute known_tests.



35
36
37
# File 'lib/datadog/ci/test_tracing/component.rb', line 35

def known_tests
  @known_tests
end

#known_tests_enabled ⇒ Object (readonly)

Returns the value of attribute known_tests_enabled.



35
36
37
# File 'lib/datadog/ci/test_tracing/component.rb', line 35

def known_tests_enabled
  @known_tests_enabled
end

#local_test_suites_mode ⇒ Object (readonly)

Returns the value of attribute local_test_suites_mode.



35
36
37
# File 'lib/datadog/ci/test_tracing/component.rb', line 35

def local_test_suites_mode
  @local_test_suites_mode
end

#logical_test_session_name ⇒ Object (readonly)

Returns the value of attribute logical_test_session_name.



35
36
37
# File 'lib/datadog/ci/test_tracing/component.rb', line 35

def logical_test_session_name
  @logical_test_session_name
end

#trace_setup_teardown_enabled ⇒ Object (readonly)

Returns the value of attribute trace_setup_teardown_enabled.



35
36
37
# File 'lib/datadog/ci/test_tracing/component.rb', line 35

def trace_setup_teardown_enabled
  @trace_setup_teardown_enabled
end

Class Method Details

.build(api:, dd_env:, config_tags:, **options) ⇒ Object



38
39
40
41
# File 'lib/datadog/ci/test_tracing/component.rb', line 38

def self.build(api:, dd_env:, config_tags:, **options)
  known_tests_client = KnownTests.new(api: api, dd_env: dd_env, config_tags: config_tags)
  new(known_tests_client: known_tests_client, **options)
end

.build_transport(api:, discard_traces:, dd_env:) ⇒ Object



43
44
45
46
47
# File 'lib/datadog/ci/test_tracing/component.rb', line 43

def self.build_transport(api:, discard_traces:, dd_env:)
  return NullTransport.new if discard_traces

  Transport.new(api: api, dd_env: dd_env)
end

Instance Method Details

#active_span ⇒ Object



157
158
159
# File 'lib/datadog/ci/test_tracing/component.rb', line 157

def active_span
  @context.active_span
end

#active_test ⇒ Object



161
162
163
# File 'lib/datadog/ci/test_tracing/component.rb', line 161

def active_test
  @context.active_test
end

#active_test_module ⇒ Object



169
170
171
# File 'lib/datadog/ci/test_tracing/component.rb', line 169

def active_test_module
  maybe_remote_context.active_test_module
end

#active_test_session ⇒ Object



165
166
167
# File 'lib/datadog/ci/test_tracing/component.rb', line 165

def active_test_session
  maybe_remote_context.active_test_session
end

#active_test_suite(test_suite_name) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/datadog/ci/test_tracing/component.rb', line 173

def active_test_suite(test_suite_name)
  # when test_suite_name is not provided (from public API most likely)
  # we return the single active test suite because most of the time there is only one test suite running
  return single_active_test_suite if test_suite_name.nil?

  test_suite_name = Utils::TestName.normalize(test_suite_name)

  # when fetching test_suite to use as test's context, try local context instance first
  local_test_suite = @context.active_test_suite(test_suite_name)
  return local_test_suite if local_test_suite

  maybe_remote_context.active_test_suite(test_suite_name)
end

#client_process? ⇒ Boolean

Returns:

  • (Boolean)


225
226
227
228
229
230
231
# File 'lib/datadog/ci/test_tracing/component.rb', line 225

def client_process?
  # We cannot assume here that every forked process is a client process
  # there are examples of custom test runners that run tests in forks but don't have a test session
  # started in the parent process.
  # So we need to check if the process is forked and if the context service URI is not empty.
  (forked? && !@context_service_uri.nil? && !@context_service_uri.empty?) || @is_client_process
end

#configure(library_configuration, test_session) ⇒ Object



86
87
88
89
90
91
92
93
94
# File 'lib/datadog/ci/test_tracing/component.rb', line 86

def configure(library_configuration, test_session)
  return unless library_configuration.known_tests_enabled?

  @known_tests_enabled = true
  return if load_component_state

  fetch_known_tests(test_session)
  store_component_state if test_session.distributed
end

#deactivate_test ⇒ Object



187
188
189
190
191
192
# File 'lib/datadog/ci/test_tracing/component.rb', line 187

def deactivate_test
  test = active_test
  on_test_finished(test) if test

  @context.deactivate_test
end

#deactivate_test_module ⇒ Object



201
202
203
204
205
206
# File 'lib/datadog/ci/test_tracing/component.rb', line 201

def deactivate_test_module
  test_module = active_test_module
  on_test_module_finished(test_module) if test_module

  @context.deactivate_test_module
end

#deactivate_test_session ⇒ Object



194
195
196
197
198
199
# File 'lib/datadog/ci/test_tracing/component.rb', line 194

def deactivate_test_session
  test_session = active_test_session
  on_test_session_finished(test_session) if test_session

  @context.deactivate_test_session
end

#deactivate_test_suite(test_suite_name) ⇒ Object



208
209
210
211
212
213
214
215
# File 'lib/datadog/ci/test_tracing/component.rb', line 208

def deactivate_test_suite(test_suite_name)
  test_suite_name = Utils::TestName.normalize(test_suite_name)
  test_suite = active_test_suite(test_suite_name)
  on_test_suite_finished(test_suite) if test_suite

  # deactivation always happens on the same process where test suite is located
  @context.deactivate_test_suite(test_suite_name)
end

#itr_enabled? ⇒ Boolean

Returns:

  • (Boolean)


217
218
219
# File 'lib/datadog/ci/test_tracing/component.rb', line 217

def itr_enabled?
  test_impact_analysis.enabled?
end

#restore_state_from_datadog_test_runner ⇒ Object



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/datadog/ci/test_tracing/component.rb', line 233

def restore_state_from_datadog_test_runner
  Datadog.logger.debug { "Restoring known tests from Test Optimization cache" }

  known_tests_data = load_cached_known_tests
  if known_tests_data.nil?
    Datadog.logger.debug { "Restoring known tests failed, will request again" }
    return false
  end

  Datadog.logger.debug { "Restored known tests from Test Optimization: #{known_tests_data}" }

  @known_tests = KnownTests::Response.from_json(known_tests_data).tests
  @known_tests_enabled = !@known_tests.empty?

  unless @known_tests_enabled
    Datadog.logger.debug("Empty set of known tests from the Test Optimization cache file")
  end

  Datadog.logger.debug { "Found [#{@known_tests.size}] known tests from context" }

  true
end

#shutdown! ⇒ Object



221
222
223
# File 'lib/datadog/ci/test_tracing/component.rb', line 221

def shutdown!
  # noop, there is no thread owned by test visibility component
end

#start_test_module(test_module_name, service: nil, tags: {}) ⇒ Object



110
111
112
113
114
115
# File 'lib/datadog/ci/test_tracing/component.rb', line 110

def start_test_module(test_module_name, service: nil, tags: {})
  test_module = maybe_remote_context.start_test_module(test_module_name, service: service, tags: tags)
  on_test_module_started(test_module)

  test_module
end

#start_test_session(service: nil, tags: {}, estimated_total_tests_count: 0, distributed: nil, local_test_suites_mode: true) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/datadog/ci/test_tracing/component.rb', line 96

def start_test_session(service: nil, tags: {}, estimated_total_tests_count: 0, distributed: nil, local_test_suites_mode: true)
  @local_test_suites_mode = local_test_suites_mode

  start_drb_service

  test_session = maybe_remote_context.start_test_session(service: service, tags: tags)
  test_session.estimated_total_tests_count = estimated_total_tests_count
  test_session.distributed = distributed unless distributed.nil?

  on_test_session_started(test_session)

  test_session
end

#start_test_suite(test_suite_name, service: nil, tags: {}) ⇒ Object



117
118
119
120
121
122
123
124
# File 'lib/datadog/ci/test_tracing/component.rb', line 117

def start_test_suite(test_suite_name, service: nil, tags: {})
  test_suite_name = Utils::TestName.normalize(test_suite_name)
  context = @local_test_suites_mode ? @context : maybe_remote_context

  test_suite = context.start_test_suite(test_suite_name, service: service, tags: tags)
  on_test_suite_started(test_suite)
  test_suite
end

#trace(span_name, type: "span", tags: {}, &block) ⇒ Object



147
148
149
150
151
152
153
154
155
# File 'lib/datadog/ci/test_tracing/component.rb', line 147

def trace(span_name, type: "span", tags: {}, &block)
  if block
    @context.trace(span_name, type: type, tags: tags) do |span|
      block.call(span)
    end
  else
    @context.trace(span_name, type: type, tags: tags)
  end
end

#trace_test(test_name, test_suite_name, service: nil, tags: {}, &block) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/datadog/ci/test_tracing/component.rb', line 126

def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
  test_name = Utils::TestName.normalize(test_name)
  test_suite_name = Utils::TestName.normalize(test_suite_name)

  test_suite = active_test_suite(test_suite_name)
  tags[Ext::Test::TAG_SUITE] ||= test_suite_name

  if block
    @context.trace_test(test_name, test_suite, service: service, tags: tags) do |test|
      on_test_started(test)
      res = block.call(test)
      on_test_finished(test)
      res
    end
  else
    test = @context.trace_test(test_name, test_suite, service: service, tags: tags)
    on_test_started(test)
    test
  end
end