datadog-ci 1.4.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -2
  3. data/README.md +1 -0
  4. data/lib/datadog/ci/configuration/components.rb +14 -7
  5. data/lib/datadog/ci/configuration/settings.rb +11 -0
  6. data/lib/datadog/ci/contrib/cucumber/filter.rb +40 -0
  7. data/lib/datadog/ci/contrib/cucumber/instrumentation.rb +15 -1
  8. data/lib/datadog/ci/contrib/cucumber/patcher.rb +0 -2
  9. data/lib/datadog/ci/contrib/minitest/runner.rb +6 -1
  10. data/lib/datadog/ci/contrib/minitest/test.rb +4 -0
  11. data/lib/datadog/ci/contrib/rspec/example.rb +11 -10
  12. data/lib/datadog/ci/contrib/rspec/runner.rb +2 -1
  13. data/lib/datadog/ci/ext/settings.rb +2 -0
  14. data/lib/datadog/ci/ext/telemetry.rb +9 -0
  15. data/lib/datadog/ci/ext/test.rb +12 -1
  16. data/lib/datadog/ci/ext/transport.rb +7 -0
  17. data/lib/datadog/ci/remote/component.rb +13 -2
  18. data/lib/datadog/ci/remote/library_settings.rb +48 -7
  19. data/lib/datadog/ci/remote/library_settings_client.rb +2 -1
  20. data/lib/datadog/ci/remote/slow_test_retries.rb +53 -0
  21. data/lib/datadog/ci/span.rb +7 -0
  22. data/lib/datadog/ci/test.rb +15 -2
  23. data/lib/datadog/ci/test_optimisation/component.rb +9 -6
  24. data/lib/datadog/ci/test_optimisation/skippable.rb +1 -1
  25. data/lib/datadog/ci/test_retries/component.rb +68 -39
  26. data/lib/datadog/ci/test_retries/driver/base.rb +25 -0
  27. data/lib/datadog/ci/test_retries/driver/no_retry.rb +16 -0
  28. data/lib/datadog/ci/test_retries/driver/retry_failed.rb +37 -0
  29. data/lib/datadog/ci/test_retries/driver/retry_new.rb +50 -0
  30. data/lib/datadog/ci/test_retries/null_component.rb +7 -6
  31. data/lib/datadog/ci/test_retries/strategy/base.rb +11 -4
  32. data/lib/datadog/ci/test_retries/strategy/no_retry.rb +0 -2
  33. data/lib/datadog/ci/test_retries/strategy/retry_failed.rb +30 -13
  34. data/lib/datadog/ci/test_retries/strategy/retry_new.rb +132 -0
  35. data/lib/datadog/ci/test_retries/unique_tests_client.rb +132 -0
  36. data/lib/datadog/ci/test_session.rb +20 -0
  37. data/lib/datadog/ci/test_suite.rb +8 -0
  38. data/lib/datadog/ci/test_visibility/component.rb +38 -15
  39. data/lib/datadog/ci/test_visibility/context.rb +4 -0
  40. data/lib/datadog/ci/test_visibility/null_component.rb +8 -1
  41. data/lib/datadog/ci/test_visibility/telemetry.rb +10 -3
  42. data/lib/datadog/ci/test_visibility/transport.rb +21 -3
  43. data/lib/datadog/ci/utils/test_run.rb +9 -1
  44. data/lib/datadog/ci/version.rb +2 -2
  45. data/lib/datadog/ci.rb +6 -3
  46. metadata +11 -5
  47. data/lib/datadog/ci/contrib/cucumber/configuration_override.rb +0 -37
  48. data/lib/datadog/ci/utils/identity.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ddc261b14aac25efcc4c94bf30e18962e714a83e35d47cb3a1f3210d640fd5a
4
- data.tar.gz: 47acdc6af95b2ab2e7385a22d0c82359e63ff0eabb8f0dfdf1bd0197f73eb6ee
3
+ metadata.gz: f38fbb3ab8e2c8c1b058d7cbefb3ac30c59cd138fb2a69929d2c7309763b0c6e
4
+ data.tar.gz: 862f8c10065b47fb8763701399de65cf0da652f36f0d301ba3b0cd4e1c12080c
5
5
  SHA512:
