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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -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/contrib/minitest/helpers.rb +21 -6
- data/lib/datadog/ci/contrib/rspec/anonymous_example_name.rb +515 -0
- data/lib/datadog/ci/contrib/rspec/example.rb +23 -5
- data/lib/datadog/ci/contrib/simplecov/result_extractor.rb +9 -1
- 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_discovery/component.rb +4 -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/component.rb +8 -0
- data/lib/datadog/ci/test_tracing/deprecated_total_coverage_metric.rb +11 -1
- data/lib/datadog/ci/test_tracing/known_tests.rb +20 -0
- data/lib/datadog/ci/utils/test_name.rb +112 -0
- data/lib/datadog/ci/version.rb +1 -1
- metadata +4 -1
|
@@ -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
|
-
|
|
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,
|
|
@@ -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!
|
|
@@ -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
|
-
|
|
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
|
|
@@ -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
|
|
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
|