datadog-ci 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -2
  3. data/lib/datadog/ci/configuration/components.rb +12 -6
  4. data/lib/datadog/ci/configuration/settings.rb +6 -0
  5. data/lib/datadog/ci/contrib/cucumber/filter.rb +40 -0
  6. data/lib/datadog/ci/contrib/cucumber/instrumentation.rb +15 -1
  7. data/lib/datadog/ci/contrib/cucumber/patcher.rb +0 -2
  8. data/lib/datadog/ci/contrib/minitest/runner.rb +6 -1
  9. data/lib/datadog/ci/contrib/minitest/test.rb +4 -0
  10. data/lib/datadog/ci/contrib/rspec/example.rb +11 -10
  11. data/lib/datadog/ci/contrib/rspec/runner.rb +2 -1
  12. data/lib/datadog/ci/ext/settings.rb +1 -0
  13. data/lib/datadog/ci/ext/telemetry.rb +9 -0
  14. data/lib/datadog/ci/ext/test.rb +6 -1
  15. data/lib/datadog/ci/ext/transport.rb +7 -0
  16. data/lib/datadog/ci/remote/component.rb +13 -2
  17. data/lib/datadog/ci/remote/library_settings.rb +48 -7
  18. data/lib/datadog/ci/remote/library_settings_client.rb +2 -1
  19. data/lib/datadog/ci/remote/slow_test_retries.rb +53 -0
  20. data/lib/datadog/ci/test.rb +15 -2
  21. data/lib/datadog/ci/test_optimisation/component.rb +9 -6
  22. data/lib/datadog/ci/test_optimisation/skippable.rb +1 -1
  23. data/lib/datadog/ci/test_retries/component.rb +68 -39
  24. data/lib/datadog/ci/test_retries/driver/base.rb +25 -0
  25. data/lib/datadog/ci/test_retries/driver/no_retry.rb +16 -0
  26. data/lib/datadog/ci/test_retries/driver/retry_failed.rb +37 -0
  27. data/lib/datadog/ci/test_retries/driver/retry_new.rb +50 -0
  28. data/lib/datadog/ci/test_retries/null_component.rb +7 -6
  29. data/lib/datadog/ci/test_retries/strategy/base.rb +11 -4
  30. data/lib/datadog/ci/test_retries/strategy/no_retry.rb +0 -2
  31. data/lib/datadog/ci/test_retries/strategy/retry_failed.rb +30 -13
  32. data/lib/datadog/ci/test_retries/strategy/retry_new.rb +132 -0
  33. data/lib/datadog/ci/test_retries/unique_tests_client.rb +132 -0
  34. data/lib/datadog/ci/test_session.rb +2 -0
  35. data/lib/datadog/ci/test_suite.rb +8 -0
  36. data/lib/datadog/ci/test_visibility/component.rb +23 -13
  37. data/lib/datadog/ci/test_visibility/null_component.rb +1 -1
  38. data/lib/datadog/ci/test_visibility/telemetry.rb +9 -0
  39. data/lib/datadog/ci/utils/test_run.rb +1 -1
  40. data/lib/datadog/ci/version.rb +2 -2
  41. data/lib/datadog/ci.rb +6 -3
  42. metadata +11 -5
  43. data/lib/datadog/ci/contrib/cucumber/configuration_override.rb +0 -37
  44. data/lib/datadog/ci/utils/identity.rb +0 -20
@@ -1,7 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "driver/no_retry"
4
+ require_relative "driver/retry_failed"
5
+ require_relative "driver/retry_new"
6
+
3
7
  require_relative "strategy/no_retry"
4
8
  require_relative "strategy/retry_failed"
9
+ require_relative "strategy/retry_new"
10
+
11
+ require_relative "../ext/telemetry"
12
+ require_relative "../utils/telemetry"
5
13
 
6
14
  module Datadog
7
15
  module CI
@@ -10,73 +18,94 @@ module Datadog
10
18
  # - retrying failed tests - improve success rate of CI pipelines
11
19
  # - retrying new tests - detect flaky tests as early as possible to prevent them from being merged
12
20
  class Component
13
- attr_reader :retry_failed_tests_enabled, :retry_failed_tests_max_attempts,
14
- :retry_failed_tests_total_limit, :retry_failed_tests_count
21
+ FIBER_LOCAL_CURRENT_RETRY_DRIVER_KEY = :__dd_current_retry_driver
15
22
 
