datadog-ci 1.7.0 → 1.8.1
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 +27 -2
- data/exe/ddcirb +5 -0
- data/lib/datadog/ci/cli/cli.rb +24 -0
- data/lib/datadog/ci/cli/command/base.rb +59 -0
- data/lib/datadog/ci/cli/command/skippable_tests_percentage.rb +26 -0
- data/lib/datadog/ci/cli/command/skippable_tests_percentage_estimate.rb +21 -0
- data/lib/datadog/ci/configuration/components.rb +31 -12
- data/lib/datadog/ci/configuration/settings.rb +6 -0
- data/lib/datadog/ci/contrib/rspec/configuration/settings.rb +6 -0
- data/lib/datadog/ci/contrib/rspec/example.rb +1 -1
- data/lib/datadog/ci/contrib/rspec/example_group.rb +1 -1
- data/lib/datadog/ci/contrib/rspec/knapsack_pro/runner.rb +1 -1
- data/lib/datadog/ci/contrib/rspec/runner.rb +1 -1
- data/lib/datadog/ci/ext/transport.rb +1 -0
- data/lib/datadog/ci/git/local_repository.rb +31 -3
- data/lib/datadog/ci/test.rb +1 -1
- data/lib/datadog/ci/test_optimisation/component.rb +18 -7
- data/lib/datadog/ci/test_optimisation/skippable.rb +6 -0
- data/lib/datadog/ci/test_optimisation/skippable_percentage/base.rb +51 -0
- data/lib/datadog/ci/test_optimisation/skippable_percentage/calculator.rb +79 -0
- data/lib/datadog/ci/test_optimisation/skippable_percentage/estimator.rb +58 -0
- data/lib/datadog/ci/test_visibility/null_transport.rb +16 -0
- data/lib/datadog/ci/transport/http.rb +41 -9
- data/lib/datadog/ci/version.rb +2 -2
- metadata +16 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5ee5645ee9ec6c07af9660a2742402d617965668a00e48764839d6b7ca8470e
|
4
|
+
data.tar.gz: 878ee49cc336a2d8d37e718c09a6f17b949cc149c8f6a57162410ff2da9ccfea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4fc31d4106750af2d5cc556164459e24c6782e7ade09abf57b0a3a48320552aa74f9b42488a40df256b0562f304883249b86c44b16eef59cce00af4c15f00aa9
|
7
|
+
data.tar.gz: fa12a596824ad2963bcd394c8a634db54300e4fab62f482c17bad0210a7a6b159a48355a070dd65a413412225ab5f570d125c547390d46f946a4fd6389244222
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.8.1] - 2024-10-18
|
4
|
+
|
5
|
+
|
6
|
+
### Fixed
|
7
|
+
* Make --spec-path option available to skipped-tests-estimate cli command ([#250][])
|
8
|
+
|
9
|
+
## [1.8.0] - 2024-10-17
|
10
|
+
|
11
|
+
### Added
|
12
|
+
* Add command line tool to compute a percentage of skippable tests for RSpec ([#194][])
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
* Bump gem datadog dependency to 2.4 and update test dependencies ([#248][])
|
16
|
+
* Optimise LocalRepository.relative_to_root helper to make test impact analysis faster ([#244][])
|
17
|
+
* Retry HTTP requests on 429 and 5xx responses ([#243][])
|
18
|
+
* Use correct monotonic clock time if Timecop.mock_process_clock is set ([#242][])
|
19
|
+
|
3
20
|
## [1.7.0] - 2024-09-25
|
4
21
|
|
5
22
|
### Added
|
@@ -339,7 +356,9 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
339
356
|
|
340
357
|
- Ruby versions < 2.7 no longer supported ([#8][])
|
341
358
|
|
342
|
-
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.
|
359
|
+
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.8.1...main
|
360
|
+
[1.8.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.8.0...v1.8.1
|
361
|
+
[1.8.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.7.0...v1.8.0
|
343
362
|
[1.7.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.6.0...v1.7.0
|
344
363
|
[1.6.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.5.0...v1.6.0
|
345
364
|
[1.5.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.4.1...v1.5.0
|
@@ -459,6 +478,7 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
459
478
|
[#189]: https://github.com/DataDog/datadog-ci-rb/issues/189
|
460
479
|
[#190]: https://github.com/DataDog/datadog-ci-rb/issues/190
|
461
480
|
[#193]: https://github.com/DataDog/datadog-ci-rb/issues/193
|
481
|
+
[#194]: https://github.com/DataDog/datadog-ci-rb/issues/194
|
462
482
|
[#197]: https://github.com/DataDog/datadog-ci-rb/issues/197
|
463
483
|
[#200]: https://github.com/DataDog/datadog-ci-rb/issues/200
|
464
484
|
[#201]: https://github.com/DataDog/datadog-ci-rb/issues/201
|
@@ -488,4 +508,9 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
488
508
|
[#236]: https://github.com/DataDog/datadog-ci-rb/issues/236
|
489
509
|
[#238]: https://github.com/DataDog/datadog-ci-rb/issues/238
|
490
510
|
[#239]: https://github.com/DataDog/datadog-ci-rb/issues/239
|
491
|
-
[#240]: https://github.com/DataDog/datadog-ci-rb/issues/240
|
511
|
+
[#240]: https://github.com/DataDog/datadog-ci-rb/issues/240
|
512
|
+
[#242]: https://github.com/DataDog/datadog-ci-rb/issues/242
|
513
|
+
[#243]: https://github.com/DataDog/datadog-ci-rb/issues/243
|
514
|
+
[#244]: https://github.com/DataDog/datadog-ci-rb/issues/244
|
515
|
+
[#248]: https://github.com/DataDog/datadog-ci-rb/issues/248
|
516
|
+
[#250]: https://github.com/DataDog/datadog-ci-rb/issues/250
|
data/exe/ddcirb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "datadog"
|
2
|
+
require "datadog/ci"
|
3
|
+
|
4
|
+
require_relative "command/skippable_tests_percentage"
|
5
|
+
require_relative "command/skippable_tests_percentage_estimate"
|
6
|
+
|
7
|
+
module Datadog
|
8
|
+
module CI
|
9
|
+
module CLI
|
10
|
+
def self.exec(action)
|
11
|
+
case action
|
12
|
+
when "skipped-tests", "skippable-tests"
|
13
|
+
Command::SkippableTestsPercentage.new.exec
|
14
|
+
when "skipped-tests-estimate", "skippable-tests-estimate"
|
15
|
+
Command::SkippableTestsPercentageEstimate.new.exec
|
16
|
+
else
|
17
|
+
puts("Usage: bundle exec ddcirb [command] [options]. Available commands:")
|
18
|
+
puts(" skippable-tests - calculates the exact percentage of skipped tests and prints it to stdout or file")
|
19
|
+
puts(" skippable-tests-estimate - estimates the percentage of skipped tests and prints it to stdout or file")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "optparse"
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module CI
|
5
|
+
module CLI
|
6
|
+
module Command
|
7
|
+
class Base
|
8
|
+
def exec
|
9
|
+
action = build_action
|
10
|
+
result = action&.call
|
11
|
+
|
12
|
+
validate!(action)
|
13
|
+
output(result)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def build_action
|
19
|
+
end
|
20
|
+
|
21
|
+
def options
|
22
|
+
return @options if defined?(@options)
|
23
|
+
|
24
|
+
ddcirb_options = {}
|
25
|
+
OptionParser.new do |opts|
|
26
|
+
opts.banner = "Usage: bundle exec ddcirb [command] [options]\n Available commands: skippable-tests, skippable-tests-estimate"
|
27
|
+
|
28
|
+
opts.on("-f", "--file FILENAME", "Output result to file FILENAME")
|
29
|
+
opts.on("--verbose", "Verbose output to stdout")
|
30
|
+
opts.on("--spec-path=[SPEC_PATH]", "Relative path to the spec directory, example: spec")
|
31
|
+
|
32
|
+
command_options(opts)
|
33
|
+
end.parse!(into: ddcirb_options)
|
34
|
+
|
35
|
+
@options = ddcirb_options
|
36
|
+
end
|
37
|
+
|
38
|
+
def command_options(opts)
|
39
|
+
end
|
40
|
+
|
41
|
+
def validate!(action)
|
42
|
+
if action.nil? || action.failed
|
43
|
+
Datadog.logger.error("ddcirb failed, exiting")
|
44
|
+
Kernel.exit(1)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def output(result)
|
49
|
+
if options[:file]
|
50
|
+
File.write(options[:file], result)
|
51
|
+
else
|
52
|
+
print(result)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
require_relative "../../test_optimisation/skippable_percentage/calculator"
|
3
|
+
|
4
|
+
module Datadog
|
5
|
+
module CI
|
6
|
+
module CLI
|
7
|
+
module Command
|
8
|
+
class SkippableTestsPercentage < Base
|
9
|
+
private
|
10
|
+
|
11
|
+
def build_action
|
12
|
+
::Datadog::CI::TestOptimisation::SkippablePercentage::Calculator.new(
|
13
|
+
rspec_cli_options: (options[:"rspec-opts"] || "").split,
|
14
|
+
verbose: !options[:verbose].nil?,
|
15
|
+
spec_path: options[:"spec-path"] || "spec"
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def command_options(opts)
|
20
|
+
opts.on("--rspec-opts=[OPTIONS]", "Command line options to pass to RSpec")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
require_relative "../../test_optimisation/skippable_percentage/estimator"
|
3
|
+
|
4
|
+
module Datadog
|
5
|
+
module CI
|
6
|
+
module CLI
|
7
|
+
module Command
|
8
|
+
class SkippableTestsPercentageEstimate < Base
|
9
|
+
private
|
10
|
+
|
11
|
+
def build_action
|
12
|
+
::Datadog::CI::TestOptimisation::SkippablePercentage::Estimator.new(
|
13
|
+
verbose: !options[:verbose].nil?,
|
14
|
+
spec_path: options[:"spec-path"] || "spec"
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -19,6 +19,7 @@ require_relative "../test_visibility/serializers/factories/test_level"
|
|
19
19
|
require_relative "../test_visibility/serializers/factories/test_suite_level"
|
20
20
|
require_relative "../test_visibility/transport"
|
21
21
|
require_relative "../transport/adapters/telemetry_webmock_safe_adapter"
|
22
|
+
require_relative "../test_visibility/null_transport"
|
22
23
|
require_relative "../transport/api/builder"
|
23
24
|
require_relative "../utils/parsing"
|
24
25
|
require_relative "../utils/test_run"
|
@@ -79,16 +80,8 @@ module Datadog
|
|
79
80
|
# startup logs are useless for test visibility and create noise
|
80
81
|
settings.diagnostics.startup_logs.enabled = false
|
81
82
|
|
82
|
-
#
|
83
|
-
|
84
|
-
if timecop?
|
85
|
-
settings.time_now_provider = -> do
|
86
|
-
Time.now_without_mock_time
|
87
|
-
rescue NoMethodError
|
88
|
-
# fallback to normal Time.now if Time.now_without_mock_time is not defined for any reason
|
89
|
-
Time.now
|
90
|
-
end
|
91
|
-
end
|
83
|
+
# timecop configuration
|
84
|
+
configure_time_providers(settings)
|
92
85
|
|
93
86
|
# Configure Datadog::Tracing module
|
94
87
|
|
@@ -181,7 +174,6 @@ module Datadog
|
|
181
174
|
# Tests are running without CI visibility enabled
|
182
175
|
settings.ci.enabled = false
|
183
176
|
end
|
184
|
-
|
185
177
|
else
|
186
178
|
Datadog.logger.debug("CI visibility configured to use agent transport via EVP proxy")
|
187
179
|
|
@@ -203,6 +195,9 @@ module Datadog
|
|
203
195
|
end
|
204
196
|
|
205
197
|
def build_tracing_transport(settings, api)
|
198
|
+
# NullTransport ignores traces
|
199
|
+
return TestVisibility::NullTransport.new if settings.ci.discard_traces
|
200
|
+
# nil means that default legacy APM transport will be used (only for very old Datadog Agent versions)
|
206
201
|
return nil if api.nil?
|
207
202
|
|
208
203
|
TestVisibility::Transport.new(
|
@@ -213,7 +208,8 @@ module Datadog
|
|
213
208
|
end
|
214
209
|
|
215
210
|
def build_coverage_writer(settings, api)
|
216
|
-
|
211
|
+
# nil means that coverage event will be ignored
|
212
|
+
return nil if api.nil? || settings.ci.discard_traces
|
217
213
|
|
218
214
|
TestOptimisation::Coverage::Writer.new(
|
219
215
|
transport: TestOptimisation::Coverage::Transport.new(api: api)
|
@@ -294,6 +290,29 @@ module Datadog
|
|
294
290
|
end
|
295
291
|
end
|
296
292
|
|
293
|
+
# When timecop is present:
|
294
|
+
# - Time.now is mocked and .now_without_mock_time is added on Time to get the current time without the mock.
|
295
|
+
# - Process.clock_gettime is mocked and .clock_gettime_without_mock is added on Process to get the monotonic time without the mock.
|
296
|
+
def configure_time_providers(settings)
|
297
|
+
return unless timecop?
|
298
|
+
|
299
|
+
settings.time_now_provider = -> do
|
300
|
+
Time.now_without_mock_time
|
301
|
+
rescue NoMethodError
|
302
|
+
# fallback to normal Time.now if Time.now_without_mock_time is not defined for any reason
|
303
|
+
Time.now
|
304
|
+
end
|
305
|
+
|
306
|
+
if defined?(Process.clock_gettime_without_mock)
|
307
|
+
settings.get_time_provider = ->(unit = :float_second) do
|
308
|
+
::Process.clock_gettime_without_mock(::Process::CLOCK_MONOTONIC, unit)
|
309
|
+
rescue NoMethodError
|
310
|
+
# fallback to normal Process.clock_gettime if Process.clock_gettime_without_mock is not defined for any reason
|
311
|
+
Process.clock_gettime(::Process::CLOCK_MONOTONIC, unit)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
297
316
|
def timecop?
|
298
317
|
Gem.loaded_specs.key?("timecop") || !!defined?(Timecop)
|
299
318
|
end
|
@@ -117,6 +117,12 @@ module Datadog
|
|
117
117
|
o.default true
|
118
118
|
end
|
119
119
|
|
120
|
+
# internal only
|
121
|
+
option :discard_traces do |o|
|
122
|
+
o.type :bool
|
123
|
+
o.default false
|
124
|
+
end
|
125
|
+
|
120
126
|
define_method(:instrument) do |integration_name, options = {}, &block|
|
121
127
|
return unless enabled
|
122
128
|
|
@@ -17,7 +17,7 @@ module Datadog
|
|
17
17
|
|
18
18
|
module InstanceMethods
|
19
19
|
def run(*args)
|
20
|
-
return super if ::RSpec.configuration.dry_run?
|
20
|
+
return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled]
|
21
21
|
return super unless datadog_configuration[:enabled]
|
22
22
|
|
23
23
|
test_name = full_description.strip
|
@@ -16,7 +16,7 @@ module Datadog
|
|
16
16
|
# Instance methods for configuration
|
17
17
|
module ClassMethods
|
18
18
|
def run(reporter = ::RSpec::Core::NullReporter)
|
19
|
-
return super if ::RSpec.configuration.dry_run?
|
19
|
+
return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled]
|
20
20
|
return super unless datadog_configuration[:enabled]
|
21
21
|
return super unless top_level?
|
22
22
|
|
@@ -15,7 +15,7 @@ module Datadog
|
|
15
15
|
|
16
16
|
module InstanceMethods
|
17
17
|
def knapsack__run_specs(*args)
|
18
|
-
return super if ::RSpec.configuration.dry_run?
|
18
|
+
return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled]
|
19
19
|
return super unless datadog_configuration[:enabled]
|
20
20
|
|
21
21
|
test_session = test_visibility_component.start_test_session(
|
@@ -15,7 +15,7 @@ module Datadog
|
|
15
15
|
|
16
16
|
module InstanceMethods
|
17
17
|
def run_specs(*args)
|
18
|
-
return super if ::RSpec.configuration.dry_run?
|
18
|
+
return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled]
|
19
19
|
return super unless datadog_configuration[:enabled]
|
20
20
|
|
21
21
|
test_session = test_visibility_component.start_test_session(
|
@@ -12,6 +12,7 @@ module Datadog
|
|
12
12
|
HEADER_CONTENT_ENCODING = "Content-Encoding"
|
13
13
|
HEADER_EVP_SUBDOMAIN = "X-Datadog-EVP-Subdomain"
|
14
14
|
HEADER_CONTAINER_ID = "Datadog-Container-ID"
|
15
|
+
HEADER_RATELIMIT_RESET = "X-RateLimit-Reset"
|
15
16
|
|
16
17
|
EVP_PROXY_V2_PATH_PREFIX = "/evp_proxy/v2/"
|
17
18
|
EVP_PROXY_V4_PATH_PREFIX = "/evp_proxy/v4/"
|
@@ -30,16 +30,44 @@ module Datadog
|
|
30
30
|
@root = git_root || Dir.pwd
|
31
31
|
end
|
32
32
|
|
33
|
+
# ATTENTION: this function is running in a hot path
|
34
|
+
# and should be optimized for performance
|
33
35
|
def self.relative_to_root(path)
|
34
36
|
return "" if path.nil?
|
35
37
|
|
36
38
|
root_path = root
|
37
39
|
return path if root_path.nil?
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
+
if File.absolute_path?(path)
|
42
|
+
# prefix_index is where the root path ends in the given path
|
43
|
+
prefix_index = root_path.size
|
44
|
+
|
45
|
+
# impossible case - absolute paths are returned from code coverage tool that always checks
|
46
|
+
# that root is a prefix of the path
|
47
|
+
return "" if path.size < prefix_index
|
48
|
+
|
49
|
+
prefix_index += 1 if path[prefix_index] == File::SEPARATOR
|
50
|
+
res = path[prefix_index..]
|
51
|
+
else
|
52
|
+
# prefix_to_root is a difference between the root path and the given path
|
53
|
+
if @prefix_to_root == ""
|
54
|
+
return path
|
55
|
+
elsif @prefix_to_root
|
56
|
+
return File.join(@prefix_to_root, path)
|
57
|
+
end
|
58
|
+
|
59
|
+
pathname = Pathname.new(File.expand_path(path))
|
60
|
+
root_path = Pathname.new(root_path)
|
61
|
+
|
62
|
+
# relative_path_from is an expensive function
|
63
|
+
res = pathname.relative_path_from(root_path).to_s
|
64
|
+
|
65
|
+
unless defined?(@prefix_to_root)
|
66
|
+
@prefix_to_root = res&.gsub(path, "") if res.end_with?(path)
|
67
|
+
end
|
68
|
+
end
|
41
69
|
|
42
|
-
|
70
|
+
res || ""
|
43
71
|
end
|
44
72
|
|
45
73
|
def self.repository_name
|
data/lib/datadog/ci/test.rb
CHANGED
@@ -25,7 +25,9 @@ module Datadog
|
|
25
25
|
class Component
|
26
26
|
include Core::Utils::Forking
|
27
27
|
|
28
|
-
attr_reader :correlation_id, :skippable_tests, :
|
28
|
+
attr_reader :correlation_id, :skippable_tests, :skippable_tests_fetch_error,
|
29
|
+
:skipped_tests_count, :total_tests_count,
|
30
|
+
:enabled, :test_skipping_enabled, :code_coverage_enabled
|
29
31
|
|
30
32
|
def initialize(
|
31
33
|
dd_env:,
|
@@ -58,7 +60,9 @@ module Datadog
|
|
58
60
|
@correlation_id = nil
|
59
61
|
@skippable_tests = Set.new
|
60
62
|
|
63
|
+
@total_tests_count = 0
|
61
64
|
@skipped_tests_count = 0
|
65
|
+
|
62
66
|
@mutex = Mutex.new
|
63
67
|
|
64
68
|
Datadog.logger.debug("TestOptimisation initialized with enabled: #{@enabled}")
|
@@ -159,14 +163,16 @@ module Datadog
|
|
159
163
|
end
|
160
164
|
|
161
165
|
def count_skipped_test(test)
|
162
|
-
|
166
|
+
@mutex.synchronize do
|
167
|
+
@total_tests_count += 1
|
163
168
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
169
|
+
return if !test.skipped? || !test.skipped_by_itr?
|
170
|
+
|
171
|
+
if forked?
|
172
|
+
Datadog.logger.warn { "ITR is not supported for forking test runners yet" }
|
173
|
+
return
|
174
|
+
end
|
168
175
|
|
169
|
-
@mutex.synchronize do
|
170
176
|
Telemetry.itr_skipped
|
171
177
|
|
172
178
|
@skipped_tests_count += 1
|
@@ -183,6 +189,10 @@ module Datadog
|
|
183
189
|
test_session.set_tag(Ext::Test::TAG_ITR_TEST_SKIPPING_COUNT, @skipped_tests_count)
|
184
190
|
end
|
185
191
|
|
192
|
+
def skippable_tests_count
|
193
|
+
skippable_tests.count
|
194
|
+
end
|
195
|
+
|
186
196
|
def shutdown!
|
187
197
|
@coverage_writer&.stop
|
188
198
|
end
|
@@ -229,6 +239,7 @@ module Datadog
|
|
229
239
|
skippable_response =
|
230
240
|
Skippable.new(api: @api, dd_env: @dd_env, config_tags: @config_tags)
|
231
241
|
.fetch_skippable_tests(test_session)
|
242
|
+
@skippable_tests_fetch_error = skippable_response.error_message unless skippable_response.ok?
|
232
243
|
|
233
244
|
@correlation_id = skippable_response.correlation_id
|
234
245
|
@skippable_tests = skippable_response.tests
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module CI
|
5
|
+
module TestOptimisation
|
6
|
+
module SkippablePercentage
|
7
|
+
class Base
|
8
|
+
attr_reader :failed
|
9
|
+
|
10
|
+
def initialize(verbose: false, spec_path: "spec")
|
11
|
+
@verbose = verbose
|
12
|
+
@spec_path = spec_path
|
13
|
+
@failed = false
|
14
|
+
|
15
|
+
log("Spec path: #{@spec_path}")
|
16
|
+
error!("Spec path is not a directory: #{@spec_path}") if !File.directory?(@spec_path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
0.0
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def validate_test_optimisation_state!
|
26
|
+
unless test_optimisation.enabled
|
27
|
+
error!("ITR wasn't enabled, check the environment variables (DD_SERVICE, DD_ENV)")
|
28
|
+
end
|
29
|
+
|
30
|
+
if test_optimisation.skippable_tests_fetch_error
|
31
|
+
error!("Skippable tests couldn't be fetched, error: #{test_optimisation.skippable_tests_fetch_error}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def log(message)
|
36
|
+
Datadog.logger.info(message) if @verbose
|
37
|
+
end
|
38
|
+
|
39
|
+
def error!(message)
|
40
|
+
Datadog.logger.error(message)
|
41
|
+
@failed = true
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_optimisation
|
45
|
+
Datadog.send(:components).test_optimisation
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module CI
|
7
|
+
module TestOptimisation
|
8
|
+
module SkippablePercentage
|
9
|
+
# This class calculates the percentage of tests that are going to be skipped in the next run
|
10
|
+
# without actually running the tests.
|
11
|
+
#
|
12
|
+
# It is useful to determine the number of parallel jobs that are required for the CI pipeline.
|
13
|
+
#
|
14
|
+
# NOTE: Only RSpec is supported at the moment.
|
15
|
+
class Calculator < Base
|
16
|
+
def initialize(rspec_cli_options: [], verbose: false, spec_path: "spec")
|
17
|
+
super(verbose: verbose, spec_path: spec_path)
|
18
|
+
|
19
|
+
@rspec_cli_options = rspec_cli_options || []
|
20
|
+
end
|
21
|
+
|
22
|
+
def call
|
23
|
+
return 0.0 if @failed
|
24
|
+
|
25
|
+
require_rspec!
|
26
|
+
return 0.0 if @failed
|
27
|
+
|
28
|
+
configure_datadog
|
29
|
+
|
30
|
+
exit_code = dry_run
|
31
|
+
if exit_code != 0
|
32
|
+
Datadog.logger.error("RSpec dry-run failed with exit code #{exit_code}")
|
33
|
+
@failed = true
|
34
|
+
return 0.0
|
35
|
+
end
|
36
|
+
|
37
|
+
log("Total tests count: #{test_optimisation.total_tests_count}")
|
38
|
+
log("Skipped tests count: #{test_optimisation.skipped_tests_count}")
|
39
|
+
validate_test_optimisation_state!
|
40
|
+
|
41
|
+
(test_optimisation.skipped_tests_count.to_f / test_optimisation.total_tests_count.to_f).floor(2)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def require_rspec!
|
47
|
+
require "rspec/core"
|
48
|
+
rescue LoadError
|
49
|
+
Datadog.logger.error("RSpec is not installed, currently this functionality is only supported for RSpec.")
|
50
|
+
@failed = true
|
51
|
+
end
|
52
|
+
|
53
|
+
def configure_datadog
|
54
|
+
Datadog.configure do |c|
|
55
|
+
c.ci.enabled = true
|
56
|
+
c.ci.itr_enabled = true
|
57
|
+
c.ci.retry_failed_tests_enabled = false
|
58
|
+
c.ci.retry_new_tests_enabled = false
|
59
|
+
c.ci.discard_traces = true
|
60
|
+
c.ci.instrument :rspec, dry_run_enabled: true
|
61
|
+
c.tracing.enabled = true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def dry_run
|
66
|
+
cli_options_array = @rspec_cli_options + ["--dry-run", @spec_path]
|
67
|
+
|
68
|
+
rspec_config_options = ::RSpec::Core::ConfigurationOptions.new(cli_options_array)
|
69
|
+
devnull = File.new("/dev/null", "w")
|
70
|
+
out = @verbose ? $stdout : devnull
|
71
|
+
err = @verbose ? $stderr : devnull
|
72
|
+
|
73
|
+
::RSpec::Core::Runner.new(rspec_config_options).run(out, err)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module CI
|
7
|
+
module TestOptimisation
|
8
|
+
module SkippablePercentage
|
9
|
+
# This class estimates the percentage of tests that are going to be skipped in the next run
|
10
|
+
# without actually running the tests. This estimate is very rough:
|
11
|
+
#
|
12
|
+
# - it counts the number of lines that start with "it" or "scenario" in the spec files, which could be inaccurate
|
13
|
+
# if you use shared examples
|
14
|
+
# - it only counts the number of tests that could be skipped, this does not mean that they will be actually skipped:
|
15
|
+
# if in this commit you replaced all the tests in your test suite with new ones, all the tests would be run (but
|
16
|
+
# this is highly unlikely)
|
17
|
+
#
|
18
|
+
# It is useful to determine the number of parallel jobs that are required for the CI pipeline.
|
19
|
+
#
|
20
|
+
# NOTE: Only RSpec is supported at the moment.
|
21
|
+
class Estimator < Base
|
22
|
+
def initialize(verbose: false, spec_path: "spec")
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
def call
|
27
|
+
return 0.0 if @failed
|
28
|
+
|
29
|
+
Datadog.configure do |c|
|
30
|
+
c.ci.enabled = true
|
31
|
+
c.ci.itr_enabled = true
|
32
|
+
c.ci.retry_failed_tests_enabled = false
|
33
|
+
c.ci.retry_new_tests_enabled = false
|
34
|
+
c.ci.discard_traces = true
|
35
|
+
c.tracing.enabled = true
|
36
|
+
end
|
37
|
+
|
38
|
+
spec_files = Dir["#{@spec_path}/**/*_spec.rb"]
|
39
|
+
estimated_tests_count = spec_files.sum do |file|
|
40
|
+
content = File.read(file)
|
41
|
+
content.scan(/(^\s*it\s+)|(^\s*scenario\s+)/).size
|
42
|
+
end
|
43
|
+
|
44
|
+
# starting and finishing a test session is required to get the skippable tests response
|
45
|
+
Datadog::CI.start_test_session(total_tests_count: estimated_tests_count)&.finish
|
46
|
+
skippable_tests_count = test_optimisation.skippable_tests_count
|
47
|
+
|
48
|
+
log("Estimated tests count: #{estimated_tests_count}")
|
49
|
+
log("Skippable tests count: #{skippable_tests_count}")
|
50
|
+
validate_test_optimisation_state!
|
51
|
+
|
52
|
+
[(skippable_tests_count.to_f / estimated_tests_count).floor(2), 0.99].min || 0.0
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -23,6 +23,7 @@ module Datadog
|
|
23
23
|
DEFAULT_TIMEOUT = 30
|
24
24
|
MAX_RETRIES = 3
|
25
25
|
INITIAL_BACKOFF = 1
|
26
|
+
MAX_BACKOFF = 30
|
26
27
|
|
27
28
|
def initialize(host:, port:, timeout: DEFAULT_TIMEOUT, ssl: true, compress: false)
|
28
29
|
@host = host
|
@@ -78,21 +79,48 @@ module Datadog
|
|
78
79
|
private
|
79
80
|
|
80
81
|
def perform_http_call(path:, payload:, headers:, verb:, retries: MAX_RETRIES, backoff: INITIAL_BACKOFF)
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
82
|
+
response = nil
|
83
|
+
|
84
|
+
begin
|
85
|
+
response = adapter.call(
|
86
|
+
path: path, payload: payload, headers: headers, verb: verb
|
87
|
+
)
|
88
|
+
return response if response.ok?
|
89
|
+
|
90
|
+
if response.code == 429
|
91
|
+
backoff = (response.header(Ext::Transport::HEADER_RATELIMIT_RESET) || 1).to_i
|
92
|
+
|
93
|
+
Datadog.logger.debug do
|
94
|
+
"Received rate limit response, retrying in #{backoff} seconds from X-RateLimit-Reset header"
|
95
|
+
end
|
96
|
+
elsif response.server_error?
|
97
|
+
Datadog.logger.debug { "Received server error response, retrying in #{backoff} seconds" }
|
98
|
+
else
|
99
|
+
return response
|
100
|
+
end
|
101
|
+
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, SocketError, Net::HTTPBadResponse => e
|
102
|
+
Datadog.logger.debug { "Failed to send request with #{e} (#{e.message})" }
|
86
103
|
|
87
|
-
|
104
|
+
response = ErrorResponse.new(e)
|
105
|
+
end
|
106
|
+
|
107
|
+
if retries.positive? && backoff <= MAX_BACKOFF
|
88
108
|
sleep(backoff)
|
89
109
|
|
90
110
|
perform_http_call(
|
91
|
-
path: path,
|
111
|
+
path: path,
|
112
|
+
payload: payload,
|
113
|
+
headers: headers,
|
114
|
+
verb: verb,
|
115
|
+
retries: retries - 1,
|
116
|
+
backoff: backoff * 2
|
92
117
|
)
|
93
118
|
else
|
94
|
-
Datadog.logger.error(
|
95
|
-
|
119
|
+
Datadog.logger.error(
|
120
|
+
"Failed to send request after #{MAX_RETRIES - retries} retries (current backoff value #{backoff})"
|
121
|
+
)
|
122
|
+
|
123
|
+
response
|
96
124
|
end
|
97
125
|
end
|
98
126
|
|
@@ -121,6 +149,10 @@ module Datadog
|
|
121
149
|
nil
|
122
150
|
end
|
123
151
|
|
152
|
+
def response_size
|
153
|
+
0
|
154
|
+
end
|
155
|
+
|
124
156
|
def inspect
|
125
157
|
"ErrorResponse error:#{error}"
|
126
158
|
end
|
data/lib/datadog/ci/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: datadog-ci
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Datadog, Inc.
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-10-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: datadog
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '2.
|
19
|
+
version: '2.4'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '2.
|
26
|
+
version: '2.4'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: msgpack
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,7 +44,8 @@ description: |2
|
|
44
44
|
their CI pipelines.
|
45
45
|
email:
|
46
46
|
- dev@datadoghq.com
|
47
|
-
executables:
|
47
|
+
executables:
|
48
|
+
- ddcirb
|
48
49
|
extensions:
|
49
50
|
- ext/datadog_cov/extconf.rb
|
50
51
|
extra_rdoc_files: []
|
@@ -56,9 +57,14 @@ files:
|
|
56
57
|
- LICENSE.BSD3
|
57
58
|
- NOTICE
|
58
59
|
- README.md
|
60
|
+
- exe/ddcirb
|
59
61
|
- ext/datadog_cov/datadog_cov.c
|
60
62
|
- ext/datadog_cov/extconf.rb
|
61
63
|
- lib/datadog/ci.rb
|
64
|
+
- lib/datadog/ci/cli/cli.rb
|
65
|
+
- lib/datadog/ci/cli/command/base.rb
|
66
|
+
- lib/datadog/ci/cli/command/skippable_tests_percentage.rb
|
67
|
+
- lib/datadog/ci/cli/command/skippable_tests_percentage_estimate.rb
|
62
68
|
- lib/datadog/ci/codeowners/matcher.rb
|
63
69
|
- lib/datadog/ci/codeowners/parser.rb
|
64
70
|
- lib/datadog/ci/codeowners/rule.rb
|
@@ -154,6 +160,9 @@ files:
|
|
154
160
|
- lib/datadog/ci/test_optimisation/coverage/transport.rb
|
155
161
|
- lib/datadog/ci/test_optimisation/coverage/writer.rb
|
156
162
|
- lib/datadog/ci/test_optimisation/skippable.rb
|
163
|
+
- lib/datadog/ci/test_optimisation/skippable_percentage/base.rb
|
164
|
+
- lib/datadog/ci/test_optimisation/skippable_percentage/calculator.rb
|
165
|
+
- lib/datadog/ci/test_optimisation/skippable_percentage/estimator.rb
|
157
166
|
- lib/datadog/ci/test_optimisation/telemetry.rb
|
158
167
|
- lib/datadog/ci/test_retries/component.rb
|
159
168
|
- lib/datadog/ci/test_retries/driver/base.rb
|
@@ -172,6 +181,7 @@ files:
|
|
172
181
|
- lib/datadog/ci/test_visibility/context.rb
|
173
182
|
- lib/datadog/ci/test_visibility/flush.rb
|
174
183
|
- lib/datadog/ci/test_visibility/null_component.rb
|
184
|
+
- lib/datadog/ci/test_visibility/null_transport.rb
|
175
185
|
- lib/datadog/ci/test_visibility/serializers/base.rb
|
176
186
|
- lib/datadog/ci/test_visibility/serializers/factories/test_level.rb
|
177
187
|
- lib/datadog/ci/test_visibility/serializers/factories/test_suite_level.rb
|