Back to Repositories

Testing Retry State Implementation in Fluentd Plugin Helper System

This test suite validates the retry state functionality in Fluentd’s plugin helper system, focusing on exponential backoff and periodic retry mechanisms with configurable parameters.

Test Coverage Overview

Comprehensive testing of retry state behavior including exponential backoff and periodic retry patterns. Covers primary and secondary retry states, timeout handling, step limits, and interval configurations.

Key areas tested:
  • Randomization within specified ranges
  • Basic retry state machine creation
  • Periodic retry patterns with various configurations
  • Exponential backoff with different parameters
  • Secondary retry state transitions

Implementation Analysis

The test suite employs a systematic approach using Test::Unit framework with custom test helpers and mock objects. Tests are structured to validate retry behavior through time simulation and state tracking.

Testing patterns include:
  • Time override mechanisms for controlled testing
  • State machine verification
  • Step-by-step retry progression validation
  • Boundary condition testing

Technical Details

Testing tools and configuration:
  • Test::Unit as the primary testing framework
  • Custom RetryRecord class for state verification
  • Mock time manipulation through module prepending
  • Dummy plugin class for helper integration testing
  • Scenario-based test data structures

Best Practices Demonstrated

The test suite showcases several testing best practices:

  • Comprehensive scenario testing with detailed test cases
  • Systematic state verification
  • Clear test case organization
  • Thorough edge case coverage
  • Modular test helper implementation
  • Detailed failure messages for debugging

fluent/fluentd

test/plugin_helper/test_retry_state.rb

            
require_relative '../helper'
require 'fluent/plugin_helper/retry_state'
require 'fluent/plugin/base'

require 'time'