16
23
  def initialize(
17
24
  retry_failed_tests_enabled:,
18
25
  retry_failed_tests_max_attempts:,
19
- retry_failed_tests_total_limit:
26
+ retry_failed_tests_total_limit:,
27
+ retry_new_tests_enabled:,
28
+ unique_tests_client:
20
29
  )
21
- @retry_failed_tests_enabled = retry_failed_tests_enabled
22
- @retry_failed_tests_max_attempts = retry_failed_tests_max_attempts
23
- @retry_failed_tests_total_limit = retry_failed_tests_total_limit
24
- # counter that store the current number of failed tests retried
25
- @retry_failed_tests_count = 0
30
+ no_retries_strategy = Strategy::NoRetry.new
31
+
32
+ retry_failed_strategy = Strategy::RetryFailed.new(
33
+ enabled: retry_failed_tests_enabled,
34
+ max_attempts: retry_failed_tests_max_attempts,
35
+ total_limit: retry_failed_tests_total_limit
36
+ )
26
37
 
38
+ retry_new_strategy = Strategy::RetryNew.new(
39
+ enabled: retry_new_tests_enabled,
40
+ unique_tests_client: unique_tests_client
41
+ )
42
+
43
+ # order is important, we should try to retry new tests first
44
+ @retry_strategies = [retry_new_strategy, retry_failed_strategy, no_retries_strategy]
27
45
  @mutex = Mutex.new
28
46
  end
29
47
 
30
- def configure(library_settings)
31
- @retry_failed_tests_enabled &&= library_settings.flaky_test_retries_enabled?
48
+ def configure(library_settings, test_session)
49
+ # let all strategies configure themselves
50
+ @retry_strategies.each do |strategy|
51
+ strategy.configure(library_settings, test_session)
52
+ end
32
53
  end
33
54
 
34
55
  def with_retries(&block)
35
- # @type var retry_strategy: Strategy::Base
36
- retry_strategy = nil
37
-
38
- test_finished_callback = lambda do |test_span|
39
- if retry_strategy.nil?
40
- # we always run test at least once and after first pass create a correct retry strategy
41
- retry_strategy = build_strategy(test_span)
42
- else
43
- # after each retry we record the result, strategy will decide if we should retry again
44
- retry_strategy&.record_retry(test_span)
45
- end
46
- end
47
-
48
- test_visibility_component.set_test_finished_callback(test_finished_callback)
56
+ reset_retries!
49
57
 
50
58
  loop do
51
59
  yield
52
60
 
53
- break unless retry_strategy&.should_retry?
61
+ break unless should_retry?
54
62
  end
55
63
  ensure
56
- test_visibility_component.remove_test_finished_callback
64
+ reset_retries!
57
65
  end
58
66
 
59
- def build_strategy(test_span)
67
+ def build_driver(test_span)
60
68
  @mutex.synchronize do
61
- if should_retry_failed_test?(test_span)
62
- Datadog.logger.debug("Failed test retry starts")
63
- @retry_failed_tests_count += 1
64
-
65
- Strategy::RetryFailed.new(max_attempts: @retry_failed_tests_max_attempts)
66
- else
67
- Strategy::NoRetry.new
68
- end
69
+ # find the first strategy that covers the test span and let it build the driver
70
+ strategy = @retry_strategies.find { |strategy| strategy.covers?(test_span) }
71
+
72
+ raise "No retry strategy found for test span: #{test_span.name}" if strategy.nil?
73
+
74
+ strategy.build_driver(test_span)
69
75
  end
70
76
  end
71
77
 
78
+ def record_test_finished(test_span)
79
+ if current_retry_driver.nil?
80
+ # we always run test at least once and after the first pass create a correct retry driver
81
+ self.current_retry_driver = build_driver(test_span)
82
+ else
83
+ # after each retry we record the result, the driver will decide if we should retry again
84
+ current_retry_driver&.record_retry(test_span)
85
+ end
86
+ end
87
+
88
+ def record_test_span_duration(tracer_span)
89
+ current_retry_driver&.record_duration(tracer_span.duration)
90
+ end
91
+
92
+ # this API is targeted on Cucumber instrumentation or any other that cannot leverage #with_retries method
93
+ def reset_retries!
94
+ self.current_retry_driver = nil
95
+ end
96
+
97
+ def should_retry?
98
+ !!current_retry_driver&.should_retry?
99
+ end
100
+
72
101
  private
