datadog-ci 1.34.0 → 1.36.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.
@@ -7,6 +7,7 @@ require_relative "../../source_code/path_filter"
7
7
  require_relative "../../utils/bundle"
8
8
  require_relative "../../utils/test_run"
9
9
  require_relative "../instrumentation"
10
+ require_relative "anonymous_example_name"
10
11
  require_relative "ext"
11
12
 
12
13
  module Datadog
@@ -110,11 +111,7 @@ module Datadog
110
111
  def datadog_test_name
111
112
  return @datadog_test_name if defined?(@datadog_test_name)
112
113
 
113
- test_name = full_description.strip
114
- if metadata[:description].empty?
115
- # for unnamed it blocks this appends something like "example at ./spec/some_spec.rb:10"
116
- test_name << " #{description}"
117
- end
114
+ test_name = datadog_unnamed_example? ? datadog_unnamed_example_name : full_description.strip
118
115
 
119
116
  # remove example group description from test name to avoid duplication
120
117
  test_name = test_name.sub(datadog_test_suite_description, "").strip
@@ -266,6 +263,27 @@ module Datadog
266
263
  end
267
264
  end
268
265
 
266
+ # ============================================
267
+ # Test name helpers
268
+ # ============================================
269
+
270
+ def datadog_unnamed_example?
271
+ # @type var description_args: Array[untyped]
272
+ description_args = Array(metadata[:description_args])
273
+ description_args.empty? || description_args.all? { |description_arg| description_arg.to_s.strip.empty? }
274
+ end
275
+
276
+ def datadog_unnamed_example_name
277
+ example_name = AnonymousExampleName.call(datadog_anonymous_example_block)
278
+ example_name ||= "anonymous example"
279
+
280
+ "#{metadata[:example_group][:full_description]} #{example_name}"
281
+ end
282
+
283
+ def datadog_anonymous_example_block
284
+ metadata[:block] || @example_block
285
+ end
286
+
269
287
  # ============================================
270
288
  # Source location resolution
271
289
  # ============================================
@@ -22,7 +22,15 @@ module Datadog
22
22
  ::SimpleCov::ResultAdapter.call(::Coverage.peek_result)
23
23
  )
24
24
 
25
- ::SimpleCov::Result.new(add_not_loaded_files(result))
25
+ # SimpleCov 1.0 changed add_not_loaded_files to return a
26
+ # [coverage, not_loaded_files] tuple instead of a coverage hash.
27
+ processed = add_not_loaded_files(result)
28
+ if processed.is_a?(::Array)
29
+ coverage, not_loaded_files = processed
30
+ ::SimpleCov::Result.new(coverage, not_loaded_files: not_loaded_files)
31
+ else
32
+ ::SimpleCov::Result.new(processed)
33
+ end
26
34
  end
27
35
 
28
36
  def datadog_configuration
@@ -28,6 +28,7 @@ module Datadog
28
28
  Environment::TAG_JOB_URL => @provider.job_url,
29
29
  Environment::TAG_PIPELINE_ID => @provider.pipeline_id,
30
30
  Environment::TAG_PIPELINE_NAME => @provider.pipeline_name,
31
+ Environment::TAG_PIPELINE_DISPLAY_NAME => @provider.pipeline_display_name,
31
32
  Environment::TAG_PIPELINE_NUMBER => @provider.pipeline_number,
32
33
  Environment::TAG_PIPELINE_URL => @provider.pipeline_url,
33
34
  Environment::TAG_PROVIDER_NAME => @provider.provider_name,
@@ -33,6 +33,9 @@ module Datadog
33
33
  def pipeline_name
34
34
  end
35
35
 
36
+ def pipeline_display_name
37
+ end
38
+
36
39
  def pipeline_number
37
40
  end
38
41
 
@@ -36,6 +36,10 @@ module Datadog
36
36
  env["BUILDKITE_PIPELINE_SLUG"]
37
37
  end
38
38
 
39
+ def pipeline_display_name
40
+ env["BUILDKITE_PIPELINE_NAME"]
41
+ end
42
+
39
43
  def pipeline_number
40
44
  env["BUILDKITE_BUILD_NUMBER"]
41
45
  end
@@ -18,6 +18,7 @@ module Datadog
18
18
  TAG_JOB_URL = "ci.job.url"
19
19
  TAG_PIPELINE_ID = "ci.pipeline.id"
20
20
  TAG_PIPELINE_NAME = "ci.pipeline.name"
