datadog-ci 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -2
  3. data/README.md +1 -0
  4. data/ext/datadog_cov/datadog_cov.c +1 -1
  5. data/lib/datadog/ci/configuration/components.rb +12 -6
  6. data/lib/datadog/ci/configuration/settings.rb +6 -0
  7. data/lib/datadog/ci/contrib/cucumber/filter.rb +40 -0
  8. data/lib/datadog/ci/contrib/cucumber/instrumentation.rb +15 -1
  9. data/lib/datadog/ci/contrib/cucumber/patcher.rb +0 -2
  10. data/lib/datadog/ci/contrib/minitest/runner.rb +6 -1
  11. data/lib/datadog/ci/contrib/minitest/test.rb +4 -0
  12. data/lib/datadog/ci/contrib/rspec/example.rb +11 -10
  13. data/lib/datadog/ci/contrib/rspec/runner.rb +2 -1
  14. data/lib/datadog/ci/ext/settings.rb +1 -0
  15. data/lib/datadog/ci/ext/telemetry.rb +9 -0
  16. data/lib/datadog/ci/ext/test.rb +6 -1
  17. data/lib/datadog/ci/ext/transport.rb +7 -0
  18. data/lib/datadog/ci/remote/component.rb +13 -2
  19. data/lib/datadog/ci/remote/library_settings.rb +48 -7
  20. data/lib/datadog/ci/remote/library_settings_client.rb +2 -1
  21. data/lib/datadog/ci/remote/slow_test_retries.rb +53 -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 +2 -0
  37. data/lib/datadog/ci/test_suite.rb +8 -0
  38. data/lib/datadog/ci/test_visibility/component.rb +23 -13
  39. data/lib/datadog/ci/test_visibility/null_component.rb +1 -1
  40. data/lib/datadog/ci/test_visibility/telemetry.rb +9 -0
  41. data/lib/datadog/ci/utils/test_run.rb +1 -1
  42. data/lib/datadog/ci/version.rb +1 -1
  43. data/lib/datadog/ci.rb +6 -3
  44. metadata +11 -5
  45. data/lib/datadog/ci/contrib/cucumber/configuration_override.rb +0 -37
  46. data/lib/datadog/ci/utils/identity.rb +0 -20
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ require_relative "../ext/telemetry"
6
+ require_relative "../ext/transport"
7
+ require_relative "../transport/telemetry"
8
+ require_relative "../utils/telemetry"
9
+ require_relative "../utils/test_run"
10
+
11
+ module Datadog
12
+ module CI
13
+ module TestRetries
14
+ # fetch a list of unique known tests from the backend
15
+ class UniqueTestsClient
16
+ class Response
17
+ def initialize(http_response)
18
+ @http_response = http_response
19
+ @json = nil
20
+ end
21
+
22
+ def ok?
23
+ resp = @http_response
24
+ !resp.nil? && resp.ok?
25
+ end
26
+
27
+ def tests
28
+ res = Set.new
29
+
30
+ payload
31
+ .fetch("data", {})
32
+ .fetch("attributes", {})
33
+ .fetch("tests", {})
34
+ .each do |_test_module, suites_hash|
35
+ suites_hash.each do |test_suite, tests|
36
+ tests.each do |test_name|
37
+ res << Utils::TestRun.datadog_test_id(test_name, test_suite)
38
+ end
39
+ end
40
+ end
41
+
42
+ res
43
+ end
44
+
45
+ private
46
+
47
+ def payload
48
+ cached = @json
49
+ return cached unless cached.nil?
50
+
51
+ resp = @http_response
52
+ return @json = {} if resp.nil? || !ok?
53
+
54
+ begin
55
+ @json = JSON.parse(resp.payload)
56
+ rescue JSON::ParserError => e
57
+ Datadog.logger.error("Failed to parse unique known tests response payload: #{e}. Payload was: #{resp.payload}")
58
+ @json = {}
59
+ end
60
+ end
61
+ end
62
+
63
+ def initialize(dd_env:, api: nil, config_tags: {})
64
+ @api = api
65
+ @dd_env = dd_env
66
+ @config_tags = config_tags
67
+ end
68
+
69
+ def fetch_unique_tests(test_session)
70
+ api = @api
71
+ return Set.new unless api
72
+
73
+ request_payload = payload(test_session)
74
+ Datadog.logger.debug("Fetching unique known tests with request: #{request_payload}")
75
+
76
+ http_response = api.api_request(
77
+ path: Ext::Transport::DD_API_UNIQUE_TESTS_PATH,
78
+ payload: request_payload
79
+ )
80
+
81
+ Transport::Telemetry.api_requests(
82
+ Ext::Telemetry::METRIC_EFD_UNIQUE_TESTS_REQUEST,
83
+ 1,
84
+ compressed: http_response.request_compressed
85
+ )
86
+ Utils::Telemetry.distribution(Ext::Telemetry::METRIC_EFD_UNIQUE_TESTS_REQUEST_MS, http_response.duration_ms)
87
+ Utils::Telemetry.distribution(
88
+ Ext::Telemetry::METRIC_EFD_UNIQUE_TESTS_RESPONSE_BYTES,
89
+ http_response.response_size.to_f,
90
+ {Ext::Telemetry::TAG_RESPONSE_COMPRESSED => http_response.gzipped_content?.to_s}
91
+ )
92
+
93
+ unless http_response.ok?
94
+ Transport::Telemetry.api_requests_errors(
95
+ Ext::Telemetry::METRIC_EFD_UNIQUE_TESTS_REQUEST_ERRORS,
96
+ 1,
97
+ error_type: http_response.telemetry_error_type,
98
+ status_code: http_response.code
99
+ )
100
+ end
101
+
102
+ Response.new(http_response).tests
103
+ end
104
+
105
+ private
106
+
107
+ def payload(test_session)
108
+ {
109
+ "data" => {
110
+ "id" => Datadog::Core::Environment::Identity.id,
111
+ "type" => Ext::Transport::DD_API_UNIQUE_TESTS_TYPE,
112
+ "attributes" => {
113
+ "repository_url" => test_session.git_repository_url,
114
+ "service" => test_session.service,
115
+ "env" => @dd_env,
116
+ "sha" => test_session.git_commit_sha,
117
+ "configurations" => {
118
+ Ext::Test::TAG_OS_PLATFORM => test_session.os_platform,
119
+ Ext::Test::TAG_OS_ARCHITECTURE => test_session.os_architecture,
120
+ Ext::Test::TAG_OS_VERSION => test_session.os_version,
121
+ Ext::Test::TAG_RUNTIME_NAME => test_session.runtime_name,
122
+ Ext::Test::TAG_RUNTIME_VERSION => test_session.runtime_version,
123
+ "custom" => @config_tags
124
+ }
125
+ }
126
+ }
127
+ }.to_json
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -12,6 +12,8 @@ module Datadog
12
12
  #
