Skip to content

Commit

Permalink
feat(finite_loop): introduce FiniteLoop.finite_loop
Browse files Browse the repository at this point in the history
  • Loading branch information
marian13 committed Mar 11, 2023
1 parent 1822ff6 commit ae363c1
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 4 deletions.
35 changes: 35 additions & 0 deletions lib/convenient_service/support/finite_loop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@
module ConvenientService
module Support
module FiniteLoop
##
# @return [Integer]
#
MAX_ITERATION_COUNT = 1_000

module Errors
class MaxIterationCountExceeded < ::StandardError
##
# @param limit [Integer]
# @return [void]
#
def initialize(limit:)
message = <<~TEXT
Max iteration count is exceeded. Current limit is #{limit}.
Expand All @@ -19,6 +26,9 @@ def initialize(limit:)
end

class NoBlockGiven < ::StandardError
##
# @return [void]
#
def initialize
message = <<~TEXT
`finite_loop` always expects a block to be given.
Expand All @@ -31,6 +41,31 @@ def initialize

private

##
# @example Provides `self.finite_loop` in order to have a way to use `finite_loop` without including `ConvenientService::Support::FiniteLoop`.
# ConvenientService::Support::FiniteLoop.finite_loop do |index|
# break if index > 3
# end
#
module_function

##
# @param max_iteration_count [Integer]
# @param raise_on_exceedance [Boolean]
# @param block [Proc]
# @return [Object] Can be any type.
#
# @example
# class Person
# include ConvenientService::Support::FiniteLoop
#
# def foo
# finite_loop do |index|
# break if index > 3
# end
# end
# end
#
def finite_loop(max_iteration_count: MAX_ITERATION_COUNT, raise_on_exceedance: true, &block)
raise Errors::NoBlockGiven.new unless block

Expand Down
132 changes: 128 additions & 4 deletions spec/lib/convenient_service/support/finite_loop_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
end

example_group "instance methods" do
describe "finite_loop" do
describe "#finite_loop" do
let(:base_klass) do
Class.new do
include ConvenientService::Support::FiniteLoop
Expand Down Expand Up @@ -69,7 +69,7 @@ def foo
TEXT
end

it "raises ConvenientService::Support::FiniteLoop::Errors::NoBlockGiven" do
it "raises `ConvenientService::Support::FiniteLoop::Errors::NoBlockGiven`" do
expect { instance.foo }
.to raise_error(ConvenientService::Support::FiniteLoop::Errors::NoBlockGiven)
.with_message(error_message)
Expand Down Expand Up @@ -139,7 +139,7 @@ def foo
end
end

it "returns nil" do
it "returns `nil`" do
expect(instance.foo).to be_nil
end
end
Expand All @@ -158,7 +158,7 @@ def foo
end
end

it "raises ConvenientService::Support::FiniteLoop::Errors::MaxIterationCountExceeded" do
it "raises `ConvenientService::Support::FiniteLoop::Errors::MaxIterationCountExceeded`" do
expect { instance.foo }
.to raise_error(ConvenientService::Support::FiniteLoop::Errors::MaxIterationCountExceeded)
.with_message(error_message)
Expand Down Expand Up @@ -196,5 +196,129 @@ def foo
end
end
end

example_group "class methods" do
describe ".finite_loop" do
let(:max_iteration_count) { 5 }

before do
stub_const("ConvenientService::Support::FiniteLoop::MAX_ITERATION_COUNT", max_iteration_count)
end

context "when iteration happens" do
let(:loop_result) do
described_class.finite_loop do |index|
break if index >= 3

print index
end
end

it "passes iteration index to block" do
expect { loop_result }.to output("012").to_stdout
end
end

context "when NO block is given" do
let(:loop_result) { described_class.finite_loop }

let(:error_message) do
<<~TEXT
`finite_loop` always expects a block to be given.
TEXT
end

it "raises `ConvenientService::Support::FiniteLoop::Errors::NoBlockGiven`" do
expect { loop_result }
.to raise_error(ConvenientService::Support::FiniteLoop::Errors::NoBlockGiven)
.with_message(error_message)
end
end

context "when `max_iteration_count` is NOT exceeded" do
let(:loop_result) do
described_class.finite_loop do |index|
break index if index >= 3
end
end

it "returns `break` value" do
expect(loop_result).to eq(3)
end
end

context "when `max_iteration_count` is exceeded" do
let(:error_message) do
<<~TEXT
Max iteration count is exceeded. Current limit is #{max_iteration_count}.
Consider using `max_iteration_count` or `raise_on_exceedance` options if that is not the expected behavior.
TEXT
end

context "when `raise_on_exceedance` is NOT passed" do
let(:loop_result) do
described_class.finite_loop do |index|
# NOTE: No `break` to exceed max iteration count.
end
end

it "defaults `raise_on_exceedance` to `true`" do
expect { loop_result }
.to raise_error(ConvenientService::Support::FiniteLoop::Errors::MaxIterationCountExceeded)
.with_message(error_message)
end
end

context "when `raise_on_exceedance` is set to `false`" do
let(:loop_result) do
described_class.finite_loop(raise_on_exceedance: false) do |index|
# NOTE: No `break` to exceed max iteration count.
end
end

it "returns `nil`" do
expect(loop_result).to be_nil
end
end

context "when `raise_on_exceedance` is set to `true`" do
let(:loop_result) do
described_class.finite_loop(raise_on_exceedance: true) do |index|
# NOTE: No `break` to exceed max iteration count.
end
end

it "raises `ConvenientService::Support::FiniteLoop::Errors::MaxIterationCountExceeded`" do
expect { loop_result }
.to raise_error(ConvenientService::Support::FiniteLoop::Errors::MaxIterationCountExceeded)
.with_message(error_message)
end
end
end

context "when `max_iteration_count` is NOT passed" do
let(:loop_result) do
described_class.finite_loop do |index|
break index if index >= 3

puts "foo"
end
end

it "uses `ConvenientService::Support::FiniteLoop::MAX_ITERATION_COUNT` as `max_iteration_count` default value" do
##
# NOTE:
# - MAX_ITERATION_COUNT is stubbed to 5.
# - max_iteration_count is NOT passed.
# - foo uses finite loop.
# - finite loop puts "foo" string on each iteration.
# - if foo prints "foo" 3 times, test can considered as successful.
#
expect { loop_result }.to output("foo\n" * 3).to_stdout
end
end
end
end
end
# rubocop:enable RSpec/NestedGroups

0 comments on commit ae363c1

Please sign in to comment.