73
102
 
74
- def should_retry_failed_test?(test_span)
75
- @retry_failed_tests_enabled && !!test_span&.failed? && @retry_failed_tests_count < @retry_failed_tests_total_limit
103
+ def current_retry_driver
104
+ Thread.current[FIBER_LOCAL_CURRENT_RETRY_DRIVER_KEY]
76
105
  end
77
106
 
78
- def test_visibility_component
79
- Datadog.send(:components).test_visibility
107
+ def current_retry_driver=(driver)
108
+ Thread.current[FIBER_LOCAL_CURRENT_RETRY_DRIVER_KEY] = driver
80
109
  end
81
110
  end
82
111
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module CI
5
+ module TestRetries
6
+ module Driver
7
+ # Driver is the class responsible for the current test retry mechanism.
8
+ # It receives signals about each retry execution and steers the current retry strategy.
9
+ class Base
10
+ def should_retry?
11
+ false
12
+ end
13
+
14
+ def record_retry(test_span)
15
+ test_span&.set_tag(Ext::Test::TAG_IS_RETRY, "true")
16
+ end
17
+
18
+ # duration in float seconds
19
+ def record_duration(duration)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Datadog
6
+ module CI
7
+ module TestRetries
8
+ module Driver
9
+ class NoRetry < Base
10
+ def record_retry(test_span)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ require_relative "../../ext/test"
6
+
7
+ module Datadog
8
+ module CI
9
+ module TestRetries
10
+ module Driver
11
+ class RetryFailed < Base
12
+ attr_reader :max_attempts
13
+
14
+ def initialize(max_attempts:)
15
+ @max_attempts = max_attempts
16
+
17
+ @attempts = 0
18
+ @passed_once = false
19
+ end
20
+
21
+ def should_retry?
22
+ @attempts < @max_attempts && !@passed_once
23
+ end
24
+
25
+ def record_retry(test_span)
26
+ super
27
+
28
+ @attempts += 1
29
+ @passed_once = true if test_span&.passed?
30
+
31
+ Datadog.logger.debug { "Retry Attempts [#{@attempts} / #{@max_attempts}], Passed: [#{@passed_once}]" }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ require_relative "../../ext/test"
6
+
7
+ module Datadog
8
+ module CI
9
+ module TestRetries
10
+ module Driver
11
+ # retry every new test up to 10 times (early flake detection)
12
+ class RetryNew < Base
13
+ def initialize(test_span, max_attempts_thresholds:)
14
+ @max_attempts_thresholds = max_attempts_thresholds
15
+ @attempts = 0
16
+ # will be changed based on test span duration
17
+ @max_attempts = 10
18
+
19
+ mark_new_test(test_span)
20
+ end
21
+
22
+ def should_retry?
23
+ @attempts < @max_attempts
24
+ end
25
+
26
+ def record_retry(test_span)
27
+ super
28
+
29
+ @attempts += 1
30
+ mark_new_test(test_span)
31
+
32
+ Datadog.logger.debug { "Retry Attempts [#{@attempts} / #{@max_attempts}]" }
33
+ end
34
+
35
+ def record_duration(duration)
36
+ @max_attempts = @max_attempts_thresholds.max_attempts_for_duration(duration)
37
+
38
+ Datadog.logger.debug { "Recorded test duration of [#{duration}], new Max Attempts value is [#{@max_attempts}]" }
39
+ end
40
+
41
+ private
42
+
43
+ def mark_new_test(test_span)
44
+ test_span.set_tag(Ext::Test::TAG_IS_NEW, "true")
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -6,13 +6,7 @@ module Datadog
6
6
  module CI
7
7
  module TestRetries
8
8
  class NullComponent < Component
9
- attr_reader :retry_failed_tests_enabled, :retry_failed_tests_max_attempts, :retry_failed_tests_total_limit
10
-
11
9
  def initialize
12
- # enabled only by remote settings
13
- @retry_failed_tests_enabled = false
14
- @retry_failed_tests_max_attempts = 0
15
- @retry_failed_tests_total_limit = 0
16
10
  end