21
+ TAG_PIPELINE_DISPLAY_NAME = "ci.pipeline.display_name"
21
22
  TAG_PIPELINE_NUMBER = "ci.pipeline.number"
22
23
  TAG_PIPELINE_URL = "ci.pipeline.url"
23
24
  TAG_PROVIDER_NAME = "ci.provider.name"
@@ -33,6 +33,7 @@ module Datadog
33
33
  ENV_AUTO_INSTRUMENTATION_PROVIDER = "DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER"
34
34
  ENV_TIA_STATIC_DEPENDENCIES_TRACKING_ENABLED = "DD_TEST_OPTIMIZATION_TIA_STATIC_DEPS_COVERAGE_ENABLED"
35
35
  ENV_CODE_COVERAGE_REPORT_UPLOAD_ENABLED = "DD_CIVISIBILITY_CODE_COVERAGE_REPORT_UPLOAD_ENABLED"
36
+ ENV_CODE_COVERAGE_FLAGS = "DD_CODE_COVERAGE_FLAGS"
36
37
  ENV_RUNTIME_TAGS = "DD_TEST_OPTIMIZATION_RUNTIME_TAGS"
37
38
 
38
39
  # Source: https://docs.datadoghq.com/getting_started/site/
@@ -65,6 +65,9 @@ module Datadog
65
65
  METRIC_KNOWN_TESTS_REQUEST_ERRORS = "known_tests.request_errors"
66
66
  METRIC_KNOWN_TESTS_RESPONSE_BYTES = "known_tests.response_bytes"
67
67
  METRIC_KNOWN_TESTS_RESPONSE_TESTS = "known_tests.response_tests"
68
+ METRIC_KNOWN_TESTS_PAGES_FETCHED = "known_tests.pages_fetched"
69
+ METRIC_KNOWN_TESTS_TOTAL_FETCH_MS = "known_tests.total_fetch_ms"
70
+ METRIC_KNOWN_TESTS_TOTAL_REQUEST_MS = "known_tests.total_request_ms"
68
71
 
69
72
  METRIC_TEST_MANAGEMENT_TESTS_REQUEST = "test_management_tests.request"
70
73
  METRIC_TEST_MANAGEMENT_TESTS_REQUEST_MS = "test_management_tests.request_ms"
@@ -12,6 +12,13 @@ module Datadog
12
12
  #
13
13
  # @public_api
14
14
  class Test < Span
15
+ def initialize(tracer_span)
16
+ super
17
+
18
+ @custom_impacted_files = []
19
+ @inherited_custom_impacted_files = []
20
+ end
21
+
15
22
  # Context IDs for this test (used for TIA context coverage merging).
16
23
  # Contains list of context identifiers from outermost to innermost.
17
24
  # @return [Array<String>] list of context IDs
@@ -157,6 +164,66 @@ module Datadog
157
164
  end
158
165
  end
159
166
 
167
+ # Adds custom files that Test Impact Analysis should consider capable of
168
+ # impacting this test.
169
+ #
170
+ # This is useful for files that Datadog's native Ruby coverage cannot
171
+ # observe, such as JavaScript executed in a browser during a Capybara
172
+ # test. Paths must resolve inside the Git repository. Relative paths must
173
+ # be relative to the repository root. Absolute paths are also accepted.
174
+ #
175
+ # If paths are relative to the current working directory, convert them to
176
+ # absolute paths with +File.expand_path+ before calling this method.
177
+ # Submitted paths must be lexically normalized without redundant +.+ or
178
+ # +..+ components.
179
+ #
180
+ # Path resolution does not access the filesystem, so files do not need
181
+ # to exist when they are added. Paths outside the repository are ignored
182
+ # when the coverage event is serialized. Calls are incremental: every
183
+ # call adds files for the remainder of the test.
184
+ #
185
+ # @example Register JavaScript files loaded during an RSpec test
186
+ # RSpec.configure do |config|
187
+ # config.after do
188
+ # Datadog::CI.active_test&.add_impacted_files(
189
+ # javascript_files_loaded_by_browser.map do |path|
190
+ # File.expand_path(path, Dir.pwd)
191
+ # end
192
+ # )
193
+ # end
194
+ # end
195
+ #
196
+ # @param [Array<String>] file_paths custom paths that can impact this test
197
+ # @raise [ArgumentError] if file_paths is not an Array
198
+ # @return [void]
199
+ def add_impacted_files(file_paths)
200
+ raise ArgumentError, "file_paths must be an Array" unless file_paths.is_a?(Array)
201
+
202
+ @custom_impacted_files.concat(file_paths)
203
+ nil
204
+ end
205
+
206
+ # Inherits suite-level custom impacted files without copying them.
207
+ #
208
+ # @internal
209
+ # @return [void]
210
+ def inherit_impacted_files(custom_impacted_files)
211
+ @inherited_custom_impacted_files = custom_impacted_files
212
+ nil
213
+ end
214
+
215
+ # Locks custom impacted files against further changes and returns them to
216
+ # Test Impact Analysis.
217
+ #
218
+ # @internal
219
+ # @return [Array<String>]
220
+ def lock_custom_impacted_files
221
+ return @custom_impacted_files if @custom_impacted_files.frozen?
222
+
223
+ @custom_impacted_files =
224
+ (@inherited_custom_impacted_files | @custom_impacted_files).freeze
225
+ end
226
+
160
227
  # Sets the status of the span to "pass".
