summaryrefslogtreecommitdiff
path: root/spec/ruby/core/struct
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/core/struct')
-rw-r--r--spec/ruby/core/struct/clone_spec.rb7
-rw-r--r--spec/ruby/core/struct/constants_spec.rb13
-rw-r--r--spec/ruby/core/struct/deconstruct_keys_spec.rb130
-rw-r--r--spec/ruby/core/struct/deconstruct_spec.rb10
-rw-r--r--spec/ruby/core/struct/dig_spec.rb14
-rw-r--r--spec/ruby/core/struct/dup_spec.rb3
-rw-r--r--spec/ruby/core/struct/element_reference_spec.rb12
-rw-r--r--spec/ruby/core/struct/element_set_spec.rb15
-rw-r--r--spec/ruby/core/struct/filter_spec.rb10
-rw-r--r--spec/ruby/core/struct/fixtures/classes.rb8
-rw-r--r--spec/ruby/core/struct/hash_spec.rb22
-rw-r--r--spec/ruby/core/struct/initialize_spec.rb8
-rw-r--r--spec/ruby/core/struct/inspect_spec.rb5
-rw-r--r--spec/ruby/core/struct/instance_variable_get_spec.rb16
-rw-r--r--spec/ruby/core/struct/keyword_init_spec.rb45
-rw-r--r--spec/ruby/core/struct/members_spec.rb12
-rw-r--r--spec/ruby/core/struct/new_spec.rb185
-rw-r--r--spec/ruby/core/struct/shared/dup.rb9
-rw-r--r--spec/ruby/core/struct/shared/inspect.rb35
-rw-r--r--spec/ruby/core/struct/struct_spec.rb9
-rw-r--r--spec/ruby/core/struct/to_h_spec.rb90
-rw-r--r--spec/ruby/core/struct/values_at_spec.rb55
22 files changed, 583 insertions, 130 deletions
diff --git a/spec/ruby/core/struct/clone_spec.rb b/spec/ruby/core/struct/clone_spec.rb
new file mode 100644
index 0000000000..40c4d52d57
--- /dev/null
+++ b/spec/ruby/core/struct/clone_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/dup'
+
+describe "Struct-based class#clone" do
+ it_behaves_like :struct_dup, :clone
+end
diff --git a/spec/ruby/core/struct/constants_spec.rb b/spec/ruby/core/struct/constants_spec.rb
new file mode 100644
index 0000000000..7e8af1a211
--- /dev/null
+++ b/spec/ruby/core/struct/constants_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Struct::Group" do
+ it "is no longer defined" do
+ Struct.should_not.const_defined?(:Group)
+ end
+end
+
+describe "Struct::Passwd" do
+ it "is no longer defined" do
+ Struct.should_not.const_defined?(:Passwd)
+ end
+end
diff --git a/spec/ruby/core/struct/deconstruct_keys_spec.rb b/spec/ruby/core/struct/deconstruct_keys_spec.rb
new file mode 100644
index 0000000000..e16b50f930
--- /dev/null
+++ b/spec/ruby/core/struct/deconstruct_keys_spec.rb
@@ -0,0 +1,130 @@
+require_relative '../../spec_helper'
+
+describe "Struct#deconstruct_keys" do
+ it "returns a hash of attributes" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ s.deconstruct_keys([:x, :y]).should == {x: 1, y: 2}
+ end
+
+ it "requires one argument" do
+ struct = Struct.new(:x)
+ obj = struct.new(1)
+
+ -> {
+ obj.deconstruct_keys
+ }.should raise_error(ArgumentError, /wrong number of arguments \(given 0, expected 1\)/)
+ end
+
+ it "returns only specified keys" do
+ struct = Struct.new(:x, :y, :z)
+ s = struct.new(1, 2, 3)
+
+ s.deconstruct_keys([:x, :y]).should == {x: 1, y: 2}
+ s.deconstruct_keys([:x] ).should == {x: 1}
+ s.deconstruct_keys([] ).should == {}
+ end
+
+ it "accepts string attribute names" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ s.deconstruct_keys(['x', 'y']).should == {'x' => 1, 'y' => 2}
+ end
+
+ it "accepts argument position number as well but returns them as keys" do
+ struct = Struct.new(:x, :y, :z)
+ s = struct.new(10, 20, 30)
+
+ s.deconstruct_keys([0, 1, 2]).should == {0 => 10, 1 => 20, 2 => 30}
+ s.deconstruct_keys([0, 1] ).should == {0 => 10, 1 => 20}
+ s.deconstruct_keys([0] ).should == {0 => 10}
+ s.deconstruct_keys([-1] ).should == {-1 => 30}
+ end
+
+ it "ignores incorrect position numbers" do
+ struct = Struct.new(:x, :y, :z)
+ s = struct.new(10, 20, 30)
+
+ s.deconstruct_keys([0, 3]).should == {0 => 10}
+ end
+
+ it "support mixing attribute names and argument position numbers" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ s.deconstruct_keys([0, :x]).should == {0 => 1, :x => 1}
+ end
+
+ it "returns an empty hash when there are more keys than attributes" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ s.deconstruct_keys([:x, :y, :a]).should == {}
+ end
+
+ it "returns at first not existing attribute name" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ s.deconstruct_keys([:a, :x]).should == {}
+ s.deconstruct_keys([:x, :a]).should == {x: 1}
+ end
+
+ it "returns at first not existing argument position number" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ s.deconstruct_keys([3, 0]).should == {}
+ s.deconstruct_keys([0, 3]).should == {0 => 1}
+ end
+
+ it "accepts nil argument and return all the attributes" do
+ struct = Struct.new(:x, :y)
+ obj = struct.new(1, 2)
+
+ obj.deconstruct_keys(nil).should == {x: 1, y: 2}
+ end
+
+ it "tries to convert a key with #to_int if index is not a String nor a Symbol, but responds to #to_int" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ key = mock("to_int")
+ key.should_receive(:to_int).and_return(1)
+
+ s.deconstruct_keys([key]).should == { key => 2 }
+ end
+
+ it "raises a TypeError if the conversion with #to_int does not return an Integer" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ key = mock("to_int")
+ key.should_receive(:to_int).and_return("not an Integer")
+
+ -> {
+ s.deconstruct_keys([key])
+ }.should raise_error(TypeError, /can't convert MockObject to Integer/)
+ end
+
+ it "raises TypeError if index is not a String, a Symbol and not convertible to Integer" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ -> {
+ s.deconstruct_keys([0, []])
+ }.should raise_error(TypeError, "no implicit conversion of Array into Integer")
+ end
+
+ it "raise TypeError if passed anything except nil or array" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ -> { s.deconstruct_keys('x') }.should raise_error(TypeError, /expected Array or nil/)
+ -> { s.deconstruct_keys(1) }.should raise_error(TypeError, /expected Array or nil/)
+ -> { s.deconstruct_keys(:x) }.should raise_error(TypeError, /expected Array or nil/)
+ -> { s.deconstruct_keys({}) }.should raise_error(TypeError, /expected Array or nil/)
+ end
+end
diff --git a/spec/ruby/core/struct/deconstruct_spec.rb b/spec/ruby/core/struct/deconstruct_spec.rb
new file mode 100644
index 0000000000..32d4f6bac4
--- /dev/null
+++ b/spec/ruby/core/struct/deconstruct_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+describe "Struct#deconstruct" do
+ it "returns an array of attribute values" do
+ struct = Struct.new(:x, :y)
+ s = struct.new(1, 2)
+
+ s.deconstruct.should == [1, 2]
+ end
+end
diff --git a/spec/ruby/core/struct/dig_spec.rb b/spec/ruby/core/struct/dig_spec.rb
index c7c979fabe..93a52dbbe1 100644
--- a/spec/ruby/core/struct/dig_spec.rb
+++ b/spec/ruby/core/struct/dig_spec.rb
@@ -10,6 +10,16 @@ describe "Struct#dig" do
@instance.dig(:a, :a).should == { b: [1, 2, 3] }
end
+ it "accepts String keys" do
+ @instance.dig('a', 'a').should == { b: [1, 2, 3] }
+ end
+
+ it "returns the value by the index" do
+ instance = Struct.new(:a, :b).new(:one, :two)
+ instance.dig(0).should == :one
+ instance.dig(1).should == :two
+ end
+
it "returns the nested value specified if the sequence includes an index" do
@instance.dig(:a, :a, :b, 0).should == 1
end
@@ -20,13 +30,13 @@ describe "Struct#dig" do
it "raises a TypeError if any intermediate step does not respond to #dig" do
instance = @klass.new(1)
- lambda {
+ -> {
instance.dig(:a, 3)
}.should raise_error(TypeError)
end
it "raises an ArgumentError if no arguments provided" do
- lambda { @instance.dig }.should raise_error(ArgumentError)
+ -> { @instance.dig }.should raise_error(ArgumentError)
end
it "calls #dig on any intermediate step with the rest of the sequence as arguments" do
diff --git a/spec/ruby/core/struct/dup_spec.rb b/spec/ruby/core/struct/dup_spec.rb
index cb54b7ceee..8b50c39014 100644
--- a/spec/ruby/core/struct/dup_spec.rb
+++ b/spec/ruby/core/struct/dup_spec.rb
@@ -1,8 +1,11 @@
require_relative '../../spec_helper'
require_relative 'fixtures/classes'
+require_relative 'shared/dup'
describe "Struct-based class#dup" do
+ it_behaves_like :struct_dup, :dup
+
# From https://github.com/jruby/jruby/issues/3686
it "retains an included module in the ancestor chain for the struct's singleton class" do
klass = Struct.new(:foo)
diff --git a/spec/ruby/core/struct/element_reference_spec.rb b/spec/ruby/core/struct/element_reference_spec.rb
index 28706a65aa..0f6d547f66 100644
--- a/spec/ruby/core/struct/element_reference_spec.rb
+++ b/spec/ruby/core/struct/element_reference_spec.rb
@@ -26,20 +26,20 @@ describe "Struct#[]" do
it "fails when it does not know about the requested attribute" do
car = StructClasses::Car.new('Ford', 'Ranger')
- lambda { car[3] }.should raise_error(IndexError)
- lambda { car[-4] }.should raise_error(IndexError)
- lambda { car[:body] }.should raise_error(NameError)
- lambda { car['wheels'] }.should raise_error(NameError)
+ -> { car[3] }.should raise_error(IndexError)
+ -> { car[-4] }.should raise_error(IndexError)
+ -> { car[:body] }.should raise_error(NameError)
+ -> { car['wheels'] }.should raise_error(NameError)
end
it "fails if passed too many arguments" do
car = StructClasses::Car.new('Ford', 'Ranger')
- lambda { car[:make, :model] }.should raise_error(ArgumentError)
+ -> { car[:make, :model] }.should raise_error(ArgumentError)
end
it "fails if not passed a string, symbol, or integer" do
car = StructClasses::Car.new('Ford', 'Ranger')
- lambda { car[Object.new] }.should raise_error(TypeError)
+ -> { car[Object.new] }.should raise_error(TypeError)
end
it "returns attribute names that contain hyphens" do
diff --git a/spec/ruby/core/struct/element_set_spec.rb b/spec/ruby/core/struct/element_set_spec.rb
index 246e7cf3c2..0a0e34a5ee 100644
--- a/spec/ruby/core/struct/element_set_spec.rb
+++ b/spec/ruby/core/struct/element_set_spec.rb
@@ -21,9 +21,16 @@ describe "Struct#[]=" do
it "fails when trying to assign attributes which don't exist" do
car = StructClasses::Car.new('Ford', 'Ranger')
- lambda { car[:something] = true }.should raise_error(NameError)
- lambda { car[3] = true }.should raise_error(IndexError)
- lambda { car[-4] = true }.should raise_error(IndexError)
- lambda { car[Object.new] = true }.should raise_error(TypeError)
+ -> { car[:something] = true }.should raise_error(NameError)
+ -> { car[3] = true }.should raise_error(IndexError)
+ -> { car[-4] = true }.should raise_error(IndexError)
+ -> { car[Object.new] = true }.should raise_error(TypeError)
+ end
+
+ it "raises a FrozenError on a frozen struct" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ car.freeze
+
+ -> { car[:model] = 'Escape' }.should raise_error(FrozenError)
end
end
diff --git a/spec/ruby/core/struct/filter_spec.rb b/spec/ruby/core/struct/filter_spec.rb
index 1105ed064a..0ccd8ad6b2 100644
--- a/spec/ruby/core/struct/filter_spec.rb
+++ b/spec/ruby/core/struct/filter_spec.rb
@@ -3,10 +3,8 @@ require_relative 'shared/select'
require_relative 'shared/accessor'
require_relative '../enumerable/shared/enumeratorized'
-ruby_version_is "2.6" do
- describe "Struct#filter" do
- it_behaves_like :struct_select, :filter
- it_behaves_like :struct_accessor, :filter
- it_behaves_like :enumeratorized_with_origin_size, :filter, Struct.new(:foo).new
- end
+describe "Struct#filter" do
+ it_behaves_like :struct_select, :filter
+ it_behaves_like :struct_accessor, :filter
+ it_behaves_like :enumeratorized_with_origin_size, :filter, Struct.new(:foo).new
end
diff --git a/spec/ruby/core/struct/fixtures/classes.rb b/spec/ruby/core/struct/fixtures/classes.rb
index 6d620f9060..7b80b814ef 100644
--- a/spec/ruby/core/struct/fixtures/classes.rb
+++ b/spec/ruby/core/struct/fixtures/classes.rb
@@ -13,6 +13,12 @@ module StructClasses
end
end
+ class StructWithOverriddenName < Struct.new(:a)
+ def self.name
+ "A"
+ end
+ end
+
class SubclassX < Struct
end
@@ -23,4 +29,6 @@ module StructClasses
super
end
end
+
+ class StructSubclass < Struct; end
end
diff --git a/spec/ruby/core/struct/hash_spec.rb b/spec/ruby/core/struct/hash_spec.rb
index 71e8ccbd3a..53361eb7a9 100644
--- a/spec/ruby/core/struct/hash_spec.rb
+++ b/spec/ruby/core/struct/hash_spec.rb
@@ -4,11 +4,11 @@ require_relative 'shared/accessor'
describe "Struct#hash" do
- it "returns the same fixnum for structs with the same content" do
+ it "returns the same integer for structs with the same content" do
[StructClasses::Ruby.new("1.8.6", "PPC"),
StructClasses::Car.new("Hugo", "Foo", "1972")].each do |stc|
stc.hash.should == stc.dup.hash
- stc.hash.should be_kind_of(Fixnum)
+ stc.hash.should be_kind_of(Integer)
end
end
@@ -19,6 +19,20 @@ describe "Struct#hash" do
car.hash.should == similar_car.hash
end
+ it "returns different hashes for structs with different values" do
+ s1 = StructClasses::Ruby.new('2.7.0', 'linux')
+ s2 = StructClasses::Ruby.new('2.7.0', 'macos')
+ s1.hash.should_not == s2.hash
+ end
+
+ it "returns different hashes for structs with different values when using keyword_init: true" do
+ key = :"1 non symbol member"
+ struct_class = Struct.new(key, keyword_init: true)
+ t1 = struct_class.new(key => 1)
+ t2 = struct_class.new(key => 2)
+ t1.hash.should_not == t2.hash
+ end
+
it "allows for overriding methods in an included module" do
mod = Module.new do
def hash
@@ -42,5 +56,9 @@ describe "Struct#hash" do
# See the Struct#eql? specs
end
+ it "returns different hashes for different struct classes" do
+ Struct.new(:x).new(1).hash.should != Struct.new(:y).new(1).hash
+ end
+
it_behaves_like :struct_accessor, :hash
end
diff --git a/spec/ruby/core/struct/initialize_spec.rb b/spec/ruby/core/struct/initialize_spec.rb
index e82289071a..06055594d5 100644
--- a/spec/ruby/core/struct/initialize_spec.rb
+++ b/spec/ruby/core/struct/initialize_spec.rb
@@ -40,4 +40,12 @@ describe "Struct#initialize" do
it "can be overridden" do
StructClasses::SubclassX.new(:y).new.key.should == :value
end
+
+ it "can be initialized with keyword arguments" do
+ positional_args = StructClasses::Ruby.new("3.2", "OS")
+ keyword_args = StructClasses::Ruby.new(version: "3.2", platform: "OS")
+
+ positional_args.version.should == keyword_args.version
+ positional_args.platform.should == keyword_args.platform
+ end
end
diff --git a/spec/ruby/core/struct/inspect_spec.rb b/spec/ruby/core/struct/inspect_spec.rb
index 83e13597ba..657b06abc1 100644
--- a/spec/ruby/core/struct/inspect_spec.rb
+++ b/spec/ruby/core/struct/inspect_spec.rb
@@ -3,10 +3,5 @@ require_relative 'fixtures/classes'
require_relative 'shared/inspect'
describe "Struct#inspect" do
- it "returns a string representation showing members and values" do
- car = StructClasses::Car.new('Ford', 'Ranger')
- car.inspect.should == '#<struct StructClasses::Car make="Ford", model="Ranger", year=nil>'
- end
-
it_behaves_like :struct_inspect, :inspect
end
diff --git a/spec/ruby/core/struct/instance_variable_get_spec.rb b/spec/ruby/core/struct/instance_variable_get_spec.rb
new file mode 100644
index 0000000000..e4a3ea87dc
--- /dev/null
+++ b/spec/ruby/core/struct/instance_variable_get_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Struct#instance_variable_get" do
+ it "returns nil for attributes" do
+ car = StructClasses::Car.new("Hugo", "Foo", "1972")
+ car.instance_variable_get(:@make).should be_nil
+ end
+
+ it "returns a user value for variables with the same name as attributes" do
+ car = StructClasses::Car.new("Hugo", "Foo", "1972")
+ car.instance_variable_set :@make, "explicit"
+ car.instance_variable_get(:@make).should == "explicit"
+ car.make.should == "Hugo"
+ end
+end
diff --git a/spec/ruby/core/struct/keyword_init_spec.rb b/spec/ruby/core/struct/keyword_init_spec.rb
new file mode 100644
index 0000000000..536b82041a
--- /dev/null
+++ b/spec/ruby/core/struct/keyword_init_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# See https://bugs.ruby-lang.org/issues/18008
+describe "StructClass#keyword_init?" do
+ it "returns true for a struct that accepts keyword arguments to initialize" do
+ struct = Struct.new(:arg, keyword_init: true)
+ struct.keyword_init?.should be_true
+ end
+
+ it "returns false for a struct that does not accept keyword arguments to initialize" do
+ struct = Struct.new(:arg, keyword_init: false)
+ struct.keyword_init?.should be_false
+ end
+
+ it "returns nil for a struct that did not explicitly specify keyword_init" do
+ struct = Struct.new(:arg)
+ struct.keyword_init?.should be_nil
+ end
+
+ it "returns nil for a struct that does specify keyword_init to be nil" do
+ struct = Struct.new(:arg, keyword_init: nil)
+ struct.keyword_init?.should be_nil
+ end
+
+ it "returns true for any truthy value, not just for true" do
+ struct = Struct.new(:arg, keyword_init: 1)
+ struct.keyword_init?.should be_true
+
+ struct = Struct.new(:arg, keyword_init: "")
+ struct.keyword_init?.should be_true
+
+ struct = Struct.new(:arg, keyword_init: [])
+ struct.keyword_init?.should be_true
+
+ struct = Struct.new(:arg, keyword_init: {})
+ struct.keyword_init?.should be_true
+ end
+
+ context "class inheriting Struct" do
+ it "isn't available in a subclass" do
+ StructClasses::StructSubclass.should_not.respond_to?(:keyword_init?)
+ end
+ end
+end
diff --git a/spec/ruby/core/struct/members_spec.rb b/spec/ruby/core/struct/members_spec.rb
index 1f2ff950d9..1ff7b9387a 100644
--- a/spec/ruby/core/struct/members_spec.rb
+++ b/spec/ruby/core/struct/members_spec.rb
@@ -11,3 +11,15 @@ describe "Struct#members" do
it_behaves_like :struct_accessor, :members
end
+
+describe "StructClass#members" do
+ it "returns an array of attribute names" do
+ StructClasses::Car.members.should == [:make, :model, :year]
+ end
+
+ context "class inheriting Struct" do
+ it "isn't available in a subclass" do
+ StructClasses::StructSubclass.should_not.respond_to?(:members)
+ end
+ end
+end
diff --git a/spec/ruby/core/struct/new_spec.rb b/spec/ruby/core/struct/new_spec.rb
index 01231e85fb..1d35de7b87 100644
--- a/spec/ruby/core/struct/new_spec.rb
+++ b/spec/ruby/core/struct/new_spec.rb
@@ -6,6 +6,8 @@ describe "Struct.new" do
struct = Struct.new('Animal', :name, :legs, :eyeballs)
struct.should == Struct::Animal
struct.name.should == "Struct::Animal"
+ ensure
+ Struct.send(:remove_const, :Animal)
end
it "overwrites previously defined constants with string as first argument" do
@@ -13,12 +15,14 @@ describe "Struct.new" do
first.should == Struct::Person
second = nil
- lambda {
+ -> {
second = Struct.new('Person', :hair, :sex)
- }.should complain(/redefining constant/)
+ }.should complain(/constant/)
second.should == Struct::Person
first.members.should_not == second.members
+ ensure
+ Struct.send(:remove_const, :Person)
end
it "calls to_str on its first argument (constant name)" do
@@ -27,6 +31,8 @@ describe "Struct.new" do
struct = Struct.new(obj)
struct.should == Struct::Foo
struct.name.should == "Struct::Foo"
+ ensure
+ Struct.send(:remove_const, :Foo)
end
it "creates a new anonymous class with nil first argument" do
@@ -47,37 +53,52 @@ describe "Struct.new" do
Struct.const_defined?("Animal2").should be_false
end
+ it "allows non-ASCII member name" do
+ name = "r\xe9sum\xe9".dup.force_encoding(Encoding::ISO_8859_1).to_sym
+ struct = Struct.new(name)
+ struct.new("foo").send(name).should == "foo"
+ end
it "fails with invalid constant name as first argument" do
- lambda { Struct.new('animal', :name, :legs, :eyeballs) }.should raise_error(NameError)
+ -> { Struct.new('animal', :name, :legs, :eyeballs) }.should raise_error(NameError)
end
it "raises a TypeError if object doesn't respond to to_sym" do
- lambda { Struct.new(:animal, mock('giraffe')) }.should raise_error(TypeError)
- lambda { Struct.new(:animal, 1.0) }.should raise_error(TypeError)
- lambda { Struct.new(:animal, Time.now) }.should raise_error(TypeError)
- lambda { Struct.new(:animal, Class) }.should raise_error(TypeError)
- lambda { Struct.new(:animal, nil) }.should raise_error(TypeError)
- lambda { Struct.new(:animal, true) }.should raise_error(TypeError)
- lambda { Struct.new(:animal, ['chris', 'evan']) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, mock('giraffe')) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, 1.0) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, Time.now) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, Class) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, nil) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, true) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, ['chris', 'evan']) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed a Hash with an unknown key" do
+ -> { Struct.new(:animal, { name: 'chris' }) }.should raise_error(TypeError)
end
- ruby_version_is ""..."2.5" do
- it "raises a TypeError if an argument is a Hash" do
- lambda { Struct.new(:animal, { name: 'chris' }) }.should raise_error(TypeError)
+ ruby_version_is ""..."3.3" do
+ it "raises ArgumentError if not provided any arguments" do
+ -> { Struct.new }.should raise_error(ArgumentError)
end
end
- ruby_version_is "2.5" do
- it "raises a ArgumentError if passed a Hash with an unknown key" do
- lambda { Struct.new(:animal, { name: 'chris' }) }.should raise_error(ArgumentError)
+ ruby_version_is "3.3" do
+ it "works when not provided any arguments" do
+ c = Struct.new
+ c.should be_kind_of(Class)
+ c.superclass.should == Struct
end
end
+ it "raises ArgumentError when there is a duplicate member" do
+ -> { Struct.new(:foo, :foo) }.should raise_error(ArgumentError, "duplicate member: foo")
+ end
+
it "raises a TypeError if object is not a Symbol" do
obj = mock(':ruby')
def obj.to_sym() :ruby end
- lambda { Struct.new(:animal, obj) }.should raise_error(TypeError)
+ -> { Struct.new(:animal, obj) }.should raise_error(TypeError)
end
it "processes passed block with instance_eval" do
@@ -111,6 +132,8 @@ describe "Struct.new" do
it "creates a constant in subclass' namespace" do
struct = StructClasses::Apple.new('Computer', :size)
struct.should == StructClasses::Apple::Computer
+ ensure
+ StructClasses::Apple.send(:remove_const, :Computer)
end
it "creates an instance" do
@@ -128,67 +151,113 @@ describe "Struct.new" do
end
it "fails with too many arguments" do
- lambda { StructClasses::Ruby.new('2.0', 'i686', true) }.should raise_error(ArgumentError)
+ -> { StructClasses::Ruby.new('2.0', 'i686', true) }.should raise_error(ArgumentError)
end
- it "passes a hash as a normal argument" do
+ it "accepts keyword arguments to initialize" do
type = Struct.new(:args)
- obj = type.new(keyword: :arg)
- obj2 = type.new(*[{keyword: :arg}])
+ obj = type.new(args: 42)
+ obj2 = type.new(42)
obj.should == obj2
- obj.args.should == {keyword: :arg}
- obj2.args.should == {keyword: :arg}
+ obj.args.should == 42
+ obj2.args.should == 42
end
- end
- ruby_version_is "2.5" do
- context "keyword_init: true option" do
- before :all do
- @struct_with_kwa = Struct.new(:name, :legs, keyword_init: true)
- @struct_without_kwa = Struct.new(:name, :legs, keyword_init: false)
+ context "given positional and keyword arguments" do
+ it "treats keyword arguments as a positional parameter" do
+ type = Struct.new(:a, :b)
+ s = type.new("a", b: "b")
+ s.a.should == "a"
+ s.b.should == {b: "b"}
+
+ type = Struct.new(:a, :b, :c)
+ s = type.new("a", b: "b", c: "c")
+ s.a.should == "a"
+ s.b.should == {b: "b", c: "c"}
+ s.c.should == nil
end
- it "creates a class that accepts keyword arguments to initialize" do
- obj = @struct_with_kwa.new(name: "elefant", legs: 4)
- obj.name.should == "elefant"
- obj.legs.should == 4
+ it "ignores empty keyword arguments" do
+ type = Struct.new(:a, :b)
+ h = {}
+ s = type.new("a", **h)
+
+ s.a.should == "a"
+ s.b.should == nil
end
- describe "new class instantiation" do
- it "accepts arguments as hash as well" do
- obj = @struct_with_kwa.new({name: "elefant", legs: 4})
- obj.name.should == "elefant"
- obj.legs.should == 4
- end
+ it "raises ArgumentError when all struct attribute values are specified" do
+ type = Struct.new(:a, :b)
+ -> { type.new("a", "b", c: "c") }.should raise_error(ArgumentError, "struct size differs")
+ end
+ end
+ end
- it "raises ArgumentError when passed not declared keyword argument" do
- -> {
- @struct_with_kwa.new(name: "elefant", legs: 4, foo: "bar")
- }.should raise_error(ArgumentError, /unknown keywords: foo/)
- end
+ context "keyword_init: true option" do
+ before :all do
+ @struct_with_kwa = Struct.new(:name, :legs, keyword_init: true)
+ end
- it "raises ArgumentError when passed a list of arguments" do
- -> {
- @struct_with_kwa.new("elefant", 4)
- }.should raise_error(ArgumentError, /wrong number of arguments/)
- end
+ it "creates a class that accepts keyword arguments to initialize" do
+ obj = @struct_with_kwa.new(name: "elefant", legs: 4)
+ obj.name.should == "elefant"
+ obj.legs.should == 4
+ end
- it "raises ArgumentError when passed a single non-hash argument" do
- -> {
- @struct_with_kwa.new("elefant")
- }.should raise_error(ArgumentError, /wrong number of arguments/)
- end
- end
+ it "raises when there is a duplicate member" do
+ -> { Struct.new(:foo, :foo, keyword_init: true) }.should raise_error(ArgumentError, "duplicate member: foo")
end
- context "keyword_init: false option" do
- it "behaves like it does without :keyword_init option" do
- obj = @struct_without_kwa.new("elefant", 4)
+ describe "new class instantiation" do
+ it "accepts arguments as hash as well" do
+ obj = @struct_with_kwa.new({name: "elefant", legs: 4})
obj.name.should == "elefant"
obj.legs.should == 4
end
+
+ it "allows missing arguments" do
+ obj = @struct_with_kwa.new(name: "elefant")
+ obj.name.should == "elefant"
+ obj.legs.should be_nil
+ end
+
+ it "allows no arguments" do
+ obj = @struct_with_kwa.new
+ obj.name.should be_nil
+ obj.legs.should be_nil
+ end
+
+ it "raises ArgumentError when passed not declared keyword argument" do
+ -> {
+ @struct_with_kwa.new(name: "elefant", legs: 4, foo: "bar")
+ }.should raise_error(ArgumentError, /unknown keywords: foo/)
+ end
+
+ it "raises ArgumentError when passed a list of arguments" do
+ -> {
+ @struct_with_kwa.new("elefant", 4)
+ }.should raise_error(ArgumentError, /wrong number of arguments/)
+ end
+
+ it "raises ArgumentError when passed a single non-hash argument" do
+ -> {
+ @struct_with_kwa.new("elefant")
+ }.should raise_error(ArgumentError, /wrong number of arguments/)
+ end
+ end
+ end
+
+ context "keyword_init: false option" do
+ before :all do
+ @struct_without_kwa = Struct.new(:name, :legs, keyword_init: false)
+ end
+
+ it "behaves like it does without :keyword_init option" do
+ obj = @struct_without_kwa.new("elefant", 4)
+ obj.name.should == "elefant"
+ obj.legs.should == 4
end
end
end
diff --git a/spec/ruby/core/struct/shared/dup.rb b/spec/ruby/core/struct/shared/dup.rb
new file mode 100644
index 0000000000..994f3f443e
--- /dev/null
+++ b/spec/ruby/core/struct/shared/dup.rb
@@ -0,0 +1,9 @@
+describe :struct_dup, shared: true do
+ it "duplicates members" do
+ klass = Struct.new(:foo, :bar)
+ instance = klass.new(14, 2)
+ duped = instance.send(@method)
+ duped.foo.should == 14
+ duped.bar.should == 2
+ end
+end
diff --git a/spec/ruby/core/struct/shared/inspect.rb b/spec/ruby/core/struct/shared/inspect.rb
index 90594a5452..1a0fb6a6b2 100644
--- a/spec/ruby/core/struct/shared/inspect.rb
+++ b/spec/ruby/core/struct/shared/inspect.rb
@@ -1,5 +1,40 @@
describe :struct_inspect, shared: true do
+ it "returns a string representation showing members and values" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ car.send(@method).should == '#<struct StructClasses::Car make="Ford", model="Ranger", year=nil>'
+ end
+
it "returns a string representation without the class name for anonymous structs" do
Struct.new(:a).new("").send(@method).should == '#<struct a="">'
end
+
+ it "returns a string representation without the class name for structs nested in anonymous classes" do
+ c = Class.new
+ c.class_eval <<~DOC
+ class Foo < Struct.new(:a); end
+ DOC
+
+ c::Foo.new("").send(@method).should == '#<struct a="">'
+ end
+
+ it "returns a string representation without the class name for structs nested in anonymous modules" do
+ m = Module.new
+ m.module_eval <<~DOC
+ class Foo < Struct.new(:a); end
+ DOC
+
+ m::Foo.new("").send(@method).should == '#<struct a="">'
+ end
+
+ it "does not call #name method" do
+ struct = StructClasses::StructWithOverriddenName.new("")
+ struct.send(@method).should == '#<struct StructClasses::StructWithOverriddenName a="">'
+ end
+
+ it "does not call #name method when struct is anonymous" do
+ struct = Struct.new(:a)
+ def struct.name; "A"; end
+
+ struct.new("").send(@method).should == '#<struct a="">'
+ end
end
diff --git a/spec/ruby/core/struct/struct_spec.rb b/spec/ruby/core/struct/struct_spec.rb
index 11e619dcde..1b6a4488ce 100644
--- a/spec/ruby/core/struct/struct_spec.rb
+++ b/spec/ruby/core/struct/struct_spec.rb
@@ -21,7 +21,7 @@ describe "Struct anonymous class instance methods" do
it "reader method should not interfere with undefined methods" do
car = StructClasses::Car.new('Ford', 'Ranger')
- lambda { car.something_weird }.should raise_error(NoMethodError)
+ -> { car.something_weird }.should raise_error(NoMethodError)
end
it "writer method be a synonym for []=" do
@@ -33,6 +33,13 @@ describe "Struct anonymous class instance methods" do
car['model'].should == 'F150'
car[1].should == 'F150'
end
+
+ it "writer methods raise a FrozenError on a frozen struct" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+ car.freeze
+
+ -> { car.model = 'Escape' }.should raise_error(FrozenError)
+ end
end
describe "Struct subclasses" do
diff --git a/spec/ruby/core/struct/to_h_spec.rb b/spec/ruby/core/struct/to_h_spec.rb
index b5f4e40701..861ce3f49d 100644
--- a/spec/ruby/core/struct/to_h_spec.rb
+++ b/spec/ruby/core/struct/to_h_spec.rb
@@ -13,46 +13,56 @@ describe "Struct#to_h" do
car.make.should == 'Ford'
end
- ruby_version_is "2.6" do
- context "with block" do
- it "converts [key, value] pairs returned by the block to a hash" do
- car = StructClasses::Car.new('Ford', 'Ranger')
-
- h = car.to_h { |k, v| [k.to_s, "#{v}".downcase] }
- h.should == { "make" => "ford", "model" => "ranger", "year" => "" }
- end
-
- it "raises ArgumentError if block returns longer or shorter array" do
- -> do
- StructClasses::Car.new.to_h { |k, v| [k.to_s, "#{v}".downcase, 1] }
- end.should raise_error(ArgumentError, /element has wrong array length/)
-
- -> do
- StructClasses::Car.new.to_h { |k, v| [k] }
- end.should raise_error(ArgumentError, /element has wrong array length/)
- end
-
- it "raises TypeError if block returns something other than Array" do
- -> do
- StructClasses::Car.new.to_h { |k, v| "not-array" }
- end.should raise_error(TypeError, /wrong element type String/)
- end
-
- it "coerces returned pair to Array with #to_ary" do
- x = mock('x')
- x.stub!(:to_ary).and_return([:b, 'b'])
-
- StructClasses::Car.new.to_h { |k| x }.should == { :b => 'b' }
- end
-
- it "does not coerce returned pair to Array with #to_a" do
- x = mock('x')
- x.stub!(:to_a).and_return([:b, 'b'])
-
- -> do
- StructClasses::Car.new.to_h { |k| x }
- end.should raise_error(TypeError, /wrong element type MockObject/)
- end
+ context "with block" do
+ it "converts [key, value] pairs returned by the block to a hash" do
+ car = StructClasses::Car.new('Ford', 'Ranger')
+
+ h = car.to_h { |k, v| [k.to_s, "#{v}".downcase] }
+ h.should == { "make" => "ford", "model" => "ranger", "year" => "" }
+ end
+
+ it "passes to a block each pair's key and value as separate arguments" do
+ s = StructClasses::Ruby.new('3.2.4', 'macos')
+
+ ScratchPad.record []
+ s.to_h { |k, v| ScratchPad << [k, v]; [k, v] }
+ ScratchPad.recorded.sort.should == [[:platform, 'macos'], [:version, '3.2.4']]
+
+ ScratchPad.record []
+ s.to_h { |*args| ScratchPad << args; [args[0], args[1]] }
+ ScratchPad.recorded.sort.should == [[:platform, 'macos'], [:version, '3.2.4']]
+ end
+
+ it "raises ArgumentError if block returns longer or shorter array" do
+ -> do
+ StructClasses::Car.new.to_h { |k, v| [k.to_s, "#{v}".downcase, 1] }
+ end.should raise_error(ArgumentError, /element has wrong array length/)
+
+ -> do
+ StructClasses::Car.new.to_h { |k, v| [k] }
+ end.should raise_error(ArgumentError, /element has wrong array length/)
+ end
+
+ it "raises TypeError if block returns something other than Array" do
+ -> do
+ StructClasses::Car.new.to_h { |k, v| "not-array" }
+ end.should raise_error(TypeError, /wrong element type String/)
+ end
+
+ it "coerces returned pair to Array with #to_ary" do
+ x = mock('x')
+ x.stub!(:to_ary).and_return([:b, 'b'])
+
+ StructClasses::Car.new.to_h { |k| x }.should == { :b => 'b' }
+ end
+
+ it "does not coerce returned pair to Array with #to_a" do
+ x = mock('x')
+ x.stub!(:to_a).and_return([:b, 'b'])
+
+ -> do
+ StructClasses::Car.new.to_h { |k| x }
+ end.should raise_error(TypeError, /wrong element type MockObject/)
end
end
end
diff --git a/spec/ruby/core/struct/values_at_spec.rb b/spec/ruby/core/struct/values_at_spec.rb
index f9602d53f7..5e5a496600 100644
--- a/spec/ruby/core/struct/values_at_spec.rb
+++ b/spec/ruby/core/struct/values_at_spec.rb
@@ -1,16 +1,59 @@
require_relative '../../spec_helper'
require_relative 'fixtures/classes'
+# Should be synchronized with core/array/values_at_spec.rb
describe "Struct#values_at" do
- it "returns an array of values" do
+ before do
clazz = Struct.new(:name, :director, :year)
- movie = clazz.new('Sympathy for Mr. Vengence', 'Chan-wook Park', 2002)
- movie.values_at(0, 1).should == ['Sympathy for Mr. Vengence', 'Chan-wook Park']
- movie.values_at(0..2).should == ['Sympathy for Mr. Vengence', 'Chan-wook Park', 2002]
+ @movie = clazz.new('Sympathy for Mr. Vengeance', 'Chan-wook Park', 2002)
+ end
+
+ context "when passed a list of Integers" do
+ it "returns an array containing each value given by one of integers" do
+ @movie.values_at(0, 1).should == ['Sympathy for Mr. Vengeance', 'Chan-wook Park']
+ end
+
+ it "raises IndexError if any of integers is out of range" do
+ -> { @movie.values_at(3) }.should raise_error(IndexError, "offset 3 too large for struct(size:3)")
+ -> { @movie.values_at(-4) }.should raise_error(IndexError, "offset -4 too small for struct(size:3)")
+ end
+ end
+
+ context "when passed an integer Range" do
+ it "returns an array containing each value given by the elements of the range" do
+ @movie.values_at(0..2).should == ['Sympathy for Mr. Vengeance', 'Chan-wook Park', 2002]
+ end
+
+ it "fills with nil values for range elements larger than the structure" do
+ @movie.values_at(0..3).should == ['Sympathy for Mr. Vengeance', 'Chan-wook Park', 2002, nil]
+ end
+
+ it "raises RangeError if any element of the range is negative and out of range" do
+ -> { @movie.values_at(-4..3) }.should raise_error(RangeError, "-4..3 out of range")
+ end
+
+ it "supports endless Range" do
+ @movie.values_at(0..).should == ["Sympathy for Mr. Vengeance", "Chan-wook Park", 2002]
+ end
+
+ it "supports beginningless Range" do
+ @movie.values_at(..2).should == ["Sympathy for Mr. Vengeance", "Chan-wook Park", 2002]
+ end
+ end
+
+ it "supports multiple integer Ranges" do
+ @movie.values_at(0..2, 1..2).should == ['Sympathy for Mr. Vengeance', 'Chan-wook Park', 2002, 'Chan-wook Park', 2002]
+ end
+
+ it "supports mixing integer Ranges and Integers" do
+ @movie.values_at(0..2, 2).should == ['Sympathy for Mr. Vengeance', 'Chan-wook Park', 2002, 2002]
+ end
+
+ it "returns a new empty Array if no arguments given" do
+ @movie.values_at().should == []
end
it "fails when passed unsupported types" do
- car = StructClasses::Car.new('Ford', 'Ranger')
- lambda { car.values_at('make') }.should raise_error(TypeError)
+ -> { @movie.values_at('make') }.should raise_error(TypeError, "no implicit conversion of String into Integer")
end
end