diff options
Diffstat (limited to 'test/ruby/test_shapes.rb')
| -rw-r--r-- | test/ruby/test_shapes.rb | 387 |
1 files changed, 320 insertions, 67 deletions
diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb index 0c1d8d424e..ef5dbd9fb1 100644 --- a/test/ruby/test_shapes.rb +++ b/test/ruby/test_shapes.rb @@ -2,10 +2,11 @@ require 'test/unit' require 'objspace' require 'json' +require 'securerandom' # These test the functionality of object shapes class TestShapes < Test::Unit::TestCase - MANY_IVS = 80 + MANY_IVS = RubyVM::Shape::SHAPE_MAX_FIELDS + 1 class IVOrder def expected_ivs @@ -92,15 +93,18 @@ class TestShapes < Test::Unit::TestCase # RubyVM::Shape.of returns new instances of shape objects for # each call. This helper method allows us to define equality for # shapes - def assert_shape_equal(shape1, shape2) - assert_equal(shape1.id, shape2.id) - assert_equal(shape1.parent_id, shape2.parent_id) - assert_equal(shape1.depth, shape2.depth) - assert_equal(shape1.type, shape2.type) + def assert_shape_equal(e, a) + assert_equal( + {id: e.offset, parent_offset: e.parent_offset, depth: e.depth, type: e.type, name: e.edge_name}, + {id: a.offset, parent_offset: a.parent_offset, depth: a.depth, type: a.type, name: e.edge_name}, + ) end - def refute_shape_equal(shape1, shape2) - refute_equal(shape1.id, shape2.id) + def refute_shape_equal(e, a) + refute_equal( + {id: e.offset, parent_offset: e.parent_offset, depth: e.depth, type: e.type, name: e.edge_name}, + {id: a.offset, parent_offset: a.parent_offset, depth: a.depth, type: a.type, name: e.edge_name}, + ) end def test_iv_order_correct_on_complex_objects @@ -113,12 +117,12 @@ class TestShapes < Test::Unit::TestCase assert_equal obj.expected_ivs, iv_list.map(&:to_s) end - def test_too_complex + def test_complex ensure_complex tc = TooComplex.new tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? end def test_ordered_alloc_is_not_complex @@ -127,6 +131,48 @@ class TestShapes < Test::Unit::TestCase assert_operator obj["variation_count"], :<, RubyVM::Shape::SHAPE_MAX_VARIATIONS end + def test_max_iv_count + klass = Class.new + object = klass.new + + assert_equal 0, RubyVM::Shape.class_max_iv_count(klass) + 8.times do |i| + object.instance_variable_set("@ivar_#{i}", i) + end + assert_equal 8, RubyVM::Shape.class_max_iv_count(klass) + + subklass = Class.new(klass) + assert_equal 8, RubyVM::Shape.class_max_iv_count(subklass) + end + + def test_max_iv_count_on_Object + object = Object.new + + assert_equal 0, RubyVM::Shape.class_max_iv_count(Object) + 8.times do |i| + object.instance_variable_set("@ivar_#{i}", i) + end + assert_equal 0, RubyVM::Shape.class_max_iv_count(Object) + end + + def test_max_iv_count_on_BasicObject + object = BasicObject.new + + assert_equal 0, RubyVM::Shape.class_max_iv_count(BasicObject) + 8.times do |i| + Object.instance_method(:instance_variable_set).bind_call(object, "@ivar_#{i}", i) + end + assert_equal 0, RubyVM::Shape.class_max_iv_count(BasicObject) + + subklass = Class.new(BasicObject) + object = subklass.new + assert_equal 0, RubyVM::Shape.class_max_iv_count(subklass) + 8.times do |i| + Object.instance_method(:instance_variable_set).bind_call(object, "@ivar_#{i}", i) + end + assert_equal 8, RubyVM::Shape.class_max_iv_count(subklass) + end + def test_too_many_ivs_on_obj assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; @@ -139,18 +185,21 @@ class TestShapes < Test::Unit::TestCase obj.instance_variable_set(:@c, 1) obj.instance_variable_set(:@d, 1) - assert_predicate RubyVM::Shape.of(obj), :too_complex? + assert_predicate RubyVM::Shape.of(obj), :complex? end; end def test_too_many_ivs_on_class obj = Class.new - (MANY_IVS + 1).times do + obj.instance_variable_set(:@test_too_many_ivs_on_class, 1) + refute_predicate RubyVM::Shape.of(obj), :complex? + + MANY_IVS.times do obj.instance_variable_set(:"@a#{_1}", 1) end - assert_false RubyVM::Shape.of(obj).too_complex? + assert_predicate RubyVM::Shape.of(obj), :complex? end def test_removing_when_too_many_ivs_on_class @@ -179,7 +228,7 @@ class TestShapes < Test::Unit::TestCase assert_empty obj.instance_variables end - def test_too_complex_geniv + def test_complex_geniv assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; class TooComplex < Hash @@ -221,7 +270,7 @@ class TestShapes < Test::Unit::TestCase end def test_run_out_of_shape_for_object - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; class A def initialize @@ -332,7 +381,7 @@ class TestShapes < Test::Unit::TestCase end def test_gc_stress_during_evacuate_generic_ivar - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; [].instance_variable_set(:@a, 1) @@ -500,7 +549,7 @@ class TestShapes < Test::Unit::TestCase end def test_run_out_of_shape_rb_obj_copy_ivar - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; class A def initialize @@ -579,7 +628,7 @@ class TestShapes < Test::Unit::TestCase end; end - def test_too_complex_ractor + def test_complex_ractor assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; $VERBOSE = nil @@ -594,14 +643,14 @@ class TestShapes < Test::Unit::TestCase tc = TooComplex.new tc.instance_variable_set(:"@very_unique", 3) - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? assert_equal 3, tc.very_unique - assert_equal 3, Ractor.new(tc) { |x| Ractor.yield(x.very_unique) }.take - assert_equal tc.instance_variables.sort, Ractor.new(tc) { |x| Ractor.yield(x.instance_variables) }.take.sort + assert_equal 3, Ractor.new(tc) { |x| x.very_unique }.value + assert_equal tc.instance_variables.sort, Ractor.new(tc) { |x| x.instance_variables }.value.sort end; end - def test_too_complex_ractor_shareable + def test_complex_ractor_shareable assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; $VERBOSE = nil @@ -616,13 +665,104 @@ class TestShapes < Test::Unit::TestCase tc = TooComplex.new tc.instance_variable_set(:"@very_unique", 3) - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? assert_equal 3, tc.very_unique assert_equal 3, Ractor.make_shareable(tc).very_unique end; end - def test_too_complex_obj_ivar_ractor_share + def test_complex_and_frozen + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + class TooComplex + attr_reader :very_unique + end + + RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do + TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new) + end + + tc = TooComplex.new + tc.instance_variable_set(:"@very_unique", 3) + + shape = RubyVM::Shape.of(tc) + assert_predicate shape, :complex? + refute_predicate shape, :shape_frozen? + tc.freeze + frozen_shape = RubyVM::Shape.of(tc) + refute_equal shape.id, frozen_shape.id + assert_predicate frozen_shape, :complex? + assert_predicate frozen_shape, :shape_frozen? + + assert_equal 3, tc.very_unique + assert_equal 3, Ractor.make_shareable(tc).very_unique + end; + end + + def test_object_id_transition_complex + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + obj = Object.new + obj.instance_variable_set(:@a, 1) + RubyVM::Shape.exhaust_shapes + assert_equal obj.object_id, obj.object_id + end; + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Hi; end + obj = Hi.new + obj.instance_variable_set(:@a, 1) + obj.instance_variable_set(:@b, 2) + old_id = obj.object_id + + RubyVM::Shape.exhaust_shapes + obj.remove_instance_variable(:@a) + + assert_equal old_id, obj.object_id + end; + end + + def test_complex_and_frozen_and_object_id + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + class TooComplex + attr_reader :very_unique + end + + RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do + TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new) + end + + tc = TooComplex.new + tc.instance_variable_set(:"@very_unique", 3) + + shape = RubyVM::Shape.of(tc) + assert_predicate shape, :complex? + refute_predicate shape, :shape_frozen? + tc.freeze + frozen_shape = RubyVM::Shape.of(tc) + refute_equal shape.id, frozen_shape.id + assert_predicate frozen_shape, :complex? + assert_predicate frozen_shape, :shape_frozen? + refute_predicate frozen_shape, :has_object_id? + + assert_equal tc.object_id, tc.object_id + + id_shape = RubyVM::Shape.of(tc) + refute_equal frozen_shape.id, id_shape.id + assert_predicate id_shape, :complex? + assert_predicate id_shape, :has_object_id? + assert_predicate id_shape, :shape_frozen? + + assert_equal 3, tc.very_unique + assert_equal 3, Ractor.make_shareable(tc).very_unique + end; + end + + def test_complex_obj_ivar_ractor_share assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; $VERBOSE = nil @@ -632,15 +772,15 @@ class TestShapes < Test::Unit::TestCase r = Ractor.new do o = Object.new o.instance_variable_set(:@a, "hello") - Ractor.yield(o) + o end - o = r.take + o = r.value assert_equal "hello", o.instance_variable_get(:@a) end; end - def test_too_complex_generic_ivar_ractor_share + def test_complex_generic_ivar_ractor_share assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; $VERBOSE = nil @@ -650,10 +790,10 @@ class TestShapes < Test::Unit::TestCase r = Ractor.new do o = [] o.instance_variable_set(:@a, "hello") - Ractor.yield(o) + o end - o = r.take + o = r.value assert_equal "hello", o.instance_variable_get(:@a) end; end @@ -663,7 +803,7 @@ class TestShapes < Test::Unit::TestCase tc = TooComplex.new tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? assert_equal 3, tc.a3_m end @@ -672,7 +812,7 @@ class TestShapes < Test::Unit::TestCase tc = TooComplex.new tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? assert_equal 3, tc.a3_m assert_equal 3, tc.a3 end @@ -682,7 +822,7 @@ class TestShapes < Test::Unit::TestCase tc = TooComplex.new tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? tc.write_iv_method tc.write_iv_method assert_equal 12345, tc.a3_m @@ -694,7 +834,7 @@ class TestShapes < Test::Unit::TestCase tc = TooComplex.new tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? tc.write_iv tc.write_iv assert_equal 12345, tc.a3_m @@ -706,7 +846,7 @@ class TestShapes < Test::Unit::TestCase tc = TooComplex.new tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? assert_equal 3, tc.a3_m assert_equal 3, tc.instance_variable_get(:@a3) end @@ -716,20 +856,53 @@ class TestShapes < Test::Unit::TestCase tc = TooComplex.new tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? assert_equal 3, tc.a3_m # make sure IV is initialized assert tc.instance_variable_defined?(:@a3) tc.remove_instance_variable(:@a3) + refute tc.instance_variable_defined?(:@a3) + assert_nil tc.a3 + end + + def test_delete_iv_after_complex_and_object_id + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :complex? + + assert_equal 3, tc.a3_m # make sure IV is initialized + assert tc.instance_variable_defined?(:@a3) + tc.object_id + tc.remove_instance_variable(:@a3) + refute tc.instance_variable_defined?(:@a3) assert_nil tc.a3 end + def test_delete_iv_after_complex_and_freeze + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :complex? + + assert_equal 3, tc.a3_m # make sure IV is initialized + assert tc.instance_variable_defined?(:@a3) + tc.freeze + assert_raise FrozenError do + tc.remove_instance_variable(:@a3) + end + assert tc.instance_variable_defined?(:@a3) + assert_equal 3, tc.a3 + end + def test_delete_undefined_after_complex ensure_complex tc = TooComplex.new tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? refute tc.instance_variable_defined?(:@a3) assert_raise(NameError) do @@ -786,13 +959,15 @@ class TestShapes < Test::Unit::TestCase def test_remove_instance_variable_capacity_transition assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; - t_object_shape = RubyVM::Shape.find_by_id(RubyVM::Shape::FIRST_T_OBJECT_SHAPE_ID) - assert_equal(RubyVM::Shape::SHAPE_T_OBJECT, t_object_shape.type) - - initial_capacity = t_object_shape.capacity # a does not transition in capacity a = Class.new.new + root_shape = RubyVM::Shape.of(a) + + assert_equal(RubyVM::Shape::SHAPE_ROOT, root_shape.type) + initial_capacity = root_shape.capacity + refute_equal(0, initial_capacity) + initial_capacity.times do |i| a.instance_variable_set(:"@ivar#{i + 1}", i) end @@ -821,11 +996,11 @@ class TestShapes < Test::Unit::TestCase tc = TooComplex.new tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? tc.freeze assert_raise(FrozenError) { tc.a3_m } # doesn't transition to frozen shape in this case - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? end def test_read_undefined_iv_after_complex @@ -833,9 +1008,9 @@ class TestShapes < Test::Unit::TestCase tc = TooComplex.new tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? assert_equal nil, tc.iv_not_defined - assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_predicate RubyVM::Shape.of(tc), :complex? end def test_shape_order @@ -852,13 +1027,13 @@ class TestShapes < Test::Unit::TestCase def test_iv_index example = RemoveAndAdd.new initial_shape = RubyVM::Shape.of(example) - assert_equal 0, initial_shape.next_iv_index + assert_equal 0, initial_shape.next_field_index example.add_foo # makes a transition add_foo_shape = RubyVM::Shape.of(example) assert_equal([:@foo], example.instance_variables) - assert_equal(initial_shape.id, add_foo_shape.parent.id) - assert_equal(1, add_foo_shape.next_iv_index) + assert_equal(initial_shape.offset, add_foo_shape.parent.offset) + assert_equal(1, add_foo_shape.next_field_index) example.remove_foo # makes a transition remove_foo_shape = RubyVM::Shape.of(example) @@ -868,8 +1043,8 @@ class TestShapes < Test::Unit::TestCase example.add_bar # makes a transition bar_shape = RubyVM::Shape.of(example) assert_equal([:@bar], example.instance_variables) - assert_equal(initial_shape.id, bar_shape.parent_id) - assert_equal(1, bar_shape.next_iv_index) + assert_equal(initial_shape.offset, bar_shape.parent_offset) + assert_equal(1, bar_shape.next_field_index) end def test_remove_then_add_again @@ -888,7 +1063,7 @@ class TestShapes < Test::Unit::TestCase def test_new_obj_has_t_object_shape obj = TestObject.new shape = RubyVM::Shape.of(obj) - assert_equal RubyVM::Shape::SHAPE_T_OBJECT, shape.type + assert_equal RubyVM::Shape::SHAPE_ROOT, shape.type assert_nil shape.parent end @@ -900,16 +1075,29 @@ class TestShapes < Test::Unit::TestCase assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of([])) end - def test_true_has_special_const_shape_id - assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(true).id) - end - - def test_nil_has_special_const_shape_id - assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(nil).id) + def test_raise_on_special_consts + assert_raise ArgumentError do + RubyVM::Shape.of(true) + end + assert_raise ArgumentError do + RubyVM::Shape.of(false) + end + assert_raise ArgumentError do + RubyVM::Shape.of(nil) + end + assert_raise ArgumentError do + RubyVM::Shape.of(0) + end + # 32-bit platforms don't have flonums or static symbols as special + # constants + # TODO(max): Add ArgumentError tests for symbol and flonum, skipping if + # RUBY_PLATFORM =~ /i686/ end - def test_root_shape_transition_to_special_const_on_frozen - assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of([].freeze).id) + def test_root_shape_frozen + frozen_root_shape = RubyVM::Shape.of([].freeze) + assert_predicate(frozen_root_shape, :frozen?) + assert_equal(RubyVM::Shape.root_shape.id, frozen_root_shape.offset) end def test_basic_shape_transition @@ -920,7 +1108,7 @@ class TestShapes < Test::Unit::TestCase assert_equal RubyVM::Shape::SHAPE_IVAR, shape.type shape = shape.parent - assert_equal RubyVM::Shape::SHAPE_T_OBJECT, shape.type + assert_equal RubyVM::Shape::SHAPE_ROOT, shape.type assert_nil shape.parent assert_equal(1, obj.instance_variable_get(:@a)) @@ -940,7 +1128,7 @@ class TestShapes < Test::Unit::TestCase assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) end - def test_duplicating_too_complex_objects_memory_leak + def test_duplicating_complex_objects_memory_leak assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", "[Bug #20162]", rss: true) RubyVM::Shape.exhaust_shapes @@ -955,18 +1143,19 @@ class TestShapes < Test::Unit::TestCase def test_freezing_and_duplicating_object obj = Object.new.freeze + assert_predicate(RubyVM::Shape.of(obj), :shape_frozen?) + + # dup'd objects shouldn't be frozen obj2 = obj.dup refute_predicate(obj2, :frozen?) - # dup'd objects shouldn't be frozen, and the shape should be the - # parent shape of the copied object - assert_equal(RubyVM::Shape.of(obj).parent.id, RubyVM::Shape.of(obj2).id) + refute_predicate(RubyVM::Shape.of(obj2), :shape_frozen?) end def test_freezing_and_duplicating_object_with_ivars obj = Example.new.freeze obj2 = obj.dup refute_predicate(obj2, :frozen?) - refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) + refute_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) assert_equal(obj2.instance_variable_get(:@a), 1) end @@ -976,6 +1165,7 @@ class TestShapes < Test::Unit::TestCase str.freeze str2 = str.dup refute_predicate(str2, :frozen?) + refute_equal(RubyVM::Shape.of(str).id, RubyVM::Shape.of(str2).id) assert_equal(str2.instance_variable_get(:@a), 1) end @@ -991,9 +1181,8 @@ class TestShapes < Test::Unit::TestCase obj = Object.new obj2 = obj.clone(freeze: true) assert_predicate(obj2, :frozen?) - refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) - assert_equal(RubyVM::Shape::SHAPE_FROZEN, RubyVM::Shape.of(obj2).type) - assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2).parent) + refute_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) + assert_predicate(RubyVM::Shape.of(obj2), :shape_frozen?) end def test_freezing_and_cloning_object_with_ivars @@ -1036,4 +1225,68 @@ class TestShapes < Test::Unit::TestCase tc.send("a#{_1}_m") end end + + def assert_complex_during_delete(obj) + obj.instance_variable_set("@___#{SecureRandom.hex}", 1) + + (RubyVM::Shape::SHAPE_MAX_VARIATIONS * 2).times do |i| + obj.instance_variable_set("@ivar#{i}", i) + end + + refute_predicate RubyVM::Shape.of(obj), :complex? + (RubyVM::Shape::SHAPE_MAX_VARIATIONS * 2).times do |i| + obj.remove_instance_variable("@ivar#{i}") + end + assert_predicate RubyVM::Shape.of(obj), :complex? + end + + def test_object_complex_during_delete + assert_complex_during_delete(Class.new.new) + end + + def test_class_complex_during_delete + assert_complex_during_delete(Module.new) + end + + def test_generic_complex_during_delete + assert_complex_during_delete(Class.new(Array).new) + end + + def assert_complex_max_fields(obj) + extra_fields = RubyVM::Shape::SHAPE_MAX_FIELDS - obj.instance_variables.size + extra_fields.times do |i| + obj.instance_variable_set("@camel_ivar#{i}", i) + end + refute_predicate RubyVM::Shape.of(obj), :complex? + obj.instance_variable_set("@camel_straw", true) + assert_predicate RubyVM::Shape.of(obj), :complex? + end + + def test_max_fields_complex + assert_complex_max_fields(Class.new(Object).new) + end + + def test_generic_max_fields_complex + assert_complex_max_fields(Class.new(Array).new) + end + + def test_class_max_fields_complex + assert_complex_max_fields(Class.new(Module).new) + end + + def test_max_initial_fields + klass = Class.new + init_ivars = (RubyVM::Shape::SHAPE_MAX_FIELDS + 1).times.map { |i| "@ivar_#{i} = #{i}" } + klass.class_eval(<<~RUBY) + def initialize(init = false) + if init + #{init_ivars.join(";")} + end + end + RUBY + assert_predicate RubyVM::Shape.of(klass.new), :complex? + assert_predicate RubyVM::Shape.of(klass.new.dup), :complex? + assert_predicate RubyVM::Shape.of(klass.new(true)), :complex? + assert_predicate RubyVM::Shape.of(klass.new(true).dup), :complex? + end end if defined?(RubyVM::Shape) |