161
228
  # @return [void]
162
229
  def passed!
@@ -4,6 +4,7 @@ require "fileutils"
4
4
  require "json"
5
5
  require_relative "../ext/test"
6
6
  require_relative "../ext/test_discovery"
7
+ require_relative "../utils/test_name"
7
8
 
8
9
  module Datadog
9
10
  module CI
@@ -76,6 +77,9 @@ module Datadog
76
77
  end
77
78
 
78
79
  def record_test(name:, suite:, module_name:, parameters:, source_file:)
80
+ name = name.nil? ? nil : Utils::TestName.normalize(name)
81
+ suite = suite.nil? ? nil : Utils::TestName.normalize(suite)
82
+
79
83
  test_info = {
80
84
  "name" => name,
81
85
  "suite" => suite,
@@ -16,6 +16,7 @@ require_relative "../utils/stateful"
16
16
  require_relative "../utils/telemetry"
17
17
 
18
18
  require_relative "coverage/event"
19
+ require_relative "coverage/files"
19
20
  require_relative "skippable"
20
21
  require_relative "telemetry"
21
22
 
@@ -187,6 +188,8 @@ module Datadog
187
188
  # @param test [Datadog::CI::Test] The test that is starting
188
189
  # @return [void]
189
190
  def on_test_started(test)
191
+ inherit_suite_impacted_files(test) if test_skipping_mode?
192
+
190
193
  return if !enabled? || !code_coverage?
191
194
  return if suite_skipping_mode?
192
195
 
@@ -215,7 +218,11 @@ module Datadog
215
218
  # @return [Datadog::CI::TestImpactAnalysis::Coverage::Event, nil] The coverage event or nil
216
219
  def on_test_finished(test, context)
217
220
  return unless enabled?
218
- return if suite_skipping_mode?
221
+
222
+ if suite_skipping_mode?
223
+ test.test_suite&.add_impacted_files(test.lock_custom_impacted_files)
224
+ return
225
+ end
219
226
 
220
227
  # Handle ITR statistics
221
228
  if test.skipped_by_test_impact_analysis?
@@ -243,7 +250,8 @@ module Datadog
243
250
  test_suite_id: test.test_suite_id.to_s,
244
251
  test_session_id: test.test_session_id.to_s,
245
252
  source_file: test.source_file,
246
- coverage: coverage
253
+ coverage: coverage,
254
+ custom_impacted_files: test.lock_custom_impacted_files
247
255
  )
248
256
  end
249
257
 
@@ -350,7 +358,8 @@ module Datadog
350
358
  test_suite_id: test_suite.id.to_s,
351
359
  test_session_id: test_suite.get_tag(Ext::Test::TAG_TEST_SESSION_ID).to_s,
352
360
  source_file: test_suite.source_file,
353
- coverage: coverage
361
+ coverage: coverage,
362
+ custom_impacted_files: test_suite.lock_custom_impacted_files
354
363
  )
355
364
  end
356
365
 
@@ -451,7 +460,7 @@ module Datadog
451
460
  return unless @static_dependencies_tracking_enabled
452
461
 
453
462
  static_dependencies_map = {}
454
- coverage.keys.each do |file|
463
+ coverage.each_key do |file|
455
464
  static_dependencies_map.merge!(
456
465
  Datadog::CI::SourceCode::StaticDependencies.fetch_static_dependencies(file)
457
466
  )
@@ -466,9 +475,16 @@ module Datadog
466
475
  coverage[absolute_test_source_file_path] = true
467
476
  end
468
477
 
