Inline some rspec-wait into our code

Doing so to remove the usage of Timeout.timeout as its behavior is
unpredictable.
This commit is contained in:
Christophe Bliard
2023-07-10 11:15:32 +02:00
parent d4decec7a9
commit e60d440edd
5 changed files with 146 additions and 6 deletions
-3
View File
@@ -228,9 +228,6 @@ group :test do
gem 'retriable', '~> 3.1.1'
gem 'rspec-retry', '~> 0.6.1'
# Wait for conditions in RSpec
gem 'rspec-wait'
# Modify ENV
gem 'climate_control'
-3
View File
@@ -834,8 +834,6 @@ GEM
rspec-retry (0.6.2)
rspec-core (> 3.3)
rspec-support (3.12.0)
rspec-wait (0.0.9)
rspec (>= 3, < 4)
rubocop (1.54.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
@@ -1135,7 +1133,6 @@ DEPENDENCIES
rspec (~> 3.12.0)
rspec-rails (~> 6.0.0)
rspec-retry (~> 0.6.1)
rspec-wait
rubocop
rubocop-rails
rubocop-rspec
+35
View File
@@ -0,0 +1,35 @@
# frozen_string_literal: true
# borrowed from rspec-wait, avoiding usage of Timeout
module RSpec
module Wait
module Handler
def handle_matcher(target, *args, &)
t = Time.current
begin
actual = target.respond_to?(:call) ? target.call : target
super(actual, *args, &)
rescue RSpec::Expectations::ExpectationNotMetError => e
elapsed = Time.current - t
if elapsed < RSpec.configuration.wait_timeout
sleep RSpec.configuration.wait_delay
retry
else
raise e
end
end
end
end
# From: https://github.com/rspec/rspec-expectations/blob/v3.12.3/lib/rspec/expectations/handler.rb#L46-L71
class PositiveHandler < RSpec::Expectations::PositiveExpectationHandler
extend Handler
end
# From: https://github.com/rspec/rspec-expectations/blob/v3.12.3/lib/rspec/expectations/handler.rb#L74-L107
class NegativeHandler < RSpec::Expectations::NegativeExpectationHandler
extend Handler
end
end
end
+52
View File
@@ -0,0 +1,52 @@
# frozen_string_literal: true
# borrowed from rspec-wait
module RSpec
module Wait
class Target < RSpec::Expectations::ExpectationTarget
# From: https://github.com/rspec/rspec-expectations/blob/v3.12.3/lib/rspec/expectations/expectation_target.rb#L22
UndefinedValue = Module.new
# From: https://github.com/rspec/rspec-expectations/blob/v3.12.3/lib/rspec/expectations/expectation_target.rb#L31-L33
def initialize(target, **options)
@wait_options = options
super(target)
end
# From: https://github.com/rspec/rspec-expectations/blob/v3.12.3/lib/rspec/expectations/expectation_target.rb#L36-L47
def self.for(value, block, **options)
if UndefinedValue.equal?(value)
unless block
raise ArgumentError, "You must pass either an argument or a block to `wait_for`."
end
new(block, **options)
elsif block
raise ArgumentError, "You cannot pass both an argument and a block to `wait_for`."
else
new(value, **options)
end
end
# From: https://github.com/rspec/rspec-expectations/blob/v3.12.3/lib/rspec/expectations/expectation_target.rb#L63-L66
def to(matcher = nil, message = nil, &)
prevent_operator_matchers(:to) unless matcher
with_wait { PositiveHandler.handle_matcher(@target, matcher, message, &) }
end
# From: https://github.com/rspec/rspec-expectations/blob/v3.12.3/lib/rspec/expectations/expectation_target.rb#L76-L79
def not_to(matcher = nil, message = nil, &)
prevent_operator_matchers(:not_to) unless matcher
with_wait { NegativeHandler.handle_matcher(@target, matcher, message, &) }
end
alias_method :to_not, :not_to
private
def with_wait(&)
Wait.with_wait(**@wait_options, &)
end
end
end
end
+59
View File
@@ -0,0 +1,59 @@
# frozen_string_literal: true
require_relative 'wait/handler'
require_relative 'wait/target'
# borrowed from rspec-wait, avoiding usage of Timeout
module RSpec
module Wait
module_function
# Drop-in replacement for `expect` assertions, waiting for the assertion to
# pass.
#
# Useful for testing user interfaces with tricky timing elements like
# JavaScript interactions or remote requests.
#
# The assertion is checked every `RSpec.configuration.wait_delay` seconds
# until `RSpec.configuration.wait_timeout` seconds have passed.
#
# Default value of `wait_timeout` is 3. It can be overriden with `:timeout`
# keyword parameter.
#
# Default value of `wait_delay` is 0.05. It can be overriden with `:delay`
# keyword parameter.
#
# Examples:
# wait_for(ticker.tape).to eq("··-·")
# wait_for(ticker.tape).to eq("··-· ---")
# wait_for(ticker.tape).to eq("··-· --- ---", timeout: 5)
def wait_for(value = Target::UndefinedValue, &block)
Target.for(value, block)
end
# Sets timeout and delay values for `wait_for`.
#
# @param timeout [Numeric] time in seconds to wait up for assertions to pass
# @param delay [Numeric] time in seconds elapsing between two checks of an
# assertion
def with_wait(timeout: nil, delay: nil)
original_timeout = RSpec.configuration.wait_timeout
original_delay = RSpec.configuration.wait_delay
RSpec.configuration.wait_timeout = timeout if timeout
RSpec.configuration.wait_delay = delay if delay
yield
ensure
RSpec.configuration.wait_timeout = original_timeout
RSpec.configuration.wait_delay = original_delay
end
end
end
RSpec.configure do |config|
config.include(RSpec::Wait)
config.add_setting(:wait_timeout, default: 3)
config.add_setting(:wait_delay, default: 0.05)
end