diff options
Diffstat (limited to 'qa/spec')
-rw-r--r-- | qa/spec/spec_helper.rb | 5 | ||||
-rw-r--r-- | qa/spec/specs/helpers/context_selector_spec.rb | 6 | ||||
-rw-r--r-- | qa/spec/support/matchers/eventually_matcher.rb | 132 |
3 files changed, 138 insertions, 5 deletions
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb index 0c9643c830b..f4bfd57504e 100644 --- a/qa/spec/spec_helper.rb +++ b/qa/spec/spec_helper.rb @@ -4,6 +4,7 @@ require_relative '../qa' require 'rspec/retry' require 'rspec-parameterized' require 'active_support/core_ext/hash' +require 'active_support/core_ext/object/blank' if ENV['CI'] && QA::Runtime::Env.knapsack? && !ENV['NO_KNAPSACK'] require 'knapsack' @@ -11,8 +12,8 @@ if ENV['CI'] && QA::Runtime::Env.knapsack? && !ENV['NO_KNAPSACK'] end QA::Runtime::Browser.configure! - -QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) if QA::Runtime::Env.runtime_scenario_attributes +QA::Runtime::AllureReport.configure! +QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) Dir[::File.join(__dir__, "support/helpers/*.rb")].sort.each { |f| require f } Dir[::File.join(__dir__, "support/matchers/*.rb")].sort.each { |f| require f } diff --git a/qa/spec/specs/helpers/context_selector_spec.rb b/qa/spec/specs/helpers/context_selector_spec.rb index 16b6c6601b1..7792d33dcf9 100644 --- a/qa/spec/specs/helpers/context_selector_spec.rb +++ b/qa/spec/specs/helpers/context_selector_spec.rb @@ -189,9 +189,9 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do it 'runs on default branch pipelines' do group = describe_successfully do - it('runs on master pipeline given a single pipeline', only: { pipeline: :master }) {} - it('runs in master given an array of pipelines', only: { pipeline: [:canary, :master] }) {} - it('does not run in non-default pipelines', only: { pipeline: [:nightly, :not_nightly, :not_master] }) {} + it('runs on main pipeline given a single pipeline', only: { pipeline: :main }) {} + it('runs in main given an array of pipelines', only: { pipeline: [:canary, :main] }) {} + it('does not run in non-default pipelines', only: { pipeline: [:nightly, :not_nightly, :not_main] }) {} end aggregate_failures do diff --git a/qa/spec/support/matchers/eventually_matcher.rb b/qa/spec/support/matchers/eventually_matcher.rb new file mode 100644 index 00000000000..3f0afd6fb54 --- /dev/null +++ b/qa/spec/support/matchers/eventually_matcher.rb @@ -0,0 +1,132 @@ +# 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 |