13
13
  # @public_api
14
14
  class TestSession < ConcurrentSpan
15
+ attr_accessor :total_tests_count
16
+
15
17
  # Finishes the current test session.
16
18
  # @return [void]
17
19
  def finish
@@ -51,6 +51,14 @@ module Datadog
51
51
  end
52
52
  end
53
53
 
54
+ # @internal
55
+ def any_test_retry_passed?(test_id)
56
+ synchronize do
57
+ stats = @execution_stats_per_test[test_id]
58
+ stats && stats[Ext::Test::Status::PASS] > 0
59
+ end
60
+ end
61
+
54
62
  # @internal
55
63
  def test_executed?(test_id)
56
64
  synchronize do
@@ -19,8 +19,6 @@ module Datadog
19
19
  class Component
20
20
  attr_reader :test_suite_level_visibility_enabled
21
21
 
22
- FIBER_LOCAL_TEST_FINISHED_CALLBACK_KEY = :__dd_test_finished_callback
23
-
24
22
  def initialize(
25
23
  test_suite_level_visibility_enabled: false,
26
24
  codeowners: Codeowners::Parser.new(Git::LocalRepository.root).parse
@@ -30,10 +28,12 @@ module Datadog
30
28
  @codeowners = codeowners
31
29
  end
32
30
 
33
- def start_test_session(service: nil, tags: {})
31
+ def start_test_session(service: nil, tags: {}, total_tests_count: 0)
34
32
  return skip_tracing unless test_suite_level_visibility_enabled
35
33
 
36
34
  test_session = @context.start_test_session(service: service, tags: tags)
35
+ test_session.total_tests_count = total_tests_count
36
+
37
37
  on_test_session_started(test_session)
38
38
  test_session
39
39
  end
@@ -57,6 +57,8 @@ module Datadog
57
57
  def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
58
58
  if block
59
59
  @context.trace_test(test_name, test_suite_name, service: service, tags: tags) do |test|
60
+ subscribe_to_after_stop_event(test.tracer_span)
61
+
60
62
  on_test_started(test)
61
63
  res = block.call(test)
62
64
  on_test_finished(test)
@@ -64,6 +66,7 @@ module Datadog
64
66
  end
65
67
  else
66
68
  test = @context.trace_test(test_name, test_suite_name, service: service, tags: tags)
69
+ subscribe_to_after_stop_event(test.tracer_span)
67
70
  on_test_started(test)
68
71
  test
69
72
  end
@@ -127,15 +130,6 @@ module Datadog
127
130
  @context.deactivate_test_suite(test_suite_name)
128
131
  end
129
132
 
130
- # sets fiber-local callback to be called when test is finished
131
- def set_test_finished_callback(callback)
132
- Thread.current[FIBER_LOCAL_TEST_FINISHED_CALLBACK_KEY] = callback
133
- end
134
-
135
- def remove_test_finished_callback
136
- Thread.current[FIBER_LOCAL_TEST_FINISHED_CALLBACK_KEY] = nil
137
- end
138
-
139
133
  def itr_enabled?
140
134
  test_optimisation.enabled?
141
135
  end
@@ -204,7 +198,11 @@ module Datadog
204
198
 
205
199
  Telemetry.event_finished(test)
206
200
 
207
- Thread.current[FIBER_LOCAL_TEST_FINISHED_CALLBACK_KEY]&.call(test)
201
+ test_retries.record_test_finished(test)
202
+ end
203
+
204
+ def on_after_test_span_finished(tracer_span)
205
+ test_retries.record_test_span_duration(tracer_span)
208
206
  end
209
207
 
210
208
  # HELPERS
@@ -212,6 +210,14 @@ module Datadog
212
210
  block&.call(nil)
213
211
  end
214
212
 
213
+ def subscribe_to_after_stop_event(tracer_span)
214
+ events = tracer_span.send(:events)
215
+
216
+ events.after_stop.subscribe do |span|
217
+ on_after_test_span_finished(span)
218
+ end
219
+ end
220
+
215
221
  def set_codeowners(test)
216
222
  source = test.source_file
217
223
  owners = @codeowners.list_owners(source) if source
@@ -267,6 +273,10 @@ module Datadog
267
273
  Datadog.send(:components).test_optimisation
268
274
  end
269
275
 
276
+ def test_retries
277
+ Datadog.send(:components).test_retries
278
+ end
279
+
270
280
  def git_tree_upload_worker
271
281
  Datadog.send(:components).git_tree_upload_worker
272
282
  end
@@ -5,7 +5,7 @@ module Datadog
5
5
  module TestVisibility
6
6
  # Special test visibility component that does not record anything
7
7
  class NullComponent
8
- def start_test_session(service: nil, tags: {})
8
+ def start_test_session(service: nil, tags: {}, total_tests_count: 0)
9
9
  skip_tracing
10
10
  end
11
11
 
@@ -58,6 +58,15 @@ module Datadog
58
58
  # set is_retry tag if span represents a retried test
59
59
  tags[Ext::Telemetry::TAG_IS_RETRY] = "true" if span.get_tag(Ext::Test::TAG_IS_RETRY)
60
60
 
61
+ # is_new
62
+ tags[Ext::Telemetry::TAG_IS_NEW] = "true" if span.get_tag(Ext::Test::TAG_IS_NEW)
63
+
64
+ # session-level tag - early_flake_detection_abort_reason
65
+ early_flake_detection_abort_reason = span.get_tag(Ext::Test::TAG_EARLY_FLAKE_ABORT_REASON)
66
+ if early_flake_detection_abort_reason
67
+ tags[Ext::Telemetry::TAG_EARLY_FLAKE_DETECTION_ABORT_REASON] = early_flake_detection_abort_reason
68
+ end
69
+
61
70
  tags
62
71
  end
63
72
 
@@ -10,7 +10,7 @@ module Datadog
10
10
  @command = "#{$0} #{ARGV.join(" ")}"
11
11
  end
12
12
 
13
- def self.skippable_test_id(test_name, suite, parameters = nil)
13
+ def self.datadog_test_id(test_name, suite, parameters = nil)
14
14
  "#{suite}.#{test_name}.#{parameters}"
15
15
  end
16
16
 
@@ -4,7 +4,7 @@ module Datadog
4
4
  module CI
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 4
7
+ MINOR = 5
8
8
  PATCH = 0
9
9
  PRE = nil
10
10
  BUILD = nil
data/lib/datadog/ci.rb CHANGED
@@ -6,6 +6,7 @@ require_relative "ci/utils/telemetry"
6
6
  require_relative "ci/ext/app_types"
7
7
  require_relative "ci/ext/telemetry"
8
8
 
9
+ require "datadog"
9
10
  require "datadog/core"
10
11
 
11
12
  module Datadog
@@ -27,7 +28,8 @@ module Datadog
27
28
  # ```
28
29
  # Datadog::CI.start_test_session(
29
30
  # service: "my-web-site-tests",
30
- # tags: { Datadog::CI::Ext::Test::TAG_FRAMEWORK => "my-test-framework" }
31
+ # tags: { Datadog::CI::Ext::Test::TAG_FRAMEWORK => "my-test-framework" },
32
+ # total_tests_count: 100
31
33
  # )
32
34
  #
33
35
  # # Somewhere else after test run has ended
@@ -38,15 +40,16 @@ module Datadog
38
40
  #
39
41
  # @param [String] service the service name for this session (optional, defaults to DD_SERVICE or repository name)
40
42
  # @param [Hash<String,String>] tags extra tags which should be added to the test session.
43
+ # @param [Integer] total_tests_count the total number of tests in the test session (optional, defaults to 0) - it is used to limit the number of new tests retried within session if early flake detection is enabled
41
44
  # @return [Datadog::CI::TestSession] the active, running {Datadog::CI::TestSession}.
42
45
  # @return [nil] if test suite level visibility is disabled or CI mode is disabled.
43
- def start_test_session(service: Utils::Configuration.fetch_service_name("test"), tags: {})
46
+ def start_test_session(service: Utils::Configuration.fetch_service_name("test"), tags: {}, total_tests_count: 0)
44
47
  Utils::Telemetry.inc(
45
48
  Ext::Telemetry::METRIC_MANUAL_API_EVENTS,
46
49
  1,
47
50
  {Ext::Telemetry::TAG_EVENT_TYPE => Ext::Telemetry::EventType::SESSION}
48
51
  )
49
- test_visibility.start_test_session(service: service, tags: tags)
52
+ test_visibility.start_test_session(service: service, tags: tags, total_tests_count: total_tests_count)
50
53
  end
51
54
 
52
55
  # The active, unfinished test session.
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.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-26 00:00:00.000000000 Z
11
+ date: 2024-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: datadog
@@ -68,8 +68,8 @@ files:
68
68
  - lib/datadog/ci/configuration/settings.rb
69
69
  - lib/datadog/ci/contrib/contrib.rb
70
70
  - lib/datadog/ci/contrib/cucumber/configuration/settings.rb
71
- - lib/datadog/ci/contrib/cucumber/configuration_override.rb
72
71
  - lib/datadog/ci/contrib/cucumber/ext.rb
72
+ - lib/datadog/ci/contrib/cucumber/filter.rb
73
73
  - lib/datadog/ci/contrib/cucumber/formatter.rb
74
74
  - lib/datadog/ci/contrib/cucumber/instrumentation.rb
75
75
  - lib/datadog/ci/contrib/cucumber/integration.rb
@@ -139,6 +139,7 @@ files:
139
139
  - lib/datadog/ci/remote/component.rb
140
140
  - lib/datadog/ci/remote/library_settings.rb
141
141
  - lib/datadog/ci/remote/library_settings_client.rb
142
+ - lib/datadog/ci/remote/slow_test_retries.rb
142
143
  - lib/datadog/ci/span.rb
143
144
  - lib/datadog/ci/test.rb
144
145
  - lib/datadog/ci/test_module.rb
@@ -150,10 +151,16 @@ files:
150
151
  - lib/datadog/ci/test_optimisation/skippable.rb
151
152
  - lib/datadog/ci/test_optimisation/telemetry.rb
152
153
  - lib/datadog/ci/test_retries/component.rb
154
+ - lib/datadog/ci/test_retries/driver/base.rb
155
+ - lib/datadog/ci/test_retries/driver/no_retry.rb
156
+ - lib/datadog/ci/test_retries/driver/retry_failed.rb
157
+ - lib/datadog/ci/test_retries/driver/retry_new.rb
153
158
  - lib/datadog/ci/test_retries/null_component.rb
154
159
  - lib/datadog/ci/test_retries/strategy/base.rb
155
160
  - lib/datadog/ci/test_retries/strategy/no_retry.rb
156
161
  - lib/datadog/ci/test_retries/strategy/retry_failed.rb
162
+ - lib/datadog/ci/test_retries/strategy/retry_new.rb
163
+ - lib/datadog/ci/test_retries/unique_tests_client.rb
157
164
  - lib/datadog/ci/test_session.rb
158
165
  - lib/datadog/ci/test_suite.rb
159
166
  - lib/datadog/ci/test_visibility/component.rb
@@ -187,7 +194,6 @@ files:
187
194
  - lib/datadog/ci/utils/bundle.rb
188
195
  - lib/datadog/ci/utils/configuration.rb
189
196
  - lib/datadog/ci/utils/git.rb
190
- - lib/datadog/ci/utils/identity.rb
191
197
  - lib/datadog/ci/utils/parsing.rb
192
198
  - lib/datadog/ci/utils/telemetry.rb
193
199
  - lib/datadog/ci/utils/test_run.rb
@@ -219,7 +225,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
219
225
  - !ruby/object:Gem::Version
220
226
  version: 2.0.0
221
227
  requirements: []
222
- rubygems_version: 3.5.11
228
+ rubygems_version: 3.5.16
223
229
  signing_key:
224
230
  specification_version: 4
225
231
  summary: Datadog CI visibility for your ruby application
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "formatter"
4
-
5
- module Datadog
6
- module CI
7
- module Contrib
8
- module Cucumber
9
- # Changes behaviour of Cucumber::Configuration class
10
- module ConfigurationOverride
11
- def self.included(base)
12
- base.prepend(InstanceMethods)
13
- end
14
-
15
- # Instance methods for configuration
16
- module InstanceMethods
17
- def retry_attempts
18
- super if !datadog_test_retries_component&.retry_failed_tests_enabled
19
-
20
- datadog_test_retries_component&.retry_failed_tests_max_attempts
21
- end
22
-
23
- def retry_total_tests
24
- super if !datadog_test_retries_component&.retry_failed_tests_enabled
25
-
26
- datadog_test_retries_component&.retry_failed_tests_total_limit
27
- end
28
-
29
- def datadog_test_retries_component
30
- Datadog.send(:components).test_retries
31
- end
32
- end
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Datadog
4
- module CI
5
- module Utils
6
- module Identity
7
- def self.included(base)
8
- base.singleton_class.prepend(ClassMethods)
9
- end
10
-
11
- module ClassMethods
12
- # return datadog-ci gem version instead of datadog gem version
13
- def gem_datadog_version
14
- Datadog::CI::VERSION::STRING
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end