datadog-ci 1.4.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.