6
- metadata.gz: 1a774a633e3da98fc313dcf2bfeb14c02e231fc52fc7abced54ae4246afd9930fe4598d5885e39a999ede252da6c9aa0262e041aad7b4873d4fcdb8349dab673
7
- data.tar.gz: ed05b4354183b31a619f9f18aa22c547dc0ccbfdd829e8fa24e250fe98becd2a434855bb71839325214d017d6e1b79c99dcf681eeb7f820e1508991516722070
6
+ metadata.gz: 4be4dec3f2b5a11d0a822a3cd17d422eedab4262445dd42ba4d4efa96d4ee3f92049e5b37e1b6497928cf1fe980a847e4749538275092ca9fecd24700db3c62f
7
+ data.tar.gz: 2ebcae9e728ffd517d2c4f583808ade0170cb705967b59710213633bc73d1cd5d529c2b22323f8f70d45f657a8d848e856560aacc53c3ec7aea6e3aff45110af
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.6.0] - 2024-09-20
4
+
5
+
6
+ ### Added
7
+ * support logical names for test sessions ([#235][])
8
+ * Send internal vCPU count metric ([#236][])
9
+
10
+ ## [1.5.0] - 2024-09-18
11
+
12
+ ### Added
13
+ * Retry new tests - parse remote configuration and fetch unique known tests ([#227][])
14
+ * early flake detection support for rspec and minitest ([#229][])
15
+ * Early flake detection support for Cucumber ([#231][])
16
+
17
+ ### Fixed
18
+ * Minor telemetry fixes ([#226][])
19
+
3
20
  ## [1.4.1] - 2024-08-28
4
21
 
5
22
  ### Fixed
@@ -315,7 +332,9 @@ Currently test suite level visibility is not used by our instrumentation: it wil
315
332
 
316
333
  - Ruby versions < 2.7 no longer supported ([#8][])
317
334
 
318
- [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.4.1...main
335
+ [Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.6.0...main
336
+ [1.6.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.5.0...v1.6.0
337
+ [1.5.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.4.1...v1.5.0
319
338
  [1.4.1]: https://github.com/DataDog/datadog-ci-rb/compare/v1.4.0...v1.4.1
320
339
  [1.4.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.3.0...v1.4.0
321
340
  [1.3.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.2.0...v1.3.0
@@ -452,4 +471,10 @@ Currently test suite level visibility is not used by our instrumentation: it wil
452
471
  [#219]: https://github.com/DataDog/datadog-ci-rb/issues/219
453
472
  [#220]: https://github.com/DataDog/datadog-ci-rb/issues/220
454
473
  [#221]: https://github.com/DataDog/datadog-ci-rb/issues/221
455
- [#224]: https://github.com/DataDog/datadog-ci-rb/issues/224
474
+ [#224]: https://github.com/DataDog/datadog-ci-rb/issues/224
475
+ [#226]: https://github.com/DataDog/datadog-ci-rb/issues/226
476
+ [#227]: https://github.com/DataDog/datadog-ci-rb/issues/227
477
+ [#229]: https://github.com/DataDog/datadog-ci-rb/issues/229
478
+ [#231]: https://github.com/DataDog/datadog-ci-rb/issues/231
479
+ [#235]: https://github.com/DataDog/datadog-ci-rb/issues/235
480
+ [#236]: https://github.com/DataDog/datadog-ci-rb/issues/236
data/README.md CHANGED
@@ -13,6 +13,7 @@ Learn more on our [official website](https://docs.datadoghq.com/tests/) and chec
13
13
  - [Test Visibility](https://docs.datadoghq.com/tests/) - collect metrics and results for your tests
14
14
  - [Intelligent test runner](https://docs.datadoghq.com/intelligent_test_runner/) - save time by selectively running only tests affected by code changes
15
15
  - [Auto test retries](https://docs.datadoghq.com/tests/auto_test_retries/?tab=ruby) - retrying failing tests up to N times to avoid failing your build due to flaky tests
16
+ - [Early flake detection](https://docs.datadoghq.com/tests/early_flake_detection?tab=ruby) - Datadog’s test flakiness solution that identifies flakes early by running newly added tests multiple times
16
17
  - [Search and manage CI tests](https://docs.datadoghq.com/tests/search/)
17
18
  - [Enhance developer workflows](https://docs.datadoghq.com/tests/developer_workflows)
18
19
  - [Flaky test management](https://docs.datadoghq.com/tests/guides/flaky_test_management/)
@@ -11,6 +11,7 @@ require_relative "../test_optimisation/coverage/transport"
11
11
  require_relative "../test_optimisation/coverage/writer"
12
12
  require_relative "../test_retries/component"
13
13
  require_relative "../test_retries/null_component"
14
+ require_relative "../test_retries/unique_tests_client"
14
15
  require_relative "../test_visibility/component"
15
16
  require_relative "../test_visibility/flush"
16
17
  require_relative "../test_visibility/null_component"
@@ -19,7 +20,6 @@ require_relative "../test_visibility/serializers/factories/test_suite_level"
19
20
  require_relative "../test_visibility/transport"
20
21
  require_relative "../transport/adapters/telemetry_webmock_safe_adapter"
21
22
  require_relative "../transport/api/builder"
22
- require_relative "../utils/identity"
23
23
  require_relative "../utils/parsing"
24
24
  require_relative "../utils/test_run"
25
25
  require_relative "../worker"
@@ -116,12 +116,15 @@ module Datadog
116
116
  @test_retries = TestRetries::Component.new(
117
117
  retry_failed_tests_enabled: settings.ci.retry_failed_tests_enabled,
118
118
  retry_failed_tests_max_attempts: settings.ci.retry_failed_tests_max_attempts,
119
- retry_failed_tests_total_limit: settings.ci.retry_failed_tests_total_limit
119
+ retry_failed_tests_total_limit: settings.ci.retry_failed_tests_total_limit,
120
+ retry_new_tests_enabled: settings.ci.retry_new_tests_enabled,
121
+ unique_tests_client: build_unique_tests_client(settings, test_visibility_api)
120
122
  )
121
123
  # @type ivar @test_optimisation: Datadog::CI::TestOptimisation::Component
122
124
  @test_optimisation = build_test_optimisation(settings, test_visibility_api)
123
125
  @test_visibility = TestVisibility::Component.new(
124
- test_suite_level_visibility_enabled: !settings.ci.force_test_level_visibility
126
+ test_suite_level_visibility_enabled: !settings.ci.force_test_level_visibility,
127
+ logical_test_session_name: settings.ci.test_session_name
125
128
  )
126
129
  end
127
130
 
@@ -236,6 +239,14 @@ module Datadog
236
239
  )
237
240
  end
238
241
 
242
+ def build_unique_tests_client(settings, api)
243
+ TestRetries::UniqueTestsClient.new(
244
+ api: api,
245
+ dd_env: settings.env,
246
+ config_tags: custom_configuration(settings)
247
+ )
248
+ end
249
+
239
250
  # fetch custom tags provided by the user in DD_TAGS env var
240
251
  # with prefix test.configuration.
241
252
  def custom_configuration(settings)
@@ -274,12 +285,8 @@ module Datadog
274
285
  settings.telemetry.shutdown_timeout_seconds = 60.0
275
286
 
276
287
  begin
277
- require "datadog/core/environment/identity"
278
288
  require "datadog/core/telemetry/http/adapters/net"
279
289
 
280
- # patch gem's identity to report datadog-ci library version instead of datadog gem version
281
- Core::Environment::Identity.include(CI::Utils::Identity)
282
-
283
290
  # patch gem's telemetry transport layer to use Net::HTTP instead of WebMock's Net::HTTP
284
291
  Core::Telemetry::Http::Adapters::Net.include(CI::Transport::Adapters::TelemetryWebmockSafeAdapter)
285
292
  rescue => e
@@ -24,6 +24,11 @@ module Datadog
24
24
  o.default false
25
25
  end
26
26
 
27
+ option :test_session_name do |o|
28
+ o.type :string, nilable: true
29
+ o.env CI::Ext::Settings::ENV_TEST_SESSION_NAME
30
+ end
31
+
27
32
  option :agentless_mode_enabled do |o|
28
33
  o.type :bool
29
34
  o.env CI::Ext::Settings::ENV_AGENTLESS_MODE_ENABLED
@@ -106,6 +111,12 @@ module Datadog
106
111
  o.default 1000
107
112
  end
108
113
 
114
+ option :retry_new_tests_enabled do |o|
115
+ o.type :bool
116
+ o.env CI::Ext::Settings::ENV_RETRY_NEW_TESTS_ENABLED
117
+ o.default true
118
+ end
119
+
109
120
  define_method(:instrument) do |integration_name, options = {}, &block|
110
121
  return unless enabled
111
122
 
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module Contrib
6
+ module Cucumber
7
+ class Filter < ::Cucumber::Core::Filter.new(:configuration)
8
+ def test_case(test_case)
9
+ test_retries_component.reset_retries! unless test_case_seen[test_case]
10
+ test_case_seen[test_case] = true
11
+
12
+ configuration.on_event(:test_case_finished) do |event|
13
+ next unless retry_required?(test_case, event)
14
+
15
+ test_case.describe_to(receiver)
16
+ end
17
+
18
+ super
19
+ end
20
+
21
+ private
22
+
23
+ def retry_required?(test_case, event)
24
+ return false unless event.test_case == test_case
25
+
26
+ test_retries_component.should_retry?
27
+ end
28
+
29
+ def test_case_seen
30
+ @test_case_seen ||= Hash.new { |h, k| h[k] = false }
31
+ end
32
+
33
+ def test_retries_component
34
+ @test_retries_component ||= Datadog.send(:components).test_retries
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -18,10 +18,24 @@ module Datadog
18
18
 
19
19
  def formatters
20
20
  existing_formatters = super
21
- @datadog_formatter ||= CI::Contrib::Cucumber::Formatter.new(@configuration)
21
+ @datadog_formatter ||= Formatter.new(@configuration)
22
22
  [@datadog_formatter] + existing_formatters
23
23
  end
24
24
 
25
+ def filters
26
+ require_relative "filter"
27
+
28
+ filters_list = super
29
+ datadog_filter = Filter.new(@configuration)
30
+ unless @configuration.dry_run?
31
+ # insert our filter the pre-last position because Cucumber::Filters::PrepareWorld must be the last one
32
+ # see:
33
+ # https://github.com/cucumber/cucumber-ruby/blob/58dd8f12c0ac5f4e607335ff2e7d385c1ed25899/lib/cucumber/runtime.rb#L266
34
+ filters_list.insert(-2, datadog_filter)
35
+ end
36
+ filters_list
37
+ end
38
+
25
39
  def begin_scenario(test_case)
26
40
  if Datadog::CI.active_test&.skipped_by_itr?
27
41
  raise ::Cucumber::Core::Test::Result::Skipped, CI::Ext::Test::ITR_TEST_SKIP_REASON
@@ -3,7 +3,6 @@
3
3
  require "datadog/tracing/contrib/patcher"
4
4
 
5
5
  require_relative "instrumentation"
6
- require_relative "configuration_override"
7
6
 
8
7
  module Datadog
9
8
  module CI
@@ -21,7 +20,6 @@ module Datadog
21
20
 
22
21
  def patch
23
22
  ::Cucumber::Runtime.include(Instrumentation)
24
- ::Cucumber::Configuration.include(ConfigurationOverride)
25
23
  end
26
24
  end
27
25
  end
@@ -8,6 +8,8 @@ module Datadog
8
8
  module Contrib
9
9
  module Minitest
10
10
  module Runner
11
+ DD_ESTIMATED_TESTS_PER_SUITE = 5
12
+
11
13
  def self.included(base)
12
14
  base.singleton_class.prepend(ClassMethods)
13
15
  end
@@ -18,12 +20,15 @@ module Datadog
18
20
 
19
21
  return unless datadog_configuration[:enabled]
20
22
 
23
+ # minitest does not store the total number of tests, so we can't pass it to the test session
24
+ # instead, we use the number of test suites * DD_ESTIMATED_TESTS_PER_SUITE as a rough estimate
21
25
  test_visibility_component.start_test_session(
22
26
  tags: {
23
27
  CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
24
28
  CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Minitest::Integration.version.to_s
25
29
  },
26
- service: datadog_configuration[:service_name]
30
+ service: datadog_configuration[:service_name],
31
+ total_tests_count: (DD_ESTIMATED_TESTS_PER_SUITE * ::Minitest::Runnable.runnables.size).to_i
27
32
  )
28
33
  test_visibility_component.start_test_module(Ext::FRAMEWORK)
29
34
  end
@@ -51,6 +51,10 @@ module Datadog
51
51
  return super unless test_span
52
52
 
53
53
  finish_with_result(test_span, result_code)
54
+
55
+ # remove failures if test passed at least once on retries
56
+ self.failures = [] if test_span.any_retry_passed?
57
+
54
58
  if Helpers.parallel?(self.class)
55
59
  finish_with_result(test_span.test_suite, result_code)
56
60
  end
@@ -64,9 +64,9 @@ module Datadog
64
64
 
65
65
  result = super
66
66
 
67
- # In case when test job is canceled and RSpec is quitting we don't want to report the last test
67
+ # When test job is canceled and RSpec is quitting we don't want to report the last test
68
68
  # before RSpec context unwinds. This test might have some unrelated errors that we don't want to
69
- # report.
69
+ # see in Datadog.
70
70
  return result if ::RSpec.world.wants_to_quit
71
71
 
72
72
  case execution_result.status
@@ -74,6 +74,8 @@ module Datadog
74
74
  test_span&.passed!
75
75
  when :failed
76
76
  test_span&.failed!(exception: execution_result.exception)
77
+ # if any of the retries passed, we don't fail the test run
78
+ @exception = nil if test_span&.any_retry_passed?
77
79
  else
78
80
  # :pending or nil
79
81
  test_span&.skipped!(
@@ -84,21 +86,20 @@ module Datadog
84
86
  end
85
87
  end
86
88
 
87
- # after retries are done, we can report the test to RSpec
88
- @skip_reporting = false
89
-
90
89
  # this is a special case for ci-queue, we need to finish the test suite span
91
90
  ci_queue_test_span&.finish
92
91
 
93
- # Finish spec with latest retry's result
94
- # TODO: when implementing new test retries make sure to clean @exception before calling this method
95
- # if test passed at least once
92
+ # after retries are done, we can finally report the test to RSpec
93
+ @skip_reporting = false
96
94
  finish(reporter)
97
95
  end
98
96
 
99
97
  def finish(reporter)
100
- # by default finish test but do not report it to RSpec::Core::Reporter
101
- # it is going to be reported once after retries are done
98
+ # By default finish test but do not report it to RSpec::Core::Reporter
99
+ # it is going to be reported once after retries are done.
100
+ #
101
+ # We need to do this because RSpec breaks when we try to report the same example multiple times with different
102
+ # results.
102
103
  return super unless @skip_reporting
103
104
 
104
105
  super(::RSpec::Core::NullReporter)
@@ -23,7 +23,8 @@ module Datadog
23
23
  CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
24
24
  CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::RSpec::Integration.version.to_s
25
25
  },
26
- service: datadog_configuration[:service_name]
26
+ service: datadog_configuration[:service_name],
27
+ total_tests_count: ::RSpec.world.example_count
27
28
  )
28
29
 
29
30
  test_module = test_visibility_component.start_test_module(Ext::FRAMEWORK)
@@ -18,6 +18,8 @@ module Datadog
18
18
  ENV_RETRY_FAILED_TESTS_ENABLED = "DD_CIVISIBILITY_FLAKY_RETRY_ENABLED"
19
19
  ENV_RETRY_FAILED_TESTS_MAX_ATTEMPTS = "DD_CIVISIBILITY_FLAKY_RETRY_COUNT"
20
20
  ENV_RETRY_FAILED_TESTS_TOTAL_LIMIT = "DD_CIVISIBILITY_TOTAL_FLAKY_RETRY_COUNT"
21
+ ENV_RETRY_NEW_TESTS_ENABLED = "DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED"
22
+ ENV_TEST_SESSION_NAME = "DD_TEST_SESSION_NAME"
21
23
 
22
24
  # Source: https://docs.datadoghq.com/getting_started/site/
23
25
  DD_SITE_ALLOWLIST = %w[
@@ -54,6 +54,12 @@ module Datadog
54
54
  METRIC_CODE_COVERAGE_IS_EMPTY = "code_coverage.is_empty"
55
55
  METRIC_CODE_COVERAGE_FILES = "code_coverage.files"
56
56
 
57
+ METRIC_EFD_UNIQUE_TESTS_REQUEST = "early_flake_detection.request"
58
+ METRIC_EFD_UNIQUE_TESTS_REQUEST_MS = "early_flake_detection.request_ms"
59
+ METRIC_EFD_UNIQUE_TESTS_REQUEST_ERRORS = "early_flake_detection.request_errors"
60
+ METRIC_EFD_UNIQUE_TESTS_RESPONSE_BYTES = "early_flake_detection.response_bytes"
61
+ METRIC_EFD_UNIQUE_TESTS_RESPONSE_TESTS = "early_flake_detection.response_tests"
62
+
57
63
  METRIC_TEST_SESSION = "test_session"
58
64
 
59
65
  TAG_TEST_FRAMEWORK = "test_framework"
@@ -63,6 +69,7 @@ module Datadog
63
69
  TAG_BROWSER_DRIVER = "browser_driver"
64
70
  TAG_IS_RUM = "is_rum"
65
71
  TAG_IS_RETRY = "is_retry"
72
+ TAG_IS_NEW = "is_new"
66
73
  TAG_LIBRARY = "library"
67
74
  TAG_ENDPOINT = "endpoint"
68
75
  TAG_ERROR_TYPE = "error_type"
@@ -73,6 +80,8 @@ module Datadog
73
80
  TAG_COMMAND = "command"
74
81
  TAG_COVERAGE_ENABLED = "coverage_enabled"
75
82
  TAG_ITR_SKIP_ENABLED = "itrskip_enabled"
83
+ TAG_EARLY_FLAKE_DETECTION_ENABLED = "early_flake_detection_enabled"
84
+ TAG_EARLY_FLAKE_DETECTION_ABORT_REASON = "early_flake_detection_abort_reason"
76
85
  TAG_PROVIDER = "provider"
77
86
  TAG_AUTO_INJECTED = "auto_injected"
78
87
 
@@ -58,13 +58,22 @@ module Datadog
58
58
  # version of the browser, if multiple browsers or multiple versions then this tag is empty
59
59
  TAG_BROWSER_VERSION = "test.browser.version"
60
60
 
61
- # Tags for test retries
61
+ # Tags for retries
62
62
  TAG_IS_RETRY = "test.is_retry" # true if test was retried by datadog-ci library
63
+ TAG_IS_NEW = "test.is_new" # true if test was marked as new by new test retries (early flake detection)
64
+ TAG_EARLY_FLAKE_ENABLED = "test.early_flake.enabled" # true if early flake detection is enabled
65
+ TAG_EARLY_FLAKE_ABORT_REASON = "test.early_flake.abort_reason" # reason why early flake detection was aborted
63
66
 
64
67
  # internal APM tag to mark a span as a test span
65
68
  TAG_SPAN_KIND = "span.kind"
66
69
  SPAN_KIND_TEST = "test"
67
70
 
71
+ # common tags that are serialized directly in msgpack header in metadata field
72
+ METADATA_TAG_TEST_SESSION_NAME = "test_session.name"
73
+
74
+ # internal metric with the number of virtual CPUs
75
+ METRIC_CPU_COUNT = "_dd.host.vcpu_count"
76
+
68
77
  # tags that are common for the whole session and can be inherited from the test session
69
78
  INHERITABLE_TAGS = [TAG_FRAMEWORK, TAG_FRAMEWORK_VERSION].freeze
70
79
 
@@ -73,6 +82,8 @@ module Datadog
73
82
  ITR_TEST_SKIP_REASON = "Skipped by Datadog's intelligent test runner"
74
83
  ITR_UNSKIPPABLE_OPTION = :datadog_itr_unskippable
75
84
 
85
+ EARLY_FLAKE_FAULTY = "faulty"
86
+
76
87
  # test status as recognized by Datadog
77
88
  module Status
78
89
  PASS = "pass"
@@ -37,6 +37,10 @@ module Datadog
37
37
  DD_API_SETTINGS_RESPONSE_TESTS_SKIPPING_KEY = "tests_skipping"
38
38
  DD_API_SETTINGS_RESPONSE_REQUIRE_GIT_KEY = "require_git"
39
39
  DD_API_SETTINGS_RESPONSE_FLAKY_TEST_RETRIES_KEY = "flaky_test_retries_enabled"
40
+ DD_API_SETTINGS_RESPONSE_EARLY_FLAKE_DETECTION_KEY = "early_flake_detection"
41
+ DD_API_SETTINGS_RESPONSE_ENABLED_KEY = "enabled"
42
+ DD_API_SETTINGS_RESPONSE_SLOW_TEST_RETRIES_KEY = "slow_test_retries"
43
+ DD_API_SETTINGS_RESPONSE_FAULTY_SESSION_THRESHOLD_KEY = "faulty_session_threshold"
40
44
  DD_API_SETTINGS_RESPONSE_DEFAULT = {DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY => false}.freeze
41
45
 
42
46
  DD_API_GIT_SEARCH_COMMITS_PATH = "/api/v2/git/repository/search_commits"
@@ -46,6 +50,9 @@ module Datadog
46
50
  DD_API_SKIPPABLE_TESTS_PATH = "/api/v2/ci/tests/skippable"
47
51
  DD_API_SKIPPABLE_TESTS_TYPE = "test_params"
48
52
 
53
+ DD_API_UNIQUE_TESTS_PATH = "/api/v2/ci/libraries/tests"
54
+ DD_API_UNIQUE_TESTS_TYPE = "ci_app_libraries_tests_request"
55
+
49
56
  CONTENT_TYPE_MESSAGEPACK = "application/msgpack"
50
57
  CONTENT_TYPE_JSON = "application/json"
51
58
  CONTENT_TYPE_MULTIPART_FORM_DATA = "multipart/form-data"
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../worker"
4
+
3
5
  module Datadog
4
6
  module CI
5
7
  module Remote
@@ -27,8 +29,17 @@ module Datadog
27
29
  end
28
30
  end
29
31
 
30
- test_optimisation.configure(library_configuration, test_session)
31
- test_retries.configure(library_configuration)
32
+ # configure different components in parallel because they might block on HTTP requests
33
+ configuration_workers = [
34
+ Worker.new { test_optimisation.configure(library_configuration, test_session) },
35
+ Worker.new { test_retries.configure(library_configuration, test_session) }
36
+ ]
37
+
38
+ # launch configuration workers
39
+ configuration_workers.each(&:perform)
40
+
41
+ # block until all workers are done (or 60 seconds has passed)
42
+ configuration_workers.each(&:wait_until_done)
32
43
  end
33
44
 
34
45
  private
@@ -7,6 +7,8 @@ require_relative "../ext/transport"
7
7
  require_relative "../transport/telemetry"
8
8
  require_relative "../utils/parsing"
9
9
 
10
+ require_relative "slow_test_retries"
11
+
10
12
  module Datadog
11
13
  module CI
12
14
  module Remote
@@ -49,37 +51,76 @@ module Datadog
49
51
  def require_git?
50
52
  return @require_git if defined?(@require_git)
51
53
 
52
- @require_git = bool(Ext::Transport::DD_API_SETTINGS_RESPONSE_REQUIRE_GIT_KEY)
54
+ @require_git = Utils::Parsing.convert_to_bool(
55
+ payload.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_REQUIRE_GIT_KEY, false)
56
+ )
53
57
  end
54
58
 
55
59
  def itr_enabled?
56
60
  return @itr_enabled if defined?(@itr_enabled)
57
61
 
58
- @itr_enabled = bool(Ext::Transport::DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY)
62
+ @itr_enabled = Utils::Parsing.convert_to_bool(
63
+ payload.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_ITR_ENABLED_KEY, false)
64
+ )
59
65
  end
60
66
 
61
67
  def code_coverage_enabled?
62
68
  return @code_coverage_enabled if defined?(@code_coverage_enabled)
63
69
 
64
- @code_coverage_enabled = bool(Ext::Transport::DD_API_SETTINGS_RESPONSE_CODE_COVERAGE_KEY)
70
+ @code_coverage_enabled = Utils::Parsing.convert_to_bool(
71
+ payload.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_CODE_COVERAGE_KEY, false)
72
+ )
65
73
  end
66
74
 
67
75
  def tests_skipping_enabled?
68
76
  return @tests_skipping_enabled if defined?(@tests_skipping_enabled)
69
77
 
70
- @tests_skipping_enabled = bool(Ext::Transport::DD_API_SETTINGS_RESPONSE_TESTS_SKIPPING_KEY)
78
+ @tests_skipping_enabled = Utils::Parsing.convert_to_bool(
79
+ payload.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_TESTS_SKIPPING_KEY, false)
80
+ )
71
81
  end
72
82
 
73
83
  def flaky_test_retries_enabled?
74
84
  return @flaky_test_retries_enabled if defined?(@flaky_test_retries_enabled)
75
85
 
76
- @flaky_test_retries_enabled = bool(Ext::Transport::DD_API_SETTINGS_RESPONSE_FLAKY_TEST_RETRIES_KEY)
86
+ @flaky_test_retries_enabled = Utils::Parsing.convert_to_bool(
87
+ payload.fetch(
88
+ Ext::Transport::DD_API_SETTINGS_RESPONSE_FLAKY_TEST_RETRIES_KEY, false
89
+ )
90
+ )
91
+ end
92
+
93
+ def early_flake_detection_enabled?
94
+ return @early_flake_detection_enabled if defined?(@early_flake_detection_enabled)
95
+
96
+ @early_flake_detection_enabled = Utils::Parsing.convert_to_bool(
97
+ early_flake_detection_payload.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_ENABLED_KEY, false)
98
+ )
99
+ end
100
+
101
+ def slow_test_retries
102
+ return @slow_test_retries if defined?(@slow_test_retries)
103
+
104
+ @slow_test_retries = SlowTestRetries.new(
105
+ early_flake_detection_payload.fetch(Ext::Transport::DD_API_SETTINGS_RESPONSE_SLOW_TEST_RETRIES_KEY, {})
106
+ )
107
+ end
108
+
109
+ def faulty_session_threshold
110
+ return @faulty_session_threshold if defined?(@faulty_session_threshold)
111
+
112
+ @faulty_session_threshold = early_flake_detection_payload.fetch(
113
+ Ext::Transport::DD_API_SETTINGS_RESPONSE_FAULTY_SESSION_THRESHOLD_KEY, 0
114
+ )
77
115
  end
78
116
 
79
117
  private
80
118
 
81
- def bool(key)
82
- Utils::Parsing.convert_to_bool(payload.fetch(key, false))
119
+ def early_flake_detection_payload
120
+ payload.fetch(
121
+ Ext::Transport::DD_API_SETTINGS_RESPONSE_EARLY_FLAKE_DETECTION_KEY,
122
+ {}
123
+ )
83
124
  end
84
125
 
85
126
  def default_payload
@@ -58,7 +58,8 @@ module Datadog
58
58
  1,
59
59
  {
60
60
  Ext::Telemetry::TAG_COVERAGE_ENABLED => library_settings.code_coverage_enabled?.to_s,
61
- Ext::Telemetry::TAG_ITR_SKIP_ENABLED => library_settings.tests_skipping_enabled?.to_s
61
+ Ext::Telemetry::TAG_ITR_SKIP_ENABLED => library_settings.tests_skipping_enabled?.to_s,
62
+ Ext::Telemetry::TAG_EARLY_FLAKE_DETECTION_ENABLED => library_settings.early_flake_detection_enabled?.to_s
62
63
  }
63
64
  )
64
65
 
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module Remote
6
+ # Parses "slow_test_retries" payload for early flake detection settings
7
+ #
8
+ # Example payload:
9
+ # {
10
+ # "5s" => 10,
11
+ # "10s" => 5,
12
+ # "30s" => 3,
13
+ # "5m" => 2
14
+ # }
15
+ #
16
+ # The payload above means that for tests that run less than 5 seconds, we should retry them 10 times,
17
+ # for tests that run less than 10 seconds, we should retry them 5 times, and so on.
18
+ class SlowTestRetries
19
+ attr_reader :entries
20
+
21
+ Entry = Struct.new(:duration, :max_attempts)
22
+
23
+ DURATION_MEASURES = {
24
+ "s" => 1,
25
+ "m" => 60
26
+ }.freeze
27
+
28
+ def initialize(payload)
29
+ @entries = parse(payload)
30
+ end
31
+
32
+ def max_attempts_for_duration(duration)
33
+ @entries.each do |entry|
34
+ return entry.max_attempts if duration < entry.duration
35
+ end
36
+
37
+ 0
38
+ end
39
+
40
+ private
41
+
42
+ def parse(payload)
43
+ (payload || {}).keys.filter_map do |key|
44
+ duration, measure = key.match(/(\d+)(\w+)/)&.captures
45
+ next if duration.nil? || measure.nil? || !DURATION_MEASURES.key?(measure)
46
+
47
+ Entry.new(duration.to_f * DURATION_MEASURES.fetch(measure, 1), payload[key].to_i)
48
+ end.sort_by(&:duration)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -114,6 +114,13 @@ module Datadog
114
114
  tracer_span.clear_tag(key)
115
115
  end
116
116
 
117
+ # Gets metric value by key.
118
+ # @param [String] key the key of the metric.
119
+ # @return [Numeric] value the value of the metric.
120
+ def get_metric(key)
121
+ tracer_span.get_metric(key)
122
+ end
123
+
117
124
  # Sets metric value by key.
118
125
  # @param [String] key the key of the metric.
119
126
  # @param [Numeric] value the value of the metric.