Class: Datadog::CI::TestRetries::Component

Inherits:
Object
  • Object
show all
Defined in:
lib/datadog/ci/test_retries/component.rb

Overview

Encapsulates the logic to enable test retries, including:

  • retrying failed tests - improve success rate of CI pipelines

  • retrying new tests - detect flaky tests as early as possible to prevent them from being merged

Direct Known Subclasses

NullComponent

Constant Summary collapse

FIBER_LOCAL_CURRENT_RETRY_DRIVER_KEY =
:__dd_current_retry_driver

Instance Method Summary collapse

Constructor Details

#initialize(retry_failed_tests_enabled:, retry_failed_tests_max_attempts:, retry_failed_tests_total_limit:, retry_new_tests_enabled:, unique_tests_client:) ⇒ Component

Returns a new instance of Component.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/datadog/ci/test_retries/component.rb', line 23

def initialize(
  retry_failed_tests_enabled:,
  retry_failed_tests_max_attempts:,
  retry_failed_tests_total_limit:,
  retry_new_tests_enabled:,
  unique_tests_client:
)
  no_retries_strategy = Strategy::NoRetry.new

  retry_failed_strategy = Strategy::RetryFailed.new(
    enabled: retry_failed_tests_enabled,
    max_attempts: retry_failed_tests_max_attempts,
    total_limit: retry_failed_tests_total_limit
  )

  retry_new_strategy = Strategy::RetryNew.new(
    enabled: retry_new_tests_enabled,
    unique_tests_client: unique_tests_client
  )

  # order is important, we should try to retry new tests first
  @retry_strategies = [retry_new_strategy, retry_failed_strategy, no_retries_strategy]
  @mutex = Mutex.new
end

Instance Method Details

#build_driver(test_span) ⇒ Object



67
68
69
70
71
72
73
74
75
76
# File 'lib/datadog/ci/test_retries/component.rb', line 67

def build_driver(test_span)
  @mutex.synchronize do
    # find the first strategy that covers the test span and let it build the driver
    strategy = @retry_strategies.find { |strategy| strategy.covers?(test_span) }

    raise "No retry strategy found for test span: #{test_span.name}" if strategy.nil?

    strategy.build_driver(test_span)
  end
end

#configure(library_settings, test_session) ⇒ Object



48
49
50
51
52
53
# File 'lib/datadog/ci/test_retries/component.rb', line 48

def configure(library_settings, test_session)
  # let all strategies configure themselves
  @retry_strategies.each do |strategy|
    strategy.configure(library_settings, test_session)
  end
end

#record_test_finished(test_span) ⇒ Object



78
79
80
81
82
83
84
85
86
# File 'lib/datadog/ci/test_retries/component.rb', line 78

def record_test_finished(test_span)
  if current_retry_driver.nil?
    # we always run test at least once and after the first pass create a correct retry driver
    self.current_retry_driver = build_driver(test_span)
  else
    # after each retry we record the result, the driver will decide if we should retry again
    current_retry_driver&.record_retry(test_span)
  end
end

#record_test_span_duration(tracer_span) ⇒ Object



88
89
90
# File 'lib/datadog/ci/test_retries/component.rb', line 88

def record_test_span_duration(tracer_span)
  current_retry_driver&.record_duration(tracer_span.duration)
end

#reset_retries!Object

this API is targeted on Cucumber instrumentation or any other that cannot leverage #with_retries method



93
94
95
# File 'lib/datadog/ci/test_retries/component.rb', line 93

def reset_retries!
  self.current_retry_driver = nil
end

#should_retry?Boolean

Returns:

  • (Boolean)


97
98
99
# File 'lib/datadog/ci/test_retries/component.rb', line 97

def should_retry?
  !!current_retry_driver&.should_retry?
end

#with_retries(&block) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
# File 'lib/datadog/ci/test_retries/component.rb', line 55

def with_retries(&block)
  reset_retries!

  loop do
    yield

    break unless should_retry?
  end
ensure
  reset_retries!
end