diff options
Diffstat (limited to 'test/ruby/test_enum.rb')
| -rw-r--r-- | test/ruby/test_enum.rb | 457 |
1 files changed, 435 insertions, 22 deletions
diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb index 27d406680a..237bdc8a4d 100644 --- a/test/ruby/test_enum.rb +++ b/test/ruby/test_enum.rb @@ -27,7 +27,6 @@ class TestEnumerable < Test::Unit::TestCase end end @verbose = $VERBOSE - $VERBOSE = nil end def teardown @@ -63,11 +62,32 @@ class TestEnumerable < Test::Unit::TestCase assert_equal([[2, 1], [2, 4]], a) end + def test_grep_optimization + bug17030 = '[ruby-core:99156]' + 'set last match' =~ /set last (.*)/ + assert_equal([:a, 'b', :c], [:a, 'b', 'z', :c, 42, nil].grep(/[a-d]/), bug17030) + assert_equal(['z', 42, nil], [:a, 'b', 'z', :c, 42, nil].grep_v(/[a-d]/), bug17030) + assert_equal('match', $1, bug17030) + + regexp = Regexp.new('x') + assert_equal([], @obj.grep(regexp), bug17030) # sanity check + def regexp.===(other) + true + end + assert_equal([1, 2, 3, 1, 2], @obj.grep(regexp), bug17030) + + o = Object.new + def o.to_str + 'hello' + end + assert_same(o, [o].grep(/ll/).first, bug17030) + end + def test_count assert_equal(5, @obj.count) assert_equal(2, @obj.count(1)) assert_equal(3, @obj.count {|x| x % 2 == 1 }) - assert_equal(2, @obj.count(1) {|x| x % 2 == 1 }) + assert_equal(2, assert_warning(/given block not used/) {@obj.count(1) {|x| x % 2 == 1 }}) assert_raise(ArgumentError) { @obj.count(0, 1) } if RUBY_ENGINE == "ruby" @@ -95,7 +115,7 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(1, @obj.find_index {|x| x % 2 == 0 }) assert_equal(nil, @obj.find_index {|x| false }) assert_raise(ArgumentError) { @obj.find_index(0, 1) } - assert_equal(1, @obj.find_index(2) {|x| x == 1 }) + assert_equal(1, assert_warning(/given block not used/) {@obj.find_index(2) {|x| x == 1 }}) end def test_find_all @@ -114,6 +134,12 @@ class TestEnumerable < Test::Unit::TestCase assert_equal([1, 2, 3, 1, 2], @obj.to_a) end + def test_to_a_keywords + @obj.singleton_class.remove_method(:each) + def @obj.each(foo:) yield foo end + assert_equal([1], @obj.to_a(foo: 1)) + end + def test_to_a_size_symbol sym = Object.new class << sym @@ -144,8 +170,7 @@ class TestEnumerable < Test::Unit::TestCase assert_equal([], inf.to_a) end - def test_to_h - obj = Object.new + StubToH = Object.new.tap do |obj| def obj.each(*args) yield(*args) yield [:key, :value] @@ -157,6 +182,12 @@ class TestEnumerable < Test::Unit::TestCase yield kvp end obj.extend Enumerable + obj.freeze + end + + def test_to_h + obj = StubToH + assert_equal({ :hello => :world, :key => :value, @@ -175,13 +206,36 @@ class TestEnumerable < Test::Unit::TestCase assert_equal "element has wrong array length (expected 2, was 1)", e.message end + def test_to_h_block + obj = StubToH + + assert_equal({ + "hello" => "world", + "key" => "value", + "other_key" => "other_value", + "obtained" => "via_to_ary", + }, obj.to_h(:hello, :world) {|k, v| [k.to_s, v.to_s]}) + + e = assert_raise(TypeError) { + obj.to_h {:not_an_array} + } + assert_equal "wrong element type Symbol (expected array)", e.message + + e = assert_raise(ArgumentError) { + obj.to_h {[1]} + } + assert_equal "element has wrong array length (expected 2, was 1)", e.message + end + def test_inject assert_equal(12, @obj.inject {|z, x| z * x }) assert_equal(48, @obj.inject {|z, x| z * 2 + x }) assert_equal(12, @obj.inject(:*)) assert_equal(24, @obj.inject(2) {|z, x| z * x }) - assert_equal(24, @obj.inject(2, :*) {|z, x| z * x }) + assert_equal(24, assert_warning(/given block not used/) {@obj.inject(2, :*) {|z, x| z * x }}) assert_equal(nil, @empty.inject() {9}) + + assert_raise(ArgumentError) {@obj.inject} end FIXNUM_MIN = RbConfig::LIMITS['FIXNUM_MIN'] @@ -196,37 +250,137 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(105, [5, 7].inject(3, :*)) end - def assert_float_equal(e, v, msg=nil) - assert_equal(Float, v.class, msg) - assert_equal(e, v, msg) - end - def test_inject_array_plus assert_equal(3, [3].inject(:+)) assert_equal(8, [3, 5].inject(:+)) assert_equal(15, [3, 5, 7].inject(:+)) assert_float_equal(15.0, [3, 5, 7.0].inject(:+)) assert_equal(2*FIXNUM_MAX, Array.new(2, FIXNUM_MAX).inject(:+)) + assert_equal(3*FIXNUM_MAX, Array.new(3, FIXNUM_MAX).inject(:+)) assert_equal(2*(FIXNUM_MAX+1), Array.new(2, FIXNUM_MAX+1).inject(:+)) assert_equal(10*FIXNUM_MAX, Array.new(10, FIXNUM_MAX).inject(:+)) assert_equal(0, ([FIXNUM_MAX, 1, -FIXNUM_MAX, -1]*10).inject(:+)) assert_equal(FIXNUM_MAX*10, ([FIXNUM_MAX+1, -1]*10).inject(:+)) assert_equal(2*FIXNUM_MIN, Array.new(2, FIXNUM_MIN).inject(:+)) + assert_equal(3*FIXNUM_MIN, Array.new(3, FIXNUM_MIN).inject(:+)) assert_equal((FIXNUM_MAX+1).to_f, [FIXNUM_MAX, 1, 0.0].inject(:+)) assert_float_equal(10.0, [3.0, 5].inject(2.0, :+)) assert_float_equal((FIXNUM_MAX+1).to_f, [0.0, FIXNUM_MAX+1].inject(:+)) assert_equal(2.0+3.0i, [2.0, 3.0i].inject(:+)) end - def test_inject_array_plus_redefined - assert_separately([], <<-"end;") - class Integer - undef :+ - def +(x) - 0 + def test_inject_op_redefined + assert_separately([], "#{<<~"end;"}\n""end") + k = Class.new do + include Enumerable + def each + yield 1 + yield 2 + yield 3 + end + end + all_assertions_foreach("", *%i[+ * / - %]) do |op| + bug = '[ruby-dev:49510] [Bug#12178] should respect redefinition' + begin + Integer.class_eval do + alias_method :orig, op + define_method(op) do |x| + 0 + end + end + assert_equal(0, k.new.inject(op), bug) + ensure + Integer.class_eval do + undef_method op + alias_method op, :orig end end - assert_equal(0, [1,2,3].inject(:+), "[ruby-dev:49510] [Bug#12178]") + end; + end + + def test_inject_op_private + assert_separately([], "#{<<~"end;"}\n""end") + k = Class.new do + include Enumerable + def each + yield 1 + yield 2 + yield 3 + end + end + all_assertions_foreach("", *%i[+ * / - %]) do |op| + bug = '[ruby-core:81349] [Bug #13592] should respect visibility' + assert_raise_with_message(NoMethodError, /private method/, bug) do + begin + Integer.class_eval do + private op + end + k.new.inject(op) + ensure + Integer.class_eval do + public op + end + end + end + end; + end + + def test_inject_array_op_redefined + assert_separately([], "#{<<~"end;"}\n""end") + all_assertions_foreach("", *%i[+ * / - %]) do |op| + bug = '[ruby-dev:49510] [Bug#12178] should respect redefinition' + begin + Integer.class_eval do + alias_method :orig, op + define_method(op) do |x| + 0 + end + end + assert_equal(0, [1,2,3].inject(op), bug) + ensure + Integer.class_eval do + undef_method op + alias_method op, :orig + end + end + end; + end + + def test_inject_array_op_private + assert_separately([], "#{<<~"end;"}\n""end") + all_assertions_foreach("", *%i[+ * / - %]) do |op| + bug = '[ruby-core:81349] [Bug #13592] should respect visibility' + assert_raise_with_message(NoMethodError, /private method/, bug) do + begin + Integer.class_eval do + private op + end + [1,2,3].inject(op) + ensure + Integer.class_eval do + public op + end + end + end + end; + end + + def test_refine_Enumerable_then_include + assert_separately([], "#{<<~"end;"}\n") + module RefinementBug + refine Enumerable do + def refined_method + :rm + end + end + end + using RefinementBug + + class A + include Enumerable + end + + assert_equal(:rm, [].refined_method) end; end @@ -245,6 +399,50 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(h, @obj.each_with_index.group_by(&cond)) end + def test_tally + h = {1 => 2, 2 => 2, 3 => 1} + assert_equal(h, @obj.tally) + + h = {1 => 5, 2 => 2, 3 => 1, 4 => "x"} + assert_equal(h, @obj.tally({1 => 3, 4 => "x"})) + + assert_raise(TypeError) do + @obj.tally({1 => ""}) + end + + h = {1 => 2, 2 => 2, 3 => 1} + assert_same(h, @obj.tally(h)) + + h = {1 => 2, 2 => 2, 3 => 1}.freeze + assert_raise(FrozenError) do + @obj.tally(h) + end + assert_equal({1 => 2, 2 => 2, 3 => 1}, h) + + hash_convertible = Object.new + def hash_convertible.to_hash + {1 => 3, 4 => "x"} + end + assert_equal({1 => 5, 2 => 2, 3 => 1, 4 => "x"}, @obj.tally(hash_convertible)) + + hash_convertible = Object.new + def hash_convertible.to_hash + {1 => 3, 4 => "x"}.freeze + end + assert_raise(FrozenError) do + @obj.tally(hash_convertible) + end + assert_equal({1 => 3, 4 => "x"}, hash_convertible.to_hash) + + assert_raise(TypeError) do + @obj.tally(BasicObject.new) + end + + h = {1 => 2, 2 => 2, 3 => 1} + assert_equal(h, @obj.tally(Hash.new(100))) + assert_equal(h, @obj.tally(Hash.new {100})) + end + def test_first assert_equal(1, @obj.first) assert_equal([1, 2, 3], @obj.first(3)) @@ -265,6 +463,17 @@ class TestEnumerable < Test::Unit::TestCase empty.first empty.block.call end; + + bug18475 = '[ruby-dev:107059]' + assert_in_out_err([], <<-'end;', [], /unexpected break/, bug18475) + e = Enumerator.new do |g| + Thread.new do + g << 1 + end.join + end + + e.first + end; end def test_sort @@ -287,6 +496,23 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(false, [true, true, false].all?) assert_equal(true, [].all?) assert_equal(true, @empty.all?) + assert_equal(true, @obj.all?(Integer)) + assert_equal(false, @obj.all?(1..2)) + end + + def test_all_with_unused_block + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + [1, 2].all?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + (1..2).all?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + 3.times.all?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + {a: 1, b: 2}.all?([:b, 2]) {|x| x == 4 } + EOS end def test_any @@ -296,27 +522,73 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(false, [false, false, false].any?) assert_equal(false, [].any?) assert_equal(false, @empty.any?) + assert_equal(true, @obj.any?(1..2)) + assert_equal(false, @obj.any?(Float)) + assert_equal(false, [1, 42].any?(Float)) + assert_equal(true, [1, 4.2].any?(Float)) + assert_equal(false, {a: 1, b: 2}.any?(->(kv) { kv == [:foo, 42] })) + assert_equal(true, {a: 1, b: 2}.any?(->(kv) { kv == [:b, 2] })) + end + + def test_any_with_unused_block + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + [1, 23].any?(1) {|x| x == 1 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + (1..2).any?(34) {|x| x == 2 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + 3.times.any?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + {a: 1, b: 2}.any?([:b, 2]) {|x| x == 4 } + EOS end def test_one assert(@obj.one? {|x| x == 3 }) assert(!(@obj.one? {|x| x == 1 })) assert(!(@obj.one? {|x| x == 4 })) + assert(@obj.one?(3..4)) + assert(!(@obj.one?(1..2))) + assert(!(@obj.one?(4..5))) assert(%w{ant bear cat}.one? {|word| word.length == 4}) assert(!(%w{ant bear cat}.one? {|word| word.length > 4})) assert(!(%w{ant bear cat}.one? {|word| word.length < 4})) + assert(%w{ant bear cat}.one?(/b/)) + assert(!(%w{ant bear cat}.one?(/t/))) assert(!([ nil, true, 99 ].one?)) assert([ nil, true, false ].one?) assert(![].one?) assert(!@empty.one?) + assert([ nil, true, 99 ].one?(Integer)) + end + + def test_one_with_unused_block + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + [1, 2].one?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + (1..2).one?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + 3.times.one?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + {a: 1, b: 2}.one?([:b, 2]) {|x| x == 4 } + EOS end def test_none assert(@obj.none? {|x| x == 4 }) assert(!(@obj.none? {|x| x == 1 })) assert(!(@obj.none? {|x| x == 3 })) + assert(@obj.none?(4..5)) + assert(!(@obj.none?(1..3))) assert(%w{ant bear cat}.none? {|word| word.length == 5}) assert(!(%w{ant bear cat}.none? {|word| word.length >= 4})) + assert(%w{ant bear cat}.none?(/d/)) + assert(!(%w{ant bear cat}.none?(/b/))) assert([].none?) assert([nil].none?) assert([nil,false].none?) @@ -324,6 +596,21 @@ class TestEnumerable < Test::Unit::TestCase assert(@empty.none?) end + def test_none_with_unused_block + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + [1, 2].none?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + (1..2).none?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + 3.times.none?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + {a: 1, b: 2}.none?([:b, 2]) {|x| x == 4 } + EOS + end + def test_min assert_equal(1, @obj.min) assert_equal(3, @obj.min {|a,b| b <=> a }) @@ -463,6 +750,9 @@ class TestEnumerable < Test::Unit::TestCase ary.clear (1..10).each_slice(11) {|a| ary << a} assert_equal([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]], ary) + + assert_equal(1..10, (1..10).each_slice(3) { }) + assert_equal([], [].each_slice(3) { }) end def test_each_cons @@ -482,6 +772,9 @@ class TestEnumerable < Test::Unit::TestCase ary.clear (1..5).each_cons(6) {|a| ary << a} assert_empty(ary) + + assert_equal(1..5, (1..5).each_cons(3) { }) + assert_equal([], [].each_cons(3) { }) end def test_zip @@ -550,6 +843,8 @@ class TestEnumerable < Test::Unit::TestCase end def test_callcc + omit 'requires callcc support' unless respond_to?(:callcc) + assert_raise(RuntimeError) do c = nil @obj.sort_by {|x| callcc {|c2| c ||= c2 }; x } @@ -581,12 +876,41 @@ class TestEnumerable < Test::Unit::TestCase [o, o, o].sort_by {|x| x } c.call end + + assert_raise_with_message(RuntimeError, /reentered/) do + i = 0 + c = nil + o = Object.new + class << o; self; end.class_eval do + define_method(:<=>) do |x| + callcc {|c2| c ||= c2 } + i += 1 + 0 + end + end + [o, o].min(1) + assert_operator(i, :<=, 5, "infinite loop") + c.call + end end def test_reverse_each assert_equal([2,1,3,2,1], @obj.reverse_each.to_a) end + def test_reverse_each_memory_corruption + bug16354 = '[ruby-dev:50867]' + assert_normal_exit %q{ + size = 1000 + (0...size).reverse_each do |i| + i.inspect + ObjectSpace.each_object(Array) do |a| + a.clear if a.length == size + end + end + }, bug16354 + end + def test_chunk e = [].chunk {|elt| true } assert_equal([], e.to_a) @@ -798,6 +1122,10 @@ class TestEnumerable < Test::Unit::TestCase lambda2 = ->(x, i) { [x.upcase, i] } assert_equal([['A',0], ['B',1], ['C',2], ['D',3], ['E',4]], @obj.each_with_index.map(&lambda2)) + + hash = { a: 'hoge', b: 'fuga' } + lambda = -> (k, v) { "#{k}:#{v}" } + assert_equal ["a:hoge", "b:fuga"], hash.map(&lambda) end def test_flat_map @@ -905,12 +1233,23 @@ class TestEnumerable < Test::Unit::TestCase assert_float_equal(large_number+(small_number*11), [small_number, large_number/1r, *[small_number]*10].each.sum) assert_float_equal(small_number, [large_number, small_number, -large_number].each.sum) + k = Class.new do + include Enumerable + def initialize(*values) + @values = values + end + def each(&block) + @values.each(&block) + end + end + assert_equal(+Float::INFINITY, k.new(0.0, +Float::INFINITY).sum) + assert_equal(+Float::INFINITY, k.new(+Float::INFINITY, 0.0).sum) + assert_equal(-Float::INFINITY, k.new(0.0, -Float::INFINITY).sum) + assert_equal(-Float::INFINITY, k.new(-Float::INFINITY, 0.0).sum) + assert_predicate(k.new(-Float::INFINITY, Float::INFINITY).sum, :nan?) + assert_equal("abc", ["a", "b", "c"].each.sum("")) assert_equal([1, [2], 3], [[1], [[2]], [3]].each.sum([])) - - assert_separately(%w[-rmathn], <<-EOS, ignore_stderr: true) - assert_equal(6, [1r, 2, 3r].each.sum) - EOS end def test_hash_sum @@ -927,6 +1266,19 @@ class TestEnumerable < Test::Unit::TestCase assert_int_equal(5, (2..0).sum(5)) assert_int_equal(2, (2..2).sum) assert_int_equal(42, (2...2).sum(42)) + + not_a_range = Class.new do + include Enumerable # Defines the `#sum` method + def each + yield 2 + yield 4 + yield 6 + end + + def begin; end + def end; end + end + assert_equal(12, not_a_range.new.sum) end def test_uniq @@ -942,5 +1294,66 @@ class TestEnumerable < Test::Unit::TestCase assert_equal([[1896, "Athens"], [1900, "Paris"], [1904, "Chicago"], [1908, "Rome"]], olympics.uniq{|k,v| v}) assert_equal([1, 2, 3, 4, 5, 10], (1..100).uniq{|x| (x**2) % 10 }.first(6)) + assert_equal([1, [1, 2]], Foo.new.to_enum.uniq) + end + + def test_compact + class << (enum = Object.new) + include Enumerable + def each + yield 3 + yield nil + yield 7 + yield 9 + yield nil + end + end + + assert_equal([3, 7, 9], enum.compact) + end + + def test_transient_heap_sort_by + klass = Class.new do + include Comparable + attr_reader :i + def initialize e + @i = e + end + def <=> other + GC.start + i <=> other.i + end + end + assert_equal [1, 2, 3, 4, 5], (1..5).sort_by{|e| klass.new e} + end + + def test_filter_map + @obj = (1..8).to_a + assert_equal([4, 8, 12, 16], @obj.filter_map { |i| i * 2 if i.even? }) + assert_equal([2, 4, 6, 8, 10, 12, 14, 16], @obj.filter_map { |i| i * 2 }) + assert_equal([0, 0, 0, 0, 0, 0, 0, 0], @obj.filter_map { 0 }) + assert_equal([], @obj.filter_map { false }) + assert_equal([], @obj.filter_map { nil }) + assert_instance_of(Enumerator, @obj.filter_map) + end + + def test_ruby_svar + klass = Class.new do + include Enumerable + def each + %w(bar baz).each{|e| yield e} + end + end + svars = [] + klass.new.grep(/(b.)/) { svars << $1 } + assert_equal(["ba", "ba"], svars) + end + + def test_all_fast + data = { "key" => { "key2" => 1 } } + kk = vv = nil + data.all? { |(k, v)| kk, vv = k, v } + assert_equal(kk, "key") + assert_equal(vv, { "key2" => 1 }) end end |
