datadog-ci 1.7.0 → 1.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|