summaryrefslogtreecommitdiff
path: root/test/ruby/test_struct.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_struct.rb')
-rw-r--r--test/ruby/test_struct.rb461
1 files changed, 393 insertions, 68 deletions
diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb
index 49dcdb45b2..01e5cc68f6 100644
--- a/test/ruby/test_struct.rb
+++ b/test/ruby/test_struct.rb
@@ -1,10 +1,12 @@
+# -*- coding: us-ascii -*-
+# frozen_string_literal: false
require 'test/unit'
require 'timeout'
-class TestStruct < Test::Unit::TestCase
+module TestStruct
def test_struct
- struct_test = Struct.new("Test", :foo, :bar)
- assert_equal(Struct::Test, struct_test)
+ struct_test = @Struct.new("Test", :foo, :bar)
+ assert_equal(@Struct::Test, struct_test)
test = struct_test.new(1, 2)
assert_equal(1, test.foo)
@@ -21,13 +23,17 @@ class TestStruct < Test::Unit::TestCase
test.bar = 47
assert_equal(47, test.bar)
+
+ @Struct.class_eval do
+ remove_const :Test
+ end
end
# [ruby-dev:26247] more than 10 struct members causes segmentation fault
def test_morethan10members
list = %w( a b c d e f g h i j k l m n o p )
until list.empty?
- c = Struct.new(* list.map {|ch| ch.intern }).new
+ c = @Struct.new(* list.map {|ch| ch.intern }).new
list.each do |ch|
c.__send__(ch)
end
@@ -35,11 +41,19 @@ class TestStruct < Test::Unit::TestCase
end
end
+ def test_larger_than_largest_pool
+ count = (GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] / RbConfig::SIZEOF["void*"]) + 1
+ list = Array(0..count)
+ klass = @Struct.new(*list.map { |i| :"a_#{i}"})
+ struct = klass.new(*list)
+ assert_equal 0, struct.a_0
+ end
+
def test_small_structs
names = [:a, :b, :c, :d]
1.upto(4) {|n|
fields = names[0, n]
- klass = Struct.new(*fields)
+ klass = @Struct.new(*fields)
o = klass.new(*(0...n).to_a)
fields.each_with_index {|name, i|
assert_equal(i, o[name])
@@ -52,39 +66,32 @@ class TestStruct < Test::Unit::TestCase
end
def test_inherit
- klass = Struct.new(:a)
+ klass = @Struct.new(:a)
klass2 = Class.new(klass)
o = klass2.new(1)
assert_equal(1, o.a)
end
+ def test_attrset_id
+ assert_raise(ArgumentError) { Struct.new(:x=) }
+ end
+
def test_members
- klass = Struct.new(:a)
+ klass = @Struct.new(:a)
o = klass.new(1)
assert_equal([:a], klass.members)
assert_equal([:a], o.members)
end
def test_ref
- klass = Struct.new(:a)
+ klass = @Struct.new(:a)
o = klass.new(1)
assert_equal(1, o[:a])
assert_raise(NameError) { o[:b] }
end
- def test_modify
- klass = Struct.new(:a)
- o = klass.new(1)
- assert_raise(SecurityError) do
- Thread.new do
- $SAFE = 4
- o.a = 2
- end.value
- end
- end
-
def test_set
- klass = Struct.new(:a)
+ klass = @Struct.new(:a)
o = klass.new(1)
o[:a] = 2
assert_equal(2, o[:a])
@@ -92,161 +99,479 @@ class TestStruct < Test::Unit::TestCase
end
def test_struct_new
- assert_raise(NameError) { Struct.new("foo") }
- assert_nothing_raised { Struct.new("Foo") }
- Struct.instance_eval { remove_const(:Foo) }
- assert_nothing_raised { Struct.new(:a) { } }
- assert_raise(RuntimeError) { Struct.new(:a) { raise } }
+ assert_raise(NameError) { @Struct.new("foo") }
+ assert_nothing_raised { @Struct.new("Foo") }
+ @Struct.instance_eval { remove_const(:Foo) }
+ assert_nothing_raised { @Struct.new(:a) { } }
+ assert_raise(RuntimeError) { @Struct.new(:a) { raise } }
assert_equal([:utime, :stime, :cutime, :cstime], Process.times.members)
end
+ def test_struct_new_with_hash
+ assert_raise_with_message(TypeError, /not a symbol/) {Struct.new(:a, {})}
+ assert_raise_with_message(TypeError, /not a symbol/) {Struct.new(:a, {name: "b"})}
+ end
+
+ def test_struct_new_with_keyword_init
+ @Struct.new("KeywordInitTrue", :a, :b, keyword_init: true)
+ @Struct.new("KeywordInitFalse", :a, :b, keyword_init: false)
+
+ assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(1, 2) }
+ assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new({a: 100}, 2) }
+ assert_nothing_raised { @Struct::KeywordInitFalse.new(1, 2) }
+ assert_nothing_raised { @Struct::KeywordInitTrue.new(a: 1, b: 2) }
+ assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(1, b: 2) }
+ assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(a: 1, b: 2, c: 3) }
+ assert_equal @Struct::KeywordInitTrue.new(a: 1, b: 2).values, @Struct::KeywordInitFalse.new(1, 2).values
+ assert_equal "#{@Struct}::KeywordInitFalse", @Struct::KeywordInitFalse.inspect
+ assert_equal "#{@Struct}::KeywordInitTrue(keyword_init: true)", @Struct::KeywordInitTrue.inspect
+ # eval is needed to prevent the warning duplication filter
+ k = Class.new(@Struct::KeywordInitTrue) {def initialize(b, options); super(a: options, b: b); end}
+ o = assert_warn('') { k.new(42, {foo: 1, bar: 2}) }
+ assert_equal(1, o.a[:foo])
+
+ @Struct.instance_eval do
+ remove_const(:KeywordInitTrue)
+ remove_const(:KeywordInitFalse)
+ end
+ end
+
+ def test_struct_new_with_keyword_init_and_block
+ struct = @Struct.new(:a, :b, keyword_init: true) do
+ def c
+ a + b
+ end
+ end
+
+ assert_equal(3, struct.new(a: 1, b: 2).c)
+ end
+
+ def test_struct_keyword_init_p
+ struct = @Struct.new(:a, :b, keyword_init: true)
+ assert_equal(true, struct.keyword_init?)
+
+ struct = @Struct.new(:a, :b, keyword_init: false)
+ assert_equal(false, struct.keyword_init?)
+
+ struct = @Struct.new(:a, :b)
+ assert_nil(struct.keyword_init?)
+ end
+
def test_initialize
- klass = Struct.new(:a)
+ klass = @Struct.new(:a)
assert_raise(ArgumentError) { klass.new(1, 2) }
+ klass = @Struct.new(:total) do
+ def initialize(a, b)
+ super(a+b)
+ end
+ end
+ assert_equal 3, klass.new(1,2).total
+ end
+
+ def test_initialize_with_kw
+ klass = @Struct.new(:foo, :options) do
+ def initialize(foo, **options)
+ super(foo, options)
+ end
+ end
+ assert_equal({}, klass.new(42, **Hash.new).options)
+ x = assert_warn('') { klass.new(1, bar: 2) }
+ assert_equal 2, x.options[:bar]
end
def test_each
- klass = Struct.new(:a, :b)
+ klass = @Struct.new(:a, :b)
o = klass.new(1, 2)
assert_equal([1, 2], o.each.to_a)
end
def test_each_pair
- klass = Struct.new(:a, :b)
+ klass = @Struct.new(:a, :b)
o = klass.new(1, 2)
assert_equal([[:a, 1], [:b, 2]], o.each_pair.to_a)
+ bug7382 = '[ruby-dev:46533]'
+ a = []
+ o.each_pair {|x| a << x}
+ assert_equal([[:a, 1], [:b, 2]], a, bug7382)
end
def test_inspect
- klass = Struct.new(:a)
+ klass = @Struct.new(:a)
o = klass.new(1)
assert_equal("#<struct a=1>", o.inspect)
o.a = o
assert_match(/^#<struct a=#<struct #<.*?>:...>>$/, o.inspect)
- Struct.new("Foo", :a)
- o = Struct::Foo.new(1)
- assert_equal("#<struct Struct::Foo a=1>", o.inspect)
- Struct.instance_eval { remove_const(:Foo) }
+ @Struct.new("Foo", :a)
+ o = @Struct::Foo.new(1)
+ assert_equal("#<struct #@Struct::Foo a=1>", o.inspect)
+ @Struct.instance_eval { remove_const(:Foo) }
- klass = Struct.new(:a, :b)
+ klass = @Struct.new(:a, :b)
o = klass.new(1, 2)
assert_equal("#<struct a=1, b=2>", o.inspect)
- klass = Struct.new(:@a)
+ klass = @Struct.new(:@a)
o = klass.new(1)
+ assert_equal(1, o.__send__(:@a))
assert_equal("#<struct :@a=1>", o.inspect)
+ o.__send__(:"@a=", 2)
+ assert_equal(2, o.__send__(:@a))
+ assert_equal("#<struct :@a=2>", o.inspect)
+ o.__send__("@a=", 3)
+ assert_equal(3, o.__send__(:@a))
+ assert_equal("#<struct :@a=3>", o.inspect)
+
+ methods = klass.instance_methods(false)
+ assert_equal([:@a, :"@a="].sort.inspect, methods.sort.inspect, '[Bug #8756]')
+ assert_include(methods, :@a)
+ assert_include(methods, :"@a=")
end
def test_init_copy
- klass = Struct.new(:a)
+ klass = @Struct.new(:a)
o = klass.new(1)
assert_equal(o, o.dup)
end
def test_aref
- klass = Struct.new(:a)
+ klass = @Struct.new(:a)
o = klass.new(1)
assert_equal(1, o[0])
- assert_raise(IndexError) { o[-2] }
- assert_raise(IndexError) { o[1] }
+ assert_raise_with_message(IndexError, /offset -2\b/) {o[-2]}
+ assert_raise_with_message(IndexError, /offset 1\b/) {o[1]}
+ assert_raise_with_message(NameError, /foo/) {o["foo"]}
+ assert_raise_with_message(NameError, /foo/) {o[:foo]}
end
def test_aset
- klass = Struct.new(:a)
+ klass = @Struct.new(:a)
o = klass.new(1)
o[0] = 2
assert_equal(2, o[:a])
- assert_raise(IndexError) { o[-2] = 3 }
- assert_raise(IndexError) { o[1] = 3 }
+ assert_raise_with_message(IndexError, /offset -2\b/) {o[-2] = 3}
+ assert_raise_with_message(IndexError, /offset 1\b/) {o[1] = 3}
+ assert_raise_with_message(NameError, /foo/) {o["foo"] = 3}
+ assert_raise_with_message(NameError, /foo/) {o[:foo] = 3}
end
def test_values_at
- klass = Struct.new(:a, :b, :c, :d, :e, :f)
+ klass = @Struct.new(:a, :b, :c, :d, :e, :f)
o = klass.new(1, 2, 3, 4, 5, 6)
assert_equal([2, 4, 6], o.values_at(1, 3, 5))
assert_equal([2, 3, 4, 3, 4, 5], o.values_at(1..3, 2...5))
end
def test_select
- klass = Struct.new(:a, :b, :c, :d, :e, :f)
+ klass = @Struct.new(:a, :b, :c, :d, :e, :f)
o = klass.new(1, 2, 3, 4, 5, 6)
assert_equal([1, 3, 5], o.select {|v| v % 2 != 0 })
assert_raise(ArgumentError) { o.select(1) }
end
+ def test_filter
+ klass = @Struct.new(:a, :b, :c, :d, :e, :f)
+ o = klass.new(1, 2, 3, 4, 5, 6)
+ assert_equal([1, 3, 5], o.filter {|v| v % 2 != 0 })
+ assert_raise(ArgumentError) { o.filter(1) }
+ end
+
+ def test_big_struct
+ klass1 = @Struct.new(*('a'..'z').map(&:to_sym))
+ o = klass1.new
+ assert_nil o.z
+ assert_equal(:foo, o.z = :foo)
+ assert_equal(:foo, o.z)
+ assert_equal(:foo, o[25])
+ end
+
+ def test_overridden_aset
+ bug10601 = '[ruby-core:66846] [Bug #10601]: should not be affected by []= method'
+
+ struct = Class.new(Struct.new(*(:a..:z), :result)) do
+ def []=(*args)
+ raise args.inspect
+ end
+ end
+
+ obj = struct.new
+ assert_nothing_raised(RuntimeError, bug10601) do
+ obj.result = 42
+ end
+ assert_equal(42, obj.result, bug10601)
+ end
+
+ def test_overridden_aref
+ bug10601 = '[ruby-core:66846] [Bug #10601]: should not be affected by [] method'
+
+ struct = Class.new(Struct.new(*(:a..:z), :result)) do
+ def [](*args)
+ raise args.inspect
+ end
+ end
+
+ obj = struct.new
+ obj.result = 42
+ result = assert_nothing_raised(RuntimeError, bug10601) do
+ break obj.result
+ end
+ assert_equal(42, result, bug10601)
+ end
+
def test_equal
- klass1 = Struct.new(:a)
- klass2 = Struct.new(:a, :b)
+ klass1 = @Struct.new(:a)
+ klass2 = @Struct.new(:a, :b)
o1 = klass1.new(1)
o2 = klass1.new(1)
o3 = klass2.new(1)
- assert(o1.==(o2))
- assert(o1 != o3)
+ assert_equal(o1, o2)
+ assert_not_equal(o1, o3)
end
def test_hash
- klass = Struct.new(:a)
+ klass = @Struct.new(:a)
o = klass.new(1)
- assert(o.hash.is_a?(Fixnum))
+ assert_kind_of(Integer, o.hash)
+ assert_kind_of(String, o.hash.to_s)
end
def test_eql
- klass1 = Struct.new(:a)
- klass2 = Struct.new(:a, :b)
+ klass1 = @Struct.new(:a)
+ klass2 = @Struct.new(:a, :b)
o1 = klass1.new(1)
o2 = klass1.new(1)
o3 = klass2.new(1)
- assert(o1.eql?(o2))
- assert(!(o1.eql?(o3)))
+ assert_operator(o1, :eql?, o2)
+ assert_not_operator(o1, :eql?, o3)
end
def test_size
- klass = Struct.new(:a)
+ klass = @Struct.new(:a)
o = klass.new(1)
assert_equal(1, o.size)
end
def test_error
assert_raise(TypeError){
- Struct.new(0)
+ @Struct.new(0)
}
end
+ def test_redefinition_warning
+ @Struct.new(name = "RedefinitionWarning")
+ e = EnvUtil.verbose_warning do
+ @Struct.new("RedefinitionWarning")
+ end
+ assert_match(/redefining constant #@Struct::RedefinitionWarning/, e)
+
+ @Struct.class_eval do
+ remove_const name
+ end
+ end
+
+ def test_keyword_args_warning
+ assert_warn('') { assert_equal(1, @Struct.new(:a).new(a: 1).a) }
+ assert_warn('') { assert_equal(1, @Struct.new(:a, keyword_init: nil).new(a: 1).a) }
+ assert_warn('') { assert_equal({a: 1}, @Struct.new(:a).new({a: 1}).a) }
+ assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, :b).new(1, a: 1).b) }
+ assert_warn('') { assert_equal(1, @Struct.new(:a, keyword_init: true).new(a: 1).a) }
+ assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: nil).new({a: 1}).a) }
+ assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: false).new(a: 1).a) }
+ assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: false).new({a: 1}).a) }
+ end
+
def test_nonascii
- struct_test = Struct.new("R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}")
- assert_equal(Struct.const_get("R\u{e9}sum\u{e9}"), struct_test, '[ruby-core:24849]')
+ struct_test = @Struct.new(name = "R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}")
+ assert_equal(@Struct.const_get("R\u{e9}sum\u{e9}"), struct_test, '[ruby-core:24849]')
a = struct_test.new(42)
- assert_equal("#<struct Struct::R\u{e9}sum\u{e9} r\u{e9}sum\u{e9}=42>", a.inspect, '[ruby-core:24849]')
+ assert_equal("#<struct #@Struct::R\u{e9}sum\u{e9} r\u{e9}sum\u{e9}=42>", a.inspect, '[ruby-core:24849]')
+ e = EnvUtil.verbose_warning do
+ @Struct.new("R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}")
+ end
+ assert_nothing_raised(Encoding::CompatibilityError) do
+ assert_match(/redefining constant #@Struct::R\u{e9}sum\u{e9}/, e)
+ end
+
+ @Struct.class_eval do
+ remove_const name
+ end
+ end
+
+ def test_junk
+ struct_test = @Struct.new("Foo", "a\000")
+ o = struct_test.new(1)
+ assert_equal(1, o.send("a\000"))
+ @Struct.instance_eval { remove_const(:Foo) }
end
def test_comparison_when_recursive
- klass1 = Struct.new(:a, :b, :c)
+ klass1 = @Struct.new(:a, :b, :c)
x = klass1.new(1, 2, nil); x.c = x
y = klass1.new(1, 2, nil); y.c = y
Timeout.timeout(1) {
- assert x == y
- assert x.eql? y
+ assert_equal x, y
+ assert_operator x, :eql?, y
}
z = klass1.new(:something, :other, nil); z.c = z
Timeout.timeout(1) {
- assert x != z
- assert !x.eql?(z)
+ assert_not_equal x, z
+ assert_not_operator x, :eql?, z
}
x.c = y; y.c = x
Timeout.timeout(1) {
- assert x == y
- assert x.eql?(y)
+ assert_equal x, y
+ assert_operator x, :eql?, y
}
x.c = z; z.c = x
Timeout.timeout(1) {
- assert x != z
- assert !x.eql?(z)
+ assert_not_equal x, z
+ assert_not_operator x, :eql?, z
+ }
+ end
+
+ def test_to_h
+ klass = @Struct.new(:a, :b, :c, :d, :e, :f)
+ o = klass.new(1, 2, 3, 4, 5, 6)
+ assert_equal({a:1, b:2, c:3, d:4, e:5, f:6}, o.to_h)
+ end
+
+ def test_to_h_block
+ klass = @Struct.new(:a, :b, :c, :d, :e, :f)
+ o = klass.new(1, 2, 3, 4, 5, 6)
+ assert_equal({"a" => 1, "b" => 4, "c" => 9, "d" => 16, "e" => 25, "f" => 36},
+ o.to_h {|k, v| [k.to_s, v*v]})
+ end
+
+ def test_question_mark_in_member
+ klass = @Struct.new(:a, :b?)
+ x = Object.new
+ o = klass.new("test", x)
+ assert_same(x, o.b?)
+ o.send("b?=", 42)
+ assert_equal(42, o.b?)
+ end
+
+ def test_bang_mark_in_member
+ klass = @Struct.new(:a, :b!)
+ x = Object.new
+ o = klass.new("test", x)
+ assert_same(x, o.b!)
+ o.send("b!=", 42)
+ assert_equal(42, o.b!)
+ end
+
+ def test_setter_method_returns_value
+ klass = @Struct.new(:a)
+ x = klass.new
+ assert_equal "[Bug #9353]", x.send(:a=, "[Bug #9353]")
+ end
+
+ def test_dig
+ klass = @Struct.new(:a)
+ o = klass.new(klass.new({b: [1, 2, 3]}))
+ assert_equal(1, o.dig(:a, :a, :b, 0))
+ assert_nil(o.dig(:b, 0))
+ end
+
+ def test_new_duplicate
+ bug12291 = '[ruby-core:74971] [Bug #12291]'
+ assert_raise_with_message(ArgumentError, /duplicate member/, bug12291) {
+ @Struct.new(:a, :a)
+ }
+ end
+
+ def test_deconstruct_keys
+ klass = @Struct.new(:a, :b)
+ o = klass.new(1, 2)
+ assert_equal({a: 1, b: 2}, o.deconstruct_keys(nil))
+ assert_equal({a: 1, b: 2}, o.deconstruct_keys([:b, :a]))
+ assert_equal({a: 1}, o.deconstruct_keys([:a]))
+ assert_not_send([o.deconstruct_keys([:a, :c]), :key?, :c])
+ assert_raise(TypeError) {
+ o.deconstruct_keys(0)
}
end
+
+ def test_public_send
+ klass = @Struct.new(:a)
+ x = klass.new(1)
+ assert_equal(1, x.public_send("a"))
+ assert_equal(42, x.public_send("a=", 42))
+ assert_equal(42, x.public_send("a"))
+ end
+
+ def test_arity
+ klass = @Struct.new(:a)
+ assert_equal 0, klass.instance_method(:a).arity
+ assert_equal 1, klass.instance_method(:a=).arity
+
+ klass.module_eval do
+ define_method(:b=, instance_method(:a=))
+ alias c= a=
+ end
+
+ assert_equal 1, klass.instance_method(:b=).arity
+ assert_equal 1, klass.instance_method(:c=).arity
+ end
+
+ def test_parameters
+ klass = @Struct.new(:a)
+ assert_equal [], klass.instance_method(:a).parameters
+ # NOTE: :_ may not be a spec.
+ assert_equal [[:req, :_]], klass.instance_method(:a=).parameters
+
+ klass.module_eval do
+ define_method(:b=, instance_method(:a=))
+ alias c= a=
+ end
+
+ assert_equal [[:req, :_]], klass.instance_method(:b=).parameters
+ assert_equal [[:req, :_]], klass.instance_method(:c=).parameters
+ end
+
+ def test_named_structs_are_not_rooted
+ omit 'skip on riscv64-linux CI machine. See https://github.com/ruby/ruby/pull/13422' if ENV['RUBY_DEBUG'] == 'ci' && /riscv64-linux/ =~ RUBY_DESCRIPTION
+
+ # [Bug #20311]
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ code = proc do
+ Struct.new("A")
+ Struct.send(:remove_const, :A)
+ end
+
+ 10_000.times(&code)
+ PREP
+ 50_000.times(&code)
+ CODE
+ end
+
+ def test_frozen_subclass
+ test = Class.new(@Struct.new(:a)).freeze.new(a: 0)
+ assert_kind_of(@Struct, test)
+ assert_equal([:a], test.members)
+ end
+
+ class TopStruct < Test::Unit::TestCase
+ include TestStruct
+
+ def initialize(*)
+ super
+ @Struct = Struct
+ end
+ end
+
+ class SubStruct < Test::Unit::TestCase
+ include TestStruct
+ SubStruct = Class.new(Struct)
+
+ def initialize(*)
+ super
+ @Struct = SubStruct
+ end
+ end
end