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 +4 -4
- data/CHANGELOG.md +14 -2
- data/lib/datadog/ci/code_coverage/component.rb +26 -2
- data/lib/datadog/ci/configuration/components.rb +2 -1
- data/lib/datadog/ci/configuration/settings.rb +5 -0
- data/lib/datadog/ci/ext/environment/extractor.rb +1 -0
- data/lib/datadog/ci/ext/environment/providers/base.rb +3 -0
- data/lib/datadog/ci/ext/environment/providers/buildkite.rb +4 -0
- data/lib/datadog/ci/ext/environment.rb +1 -0
- data/lib/datadog/ci/ext/settings.rb +1 -0
- data/lib/datadog/ci/ext/telemetry.rb +3 -0
- data/lib/datadog/ci/test.rb +67 -0
- data/lib/datadog/ci/test_impact_analysis/component.rb +36 -8
- data/lib/datadog/ci/test_impact_analysis/coverage/event.rb +24 -15
- data/lib/datadog/ci/test_impact_analysis/coverage/files.rb +66 -0
- data/lib/datadog/ci/test_suite.rb +67 -0
- data/lib/datadog/ci/test_tracing/known_tests.rb +20 -0
- data/lib/datadog/ci/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35241d1cd03e2ca4c3eca7c92979991abf620452a57b25bb1452f799a973706b
|
|
4
|
+
data.tar.gz: 953eba8e467af1a595ba9f75a5d017dbc147002e8cd938d07df5162efffb3e24
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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,
|
|
@@ -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"
|
data/lib/datadog/ci/test.rb
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 "
|
|
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
|
|
13
|
-
|
|
14
|
-
def initialize(
|
|
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
|
-
@
|
|
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
|
|
25
|
-
|
|
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(
|
|
65
|
+
packer.write(filename)
|
|
58
66
|
end
|
|
59
67
|
end
|
|
60
68
|
|
|
61
69
|
def to_s
|
|
62
|
-
|
|
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 @
|
|
74
|
-
q.text
|
|
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
|
|
data/lib/datadog/ci/version.rb
CHANGED
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.
|
|
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
|