class RetryStateHelperTest < Test::Unit::TestCase
  def override_current_time(state, time)
    mojule = Module.new do
      define_method(:current_time){ time }
    end
    state.singleton_class.module_eval do
      prepend mojule
    end
  end

  class Dummy < Fluent::Plugin::TestBase
    helpers :retry_state
  end

  class RetryRecord
    attr_reader :retry_count, :elapsed_sec, :is_secondary
    def initialize(retry_count, elapsed_sec, is_secondary)
      @retry_count = retry_count # This is Nth retryment
      @elapsed_sec = elapsed_sec
      @is_secondary = is_secondary
    end

    def ==(obj)
      @retry_count == obj.retry_count &&
      @elapsed_sec == obj.elapsed_sec &&
      @is_secondary == obj.is_secondary
    end
  end

  setup do
    @d = Dummy.new
  end

  test 'randomize can generate value within specified +/- range' do
    s = @d.retry_state_create(:t1, :exponential_backoff, 0.1, 30) # default enabled w/ 0.125
    500.times do
      r = s.randomize(1000)
      assert{ r >= 875 && r < 1125 }
    end

    s = @d.retry_state_create(:t1, :exponential_backoff, 0.1, 30, randomize_width: 0.25)
    500.times do
      r = s.randomize(1000)
      assert{ r >= 750 && r < 1250 }
    end
  end

  test 'plugin can create retry_state machine' do
    s = @d.retry_state_create(:t1, :exponential_backoff, 0.1, 30)
    # attr_reader :title, :start, :steps, :next_time, :timeout_at, :current, :secondary_transition_at, :secondary_transition_times

    assert_equal :t1, s.title
    start_time = s.start

    assert_equal 0, s.steps
    assert_equal (start_time + 0.1).to_i, s.next_time.to_i
    assert_equal (start_time + 0.1).nsec, s.next_time.nsec
    assert_equal (start_time + 30), s.timeout_at

    assert_equal :primary, s.current
    assert{ s.is_a? Fluent::PluginHelper::RetryState::ExponentialBackOffRetry }
  end

  test 'periodic retries' do
    s = @d.retry_state_create(:t2, :periodic, 3, 29, randomize: false)
    dummy_current_time = s.start
    override_current_time(s, dummy_current_time)

    assert_equal dummy_current_time, s.current_time
    assert_equal (dummy_current_time + 29), s.timeout_at
    assert_equal (dummy_current_time + 3), s.next_time

    i = 1
    while i < 9
      override_current_time(s, s.next_time)
      s.step
      assert_equal i, s.steps
      assert_equal (s.current_time + 3), s.next_time
      assert !s.limit?
      i += 1
    end

    assert_equal 9, i
    override_current_time(s, s.next_time)
    s.step
    assert_equal s.timeout_at, s.next_time
    s.step
    assert s.limit?
  end

  test 'periodic retries with max_steps' do
    s = @d.retry_state_create(:t2, :periodic, 3, 29, randomize: false, max_steps: 5)
    dummy_current_time = s.start
    override_current_time(s, dummy_current_time)

    assert_equal dummy_current_time, s.current_time
    assert_equal (dummy_current_time + 29), s.timeout_at
    assert_equal (dummy_current_time + 3), s.next_time

    i = 1
    while i < 5
      override_current_time(s, s.next_time)
      s.step
      assert_equal i, s.steps
      assert_equal (s.current_time + 3), s.next_time
      assert !s.limit?
      i += 1
    end

    assert_equal 5, i
    override_current_time(s, s.next_time)
    s.step
    assert s.limit?
  end

  test 'periodic retries with secondary' do
    s = @d.retry_state_create(:t3, :periodic, 3, 100, randomize: false, secondary: true) # threshold 0.8
    dummy_current_time = s.start
    override_current_time(s, dummy_current_time)

    assert_equal dummy_current_time, s.current_time
    assert_equal (dummy_current_time + 100), s.timeout_at
    assert_equal (dummy_current_time + 100 * 0.8), s.secondary_transition_at

    assert_equal (dummy_current_time + 3), s.next_time
    assert !s.secondary?

    i = 1
    while i < 26
      override_current_time(s, s.next_time)
      assert !s.secondary?

      s.step
      assert_equal i, s.steps
      assert_equal (s.current_time + 3), s.next_time
      assert !s.limit?
      i += 1
    end

    assert_equal 26, i
    override_current_time(s, s.next_time) # 78
    assert !s.secondary?

    s.step
    assert_equal 26, s.steps
    assert_equal s.secondary_transition_at, s.next_time
    assert !s.limit?

    i += 1
    assert_equal 27, i
    override_current_time(s, s.next_time) # 80
    assert s.secondary?

    s.step
    assert_equal (s.current_time + 3), s.next_time
    assert_equal s.steps, s.secondary_transition_steps
    assert !s.limit?

    i += 1

    while i < 33
      override_current_time(s, s.next_time)
      assert s.secondary?

      s.step
      assert_equal (s.current_time + 3), s.next_time
      assert !s.limit?
      i += 1
    end

    assert_equal 33, i
    override_current_time(s, s.next_time) # 98
    assert s.secondary?

    s.step
    assert_equal s.timeout_at, s.next_time # 100

    s.step
    assert s.limit?
  end

  test 'periodic retries with secondary and specified threshold' do
    s = @d.retry_state_create(:t3, :periodic, 3, 100, randomize: false, secondary: true, secondary_threshold: 0.75)
    dummy_current_time = s.start
    override_current_time(s, dummy_current_time)

    assert_equal dummy_current_time, s.current_time
    assert_equal (dummy_current_time + 100), s.timeout_at
    assert_equal (dummy_current_time + 100 * 0.75), s.secondary_transition_at
  end

  test 'periodic retries with secondary and max_steps' do
    s = @d.retry_state_create(:t3, :periodic, 3, 100, max_steps: 5, randomize: false, secondary: true)
    dummy_current_time = s.start
    override_current_time(s, dummy_current_time)

    assert_equal dummy_current_time, s.current_time
    assert_equal (dummy_current_time + 100), s.timeout_at
    assert_equal (dummy_current_time + 3 * 5 * 0.8), s.secondary_transition_at
  end

  test 'exponential backoff forever without randomization' do
    s = @d.retry_state_create(:t11, :exponential_backoff, 0.1, 300, randomize: false, forever: true, backoff_base: 2)
    dummy_current_time = s.start
    override_current_time(s, dummy_current_time)

    assert_equal dummy_current_time, s.current_time

    assert_equal 0, s.steps
    assert_equal (dummy_current_time + 0.1), s.next_time

    i = 1
    while i < 300
      s.step
      assert_equal i, s.steps
      assert_equal (dummy_current_time + 0.1 * (2 ** i)), s.next_time
      assert !s.limit?
      i += 1
    end
  end

  test 'exponential backoff with max_interval' do
    s = @d.retry_state_create(:t12, :exponential_backoff, 0.1, 300, randomize: false, forever: true, backoff_base: 2, max_interval: 100)
    dummy_current_time = s.start
    override_current_time(s, dummy_current_time)

    assert_equal dummy_current_time, s.current_time

    assert_equal 0, s.steps
    assert_equal (dummy_current_time + 0.1), s.next_time

    # 0.1 * 2 ** 9 == 51.2
    # 0.1 * 2 ** 10 == 102.4
    i = 1
    while i < 10
      s.step
      assert_equal i, s.steps
      assert_equal (dummy_current_time + 0.1 * (2 ** i)), s.next_time, "start:#{dummy_current_time}, i:#{i}"
      i += 1
    end

    s.step
    assert_equal 10, s.steps
    assert_equal (dummy_current_time + 100), s.next_time

    s.step
    assert_equal 11, s.steps
    assert_equal (dummy_current_time + 100), s.next_time
  end

  test 'exponential backoff with shorter timeout' do
    s = @d.retry_state_create(:t13, :exponential_backoff, 1, 12, randomize: false, backoff_base: 2, max_interval: 10)
    dummy_current_time = s.start
    override_current_time(s, dummy_current_time)

    assert_equal dummy_current_time, s.current_time

    assert_equal (dummy_current_time + 12), s.timeout_at

    assert_equal 0, s.steps
    assert_equal (dummy_current_time + 1), s.next_time

    # 1 + 2 + 4 (=7)

    override_current_time(s, s.next_time)
    s.step
    assert_equal 1, s.steps
    assert_equal (s.current_time + 2), s.next_time

    override_current_time(s, s.next_time)
    s.step
    assert_equal 2, s.steps
    assert_equal (s.current_time + 4), s.next_time

    assert !s.limit?

    # + 8 (=15) > 12

    override_current_time(s, s.next_time)
    s.step
    assert_equal 3, s.steps
    assert_equal s.timeout_at, s.next_time

    s.step
    assert s.limit?
  end

  test 'exponential backoff with max_steps' do
    s = @d.retry_state_create(:t14, :exponential_backoff, 1, 120, randomize: false, backoff_base: 2, max_interval: 10, max_steps: 6)
    dummy_current_time = s.start
    override_current_time(s, dummy_current_time)

    assert_equal dummy_current_time, s.current_time

    assert_equal (dummy_current_time + 120), s.timeout_at

    assert_equal 0, s.steps
    assert_equal (dummy_current_time + 1), s.next_time

    override_current_time(s, s.next_time)
    s.step
    assert_equal 1, s.steps
    assert_equal (s.current_time + 2), s.next_time

    override_current_time(s, s.next_time)
    s.step
    assert_equal 2, s.steps
    assert_equal (s.current_time + 4), s.next_time

    override_current_time(s, s.next_time)
    s.step
    assert_equal 3, s.steps
    assert_equal (s.current_time + 8), s.next_time

    assert !s.limit?

    override_current_time(s, s.next_time)
    s.step
    assert_equal 4, s.steps
    assert_equal (s.current_time + 10), s.next_time

    assert !s.limit?

    override_current_time(s, s.next_time)
    s.step
    assert_equal 5, s.steps
    assert_equal (s.current_time + 10), s.next_time

    assert !s.limit?

    override_current_time(s, s.next_time)
    s.step
    assert_equal 6, s.steps
    assert s.limit?
  end

  test 'exponential backoff retries with secondary' do
    s = @d.retry_state_create(:t15, :exponential_backoff, 1, 100, randomize: false, backoff_base: 2, secondary: true) # threshold 0.8
    dummy_current_time = s.start
    override_current_time(s, dummy_current_time)

    assert_equal dummy_current_time, s.current_time
    assert_equal (dummy_current_time + 100), s.timeout_at
    assert_equal (dummy_current_time + 100 * 0.8), s.secondary_transition_at

    assert_equal (dummy_current_time + 1), s.next_time
    assert !s.secondary?

    # primary: 3, 7, 15, 31, 63, 80 (timeout * threashold)
    # secondary: 81, 83, 87, 95, 100
    i = 1
    while i < 6
      override_current_time(s, s.next_time)
      assert !s.secondary?

      s.step
      assert_equal i, s.steps
      assert_equal (s.current_time + 1 * (2 ** i)), s.next_time
      assert !s.limit?
      i += 1
    end

    assert_equal 6, i
    override_current_time(s, s.next_time) # 63
    assert !s.secondary?

    s.step
    assert_equal 6, s.steps
    assert_equal s.secondary_transition_at, s.next_time
    assert !s.limit?

    i += 1
    assert_equal 7, i
    override_current_time(s, s.next_time) # 80
    assert s.secondary?

    s.step
    assert_equal 7, s.steps
    assert_equal s.steps, s.secondary_transition_steps
    assert_equal (s.secondary_transition_at + 1.0), s.next_time # 81
    assert !s.limit?
    assert_equal :secondary, s.current

    # 83, 87, 95, 100
    j = 1
    while j < 4
      override_current_time(s, s.next_time)
      assert s.secondary?
      assert_equal :secondary, s.current

      s.step
      assert_equal (7 + j), s.steps
      assert_equal (s.current_time + (1 * (2 ** j))), s.next_time
      assert !s.limit?, "j:#{j}"
      j += 1
    end

    assert_equal 4, j
    override_current_time(s, s.next_time) # 95
    assert s.secondary?

    s.step
    assert_equal s.timeout_at, s.next_time # 100

    s.step
    assert s.limit?
  end

  test 'exponential backoff retries with secondary and specified threshold' do
    s = @d.retry_state_create(:t16, :exponential_backoff, 1, 100, randomize: false, secondary: true, backoff_base: 2, secondary_threshold: 0.75)
    dummy_current_time = s.start
    override_current_time(s, dummy_current_time)

    assert_equal dummy_current_time, s.current_time
    assert_equal (dummy_current_time + 100), s.timeout_at
    assert_equal (dummy_current_time + 100 * 0.75), s.secondary_transition_at
  end

  test 'exponential backoff retries with secondary and max_steps' do
    s = @d.retry_state_create(:t15, :exponential_backoff, 1, 100, randomize: false, max_steps: 5, backoff_base: 2, secondary: true) # threshold 0.8
    dummy_current_time = s.start
    override_current_time(s, dummy_current_time)

    timeout = 0
    5.times { |i| timeout += 1.0 * (2 ** i) }

    assert_equal dummy_current_time, s.current_time
    assert_equal (dummy_current_time + 100), s.timeout_at
    assert_equal (dummy_current_time + timeout * 0.8), s.secondary_transition_at
  end

  sub_test_case 'exponential backoff' do
    test 'too big steps(check inf handling)' do
      s = @d.retry_state_create(:t11, :exponential_backoff, 1, 300, randomize: false, forever: true, backoff_base: 2)
      dummy_current_time = s.start
      override_current_time(s, dummy_current_time)

      i = 1
      while i < 1027
        if i >= 1025
          # With this setting, 1025+ number causes inf in `calc_interval`, so 1024 value is used for next_time
          assert_nothing_raised(FloatDomainError) { s.step }
          assert_equal (dummy_current_time + (2 ** (1024 - 1))), s.next_time
        else
          s.step
        end
        i += 1
      end
    end
  end

  sub_test_case "ExponentialBackOff_ScenarioTests" do
    data("Simple timeout", {
      timeout: 100, max_steps: nil, max_interval: nil, use_sec: false, sec_thres: 0.8, wait: 1, backoff_base: 2,
      expected: [
        RetryRecord.new(1, 1, false),
        RetryRecord.new(2, 3, false),
        RetryRecord.new(3, 7, false),
        RetryRecord.new(4, 15, false),
        RetryRecord.new(5, 31, false),
        RetryRecord.new(6, 63, false),
        RetryRecord.new(7, 100, false),
      ],
    })
    data("Simple timeout with secondary", {
      timeout: 100, max_steps: nil, max_interval: nil, use_sec: true, sec_thres: 0.8, wait: 1, backoff_base: 2,
      expected: [
        RetryRecord.new(1, 1, false),
        RetryRecord.new(2, 3, false),
        RetryRecord.new(3, 7, false),
        RetryRecord.new(4, 15, false),
        RetryRecord.new(5, 31, false),
        RetryRecord.new(6, 63, false),
        RetryRecord.new(7, 80, true),
        RetryRecord.new(8, 81, true),
        RetryRecord.new(9, 83, true),
        RetryRecord.new(10, 87, true),
        RetryRecord.new(11, 95, true),
        RetryRecord.new(12, 100, true),
      ],
    })
    data("Simple timeout with custom wait and backoff_base", {
      timeout: 1000, max_steps: nil, max_interval: nil, use_sec: false, sec_thres: 0.8, wait: 2, backoff_base: 3,
      expected: [
        RetryRecord.new(1, 2, false),
        RetryRecord.new(2, 8, false),
        RetryRecord.new(3, 26, false),
        RetryRecord.new(4, 80, false),
        RetryRecord.new(5, 242, false),
        RetryRecord.new(6, 728, false),
        RetryRecord.new(7, 1000, false),
      ],
    })
    data("Simple timeout with custom wait and backoff_base and secondary", {
      timeout: 1000, max_steps: nil, max_interval: nil, use_sec: true, sec_thres: 0.8, wait: 2, backoff_base: 3,
      expected: [
        RetryRecord.new(1, 2, false),
        RetryRecord.new(2, 8, false),
        RetryRecord.new(3, 26, false),
        RetryRecord.new(4, 80, false),
        RetryRecord.new(5, 242, false),
        RetryRecord.new(6, 728, false),
        RetryRecord.new(7, 800, true),
        RetryRecord.new(8, 802, true),
        RetryRecord.new(9, 808, true),
        RetryRecord.new(10, 826, true),
        RetryRecord.new(11, 880, true),
        RetryRecord.new(12, 1000, true),
      ],
    })
    data("Default timeout", {
      timeout: 72*3600, max_steps: nil, max_interval: nil, use_sec: false, sec_thres: 0.8, wait: 1, backoff_base: 2,
      expected: [
        RetryRecord.new(1, 1, false),
        RetryRecord.new(2, 3, false),
        RetryRecord.new(3, 7, false),
        RetryRecord.new(4, 15, false),
        RetryRecord.new(5, 31, false),
        RetryRecord.new(6, 63, false),
        RetryRecord.new(7, 127, false),
        RetryRecord.new(8, 255, false),
        RetryRecord.new(9, 511, false),
        RetryRecord.new(10, 1023, false),
        RetryRecord.new(11, 2047, false),
        RetryRecord.new(12, 4095, false),
        RetryRecord.new(13, 8191, false),
        RetryRecord.new(14, 16383, false),
        RetryRecord.new(15, 32767, false),
        RetryRecord.new(16, 65535, false),
        RetryRecord.new(17, 131071, false),
        RetryRecord.new(18, 259200, false),
      ],
    })
    data("Default timeout with secondary", {
      timeout: 72*3600, max_steps: nil, max_interval: nil, use_sec: true, sec_thres: 0.8, wait: 1, backoff_base: 2,
      expected: [
        RetryRecord.new(1, 1, false),
        RetryRecord.new(2, 3, false),
        RetryRecord.new(3, 7, false),
        RetryRecord.new(4, 15, false),
        RetryRecord.new(5, 31, false),
        RetryRecord.new(6, 63, false),
        RetryRecord.new(7, 127, false),
        RetryRecord.new(8, 255, false),
        RetryRecord.new(9, 511, false),
        RetryRecord.new(10, 1023, false),
        RetryRecord.new(11, 2047, false),
        RetryRecord.new(12, 4095, false),
        RetryRecord.new(13, 8191, false),
        RetryRecord.new(14, 16383, false),
        RetryRecord.new(15, 32767, false),
        RetryRecord.new(16, 65535, false),
        RetryRecord.new(17, 131071, false),
        RetryRecord.new(18, 207360, true),
        RetryRecord.new(19, 207361, true),
        RetryRecord.new(20, 207363, true),
        RetryRecord.new(21, 207367, true),
        RetryRecord.new(22, 207375, true),
        RetryRecord.new(23, 207391, true),
        RetryRecord.new(24, 207423, true),
        RetryRecord.new(25, 207487, true),
        RetryRecord.new(26, 207615, true),
        RetryRecord.new(27, 207871, true),
        RetryRecord.new(28, 208383, true),
        RetryRecord.new(29, 209407, true),
        RetryRecord.new(30, 211455, true),
        RetryRecord.new(31, 215551, true),
        RetryRecord.new(32, 223743, true),
        RetryRecord.new(33, 240127, true),
        RetryRecord.new(34, 259200, true),
      ],
    })
    data("Default timeout with secondary and custom threshold", {
      timeout: 72*3600, max_steps: nil, max_interval: nil, use_sec: true, sec_thres: 0.5, wait: 1, backoff_base: 2,
      expected: [
        RetryRecord.new(1, 1, false),
        RetryRecord.new(2, 3, false),
        RetryRecord.new(3, 7, false),
        RetryRecord.new(4, 15, false),
        RetryRecord.new(5, 31, false),
        RetryRecord.new(6, 63, false),
        RetryRecord.new(7, 127, false),
        RetryRecord.new(8, 255, false),
        RetryRecord.new(9, 511, false),
        RetryRecord.new(10, 1023, false),
        RetryRecord.new(11, 2047, false),
        RetryRecord.new(12, 4095, false),
        RetryRecord.new(13, 8191, false),
        RetryRecord.new(14, 16383, false),
        RetryRecord.new(15, 32767, false),
        RetryRecord.new(16, 65535, false),
        RetryRecord.new(17, 129600, true),
        RetryRecord.new(18, 129601, true),
        RetryRecord.new(19, 129603, true),
        RetryRecord.new(20, 129607, true),
        RetryRecord.new(21, 129615, true),
        RetryRecord.new(22, 129631, true),
        RetryRecord.new(23, 129663, true),
        RetryRecord.new(24, 129727, true),
        RetryRecord.new(25, 129855, true),
        RetryRecord.new(26, 130111, true),
        RetryRecord.new(27, 130623, true),
        RetryRecord.new(28, 131647, true),
        RetryRecord.new(29, 133695, true),
        RetryRecord.new(30, 137791, true),
        RetryRecord.new(31, 145983, true),
        RetryRecord.new(32, 162367, true),
        RetryRecord.new(33, 195135, true),
        RetryRecord.new(34, 259200, true),
      ],
    })
    data("Simple max_steps", {
      timeout: 72*3600, max_steps: 10, max_interval: nil, use_sec: false, sec_thres: 0.8, wait: 1, backoff_base: 2,
      expected: [
        RetryRecord.new(1, 1, false),
        RetryRecord.new(2, 3, false),
        RetryRecord.new(3, 7, false),
        RetryRecord.new(4, 15, false),
        RetryRecord.new(5, 31, false),
        RetryRecord.new(6, 63, false),
        RetryRecord.new(7, 127, false),
        RetryRecord.new(8, 255, false),
        RetryRecord.new(9, 511, false),
        RetryRecord.new(10, 1023, false),
      ],
    })
    data("Simple max_steps with secondary", {
      timeout: 72*3600, max_steps: 10, max_interval: nil, use_sec: true, sec_thres: 0.8, wait: 1, backoff_base: 2,
      expected: [
        RetryRecord.new(1, 1, false),
        RetryRecord.new(2, 3, false),
        RetryRecord.new(3, 7, false),
        RetryRecord.new(4, 15, false),
        RetryRecord.new(5, 31, false),
        RetryRecord.new(6, 63, false),
        RetryRecord.new(7, 127, false),
        RetryRecord.new(8, 255, false),
        RetryRecord.new(9, 511, false),
        RetryRecord.new(10, 818, true),
      ],
    })
    data("Simple interval", {
      timeout: 72*3600, max_steps: nil, max_interval: 3600, use_sec: false, sec_thres: 0.8, wait: 1, backoff_base: 2,
      expected: [
        RetryRecord.new(1, 1, false),
        RetryRecord.new(2, 3, false),
        RetryRecord.new(3, 7, false),
        RetryRecord.new(4, 15, false),
        RetryRecord.new(5, 31, false),
        RetryRecord.new(6, 63, false),
        RetryRecord.new(7, 127, false),
        RetryRecord.new(8, 255, false),
        RetryRecord.new(9, 511, false),
        RetryRecord.new(10, 1023, false),
        RetryRecord.new(11, 2047, false),
        RetryRecord.new(12, 4095, false),
        RetryRecord.new(13, 7695, false),
        RetryRecord.new(14, 11295, false),
        RetryRecord.new(15, 14895, false),
        RetryRecord.new(16, 18495, false),
        RetryRecord.new(17, 22095, false),
        RetryRecord.new(18, 25695, false),
        RetryRecord.new(19, 29295, false),
        RetryRecord.new(20, 32895, false),
        RetryRecord.new(21, 36495, false),
        RetryRecord.new(22, 40095, false),
        RetryRecord.new(23, 43695, false),
        RetryRecord.new(24, 47295, false),
        RetryRecord.new(25, 50895, false),
        RetryRecord.new(26, 54495, false),
        RetryRecord.new(27, 58095, false),
        RetryRecord.new(28, 61695, false),
        RetryRecord.new(29, 65295, false),
        RetryRecord.new(30, 68895, false),
        RetryRecord.new(31, 72495, false),
        RetryRecord.new(32, 76095, false),
        RetryRecord.new(33, 79695, false),
        RetryRecord.new(34, 83295, false),
        RetryRecord.new(35, 86895, false),
        RetryRecord.new(36, 90495, false),
        RetryRecord.new(37, 94095, false),
        RetryRecord.new(38, 97695, false),
        RetryRecord.new(39, 101295, false),
        RetryRecord.new(40, 104895, false),
        RetryRecord.new(41, 108495, false),
        RetryRecord.new(42, 112095, false),
        RetryRecord.new(43, 115695, false),
        RetryRecord.new(44, 119295, false),
        RetryRecord.new(45, 122895, false),
        RetryRecord.new(46, 126495, false),
        RetryRecord.new(47, 130095, false),
        RetryRecord.new(48, 133695, false),
        RetryRecord.new(49, 137295, false),
        RetryRecord.new(50, 140895, false),
        RetryRecord.new(51, 144495, false),
        RetryRecord.new(52, 148095, false),
        RetryRecord.new(53, 151695, false),
        RetryRecord.new(54, 155295, false),
        RetryRecord.new(55, 158895, false),
        RetryRecord.new(56, 162495, false),
        RetryRecord.new(57, 166095, false),
        RetryRecord.new(58, 169695, false),
        RetryRecord.new(59, 173295, false),
        RetryRecord.new(60, 176895, false),
        RetryRecord.new(61, 180495, false),
        RetryRecord.new(62, 184095, false),
        RetryRecord.new(63, 187695, false),
        RetryRecord.new(64, 191295, false),
        RetryRecord.new(65, 194895, false),
        RetryRecord.new(66, 198495, false),
        RetryRecord.new(67, 202095, false),
        RetryRecord.new(68, 205695, false),
        RetryRecord.new(69, 209295, false),
        RetryRecord.new(70, 212895, false),
        RetryRecord.new(71, 216495, false),
        RetryRecord.new(72, 220095, false),
        RetryRecord.new(73, 223695, false),
        RetryRecord.new(74, 227295, false),
        RetryRecord.new(75, 230895, false),
        RetryRecord.new(76, 234495, false),
        RetryRecord.new(77, 238095, false),
        RetryRecord.new(78, 241695, false),
        RetryRecord.new(79, 245295, false),
        RetryRecord.new(80, 248895, false),
        RetryRecord.new(81, 252495, false),
        RetryRecord.new(82, 256095, false),
        RetryRecord.new(83, 259200, false),
      ],
    })
    data("Simple interval with secondary", {
      timeout: 72*3600, max_steps: nil, max_interval: 3600, use_sec: true, sec_thres: 0.8, wait: 1, backoff_base: 2,
      expected: [
        RetryRecord.new(1, 1, false),
        RetryRecord.new(2, 3, false),
        RetryRecord.new(3, 7, false),
        RetryRecord.new(4, 15, false),
        RetryRecord.new(5, 31, false),
        RetryRecord.new(6, 63, false),
        RetryRecord.new(7, 127, false),
        RetryRecord.new(8, 255, false),
        RetryRecord.new(9, 511, false),
        RetryRecord.new(10, 1023, false),
        RetryRecord.new(11, 2047, false),
        RetryRecord.new(12, 4095, false),
        RetryRecord.new(13, 7695, false),
        RetryRecord.new(14, 11295, false),
        RetryRecord.new(15, 14895, false),
        RetryRecord.new(16, 18495, false),
        RetryRecord.new(17, 22095, false),
        RetryRecord.new(18, 25695, false),
        RetryRecord.new(19, 29295, false),
        RetryRecord.new(20, 32895, false),
        RetryRecord.new(21, 36495, false),
        RetryRecord.new(22, 40095, false),
        RetryRecord.new(23, 43695, false),
        RetryRecord.new(24, 47295, false),
        RetryRecord.new(25, 50895, false),
        RetryRecord.new(26, 54495, false),
        RetryRecord.new(27, 58095, false),
        RetryRecord.new(28, 61695, false),
        RetryRecord.new(29, 65295, false),
        RetryRecord.new(30, 68895, false),
        RetryRecord.new(31, 72495, false),
        RetryRecord.new(32, 76095, false),
        RetryRecord.new(33, 79695, false),
        RetryRecord.new(34, 83295, false),
        RetryRecord.new(35, 86895, false),
        RetryRecord.new(36, 90495, false),
        RetryRecord.new(37, 94095, false),
        RetryRecord.new(38, 97695, false),
        RetryRecord.new(39, 101295, false),
        RetryRecord.new(40, 104895, false),
        RetryRecord.new(41, 108495, false),
        RetryRecord.new(42, 112095, false),
        RetryRecord.new(43, 115695, false),
        RetryRecord.new(44, 119295, false),
        RetryRecord.new(45, 122895, false),
        RetryRecord.new(46, 126495, false),
        RetryRecord.new(47, 130095, false),
        RetryRecord.new(48, 133695, false),
        RetryRecord.new(49, 137295, false),
        RetryRecord.new(50, 140895, false),
        RetryRecord.new(51, 144495, false),
        RetryRecord.new(52, 148095, false),
        RetryRecord.new(53, 151695, false),
        RetryRecord.new(54, 155295, false),
        RetryRecord.new(55, 158895, false),
        RetryRecord.new(56, 162495, false),
        RetryRecord.new(57, 166095, false),
        RetryRecord.new(58, 169695, false),
        RetryRecord.new(59, 173295, false),
        RetryRecord.new(60, 176895, false),
        RetryRecord.new(61, 180495, false),
        RetryRecord.new(62, 184095, false),
        RetryRecord.new(63, 187695, false),
        RetryRecord.new(64, 191295, false),
        RetryRecord.new(65, 194895, false),
        RetryRecord.new(66, 198495, false),
        RetryRecord.new(67, 202095, false),
        RetryRecord.new(68, 205695, false),
        RetryRecord.new(69, 207360, true),
        RetryRecord.new(70, 207361, true),
        RetryRecord.new(71, 207363, true),
        RetryRecord.new(72, 207367, true),
        RetryRecord.new(73, 207375, true),
        RetryRecord.new(74, 207391, true),
        RetryRecord.new(75, 207423, true),
        RetryRecord.new(76, 207487, true),
        RetryRecord.new(77, 207615, true),
        RetryRecord.new(78, 207871, true),
        RetryRecord.new(79, 208383, true),
        RetryRecord.new(80, 209407, true),
        RetryRecord.new(81, 211455, true),
        RetryRecord.new(82, 215055, true),
        RetryRecord.new(83, 218655, true),
        RetryRecord.new(84, 222255, true),
        RetryRecord.new(85, 225855, true),
        RetryRecord.new(86, 229455, true),
        RetryRecord.new(87, 233055, true),
        RetryRecord.new(88, 236655, true),
        RetryRecord.new(89, 240255, true),
        RetryRecord.new(90, 243855, true),
        RetryRecord.new(91, 247455, true),
        RetryRecord.new(92, 251055, true),
        RetryRecord.new(93, 254655, true),
        RetryRecord.new(94, 258255, true),
        RetryRecord.new(95, 259200, true),
      ],
    })
    data("Max_steps and max_interval", {
      timeout: 72*3600, max_steps: 30, max_interval: 3600, use_sec: false, sec_thres: 0.8, wait: 1, backoff_base: 2,
      expected: [
        RetryRecord.new(1, 1, false),
        RetryRecord.new(2, 3, false),
        RetryRecord.new(3, 7, false),
        RetryRecord.new(4, 15, false),
        RetryRecord.new(5, 31, false),
        RetryRecord.new(6, 63, false),
        RetryRecord.new(7, 127, false),
        RetryRecord.new(8, 255, false),
        RetryRecord.new(9, 511, false),
        RetryRecord.new(10, 1023, false),
        RetryRecord.new(11, 2047, false),
        RetryRecord.new(12, 4095, false),
        RetryRecord.new(13, 7695, false),
        RetryRecord.new(14, 11295, false),
        RetryRecord.new(15, 14895, false),
        RetryRecord.new(16, 18495, false),
        RetryRecord.new(17, 22095, false),
        RetryRecord.new(18, 25695, false),
        RetryRecord.new(19, 29295, false),
        RetryRecord.new(20, 32895, false),
        RetryRecord.new(21, 36495, false),
        RetryRecord.new(22, 40095, false),
        RetryRecord.new(23, 43695, false),
        RetryRecord.new(24, 47295, false),
        RetryRecord.new(25, 50895, false),
        RetryRecord.new(26, 54495, false),
        RetryRecord.new(27, 58095, false),
        RetryRecord.new(28, 61695, false),
        RetryRecord.new(29, 65295, false),
        RetryRecord.new(30, 68895, false),
      ],
    })
    data("Max_steps and max_interval with secondary", {
      timeout: 72*3600, max_steps: 30, max_interval: 3600, use_sec: true, sec_thres: 0.8, wait: 1, backoff_base: 2,
      expected: [
        RetryRecord.new(1, 1, false),
        RetryRecord.new(2, 3, false),
        RetryRecord.new(3, 7, false),
        RetryRecord.new(4, 15, false),
        RetryRecord.new(5, 31, false),
        RetryRecord.new(6, 63, false),
        RetryRecord.new(7, 127, false),
        RetryRecord.new(8, 255, false),
        RetryRecord.new(9, 511, false),
        RetryRecord.new(10, 1023, false),
        RetryRecord.new(11, 2047, false),
        RetryRecord.new(12, 4095, false),
        RetryRecord.new(13, 7695, false),
        RetryRecord.new(14, 11295, false),
        RetryRecord.new(15, 14895, false),
        RetryRecord.new(16, 18495, false),
        RetryRecord.new(17, 22095, false),
        RetryRecord.new(18, 25695, false),
        RetryRecord.new(19, 29295, false),
        RetryRecord.new(20, 32895, false),
        RetryRecord.new(21, 36495, false),
        RetryRecord.new(22, 40095, false),
        RetryRecord.new(23, 43695, false),
        RetryRecord.new(24, 47295, false),
        RetryRecord.new(25, 50895, false),
        RetryRecord.new(26, 54495, false),
        RetryRecord.new(27, 55116, true),
        RetryRecord.new(28, 55117, true),
        RetryRecord.new(29, 55119, true),
        RetryRecord.new(30, 55123, true),
      ],
    })
    data("Max_steps and max_interval with timeout", {
      timeout: 10000, max_steps: 30, max_interval: 1000, use_sec: false, sec_thres: 0.8, wait: 1, backoff_base: 2,
      expected: [
        RetryRecord.new(1, 1, false),
        RetryRecord.new(2, 3, false),
        RetryRecord.new(3, 7, false),
        RetryRecord.new(4, 15, false),
        RetryRecord.new(5, 31, false),
        RetryRecord.new(6, 63, false),
        RetryRecord.new(7, 127, false),
        RetryRecord.new(8, 255, false),
        RetryRecord.new(9, 511, false),
        RetryRecord.new(10, 1023, false),
        RetryRecord.new(11, 2023, false),
        RetryRecord.new(12, 3023, false),
        RetryRecord.new(13, 4023, false),
        RetryRecord.new(14, 5023, false),
        RetryRecord.new(15, 6023, false),
        RetryRecord.new(16, 7023, false),
        RetryRecord.new(17, 8023, false),
        RetryRecord.new(18, 9023, false),
        RetryRecord.new(19, 10000, false),
      ],
    })
    data("Max_steps and max_interval with timeout and secondary", {
      timeout: 10000, max_steps: 30, max_interval: 1000, use_sec: true, sec_thres: 0.8, wait: 1, backoff_base: 2,
      expected: [
        RetryRecord.new(1, 1, false),
        RetryRecord.new(2, 3, false),
        RetryRecord.new(3, 7, false),
        RetryRecord.new(4, 15, false),
        RetryRecord.new(5, 31, false),
        RetryRecord.new(6, 63, false),
        RetryRecord.new(7, 127, false),
        RetryRecord.new(8, 255, false),
        RetryRecord.new(9, 511, false),
        RetryRecord.new(10, 1023, false),
        RetryRecord.new(11, 2023, false),
        RetryRecord.new(12, 3023, false),
        RetryRecord.new(13, 4023, false),
        RetryRecord.new(14, 5023, false),
        RetryRecord.new(15, 6023, false),
        RetryRecord.new(16, 7023, false),
        RetryRecord.new(17, 8000, true),
        RetryRecord.new(18, 8001, true),
        RetryRecord.new(19, 8003, true),
        RetryRecord.new(20, 8007, true),
        RetryRecord.new(21, 8015, true),
        RetryRecord.new(22, 8031, true),
        RetryRecord.new(23, 8063, true),
        RetryRecord.new(24, 8127, true),
        RetryRecord.new(25, 8255, true),
        RetryRecord.new(26, 8511, true),
        RetryRecord.new(27, 9023, true),
        RetryRecord.new(28, 10000, true),
      ],
    })
    test "exponential backoff with senario" do |data|
      print_for_debug = false # change this value true if need to see msg always.
      trying_count = 1000 # just for avoiding infinite loop

      retry_records = []
      msg = ""

      s = @d.retry_state_create(
        :t15, :exponential_backoff, data[:wait], data[:timeout],
        max_steps: data[:max_steps], max_interval: data[:max_interval],
        secondary: data[:use_sec], secondary_threshold: data[:sec_thres],
        backoff_base: data[:backoff_base], randomize: false
      )
      override_current_time(s, s.start)

      retry_count = 0
      trying_count.times do
        next_elapsed = (s.next_time - s.start).to_i

        msg << "step: #{s.steps}, next: #{next_elapsed}s (#{next_elapsed / 3600}h)\n"

        # Wait until next time to trigger the next retry
        override_current_time(s, s.next_time)

        # Retry will be triggered at this point.
        retry_count += 1
        rec = RetryRecord.new(retry_count, next_elapsed, s.secondary?)
        retry_records.append(rec)
        msg << "[#{next_elapsed}s elapsed point] #{retry_count}th-Retry(#{s.secondary? ? "SEC" : "PRI"}) is triggered.\n"

        # Update retry statement
        s.step
        if s.limit?
          msg << "--- Reach limit. ---\n"
          break
        end
      end

      assert_equal(data[:expected], retry_records, msg)

      print(msg) if print_for_debug
    end
  end
end