diff options
Diffstat (limited to 'spec/initializers/enumerator_next_patch_spec.rb')
-rw-r--r-- | spec/initializers/enumerator_next_patch_spec.rb | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/spec/initializers/enumerator_next_patch_spec.rb b/spec/initializers/enumerator_next_patch_spec.rb new file mode 100644 index 00000000000..99e73af5e86 --- /dev/null +++ b/spec/initializers/enumerator_next_patch_spec.rb @@ -0,0 +1,167 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Enumerator#next patch fix' do + describe 'Enumerator' do + RSpec::Matchers.define :contain_unique_method_calls_in_order do |expected| + attr_reader :actual + + match do |actual| + @actual_err = actual + regexps = expected.map { |method_name| { name: method_name, regexp: make_regexp(method_name) } } + @actual = actual.backtrace.filter_map do |line| + regexp = regexps.find { |r| r[:regexp].match? line } + + regexp[:name] if regexp + end + + expected == @actual + end + + diffable + + failure_message do + "#{super()}\n\nFull error backtrace:\n #{@actual_err.backtrace.join("\n ")}" + end + + private + + def make_regexp(method_name) + Regexp.new("/spec/initializers/enumerator_next_patch_spec\\.rb:[0-9]+:in `#{method_name}'$") + end + end + + def have_been_raised_by_next_and_not_fixed_up + contain_unique_method_calls_in_order %w(call_enum_method) + end + + def have_been_raised_by_enum_object_and_fixed_up + contain_unique_method_calls_in_order %w(make_error call_enum_method) + end + + def have_been_raised_by_nested_next_and_fixed_up + contain_unique_method_calls_in_order %w(call_nested_next call_enum_method) + end + + methods = [ + { + name: 'next', + expected_value: 'Test value' + }, + { + name: 'next_values', + expected_value: ['Test value'] + }, + { + name: 'peek', + expected_value: 'Test value' + }, + { + name: 'peek_values', + expected_value: ['Test value'] + } + ] + + methods.each do |method| + describe "##{method[:name]}" do + def call_enum_method + enumerator.send(method_name) + end + + let(:method_name) { method[:name] } + + subject { call_enum_method } + + describe 'normal yield' do + let(:enumerator) { Enumerator.new { |yielder| yielder << 'Test value' } } + + it 'returns yielded value' do + is_expected.to eq(method[:expected_value]) + end + end + + describe 'end of iteration' do + let(:enumerator) { Enumerator.new { |_| } } + + it 'does not fix up StopIteration' do + expect { subject }.to raise_error do |err| + expect(err).to be_a(StopIteration) + expect(err).to have_been_raised_by_next_and_not_fixed_up + end + end + + context 'nested enum object' do + def call_nested_next + nested_enumerator.next + end + + let(:nested_enumerator) { Enumerator.new { |_| } } + let(:enumerator) { Enumerator.new { |yielder| yielder << call_nested_next } } + + it 'fixes up StopIteration thrown by another instance of #next' do + expect { subject }.to raise_error do |err| + expect(err).to be_a(StopIteration) + expect(err).to have_been_raised_by_nested_next_and_fixed_up + end + end + end + end + + describe 'arguments error' do + def call_enum_method + enumerator.send(method_name, 'extra argument') + end + + let(:enumerator) { Enumerator.new { |_| } } + + it 'does not fix up ArgumentError' do + expect { subject }.to raise_error do |err| + expect(err).to be_a(ArgumentError) + expect(err).to have_been_raised_by_next_and_not_fixed_up + end + end + end + + describe 'error' do + let(:enumerator) { Enumerator.new { |_| raise error } } + let(:error) { make_error } + + it 'fixes up StopIteration' do + def make_error + StopIteration.new.tap { |err| err.set_backtrace(caller) } + end + + expect { subject }.to raise_error do |err| + expect(err).to be(error) + expect(err).to have_been_raised_by_enum_object_and_fixed_up + end + end + + it 'fixes up ArgumentError' do + def make_error + ArgumentError.new.tap { |err| err.set_backtrace(caller) } + end + + expect { subject }.to raise_error do |err| + expect(err).to be(error) + expect(err).to have_been_raised_by_enum_object_and_fixed_up + end + end + + it 'adds backtrace from other errors' do + def make_error + StandardError.new('This is a test').tap { |err| err.set_backtrace(caller) } + end + + expect { subject }.to raise_error do |err| + expect(err).to be(error) + expect(err).to have_been_raised_by_enum_object_and_fixed_up + expect(err.message).to eq('This is a test') + end + end + end + end + end + end +end |