diff options
Diffstat (limited to 'spec/ruby/core')
39 files changed, 939 insertions, 94 deletions
diff --git a/spec/ruby/core/binding/shared/clone.rb b/spec/ruby/core/binding/shared/clone.rb index 1224b8ec7d..2d854fce96 100644 --- a/spec/ruby/core/binding/shared/clone.rb +++ b/spec/ruby/core/binding/shared/clone.rb @@ -40,7 +40,7 @@ describe :binding_clone, shared: true do end it "copies the finalizer" do - code = <<-RUBY + code = <<-'RUBY' obj = binding ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) diff --git a/spec/ruby/core/dir/chdir_spec.rb b/spec/ruby/core/dir/chdir_spec.rb index 69a8b40415..015386a902 100644 --- a/spec/ruby/core/dir/chdir_spec.rb +++ b/spec/ruby/core/dir/chdir_spec.rb @@ -95,10 +95,10 @@ describe "Dir.chdir" do end it "raises an Errno::ENOENT if the original directory no longer exists" do - dir1 = tmp('/testdir1') - dir2 = tmp('/testdir2') - File.should_not.exist?(dir1) - File.should_not.exist?(dir2) + dir1 = tmp('testdir1') + dir2 = tmp('testdir2') + Dir.should_not.exist?(dir1) + Dir.should_not.exist?(dir2) Dir.mkdir dir1 Dir.mkdir dir2 begin @@ -108,8 +108,8 @@ describe "Dir.chdir" do end }.should raise_error(Errno::ENOENT) ensure - Dir.unlink dir1 if File.exist?(dir1) - Dir.unlink dir2 if File.exist?(dir2) + Dir.unlink dir1 if Dir.exist?(dir1) + Dir.unlink dir2 if Dir.exist?(dir2) end end @@ -177,28 +177,29 @@ ruby_version_is '3.3' do dir.close end - it "raises an Errno::ENOENT if the original directory no longer exists" do - dir_name1 = tmp('/testdir1') - dir_name2 = tmp('/testdir2') - File.should_not.exist?(dir_name1) - File.should_not.exist?(dir_name2) - Dir.mkdir dir_name1 - Dir.mkdir dir_name2 + platform_is_not :windows do + it "does not raise an Errno::ENOENT if the original directory no longer exists" do + dir_name1 = tmp('testdir1') + dir_name2 = tmp('testdir2') + Dir.should_not.exist?(dir_name1) + Dir.should_not.exist?(dir_name2) + Dir.mkdir dir_name1 + Dir.mkdir dir_name2 - dir1 = Dir.new(dir_name1) + dir2 = Dir.new(dir_name2) - begin - -> { - dir1.chdir do - Dir.chdir(dir_name2) { Dir.unlink dir_name1 } + begin + Dir.chdir(dir_name1) do + dir2.chdir { Dir.unlink dir_name1 } end - }.should raise_error(Errno::ENOENT) + Dir.pwd.should == @original + ensure + Dir.unlink dir_name1 if Dir.exist?(dir_name1) + Dir.unlink dir_name2 if Dir.exist?(dir_name2) + end ensure - Dir.unlink dir_name1 if File.exist?(dir_name1) - Dir.unlink dir_name2 if File.exist?(dir_name2) + dir2.close end - ensure - dir1.close end it "always returns to the original directory when given a block" do diff --git a/spec/ruby/core/dir/for_fd_spec.rb b/spec/ruby/core/dir/for_fd_spec.rb index b2af7bda84..3afcc7e949 100644 --- a/spec/ruby/core/dir/for_fd_spec.rb +++ b/spec/ruby/core/dir/for_fd_spec.rb @@ -43,8 +43,8 @@ ruby_version_is '3.3' do it "calls #to_int to convert a value to an Integer" do dir = Dir.new(DirSpecs.mock_dir) - obj = Object.new - obj.singleton_class.define_method(:to_int) { dir.fileno } + obj = mock("fd") + obj.should_receive(:to_int).and_return(dir.fileno) dir_new = Dir.for_fd(obj) dir_new.fileno.should == dir.fileno diff --git a/spec/ruby/core/enumerable/collect_concat_spec.rb b/spec/ruby/core/enumerable/collect_concat_spec.rb index 6e34c9eb93..59317cfe34 100644 --- a/spec/ruby/core/enumerable/collect_concat_spec.rb +++ b/spec/ruby/core/enumerable/collect_concat_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/collect_concat' describe "Enumerable#collect_concat" do - it_behaves_like :enumerable_collect_concat , :collect_concat + it_behaves_like :enumerable_collect_concat, :collect_concat end diff --git a/spec/ruby/core/enumerable/collect_spec.rb b/spec/ruby/core/enumerable/collect_spec.rb index 1016b67798..cfa2895cce 100644 --- a/spec/ruby/core/enumerable/collect_spec.rb +++ b/spec/ruby/core/enumerable/collect_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/collect' describe "Enumerable#collect" do - it_behaves_like :enumerable_collect , :collect + it_behaves_like :enumerable_collect, :collect end diff --git a/spec/ruby/core/enumerable/detect_spec.rb b/spec/ruby/core/enumerable/detect_spec.rb index e912134fed..6959aadc44 100644 --- a/spec/ruby/core/enumerable/detect_spec.rb +++ b/spec/ruby/core/enumerable/detect_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/find' describe "Enumerable#detect" do - it_behaves_like :enumerable_find , :detect + it_behaves_like :enumerable_find, :detect end diff --git a/spec/ruby/core/enumerable/entries_spec.rb b/spec/ruby/core/enumerable/entries_spec.rb index 83232cfa06..2de4fc756a 100644 --- a/spec/ruby/core/enumerable/entries_spec.rb +++ b/spec/ruby/core/enumerable/entries_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/entries' describe "Enumerable#entries" do - it_behaves_like :enumerable_entries , :entries + it_behaves_like :enumerable_entries, :entries end diff --git a/spec/ruby/core/enumerable/filter_spec.rb b/spec/ruby/core/enumerable/filter_spec.rb index 7e4f8c0b50..c9ee23c541 100644 --- a/spec/ruby/core/enumerable/filter_spec.rb +++ b/spec/ruby/core/enumerable/filter_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/find_all' describe "Enumerable#filter" do - it_behaves_like(:enumerable_find_all , :filter) + it_behaves_like(:enumerable_find_all, :filter) end diff --git a/spec/ruby/core/enumerable/find_all_spec.rb b/spec/ruby/core/enumerable/find_all_spec.rb index ce9058fe77..9cd635f247 100644 --- a/spec/ruby/core/enumerable/find_all_spec.rb +++ b/spec/ruby/core/enumerable/find_all_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/find_all' describe "Enumerable#find_all" do - it_behaves_like :enumerable_find_all , :find_all + it_behaves_like :enumerable_find_all, :find_all end diff --git a/spec/ruby/core/enumerable/find_spec.rb b/spec/ruby/core/enumerable/find_spec.rb index 25aa3bf103..5ddebc05f8 100644 --- a/spec/ruby/core/enumerable/find_spec.rb +++ b/spec/ruby/core/enumerable/find_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/find' describe "Enumerable#find" do - it_behaves_like :enumerable_find , :find + it_behaves_like :enumerable_find, :find end diff --git a/spec/ruby/core/enumerable/flat_map_spec.rb b/spec/ruby/core/enumerable/flat_map_spec.rb index a294b9ddad..bd07eab6c5 100644 --- a/spec/ruby/core/enumerable/flat_map_spec.rb +++ b/spec/ruby/core/enumerable/flat_map_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/collect_concat' describe "Enumerable#flat_map" do - it_behaves_like :enumerable_collect_concat , :flat_map + it_behaves_like :enumerable_collect_concat, :flat_map end diff --git a/spec/ruby/core/enumerable/map_spec.rb b/spec/ruby/core/enumerable/map_spec.rb index d65aec238c..98a70781cf 100644 --- a/spec/ruby/core/enumerable/map_spec.rb +++ b/spec/ruby/core/enumerable/map_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/collect' describe "Enumerable#map" do - it_behaves_like :enumerable_collect , :map + it_behaves_like :enumerable_collect, :map end diff --git a/spec/ruby/core/enumerable/select_spec.rb b/spec/ruby/core/enumerable/select_spec.rb index 11168eb42e..7fc61926f9 100644 --- a/spec/ruby/core/enumerable/select_spec.rb +++ b/spec/ruby/core/enumerable/select_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/find_all' describe "Enumerable#select" do - it_behaves_like :enumerable_find_all , :select + it_behaves_like :enumerable_find_all, :select end diff --git a/spec/ruby/core/enumerable/to_a_spec.rb b/spec/ruby/core/enumerable/to_a_spec.rb index 0f3060dc48..723f922574 100644 --- a/spec/ruby/core/enumerable/to_a_spec.rb +++ b/spec/ruby/core/enumerable/to_a_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/entries' describe "Enumerable#to_a" do - it_behaves_like :enumerable_entries , :to_a + it_behaves_like :enumerable_entries, :to_a end diff --git a/spec/ruby/core/exception/fixtures/common.rb b/spec/ruby/core/exception/fixtures/common.rb index 1e243098bd..3d8a3c3430 100644 --- a/spec/ruby/core/exception/fixtures/common.rb +++ b/spec/ruby/core/exception/fixtures/common.rb @@ -84,6 +84,9 @@ module NoMethodErrorSpecs class InstanceException < Exception end + + class AClass; end + module AModule; end end class NameErrorSpecs diff --git a/spec/ruby/core/exception/no_method_error_spec.rb b/spec/ruby/core/exception/no_method_error_spec.rb index 26df3338e9..772c569f67 100644 --- a/spec/ruby/core/exception/no_method_error_spec.rb +++ b/spec/ruby/core/exception/no_method_error_spec.rb @@ -67,7 +67,7 @@ describe "NoMethodError#message" do end ruby_version_is ""..."3.3" do - it "calls receiver.inspect only when calling Exception#message" do + it "calls #inspect when calling Exception#message" do ScratchPad.record [] test_class = Class.new do def inspect @@ -76,19 +76,163 @@ describe "NoMethodError#message" do end end instance = test_class.new + begin instance.bar - rescue Exception => e - e.name.should == :bar - ScratchPad.recorded.should == [] - e.message.should =~ /undefined method.+\bbar\b/ + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for <inspect>:#<Class:0x\h+>$/ ScratchPad.recorded.should == [:inspect_called] end end + + it "fallbacks to a simpler representation of the receiver when receiver.inspect raises an exception" do + test_class = Class.new do + def inspect + raise NoMethodErrorSpecs::InstanceException + end + end + instance = test_class.new + + begin + instance.bar + rescue NoMethodError => error + message = error.message + message.should =~ /undefined method.+\bbar\b/ + message.should include test_class.inspect + end + end + + it "uses #name to display the receiver if it is a class" do + klass = Class.new { def self.name; "MyClass"; end } + + begin + klass.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for MyClass:Class$/ + end + end + + it "uses #name to display the receiver if it is a module" do + mod = Module.new { def self.name; "MyModule"; end } + + begin + mod.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for MyModule:Module$/ + end + end end ruby_version_is "3.3" do - it "does not call receiver.inspect even when calling Exception#message" do + it "uses a literal name when receiver is nil" do + begin + nil.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for nil\Z/ + end + end + + it "uses a literal name when receiver is true" do + begin + true.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for true\Z/ + end + end + + it "uses a literal name when receiver is false" do + begin + false.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for false\Z/ + end + end + + it "uses #name when receiver is a class" do + klass = Class.new { def self.name; "MyClass"; end } + + begin + klass.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for class MyClass\Z/ + end + end + + it "uses class' string representation when receiver is an anonymous class" do + klass = Class.new + + begin + klass.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for class #<Class:0x\h+>\Z/ + end + end + + it "uses class' string representation when receiver is a singleton class" do + obj = Object.new + singleton_class = obj.singleton_class + + begin + singleton_class.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for class #<Class:#<Object:0x\h+>>\Z/ + end + end + + it "uses #name when receiver is a module" do + mod = Module.new { def self.name; "MyModule"; end } + + begin + mod.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for module MyModule\Z/ + end + end + + it "uses module's string representation when receiver is an anonymous module" do + m = Module.new + + begin + m.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for module #<Module:0x\h+>\Z/ + end + end + + it "uses class #name when receiver is an ordinary object" do + klass = Class.new { def self.name; "MyClass"; end } + instance = klass.new + + begin + instance.bar + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for an instance of MyClass\Z/ + end + end + + it "uses class string representation when receiver is an instance of anonymous class" do + klass = Class.new + instance = klass.new + + begin + instance.bar + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for an instance of #<Class:0x\h+>\Z/ + end + end + + it "uses class name when receiver has a singleton class" do + instance = NoMethodErrorSpecs::NoMethodErrorA.new + def instance.foo; end + + begin + instance.bar + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for #<NoMethodErrorSpecs::NoMethodErrorA:0x\h+>\Z/ + end + end + + it "does not call #inspect when calling Exception#message" do ScratchPad.record [] test_class = Class.new do def inspect @@ -97,47 +241,29 @@ describe "NoMethodError#message" do end end instance = test_class.new + begin instance.bar - rescue Exception => e - e.name.should == :bar - ScratchPad.recorded.should == [] - e.message.should =~ /undefined method.+\bbar\b/ + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for an instance of #<Class:0x\h+>\Z/ ScratchPad.recorded.should == [] end end - end - it "fallbacks to a simpler representation of the receiver when receiver.inspect raises an exception" do - test_class = Class.new do - def inspect - raise NoMethodErrorSpecs::InstanceException - end - end - instance = test_class.new - begin - instance.bar - rescue Exception => e - e.name.should == :bar - message = e.message - message.should =~ /undefined method.+\bbar\b/ - message.should include test_class.inspect - end - end + it "does not truncate long class names" do + class_name = 'ExceptionSpecs::A' + 'a'*100 - it "uses #name to display the receiver if it is a class or a module" do - klass = Class.new { def self.name; "MyClass"; end } - begin - klass.foo - rescue NoMethodError => error - error.message.lines.first.chomp.should =~ /^undefined method [`']foo' for / - end + begin + eval <<~RUBY + class #{class_name} + end - mod = Module.new { def self.name; "MyModule"; end } - begin - mod.foo - rescue NoMethodError => error - error.message.lines.first.chomp.should =~ /^undefined method [`']foo' for / + obj = #{class_name}.new + obj.foo + RUBY + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for an instance of #{class_name}\Z/ + end end end end diff --git a/spec/ruby/core/fiber/storage_spec.rb b/spec/ruby/core/fiber/storage_spec.rb index d5603f006c..03e9e3b822 100644 --- a/spec/ruby/core/fiber/storage_spec.rb +++ b/spec/ruby/core/fiber/storage_spec.rb @@ -90,15 +90,38 @@ ruby_version_is "3.2" do key = :"#{self.class.name}#.#{self.object_id}" Fiber.new { Fiber[key] = 42; Fiber[key] }.resume.should == 42 end + + it "can't use invalid keys" do + invalid_keys = [Object.new, 12] + invalid_keys.each do |key| + -> { Fiber[key] }.should raise_error(TypeError) + end + end end ruby_bug "#20978", "3.2"..."3.4" do it "can use keys as strings" do key = Object.new def key.to_str; "Foo"; end - Fiber[key] = 42 - Fiber["Foo"].should == 42 + Fiber.new { Fiber[key] = 42; Fiber["Foo"] }.resume.should == 42 end + + it "converts a String key into a Symbol" do + Fiber.new { Fiber["key"] = 42; Fiber[:key] }.resume.should == 42 + Fiber.new { Fiber[:key] = 42; Fiber["key"] }.resume.should == 42 + end + + it "can use any object that responds to #to_str as a key" do + key = mock("key") + key.should_receive(:to_str).twice.and_return("key") + Fiber.new { Fiber[key] = 42; Fiber[key] }.resume.should == 42 + end + end + + it "does not call #to_sym on the key" do + key = mock("key") + key.should_not_receive(:to_sym) + -> { Fiber[key] }.should raise_error(TypeError) end it "can access the storage of the parent fiber" do diff --git a/spec/ruby/core/integer/divide_spec.rb b/spec/ruby/core/integer/divide_spec.rb index a878c4668c..665f4d57be 100644 --- a/spec/ruby/core/integer/divide_spec.rb +++ b/spec/ruby/core/integer/divide_spec.rb @@ -12,6 +12,17 @@ describe "Integer#/" do it "supports dividing negative numbers" do (-1 / 10).should == -1 + (-1 / 10**10).should == -1 + (-1 / 10**20).should == -1 + end + + it "preservers sign correctly" do + (4 / 3).should == 1 + (4 / -3).should == -2 + (-4 / 3).should == -2 + (-4 / -3).should == 1 + (0 / -3).should == 0 + (0 / 3).should == 0 end it "returns result the same class as the argument" do @@ -58,6 +69,15 @@ describe "Integer#/" do ((10**50) / -(10**40 + 1)).should == -10000000000 end + it "preservers sign correctly" do + (4 / bignum_value).should == 0 + (4 / -bignum_value).should == -1 + (-4 / bignum_value).should == -1 + (-4 / -bignum_value).should == 0 + (0 / bignum_value).should == 0 + (0 / -bignum_value).should == 0 + end + it "returns self divided by Float" do not_supported_on :opal do (bignum_value(88) / 4294967295.0).should be_close(4294967297.0, TOLERANCE) diff --git a/spec/ruby/core/kernel/Float_spec.rb b/spec/ruby/core/kernel/Float_spec.rb index 0f83cb5824..6cedfe0617 100644 --- a/spec/ruby/core/kernel/Float_spec.rb +++ b/spec/ruby/core/kernel/Float_spec.rb @@ -157,6 +157,24 @@ describe :kernel_float, shared: true do @object.send(:Float, "1\t\n").should == 1.0 end + ruby_version_is ""..."3.4" do + it "raises ArgumentError if a fractional part is missing" do + -> { @object.send(:Float, "1.") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "+1.") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "-1.") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "1.e+0") }.should raise_error(ArgumentError) + end + end + + ruby_version_is "3.4" do + it "allows String representation without a fractional part" do + @object.send(:Float, "1.").should == 1.0 + @object.send(:Float, "+1.").should == 1.0 + @object.send(:Float, "-1.").should == -1.0 + @object.send(:Float, "1.e+0").should == 1.0 + end + end + %w(e E).each do |e| it "raises an ArgumentError if #{e} is the trailing character" do -> { @object.send(:Float, "2#{e}") }.should raise_error(ArgumentError) @@ -280,7 +298,7 @@ describe :kernel_float, shared: true do nan2.should equal(nan) end - it "returns the identical Infinity if to_f is called and it returns Infinity" do + it "returns the identical Infinity if #to_f is called and it returns Infinity" do infinity = infinity_value (infinity_to_f = mock('Infinity')).should_receive(:to_f).once.and_return(infinity) infinity2 = @object.send(:Float, infinity_to_f) diff --git a/spec/ruby/core/kernel/is_a_spec.rb b/spec/ruby/core/kernel/is_a_spec.rb index dc69766f83..bd8c96529a 100644 --- a/spec/ruby/core/kernel/is_a_spec.rb +++ b/spec/ruby/core/kernel/is_a_spec.rb @@ -2,5 +2,5 @@ require_relative '../../spec_helper' require_relative 'shared/kind_of' describe "Kernel#is_a?" do - it_behaves_like :kernel_kind_of , :is_a? + it_behaves_like :kernel_kind_of, :is_a? end diff --git a/spec/ruby/core/kernel/kind_of_spec.rb b/spec/ruby/core/kernel/kind_of_spec.rb index 734035620c..c988edccb5 100644 --- a/spec/ruby/core/kernel/kind_of_spec.rb +++ b/spec/ruby/core/kernel/kind_of_spec.rb @@ -2,5 +2,5 @@ require_relative '../../spec_helper' require_relative 'shared/kind_of' describe "Kernel#kind_of?" do - it_behaves_like :kernel_kind_of , :kind_of? + it_behaves_like :kernel_kind_of, :kind_of? end diff --git a/spec/ruby/core/marshal/dump_spec.rb b/spec/ruby/core/marshal/dump_spec.rb index 05e473c64e..716ba0e946 100644 --- a/spec/ruby/core/marshal/dump_spec.rb +++ b/spec/ruby/core/marshal/dump_spec.rb @@ -47,6 +47,11 @@ describe "Marshal.dump" do Marshal.dump(-2**31 - 1).should == "\x04\bl-\a\x01\x00\x00\x80" end end + + it "does not use object links for objects repeatedly dumped" do + Marshal.dump([0, 0]).should == "\x04\b[\ai\x00i\x00" + Marshal.dump([2**16, 2**16]).should == "\x04\b[\ai\x03\x00\x00\x01i\x03\x00\x00\x01" + end end describe "with a Symbol" do @@ -93,6 +98,11 @@ describe "Marshal.dump" do value = [*value, value[0]] Marshal.dump(value).should == "\x04\b[\b#{symbol1}#{symbol2};\x00" end + + it "uses symbol links for objects repeatedly dumped" do + symbol = :foo + Marshal.dump([symbol, symbol]).should == "\x04\b[\a:\bfoo;\x00" # ;\x00 is a link to the symbol object + end end describe "with an object responding to #marshal_dump" do @@ -108,6 +118,20 @@ describe "Marshal.dump" do it "raises TypeError if an Object is an instance of an anonymous class" do -> { Marshal.dump(Class.new(UserMarshal).new) }.should raise_error(TypeError, /can't dump anonymous class/) end + + it "uses object links for objects repeatedly dumped" do + obj = UserMarshal.new + Marshal.dump([obj, obj]).should == "\x04\b[\aU:\x10UserMarshal:\tdata@\x06" # @\x06 is a link to the object + end + + it "adds instance variables of a dumped object after the object itself into the objects table" do + value = "<foo>" + obj = MarshalSpec::UserMarshalDumpWithIvar.new("string", value) + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\t, that means Integer 4) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bU:)MarshalSpec::UserMarshalDumpWithIvarI[\x06\"\vstring\x06:\t@foo\"\n<foo>@\x06@\t" + end end describe "with an object responding to #_dump" do @@ -166,6 +190,20 @@ describe "Marshal.dump" do Marshal.dump([a, a]).should == "\x04\b[\aIu:\x17MarshalSpec::M1::A\v<dump>\x06:\t@foo\"\bbar#{reference}" end + it "uses object links for objects repeatedly dumped" do + obj = UserDefined.new + Marshal.dump([obj, obj]).should == "\x04\b[\au:\x10UserDefined\x12\x04\b[\a:\nstuff;\x00@\x06" # @\x06 is a link to the object + end + + it "adds instance variables of a dumped String before the object itself into the objects table" do + value = "<foo>" + obj = MarshalSpec::UserDefinedDumpWithIVars.new(+"string", value) + + # expect a link to the object (@\a, that means Integer 2) is greater than a link + # to the instance variable value (@\x06, that means Integer 1) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bIu:*MarshalSpec::UserDefinedDumpWithIVars\vstring\x06:\t@foo\"\n<foo>@\a@\x06" + end + describe "Core library classes with #_dump returning a String with instance variables" do it "indexes instance variables and then a Time object itself" do t = Time.utc(2022) @@ -198,6 +236,10 @@ describe "Marshal.dump" do Marshal.dump(source_object).should == "\x04\bc,MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Class" end + it "uses object links for objects repeatedly dumped" do + Marshal.dump([String, String]).should == "\x04\b[\ac\vString@\x06" # @\x06 is a link to the object + end + it "raises TypeError with an anonymous Class" do -> { Marshal.dump(Class.new) }.should raise_error(TypeError, /can't dump anonymous class/) end @@ -221,6 +263,10 @@ describe "Marshal.dump" do Marshal.dump(source_object).should == "\x04\bm-MarshalSpec::Multibyte\xE3\x81\x91\xE3\x81\x92\xE3\x81\x93\xE3\x81\x94Module" end + it "uses object links for objects repeatedly dumped" do + Marshal.dump([Marshal, Marshal]).should == "\x04\b[\am\fMarshal@\x06" # @\x06 is a link to the object + end + it "raises TypeError with an anonymous Module" do -> { Marshal.dump(Module.new) }.should raise_error(TypeError, /can't dump anonymous module/) end @@ -239,6 +285,10 @@ describe "Marshal.dump" do [Marshal, nan_value, "\004\bf\bnan"], ].should be_computed_by(:dump) end + + it "uses object links for objects repeatedly dumped" do + Marshal.dump([0.0, 0.0]).should == "\x04\b[\af\x060@\x06" # @\x06 is a link to the float value + end end describe "with a Bignum" do @@ -256,6 +306,11 @@ describe "Marshal.dump" do ].should be_computed_by(:dump) end + it "uses object links for objects repeatedly dumped" do + n = 2**64 + Marshal.dump([n, n]).should == "\x04\b[\al+\n\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00@\x06" # @\x06 is a link to the object + end + it "increases the object links counter" do obj = Object.new object_1_link = "\x06" # representing of (0-based) index=1 (by adding 5 for small Integers) @@ -275,12 +330,22 @@ describe "Marshal.dump" do it "dumps a Rational" do Marshal.dump(Rational(2, 3)).should == "\x04\bU:\rRational[\ai\ai\b" end + + it "uses object links for objects repeatedly dumped" do + r = Rational(2, 3) + Marshal.dump([r, r]).should == "\x04\b[\aU:\rRational[\ai\ai\b@\x06" # @\x06 is a link to the object + end end describe "with a Complex" do it "dumps a Complex" do Marshal.dump(Complex(2, 3)).should == "\x04\bU:\fComplex[\ai\ai\b" end + + it "uses object links for objects repeatedly dumped" do + c = Complex(2, 3) + Marshal.dump([c, c]).should == "\x04\b[\aU:\fComplex[\ai\ai\b@\x06" # @\x06 is a link to the object + end end ruby_version_is "3.2" do @@ -299,6 +364,11 @@ describe "Marshal.dump" do Marshal.dump(obj).should == "\x04\bS:5MarshalSpec::DataSpec::MeasureWithOverriddenName\a:\vamountii:\tunit\"\akm" end + it "uses object links for objects repeatedly dumped" do + d = MarshalSpec::DataSpec::Measure.new(100, 'km') + Marshal.dump([d, d]).should == "\x04\b[\aS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm@\x06" # @\x06 is a link to the object + end + it "raises TypeError with an anonymous Struct" do -> { Marshal.dump(Data.define(:a).new(1)) }.should raise_error(TypeError, /can't dump anonymous class/) end @@ -360,6 +430,21 @@ describe "Marshal.dump" do it "dumps multiple strings using symlinks for the :E (encoding) symbol" do Marshal.dump(["".encode("us-ascii"), "".encode("utf-8")]).should == "\x04\b[\aI\"\x00\x06:\x06EFI\"\x00\x06;\x00T" end + + it "uses object links for objects repeatedly dumped" do + s = "string" + Marshal.dump([s, s]).should == "\x04\b[\a\"\vstring@\x06" # @\x06 is a link to the object + end + + it "adds instance variables after the object itself into the objects table" do + obj = +"string" + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\a, that means Integer 2) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bI\"\vstring\x06:\t@foo\"\n<foo>@\x06@\a" + end end describe "with a Regexp" do @@ -427,6 +512,11 @@ describe "Marshal.dump" do obj = MarshalSpec::RegexpWithOverriddenName.new("") Marshal.dump(obj).should == "\x04\bIC:*MarshalSpec::RegexpWithOverriddenName/\x00\x00\x06:\x06EF" end + + it "uses object links for objects repeatedly dumped" do + r = /\A.\Z/ + Marshal.dump([r, r]).should == "\x04\b[\aI/\n\\A.\\Z\x00\x06:\x06EF@\x06" # @\x06 is a link to the object + end end describe "with an Array" do @@ -462,6 +552,21 @@ describe "Marshal.dump" do obj = MarshalSpec::ArrayWithOverriddenName.new Marshal.dump(obj).should == "\x04\bC:)MarshalSpec::ArrayWithOverriddenName[\x00" end + + it "uses object links for objects repeatedly dumped" do + a = [1] + Marshal.dump([a, a]).should == "\x04\b[\a[\x06i\x06@\x06" # @\x06 is a link to the object + end + + it "adds instance variables after the object itself into the objects table" do + obj = [] + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\a, that means Integer 2) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bI[\x00\x06:\t@foo\"\n<foo>@\x06@\a" + end end describe "with a Hash" do @@ -519,6 +624,21 @@ describe "Marshal.dump" do obj = MarshalSpec::HashWithOverriddenName.new Marshal.dump(obj).should == "\x04\bC:(MarshalSpec::HashWithOverriddenName{\x00" end + + it "uses object links for objects repeatedly dumped" do + h = {a: 1} + Marshal.dump([h, h]).should == "\x04\b[\a{\x06:\x06ai\x06@\x06" # @\x06 is a link to the object + end + + it "adds instance variables after the object itself into the objects table" do + obj = {} + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\a, that means Integer 2) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bI{\x00\x06:\t@foo\"\n<foo>@\x06@\a" + end end describe "with a Struct" do @@ -553,9 +673,24 @@ describe "Marshal.dump" do Marshal.dump(obj).should == "\x04\bS:*MarshalSpec::StructWithOverriddenName\x06:\x06a\"\vmember" end + it "uses object links for objects repeatedly dumped" do + s = Struct::Pyramid.new + Marshal.dump([s, s]).should == "\x04\b[\aS:\x14Struct::Pyramid\x00@\x06" # @\x06 is a link to the object + end + it "raises TypeError with an anonymous Struct" do -> { Marshal.dump(Struct.new(:a).new(1)) }.should raise_error(TypeError, /can't dump anonymous class/) end + + it "adds instance variables after the object itself into the objects table" do + obj = Struct::Pyramid.new + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\a, that means Integer 2) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bIS:\x14Struct::Pyramid\x00\x06:\t@foo\"\n<foo>@\x06@\a" + end end describe "with an Object" do @@ -645,21 +780,45 @@ describe "Marshal.dump" do ObjectSpace.define_finalizer(obj, finalizer.method(:noop)) Marshal.load(Marshal.dump(obj)).class.should == Object end + + it "uses object links for objects repeatedly dumped" do + obj = Object.new + Marshal.dump([obj, obj]).should == "\x04\b[\ao:\vObject\x00@\x06" # @\x06 is a link to the object + end + + it "adds instance variables after the object itself into the objects table" do + obj = Object.new + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\a, that means Integer 2) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bo:\vObject\x06:\t@foo\"\n<foo>@\x06@\a" + end end describe "with a Range" do - it "dumps a Range inclusive of end (with indeterminant order)" do + it "dumps a Range inclusive of end" do dump = Marshal.dump(1..2) + dump.should == "\x04\bo:\nRange\b:\texclF:\nbegini\x06:\bendi\a" + load = Marshal.load(dump) load.should == (1..2) end - it "dumps a Range exclusive of end (with indeterminant order)" do + it "dumps a Range exclusive of end" do dump = Marshal.dump(1...2) + dump.should == "\x04\bo:\nRange\b:\texclT:\nbegini\x06:\bendi\a" + load = Marshal.load(dump) load.should == (1...2) end + it "uses object links for objects repeatedly dumped" do + r = 1..2 + Marshal.dump([r, r]).should == "\x04\b[\ao:\nRange\b:\texclF:\nbegini\x06:\bendi\a@\x06" # @\x06 is a link to the object + end + it "raises TypeError with an anonymous Range subclass" do -> { Marshal.dump(Class.new(Range).new(1, 2)) }.should raise_error(TypeError, /can't dump anonymous class/) end @@ -711,6 +870,26 @@ describe "Marshal.dump" do Marshal.dump(source_object).should == "\x04\bc+MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Time" end + it "uses object links for objects repeatedly dumped" do + # order of the offset and zone instance variables is a subject to change + # and may be different on different CRuby versions + base = Regexp.quote("\x04\b[\aIu:\tTime\r\xF5\xEF\e\x80\x00\x00\x00\x00\a") + offset = Regexp.quote(":\voffseti\x020*:\tzoneI\"\bAST\x06:\x06EF") + zone = Regexp.quote(":\tzoneI\"\bAST\x06:\x06EF:\voffseti\x020*") + instance_variables = /#{offset}|#{zone}/ + Marshal.dump([@t, @t]).should =~ /\A#{base}#{instance_variables}@\a\Z/ # @\a is a link to the object + end + + it "adds instance variables before the object itself into the objects table" do + obj = @utc + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\b, that means Integer 3) is greater than a link + # to the instance variable value (@\x06, that means Integer 1) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bIu:\tTime\r \x00\x1C\xC0\x00\x00\x00\x00\a:\t@foo\"\n<foo>:\tzoneI\"\bUTC\x06:\x06EF@\b@\x06" + end + it "raises TypeError with an anonymous Time subclass" do -> { Marshal.dump(Class.new(Time).now) }.should raise_error(TypeError) end @@ -765,6 +944,21 @@ describe "Marshal.dump" do Marshal.dump(e).should =~ /undefined method [`']foo' for ("":String|an instance of String)/ end + it "uses object links for objects repeatedly dumped" do + e = Exception.new + Marshal.dump([e, e]).should == "\x04\b[\ao:\x0EException\a:\tmesg0:\abt0@\x06" # @\x\a is a link to the object + end + + it "adds instance variables after the object itself into the objects table" do + obj = Exception.new + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\a, that means Integer 2) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bo:\x0EException\b:\tmesg0:\abt0:\t@foo\"\n<foo>@\x06@\a" + end + it "raises TypeError if an Object is an instance of an anonymous class" do anonymous_class = Class.new(Exception) obj = anonymous_class.new diff --git a/spec/ruby/core/marshal/fixtures/marshal_data.rb b/spec/ruby/core/marshal/fixtures/marshal_data.rb index 97fd78b2d7..3ba85aa6eb 100644 --- a/spec/ruby/core/marshal/fixtures/marshal_data.rb +++ b/spec/ruby/core/marshal/fixtures/marshal_data.rb @@ -97,6 +97,25 @@ class UserDefinedString end end +module MarshalSpec + class UserDefinedDumpWithIVars + attr_reader :string + + def initialize(string, ivar_value) + @string = string + @string.instance_variable_set(:@foo, ivar_value) + end + + def _dump(depth) + @string + end + + def self._load(data) + new(data) + end + end +end + class UserPreviouslyDefinedWithInitializedIvar attr_accessor :field1, :field2 end @@ -139,6 +158,32 @@ class UserMarshalWithIvar end end +module MarshalSpec + class UserMarshalDumpWithIvar + attr_reader :data + + def initialize(data, ivar_value) + @data = data + @ivar_value = ivar_value + end + + def marshal_dump + obj = [data] + obj.instance_variable_set(:@foo, @ivar_value) + obj + end + + def marshal_load(o) + @data = o[0] + end + + def ==(other) + self.class === other and + @data = other.data + end + end +end + class UserArray < Array end diff --git a/spec/ruby/core/method/shared/dup.rb b/spec/ruby/core/method/shared/dup.rb index 1a10b90689..c74847083f 100644 --- a/spec/ruby/core/method/shared/dup.rb +++ b/spec/ruby/core/method/shared/dup.rb @@ -16,7 +16,7 @@ describe :method_dup, shared: true do end it "copies the finalizer" do - code = <<-RUBY + code = <<-'RUBY' obj = Object.new.method(:method) ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) diff --git a/spec/ruby/core/module/fixtures/set_temporary_name.rb b/spec/ruby/core/module/fixtures/set_temporary_name.rb new file mode 100644 index 0000000000..901b3b94d1 --- /dev/null +++ b/spec/ruby/core/module/fixtures/set_temporary_name.rb @@ -0,0 +1,4 @@ +module ModuleSpecs + module SetTemporaryNameSpec + end +end diff --git a/spec/ruby/core/module/set_temporary_name_spec.rb b/spec/ruby/core/module/set_temporary_name_spec.rb index f5886a3398..12541374ae 100644 --- a/spec/ruby/core/module/set_temporary_name_spec.rb +++ b/spec/ruby/core/module/set_temporary_name_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative 'fixtures/set_temporary_name' ruby_version_is "3.3" do describe "Module#set_temporary_name" do @@ -13,13 +14,34 @@ ruby_version_is "3.3" do m.name.should be_nil end + it "returns self" do + m = Module.new + m.set_temporary_name("fake_name").should.equal? m + end + it "can assign a temporary name which is not a valid constant path" do m = Module.new - m.set_temporary_name("a::B") - m.name.should == "a::B" + + m.set_temporary_name("name") + m.name.should == "name" m.set_temporary_name("Template['foo.rb']") m.name.should == "Template['foo.rb']" + + m.set_temporary_name("a::B") + m.name.should == "a::B" + + m.set_temporary_name("A::b") + m.name.should == "A::b" + + m.set_temporary_name("A::B::") + m.name.should == "A::B::" + + m.set_temporary_name("A::::B") + m.name.should == "A::::B" + + m.set_temporary_name("A=") + m.name.should == "A=" end it "can't assign empty string as name" do @@ -43,7 +65,7 @@ ruby_version_is "3.3" do -> { Object.set_temporary_name("fake_name") }.should raise_error(RuntimeError, "can't change permanent name") end - it "can assign a temporary name to a nested module" do + it "can assign a temporary name to a module nested into an anonymous module" do m = Module.new module m::N; end m::N.name.should =~ /\A#<Module:0x\h+>::N\z/ @@ -55,6 +77,17 @@ ruby_version_is "3.3" do m::N.name.should be_nil end + it "discards a temporary name when an outer anonymous module gets a permanent name" do + m = Module.new + module m::N; end + + m::N.set_temporary_name("fake_name") + m::N.name.should == "fake_name" + + ModuleSpecs::SetTemporaryNameSpec::M = m + m::N.name.should == "ModuleSpecs::SetTemporaryNameSpec::M::N" + end + it "can update the name when assigned to a constant" do m = Module.new m::N = Module.new @@ -64,5 +97,45 @@ ruby_version_is "3.3" do m::M = m::N m::M.name.should =~ /\A#<Module:0x\h+>::M\z/m end + + it "can reassign a temporary name repeatedly" do + m = Module.new + + m.set_temporary_name("fake_name") + m.name.should == "fake_name" + + m.set_temporary_name("fake_name_2") + m.name.should == "fake_name_2" + end + + it "does not affect a name of a module nested into an anonymous module with a temporary name" do + m = Module.new + m::N = Module.new + m::N.name.should =~ /\A#<Module:0x\h+>::N\z/ + + m.set_temporary_name("foo") + m::N.name.should =~ /\A#<Module:0x\h+>::N\z/ + end + + it "keeps temporary name when assigned in an anonymous module" do + outer = Module.new + m = Module.new + m.set_temporary_name "m" + m.name.should == "m" + outer::M = m + m.name.should == "m" + m.inspect.should == "m" + end + + it "keeps temporary name when assigned in an anonymous module and nested before" do + outer = Module.new + m = Module.new + outer::A = m + m.set_temporary_name "m" + m.name.should == "m" + outer::M = m + m.name.should == "m" + m.inspect.should == "m" + end end end diff --git a/spec/ruby/core/objectspace/define_finalizer_spec.rb b/spec/ruby/core/objectspace/define_finalizer_spec.rb index 4d4cfa9270..329f8e1f30 100644 --- a/spec/ruby/core/objectspace/define_finalizer_spec.rb +++ b/spec/ruby/core/objectspace/define_finalizer_spec.rb @@ -156,7 +156,7 @@ describe "ObjectSpace.define_finalizer" do end it "allows multiple finalizers with different 'callables' to be defined" do - code = <<-RUBY + code = <<-'RUBY' obj = Object.new ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized1\n" }) diff --git a/spec/ruby/core/proc/clone_spec.rb b/spec/ruby/core/proc/clone_spec.rb index 26f031334f..730dc421a8 100644 --- a/spec/ruby/core/proc/clone_spec.rb +++ b/spec/ruby/core/proc/clone_spec.rb @@ -15,7 +15,7 @@ describe "Proc#clone" do end ruby_version_is "3.3" do - it "calls #initialize_copy on subclass" do + it "calls #initialize_clone on subclass" do obj = ProcSpecs::MyProc2.new(:a, 2) { } dup = obj.clone @@ -24,7 +24,7 @@ describe "Proc#clone" do dup.first.should == :a dup.second.should == 2 - dup.initializer.should == :copy + dup.initializer.should == :clone end end end diff --git a/spec/ruby/core/proc/fixtures/common.rb b/spec/ruby/core/proc/fixtures/common.rb index 204acde597..dfe67d7ba8 100644 --- a/spec/ruby/core/proc/fixtures/common.rb +++ b/spec/ruby/core/proc/fixtures/common.rb @@ -47,6 +47,13 @@ module ProcSpecs @first = other.first @second = other.second end + + def initialize_clone(other, **options) + super + @initializer = :clone + @first = other.first + @second = other.second + end end class Arity diff --git a/spec/ruby/core/proc/shared/dup.rb b/spec/ruby/core/proc/shared/dup.rb index c419a4078a..818f5b858e 100644 --- a/spec/ruby/core/proc/shared/dup.rb +++ b/spec/ruby/core/proc/shared/dup.rb @@ -25,7 +25,7 @@ describe :proc_dup, shared: true do end it "copies the finalizer" do - code = <<-RUBY + code = <<-'RUBY' obj = Proc.new { } ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) diff --git a/spec/ruby/core/range/overlap_spec.rb b/spec/ruby/core/range/overlap_spec.rb new file mode 100644 index 0000000000..9b6fc13493 --- /dev/null +++ b/spec/ruby/core/range/overlap_spec.rb @@ -0,0 +1,89 @@ +require_relative '../../spec_helper' + +ruby_version_is '3.3' do + describe "Range#overlap?" do + it "returns true if other Range overlaps self" do + (0..2).overlap?(1..3).should == true + (1..3).overlap?(0..2).should == true + (0..2).overlap?(0..2).should == true + (0..3).overlap?(1..2).should == true + (1..2).overlap?(0..3).should == true + + ('a'..'c').overlap?('b'..'d').should == true + end + + it "returns false if other Range does not overlap self" do + (0..2).overlap?(3..4).should == false + (0..2).overlap?(-4..-1).should == false + + ('a'..'c').overlap?('d'..'f').should == false + end + + it "raises TypeError when called with non-Range argument" do + -> { + (0..2).overlap?(1) + }.should raise_error(TypeError, "wrong argument type Integer (expected Range)") + end + + it "returns true when beginningless and endless Ranges overlap" do + (0..2).overlap?(..3).should == true + (0..2).overlap?(..1).should == true + (0..2).overlap?(..0).should == true + + (..3).overlap?(0..2).should == true + (..1).overlap?(0..2).should == true + (..0).overlap?(0..2).should == true + + (0..2).overlap?(-1..).should == true + (0..2).overlap?(1..).should == true + (0..2).overlap?(2..).should == true + + (-1..).overlap?(0..2).should == true + (1..).overlap?(0..2).should == true + (2..).overlap?(0..2).should == true + + (0..).overlap?(2..).should == true + (..0).overlap?(..2).should == true + end + + it "returns false when beginningless and endless Ranges do not overlap" do + (0..2).overlap?(..-1).should == false + (0..2).overlap?(3..).should == false + + (..-1).overlap?(0..2).should == false + (3..).overlap?(0..2).should == false + end + + it "returns false when Ranges are not compatible" do + (0..2).overlap?('a'..'d').should == false + end + + it "return false when self is empty" do + (2..0).overlap?(1..3).should == false + (2...2).overlap?(1..3).should == false + (1...1).overlap?(1...1).should == false + (2..0).overlap?(2..0).should == false + + ('c'..'a').overlap?('b'..'d').should == false + ('a'...'a').overlap?('b'..'d').should == false + ('b'...'b').overlap?('b'...'b').should == false + ('c'...'a').overlap?('c'...'a').should == false + end + + it "return false when other Range is empty" do + (1..3).overlap?(2..0).should == false + (1..3).overlap?(2...2).should == false + + ('b'..'d').overlap?('c'..'a').should == false + ('b'..'d').overlap?('c'...'c').should == false + end + + it "takes into account exclusive end" do + (0...2).overlap?(2..4).should == false + (2..4).overlap?(0...2).should == false + + ('a'...'c').overlap?('c'..'e').should == false + ('c'..'e').overlap?('a'...'c').should == false + end + end +end diff --git a/spec/ruby/core/string/append_as_bytes_spec.rb b/spec/ruby/core/string/append_as_bytes_spec.rb index 0e1d09558b..b1703e5f89 100644 --- a/spec/ruby/core/string/append_as_bytes_spec.rb +++ b/spec/ruby/core/string/append_as_bytes_spec.rb @@ -34,6 +34,18 @@ describe "String#append_bytes" do str.should == "hello\xE2\x82\f+\xAC".b end + it "truncates integers to the least significant byte" do + str = +"" + str.append_as_bytes(0x131, 0x232, 0x333, bignum_value, bignum_value(1)) + str.bytes.should == [0x31, 0x32, 0x33, 0, 1] + end + + it "wraps negative integers" do + str = "".b + str.append_as_bytes(-1, -bignum_value, -bignum_value(1)) + str.bytes.should == [0xFF, 0, 0xFF] + end + it "only accepts strings or integers, and doesn't attempt to cast with #to_str or #to_int" do to_str = mock("to_str") to_str.should_not_receive(:to_str) diff --git a/spec/ruby/core/string/modulo_spec.rb b/spec/ruby/core/string/modulo_spec.rb index 8e3853551f..33c2141812 100644 --- a/spec/ruby/core/string/modulo_spec.rb +++ b/spec/ruby/core/string/modulo_spec.rb @@ -749,9 +749,11 @@ describe "String#%" do (format % "-10.4e-20").should == (format % -10.4e-20) (format % ".5").should == (format % 0.5) (format % "-.5").should == (format % -0.5) - ruby_bug("#20705", ""..."3.4") do + + ruby_version_is "3.4" do (format % "10.").should == (format % 10) end + # Something's strange with this spec: # it works just fine in individual mode, but not when run as part of a group (format % "10_1_0.5_5_5").should == (format % 1010.555) diff --git a/spec/ruby/core/time/at_spec.rb b/spec/ruby/core/time/at_spec.rb index 48fb3c6f52..85bb6d7ebf 100644 --- a/spec/ruby/core/time/at_spec.rb +++ b/spec/ruby/core/time/at_spec.rb @@ -228,6 +228,12 @@ describe "Time.at" do time.utc_offset.should == -9*60*60 time.zone.should == nil time.to_i.should == @epoch_time + + time = Time.at(@epoch_time, in: "-09:00:01") + + time.utc_offset.should == -(9*60*60 + 1) + time.zone.should == nil + time.to_i.should == @epoch_time end it "could be UTC offset as a number of seconds" do @@ -280,5 +286,51 @@ describe "Time.at" do -> { Time.at(@epoch_time, in: "+09:99") }.should raise_error(ArgumentError) -> { Time.at(@epoch_time, in: "ABC") }.should raise_error(ArgumentError) end + + it "raises ArgumentError if hours greater than 23" do # TODO + ruby_version_is ""..."3.1" do + -> { Time.at(@epoch_time, in: "+24:00") }.should raise_error(ArgumentError, 'utc_offset out of range') + -> { Time.at(@epoch_time, in: "+2400") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + + -> { Time.at(@epoch_time, in: "+99:00") }.should raise_error(ArgumentError, 'utc_offset out of range') + -> { Time.at(@epoch_time, in: "+9900") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + end + + ruby_version_is "3.1" do + -> { Time.at(@epoch_time, in: "+24:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.at(@epoch_time, in: "+2400") }.should raise_error(ArgumentError, "utc_offset out of range") + + -> { Time.at(@epoch_time, in: "+99:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.at(@epoch_time, in: "+9900") }.should raise_error(ArgumentError, "utc_offset out of range") + end + end + + it "raises ArgumentError if minutes greater than 59" do # TODO + ruby_version_is ""..."3.1" do + -> { Time.at(@epoch_time, in: "+00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + -> { Time.at(@epoch_time, in: "+0060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + + -> { Time.at(@epoch_time, in: "+00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + -> { Time.at(@epoch_time, in: "+0099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + end + + ruby_version_is "3.1" do + -> { Time.at(@epoch_time, in: "+00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.at(@epoch_time, in: "+0060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.at(@epoch_time, in: "+00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.at(@epoch_time, in: "+0099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + end + end + + ruby_bug '#20797', ''...'3.4' do + it "raises ArgumentError if seconds greater than 59" do + -> { Time.at(@epoch_time, in: "+00:00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.at(@epoch_time, in: "+000060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') + + -> { Time.at(@epoch_time, in: "+00:00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.at(@epoch_time, in: "+000099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') + end + end end end diff --git a/spec/ruby/core/time/getlocal_spec.rb b/spec/ruby/core/time/getlocal_spec.rb index 926a6dbf45..ff3e3d8dd1 100644 --- a/spec/ruby/core/time/getlocal_spec.rb +++ b/spec/ruby/core/time/getlocal_spec.rb @@ -59,12 +59,24 @@ describe "Time#getlocal" do t.utc_offset.should == 3600 end + it "returns a Time with a UTC offset specified as +HH:MM:SS" do + t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal("+01:00:01") + t.should == Time.new(2007, 1, 9, 13, 0, 1, 3601) + t.utc_offset.should == 3601 + end + it "returns a Time with a UTC offset specified as -HH:MM" do t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal("-01:00") t.should == Time.new(2007, 1, 9, 11, 0, 0, -3600) t.utc_offset.should == -3600 end + it "returns a Time with a UTC offset specified as -HH:MM:SS" do + t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal("-01:00:01") + t.should == Time.new(2007, 1, 9, 10, 59, 59, -3601) + t.utc_offset.should == -3601 + end + describe "with an argument that responds to #to_str" do it "coerces using #to_str" do o = mock('string') @@ -97,6 +109,52 @@ describe "Time#getlocal" do -> { t.getlocal(86400) }.should raise_error(ArgumentError) end + it "raises ArgumentError if String argument and hours greater than 23" do + ruby_version_is ""..."3.1" do + -> { Time.now.getlocal("+24:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now.getlocal("+2400") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + + -> { Time.now.getlocal("+99:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now.getlocal("+9900") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + end + + ruby_version_is "3.1" do + -> { Time.now.getlocal("+24:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now.getlocal("+2400") }.should raise_error(ArgumentError, "utc_offset out of range") + + -> { Time.now.getlocal("+99:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now.getlocal("+9900") }.should raise_error(ArgumentError, "utc_offset out of range") + end + end + + it "raises ArgumentError if String argument and minutes greater than 59" do + ruby_version_is ""..."3.1" do + -> { Time.now.getlocal("+00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + -> { Time.now.getlocal("+0060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + + -> { Time.now.getlocal("+00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + -> { Time.now.getlocal("+0099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + end + + ruby_version_is "3.1" do + -> { Time.now.getlocal("+00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.now.getlocal("+0060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.now.getlocal("+00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.now.getlocal("+0099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + end + end + + ruby_bug '#20797', ''...'3.4' do + it "raises ArgumentError if String argument and seconds greater than 59" do + -> { Time.now.getlocal("+00:00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.now.getlocal("+000060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') + + -> { Time.now.getlocal("+00:00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.now.getlocal("+000099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') + end + end + describe "with a timezone argument" do it "returns a Time in the timezone" do zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60)) diff --git a/spec/ruby/core/time/localtime_spec.rb b/spec/ruby/core/time/localtime_spec.rb index 609b6532a1..5badba9fb2 100644 --- a/spec/ruby/core/time/localtime_spec.rb +++ b/spec/ruby/core/time/localtime_spec.rb @@ -72,6 +72,13 @@ describe "Time#localtime" do t.utc_offset.should == 3600 end + it "returns a Time with a UTC offset specified as +HH:MM:SS" do + t = Time.gm(2007, 1, 9, 12, 0, 0) + t.localtime("+01:00:01") + t.should == Time.new(2007, 1, 9, 13, 0, 1, 3601) + t.utc_offset.should == 3601 + end + it "returns a Time with a UTC offset specified as -HH:MM" do t = Time.gm(2007, 1, 9, 12, 0, 0) t.localtime("-01:00") @@ -79,6 +86,13 @@ describe "Time#localtime" do t.utc_offset.should == -3600 end + it "returns a Time with a UTC offset specified as -HH:MM:SS" do + t = Time.gm(2007, 1, 9, 12, 0, 0) + t.localtime("-01:00:01") + t.should == Time.new(2007, 1, 9, 10, 59, 59, -3601) + t.utc_offset.should == -3601 + end + it "returns a Time with a UTC offset specified as UTC" do t = Time.new(2007, 1, 9, 12, 0, 0, 3600) t.localtime("UTC") @@ -91,6 +105,52 @@ describe "Time#localtime" do t.utc_offset.should == 3600 * 2 end + it "raises ArgumentError if String argument and hours greater than 23" do + ruby_version_is ""..."3.1" do + -> { Time.now.localtime("+24:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now.localtime("+2400") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + + -> { Time.now.localtime("+99:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now.localtime("+9900") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + end + + ruby_version_is "3.1" do + -> { Time.now.localtime("+24:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now.localtime("+2400") }.should raise_error(ArgumentError, "utc_offset out of range") + + -> { Time.now.localtime("+99:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now.localtime("+9900") }.should raise_error(ArgumentError, "utc_offset out of range") + end + end + + it "raises ArgumentError if String argument and minutes greater than 59" do + ruby_version_is ""..."3.1" do + -> { Time.now.localtime("+00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + -> { Time.now.localtime("+0060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + + -> { Time.now.localtime("+00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + -> { Time.now.localtime("+0099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset') + end + + ruby_version_is "3.1" do + -> { Time.now.localtime("+00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.now.localtime("+0060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.now.localtime("+00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.now.localtime("+0099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + end + end + + ruby_bug '#20797', ''...'3.4' do + it "raises ArgumentError if String argument and seconds greater than 59" do + -> { Time.now.localtime("+00:00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.now.localtime("+000060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') + + -> { Time.now.localtime("+00:00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.now.localtime("+000099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') + end + end + platform_is_not :windows do it "changes the timezone according to the set one" do t = Time.new(2005, 2, 27, 22, 50, 0, -3600) diff --git a/spec/ruby/core/time/new_spec.rb b/spec/ruby/core/time/new_spec.rb index 24bb9fde0c..1a743b485e 100644 --- a/spec/ruby/core/time/new_spec.rb +++ b/spec/ruby/core/time/new_spec.rb @@ -415,6 +415,11 @@ describe "Time.new with a timezone argument" do time.utc_offset.should == -9*60*60 time.zone.should == nil + + time = Time.new(2000, 1, 1, 12, 0, 0, in: "-09:00:01") + + time.utc_offset.should == -(9*60*60 + 1) + time.zone.should == nil end it "could be UTC offset as a number of seconds" do @@ -659,7 +664,7 @@ describe "Time.new with a timezone argument" do -> { Time.new("2020-12-25 00:56:17 +23:61") - }.should raise_error(ArgumentError, /utc_offset|can't parse:/) + }.should raise_error(ArgumentError, /utc_offset/) ruby_bug '#20797', ''...'3.4' do -> { @@ -668,6 +673,28 @@ describe "Time.new with a timezone argument" do end end + it "raises ArgumentError if utc offset parts are not valid" do + -> { Time.new("2020-12-25 00:56:17 +24:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.new("2020-12-25 00:56:17 +2400") }.should raise_error(ArgumentError, "utc_offset out of range") + + -> { Time.new("2020-12-25 00:56:17 +99:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.new("2020-12-25 00:56:17 +9900") }.should raise_error(ArgumentError, "utc_offset out of range") + + -> { Time.new("2020-12-25 00:56:17 +00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.new("2020-12-25 00:56:17 +0060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.new("2020-12-25 00:56:17 +00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.new("2020-12-25 00:56:17 +0099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + + ruby_bug '#20797', ''...'3.4' do + -> { Time.new("2020-12-25 00:56:17 +00:00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.new("2020-12-25 00:56:17 +000060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') + + -> { Time.new("2020-12-25 00:56:17 +00:00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.new("2020-12-25 00:56:17 +000099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') + end + end + it "raises ArgumentError if string has not ascii-compatible encoding" do -> { Time.new("2021-11-31 00:00:60 +09:00".encode("utf-32le")) diff --git a/spec/ruby/core/time/now_spec.rb b/spec/ruby/core/time/now_spec.rb index d47f00723e..7c2425411a 100644 --- a/spec/ruby/core/time/now_spec.rb +++ b/spec/ruby/core/time/now_spec.rb @@ -16,6 +16,11 @@ describe "Time.now" do time.utc_offset.should == -9*60*60 time.zone.should == nil + + time = Time.now(in: "-09:00:01") + + time.utc_offset.should == -(9*60*60 + 1) + time.zone.should == nil end it "could be UTC offset as a number of seconds" do @@ -52,6 +57,32 @@ describe "Time.now" do -> { Time.now(in: "+09:99") }.should raise_error(ArgumentError) -> { Time.now(in: "ABC") }.should raise_error(ArgumentError) end + + it "raises ArgumentError if String argument and hours greater than 23" do + -> { Time.now(in: "+24:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now(in: "+2400") }.should raise_error(ArgumentError, "utc_offset out of range") + + -> { Time.now(in: "+99:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now(in: "+9900") }.should raise_error(ArgumentError, "utc_offset out of range") + end + + it "raises ArgumentError if String argument and minutes greater than 59" do + -> { Time.now(in: "+00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.now(in: "+0060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.now(in: "+00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.now(in: "+0099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + end + + ruby_bug '#20797', ''...'3.4' do + it "raises ArgumentError if String argument and seconds greater than 59" do + -> { Time.now(in: "+00:00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.now(in: "+000060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') + + -> { Time.now(in: "+00:00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.now(in: "+000099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') + end + end end end end diff --git a/spec/ruby/core/unboundmethod/shared/dup.rb b/spec/ruby/core/unboundmethod/shared/dup.rb index 943a7faaa3..194e2cc1a1 100644 --- a/spec/ruby/core/unboundmethod/shared/dup.rb +++ b/spec/ruby/core/unboundmethod/shared/dup.rb @@ -16,7 +16,7 @@ describe :unboundmethod_dup, shared: true do end it "copies the finalizer" do - code = <<-RUBY + code = <<-'RUBY' obj = Class.instance_method(:instance_method) ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) |