469
- def write_coverage_event(test_id:, test_suite_id:, test_session_id:, source_file:, coverage:)
478
+ def write_coverage_event(
479
+ test_id:,
480
+ test_suite_id:,
481
+ test_session_id:,
482
+ source_file:,
483
+ coverage:,
484
+ custom_impacted_files: Coverage::Files::EMPTY_FILES
485
+ )
470
486
  coverage ||= {}
471
- if coverage.empty?
487
+ if coverage.empty? && custom_impacted_files.empty?
472
488
  Telemetry.code_coverage_is_empty
473
489
  return
474
490
  end
@@ -477,13 +493,18 @@ module Datadog
477
493
 
478
494
  enrich_coverage_with_static_dependencies(coverage)
479
495
 
480
- Telemetry.code_coverage_files(coverage.size)
496
+ # Avoid normalizing and deduplicating paths on the test thread. The
497
+ # writer does that when it serializes the event. Telemetry only needs
498
+ # an estimate and may count overlaps between the two collections.
499
+ Telemetry.code_coverage_files(coverage.size + custom_impacted_files.size)
500
+
501
+ files = Coverage::Files.new(coverage, custom_impacted_files)
481
502
 
482
503
  coverage_event = Coverage::Event.new(
483
504
  test_id: test_id,
484
505
  test_suite_id: test_suite_id,
485
506
  test_session_id: test_session_id,
486
- coverage: coverage
507
+ files: files
487
508
  )
488
509
 
489
510
  Datadog.logger.debug { "Writing coverage event \n #{coverage_event.pretty_inspect}" }
@@ -493,6 +514,13 @@ module Datadog
493
514
  coverage_event
494
515
  end
495
516
 
517
+ def inherit_suite_impacted_files(test)
518
+ test_suite = test.test_suite
519
+ return if test_suite.nil?
520
+
521
+ test.inherit_impacted_files(test_suite.lock_custom_impacted_files)
522
+ end
523
+
496
524
  def fetch_skippables(test_session)
497
525
  return unless skipping_tests? || skipping_suites?
498
526
 
@@ -2,27 +2,37 @@
2
2
 
3
3
  require "msgpack"
4
4
 
5
- require_relative "../../git/local_repository"
5
+ require_relative "files"
6
6
 
7
7
  module Datadog
8
8
  module CI
9
9
  module TestImpactAnalysis
10
10
  module Coverage
11
11
  class Event
12
- attr_reader :test_id, :test_suite_id, :test_session_id, :coverage
13
-
14
- def initialize(test_id:, test_suite_id:, test_session_id:, coverage:)
12
+ attr_reader :test_id, :test_suite_id, :test_session_id
13
+
14
+ def initialize(
15
+ test_id:,
16
+ test_suite_id:,
17
+ test_session_id:,
18
+ files:
19
+ )
15
20
  @test_id = test_id
16
21
  @test_suite_id = test_suite_id
17
22
  @test_session_id = test_session_id
18
- @coverage = coverage
23
+ @files = files
24
+ end
25
+
26
+ def inspect_coverage
27
+ @files.inspect_coverage
19
28
  end
20
29
 
21
30
  def valid?
22
31
  valid = true
23
32
 
24
- %i[test_suite_id test_session_id coverage].each do |key|
25
- next unless send(key).nil?
33
+ %i[test_suite_id test_session_id files].each do |key|
34
+ value = (key == :files) ? @files : send(key)
35
+ next unless value.nil?
26
36
 
27
37
  Datadog.logger.warn("citestcov event is invalid: [#{key}] is nil. Event: #{self}")
28
38
  valid = false
@@ -47,19 +57,18 @@ module Datadog
47
57
  packer.write(test_id.to_i)
48
58
  end
49
59
 
50
- files = coverage.keys
51
60
  packer.write("files")
52
- packer.write_array_header(files.size)
53
-
54
- files.each do |filename|
61
+ packer.write_array_header(@files.size)
62
+ @files.each do |filename|
55
63
  packer.write_map_header(1)
56
64
  packer.write("filename")
57
- packer.write(Git::LocalRepository.relative_to_root(filename))
65
+ packer.write(filename)
58
66
  end
59
67
  end
60
68
 
61
69
  def to_s
62
- "Coverage::Event[test_id=#{test_id}, test_suite_id=#{test_suite_id}, test_session_id=#{test_session_id}, coverage=#{coverage}]"
70
+ coverage_value = @files.nil? ? nil : inspect_coverage
71
+ "Coverage::Event[test_id=#{test_id}, test_suite_id=#{test_suite_id}, test_session_id=#{test_session_id}, coverage=#{coverage_value}]"
63
72
  end
