diff options
Diffstat (limited to 'spec/ruby/core/struct')
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 |
