diff options
Diffstat (limited to 'spec/ruby/language/for_spec.rb')
| -rw-r--r-- | spec/ruby/language/for_spec.rb | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/spec/ruby/language/for_spec.rb b/spec/ruby/language/for_spec.rb new file mode 100644 index 0000000000..b0f3aef405 --- /dev/null +++ b/spec/ruby/language/for_spec.rb @@ -0,0 +1,380 @@ +require_relative '../spec_helper' +require_relative 'fixtures/for_scope' + +# for name[, name]... in expr [do] +# body +# end +describe "The for expression" do + it "iterates over an Enumerable passing each element to the block" do + j = 0 + for i in 1..3 + j += i + end + j.should == 6 + end + + it "iterates over a list of arrays and destructures with empty comma" do + for i, in [[1,2]] + i.should == 1 + end + end + + it "iterates over a list of arrays and destructures with an empty splat" do + for i, * in [[1,2]] + i.should == 1 + end + end + + it "iterates over a list of arrays and destructures with a splat" do + for i, *j in [[1,2]] + i.should == 1 + j.should == [2] + end + end + + it "iterates over a list of arrays and destructures with a splat and additional targets" do + for i, *j, k in [[1,2,3,4]] + i.should == 1 + j.should == [2,3] + k.should == 4 + end + end + + it "iterates over an Hash passing each key-value pair to the block" do + k = 0 + l = 0 + + for i, j in { 1 => 10, 2 => 20 } + k += i + l += j + end + + k.should == 3 + l.should == 30 + end + + it "iterates over any object responding to 'each'" do + obj = Object.new + def obj.each + (0..10).each { |i| yield i } + end + + j = 0 + for i in obj + j += i + end + j.should == 55 + end + + it "allows an instance variable as an iterator name" do + m = [1,2,3] + n = 0 + for @var in m + n += 1 + end + @var.should == 3 + n.should == 3 + end + + it "allows a class variable as an iterator name" do + class OFor + m = [1,2,3] + n = 0 + for @@var in m + n += 1 + end + @@var.should == 3 + n.should == 3 + end + end + + it "allows a constant as an iterator name" do + class OFor + m = [1,2,3] + n = 0 + -> { + for CONST in m + n += 1 + end + }.should complain(/already initialized constant/) + CONST.should == 3 + n.should == 3 + end + end + + it "allows a global variable as an iterator name" do + old_global_var = $var + m = [1,2,3] + n = 0 + for $var in m + n += 1 + end + $var.should == 3 + n.should == 3 + $var = old_global_var + end + + it "allows an attribute as an iterator name" do + class OFor + attr_accessor :target + end + + ofor = OFor.new + m = [1,2,3] + n = 0 + for ofor.target in m + n += 1 + end + ofor.target.should == 3 + n.should == 3 + end + + it "allows an attribute with safe navigation as an iterator name" do + class OFor + attr_accessor :target + end + + ofor = OFor.new + m = [1,2,3] + n = 0 + eval <<~RUBY + for ofor&.target in m + n += 1 + end + RUBY + ofor.target.should == 3 + n.should == 3 + end + + it "allows an attribute with safe navigation on a nil base as an iterator name" do + ofor = nil + m = [1,2,3] + n = 0 + eval <<~RUBY + for ofor&.target in m + n += 1 + end + RUBY + ofor.should == nil + n.should == 3 + end + + it "allows an array index writer as an iterator name" do + arr = [:a, :b, :c] + m = [1,2,3] + n = 0 + for arr[1] in m + n += 1 + end + arr.should == [:a, 3, :c] + n.should == 3 + end + + it "allows a hash index writer as an iterator name" do + hash = { a: 10, b: 20, c: 30 } + m = [1,2,3] + n = 0 + for hash[:b] in m + n += 1 + end + hash.should == { a: 10, b: 3, c: 30 } + n.should == 3 + end + + # 1.9 behaviour verified by nobu in + # http://redmine.ruby-lang.org/issues/show/2053 + it "yields only as many values as there are arguments" do + class OFor + def each + [[1,2,3], [4,5,6]].each do |a| + yield(a[0],a[1],a[2]) + end + end + end + o = OFor.new + qs = [] + for q in o + qs << q + end + qs.should == [1, 4] + q.should == 4 + end + + it "optionally takes a 'do' after the expression" do + j = 0 + for i in 1..3 do + j += i + end + j.should == 6 + end + + it "allows body begin on the same line if do is used" do + j = 0 + for i in 1..3 do j += i + end + j.should == 6 + end + + it "declares iteration variables in the surrounding variable scope" do + for a, b in [[1,2]] + end + + a.should == 1 + b.should == 2 + end + + it "declares variables in the body in the surrounding variable scope" do + for i in 1..2 + a = 123 + end + + a.should == 123 + end + + it "declares variables in the body in the surrounding variable scope with 'do'" do + for i in 1..2 do + a = 123 + end + + a.should == 123 + end + + it "declares variables inside a block as normal" do + for i in 1..2 do + proc { + inside_proc = 42 + }.call + end + local_variables.should == [:i] + end + + it "declares variables inside a lambda as normal" do + for i in 1..2 do + -> { + inside_proc = 42 + }.call + end + local_variables.should == [:i] + end + + it "can be nested" do + for a in [6] + for b in [7] + c = a * b + end + end + local_variables.sort.should == [:a, :b, :c] + c.should == 42 + end + + it "can be nested with blocks in between" do + # This is an edge case spec for Ruby implementations which have + # their own runtime scope per for loop body (like YARV and TruffleRuby) + for a in [1] + a1 = a + a1.should == a + for b in [2] + b1 = b + a1.should == a + b1.should == b + proc { + inside_proc = 42 + + a1.should == a + b1.should == b + inside_proc.should == 42 + + for c in [3].map { |enum_var| + a1.should == a + b1.should == b + inside_proc.should == 42 + enum_var + } + c1 = c + + a1.should == a + b1.should == b + c1.should == c + inside_proc.should == 42 + + for d in [4] + d1 = d + + a1.should == a + b1.should == b + c1.should == c + d1.should == d + inside_proc.should == 42 + end + end + local_variables.sort.should == [:a, :a1, :b, :b1, :c, :c1, :d, :d1, :inside_proc] + }.call + end + end + local_variables.sort.should == [:a, :a1, :b, :b1] + end + + it "can be nested with forward arguments" do + def bar(*args) + args + end + + def foo(...) + for a in [1] + r = bar(...) + end + r + end + + foo(2, 3).should == [2, 3] + end + + it "does not try to access variables outside the method" do + ForSpecs::ForInClassMethod.foo.should == [:bar, :baz] + ForSpecs::ForInClassMethod::READER.call.should == :same_variable_set_outside + end + + it "returns expr" do + for i in 1..3; end.should == (1..3) + for i,j in { 1 => 10, 2 => 20 }; end.should == { 1 => 10, 2 => 20 } + end + + it "breaks out of a loop upon 'break', returning nil" do + j = 0 + for i in 1..3 + j += i + + break if i == 2 + end.should == nil + + j.should == 3 + end + + it "allows 'break' to have an argument which becomes the value of the for expression" do + for i in 1..3 + break 10 if i == 2 + end.should == 10 + end + + it "starts the next iteration with 'next'" do + j = 0 + for i in 1..5 + next if i == 2 + + j += i + end + + j.should == 13 + end + + it "repeats current iteration with 'redo'" do + j = 0 + for i in 1..3 + j += i + + redo if i == 2 && j < 4 + end + + j.should == 8 + end +end |