64
73
 
65
74
  # Return a human readable version of the event
@@ -70,8 +79,8 @@ module Datadog
70
79
  q.text "Test Suite ID: #{@test_suite_id}\n"
71
80
  q.text "Test Session ID: #{@test_session_id}\n"
72
81
  q.group(2, "Files: [", "]\n") do
73
- q.seplist @coverage.keys.each do |key|
74
- q.text key
82
+ q.seplist @files do |filename|
83
+ q.text filename
75
84
  end
76
85
  end
77
86
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../git/local_repository"
4
+
5
+ module Datadog
6
+ module CI
7
+ module TestImpactAnalysis
8
+ module Coverage
9
+ # Keeps native coverage and custom impacted files together and
10
+ # normalizes them as one set.
11
+ #
12
+ # @internal
13
+ class Files
14
+ EMPTY_FILES = [].freeze
15
+
16
+ def initialize(coverage, custom_impacted_files = EMPTY_FILES)
17
+ @coverage = coverage
18
+ @custom_impacted_files = custom_impacted_files
19
+ @normalized_files = nil
20
+ end
21
+
22
+ def each
23
+ normalized_files.each { |file| yield file }
24
+ end
25
+
26
+ def size
27
+ normalized_files.size
28
+ end
29
+
30
+ # Returns a readable view while preserving the paths supplied by
31
+ # native coverage and the custom impacted-files API. Serialized files
32
+ # are normalized through {#each}.
33
+ def inspect_coverage
34
+ coverage = @coverage.dup
35
+ @custom_impacted_files.each do |file|
36
+ coverage[file] = true
37
+ end
38
+ coverage
39
+ end
40
+
41
+ private
42
+
43
+ def normalized_files
44
+ @normalized_files ||= begin
45
+ files = []
46
+ @coverage.each_key do |file|
47
+ relative_file = Git::LocalRepository.relative_to_root(file)
48
+ files << relative_file unless relative_file.empty?
49
+ end
50
+ @custom_impacted_files.each do |file|
51
+ # The public API defines relative custom paths as repository-relative.
52
+ relative_file = if File.absolute_path?(file)
53
+ Git::LocalRepository.relative_to_root(file)
54
+ else
55
+ file
56
+ end
57
+ files << relative_file unless relative_file.empty?
58
+ end
59
+ files.uniq
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -16,6 +16,8 @@ module Datadog
16
16
  def initialize(tracer_span)
17
17
  super
18
18
 
19
+ @custom_impacted_files = []
20
+
19
21
  # counts how many times every test in this suite was executed with each status:
20
22
  # { "MySuite.mytest.a:1" => { "pass" => 3, "fail" => 2 } }
21
23
  @execution_stats_per_test = {}
@@ -25,6 +27,63 @@ module Datadog
25
27
  @final_statuses_per_test = {}
26
28
  end
27
29
 
30
+ # Adds files that Test Impact Analysis should consider capable of
31
+ # impacting every test in this suite.
32
+ #
33
+ # In test-level skipping mode, custom impacted files must be added before
34
+ # the first test in this suite starts. In suite-level skipping mode, they
35
+ # may be added until the suite finishes.
36
+ #
37
+ # This is useful for files shared by every test that Datadog's native
38
+ # Ruby coverage cannot observe, such as JavaScript test setup. Calls are
39
+ # incremental: every call adds files for the remainder of the suite.
40
+ #
41
+ # Paths must resolve inside the Git repository. Relative paths must be
42
+ # relative to the repository root. Absolute paths are also accepted.
43
+ #
44
+ # If paths are relative to the current working directory, convert them to
45
+ # absolute paths with +File.expand_path+ before calling this method.
46
+ # Submitted paths must be lexically normalized without redundant +.+ or
47
+ # +..+ components. Resolution does not access the filesystem. Paths
48
+ # outside the repository are ignored when the coverage event is
49
+ # serialized.
50
+ #
51
+ # @example Register JavaScript setup shared by an RSpec suite
52
+ # before(:context) do
53
+ # Datadog::CI.active_test_suite&.add_impacted_files(
54
+ # ["app/frontend/test_setup.js"]
55
+ # )
56
+ # end
57
+ #
58
+ # @raise [RuntimeError] if test-level skipping is active and a test in
59
+ # this suite has already started
60
+ # @param [Array<String>] file_paths custom paths that can impact this suite
61
+ # @raise [ArgumentError] if file_paths is not an Array
62
+ # @return [void]
63
+ def add_impacted_files(file_paths)
64
+ raise ArgumentError, "file_paths must be an Array" unless file_paths.is_a?(Array)
65
+
66
+ synchronize do
67
+ ensure_custom_impacted_files_mutable!
68
+ @custom_impacted_files.concat(file_paths)
69
+ end
70
+ nil
71
+ end
72
+
73
+ # Locks suite-level custom impacted files against further changes and
74
+ # returns them to Test Impact Analysis.
75
+ #
76
+ # @internal
77
+ # @return [Array<String>]
78
+ def lock_custom_impacted_files
79
+ synchronize do
80
+ unless @custom_impacted_files.frozen?
81
+ @custom_impacted_files = @custom_impacted_files.uniq.freeze
82
+ end
83
+ @custom_impacted_files
84
+ end
85
+ end
86
+
28
87
  # Finishes this test suite.