17
11
 
18
12
  def configure(library_settings)
@@ -22,6 +16,13 @@ module Datadog
22
16
  no_action = proc {}
23
17
  yield no_action
24
18
  end
19
+
20
+ def reset_retries!
21
+ end
22
+
23
+ def should_retry?
24
+ false
25
+ end
25
26
  end
26
27
  end
27
28
  end
@@ -1,16 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../driver/no_retry"
4
+
3
5
  module Datadog
4
6
  module CI
5
7
  module TestRetries
6
8
  module Strategy
9
+ # Strategies are subcomponents of the retry mechanism. They are responsible for
10
+ # determining which tests should be retried and how.
7
11
  class Base
8
- def should_retry?
9
- false
12
+ def covers?(test_span)
13
+ true
14
+ end
15
+
16
+ def configure(_library_settings, _test_session)
10
17
  end
11
18
 
12
- def record_retry(test_span)
13
- test_span&.set_tag(Ext::Test::TAG_IS_RETRY, "true")
19
+ def build_driver(_test_span)
20
+ Driver::NoRetry.new
14
21
  end
15
22
  end
16
23
  end
@@ -7,8 +7,6 @@ module Datadog
7
7
  module TestRetries
8
8
  module Strategy
9
9
  class NoRetry < Base
10
- def record_retry(test_span)
11
- end
12
10
  end
13
11
  end
14
12
  end
@@ -2,33 +2,50 @@
2
2
 
3
3
  require_relative "base"
4
4
 
5
- require_relative "../../ext/test"
5
+ require_relative "../driver/retry_failed"
6
6
 
7
7
  module Datadog
8
8
  module CI
9
9
  module TestRetries
10
10
  module Strategy
11
11
  class RetryFailed < Base
12
- attr_reader :max_attempts
13
-
14
- def initialize(max_attempts:)
12
+ attr_reader :enabled, :max_attempts,
13
+ :total_limit, :retried_count
14
+
15
+ def initialize(
16
+ enabled:,
17
+ max_attempts:,
18
+ total_limit:
19
+ )
20
+ @enabled = enabled
15
21
  @max_attempts = max_attempts
22
+ @total_limit = total_limit
23
+ @retried_count = 0
24
+ end
25
+
26
+ def covers?(test_span)
27
+ return false unless @enabled
28
+
29
+ if @retried_count >= @total_limit
30
+ Datadog.logger.debug do
31
+ "Retry failed tests limit reached: [#{@retried_count}] out of [#{@total_limit}]"
32
+ end
33
+ @enabled = false
34
+ end
16
35
 
17
- @attempts = 0
18
- @passed_once = false
36
+ @enabled && !!test_span&.failed?
19
37
  end
20
38
 
21
- def should_retry?
22
- @attempts < @max_attempts && !@passed_once
39
+ def configure(library_settings, test_session)
40
+ @enabled &&= library_settings.flaky_test_retries_enabled?
23
41
  end
24
42
 
25
- def record_retry(test_span)
26
- super
43
+ def build_driver(test_span)
44
+ Datadog.logger.debug { "#{test_span.name} failed, will be retried" }
27
45
 
28
- @attempts += 1
29
- @passed_once = true if test_span&.passed?
46
+ @retried_count += 1
30
47
 
31
- Datadog.logger.debug { "Retry Attempts [#{@attempts} / #{@max_attempts}], Passed: [#{@passed_once}]" }
48
+ Driver::RetryFailed.new(max_attempts: max_attempts)
32
49
  end
33
50
  end
34
51
  end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ require_relative "../driver/retry_new"
