datadog-ci 1.26.0 → 1.27.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 +32 -2
- data/lib/datadog/ci/code_coverage/component.rb +55 -0
- data/lib/datadog/ci/code_coverage/null_component.rb +24 -0
- data/lib/datadog/ci/code_coverage/transport.rb +66 -0
- data/lib/datadog/ci/configuration/components.rb +17 -1
- data/lib/datadog/ci/configuration/settings.rb +20 -2
- data/lib/datadog/ci/contrib/minitest/test.rb +1 -1
- data/lib/datadog/ci/contrib/rspec/example.rb +48 -8
- data/lib/datadog/ci/contrib/rspec/example_group.rb +63 -31
- data/lib/datadog/ci/contrib/simplecov/ext.rb +2 -0
- data/lib/datadog/ci/contrib/simplecov/patcher.rb +2 -0
- data/lib/datadog/ci/contrib/simplecov/report_uploader.rb +59 -0
- data/lib/datadog/ci/ext/environment/providers/github_actions.rb +65 -2
- data/lib/datadog/ci/ext/environment.rb +10 -0
- data/lib/datadog/ci/ext/settings.rb +2 -0
- data/lib/datadog/ci/ext/telemetry.rb +5 -0
- data/lib/datadog/ci/ext/test.rb +0 -5
- data/lib/datadog/ci/ext/transport.rb +4 -0
- data/lib/datadog/ci/git/cli.rb +59 -1
- data/lib/datadog/ci/remote/component.rb +6 -1
- data/lib/datadog/ci/remote/library_settings.rb +8 -0
- data/lib/datadog/ci/test.rb +27 -18
- data/lib/datadog/ci/test_management/component.rb +9 -0
- data/lib/datadog/ci/test_management/null_component.rb +8 -0
- data/lib/datadog/ci/test_optimisation/component.rb +168 -16
- data/lib/datadog/ci/test_optimisation/null_component.rb +19 -5
- data/lib/datadog/ci/test_suite.rb +16 -21
- data/lib/datadog/ci/test_visibility/component.rb +1 -2
- data/lib/datadog/ci/test_visibility/known_tests.rb +59 -6
- data/lib/datadog/ci/transport/api/agentless.rb +8 -1
- data/lib/datadog/ci/transport/api/base.rb +21 -0
- data/lib/datadog/ci/transport/api/builder.rb +5 -1
- data/lib/datadog/ci/transport/api/evp_proxy.rb +8 -0
- data/lib/datadog/ci/version.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 47e2dae93f942112cea718e6772f7c586214d97a4d3fb9d8ecf9643826c4e9a0
|
|
4
|
+
data.tar.gz: 80558ee95a33818f80f937981b9c0533c8e638444ac181a31683ea22d53c7ad7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d442d8921f4ba7fd8ae372544f518edeaac97a6a4b89aa52d2cc8e6ff8035e3131d61104fc4bce7030634970fc2cd52a13d929944ad08497e7abfcf174634ccb
|
|
7
|
+
data.tar.gz: 4f58631b0792099fdff630a65c10f162e3c933ac2e5c32b53addbaebc4533daf4c87ae7112b69abbd4cf113257a4ac1cc58dc59fb2f8b63184ba3f9abb1aec8d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.27.0] - 2026-01-22
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
* Add context-level code coverage tracking for RSpec to improve Test Impact Analysis correctness ([#460][])
|
|
9
|
+
* Add automatic uploading of code coverage reports to Datadog Code Coverage product ([#456][])
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
* Use pagination for known tests API ([#455][]) ([#458][])
|
|
14
|
+
* GitHub Actions: extract numeric job ID for accurate job URLs ([#457][])
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
* RSpec: take into account Test Management disabled tests when deciding to skip context hooks ([#463][])
|
|
19
|
+
* Fix for attempt-to-fix flow: don't mask failures for flaky tests being fixed ([#464][])
|
|
20
|
+
* Pass -c safe.directory to all git commands to fix dubious ownership errors ([#454][])
|
|
21
|
+
* fix: rename DD_TRACE_CI_ENABLED to DD_CIVISIBILITY_ENABLED with backwards compatibility ([#453][])
|
|
22
|
+
|
|
3
23
|
## [1.26.0] - 2026-01-09
|
|
4
24
|
|
|
5
25
|
========== Changelog ==========
|
|
@@ -578,7 +598,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
|
578
598
|
|
|
579
599
|
- Ruby versions < 2.7 no longer supported ([#8][])
|
|
580
600
|
|
|
581
|
-
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.
|
|
601
|
+
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.27.0...main
|
|
602
|
+
[1.27.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.26.0...v1.27.0
|
|
582
603
|
[1.26.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.25.0...v1.26.0
|
|
583
604
|
[1.25.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.24.0...v1.25.0
|
|
584
605
|
[1.24.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.23.3...v1.24.0
|
|
@@ -819,4 +840,13 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
|
819
840
|
[#440]: https://github.com/DataDog/datadog-ci-rb/issues/440
|
|
820
841
|
[#442]: https://github.com/DataDog/datadog-ci-rb/issues/442
|
|
821
842
|
[#444]: https://github.com/DataDog/datadog-ci-rb/issues/444
|
|
822
|
-
[#446]: https://github.com/DataDog/datadog-ci-rb/issues/446
|
|
843
|
+
[#446]: https://github.com/DataDog/datadog-ci-rb/issues/446
|
|
844
|
+
[#453]: https://github.com/DataDog/datadog-ci-rb/issues/453
|
|
845
|
+
[#454]: https://github.com/DataDog/datadog-ci-rb/issues/454
|
|
846
|
+
[#455]: https://github.com/DataDog/datadog-ci-rb/issues/455
|
|
847
|
+
[#456]: https://github.com/DataDog/datadog-ci-rb/issues/456
|
|
848
|
+
[#457]: https://github.com/DataDog/datadog-ci-rb/issues/457
|
|
849
|
+
[#458]: https://github.com/DataDog/datadog-ci-rb/issues/458
|
|
850
|
+
[#460]: https://github.com/DataDog/datadog-ci-rb/issues/460
|
|
851
|
+
[#463]: https://github.com/DataDog/datadog-ci-rb/issues/463
|
|
852
|
+
[#464]: https://github.com/DataDog/datadog-ci-rb/issues/464
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../ext/environment"
|
|
4
|
+
require_relative "transport"
|
|
5
|
+
|
|
6
|
+
module Datadog
|
|
7
|
+
module CI
|
|
8
|
+
module CodeCoverage
|
|
9
|
+
# CodeCoverage component is responsible for uploading code coverage reports
|
|
10
|
+
# to Datadog's Code Coverage product.
|
|
11
|
+
class Component
|
|
12
|
+
COVERAGE_REPORT_TYPE = "coverage_report"
|
|
13
|
+
|
|
14
|
+
attr_reader :enabled
|
|
15
|
+
|
|
16
|
+
def initialize(enabled:, transport:)
|
|
17
|
+
@enabled = enabled
|
|
18
|
+
@transport = transport
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def configure(library_configuration)
|
|
22
|
+
@enabled &&= library_configuration.coverage_report_upload_enabled?
|
|
23
|
+
|
|
24
|
+
Datadog.logger.debug do
|
|
25
|
+
"[#{self.class.name}] Configured with enabled=#{@enabled}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def upload(serialized_report:, format:)
|
|
30
|
+
return unless @enabled
|
|
31
|
+
return if serialized_report.nil?
|
|
32
|
+
|
|
33
|
+
Datadog.logger.debug { "[#{self.class.name}] Uploading coverage report..." }
|
|
34
|
+
|
|
35
|
+
event = build_event(format)
|
|
36
|
+
|
|
37
|
+
@transport.send_coverage_report(event: event, coverage_report: serialized_report)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def shutdown!
|
|
41
|
+
# noop - transport is synchronous
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def build_event(format)
|
|
47
|
+
{
|
|
48
|
+
"type" => COVERAGE_REPORT_TYPE,
|
|
49
|
+
"format" => format
|
|
50
|
+
}.merge(Ext::Environment.tags(ENV))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module CI
|
|
5
|
+
module CodeCoverage
|
|
6
|
+
class NullComponent
|
|
7
|
+
attr_reader :enabled
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@enabled = false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def configure(library_configuration)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def upload(serialized_report:, format:)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def shutdown!
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
require_relative "../ext/telemetry"
|
|
6
|
+
require_relative "../ext/transport"
|
|
7
|
+
require_relative "../transport/gzip"
|
|
8
|
+
require_relative "../transport/telemetry"
|
|
9
|
+
require_relative "../utils/telemetry"
|
|
10
|
+
|
|
11
|
+
module Datadog
|
|
12
|
+
module CI
|
|
13
|
+
module CodeCoverage
|
|
14
|
+
class Transport
|
|
15
|
+
attr_reader :api
|
|
16
|
+
|
|
17
|
+
def initialize(api:)
|
|
18
|
+
@api = api
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def send_coverage_report(event:, coverage_report:)
|
|
22
|
+
return nil if api.nil?
|
|
23
|
+
|
|
24
|
+
Datadog.logger.debug { "[#{self.class.name}] Sending coverage report..." }
|
|
25
|
+
|
|
26
|
+
compressed_coverage_report = CI::Transport::Gzip.compress(coverage_report)
|
|
27
|
+
event_json = event.to_json
|
|
28
|
+
|
|
29
|
+
response = api.cicovreprt_request(
|
|
30
|
+
path: Ext::Transport::CODE_COVERAGE_REPORT_INTAKE_PATH,
|
|
31
|
+
event_payload: event_json,
|
|
32
|
+
compressed_coverage_report: compressed_coverage_report
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
CI::Transport::Telemetry.api_requests(
|
|
36
|
+
Ext::Telemetry::METRIC_COVERAGE_UPLOAD_REQUEST,
|
|
37
|
+
1,
|
|
38
|
+
compressed: response.request_compressed
|
|
39
|
+
)
|
|
40
|
+
Utils::Telemetry.distribution(
|
|
41
|
+
Ext::Telemetry::METRIC_COVERAGE_UPLOAD_REQUEST_MS,
|
|
42
|
+
response.duration_ms
|
|
43
|
+
)
|
|
44
|
+
Utils::Telemetry.distribution(
|
|
45
|
+
Ext::Telemetry::METRIC_COVERAGE_UPLOAD_REQUEST_BYTES,
|
|
46
|
+
compressed_coverage_report.bytesize.to_f,
|
|
47
|
+
{Ext::Telemetry::TAG_REQUEST_COMPRESSED => response.request_compressed.to_s}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
unless response.ok?
|
|
51
|
+
CI::Transport::Telemetry.api_requests_errors(
|
|
52
|
+
Ext::Telemetry::METRIC_COVERAGE_UPLOAD_REQUEST_ERRORS,
|
|
53
|
+
1,
|
|
54
|
+
error_type: response.telemetry_error_type,
|
|
55
|
+
status_code: response.code
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
Datadog.logger.warn { "[#{self.class.name}] Failed to send coverage report: #{response.inspect}" }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
response
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
require "datadog/core/telemetry/ext"
|
|
4
4
|
|
|
5
5
|
require_relative "../ext/settings"
|
|
6
|
+
require_relative "../code_coverage/component"
|
|
7
|
+
require_relative "../code_coverage/null_component"
|
|
8
|
+
require_relative "../code_coverage/transport"
|
|
6
9
|
require_relative "../git/tree_uploader"
|
|
7
10
|
require_relative "../impacted_tests_detection/component"
|
|
8
11
|
require_relative "../impacted_tests_detection/null_component"
|
|
@@ -42,7 +45,7 @@ module Datadog
|
|
|
42
45
|
# Adds CI behavior to Datadog trace components
|
|
43
46
|
module Components
|
|
44
47
|
attr_reader :test_visibility, :test_optimisation, :git_tree_upload_worker, :ci_remote, :test_retries,
|
|
45
|
-
:test_management, :agentless_logs_submission, :impacted_tests_detection, :test_discovery
|
|
48
|
+
:test_management, :agentless_logs_submission, :impacted_tests_detection, :test_discovery, :code_coverage
|
|
46
49
|
|
|
47
50
|
def initialize(settings)
|
|
48
51
|
@test_optimisation = TestOptimisation::NullComponent.new
|
|
@@ -53,6 +56,7 @@ module Datadog
|
|
|
53
56
|
@test_management = TestManagement::NullComponent.new
|
|
54
57
|
@impacted_tests_detection = ImpactedTestsDetection::NullComponent.new
|
|
55
58
|
@test_discovery = TestDiscovery::NullComponent.new
|
|
59
|
+
@code_coverage = CodeCoverage::NullComponent.new
|
|
56
60
|
|
|
57
61
|
# Activate CI mode if enabled
|
|
58
62
|
if settings.ci.enabled
|
|
@@ -69,6 +73,7 @@ module Datadog
|
|
|
69
73
|
@test_optimisation&.shutdown!
|
|
70
74
|
@agentless_logs_submission&.shutdown!
|
|
71
75
|
@test_discovery&.shutdown!
|
|
76
|
+
@code_coverage&.shutdown!
|
|
72
77
|
@git_tree_upload_worker&.stop
|
|
73
78
|
end
|
|
74
79
|
|
|
@@ -158,6 +163,8 @@ module Datadog
|
|
|
158
163
|
@agentless_logs_submission = build_agentless_logs_component(settings, test_visibility_api)
|
|
159
164
|
|
|
160
165
|
@impacted_tests_detection = ImpactedTestsDetection::Component.new(enabled: settings.ci.impacted_tests_detection_enabled)
|
|
166
|
+
|
|
167
|
+
@code_coverage = build_code_coverage(settings, test_visibility_api)
|
|
161
168
|
end
|
|
162
169
|
|
|
163
170
|
def build_test_optimisation(settings, test_visibility_api)
|
|
@@ -283,6 +290,15 @@ module Datadog
|
|
|
283
290
|
)
|
|
284
291
|
end
|
|
285
292
|
|
|
293
|
+
def build_code_coverage(settings, api)
|
|
294
|
+
return CodeCoverage::NullComponent.new if api.nil? || settings.ci.discard_traces
|
|
295
|
+
|
|
296
|
+
CodeCoverage::Component.new(
|
|
297
|
+
enabled: settings.ci.code_coverage_report_upload_enabled,
|
|
298
|
+
transport: CodeCoverage::Transport.new(api: api)
|
|
299
|
+
)
|
|
300
|
+
end
|
|
301
|
+
|
|
286
302
|
def build_agentless_logs_component(settings, api)
|
|
287
303
|
if settings.ci.agentless_logs_submission_enabled && !settings.ci.agentless_mode_enabled
|
|
288
304
|
Datadog.logger.warn(
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "../contrib/instrumentation"
|
|
4
4
|
require_relative "../ext/settings"
|
|
5
5
|
require_relative "../utils/bundle"
|
|
6
|
+
require_relative "../utils/parsing"
|
|
6
7
|
|
|
7
8
|
module Datadog
|
|
8
9
|
module CI
|
|
@@ -19,8 +20,19 @@ module Datadog
|
|
|
19
20
|
settings :ci do
|
|
20
21
|
option :enabled do |o|
|
|
21
22
|
o.type :bool
|
|
22
|
-
o.env CI::Ext::Settings::
|
|
23
|
-
o.default
|
|
23
|
+
o.env CI::Ext::Settings::ENV_ENABLED
|
|
24
|
+
o.default do
|
|
25
|
+
env_value = ENV[CI::Ext::Settings::ENV_MODE_ENABLED]
|
|
26
|
+
if env_value && !ENV[CI::Ext::Settings::ENV_ENABLED]
|
|
27
|
+
Datadog::Core.log_deprecation do
|
|
28
|
+
"#{CI::Ext::Settings::ENV_MODE_ENABLED} environment variable is deprecated, " \
|
|
29
|
+
"use #{CI::Ext::Settings::ENV_ENABLED} instead."
|
|
30
|
+
end
|
|
31
|
+
Utils::Parsing.convert_to_bool(env_value)
|
|
32
|
+
else
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
end
|
|
24
36
|
end
|
|
25
37
|
|
|
26
38
|
option :test_session_name do |o|
|
|
@@ -174,6 +186,12 @@ module Datadog
|
|
|
174
186
|
o.default false
|
|
175
187
|
end
|
|
176
188
|
|
|
189
|
+
option :code_coverage_report_upload_enabled do |o|
|
|
190
|
+
o.type :bool
|
|
191
|
+
o.env CI::Ext::Settings::ENV_CODE_COVERAGE_REPORT_UPLOAD_ENABLED
|
|
192
|
+
o.default true
|
|
193
|
+
end
|
|
194
|
+
|
|
177
195
|
define_method(:instrument) do |integration_name, options = {}, &block|
|
|
178
196
|
return unless enabled
|
|
179
197
|
|
|
@@ -64,7 +64,7 @@ module Datadog
|
|
|
64
64
|
|
|
65
65
|
finish_with_result(test_span, result_code)
|
|
66
66
|
|
|
67
|
-
# remove failures if
|
|
67
|
+
# remove failures if failure can be ignored because of retries
|
|
68
68
|
self.failures = [] if test_span.should_ignore_failures?
|
|
69
69
|
|
|
70
70
|
super
|
|
@@ -39,6 +39,9 @@ module Datadog
|
|
|
39
39
|
end_line = SourceCode::MethodInspect.last_line(@example_block)
|
|
40
40
|
tags[CI::Ext::Test::TAG_SOURCE_END] = end_line.to_s if end_line
|
|
41
41
|
|
|
42
|
+
# we keep track of the last test failure if we encounter any
|
|
43
|
+
test_failure = nil
|
|
44
|
+
|
|
42
45
|
test_retries_component.with_retries do
|
|
43
46
|
test_visibility_component.trace_test(
|
|
44
47
|
datadog_test_name,
|
|
@@ -46,6 +49,9 @@ module Datadog
|
|
|
46
49
|
tags: tags,
|
|
47
50
|
service: datadog_configuration[:service_name]
|
|
48
51
|
) do |test_span|
|
|
52
|
+
# Set context IDs on the test span for TIA context coverage merging
|
|
53
|
+
test_span&.context_ids = datadog_context_ids
|
|
54
|
+
|
|
49
55
|
test_span&.itr_unskippable! if datadog_unskippable?
|
|
50
56
|
|
|
51
57
|
metadata[:skip] = test_span&.datadog_skip_reason if test_span&.should_skip?
|
|
@@ -65,8 +71,10 @@ module Datadog
|
|
|
65
71
|
test_span&.passed!
|
|
66
72
|
when :failed
|
|
67
73
|
test_span&.failed!(exception: execution_result.exception)
|
|
74
|
+
|
|
68
75
|
# if any of the retries passed or test is quarantined, we don't fail the test run
|
|
69
76
|
@exception = nil if test_span&.should_ignore_failures?
|
|
77
|
+
test_failure = @exception
|
|
70
78
|
else
|
|
71
79
|
# :pending or nil
|
|
72
80
|
test_span&.skipped!(
|
|
@@ -92,6 +100,12 @@ module Datadog
|
|
|
92
100
|
metadata[Ext::METADATA_DD_SKIPPED_BY_ITR] = true
|
|
93
101
|
end
|
|
94
102
|
end
|
|
103
|
+
|
|
104
|
+
# at this point if we have encountered any test failure in any of the previous retries
|
|
105
|
+
# we restore the @exception internal state if we should not skip failures for this run
|
|
106
|
+
if test_failure && !test_span&.should_ignore_failures?
|
|
107
|
+
@exception = test_failure
|
|
108
|
+
end
|
|
95
109
|
end
|
|
96
110
|
end
|
|
97
111
|
|
|
@@ -172,19 +186,45 @@ module Datadog
|
|
|
172
186
|
Git::LocalRepository.relative_to_root(metadata[:rerun_file_path])
|
|
173
187
|
end
|
|
174
188
|
|
|
189
|
+
# Returns list of context IDs for this example, from outermost to innermost.
|
|
190
|
+
# Used for merging context-level coverage into test coverage.
|
|
191
|
+
def datadog_context_ids
|
|
192
|
+
traverse_example_group_hierarchy unless defined?(@datadog_context_ids)
|
|
193
|
+
@datadog_context_ids
|
|
194
|
+
end
|
|
195
|
+
|
|
175
196
|
private
|
|
176
197
|
|
|
177
|
-
def
|
|
198
|
+
def datadog_top_level_example_group
|
|
199
|
+
traverse_example_group_hierarchy unless defined?(@datadog_top_level_example_group)
|
|
200
|
+
@datadog_top_level_example_group
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Traverses the example group hierarchy once, populating both
|
|
204
|
+
# @datadog_context_ids and @top_level_example_group.
|
|
205
|
+
def traverse_example_group_hierarchy
|
|
206
|
+
context_ids = []
|
|
178
207
|
example_group = metadata[:example_group]
|
|
179
|
-
|
|
208
|
+
top_level = example_group
|
|
209
|
+
|
|
210
|
+
# Walk up the example group hierarchy
|
|
211
|
+
while example_group
|
|
212
|
+
# Use scoped_id as the stable identifier, fallback to file:line
|
|
213
|
+
context_id = example_group[:scoped_id] ||
|
|
214
|
+
"#{example_group[:file_path]}:#{example_group[:line_number]}"
|
|
215
|
+
|
|
216
|
+
context_ids << context_id
|
|
217
|
+
top_level = example_group
|
|
218
|
+
|
|
219
|
+
example_group = example_group[:parent_example_group]
|
|
220
|
+
end
|
|
180
221
|
|
|
181
|
-
|
|
222
|
+
@datadog_context_ids = context_ids
|
|
223
|
+
@datadog_top_level_example_group = top_level
|
|
182
224
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
res = parent
|
|
225
|
+
Datadog.logger.debug do
|
|
226
|
+
"RSpec: Built context chain for the test: #{context_ids.inspect}"
|
|
186
227
|
end
|
|
187
|
-
res
|
|
188
228
|
end
|
|
189
229
|
|
|
190
230
|
def datadog_integration
|
|
@@ -196,7 +236,7 @@ module Datadog
|
|
|
196
236
|
end
|
|
197
237
|
|
|
198
238
|
def datadog_test_suite_description
|
|
199
|
-
@datadog_test_suite_description ||=
|
|
239
|
+
@datadog_test_suite_description ||= datadog_top_level_example_group[:description]
|
|
200
240
|
end
|
|
201
241
|
|
|
202
242
|
def test_visibility_component
|
|
@@ -24,47 +24,79 @@ module Datadog
|
|
|
24
24
|
# even if it is a nested context
|
|
25
25
|
metadata[:skip] = true if all_examples_skipped_by_datadog?
|
|
26
26
|
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
27
|
+
# Start context coverage for this example group (for TIA suite-level coverage).
|
|
28
|
+
# This captures code executed in before(:context)/before(:all) hooks.
|
|
29
|
+
# The context_id uses scoped_id which is a stable identifier for RSpec example groups.
|
|
30
|
+
context_id = datadog_context_id
|
|
31
|
+
start_context_coverage(context_id)
|
|
32
|
+
|
|
33
|
+
begin
|
|
34
|
+
# return early because we create Datadog::CI::TestSuite only for top-level example groups
|
|
35
|
+
return super unless top_level?
|
|
36
|
+
|
|
37
|
+
suite_name = "#{description} at #{file_path}"
|
|
38
|
+
suite_tags = {
|
|
39
|
+
CI::Ext::Test::TAG_SOURCE_FILE => Git::LocalRepository.relative_to_root(metadata[:file_path]),
|
|
40
|
+
CI::Ext::Test::TAG_SOURCE_START => metadata[:line_number].to_s
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
test_suite =
|
|
44
|
+
test_visibility_component&.start_test_suite(
|
|
45
|
+
suite_name,
|
|
46
|
+
tags: suite_tags,
|
|
47
|
+
service: datadog_configuration[:service_name]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
success = super
|
|
51
|
+
|
|
52
|
+
return success unless test_suite
|
|
53
|
+
|
|
54
|
+
test_suite.finish
|
|
55
|
+
|
|
56
|
+
success
|
|
57
|
+
ensure
|
|
58
|
+
clear_context_coverage(context_id)
|
|
52
59
|
end
|
|
53
|
-
|
|
54
|
-
test_suite.finish
|
|
55
|
-
|
|
56
|
-
success
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
private
|
|
60
63
|
|
|
61
64
|
def all_examples_skipped_by_datadog?
|
|
62
65
|
descendant_filtered_examples.all? do |example|
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
datadog_test_id = example.datadog_test_id
|
|
67
|
+
datadog_fqn_test_id = example.datadog_fqn_test_id
|
|
68
|
+
|
|
69
|
+
# Don't skip if the test is marked as attempt_to_fix (it should run and be retried)
|
|
70
|
+
next false if test_management_component&.attempt_to_fix?(datadog_fqn_test_id)
|
|
71
|
+
|
|
72
|
+
# Skip by Test Impact Analysis: test is skippable and not marked as unskippable
|
|
73
|
+
tia_skipped = !example.datadog_unskippable? &&
|
|
74
|
+
test_optimisation_component&.skippable?(datadog_test_id)
|
|
75
|
+
|
|
76
|
+
# Skip by Test Management: test is disabled
|
|
77
|
+
test_management_disabled = test_management_component&.disabled?(datadog_fqn_test_id)
|
|
78
|
+
|
|
79
|
+
tia_skipped || test_management_disabled
|
|
65
80
|
end
|
|
66
81
|
end
|
|
67
82
|
|
|
83
|
+
# Returns a stable context ID for this example group.
|
|
84
|
+
# Uses RSpec's scoped_id which uniquely identifies each example group.
|
|
85
|
+
def datadog_context_id
|
|
86
|
+
metadata[:scoped_id] || "#{metadata[:file_path]}:#{metadata[:line_number]}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Starts context coverage collection for this example group.
|
|
90
|
+
# This captures code executed in before(:context)/before(:all) hooks.
|
|
91
|
+
def start_context_coverage(context_id)
|
|
92
|
+
test_optimisation_component&.on_test_context_started(context_id)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Clears context coverage for this example group after it finishes.
|
|
96
|
+
def clear_context_coverage(context_id)
|
|
97
|
+
test_optimisation_component&.clear_context_coverage(context_id)
|
|
98
|
+
end
|
|
99
|
+
|
|
68
100
|
def datadog_configuration
|
|
69
101
|
Datadog.configuration.ci[:rspec]
|
|
70
102
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../patcher"
|
|
4
4
|
|
|
5
|
+
require_relative "report_uploader"
|
|
5
6
|
require_relative "result_extractor"
|
|
6
7
|
|
|
7
8
|
module Datadog
|
|
@@ -16,6 +17,7 @@ module Datadog
|
|
|
16
17
|
|
|
17
18
|
def patch
|
|
18
19
|
::SimpleCov.include(ResultExtractor)
|
|
20
|
+
::SimpleCov.include(ReportUploader)
|
|
19
21
|
end
|
|
20
22
|
end
|
|
21
23
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ext"
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module CI
|
|
7
|
+
module Contrib
|
|
8
|
+
module Simplecov
|
|
9
|
+
# Module that hooks into SimpleCov.process_results_and_report_error to upload coverage reports
|
|
10
|
+
module ReportUploader
|
|
11
|
+
def self.included(base)
|
|
12
|
+
base.singleton_class.prepend(ClassMethods)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module ClassMethods
|
|
16
|
+
def process_results_and_report_error(*args)
|
|
17
|
+
result = super
|
|
18
|
+
|
|
19
|
+
upload_coverage_report
|
|
20
|
+
|
|
21
|
+
result
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def upload_coverage_report
|
|
27
|
+
return unless datadog_configuration[:enabled]
|
|
28
|
+
|
|
29
|
+
code_coverage = ::Datadog.send(:components).code_coverage
|
|
30
|
+
return unless code_coverage.enabled
|
|
31
|
+
|
|
32
|
+
serialized_report = read_coverage_result
|
|
33
|
+
return if serialized_report.nil?
|
|
34
|
+
|
|
35
|
+
code_coverage.upload(serialized_report: serialized_report, format: Ext::COVERAGE_FORMAT)
|
|
36
|
+
rescue => e
|
|
37
|
+
Datadog.logger.warn("Failed to upload coverage report: #{e.message}")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def read_coverage_result
|
|
41
|
+
coverage_file = File.join(::SimpleCov.coverage_path, ".resultset.json")
|
|
42
|
+
|
|
43
|
+
unless File.exist?(coverage_file)
|
|
44
|
+
Datadog.logger.debug { "Coverage result file not found at #{coverage_file}" }
|
|
45
|
+
return nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
File.read(coverage_file)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def datadog_configuration
|
|
52
|
+
Datadog.configuration.ci[:simplecov]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|