29
88
  # @return [void]
30
89
  def finish
@@ -133,6 +192,14 @@ module Datadog
133
192
 
134
193
  private
135
194
 
195
+ def ensure_custom_impacted_files_mutable!
196
+ return unless @custom_impacted_files.frozen?
197
+
198
+ raise(
199
+ "Custom impacted files must be added to a test suite before the first test in the suite starts"
200
+ )
201
+ end
202
+
136
203
  def set_status_from_stats!
137
204
  synchronize do
138
205
  # count how many tests have each final status
@@ -16,6 +16,7 @@ require_relative "../ext/test"
16
16
  require_relative "../git/local_repository"
17
17
  require_relative "../utils/file_storage"
18
18
  require_relative "../utils/stateful"
19
+ require_relative "../utils/test_name"
19
20
 
20
21
  require_relative "../worker"
21
22
 
@@ -109,6 +110,7 @@ module Datadog
109
110
  def start_test_suite(test_suite_name, service: nil, tags: {})
110
111
  return skip_tracing unless test_suite_level_visibility_enabled
111
112
 
113
+ test_suite_name = Utils::TestName.normalize(test_suite_name)
112
114
  context = @local_test_suites_mode ? @context : maybe_remote_context
113
115
 
114
116
  test_suite = context.start_test_suite(test_suite_name, service: service, tags: tags)
@@ -117,6 +119,9 @@ module Datadog
117
119
  end
118
120
 
119
121
  def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
122
+ test_name = Utils::TestName.normalize(test_name)
123
+ test_suite_name = Utils::TestName.normalize(test_suite_name)
124
+
120
125
  test_suite = active_test_suite(test_suite_name)
121
126
  tags[Ext::Test::TAG_SUITE] ||= test_suite_name
122
127
 
@@ -165,6 +170,8 @@ module Datadog
165
170
  # we return the single active test suite because most of the time there is only one test suite running
166
171
  return single_active_test_suite if test_suite_name.nil?
167
172
 
173
+ test_suite_name = Utils::TestName.normalize(test_suite_name)
174
+
168
175
  # when fetching test_suite to use as test's context, try local context instance first
169
176
  local_test_suite = @context.active_test_suite(test_suite_name)
170
177
  return local_test_suite if local_test_suite
@@ -194,6 +201,7 @@ module Datadog
194
201
  end
195
202
 
196
203
  def deactivate_test_suite(test_suite_name)
204
+ test_suite_name = Utils::TestName.normalize(test_suite_name)
197
205
  test_suite = active_test_suite(test_suite_name)
198
206
  on_test_suite_finished(test_suite) if test_suite
199
207
 
@@ -12,7 +12,7 @@ module Datadog
12
12
  return
13
13
  end
14
14
 
15
- unless ::SimpleCov.running
15
+ unless simplecov_running?
16
16
  Datadog.logger.debug("SimpleCov is not running, skipping code coverage extraction")
17
17
  return
18
18
  end
@@ -30,6 +30,16 @@ module Datadog
30
30
 
31
31
  test_session.set_tag(Ext::Test::TAG_CODE_COVERAGE_LINES_PCT, result.covered_percent)
32
32
  end
33
+
34
+ # SimpleCov 1.0 removed the `running` accessor and relies on Ruby's
35
+ # Coverage.running? instead.
36
+ def self.simplecov_running?
37
+ return ::SimpleCov.running if ::SimpleCov.respond_to?(:running)
38
+ return false unless defined?(::Coverage)
39
+
40
+ ::Coverage.running?
41
+ end
42
+ private_class_method :simplecov_running?
33
43
  end
34
44
  end
35
45
  end