diff options
Diffstat (limited to 'qa/spec')
-rw-r--r-- | qa/spec/page/base_spec.rb | 8 | ||||
-rw-r--r-- | qa/spec/page/logging_spec.rb | 8 | ||||
-rw-r--r-- | qa/spec/resource/events/project_spec.rb | 1 | ||||
-rw-r--r-- | qa/spec/support/repeater_spec.rb | 385 | ||||
-rw-r--r-- | qa/spec/support/retrier_spec.rb | 126 | ||||
-rw-r--r-- | qa/spec/support/waiter_spec.rb | 42 |
6 files changed, 553 insertions, 17 deletions
diff --git a/qa/spec/page/base_spec.rb b/qa/spec/page/base_spec.rb index 9e3f143ea5b..e157eb6ac3e 100644 --- a/qa/spec/page/base_spec.rb +++ b/qa/spec/page/base_spec.rb @@ -69,11 +69,11 @@ describe QA::Page::Base do it 'does not refresh' do expect(subject).not_to receive(:refresh) - subject.wait(max: 0.01) { true } + subject.wait(max: 0.01, raise_on_failure: false) { true } end it 'returns true' do - expect(subject.wait(max: 0.1) { true }).to be_truthy + expect(subject.wait(max: 0.1, raise_on_failure: false) { true }).to be_truthy end end @@ -81,13 +81,13 @@ describe QA::Page::Base do it 'refreshes' do expect(subject).to receive(:refresh).at_least(:once) - subject.wait(max: 0.01) { false } + subject.wait(max: 0.01, raise_on_failure: false) { false } end it 'returns false' do allow(subject).to receive(:refresh) - expect(subject.wait(max: 0.01) { false }).to be_falsey + expect(subject.wait(max: 0.01, raise_on_failure: false) { false }).to be_falsey end end end diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb index fb89bcd3ab4..0a394e1c38f 100644 --- a/qa/spec/page/logging_spec.rb +++ b/qa/spec/page/logging_spec.rb @@ -31,18 +31,18 @@ describe QA::Support::Page::Logging do expect { subject.wait(max: 0) {} } .to output(/next wait uses reload: true/).to_stdout_from_any_process expect { subject.wait(max: 0) {} } - .to output(/with wait/).to_stdout_from_any_process + .to output(/with wait_until/).to_stdout_from_any_process expect { subject.wait(max: 0) {} } - .to output(/ended wait after .* seconds$/).to_stdout_from_any_process + .to output(/ended wait_until$/).to_stdout_from_any_process end it 'logs wait with reload false' do expect { subject.wait(max: 0, reload: false) {} } .to output(/next wait uses reload: false/).to_stdout_from_any_process expect { subject.wait(max: 0, reload: false) {} } - .to output(/with wait/).to_stdout_from_any_process + .to output(/with wait_until/).to_stdout_from_any_process expect { subject.wait(max: 0, reload: false) {} } - .to output(/ended wait after .* seconds$/).to_stdout_from_any_process + .to output(/ended wait_until$/).to_stdout_from_any_process end it 'logs scroll_to' do diff --git a/qa/spec/resource/events/project_spec.rb b/qa/spec/resource/events/project_spec.rb index b3efdb518f3..dd544ec7ac8 100644 --- a/qa/spec/resource/events/project_spec.rb +++ b/qa/spec/resource/events/project_spec.rb @@ -33,6 +33,7 @@ describe QA::Resource::Events::Project do before do allow(subject).to receive(:max_wait).and_return(0.01) + allow(subject).to receive(:raise_on_failure).and_return(false) allow(subject).to receive(:parse_body).and_return(all_events) end diff --git a/qa/spec/support/repeater_spec.rb b/qa/spec/support/repeater_spec.rb new file mode 100644 index 00000000000..20dca6608f6 --- /dev/null +++ b/qa/spec/support/repeater_spec.rb @@ -0,0 +1,385 @@ +# frozen_string_literal: true + +require 'logger' +require 'timecop' +require 'active_support/core_ext/integer/time' + +describe QA::Support::Repeater do + before do + logger = ::Logger.new $stdout + logger.level = ::Logger::DEBUG + QA::Runtime::Logger.logger = logger + end + + subject do + Module.new do + extend QA::Support::Repeater + end + end + + let(:time_start) { Time.now } + let(:return_value) { "test passed" } + + describe '.repeat_until' do + context 'when raise_on_failure is not provided (default: true)' do + context 'when retry_on_exception is not provided (default: false)' do + context 'when max_duration is provided' do + context 'when max duration is reached' do + it 'raises an exception' do + expect do + Timecop.freeze do + subject.repeat_until(max_duration: 1) do + Timecop.travel(2) + false + end + end + end.to raise_error(QA::Support::Repeater::WaitExceededError, "Wait condition not met after 1 second") + end + + it 'ignores attempts' do + loop_counter = 0 + + expect( + Timecop.freeze do + subject.repeat_until(max_duration: 1) do + loop_counter += 1 + + if loop_counter > 3 + Timecop.travel(1) + return_value + else + false + end + end + end + ).to eq(return_value) + expect(loop_counter).to eq(4) + end + end + + context 'when max duration is not reached' do + it 'returns value from block' do + Timecop.freeze(time_start) do + expect( + subject.repeat_until(max_duration: 1) do + return_value + end + ).to eq(return_value) + end + end + end + end + + context 'when max_attempts is provided' do + context 'when max_attempts is reached' do + it 'raises an exception' do + expect do + Timecop.freeze do + subject.repeat_until(max_attempts: 1) do + false + end + end + end.to raise_error(QA::Support::Repeater::RetriesExceededError, "Retry condition not met after 1 attempt") + end + + it 'ignores duration' do + loop_counter = 0 + + expect( + Timecop.freeze do + subject.repeat_until(max_attempts: 2) do + loop_counter += 1 + Timecop.travel(1.year) + + if loop_counter > 1 + return_value + else + false + end + end + end + ).to eq(return_value) + expect(loop_counter).to eq(2) + end + end + + context 'when max_attempts is not reached' do + it 'returns value from block' do + expect( + Timecop.freeze do + subject.repeat_until(max_attempts: 1) do + return_value + end + end + ).to eq(return_value) + end + end + end + + context 'when both max_attempts and max_duration are provided' do + context 'when max_attempts is reached first' do + it 'raises an exception' do + loop_counter = 0 + expect do + Timecop.freeze do + subject.repeat_until(max_attempts: 1, max_duration: 2) do + loop_counter += 1 + Timecop.travel(time_start + loop_counter) + false + end + end + end.to raise_error(QA::Support::Repeater::RetriesExceededError, "Retry condition not met after 1 attempt") + end + end + + context 'when max_duration is reached first' do + it 'raises an exception' do + loop_counter = 0 + expect do + Timecop.freeze do + subject.repeat_until(max_attempts: 2, max_duration: 1) do + loop_counter += 1 + Timecop.travel(time_start + loop_counter) + false + end + end + end.to raise_error(QA::Support::Repeater::WaitExceededError, "Wait condition not met after 1 second") + end + end + end + end + + context 'when retry_on_exception is true' do + context 'when max duration is reached' do + it 'raises an exception' do + Timecop.freeze do + expect do + subject.repeat_until(max_duration: 1, retry_on_exception: true) do + Timecop.travel(2) + + raise "this should be raised" + end + end.to raise_error(RuntimeError, "this should be raised") + end + end + + it 'does not raise an exception until max_duration is reached' do + loop_counter = 0 + + Timecop.freeze(time_start) do + expect do + subject.repeat_until(max_duration: 2, retry_on_exception: true) do + loop_counter += 1 + Timecop.travel(time_start + loop_counter) + + raise "this should be raised" + end + end.to raise_error(RuntimeError, "this should be raised") + end + expect(loop_counter).to eq(2) + end + end + + context 'when max duration is not reached' do + it 'returns value from block' do + loop_counter = 0 + + Timecop.freeze(time_start) do + expect( + subject.repeat_until(max_duration: 3, retry_on_exception: true) do + loop_counter += 1 + Timecop.travel(time_start + loop_counter) + + raise "this should not be raised" if loop_counter == 1 + + return_value + end + ).to eq(return_value) + end + expect(loop_counter).to eq(2) + end + end + + context 'when both max_attempts and max_duration are provided' do + context 'when max_attempts is reached first' do + it 'raises an exception' do + loop_counter = 0 + expect do + Timecop.freeze do + subject.repeat_until(max_attempts: 1, max_duration: 2, retry_on_exception: true) do + loop_counter += 1 + Timecop.travel(time_start + loop_counter) + false + end + end + end.to raise_error(QA::Support::Repeater::RetriesExceededError, "Retry condition not met after 1 attempt") + end + end + + context 'when max_duration is reached first' do + it 'raises an exception' do + loop_counter = 0 + expect do + Timecop.freeze do + subject.repeat_until(max_attempts: 2, max_duration: 1, retry_on_exception: true) do + loop_counter += 1 + Timecop.travel(time_start + loop_counter) + false + end + end + end.to raise_error(QA::Support::Repeater::WaitExceededError, "Wait condition not met after 1 second") + end + end + end + end + end + + context 'when raise_on_failure is false' do + context 'when retry_on_exception is not provided (default: false)' do + context 'when max duration is reached' do + def test_wait + Timecop.freeze do + subject.repeat_until(max_duration: 1, raise_on_failure: false) do + Timecop.travel(2) + return_value + end + end + end + + it 'does not raise an exception' do + expect { test_wait }.not_to raise_error + end + + it 'returns the value from the block' do + expect(test_wait).to eq(return_value) + end + end + + context 'when max duration is not reached' do + it 'returns the value from the block' do + Timecop.freeze do + expect( + subject.repeat_until(max_duration: 1, raise_on_failure: false) do + return_value + end + ).to eq(return_value) + end + end + + it 'raises an exception' do + Timecop.freeze do + expect do + subject.repeat_until(max_duration: 1, raise_on_failure: false) do + raise "this should be raised" + end + end.to raise_error(RuntimeError, "this should be raised") + end + end + end + + context 'when both max_attempts and max_duration are provided' do + shared_examples 'repeat until' do |max_attempts:, max_duration:| + it "returns when #{max_attempts < max_duration ? 'max_attempts' : 'max_duration'} is reached" do + loop_counter = 0 + + expect( + Timecop.freeze do + subject.repeat_until(max_attempts: max_attempts, max_duration: max_duration, raise_on_failure: false) do + loop_counter += 1 + Timecop.travel(time_start + loop_counter) + false + end + end + ).to eq(false) + expect(loop_counter).to eq(1) + end + end + + context 'when max_attempts is reached first' do + it_behaves_like 'repeat until', max_attempts: 1, max_duration: 2 + end + + context 'when max_duration is reached first' do + it_behaves_like 'repeat until', max_attempts: 2, max_duration: 1 + end + end + end + + context 'when retry_on_exception is true' do + context 'when max duration is reached' do + def test_wait + Timecop.freeze do + subject.repeat_until(max_duration: 1, raise_on_failure: false, retry_on_exception: true) do + Timecop.travel(2) + return_value + end + end + end + + it 'does not raise an exception' do + expect { test_wait }.not_to raise_error + end + + it 'returns the value from the block' do + expect(test_wait).to eq(return_value) + end + end + + context 'when max duration is not reached' do + before do + @loop_counter = 0 + end + + def test_wait_with_counter + Timecop.freeze(time_start) do + subject.repeat_until(max_duration: 3, raise_on_failure: false, retry_on_exception: true) do + @loop_counter += 1 + Timecop.travel(time_start + @loop_counter) + + raise "this should not be raised" if @loop_counter == 1 + + return_value + end + end + end + + it 'does not raise an exception' do + expect { test_wait_with_counter }.not_to raise_error + end + + it 'returns the value from the block' do + expect(test_wait_with_counter).to eq(return_value) + expect(@loop_counter).to eq(2) + end + end + + context 'when both max_attempts and max_duration are provided' do + shared_examples 'repeat until' do |max_attempts:, max_duration:| + it "returns when #{max_attempts < max_duration ? 'max_attempts' : 'max_duration'} is reached" do + loop_counter = 0 + + expect( + Timecop.freeze do + subject.repeat_until(max_attempts: max_attempts, max_duration: max_duration, raise_on_failure: false, retry_on_exception: true) do + loop_counter += 1 + Timecop.travel(time_start + loop_counter) + false + end + end + ).to eq(false) + expect(loop_counter).to eq(1) + end + end + + context 'when max_attempts is reached first' do + it_behaves_like 'repeat until', max_attempts: 1, max_duration: 2 + end + + context 'when max_duration is reached first' do + it_behaves_like 'repeat until', max_attempts: 2, max_duration: 1 + end + end + end + end + end +end diff --git a/qa/spec/support/retrier_spec.rb b/qa/spec/support/retrier_spec.rb new file mode 100644 index 00000000000..fbe66a680f9 --- /dev/null +++ b/qa/spec/support/retrier_spec.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require 'logger' +require 'timecop' + +describe QA::Support::Retrier do + before do + logger = ::Logger.new $stdout + logger.level = ::Logger::DEBUG + QA::Runtime::Logger.logger = logger + end + + describe '.retry_until' do + context 'when the condition is true' do + it 'logs max attempts (3 by default)' do + expect { subject.retry_until { true } } + .to output(/with retry_until: max_attempts: 3; reload_page: ; sleep_interval: 0; raise_on_failure: false; retry_on_exception: false/).to_stdout_from_any_process + end + + it 'logs max duration' do + expect { subject.retry_until(max_duration: 1) { true } } + .to output(/with retry_until: max_duration: 1; reload_page: ; sleep_interval: 0; raise_on_failure: false; retry_on_exception: false/).to_stdout_from_any_process + end + + it 'logs the end' do + expect { subject.retry_until { true } } + .to output(/ended retry_until$/).to_stdout_from_any_process + end + end + + context 'when the condition is false' do + it 'logs the start' do + expect { subject.retry_until(max_duration: 0) { false } } + .to output(/with retry_until: max_duration: 0; reload_page: ; sleep_interval: 0; raise_on_failure: false; retry_on_exception: false/).to_stdout_from_any_process + end + + it 'logs the end' do + expect { subject.retry_until(max_duration: 0) { false } } + .to output(/ended retry_until$/).to_stdout_from_any_process + end + end + + context 'when max_duration and max_attempts are nil' do + it 'sets max attempts to 3 by default' do + expect(subject).to receive(:repeat_until).with(hash_including(max_attempts: 3)) + + subject.retry_until + end + end + + it 'sets sleep_interval to 0 by default' do + expect(subject).to receive(:repeat_until).with(hash_including(sleep_interval: 0)) + + subject.retry_until + end + + it 'sets raise_on_failure to false by default' do + expect(subject).to receive(:repeat_until).with(hash_including(raise_on_failure: false)) + + subject.retry_until + end + + it 'sets retry_on_exception to false by default' do + expect(subject).to receive(:repeat_until).with(hash_including(retry_on_exception: false)) + + subject.retry_until + end + end + + describe '.retry_on_exception' do + context 'when the condition is true' do + it 'logs max_attempts, reload_page, and sleep_interval parameters' do + expect { subject.retry_on_exception(max_attempts: 1, reload_page: nil, sleep_interval: 0) { true } } + .to output(/with retry_on_exception: max_attempts: 1; reload_page: ; sleep_interval: 0/).to_stdout_from_any_process + end + + it 'logs the end' do + expect { subject.retry_on_exception(max_attempts: 1, reload_page: nil, sleep_interval: 0) { true } } + .to output(/ended retry_on_exception$/).to_stdout_from_any_process + end + end + + context 'when the condition is false' do + it 'logs the start' do + expect { subject.retry_on_exception(max_attempts: 1, reload_page: nil, sleep_interval: 0) { false } } + .to output(/with retry_on_exception: max_attempts: 1; reload_page: ; sleep_interval: 0/).to_stdout_from_any_process + end + + it 'logs the end' do + expect { subject.retry_on_exception(max_attempts: 1, reload_page: nil, sleep_interval: 0) { false } } + .to output(/ended retry_on_exception$/).to_stdout_from_any_process + end + end + + it 'does not repeat if no exception is raised' do + loop_counter = 0 + return_value = "test passed" + + expect( + subject.retry_on_exception(max_attempts: 2) do + loop_counter += 1 + return_value + end + ).to eq(return_value) + expect(loop_counter).to eq(1) + end + + it 'sets retry_on_exception to true' do + expect(subject).to receive(:repeat_until).with(hash_including(retry_on_exception: true)) + + subject.retry_on_exception + end + + it 'sets max_attempts to 3 by default' do + expect(subject).to receive(:repeat_until).with(hash_including(max_attempts: 3)) + + subject.retry_on_exception + end + + it 'sets sleep_interval to 0.5 by default' do + expect(subject).to receive(:repeat_until).with(hash_including(sleep_interval: 0.5)) + + subject.retry_on_exception + end + end +end diff --git a/qa/spec/support/waiter_spec.rb b/qa/spec/support/waiter_spec.rb index 8283b65e1be..06e404c862a 100644 --- a/qa/spec/support/waiter_spec.rb +++ b/qa/spec/support/waiter_spec.rb @@ -9,29 +9,53 @@ describe QA::Support::Waiter do QA::Runtime::Logger.logger = logger end - describe '.wait' do + describe '.wait_until' do context 'when the condition is true' do it 'logs the start' do - expect { subject.wait(max: 0) {} } - .to output(/with wait: max 0; interval 0.1/).to_stdout_from_any_process + expect { subject.wait_until(max_duration: 0, raise_on_failure: false) { true } } + .to output(/with wait_until: max_duration: 0; reload_page: ; sleep_interval: 0.1/).to_stdout_from_any_process end it 'logs the end' do - expect { subject.wait(max: 0) {} } - .to output(/ended wait after .* seconds$/).to_stdout_from_any_process + expect { subject.wait_until(max_duration: 0, raise_on_failure: false) { true } } + .to output(/ended wait_until$/).to_stdout_from_any_process end end context 'when the condition is false' do it 'logs the start' do - expect { subject.wait(max: 0) { false } } - .to output(/with wait: max 0; interval 0.1/).to_stdout_from_any_process + expect { subject.wait_until(max_duration: 0, raise_on_failure: false) { false } } + .to output(/with wait_until: max_duration: 0; reload_page: ; sleep_interval: 0.1/).to_stdout_from_any_process end it 'logs the end' do - expect { subject.wait(max: 0) { false } } - .to output(/ended wait after .* seconds$/).to_stdout_from_any_process + expect { subject.wait_until(max_duration: 0, raise_on_failure: false) { false } } + .to output(/ended wait_until$/).to_stdout_from_any_process end end + + it 'sets max_duration to 60 by default' do + expect(subject).to receive(:repeat_until).with(hash_including(max_duration: 60)) + + subject.wait_until + end + + it 'sets sleep_interval to 0.1 by default' do + expect(subject).to receive(:repeat_until).with(hash_including(sleep_interval: 0.1)) + + subject.wait_until + end + + it 'sets raise_on_failure to false by default' do + expect(subject).to receive(:repeat_until).with(hash_including(raise_on_failure: false)) + + subject.wait_until + end + + it 'sets retry_on_exception to false by default' do + expect(subject).to receive(:repeat_until).with(hash_including(retry_on_exception: false)) + + subject.wait_until + end end end |