133 lines
3.5 KiB
Ruby
133 lines
3.5 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
# Rspec matcher with build in retry logic
|
||
|
#
|
||
|
# USAGE:
|
||
|
#
|
||
|
# Basic
|
||
|
# expect { Something.that.takes.time.to_appear }.to eventually_eq(expected_result)
|
||
|
# expect { Something.that.takes.time.to_appear }.not_to eventually_eq(expected_result)
|
||
|
#
|
||
|
# With duration and attempts override
|
||
|
# expect { Something.that.takes.time.to_appear }.to eventually_eq(expected_result).within(duration: 10, attempts: 5)
|
||
|
|
||
|
module Matchers
|
||
|
%w[
|
||
|
eq
|
||
|
be
|
||
|
include
|
||
|
be_truthy
|
||
|
be_falsey
|
||
|
be_empty
|
||
|
].each do |op|
|
||
|
RSpec::Matchers.define(:"eventually_#{op}") do |*expected|
|
||
|
chain(:within) do |options = {}|
|
||
|
@duration = options[:duration]
|
||
|
@attempts = options[:attempts]
|
||
|
end
|
||
|
|
||
|
def supports_block_expectations?
|
||
|
true
|
||
|
end
|
||
|
|
||
|
match { |actual| wait_and_check(actual, :default_expectation) }
|
||
|
|
||
|
match_when_negated { |actual| wait_and_check(actual, :when_negated_expectation) }
|
||
|
|
||
|
description do
|
||
|
"eventually #{operator_msg} #{expected.inspect}"
|
||
|
end
|
||
|
|
||
|
failure_message do
|
||
|
"#{e}:\nexpected to #{description}, last attempt was #{@result.nil? ? 'nil' : @result}"
|
||
|
end
|
||
|
|
||
|
failure_message_when_negated do
|
||
|
"#{e}:\nexpected not to #{description}, last attempt was #{@result.nil? ? 'nil' : @result}"
|
||
|
end
|
||
|
|
||
|
# Execute rspec expectation within retrier
|
||
|
#
|
||
|
# @param [Proc] actual
|
||
|
# @param [Symbol] expectation_name
|
||
|
# @return [Boolean]
|
||
|
def wait_and_check(actual, expectation_name)
|
||
|
QA::Support::Retrier.retry_until(
|
||
|
max_attempts: @attempts,
|
||
|
max_duration: @duration,
|
||
|
sleep_interval: 0.5
|
||
|
) do
|
||
|
public_send(expectation_name, actual)
|
||
|
rescue RSpec::Expectations::ExpectationNotMetError, QA::Resource::ApiFabricator::ResourceNotFoundError
|
||
|
false
|
||
|
end
|
||
|
rescue QA::Support::Repeater::RetriesExceededError, QA::Support::Repeater::WaitExceededError => e
|
||
|
@e = e
|
||
|
false
|
||
|
end
|
||
|
|
||
|
# Execute rspec expectation
|
||
|
#
|
||
|
# @param [Proc] actual
|
||
|
# @return [void]
|
||
|
def default_expectation(actual)
|
||
|
expect(result(&actual)).to public_send(*expectation_args)
|
||
|
end
|
||
|
|
||
|
# Execute negated rspec expectation
|
||
|
#
|
||
|
# @param [Proc] actual
|
||
|
# @return [void]
|
||
|
def when_negated_expectation(actual)
|
||
|
expect(result(&actual)).not_to public_send(*expectation_args)
|
||
|
end
|
||
|
|
||
|
# Result of actual block
|
||
|
#
|
||
|
# @return [Object]
|
||
|
def result
|
||
|
@result = yield
|
||
|
end
|
||
|
|
||
|
# Error message placeholder to indicate waiter did not fail properly
|
||
|
# This message should not appear under normal circumstances since it should
|
||
|
# always be assigned from repeater
|
||
|
#
|
||
|
# @return [String]
|
||
|
def e
|
||
|
@e ||= 'Waiter did not fail!'
|
||
|
end
|
||
|
|
||
|
# Operator message
|
||
|
#
|
||
|
# @return [String]
|
||
|
def operator_msg
|
||
|
case operator
|
||
|
when 'eq' then 'equal'
|
||
|
else operator
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Expect operator
|
||
|
#
|
||
|
# @return [String]
|
||
|
def operator
|
||
|
@operator ||= name.to_s.match(/eventually_(.+?)$/).to_a[1].to_s
|
||
|
end
|
||
|
|
||
|
# Expectation args
|
||
|
#
|
||
|
# @return [String, Array]
|
||
|
def expectation_args
|
||
|
if operator.include?('truthy') || operator.include?('falsey') || operator.include?('empty')
|
||
|
operator
|
||
|
elsif operator == "include" && expected.is_a?(Array)
|
||
|
[operator, *expected]
|
||
|
else
|
||
|
[operator, expected]
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|