6
+
7
+ module Datadog
8
+ module CI
9
+ module TestRetries
10
+ module Strategy
11
+ class RetryNew < Base
12
+ DEFAULT_TOTAL_TESTS_COUNT = 100
13
+
14
+ attr_reader :enabled, :max_attempts_thresholds, :unique_tests_set, :total_limit, :retried_count
15
+
16
+ def initialize(
17
+ enabled:,
18
+ unique_tests_client:
19
+ )
20
+ @enabled = enabled
21
+ @unique_tests_set = Set.new
22
+ # total maximum number of new tests to retry (will be set based on the total number of tests in the session)
23
+ @total_limit = 0
24
+ @retried_count = 0
25
+
26
+ @unique_tests_client = unique_tests_client
27
+ end
28
+
29
+ def covers?(test_span)
30
+ return false unless @enabled
31
+
32
+ if @retried_count >= @total_limit
33
+ Datadog.logger.debug do
34
+ "Retry new tests limit reached: [#{@retried_count}] out of [#{@total_limit}]"
35
+ end
36
+ @enabled = false
37
+ mark_test_session_faulty(Datadog::CI.active_test_session)
38
+ end
39
+
40
+ @enabled && !test_span.skipped? && is_new_test?(test_span)
41
+ end
42
+
43
+ def configure(library_settings, test_session)
44
+ @enabled &&= library_settings.early_flake_detection_enabled?
45
+
46
+ return unless @enabled
47
+
48
+ # mark early flake detection enabled for test session
49
+ test_session.set_tag(Ext::Test::TAG_EARLY_FLAKE_ENABLED, "true")
50
+
51
+ set_max_attempts_thresholds(library_settings)
52
+ calculate_total_retries_limit(library_settings, test_session)
53
+ fetch_known_unique_tests(test_session)
54
+ end
55
+
56
+ def build_driver(test_span)
57
+ Datadog.logger.debug do
58
+ "#{test_span.name} is new, will be retried"
59
+ end
60
+ @retried_count += 1
61
+
62
+ Driver::RetryNew.new(test_span, max_attempts_thresholds: @max_attempts_thresholds)
63
+ end
64
+
65
+ private
66
+
67
+ def mark_test_session_faulty(test_session)
68
+ test_session&.set_tag(Ext::Test::TAG_EARLY_FLAKE_ABORT_REASON, Ext::Test::EARLY_FLAKE_FAULTY)
69
+ end
70
+
71
+ def is_new_test?(test_span)
72
+ test_id = Utils::TestRun.datadog_test_id(test_span.name, test_span.test_suite_name)
73
+
74
+ result = !@unique_tests_set.include?(test_id)
75
+
76
+ if result
77
+ Datadog.logger.debug do
78
+ "#{test_id} is not found in the unique tests set, it is a new test"
79
+ end
80
+ end
81
+
82
+ result
83
+ end
84
+
85
+ def set_max_attempts_thresholds(library_settings)
86
+ @max_attempts_thresholds = library_settings.slow_test_retries
87
+ Datadog.logger.debug do
88
+ "Slow test retries thresholds: #{@max_attempts_thresholds.entries}"
89
+ end
90
+ end
91
+
92
+ def calculate_total_retries_limit(library_settings, test_session)
93
+ percentage_limit = library_settings.faulty_session_threshold
94
+ tests_count = test_session.total_tests_count.to_i
95
+ if tests_count.zero?
96
+ Datadog.logger.debug do
97
+ "Total tests count is zero, using default value for the total number of tests: [#{DEFAULT_TOTAL_TESTS_COUNT}]"
98
+ end
99
+
100
+ tests_count = DEFAULT_TOTAL_TESTS_COUNT
101
+ end
102
+ @total_limit = (tests_count * percentage_limit / 100.0).ceil
103
+ Datadog.logger.debug do
104
+ "Retry new tests total limit is [#{@total_limit}] (#{percentage_limit}%) of #{tests_count}"
105
+ end
106
+ end
107
+
108
+ def fetch_known_unique_tests(test_session)
109
+ @unique_tests_set = @unique_tests_client.fetch_unique_tests(test_session)
110
+ if @unique_tests_set.empty?
111
+ @enabled = false
112
+ mark_test_session_faulty(test_session)
113
+
114
+ Datadog.logger.warn(
115
+ "Disabling early flake detection because there are no known tests (possible reason: no test runs in default branch)"
116
+ )
117
+ end
118
+
119
+ # report how many unique tests were found
120
+ Datadog.logger.debug do
121
+ "Found [#{@unique_tests_set.size}] known unique tests"
122
+ end
123
+ Utils::Telemetry.distribution(
124
+ Ext::Telemetry::METRIC_EFD_UNIQUE_TESTS_RESPONSE_TESTS,
125
+ @unique_tests_set.size.to_f
126
+ )
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -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