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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4cb94bf0ef94f54289e38a26649d24eeaaca99ba91229f5ebb096c046bdc582
4
- data.tar.gz: 9a88847a58121a1516b95fc1abac1fa89d4a1bab5c4593f932fc804410ee8a57
3
+ metadata.gz: b5ee5645ee9ec6c07af9660a2742402d617965668a00e48764839d6b7ca8470e
4
+ data.tar.gz: 878ee49cc336a2d8d37e718c09a6f17b949cc149c8f6a57162410ff2da9ccfea
5
5
  SHA512:
6
- metadata.gz: 7830a7b52821efbea8e0f86e878588ef96718e845fd7a33575a4a5e92d350e26e36bb4b78f7ce78b58ff59654c62973c4eca58f21c63b958d44f4d8feff122cc
7
- data.tar.gz: da8d30e84f9dea71eb498cccb6e03cb092e27e773008dc7533188020ee5cb59de774e4d2be32821e3fd82e28f4ccb426572695fd532dc7f7fff6479a392bca85
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.7.0...main
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,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "datadog/ci/cli/cli"
4
+
5
+ Datadog::CI::CLI.exec(ARGV.first)
@@ -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
- # When timecop is present, Time.now is mocked and .now_without_mock_time is added on Time to
83
- # get the current time without the mock.
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
- return nil if api.nil?
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
 
@@ -24,6 +24,12 @@ module Datadog
24
24
  Utils::Configuration.fetch_service_name(Ext::DEFAULT_SERVICE_NAME)
25
25
  end
26
26
  end
27
+
28
+ # internal only
29
+ option :dry_run_enabled do |o|
30
+ o.type :bool
31
+ o.default false
32
+ end
27
33
  end
28
34
  end
29
35
  end
@@ -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
- path = Pathname.new(File.expand_path(path))
40
- root_path = Pathname.new(root_path)
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
- path.relative_path_from(root_path).to_s
70
+ res || ""
43
71
  end
44
72
 
45
73
  def self.repository_name
@@ -76,7 +76,7 @@ module Datadog
76
76
  # - tests that read files from disk
77
77
  # - tests that make network requests
78
78
  # - tests that call external processes
79
- # - tests that use forking or threading
79
+ # - tests that use forking
80
80
  #
81
81
  # @return [void]
82
82
  def itr_unskippable!
@@ -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, :skipped_tests_count
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
- return if !test.skipped? || !test.skipped_by_itr?
166
+ @mutex.synchronize do
167
+ @total_tests_count += 1
163
168
 
164
- if forked?
165
- Datadog.logger.warn { "Intelligent test runner is not supported for forking test runners yet" }
166
- return
167
- end
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
@@ -42,6 +42,12 @@ module Datadog
42
42
  res
43
43
  end
44
44
 
45
+ def error_message
46
+ return nil if ok?
47
+
48
+ "Status code: #{@http_response&.code}, response: #{@http_response&.payload}"
49
+ end
50
+
45
51
  private
46
52
 
47
53
  def payload
@@ -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
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module TestVisibility
6
+ class NullTransport
7
+ def initialize
8
+ end
9
+
10
+ def send_traces(traces)
11
+ []
12
+ end
13
+ end
14
+ end
15
+ end
16
+ 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
- adapter.call(
82
- path: path, payload: payload, headers: headers, verb: verb
83
- )
84
- rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, SocketError, Net::HTTPBadResponse => e
85
- Datadog.logger.debug("Failed to send request with #{e} (#{e.message})")
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
- if retries.positive?
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, payload: payload, headers: headers, verb: verb, retries: retries - 1, backoff: backoff * 2
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("Failed to send request after #{MAX_RETRIES} retries")
95
- ErrorResponse.new(e)
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
@@ -4,8 +4,8 @@ module Datadog
4
4
  module CI
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 7
8
- PATCH = 0
7
+ MINOR = 8
8
+ PATCH = 1
9
9
  PRE = nil
10
10
  BUILD = nil
11
11
  # PRE and BUILD above are modified for dev gems during gem build GHA workflow
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.7.0
4
+ version: 1.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-25 00:00:00.000000000 Z
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.3'
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.3'
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