datadog-ci 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -2
  3. data/lib/datadog/ci/configuration/components.rb +30 -3
  4. data/lib/datadog/ci/contrib/cucumber/formatter.rb +13 -9
  5. data/lib/datadog/ci/contrib/minitest/runnable.rb +5 -1
  6. data/lib/datadog/ci/contrib/minitest/runner.rb +6 -2
  7. data/lib/datadog/ci/contrib/minitest/test.rb +7 -3
  8. data/lib/datadog/ci/contrib/rspec/example.rb +6 -2
  9. data/lib/datadog/ci/contrib/rspec/example_group.rb +5 -1
  10. data/lib/datadog/ci/contrib/rspec/knapsack_pro/runner.rb +6 -2
  11. data/lib/datadog/ci/contrib/rspec/runner.rb +6 -2
  12. data/lib/datadog/ci/ext/environment/providers/appveyor.rb +1 -1
  13. data/lib/datadog/ci/ext/environment/providers/aws_code_pipeline.rb +1 -1
  14. data/lib/datadog/ci/ext/environment/providers/azure.rb +1 -1
  15. data/lib/datadog/ci/ext/environment/providers/bitbucket.rb +1 -1
  16. data/lib/datadog/ci/ext/environment/providers/bitrise.rb +1 -1
  17. data/lib/datadog/ci/ext/environment/providers/buddy.rb +1 -1
  18. data/lib/datadog/ci/ext/environment/providers/buildkite.rb +1 -1
  19. data/lib/datadog/ci/ext/environment/providers/circleci.rb +1 -1
  20. data/lib/datadog/ci/ext/environment/providers/codefresh.rb +1 -1
  21. data/lib/datadog/ci/ext/environment/providers/github_actions.rb +1 -1
  22. data/lib/datadog/ci/ext/environment/providers/gitlab.rb +1 -1
  23. data/lib/datadog/ci/ext/environment/providers/jenkins.rb +1 -1
  24. data/lib/datadog/ci/ext/environment/providers/teamcity.rb +1 -1
  25. data/lib/datadog/ci/ext/environment/providers/travis.rb +1 -1
  26. data/lib/datadog/ci/ext/environment.rb +17 -0
  27. data/lib/datadog/ci/ext/telemetry.rb +120 -0
  28. data/lib/datadog/ci/git/local_repository.rb +116 -25
  29. data/lib/datadog/ci/git/search_commits.rb +20 -1
  30. data/lib/datadog/ci/git/telemetry.rb +37 -0
  31. data/lib/datadog/ci/git/tree_uploader.rb +7 -0
  32. data/lib/datadog/ci/git/upload_packfile.rb +22 -1
  33. data/lib/datadog/ci/test.rb +5 -0
  34. data/lib/datadog/ci/test_optimisation/component.rb +16 -2
  35. data/lib/datadog/ci/test_optimisation/coverage/transport.rb +10 -1
  36. data/lib/datadog/ci/test_optimisation/skippable.rb +24 -0
  37. data/lib/datadog/ci/test_optimisation/telemetry.rb +56 -0
  38. data/lib/datadog/ci/test_visibility/component.rb +95 -235
  39. data/lib/datadog/ci/test_visibility/context.rb +274 -0
  40. data/lib/datadog/ci/test_visibility/{context → store}/global.rb +1 -1
  41. data/lib/datadog/ci/test_visibility/{context → store}/local.rb +1 -1
  42. data/lib/datadog/ci/test_visibility/telemetry.rb +69 -0
  43. data/lib/datadog/ci/test_visibility/transport.rb +8 -9
  44. data/lib/datadog/ci/transport/adapters/net.rb +42 -11
  45. data/lib/datadog/ci/transport/adapters/net_http_client.rb +17 -0
  46. data/lib/datadog/ci/transport/adapters/telemetry_webmock_safe_adapter.rb +28 -0
  47. data/lib/datadog/ci/transport/event_platform_transport.rb +42 -5
  48. data/lib/datadog/ci/transport/http.rb +49 -21
  49. data/lib/datadog/ci/transport/remote_settings_api.rb +39 -1
  50. data/lib/datadog/ci/transport/telemetry.rb +93 -0
  51. data/lib/datadog/ci/utils/identity.rb +20 -0
  52. data/lib/datadog/ci/utils/parsing.rb +2 -1
  53. data/lib/datadog/ci/utils/telemetry.rb +23 -0
  54. data/lib/datadog/ci/version.rb +1 -1
  55. data/lib/datadog/ci.rb +30 -0
  56. metadata +16 -6
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../ext/telemetry"
4
+ require_relative "../ext/test"
5
+ require_relative "../utils/telemetry"
6
+ require_relative "../test_visibility/telemetry"
7
+
8
+ module Datadog
9
+ module CI
10
+ module TestOptimisation
11
+ # Telemetry for test optimisation component
12
+ module Telemetry
13
+ def self.code_coverage_started(test)
14
+ Utils::Telemetry.inc(Ext::Telemetry::METRIC_CODE_COVERAGE_STARTED, 1, tags_for_test(test))
15
+ end
16
+
17
+ def self.code_coverage_finished(test)
18
+ Utils::Telemetry.inc(Ext::Telemetry::METRIC_CODE_COVERAGE_FINISHED, 1, tags_for_test(test))
19
+ end
20
+
21
+ def self.code_coverage_is_empty
22
+ Utils::Telemetry.inc(Ext::Telemetry::METRIC_CODE_COVERAGE_IS_EMPTY, 1)
23
+ end
24
+
25
+ def self.code_coverage_files(count)
26
+ Utils::Telemetry.distribution(Ext::Telemetry::METRIC_CODE_COVERAGE_FILES, count.to_f)
27
+ end
28
+
29
+ def self.itr_skipped
30
+ Utils::Telemetry.inc(Ext::Telemetry::METRIC_ITR_SKIPPED, 1, tags_for_itr_metrics)
31
+ end
32
+
33
+ def self.itr_forced_run
34
+ Utils::Telemetry.inc(Ext::Telemetry::METRIC_ITR_FORCED_RUN, 1, tags_for_itr_metrics)
35
+ end
36
+
37
+ def self.itr_unskippable
38
+ Utils::Telemetry.inc(Ext::Telemetry::METRIC_ITR_UNSKIPPABLE, 1, tags_for_itr_metrics)
39
+ end
40
+
41
+ def self.tags_for_test(test)
42
+ {
43
+ Ext::Telemetry::TAG_TEST_FRAMEWORK => test.get_tag(Ext::Test::TAG_FRAMEWORK),
44
+ Ext::Telemetry::TAG_LIBRARY => Ext::Telemetry::Library::CUSTOM
45
+ }
46
+ end
47
+
48
+ def self.tags_for_itr_metrics
49
+ {
50
+ Ext::Telemetry::TAG_EVENT_TYPE => Ext::Telemetry::EventType::TEST
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,35 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "datadog/tracing"
4
- require "datadog/tracing/contrib/component"
5
- require "datadog/tracing/trace_digest"
6
-
7
3
  require "rbconfig"
8
4
 
9
- require_relative "context/global"
10
- require_relative "context/local"
5
+ require_relative "context"
6
+ require_relative "telemetry"
11
7
 
12
8
  require_relative "../codeowners/parser"
13
9
  require_relative "../contrib/contrib"
14
- require_relative "../ext/app_types"
15
10
  require_relative "../ext/test"
16
- require_relative "../ext/environment"
17
11
  require_relative "../git/local_repository"
18
12
 
19
- require_relative "../span"
20
- require_relative "../test"
21
- require_relative "../test_session"
22
- require_relative "../test_module"
23
- require_relative "../test_suite"
24
13
  require_relative "../worker"
25
14
 
26
15
  module Datadog
27
16
  module CI
28
17
  module TestVisibility
29
18
  # Common behavior for CI tests
30
- # Note: this class has too many responsibilities and should be split into multiple classes
31
19
  class Component
32
- attr_reader :environment_tags, :test_suite_level_visibility_enabled
20
+ attr_reader :test_suite_level_visibility_enabled
33
21
 
34
22
  def initialize(
35
23
  test_optimisation:,
@@ -39,13 +27,8 @@ module Datadog
39
27
  codeowners: Codeowners::Parser.new(Git::LocalRepository.root).parse
40
28
  )
41
29
  @test_suite_level_visibility_enabled = test_suite_level_visibility_enabled
42
-
43
- @environment_tags = Ext::Environment.tags(ENV).freeze
44
- @local_context = Context::Local.new
45
- @global_context = Context::Global.new
46
-
30
+ @context = Context.new
47
31
  @codeowners = codeowners
48
-
49
32
  @test_optimisation = test_optimisation
50
33
  @remote_settings_api = remote_settings_api
51
34
  @git_tree_upload_worker = git_tree_upload_worker
@@ -58,155 +41,98 @@ module Datadog
58
41
  def start_test_session(service: nil, tags: {})
59
42
  return skip_tracing unless test_suite_level_visibility_enabled
60
43
 
61
- # finds and instruments additional test libraries that we support (ex: selenium-webdriver)
62
- Contrib.auto_instrument_on_session_start!
63
-
64
- @global_context.fetch_or_activate_test_session do
65
- tracer_span = start_datadog_tracer_span(
66
- "test.session", build_span_options(service, Ext::AppTypes::TYPE_TEST_SESSION)
67
- )
68
- set_session_context(tags, tracer_span)
69
-
70
- test_session = build_test_session(tracer_span, tags)
71
-
72
- @git_tree_upload_worker.perform(test_session.git_repository_url)
73
- configure_library(test_session)
74
-
75
- test_session
76
- end
44
+ test_session = @context.start_test_session(service: service, tags: tags)
45
+ on_test_session_started(test_session)
46
+ test_session
77
47
  end
78
48
 
79
49
  def start_test_module(test_module_name, service: nil, tags: {})
80
50
  return skip_tracing unless test_suite_level_visibility_enabled
81
51
 
82
- @global_context.fetch_or_activate_test_module do
83
- set_inherited_globals(tags)
84
- set_session_context(tags)
85
-
86
- tracer_span = start_datadog_tracer_span(
87
- test_module_name, build_span_options(service, Ext::AppTypes::TYPE_TEST_MODULE)
88
- )
89
- set_module_context(tags, tracer_span)
90
-
91
- build_test_module(tracer_span, tags)
92
- end
52
+ test_module = @context.start_test_module(test_module_name, service: service, tags: tags)
53
+ on_test_module_started(test_module)
54
+ test_module
93
55
  end
94
56
 
95
57
  def start_test_suite(test_suite_name, service: nil, tags: {})
96
58
  return skip_tracing unless test_suite_level_visibility_enabled
97
59
 
98
- @global_context.fetch_or_activate_test_suite(test_suite_name) do
99
- set_inherited_globals(tags)
100
- set_session_context(tags)
101
- set_module_context(tags)
102
-
103
- tracer_span = start_datadog_tracer_span(
104
- test_suite_name, build_span_options(service, Ext::AppTypes::TYPE_TEST_SUITE)
105
- )
106
- set_suite_context(tags, span: tracer_span)
107
-
108
- build_test_suite(tracer_span, tags)
109
- end
60
+ test_suite = @context.start_test_suite(test_suite_name, service: service, tags: tags)
61
+ on_test_suite_started(test_suite)
62
+ test_suite
110
63
  end
111
64
 
112
65
  def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
113
- set_inherited_globals(tags)
114
- set_session_context(tags)
115
- set_module_context(tags)
116
- set_suite_context(tags, name: test_suite_name)
117
-
118
- tags[Ext::Test::TAG_NAME] = test_name
119
- tags[Ext::Test::TAG_TYPE] ||= Ext::Test::Type::TEST
120
-
121
- span_options = build_span_options(
122
- service,
123
- Ext::AppTypes::TYPE_TEST,
124
- # :resource is needed for the agent APM protocol to work correctly (for older agent versions)
125
- # :continue_from is required to start a new trace for each test
126
- {resource: test_name, continue_from: Datadog::Tracing::TraceDigest.new}
127
- )
128
-
129
66
  if block
130
- start_datadog_tracer_span(test_name, span_options) do |tracer_span|
131
- test = build_test(tracer_span, tags)
132
-
133
- @local_context.activate_test(test) do
134
- on_test_started(test)
135
- res = block.call(test)
136
- on_test_finished(test)
137
- res
138
- end
67
+ @context.trace_test(test_name, test_suite_name, service: service, tags: tags) do |test|
68
+ on_test_started(test)
69
+ res = block.call(test)
70
+ on_test_finished(test)
71
+ res
139
72
  end
140
73
  else
141
- tracer_span = start_datadog_tracer_span(test_name, span_options)
142
- test = build_test(tracer_span, tags)
143
-
144
- @local_context.activate_test(test)
74
+ test = @context.trace_test(test_name, test_suite_name, service: service, tags: tags)
145
75
  on_test_started(test)
146
-
147
76
  test
148
77
  end
149
78
  end
150
79
 
151
80
  def trace(span_name, type: "span", tags: {}, &block)
152
- span_options = build_span_options(
153
- nil, # service name is completely optional for custom spans
154
- type,
155
- {resource: span_name}
156
- )
157
-
158
81
  if block
159
- start_datadog_tracer_span(span_name, span_options) do |tracer_span|
160
- block.call(build_span(tracer_span, tags))
82
+ @context.trace(span_name, type: type, tags: tags) do |span|
83
+ block.call(span)
161
84
  end
162
85
  else
163
- tracer_span = start_datadog_tracer_span(span_name, span_options)
164
-
165
- build_span(tracer_span, tags)
86
+ @context.trace(span_name, type: type, tags: tags)
166
87
  end
167
88
  end
168
89
 
169
90
  def active_span
170
- tracer_span = Datadog::Tracing.active_span
171
- Span.new(tracer_span) if tracer_span
91
+ @context.active_span
172
92
  end
173
93
 
174
94
  def active_test
175
- @local_context.active_test
95
+ @context.active_test
176
96
  end
177
97
 
178
98
  def active_test_session
179
- @global_context.active_test_session
99
+ @context.active_test_session
180
100
  end
181
101
 
182
102
  def active_test_module
183
- @global_context.active_test_module
103
+ @context.active_test_module
184
104
  end
185
105
 
186
106
  def active_test_suite(test_suite_name)
187
- @global_context.active_test_suite(test_suite_name)
107
+ @context.active_test_suite(test_suite_name)
188
108
  end
189
109
 
190
110
  def deactivate_test
191
111
  test = active_test
192
112
  on_test_finished(test) if test
193
113
 
194
- @local_context.deactivate_test
114
+ @context.deactivate_test
195
115
  end
196
116
 
197
117
  def deactivate_test_session
198
118
  test_session = active_test_session
199
119
  on_test_session_finished(test_session) if test_session
200
120
 
201
- @global_context.deactivate_test_session!
121
+ @context.deactivate_test_session
202
122
  end
203
123
 
204
124
  def deactivate_test_module
205
- @global_context.deactivate_test_module!
125
+ test_module = active_test_module
126
+ on_test_module_finished(test_module) if test_module
127
+
128
+ @context.deactivate_test_module
206
129
  end
207
130
 
208
131
  def deactivate_test_suite(test_suite_name)
209
- @global_context.deactivate_test_suite!(test_suite_name)
132
+ test_suite = active_test_suite(test_suite_name)
133
+ on_test_suite_finished(test_suite) if test_suite
134
+
135
+ @context.deactivate_test_suite(test_suite_name)
210
136
  end
211
137
 
212
138
  def itr_enabled?
@@ -215,63 +141,27 @@ module Datadog
215
141
 
216
142
  private
217
143
 
218
- def configure_library(test_session)
219
- # this will change when EFD is implemented
220
- return unless itr_enabled?
221
-
222
- remote_configuration = @remote_settings_api.fetch_library_settings(test_session)
223
- # sometimes we can skip code coverage for default branch if there are no changes in the repository
224
- # backend needs git metadata uploaded for this test session to check if we can skip code coverage
225
- if remote_configuration.require_git?
226
- Datadog.logger.debug { "Library configuration endpoint requires git upload to be finished, waiting..." }
227
- @git_tree_upload_worker.wait_until_done
228
-
229
- Datadog.logger.debug { "Requesting library configuration again..." }
230
- remote_configuration = @remote_settings_api.fetch_library_settings(test_session)
144
+ # DOMAIN EVENTS
145
+ def on_test_session_started(test_session)
146
+ Telemetry.event_created(test_session)
147
+ Telemetry.test_session_started(test_session)
231
148
 
232
- if remote_configuration.require_git?
233
- Datadog.logger.debug { "git metadata upload did not complete in time when configuring library" }
234
- end
235
- end
236
-
237
- @test_optimisation.configure(
238
- remote_configuration.payload,
239
- test_session: test_session,
240
- git_tree_upload_worker: @git_tree_upload_worker
241
- )
242
- end
243
-
244
- def skip_tracing(block = nil)
245
- block&.call(nil)
246
- end
247
-
248
- # Sets trace's origin to ciapp-test
249
- def set_trace_origin(trace)
250
- trace&.origin = Ext::Test::CONTEXT_ORIGIN
251
- end
149
+ # finds and instruments additional test libraries that we support (ex: selenium-webdriver)
150
+ Contrib.auto_instrument_on_session_start!
252
151
 
253
- def build_test_session(tracer_span, tags)
254
- test_session = TestSession.new(tracer_span)
255
- set_initial_tags(test_session, tags)
256
- test_session
152
+ @git_tree_upload_worker.perform(test_session.git_repository_url)
153
+ configure_library(test_session)
257
154
  end
258
155
 
259
- def build_test_module(tracer_span, tags)
260
- test_module = TestModule.new(tracer_span)
261
- set_initial_tags(test_module, tags)
262
- test_module
156
+ def on_test_module_started(test_module)
157
+ Telemetry.event_created(test_module)
263
158
  end
264
159
 
265
- def build_test_suite(tracer_span, tags)
266
- test_suite = TestSuite.new(tracer_span)
267
- set_initial_tags(test_suite, tags)
268
- test_suite
160
+ def on_test_suite_started(test_suite)
161
+ Telemetry.event_created(test_suite)
269
162
  end
270
163
 
271
- def build_test(tracer_span, tags)
272
- test = Test.new(tracer_span)
273
- set_initial_tags(test, tags)
274
-
164
+ def on_test_started(test)
275
165
  # sometimes test suite is not being assigned correctly
276
166
  # fix it by fetching the one single running test suite from the global context
277
167
  fix_test_suite!(test) if test.test_suite_id.nil?
@@ -279,49 +169,63 @@ module Datadog
279
169
  validate_test_suite_level_visibility_correctness(test)
280
170
  set_codeowners(test)
281
171
 
282
- test
283
- end
172
+ Telemetry.event_created(test)
284
173
 
285
- def build_span(tracer_span, tags)
286
- span = Span.new(tracer_span)
287
- set_initial_tags(span, tags)
288
- span
174
+ @test_optimisation.mark_if_skippable(test)
175
+ @test_optimisation.start_coverage(test)
289
176
  end
290
177
 
291
- def build_span_options(service, type, other_options = {})
292
- other_options[:service] = service || @global_context.service
293
- other_options[:type] = type
178
+ def on_test_session_finished(test_session)
179
+ @test_optimisation.write_test_session_tags(test_session)
294
180
 
295
- other_options
181
+ Telemetry.event_finished(test_session)
296
182
  end
297
183
 
298
- def set_inherited_globals(tags)
299
- # this code achieves the same as @global_context.inheritable_session_tags.merge(tags)
300
- # but without allocating a new hash
301
- @global_context.inheritable_session_tags.each do |key, value|
302
- tags[key] = value unless tags.key?(key)
303
- end
184
+ def on_test_module_finished(test_module)
185
+ Telemetry.event_finished(test_module)
304
186
  end
305
187
 
306
- def set_initial_tags(ci_span, tags)
307
- ci_span.set_default_tags
308
- ci_span.set_environment_runtime_tags
309
-
310
- ci_span.set_tags(tags)
311
- ci_span.set_tags(environment_tags)
188
+ def on_test_suite_finished(test_suite)
189
+ Telemetry.event_finished(test_suite)
312
190
  end
313
191
 
314
- def set_session_context(tags, test_session = nil)
315
- test_session ||= active_test_session
316
- tags[Ext::Test::TAG_TEST_SESSION_ID] = test_session.id.to_s if test_session
192
+ def on_test_finished(test)
193
+ @test_optimisation.stop_coverage(test)
194
+ @test_optimisation.count_skipped_test(test)
195
+
196
+ Telemetry.event_finished(test)
317
197
  end
318
198
 
319
- def set_module_context(tags, test_module = nil)
320
- test_module ||= active_test_module
321
- if test_module
322
- tags[Ext::Test::TAG_TEST_MODULE_ID] = test_module.id.to_s
323
- tags[Ext::Test::TAG_MODULE] = test_module.name
199
+ # TODO: move this to CI::Configuration::Remote
200
+ def configure_library(test_session)
201
+ # this will change when EFD is implemented
202
+ return unless itr_enabled?
203
+
204
+ remote_configuration = @remote_settings_api.fetch_library_settings(test_session)
205
+ # sometimes we can skip code coverage for default branch if there are no changes in the repository
206
+ # backend needs git metadata uploaded for this test session to check if we can skip code coverage
207
+ if remote_configuration.require_git?
208
+ Datadog.logger.debug { "Library configuration endpoint requires git upload to be finished, waiting..." }
209
+ @git_tree_upload_worker.wait_until_done
210
+
211
+ Datadog.logger.debug { "Requesting library configuration again..." }
212
+ remote_configuration = @remote_settings_api.fetch_library_settings(test_session)
213
+
214
+ if remote_configuration.require_git?
215
+ Datadog.logger.debug { "git metadata upload did not complete in time when configuring library" }
216
+ end
324
217
  end
218
+
219
+ @test_optimisation.configure(
220
+ remote_configuration.payload,
221
+ test_session: test_session,
222
+ git_tree_upload_worker: @git_tree_upload_worker
223
+ )
224
+ end
225
+
226
+ # HELPERS
227
+ def skip_tracing(block = nil)
228
+ block&.call(nil)
325
229
  end
326
230
 
327
231
  def set_codeowners(test)
@@ -330,21 +234,8 @@ module Datadog
330
234
  test.set_tag(Ext::Test::TAG_CODEOWNERS, owners) unless owners.nil?
331
235
  end
332
236
 
333
- def set_suite_context(tags, span: nil, name: nil)
334
- return if span.nil? && name.nil?
335
-
336
- test_suite = span || active_test_suite(name)
337
-
338
- if test_suite
339
- tags[Ext::Test::TAG_TEST_SUITE_ID] = test_suite.id.to_s
340
- tags[Ext::Test::TAG_SUITE] = test_suite.name
341
- else
342
- tags[Ext::Test::TAG_SUITE] = name
343
- end
344
- end
345
-
346
237
  def fix_test_suite!(test)
347
- test_suite = @global_context.fetch_single_test_suite
238
+ test_suite = @context.single_active_test_suite
348
239
  unless test_suite
349
240
  Datadog.logger.debug do
350
241
  "Trying to fix test suite for test [#{test.name}] but no single test suite is running."
@@ -361,22 +252,6 @@ module Datadog
361
252
  test.set_tag(Ext::Test::TAG_SUITE, test_suite.name)
362
253
  end
363
254
 
364
- def start_datadog_tracer_span(span_name, span_options, &block)
365
- if block
366
- Datadog::Tracing.trace(span_name, **span_options) do |tracer_span, trace|
367
- set_trace_origin(trace)
368
-
369
- yield tracer_span
370
- end
371
- else
372
- tracer_span = Datadog::Tracing.trace(span_name, **span_options)
373
- trace = Datadog::Tracing.active_trace
374
- set_trace_origin(trace)
375
-
376
- tracer_span
377
- end
378
- end
379
-
380
255
  def validate_test_suite_level_visibility_correctness(test)
381
256
  return unless test_suite_level_visibility_enabled
382
257
 
@@ -401,21 +276,6 @@ module Datadog
401
276
  end
402
277
  end
403
278
  end
404
-
405
- # TODO: use kind of event system to notify about test finished?
406
- def on_test_finished(test)
407
- @test_optimisation.stop_coverage(test)
408
- @test_optimisation.count_skipped_test(test)
409
- end
410
-
411
- def on_test_started(test)
412
- @test_optimisation.mark_if_skippable(test)
413
- @test_optimisation.start_coverage(test)
414
- end
415
-
416
- def on_test_session_finished(test_session)
417
- @test_optimisation.write_test_session_tags(test_session)
418
- end
419
279
  end
420
280
  end
421
281
  end