datadog-ci 1.35.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1aa81f9987d876f143abe3a9b7721e1a5b35178c5c854dcdaf79b872c5a0c9e
4
- data.tar.gz: 261ea8b1b233ac66c0ba7b417018fe477afb05a274ed6607b6bf3c235c58c3ff
3
+ metadata.gz: 35241d1cd03e2ca4c3eca7c92979991abf620452a57b25bb1452f799a973706b
4
+ data.tar.gz: 953eba8e467af1a595ba9f75a5d017dbc147002e8cd938d07df5162efffb3e24
5
5
  SHA512:
6
- metadata.gz: 05aed65898a6b065f99e6a9fd2aab34023fd3ac77aaacc4146d39d8a1e6653ad5cb6d970afc5e6d590a4ddf6eec3f54704089a5ea49d7bf2f614d27d1572ed91
7
- data.tar.gz: f89daf423afed1bd0ac7c227a28c4a980b92916fcf03a078a9278eb880e059c9fb40f8dc3b0887645e8a806f2e879e70bf23eaaa7500cd3e6e6e2d4b518a449f
6
+ metadata.gz: 0eb4f023eccfd837e7bcd3f4c67ff3f727e1afb41c1f4f7078a81bb3a856b42c6c35fb003e94227911755f06698d829a48aefd97cf75edca968ac15806e36e61
7
+ data.tar.gz: 169fa6ad3fe020aed1293fbc80e893715c0f0ff23b0fc089b382876b2b29fe37a0e0d8b205a945e4e0d7184a01cb559a2ea814fdf47baa4dbc4024b7fc5124b6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.36.0] - 2026-08-03
4
+
5
+ ### Added
6
+
7
+ * Support custom impacted files in Test Impact Analysis ([#554][])
8
+ * Add ci.pipeline.display_name tag for Buildkite ([#555][])
9
+ * Add code coverage report flags ([#542][])
10
+
3
11
  ## [1.35.0] - 2026-07-23
4
12
 
5
13
  ## [1.34.0] - 2026-06-25
@@ -667,7 +675,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil
667
675
 
668
676
  - Ruby versions < 2.7 no longer supported ([#8][])
669
677
 
670
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.35.0...main
678
+ [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.36.0...main
679
+ [1.36.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.35.0...v1.36.0
671
680
  [1.35.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.34.0...v1.35.0
672
681
  [1.34.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.33.0...v1.34.0
673
682
  [1.33.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.32.0...v1.33.0
@@ -944,4 +953,7 @@ Currently test suite level visibility is not used by our instrumentation: it wil
944
953
  [#521]: https://github.com/DataDog/datadog-ci-rb/issues/521
945
954
  [#522]: https://github.com/DataDog/datadog-ci-rb/issues/522
946
955
  [#527]: https://github.com/DataDog/datadog-ci-rb/issues/527
947
- [#530]: https://github.com/DataDog/datadog-ci-rb/issues/530
956
+ [#530]: https://github.com/DataDog/datadog-ci-rb/issues/530
957
+ [#542]: https://github.com/DataDog/datadog-ci-rb/issues/542
958
+ [#554]: https://github.com/DataDog/datadog-ci-rb/issues/554
959
+ [#555]: https://github.com/DataDog/datadog-ci-rb/issues/555
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../ext/environment"
4
+ require_relative "../ext/settings"
4
5
  require_relative "transport"
5
6
 
6
7
  module Datadog
@@ -10,12 +11,14 @@ module Datadog
10
11
  # to Datadog's Code Coverage product.
11
12
  class Component
12
13
  COVERAGE_REPORT_TYPE = "coverage_report"
14
+ MAX_REPORT_FLAGS = 32
13
15
 
14
16
  attr_reader :enabled
15
17
 
16
- def initialize(enabled:, transport:)
18
+ def initialize(enabled:, transport:, flags:)
17
19
  @enabled = enabled
18
20
  @transport = transport
21
+ @flags = parse_flags(flags)
19
22
  end
20
23
 
21
24
  def configure(library_configuration)
@@ -44,10 +47,31 @@ module Datadog
44
47
  private
45
48
 
46
49
  def build_event(format)
47
- {
50
+ event = {
48
51
  "type" => COVERAGE_REPORT_TYPE,
49
52
  "format" => format
50
53
  }.merge(Ext::Environment.tags(ENV))
54
+
55
+ return event unless @flags
56
+
57
+ event.merge("report.flags" => @flags)
58
+ end
59
+
60
+ def parse_flags(value)
61
+ return if value.nil?
62
+
63
+ flags = value.split(",").map(&:strip).reject(&:empty?)
64
+ return if flags.empty?
65
+
66
+ if flags.length > MAX_REPORT_FLAGS
67
+ Datadog.logger.warn(
68
+ "#{Ext::Settings::ENV_CODE_COVERAGE_FLAGS} contains #{flags.length} flags, " \
69
+ "but only #{MAX_REPORT_FLAGS} are allowed; omitting report flags"
70
+ )
71
+ return
72
+ end
73
+
74
+ flags.freeze
51
75
  end
52
76
  end
53
77
  end
@@ -312,7 +312,8 @@ module Datadog
312
312
 
313
313
  CodeCoverage::Component.new(
314
314
  enabled: settings.ci.code_coverage_report_upload_enabled,
315
- transport: CodeCoverage::Transport.new(api: api)
315
+ transport: CodeCoverage::Transport.new(api: api),
316
+ flags: settings.ci.code_coverage_flags
316
317
  )
317
318
  end
318
319
 
@@ -224,6 +224,11 @@ module Datadog
224
224
  o.default true
225
225
  end
226
226
 
227
+ option :code_coverage_flags do |o|
228
+ o.type :string, nilable: true
229
+ o.env CI::Ext::Settings::ENV_CODE_COVERAGE_FLAGS
230
+ end
231
+
227
232
  option :runtime_tags_overrides do |o|
228
233
  o.type :string, nilable: true
229
234
  o.env CI::Ext::Settings::ENV_RUNTIME_TAGS
@@ -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!
@@ -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
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "json"
4
4
 
5
+ require "datadog/core/utils/time"
6
+
5
7
  require_relative "../ext/telemetry"
6
8
  require_relative "../ext/transport"
7
9
  require_relative "../transport/telemetry"
@@ -14,6 +16,8 @@ module Datadog
14
16
  # fetches and stores a list of known tests from the backend
15
17
  class KnownTests
16
18
  class Response
19
+ attr_reader :http_response
20
+
17
21
  def self.from_http_response(http_response)
18
22
  new(http_response, nil)
19
23
  end
@@ -94,9 +98,12 @@ module Datadog
94
98
  return Set.new unless api
95
99
 
96
100
  result = Set.new
101
+ total_request_ms = 0.0
97
102
  page_state = nil
98
103
  page_number = 1
99
104
 
105
+ fetch_start_time = Core::Utils::Time.get_time(:float_millisecond)
106
+
100
107
  loop do
101
108
  Datadog.logger.debug { "Fetching known tests page ##{page_number}#{" with cursor" if page_state}" }
102
109
 
@@ -114,6 +121,10 @@ module Datadog
114
121
  return Set.new
115
122
  end
116
123
 
124
+ # @type var response: Datadog::CI::TestTracing::KnownTests::Response
125
+ http_response = response.http_response
126
+ total_request_ms += http_response.duration_ms if http_response
127
+
117
128
  page_tests = response.tests
118
129
  result.merge(page_tests)
119
130
  Datadog.logger.debug { "Received #{page_tests.size} known tests from page ##{page_number} (total so far: #{result.size})" }
@@ -127,7 +138,16 @@ module Datadog
127
138
  page_number += 1
128
139
  end
129
140
 
141
+ fetch_duration_ms = Core::Utils::Time.get_time(:float_millisecond) - fetch_start_time
142
+ # @type var fetch_duration_ms: Float
143
+
130
144
  Datadog.logger.debug { "Finished fetching known tests: #{result.size} tests total from #{page_number} page(s)" }
145
+
146
+ # Emit pagination aggregate metrics
147
+ Utils::Telemetry.distribution(Ext::Telemetry::METRIC_KNOWN_TESTS_PAGES_FETCHED, page_number.to_f)
148
+ Utils::Telemetry.distribution(Ext::Telemetry::METRIC_KNOWN_TESTS_TOTAL_FETCH_MS, fetch_duration_ms)
149
+ Utils::Telemetry.distribution(Ext::Telemetry::METRIC_KNOWN_TESTS_TOTAL_REQUEST_MS, total_request_ms)
150
+
131
151
  result
132
152
  end
133
153
 
@@ -4,7 +4,7 @@ module Datadog
4
4
  module CI
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 35
7
+ MINOR = 36
8
8
  PATCH = 0
9
9
  PRE = nil
10
10
  BUILD = nil
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog-ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.35.0
4
+ version: 1.36.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
@@ -248,6 +248,7 @@ files:
248
248
  - lib/datadog/ci/test_impact_analysis/component.rb
249
249
  - lib/datadog/ci/test_impact_analysis/coverage/ddcov.rb
250
250
  - lib/datadog/ci/test_impact_analysis/coverage/event.rb
251
+ - lib/datadog/ci/test_impact_analysis/coverage/files.rb
251
252
  - lib/datadog/ci/test_impact_analysis/coverage/transport.rb
252
253
  - lib/datadog/ci/test_impact_analysis/null_component.rb
253
254
  - lib/datadog/ci/test_impact_analysis/skippable.rb