summaryrefslogtreecommitdiff
path: root/spec/ruby/core/marshal
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/core/marshal')
-rw-r--r--spec/ruby/core/marshal/dump_spec.rb366
-rw-r--r--spec/ruby/core/marshal/fixtures/marshal_data.rb75
-rw-r--r--spec/ruby/core/marshal/fixtures/marshal_multibyte_data.rb12
-rw-r--r--spec/ruby/core/marshal/float_spec.rb2
-rw-r--r--spec/ruby/core/marshal/load_spec.rb1289
-rw-r--r--spec/ruby/core/marshal/restore_spec.rb5
-rw-r--r--spec/ruby/core/marshal/shared/load.rb1254
7 files changed, 1672 insertions, 1331 deletions
diff --git a/spec/ruby/core/marshal/dump_spec.rb b/spec/ruby/core/marshal/dump_spec.rb
index 0f77279a4f..9bbb7809af 100644
--- a/spec/ruby/core/marshal/dump_spec.rb
+++ b/spec/ruby/core/marshal/dump_spec.rb
@@ -1,4 +1,4 @@
-# -*- encoding: binary -*-
+# encoding: binary
require_relative '../../spec_helper'
require_relative 'fixtures/classes'
require_relative 'fixtures/marshal_data'
@@ -38,7 +38,7 @@ describe "Marshal.dump" do
].should be_computed_by(:dump)
end
- platform_is wordsize: 64 do
+ platform_is c_long_size: 64 do
it "dumps a positive Fixnum > 31 bits as a Bignum" do
Marshal.dump(2**31 + 1).should == "\x04\bl+\a\x01\x00\x00\x80"
end
@@ -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
@@ -106,7 +116,21 @@ describe "Marshal.dump" do
end
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/)
+ -> { Marshal.dump(Class.new(UserMarshal).new) }.should.raise(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
@@ -133,11 +157,11 @@ describe "Marshal.dump" do
it "raises a TypeError if _dump returns a non-string" do
m = mock("marshaled")
m.should_receive(:_dump).and_return(0)
- -> { Marshal.dump(m) }.should raise_error(TypeError)
+ -> { Marshal.dump(m) }.should.raise(TypeError)
end
it "raises TypeError if an Object is an instance of an anonymous class" do
- -> { Marshal.dump(Class.new(UserDefined).new) }.should raise_error(TypeError, /can't dump anonymous class/)
+ -> { Marshal.dump(Class.new(UserDefined).new) }.should.raise(TypeError, /can't dump anonymous class/)
end
it "favors marshal_dump over _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)
@@ -193,17 +231,24 @@ describe "Marshal.dump" do
Marshal.dump(MarshalSpec::ClassWithOverriddenName).should == "\x04\bc)MarshalSpec::ClassWithOverriddenName"
end
- it "dumps a class with multibyte characters in name" do
- source_object = eval("MarshalSpec::MultibyteぁあぃいClass".dup.force_encoding(Encoding::UTF_8))
- Marshal.dump(source_object).should == "\x04\bc,MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Class"
+ ruby_version_is "4.0" do
+ it "dumps a class with multibyte characters in name" do
+ source_object = eval("MarshalSpec::MultibyteぁあぃいClass".dup.force_encoding(Encoding::UTF_8))
+ Marshal.dump(source_object).should == "\x04\bIc,MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Class\x06:\x06ET"
+ Marshal.load(Marshal.dump(source_object)) == source_object
+ end
+ 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/)
+ -> { Marshal.dump(Class.new) }.should.raise(TypeError, /can't dump anonymous class/)
end
it "raises TypeError with a singleton Class" do
- -> { Marshal.dump(class << self; self end) }.should raise_error(TypeError)
+ -> { Marshal.dump(class << self; self end) }.should.raise(TypeError)
end
end
@@ -216,13 +261,20 @@ describe "Marshal.dump" do
Marshal.dump(MarshalSpec::ModuleWithOverriddenName).should == "\x04\bc*MarshalSpec::ModuleWithOverriddenName"
end
- it "dumps a module with multibyte characters in name" do
- source_object = eval("MarshalSpec::MultibyteけげこごModule".dup.force_encoding(Encoding::UTF_8))
- Marshal.dump(source_object).should == "\x04\bm-MarshalSpec::Multibyte\xE3\x81\x91\xE3\x81\x92\xE3\x81\x93\xE3\x81\x94Module"
+ ruby_version_is "4.0" do
+ it "dumps a module with multibyte characters in name" do
+ source_object = eval("MarshalSpec::MultibyteけげこごModule".dup.force_encoding(Encoding::UTF_8))
+ Marshal.dump(source_object).should == "\x04\bIm-MarshalSpec::Multibyte\xE3\x81\x91\xE3\x81\x92\xE3\x81\x93\xE3\x81\x94Module\x06:\x06ET"
+ Marshal.load(Marshal.dump(source_object)) == source_object
+ end
+ 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/)
+ -> { Marshal.dump(Module.new) }.should.raise(TypeError, /can't dump anonymous module/)
end
end
@@ -239,6 +291,23 @@ describe "Marshal.dump" do
[Marshal, nan_value, "\004\bf\bnan"],
].should be_computed_by(:dump)
end
+
+ it "may or may not use object links for objects repeatedly dumped" do
+ # it's an MRI implementation detail - on x86 architecture object links
+ # aren't used for Float values but on amd64 - object links are used
+
+ dump = Marshal.dump([0.0, 0.0])
+ ["\x04\b[\af\x060@\x06", "\x04\b[\af\x060f\x060"].should.include?(dump)
+
+ # if object links aren't used - entries in the objects table are still
+ # occupied by Float values
+ if dump == "\x04\b[\af\x060f\x060"
+ s = "string"
+ # an index of "string" ("@\b") in the object table equals 3 (`"\b".ord - 5`),
+ # so `0.0, 0,0` elements occupied indices 1 and 2
+ Marshal.dump([0.0, 0.0, s, s]).should == "\x04\b[\tf\x060f\x060\"\vstring@\b"
+ end
+ end
end
describe "with a Bignum" do
@@ -256,6 +325,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 +349,47 @@ 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
+
+ describe "with a Data" do
+ it "dumps a Data" do
+ Marshal.dump(MarshalSpec::DataSpec::Measure.new(100, 'km')).should == "\x04\bS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm"
+ end
+
+ it "dumps an extended Data" do
+ obj = MarshalSpec::DataSpec::MeasureExtended.new(100, "km")
+ Marshal.dump(obj).should == "\x04\bS:+MarshalSpec::DataSpec::MeasureExtended\a:\vamountii:\tunit\"\akm"
+ end
+
+ it "ignores overridden name method" do
+ obj = MarshalSpec::DataSpec::MeasureWithOverriddenName.new(100, "km")
+ 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(TypeError, /can't dump anonymous class/)
+ end
end
describe "with a String" do
@@ -338,6 +447,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
@@ -349,14 +473,26 @@ describe "Marshal.dump" do
Marshal.dump(//im).should == "\x04\bI/\x00\x05\x06:\x06EF"
end
- it "dumps a Regexp with instance variables" do
- o = Regexp.new("")
+ it "dumps a Regexp subclass with instance variables" do
+ o = UserRegexp.new("")
o.instance_variable_set(:@ivar, :ivar)
- Marshal.dump(o).should == "\x04\bI/\x00\x00\a:\x06EF:\n@ivar:\tivar"
+ Marshal.dump(o).should == "\x04\bIC:\x0FUserRegexp/\x00\x00\a:\x06EF:\n@ivar:\tivar"
+ end
+
+ it "dumps an extended Regexp subclass" do
+ Marshal.dump(UserRegexp.new("").extend(Meths)).should == "\x04\bIe:\nMethsC:\x0FUserRegexp/\x00\x00\x06:\x06EF"
end
- it "dumps an extended Regexp" do
- Marshal.dump(Regexp.new("").extend(Meths)).should == "\x04\bIe:\nMeths/\x00\x00\x06:\x06EF"
+ ruby_version_is ""..."4.1" do
+ it "dumps a Regexp with instance variables" do
+ o = Regexp.new("")
+ o.instance_variable_set(:@ivar, :ivar)
+ Marshal.dump(o).should == "\x04\bI/\x00\x00\a:\x06EF:\n@ivar:\tivar"
+ end
+
+ it "dumps an extended Regexp" do
+ Marshal.dump(Regexp.new("").extend(Meths)).should == "\x04\bIe:\nMeths/\x00\x00\x06:\x06EF"
+ end
end
it "dumps a Regexp subclass" do
@@ -405,6 +541,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
@@ -440,6 +581,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
@@ -459,24 +615,22 @@ describe "Marshal.dump" do
Marshal.dump(Hash.new(1)).should == "\004\b}\000i\006"
end
- ruby_version_is "3.1" do
- it "dumps a Hash with compare_by_identity" do
- h = {}
- h.compare_by_identity
+ it "dumps a Hash with compare_by_identity" do
+ h = {}
+ h.compare_by_identity
- Marshal.dump(h).should == "\004\bC:\tHash{\x00"
- end
+ Marshal.dump(h).should == "\004\bC:\tHash{\x00"
+ end
- it "dumps a Hash subclass with compare_by_identity" do
- h = UserHash.new
- h.compare_by_identity
+ it "dumps a Hash subclass with compare_by_identity" do
+ h = UserHash.new
+ h.compare_by_identity
- Marshal.dump(h).should == "\x04\bC:\rUserHashC:\tHash{\x00"
- end
+ Marshal.dump(h).should == "\x04\bC:\rUserHashC:\tHash{\x00"
end
it "raises a TypeError with hash having default proc" do
- -> { Marshal.dump(Hash.new {}) }.should raise_error(TypeError, "can't dump hash with default proc")
+ -> { Marshal.dump(Hash.new {}) }.should.raise(TypeError, "can't dump hash with default proc")
end
it "dumps a Hash with instance variables" do
@@ -497,6 +651,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
@@ -531,8 +700,23 @@ 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/)
+ -> { Marshal.dump(Struct.new(:a).new(1)) }.should.raise(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
@@ -581,7 +765,7 @@ describe "Marshal.dump" do
def obj.foo; end
-> {
Marshal.dump(obj)
- }.should raise_error(TypeError, "singleton can't be dumped")
+ }.should.raise(TypeError, "singleton can't be dumped")
end
it "raises TypeError if an Object has a singleton class and singleton instance variables" do
@@ -592,14 +776,14 @@ describe "Marshal.dump" do
-> {
Marshal.dump(obj)
- }.should raise_error(TypeError, "singleton can't be dumped")
+ }.should.raise(TypeError, "singleton can't be dumped")
end
it "raises TypeError if an Object is an instance of an anonymous class" do
anonymous_class = Class.new
obj = anonymous_class.new
- -> { Marshal.dump(obj) }.should raise_error(TypeError, /can't dump anonymous class/)
+ -> { Marshal.dump(obj) }.should.raise(TypeError, /can't dump anonymous class/)
end
it "raises TypeError if an Object extends an anonymous module" do
@@ -607,7 +791,7 @@ describe "Marshal.dump" do
obj = Object.new
obj.extend(anonymous_module)
- -> { Marshal.dump(obj) }.should raise_error(TypeError, /can't dump anonymous class/)
+ -> { Marshal.dump(obj) }.should.raise(TypeError, /can't dump anonymous class/)
end
it "dumps a BasicObject subclass if it defines respond_to?" do
@@ -623,23 +807,47 @@ 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/)
+ -> { Marshal.dump(Class.new(Range).new(1, 2)) }.should.raise(TypeError, /can't dump anonymous class/)
end
end
@@ -669,7 +877,7 @@ describe "Marshal.dump" do
base = "\x04\bIu:\tTime\r#{@t_dump}\a"
offset = ":\voffseti\x020*"
zone = ":\tzoneI\"\bAST\x06:\x06EF" # Last is 'F' (US-ASCII)
- [ "#{base}#{offset}#{zone}", "#{base}#{zone}#{offset}" ].should include(dump)
+ [ "#{base}#{offset}#{zone}", "#{base}#{zone}#{offset}" ].should.include?(dump)
end
end
@@ -681,16 +889,39 @@ describe "Marshal.dump" do
it "ignores overridden name method" do
obj = MarshalSpec::TimeWithOverriddenName.new
- Marshal.dump(obj).should include("MarshalSpec::TimeWithOverriddenName")
+ Marshal.dump(obj).should.include?("MarshalSpec::TimeWithOverriddenName")
+ end
+
+ ruby_version_is "4.0" do
+ it "dumps a Time subclass with multibyte characters in name" do
+ source_object = eval("MarshalSpec::MultibyteぁあぃいTime".dup.force_encoding(Encoding::UTF_8))
+ Marshal.dump(source_object).should == "\x04\bIc+MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Time\x06:\x06ET"
+ Marshal.load(Marshal.dump(source_object)) == source_object
+ end
+ 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 "dumps a Time subclass with multibyte characters in name" do
- source_object = eval("MarshalSpec::MultibyteぁあぃいTime".dup.force_encoding(Encoding::UTF_8))
- Marshal.dump(source_object).should == "\x04\bc+MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Time"
+ 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)
+ -> { Marshal.dump(Class.new(Time).now) }.should.raise(TypeError)
end
end
@@ -723,13 +954,13 @@ describe "Marshal.dump" do
begin
raise RuntimeError, "the consequence"
rescue RuntimeError => e
- e.cause.should equal(cause)
+ e.cause.should.equal?(cause)
exc = e
end
end
reloaded = Marshal.load(Marshal.dump(exc))
- reloaded.cause.should be_an_instance_of(StandardError)
+ reloaded.cause.should.instance_of?(StandardError)
reloaded.cause.message.should == "the cause"
end
@@ -743,11 +974,26 @@ 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
- -> { Marshal.dump(obj) }.should raise_error(TypeError, /can't dump anonymous class/)
+ -> { Marshal.dump(obj) }.should.raise(TypeError, /can't dump anonymous class/)
end
end
@@ -768,10 +1014,10 @@ describe "Marshal.dump" do
it "raises an ArgumentError when the recursion limit is exceeded" do
h = {'one' => {'two' => {'three' => 0}}}
- -> { Marshal.dump(h, 3) }.should raise_error(ArgumentError)
- -> { Marshal.dump([h], 4) }.should raise_error(ArgumentError)
- -> { Marshal.dump([], 0) }.should raise_error(ArgumentError)
- -> { Marshal.dump([[[]]], 1) }.should raise_error(ArgumentError)
+ -> { Marshal.dump(h, 3) }.should.raise(ArgumentError)
+ -> { Marshal.dump([h], 4) }.should.raise(ArgumentError)
+ -> { Marshal.dump([], 0) }.should.raise(ArgumentError)
+ -> { Marshal.dump([[[]]], 1) }.should.raise(ArgumentError)
end
it "ignores the recursion limit if the limit is negative" do
@@ -793,7 +1039,7 @@ describe "Marshal.dump" do
it "raises an Error when the IO-Object does not respond to #write" do
obj = mock('test')
- -> { Marshal.dump("test", obj) }.should raise_error(TypeError)
+ -> { Marshal.dump("test", obj) }.should.raise(TypeError)
end
@@ -808,29 +1054,29 @@ describe "Marshal.dump" do
describe "when passed a StringIO" do
it "should raise an error" do
require "stringio"
- -> { Marshal.dump(StringIO.new) }.should raise_error(TypeError)
+ -> { Marshal.dump(StringIO.new) }.should.raise(TypeError)
end
end
it "raises a TypeError if marshalling a Method instance" do
- -> { Marshal.dump(Marshal.method(:dump)) }.should raise_error(TypeError)
+ -> { Marshal.dump(Marshal.method(:dump)) }.should.raise(TypeError)
end
it "raises a TypeError if marshalling a Proc" do
- -> { Marshal.dump(proc {}) }.should raise_error(TypeError)
+ -> { Marshal.dump(proc {}) }.should.raise(TypeError)
end
it "raises a TypeError if dumping a IO/File instance" do
- -> { Marshal.dump(STDIN) }.should raise_error(TypeError)
- -> { File.open(__FILE__) { |f| Marshal.dump(f) } }.should raise_error(TypeError)
+ -> { Marshal.dump(STDIN) }.should.raise(TypeError)
+ -> { File.open(__FILE__) { |f| Marshal.dump(f) } }.should.raise(TypeError)
end
it "raises a TypeError if dumping a MatchData instance" do
- -> { Marshal.dump(/(.)/.match("foo")) }.should raise_error(TypeError)
+ -> { Marshal.dump(/(.)/.match("foo")) }.should.raise(TypeError)
end
it "raises a TypeError if dumping a Mutex instance" do
m = Mutex.new
- -> { Marshal.dump(m) }.should raise_error(TypeError)
+ -> { Marshal.dump(m) }.should.raise(TypeError)
end
end
diff --git a/spec/ruby/core/marshal/fixtures/marshal_data.rb b/spec/ruby/core/marshal/fixtures/marshal_data.rb
index a508b6bea1..c16d9e4bb6 100644
--- a/spec/ruby/core/marshal/fixtures/marshal_data.rb
+++ b/spec/ruby/core/marshal/fixtures/marshal_data.rb
@@ -1,4 +1,7 @@
-# -*- encoding: binary -*-
+# encoding: binary
+
+require_relative 'marshal_multibyte_data'
+
class UserDefined
class Nested
def ==(other)
@@ -94,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
@@ -136,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
@@ -267,17 +315,6 @@ module MarshalSpec
end
end
- module_eval(<<~ruby.dup.force_encoding(Encoding::UTF_8))
- class MultibyteぁあぃいClass
- end
-
- module MultibyteけげこごModule
- end
-
- class MultibyteぁあぃいTime < Time
- end
- ruby
-
class ObjectWithFreezeRaisingException < Object
def freeze
raise
@@ -494,6 +531,20 @@ module MarshalSpec
"\004\bS:\024Struct::Pyramid\000"],
"Random" => random_data,
}
+
+ module DataSpec
+ Measure = Data.define(:amount, :unit)
+ Empty = Data.define
+
+ MeasureExtended = Class.new(Measure)
+ MeasureExtended.extend(Enumerable)
+
+ class MeasureWithOverriddenName < Measure
+ def self.name
+ "Foo"
+ end
+ end
+ end
end
class ArraySub < Array
diff --git a/spec/ruby/core/marshal/fixtures/marshal_multibyte_data.rb b/spec/ruby/core/marshal/fixtures/marshal_multibyte_data.rb
new file mode 100644
index 0000000000..98a0d43392
--- /dev/null
+++ b/spec/ruby/core/marshal/fixtures/marshal_multibyte_data.rb
@@ -0,0 +1,12 @@
+# -*- encoding: utf-8 -*-
+
+module MarshalSpec
+ class MultibyteぁあぃいClass
+ end
+
+ module MultibyteけげこごModule
+ end
+
+ class MultibyteぁあぃいTime < Time
+ end
+end
diff --git a/spec/ruby/core/marshal/float_spec.rb b/spec/ruby/core/marshal/float_spec.rb
index 5793bbd564..5b2d68d6e1 100644
--- a/spec/ruby/core/marshal/float_spec.rb
+++ b/spec/ruby/core/marshal/float_spec.rb
@@ -40,7 +40,7 @@ end
describe "Marshal.load with Float" do
it "loads NaN" do
- Marshal.load("\004\bf\bnan").should be_nan
+ Marshal.load("\004\bf\bnan").should.nan?
end
it "loads +Infinity" do
diff --git a/spec/ruby/core/marshal/load_spec.rb b/spec/ruby/core/marshal/load_spec.rb
index a5bdfbf520..f5a05f8e52 100644
--- a/spec/ruby/core/marshal/load_spec.rb
+++ b/spec/ruby/core/marshal/load_spec.rb
@@ -1,6 +1,1291 @@
+# encoding: binary
require_relative '../../spec_helper'
-require_relative 'shared/load'
+require_relative 'fixtures/marshal_data'
describe "Marshal.load" do
- it_behaves_like :marshal_load, :load
+ before :all do
+ @num_self_class = 1
+ end
+
+ it "raises an ArgumentError when the dumped data is truncated" do
+ obj = {first: 1, second: 2, third: 3}
+ -> { Marshal.load(Marshal.dump(obj)[0, 5]) }.should.raise(ArgumentError, "marshal data too short")
+ end
+
+ it "raises an ArgumentError when the argument is empty String" do
+ -> { Marshal.load("") }.should.raise(ArgumentError, "marshal data too short")
+ end
+
+ it "raises an ArgumentError when the dumped class is missing" do
+ Object.send(:const_set, :KaBoom, Class.new)
+ kaboom = Marshal.dump(KaBoom.new)
+ Object.send(:remove_const, :KaBoom)
+
+ -> { Marshal.load(kaboom) }.should.raise(ArgumentError)
+ end
+
+ describe "when called with freeze: true" do
+ it "returns frozen strings" do
+ string = Marshal.load(Marshal.dump("foo"), freeze: true)
+ string.should == "foo"
+ string.should.frozen?
+
+ utf8_string = "foo".encode(Encoding::UTF_8)
+ string = Marshal.load(Marshal.dump(utf8_string), freeze: true)
+ string.should == utf8_string
+ string.should.frozen?
+ end
+
+ it "returns frozen arrays" do
+ array = Marshal.load(Marshal.dump([1, 2, 3]), freeze: true)
+ array.should == [1, 2, 3]
+ array.should.frozen?
+ end
+
+ it "returns frozen hashes" do
+ hash = Marshal.load(Marshal.dump({foo: 42}), freeze: true)
+ hash.should == {foo: 42}
+ hash.should.frozen?
+ end
+
+ it "returns frozen regexps" do
+ regexp = Marshal.load(Marshal.dump(/foo/), freeze: true)
+ regexp.should == /foo/
+ regexp.should.frozen?
+ end
+
+ it "returns frozen structs" do
+ struct = Marshal.load(Marshal.dump(MarshalSpec::StructToDump.new(1, 2)), freeze: true)
+ struct.should == MarshalSpec::StructToDump.new(1, 2)
+ struct.should.frozen?
+ end
+
+ it "returns frozen objects" do
+ source_object = Object.new
+
+ object = Marshal.load(Marshal.dump(source_object), freeze: true)
+ object.should.frozen?
+ end
+
+ describe "deep freezing" do
+ it "returns hashes with frozen keys and values" do
+ key = Object.new
+ value = Object.new
+ source_object = {key => value}
+
+ hash = Marshal.load(Marshal.dump(source_object), freeze: true)
+ hash.size.should == 1
+ hash.keys[0].should.frozen?
+ hash.values[0].should.frozen?
+ end
+
+ it "returns arrays with frozen elements" do
+ object = Object.new
+ source_object = [object]
+
+ array = Marshal.load(Marshal.dump(source_object), freeze: true)
+ array.size.should == 1
+ array[0].should.frozen?
+ end
+
+ it "returns structs with frozen members" do
+ object1 = Object.new
+ object2 = Object.new
+ source_object = MarshalSpec::StructToDump.new(object1, object2)
+
+ struct = Marshal.load(Marshal.dump(source_object), freeze: true)
+ struct.a.should.frozen?
+ struct.b.should.frozen?
+ end
+
+ it "returns objects with frozen instance variables" do
+ source_object = Object.new
+ instance_variable = Object.new
+ source_object.instance_variable_set(:@a, instance_variable)
+
+ object = Marshal.load(Marshal.dump(source_object), freeze: true)
+ object.instance_variable_get(:@a).should != nil
+ object.instance_variable_get(:@a).should.frozen?
+ end
+
+ it "deduplicates frozen strings" do
+ source_object = ["foo" + "bar", "foobar"]
+ object = Marshal.load(Marshal.dump(source_object), freeze: true)
+
+ object[0].should.equal?(object[1])
+ end
+ end
+
+ it "does not freeze modules" do
+ object = Marshal.load(Marshal.dump(Kernel), freeze: true)
+ object.should_not.frozen?
+ Kernel.should_not.frozen?
+ end
+
+ it "does not freeze classes" do
+ object = Marshal.load(Marshal.dump(Object), freeze: true)
+ object.should_not.frozen?
+ Object.should_not.frozen?
+ end
+
+ it "does freeze extended objects" do
+ object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", freeze: true)
+ object.should.frozen?
+ end
+
+ it "does freeze extended objects with instance variables" do
+ object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x06:\n@ivarT", freeze: true)
+ object.should.frozen?
+ end
+
+ it "returns frozen object having #_dump method" do
+ object = Marshal.load(Marshal.dump(UserDefined.new), freeze: true)
+ object.should.frozen?
+ end
+
+ it "returns frozen object responding to #marshal_dump and #marshal_load" do
+ object = Marshal.load(Marshal.dump(UserMarshal.new), freeze: true)
+ object.should.frozen?
+ end
+
+ it "returns frozen object extended by a module" do
+ object = Object.new
+ object.extend(MarshalSpec::ModuleToExtendBy)
+
+ object = Marshal.load(Marshal.dump(object), freeze: true)
+ object.should.frozen?
+ end
+
+ it "does not call freeze method" do
+ object = MarshalSpec::ObjectWithFreezeRaisingException.new
+ object = Marshal.load(Marshal.dump(object), freeze: true)
+ object.should.frozen?
+ end
+
+ it "returns frozen object even if object does not respond to freeze method" do
+ object = MarshalSpec::ObjectWithoutFreeze.new
+ object = Marshal.load(Marshal.dump(object), freeze: true)
+ object.should.frozen?
+ end
+
+ it "returns a frozen object when is an instance of String/Array/Regexp/Hash subclass and has instance variables" do
+ source_object = UserString.new
+ source_object.instance_variable_set(:@foo, "bar")
+
+ object = Marshal.load(Marshal.dump(source_object), freeze: true)
+ object.should.frozen?
+ end
+
+ describe "when called with a proc" do
+ it "call the proc with frozen objects" do
+ arr = []
+ s = +'hi'
+ s.instance_variable_set(:@foo, 5)
+ st = Struct.new("Brittle", :a).new
+ st.instance_variable_set(:@clue, 'none')
+ st.a = 0.0
+ h = Hash.new('def')
+ h['nine'] = 9
+ a = [:a, :b, :c]
+ a.instance_variable_set(:@two, 2)
+ obj = [s, 10, s, s, st, a]
+ obj.instance_variable_set(:@zoo, 'ant')
+ proc = Proc.new { |o| arr << o; o}
+
+ Marshal.load(
+ "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F",
+ proc,
+ freeze: true,
+ )
+
+ arr.should == [
+ false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st,
+ :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]],
+ ]
+
+ arr.each do |v|
+ v.should.frozen?
+ end
+
+ Struct.send(:remove_const, :Brittle)
+ end
+
+ it "does not freeze the object returned by the proc" do
+ string = Marshal.load(Marshal.dump("foo"), proc { |o| o.upcase }, freeze: true)
+ string.should == "FOO"
+ string.should_not.frozen?
+ end
+ end
+ end
+
+ describe "when called with a proc" do
+ it "call the proc with fully initialized strings" do
+ utf8_string = "foo".encode(Encoding::UTF_8)
+ Marshal.load(Marshal.dump(utf8_string), proc { |arg|
+ if arg.is_a?(String)
+ arg.should == utf8_string
+ arg.encoding.should == Encoding::UTF_8
+ end
+ arg
+ })
+ end
+
+ it "no longer mutate the object after it was passed to the proc" do
+ string = Marshal.load(Marshal.dump("foo"), :freeze.to_proc)
+ string.should.frozen?
+ end
+
+ it "call the proc with extended objects" do
+ objs = []
+ obj = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", Proc.new { |o| objs << o; o })
+ objs.should == [obj]
+ end
+
+ it "returns the value of the proc" do
+ Marshal.load(Marshal.dump([1,2]), proc { [3,4] }).should == [3,4]
+ end
+
+ it "calls the proc for recursively visited data" do
+ a = [1]
+ a << a
+ ret = []
+ Marshal.load(Marshal.dump(a), proc { |arg| ret << arg.inspect; arg })
+ ret[0].should == 1.inspect
+ ret[1].should == a.inspect
+ ret.size.should == 2
+ end
+
+ it "loads an Array with proc" do
+ arr = []
+ s = +'hi'
+ s.instance_variable_set(:@foo, 5)
+ st = Struct.new("Brittle", :a).new
+ st.instance_variable_set(:@clue, 'none')
+ st.a = 0.0
+ h = Hash.new('def')
+ h['nine'] = 9
+ a = [:a, :b, :c]
+ a.instance_variable_set(:@two, 2)
+ obj = [s, 10, s, s, st, a]
+ obj.instance_variable_set(:@zoo, 'ant')
+ proc = Proc.new { |o| arr << o.dup; o}
+
+ Marshal.load("\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", proc)
+
+ arr.should == [
+ false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st,
+ :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]],
+ ]
+ Struct.send(:remove_const, :Brittle)
+ end
+ end
+
+ describe "when called with nil for the proc argument" do
+ it "behaves as if no proc argument was passed" do
+ a = [1]
+ a << a
+ b = Marshal.load(Marshal.dump(a), nil)
+ b.should == a
+ end
+ end
+
+ describe "when called on objects with custom _dump methods" do
+ it "does not set instance variables of an object with user-defined _dump/_load" do
+ # this string represents: <#UserPreviouslyDefinedWithInitializedIvar @field2=7 @field1=6>
+ dump_str = "\004\bu:-UserPreviouslyDefinedWithInitializedIvar\a:\f@field2i\f:\f@field1i\v"
+
+ UserPreviouslyDefinedWithInitializedIvar.should_receive(:_load).and_return(UserPreviouslyDefinedWithInitializedIvar.new)
+ marshaled_obj = Marshal.load(dump_str)
+
+ marshaled_obj.should.instance_of?(UserPreviouslyDefinedWithInitializedIvar)
+ marshaled_obj.field1.should == nil
+ marshaled_obj.field2.should == nil
+ end
+
+ it "loads the String in non US-ASCII and non UTF-8 encoding" do
+ source_object = UserDefinedString.new("a".encode("windows-1251"))
+ object = Marshal.load(Marshal.dump(source_object))
+ object.string.should == "a".encode("windows-1251")
+ end
+
+ it "loads the String in multibyte encoding" do
+ source_object = UserDefinedString.new("a".encode("utf-32le"))
+ object = Marshal.load(Marshal.dump(source_object))
+ object.string.should == "a".encode("utf-32le")
+ end
+
+ describe "that returns an immediate value" do
+ it "loads an array containing an instance of the object, followed by multiple instances of another object" do
+ str = "string"
+
+ # this string represents: [<#UserDefinedImmediate A>, <#String "string">, <#String "string">]
+ marshaled_obj = Marshal.load("\004\b[\bu:\031UserDefinedImmediate\000\"\vstring@\a")
+
+ marshaled_obj.should == [nil, str, str]
+ end
+
+ it "loads any structure with multiple references to the same object, followed by multiple instances of another object" do
+ str = "string"
+
+ # this string represents: {a: <#UserDefinedImmediate A>, b: <#UserDefinedImmediate A>, c: <#String "string">, d: <#String "string">}
+ hash_dump = "\x04\b{\t:\x06aIu:\x19UserDefinedImmediate\x00\x06:\x06ET:\x06b@\x06:\x06cI\"\vstring\x06;\aT:\x06d@\a"
+
+ marshaled_obj = Marshal.load(hash_dump)
+ marshaled_obj.should == {a: nil, b: nil, c: str, d: str}
+
+ # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate A>, <#String "string">, <#String "string">]
+ array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ET@\x06I\"\vstring\x06;\x06T@\a"
+
+ marshaled_obj = Marshal.load(array_dump)
+ marshaled_obj.should == [nil, nil, str, str]
+ end
+
+ it "loads an array containing references to multiple instances of the object, followed by multiple instances of another object" do
+ str = "string"
+
+ # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate B>, <#String "string">, <#String "string">]
+ array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ETIu;\x00\x00\x06;\x06TI\"\vstring\x06;\x06T@\b"
+
+ marshaled_obj = Marshal.load(array_dump)
+ marshaled_obj.should == [nil, nil, str, str]
+ end
+ end
+ end
+
+ it "loads an array containing objects having _dump method, and with proc" do
+ arr = []
+ myproc = Proc.new { |o| arr << o.dup; o }
+ o1 = UserDefined.new;
+ o2 = UserDefinedWithIvar.new
+ obj = [o1, o2, o1, o2]
+
+ Marshal.load("\x04\b[\tu:\x10UserDefined\x18\x04\b[\aI\"\nstuff\x06:\x06EF@\x06u:\x18UserDefinedWithIvar>\x04\b[\bI\"\nstuff\a:\x06EF:\t@foo:\x18UserDefinedWithIvarI\"\tmore\x06;\x00F@\a@\x06@\a", myproc)
+
+ arr[0].should == o1
+ arr[1].should == o2
+ arr[2].should == obj
+ arr.size.should == 3
+ end
+
+ it "loads an array containing objects having marshal_dump method, and with proc" do
+ arr = []
+ proc = Proc.new { |o| arr << o.dup; o }
+ o1 = UserMarshal.new
+ o2 = UserMarshalWithIvar.new
+
+ Marshal.load("\004\b[\tU:\020UserMarshal\"\nstuffU:\030UserMarshalWithIvar[\006\"\fmy data@\006@\b", proc)
+
+ arr[0].should == 'stuff'
+ arr[1].should == o1
+ arr[2].should == 'my data'
+ arr[3].should == ['my data']
+ arr[4].should == o2
+ arr[5].should == [o1, o2, o1, o2]
+
+ arr.size.should == 6
+ end
+
+ it "assigns classes to nested subclasses of Array correctly" do
+ arr = ArraySub.new(ArraySub.new)
+ arr_dump = Marshal.dump(arr)
+ Marshal.load(arr_dump).class.should == ArraySub
+ end
+
+ it "loads subclasses of Array with overridden << and push correctly" do
+ arr = ArraySubPush.new
+ arr[0] = '1'
+ arr_dump = Marshal.dump(arr)
+ Marshal.load(arr_dump).should == arr
+ end
+
+ it "raises a TypeError with bad Marshal version" do
+ marshal_data = +'\xff\xff'
+ marshal_data[0] = (Marshal::MAJOR_VERSION).chr
+ marshal_data[1] = (Marshal::MINOR_VERSION + 1).chr
+
+ -> { Marshal.load(marshal_data) }.should.raise(TypeError)
+
+ marshal_data = +'\xff\xff'
+ marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr
+ marshal_data[1] = (Marshal::MINOR_VERSION).chr
+
+ -> { Marshal.load(marshal_data) }.should.raise(TypeError)
+ end
+
+ it "raises EOFError on loading an empty file" do
+ temp_file = tmp("marshal.rubyspec.tmp.#{Process.pid}")
+ file = File.new(temp_file, "w+")
+ begin
+ -> { Marshal.load(file) }.should.raise(EOFError)
+ ensure
+ file.close
+ rm_r temp_file
+ end
+ end
+
+ # Note: Ruby 1.9 should be compatible with older marshal format
+ MarshalSpec::DATA.each do |description, (object, marshal, attributes)|
+ it "loads a #{description}" do
+ Marshal.load(marshal).should == object
+ end
+ end
+
+ MarshalSpec::DATA_19.each do |description, (object, marshal, attributes)|
+ it "loads a #{description}" do
+ Marshal.load(marshal).should == object
+ end
+ end
+
+ describe "for an Array" do
+ it "loads an array containing the same objects" do
+ s = 'oh'
+ b = 'hi'
+ r = //
+ d = [b, :no, s, :go]
+ c = String
+ f = 1.0
+
+ o1 = UserMarshalWithIvar.new; o2 = UserMarshal.new
+
+ obj = [:so, 'hello', 100, :so, :so, d, :so, o2, :so, :no, o2,
+ :go, c, nil, Struct::Pyramid.new, f, :go, :no, s, b, r,
+ :so, 'huh', o1, true, b, b, 99, r, b, s, :so, f, c, :no, o1, d]
+
+ Marshal.load("\004\b[*:\aso\"\nhelloii;\000;\000[\t\"\ahi:\ano\"\aoh:\ago;\000U:\020UserMarshal\"\nstuff;\000;\006@\n;\ac\vString0S:\024Struct::Pyramid\000f\0061;\a;\006@\t@\b/\000\000;\000\"\bhuhU:\030UserMarshalWithIvar[\006\"\fmy dataT@\b@\bih@\017@\b@\t;\000@\016@\f;\006@\021@\a").should ==
+ obj
+ end
+
+ it "loads an array having ivar" do
+ s = +'well'
+ s.instance_variable_set(:@foo, 10)
+ obj = ['5', s, 'hi'].extend(Meths, MethsMore)
+ obj.instance_variable_set(:@mix, s)
+ new_obj = Marshal.load("\004\bI[\b\"\0065I\"\twell\006:\t@fooi\017\"\ahi\006:\t@mix@\a")
+ new_obj.should == obj
+ new_obj.instance_variable_get(:@mix).should.equal? new_obj[1]
+ new_obj[1].instance_variable_get(:@foo).should == 10
+ end
+
+ it "loads an extended Array object containing a user-marshaled object" do
+ obj = [UserMarshal.new, UserMarshal.new].extend(Meths)
+ dump = "\x04\be:\nMeths[\ao:\x10UserMarshal\x06:\n@dataI\"\nstuff\x06:\x06ETo;\x06\x06;\aI\"\nstuff\x06;\bT"
+ new_obj = Marshal.load(dump)
+
+ new_obj.should == obj
+ obj_ancestors = class << obj; ancestors[1..-1]; end
+ new_obj_ancestors = class << new_obj; ancestors[1..-1]; end
+ obj_ancestors.should == new_obj_ancestors
+ end
+ end
+
+ describe "for a Hash" do
+ it "loads an extended_user_hash with a parameter to initialize" do
+ obj = UserHashInitParams.new(:abc).extend(Meths)
+
+ new_obj = Marshal.load("\004\bIe:\nMethsC:\027UserHashInitParams{\000\006:\a@a:\babc")
+
+ new_obj.should == obj
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class].should == Meths
+ new_obj_metaclass_ancestors[@num_self_class+1].should == UserHashInitParams
+ end
+
+ it "loads an extended hash object containing a user-marshaled object" do
+ obj = {a: UserMarshal.new}.extend(Meths)
+
+ new_obj = Marshal.load("\004\be:\nMeths{\006:\006aU:\020UserMarshal\"\nstuff")
+
+ new_obj.should == obj
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class].should == Meths
+ new_obj_metaclass_ancestors[@num_self_class+1].should == Hash
+ end
+
+ it "preserves hash ivars when hash contains a string having ivar" do
+ s = +'string'
+ s.instance_variable_set :@string_ivar, 'string ivar'
+ h = { key: s }
+ h.instance_variable_set :@hash_ivar, 'hash ivar'
+
+ unmarshalled = Marshal.load(Marshal.dump(h))
+ unmarshalled.instance_variable_get(:@hash_ivar).should == 'hash ivar'
+ unmarshalled[:key].instance_variable_get(:@string_ivar).should == 'string ivar'
+ end
+
+ it "preserves compare_by_identity behaviour" do
+ h = { a: 1 }
+ h.compare_by_identity
+ unmarshalled = Marshal.load(Marshal.dump(h))
+ unmarshalled.should.compare_by_identity?
+
+ h = { a: 1 }
+ unmarshalled = Marshal.load(Marshal.dump(h))
+ unmarshalled.should_not.compare_by_identity?
+ end
+
+ it "preserves compare_by_identity behaviour for a Hash subclass" do
+ h = UserHash.new({ a: 1 })
+ h.compare_by_identity
+ unmarshalled = Marshal.load(Marshal.dump(h))
+ unmarshalled.should.compare_by_identity?
+
+ h = UserHash.new({ a: 1 })
+ unmarshalled = Marshal.load(Marshal.dump(h))
+ unmarshalled.should_not.compare_by_identity?
+ end
+
+ it "allocates an instance of the proper class when Hash subclass with compare_by_identity behaviour" do
+ h = UserHash.new({ a: 1 })
+ h.compare_by_identity
+
+ unmarshalled = Marshal.load(Marshal.dump(h))
+ unmarshalled.should.kind_of?(UserHash)
+ end
+ end
+
+ describe "for a Symbol" do
+ it "loads a Symbol" do
+ sym = Marshal.load("\004\b:\vsymbol")
+ sym.should == :symbol
+ sym.encoding.should == Encoding::US_ASCII
+ end
+
+ it "loads a big Symbol" do
+ sym = ('big' * 100).to_sym
+ Marshal.load("\004\b:\002,\001#{'big' * 100}").should == sym
+ end
+
+ it "loads an encoded Symbol" do
+ s = "\u2192"
+
+ sym = Marshal.load("\x04\bI:\b\xE2\x86\x92\x06:\x06ET")
+ sym.should == s.encode("utf-8").to_sym
+ sym.encoding.should == Encoding::UTF_8
+
+ sym = Marshal.load("\x04\bI:\t\xFE\xFF!\x92\x06:\rencoding\"\vUTF-16")
+ sym.should == s.encode("utf-16").to_sym
+ sym.encoding.should == Encoding::UTF_16
+
+ sym = Marshal.load("\x04\bI:\a\x92!\x06:\rencoding\"\rUTF-16LE")
+ sym.should == s.encode("utf-16le").to_sym
+ sym.encoding.should == Encoding::UTF_16LE
+
+ sym = Marshal.load("\x04\bI:\a!\x92\x06:\rencoding\"\rUTF-16BE")
+ sym.should == s.encode("utf-16be").to_sym
+ sym.encoding.should == Encoding::UTF_16BE
+
+ sym = Marshal.load("\x04\bI:\a\xA2\xAA\x06:\rencoding\"\vEUC-JP")
+ sym.should == s.encode("euc-jp").to_sym
+ sym.encoding.should == Encoding::EUC_JP
+
+ sym = Marshal.load("\x04\bI:\a\x81\xA8\x06:\rencoding\"\x10Windows-31J")
+ sym.should == s.encode("sjis").to_sym
+ sym.encoding.should == Encoding::SJIS
+ end
+
+ it "loads a binary encoded Symbol" do
+ s = "\u2192".dup.force_encoding("binary").to_sym
+ sym = Marshal.load("\x04\b:\b\xE2\x86\x92")
+ sym.should == s
+ sym.encoding.should == Encoding::BINARY
+ end
+
+ it "loads multiple Symbols sharing the same encoding" do
+ # Note that the encoding is a link for the second Symbol
+ symbol1 = "I:\t\xE2\x82\xACa\x06:\x06ET"
+ symbol2 = "I:\t\xE2\x82\xACb\x06;\x06T"
+ dump = "\x04\b[\a#{symbol1}#{symbol2}"
+ value = Marshal.load(dump)
+ value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8]
+ expected = [
+ "€a".dup.force_encoding(Encoding::UTF_8).to_sym,
+ "€b".dup.force_encoding(Encoding::UTF_8).to_sym
+ ]
+ value.should == expected
+
+ value = Marshal.load("\x04\b[\b#{symbol1}#{symbol2};\x00")
+ value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8, Encoding::UTF_8]
+ value.should == [*expected, expected[0]]
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before symbol characters end" do
+ Marshal.dump(:hello).should == "\x04\b:\nhello"
+
+ -> {
+ Marshal.load("\x04\b:\nhel")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for a String" do
+ it "loads a string having ivar with ref to self" do
+ obj = +'hi'
+ obj.instance_variable_set(:@self, obj)
+ Marshal.load("\004\bI\"\ahi\006:\n@self@\000").should == obj
+ end
+
+ it "loads a string through StringIO stream" do
+ require 'stringio'
+ obj = "This is a string which should be unmarshalled through StringIO stream!"
+ Marshal.load(StringIO.new(Marshal.dump(obj))).should == obj
+ end
+
+ it "sets binmode if it is loading through StringIO stream" do
+ io = StringIO.new("\004\b:\vsymbol")
+ def io.binmode; raise "binmode"; end
+ -> { Marshal.load(io) }.should.raise(RuntimeError, "binmode")
+ end
+
+ it "loads a string with an ivar" do
+ str = Marshal.load("\x04\bI\"\x00\x06:\t@fooI\"\bbar\x06:\x06EF")
+ str.instance_variable_get("@foo").should == "bar"
+ end
+
+ it "loads a String subclass with custom constructor" do
+ str = Marshal.load("\x04\bC: UserCustomConstructorString\"\x00")
+ str.should.instance_of?(UserCustomConstructorString)
+ end
+
+ it "loads a US-ASCII String" do
+ str = "abc".dup.force_encoding("us-ascii")
+ data = "\x04\bI\"\babc\x06:\x06EF"
+ result = Marshal.load(data)
+ result.should == str
+ result.encoding.should.equal?(Encoding::US_ASCII)
+ end
+
+ it "loads a UTF-8 String" do
+ str = "\x6d\xc3\xb6\x68\x72\x65".dup.force_encoding("utf-8")
+ data = "\x04\bI\"\vm\xC3\xB6hre\x06:\x06ET"
+ result = Marshal.load(data)
+ result.should == str
+ result.encoding.should.equal?(Encoding::UTF_8)
+ end
+
+ it "loads a String in another encoding" do
+ str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".dup.force_encoding("utf-16le")
+ data = "\x04\bI\"\x0Fm\x00\xF6\x00h\x00r\x00e\x00\x06:\rencoding\"\rUTF-16LE"
+ result = Marshal.load(data)
+ result.should == str
+ result.encoding.should.equal?(Encoding::UTF_16LE)
+ end
+
+ it "loads a String as BINARY if no encoding is specified at the end" do
+ str = "\xC3\xB8".dup.force_encoding("BINARY")
+ data = "\x04\b\"\a\xC3\xB8".dup.force_encoding("UTF-8")
+ result = Marshal.load(data)
+ result.encoding.should == Encoding::BINARY
+ result.should == str
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before string characters end" do
+ Marshal.dump("hello").should == "\x04\b\"\nhello"
+
+ -> {
+ Marshal.load("\x04\b\"\nhel")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for a Struct" do
+ it "loads a extended_struct having fields with same objects" do
+ s = 'hi'
+ obj = Struct.new("Extended", :a, :b).new.extend(Meths)
+ dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a0:\006b0"
+ Marshal.load(dump).should == obj
+
+ obj.a = [:a, s]
+ obj.b = [:Meths, s]
+ dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a"
+ Marshal.load(dump).should == obj
+ Struct.send(:remove_const, :Extended)
+ end
+
+ it "loads a struct having ivar" do
+ obj = Struct.new("Thick").new
+ obj.instance_variable_set(:@foo, 5)
+ reloaded = Marshal.load("\004\bIS:\022Struct::Thick\000\006:\t@fooi\n")
+ reloaded.should == obj
+ reloaded.instance_variable_get(:@foo).should == 5
+ Struct.send(:remove_const, :Thick)
+ end
+
+ it "loads a struct having fields" do
+ obj = Struct.new("Ure1", :a, :b).new
+ Marshal.load("\004\bS:\021Struct::Ure1\a:\006a0:\006b0").should == obj
+ Struct.send(:remove_const, :Ure1)
+ end
+
+ it "does not call initialize on the unmarshaled struct" do
+ threadlocal_key = MarshalSpec::StructWithUserInitialize::THREADLOCAL_KEY
+
+ s = MarshalSpec::StructWithUserInitialize.new('foo')
+ Thread.current[threadlocal_key].should == ['foo']
+ s.a.should == 'foo'
+
+ Thread.current[threadlocal_key] = nil
+
+ dumped = Marshal.dump(s)
+ loaded = Marshal.load(dumped)
+
+ Thread.current[threadlocal_key].should == nil
+ loaded.a.should == 'foo'
+ end
+ end
+
+ describe "for a Data" do
+ it "loads a Data" do
+ obj = MarshalSpec::DataSpec::Measure.new(100, 'km')
+ dumped = "\x04\bS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm"
+ Marshal.dump(obj).should == dumped
+
+ Marshal.load(dumped).should == obj
+ end
+
+ it "loads an extended Data" do
+ obj = MarshalSpec::DataSpec::MeasureExtended.new(100, "km")
+ dumped = "\x04\bS:+MarshalSpec::DataSpec::MeasureExtended\a:\vamountii:\tunit\"\akm"
+ Marshal.dump(obj).should == dumped
+
+ Marshal.load(dumped).should == obj
+ end
+
+ it "returns a frozen object" do
+ obj = MarshalSpec::DataSpec::Measure.new(100, 'km')
+ dumped = "\x04\bS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm"
+ Marshal.dump(obj).should == dumped
+
+ Marshal.load(dumped).should.frozen?
+ end
+ end
+
+ describe "for an Exception" do
+ it "loads a marshalled exception with no message" do
+ obj = Exception.new
+ loaded = Marshal.load("\004\bo:\016Exception\a:\abt0:\tmesg0")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ loaded = Marshal.load("\x04\bo:\x0EException\a:\tmesg0:\abt0")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ end
+
+ it "loads a marshalled exception with a message" do
+ obj = Exception.new("foo")
+ loaded = Marshal.load("\004\bo:\016Exception\a:\abt0:\tmesg\"\bfoo")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ loaded = Marshal.load("\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt0")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ end
+
+ it "loads a marshalled exception with a backtrace" do
+ obj = Exception.new("foo")
+ obj.set_backtrace(["foo/bar.rb:10"])
+ loaded = Marshal.load("\004\bo:\016Exception\a:\abt[\006\"\022foo/bar.rb:10:\tmesg\"\bfoo")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ loaded = Marshal.load("\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt[\x06I\"\x12foo/bar.rb:10\x06;\aF")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ end
+
+ it "loads an marshalled exception with ivars" do
+ s = 'hi'
+ arr = [:so, :so, s, s]
+ obj = Exception.new("foo")
+ obj.instance_variable_set :@arr, arr
+
+ loaded = Marshal.load("\x04\bo:\x0EException\b:\tmesg\"\bfoo:\abt0:\t@arr[\t:\aso;\t\"\ahi@\b")
+ new_arr = loaded.instance_variable_get :@arr
+
+ loaded.message.should == obj.message
+ new_arr.should == arr
+ end
+ end
+
+ describe "for an Object" do
+ it "loads an object" do
+ Marshal.load("\004\bo:\vObject\000").should.is_a?(Object)
+ end
+
+ it "loads an extended Object" do
+ obj = Object.new.extend(Meths)
+
+ new_obj = Marshal.load("\004\be:\nMethso:\vObject\000")
+
+ new_obj.class.should == obj.class
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class, 2].should == [Meths, Object]
+ end
+
+ it "loads an object having ivar" do
+ s = 'hi'
+ arr = [:so, :so, s, s]
+ obj = Object.new
+ obj.instance_variable_set :@str, arr
+
+ new_obj = Marshal.load("\004\bo:\vObject\006:\t@str[\t:\aso;\a\"\ahi@\a")
+ new_str = new_obj.instance_variable_get :@str
+
+ new_str.should == arr
+ end
+
+ it "loads an Object with a non-US-ASCII instance variable" do
+ ivar = "@é".dup.force_encoding(Encoding::UTF_8).to_sym
+ obj = Marshal.load("\x04\bo:\vObject\x06I:\b@\xC3\xA9\x06:\x06ETi\x06")
+ obj.instance_variables.should == [ivar]
+ obj.instance_variables[0].encoding.should == Encoding::UTF_8
+ obj.instance_variable_get(ivar).should == 1
+ end
+
+ it "raises ArgumentError if the object from an 'o' stream is not dumpable as 'o' type user class" do
+ -> do
+ Marshal.load("\x04\bo:\tFile\001\001:\001\005@path\"\x10/etc/passwd")
+ end.should.raise(ArgumentError)
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before class name end" do
+ Marshal.dump(Object.new).should == "\x04\bo:\vObject\x00"
+
+ -> {
+ Marshal.load("\x04\bo:\vObj")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for an object responding to #marshal_dump and #marshal_load" do
+ it "loads a user-marshaled object" do
+ obj = UserMarshal.new
+ obj.data = :data
+ value = [obj, :data]
+ dump = Marshal.dump(value)
+ dump.should == "\x04\b[\aU:\x10UserMarshal:\tdata;\x06"
+ reloaded = Marshal.load(dump)
+ reloaded.should == value
+ end
+ end
+
+ describe "for a user object" do
+ it "loads a user-marshaled extended object" do
+ obj = UserMarshal.new.extend(Meths)
+
+ new_obj = Marshal.load("\004\bU:\020UserMarshal\"\nstuff")
+
+ new_obj.should == obj
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class].should == UserMarshal
+ end
+
+ it "loads a UserObject" do
+ Marshal.load("\004\bo:\017UserObject\000").should.is_a?(UserObject)
+ end
+
+ describe "that extends a core type other than Object or BasicObject" do
+ after :each do
+ MarshalSpec.reset_swapped_class
+ end
+
+ it "raises ArgumentError if the resulting class does not extend the same type" do
+ MarshalSpec.set_swapped_class(Class.new(Hash))
+ data = Marshal.dump(MarshalSpec::SwappedClass.new)
+
+ MarshalSpec.set_swapped_class(Class.new(Array))
+ -> { Marshal.load(data) }.should.raise(ArgumentError)
+
+ MarshalSpec.set_swapped_class(Class.new)
+ -> { Marshal.load(data) }.should.raise(ArgumentError)
+ end
+ end
+ end
+
+ describe "for a Regexp" do
+ ruby_version_is "4.1" do
+ it "raises FrozenError for an extended Regexp" do
+ -> {
+ Marshal.load("\004\be:\nMethse:\016MethsMore/\n[a-z]\000")
+ }.should.raise(FrozenError)
+ end
+
+ it "raises FrozenError when regexp has instance variables" do
+ -> {
+ Marshal.load("\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/")
+ }.should.raise(FrozenError)
+ end
+ end
+
+ ruby_version_is ""..."4.1" do
+ it "loads an extended Regexp" do
+ obj = /[a-z]/.dup.extend(Meths, MethsMore)
+ new_obj = Marshal.load("\004\be:\nMethse:\016MethsMore/\n[a-z]\000")
+
+ new_obj.should == obj
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class, 3].should ==
+ [Meths, MethsMore, Regexp]
+ end
+
+ it "restore the regexp instance variables" do
+ obj = Regexp.new("hello")
+ obj.instance_variable_set(:@regexp_ivar, [42])
+
+ new_obj = Marshal.load("\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/")
+ new_obj.instance_variables.should == [:@regexp_ivar]
+ new_obj.instance_variable_get(:@regexp_ivar).should == [42]
+ end
+ end
+
+ it "loads a Regexp subclass instance variables" do
+ obj = UserRegexp.new('abc')
+ obj.instance_variable_set(:@noise, 'much')
+
+ new_obj = Marshal.load(Marshal.dump(obj))
+
+ new_obj.should == obj
+ new_obj.instance_variable_get(:@noise).should == 'much'
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class, 2].should ==
+ [UserRegexp, Regexp]
+ end
+
+ it "loads a Regexp subclass instance variables when it is extended with a module" do
+ obj = UserRegexp.new('').extend(Meths)
+ obj.instance_variable_set(:@noise, 'much')
+
+ new_obj = Marshal.load("\004\bIe:\nMethsC:\017UserRegexp/\000\000\006:\v@noise\"\tmuch")
+
+ new_obj.should == obj
+ new_obj.instance_variable_get(:@noise).should == 'much'
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class, 3].should ==
+ [Meths, UserRegexp, Regexp]
+ end
+
+ it "preserves Regexp encoding" do
+ source_object = Regexp.new("a".encode("utf-32le"))
+ regexp = Marshal.load(Marshal.dump(source_object))
+
+ regexp.encoding.should == Encoding::UTF_32LE
+ regexp.source.should == "a".encode("utf-32le")
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before source string end" do
+ Marshal.dump(/hello world/).should == "\x04\bI/\x10hello world\x00\x06:\x06EF"
+
+ -> {
+ Marshal.load("\x04\bI/\x10hel")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for a Float" do
+ it "loads a Float NaN" do
+ obj = 0.0 / 0.0
+ Marshal.load("\004\bf\bnan").to_s.should == obj.to_s
+ end
+
+ it "loads a Float 1.3" do
+ Marshal.load("\004\bf\v1.3\000\314\315").should == 1.3
+ end
+
+ it "loads a Float -5.1867345e-22" do
+ obj = -5.1867345e-22
+ Marshal.load("\004\bf\037-5.1867345000000008e-22\000\203_").should be_close(obj, 1e-30)
+ end
+
+ it "loads a Float 1.1867345e+22" do
+ obj = 1.1867345e+22
+ Marshal.load("\004\bf\0361.1867344999999999e+22\000\344@").should == obj
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before float string representation end" do
+ Marshal.dump(1.3).should == "\x04\bf\b1.3"
+
+ -> {
+ Marshal.load("\004\bf\v1")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for an Integer" do
+ it "loads 0" do
+ Marshal.load("\004\bi\000").should == 0
+ Marshal.load("\004\bi\005").should == 0
+ end
+
+ it "loads an Integer 8" do
+ Marshal.load("\004\bi\r" ).should == 8
+ end
+
+ it "loads and Integer -8" do
+ Marshal.load("\004\bi\363" ).should == -8
+ end
+
+ it "loads an Integer 1234" do
+ Marshal.load("\004\bi\002\322\004").should == 1234
+ end
+
+ it "loads an Integer -1234" do
+ Marshal.load("\004\bi\376.\373").should == -1234
+ end
+
+ it "loads an Integer 4611686018427387903" do
+ Marshal.load("\004\bl+\t\377\377\377\377\377\377\377?").should == 4611686018427387903
+ end
+
+ it "loads an Integer -4611686018427387903" do
+ Marshal.load("\004\bl-\t\377\377\377\377\377\377\377?").should == -4611686018427387903
+ end
+
+ it "loads an Integer 2361183241434822606847" do
+ Marshal.load("\004\bl+\n\377\377\377\377\377\377\377\377\177\000").should == 2361183241434822606847
+ end
+
+ it "loads an Integer -2361183241434822606847" do
+ Marshal.load("\004\bl-\n\377\377\377\377\377\377\377\377\177\000").should == -2361183241434822606847
+ end
+
+ it "raises ArgumentError if the input is too short" do
+ ["\004\bi",
+ "\004\bi\001",
+ "\004\bi\002",
+ "\004\bi\002\0",
+ "\004\bi\003",
+ "\004\bi\003\0",
+ "\004\bi\003\0\0",
+ "\004\bi\004",
+ "\004\bi\004\0",
+ "\004\bi\004\0\0",
+ "\004\bi\004\0\0\0"].each do |invalid|
+ -> { Marshal.load(invalid) }.should.raise(ArgumentError)
+ end
+ end
+
+ if 0.size == 8 # for platforms like x86_64
+ it "roundtrips 4611686018427387903 from dump/load correctly" do
+ Marshal.load(Marshal.dump(4611686018427387903)).should == 4611686018427387903
+ end
+ end
+ end
+
+ describe "for a Rational" do
+ it "loads" do
+ r = Marshal.load(Marshal.dump(Rational(1, 3)))
+ r.should == Rational(1, 3)
+ r.should.frozen?
+ end
+ end
+
+ describe "for a Complex" do
+ it "loads" do
+ c = Marshal.load(Marshal.dump(Complex(4, 3)))
+ c.should == Complex(4, 3)
+ c.should.frozen?
+ end
+ end
+
+ describe "for a Bignum" do
+ platform_is c_long_size: 64 do
+ context "that is Bignum on 32-bit platforms but Fixnum on 64-bit" do
+ it "dumps a Fixnum" do
+ val = Marshal.load("\004\bl+\ab:wU")
+ val.should == 1433877090
+ val.class.should == Integer
+ end
+
+ it "dumps an array containing multiple references to the Bignum as an array of Fixnum" do
+ arr = Marshal.load("\004\b[\al+\a\223BwU@\006")
+ arr.should == [1433879187, 1433879187]
+ arr.each { |v| v.class.should == Integer }
+ end
+ end
+ end
+ end
+
+ describe "for a Time" do
+ it "loads" do
+ Marshal.load(Marshal.dump(Time.at(1))).should == Time.at(1)
+ end
+
+ it "loads serialized instance variables" do
+ t = Time.new
+ t.instance_variable_set(:@foo, 'bar')
+
+ Marshal.load(Marshal.dump(t)).instance_variable_get(:@foo).should == 'bar'
+ end
+
+ it "loads Time objects stored as links" do
+ t = Time.new
+
+ t1, t2 = Marshal.load(Marshal.dump([t, t]))
+ t1.should.equal? t2
+ end
+
+ it "keeps the local zone" do
+ with_timezone 'AST', 3 do
+ t = Time.local(2012, 1, 1)
+ Marshal.load(Marshal.dump(t)).zone.should == t.zone
+ end
+ end
+
+ it "keeps UTC zone" do
+ t = Time.now.utc
+ t2 = Marshal.load(Marshal.dump(t))
+ t2.should.utc?
+ end
+
+ it "keeps the zone" do
+ t = nil
+
+ with_timezone 'AST', 4 do
+ t = Time.local(2012, 1, 1)
+ end
+
+ with_timezone 'EET', -2 do
+ Marshal.load(Marshal.dump(t)).zone.should == 'AST'
+ end
+ end
+
+ it "keeps utc offset" do
+ t = Time.new(2007,11,1,15,25,0, "+09:00")
+ t2 = Marshal.load(Marshal.dump(t))
+ t2.utc_offset.should == 32400
+ end
+
+ it "keeps nanoseconds" do
+ t = Time.now
+ Marshal.load(Marshal.dump(t)).nsec.should == t.nsec
+ end
+
+ it "does not add any additional instance variable" do
+ t = Time.now
+ t2 = Marshal.load(Marshal.dump(t))
+ t2.instance_variables.should.empty?
+ end
+ end
+
+ describe "for nil" do
+ it "loads" do
+ Marshal.load("\x04\b0").should == nil
+ end
+ end
+
+ describe "for true" do
+ it "loads" do
+ Marshal.load("\x04\bT").should == true
+ end
+ end
+
+ describe "for false" do
+ it "loads" do
+ Marshal.load("\x04\bF").should == false
+ end
+ end
+
+ describe "for a Class" do
+ it "loads" do
+ Marshal.load("\x04\bc\vString").should == String
+ end
+
+ it "raises ArgumentError if given the name of a non-Module" do
+ -> { Marshal.load("\x04\bc\vKernel") }.should.raise(ArgumentError)
+ end
+
+ it "raises ArgumentError if given a nonexistent class" do
+ -> { Marshal.load("\x04\bc\vStrung") }.should.raise(ArgumentError)
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before class name end" do
+ Marshal.dump(String).should == "\x04\bc\vString"
+
+ -> {
+ Marshal.load("\x04\bc\vStr")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for a Module" do
+ it "loads a module" do
+ Marshal.load("\x04\bm\vKernel").should == Kernel
+ end
+
+ it "raises ArgumentError if given the name of a non-Class" do
+ -> { Marshal.load("\x04\bm\vString") }.should.raise(ArgumentError)
+ end
+
+ it "loads an old module" do
+ Marshal.load("\x04\bM\vKernel").should == Kernel
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before module name end" do
+ Marshal.dump(Kernel).should == "\x04\bm\vKernel"
+
+ -> {
+ Marshal.load("\x04\bm\vKer")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for a wrapped C pointer" do
+ it "loads" do
+ class DumpableDir < Dir
+ def _dump_data
+ path
+ end
+ def _load_data path
+ initialize(path)
+ end
+ end
+
+ data = "\x04\bd:\x10DumpableDirI\"\x06.\x06:\x06ET"
+
+ dir = Marshal.load(data)
+ begin
+ dir.path.should == '.'
+ ensure
+ dir.close
+ end
+ end
+
+ it "raises TypeError when the local class is missing _load_data" do
+ class UnloadableDumpableDir < Dir
+ def _dump_data
+ path
+ end
+ # no _load_data
+ end
+
+ data = "\x04\bd:\x1AUnloadableDumpableDirI\"\x06.\x06:\x06ET"
+
+ -> { Marshal.load(data) }.should.raise(TypeError)
+ end
+
+ it "raises ArgumentError when the local class is a regular object" do
+ data = "\004\bd:\020UserDefined\0"
+
+ -> { Marshal.load(data) }.should.raise(ArgumentError)
+ end
+ end
+
+ describe "when a class does not exist in the namespace" do
+ before :each do
+ NamespaceTest.send(:const_set, :SameName, Class.new)
+ @data = Marshal.dump(NamespaceTest::SameName.new)
+ NamespaceTest.send(:remove_const, :SameName)
+ end
+
+ it "raises an ArgumentError" do
+ message = "undefined class/module NamespaceTest::SameName"
+ -> { Marshal.load(@data) }.should.raise(ArgumentError, message)
+ end
+ end
+
+ it "raises an ArgumentError with full constant name when the dumped constant is missing" do
+ NamespaceTest.send(:const_set, :KaBoom, Class.new)
+ @data = Marshal.dump(NamespaceTest::KaBoom.new)
+ NamespaceTest.send(:remove_const, :KaBoom)
+
+ -> { Marshal.load(@data) }.should.raise(ArgumentError, /NamespaceTest::KaBoom/)
+ end
end
diff --git a/spec/ruby/core/marshal/restore_spec.rb b/spec/ruby/core/marshal/restore_spec.rb
index 7e75d7dea6..3135f881d4 100644
--- a/spec/ruby/core/marshal/restore_spec.rb
+++ b/spec/ruby/core/marshal/restore_spec.rb
@@ -1,6 +1,7 @@
require_relative '../../spec_helper'
-require_relative 'shared/load'
describe "Marshal.restore" do
- it_behaves_like :marshal_load, :restore
+ it "is an alias of Marshal.load" do
+ Marshal.method(:restore).should == Marshal.method(:load)
+ end
end
diff --git a/spec/ruby/core/marshal/shared/load.rb b/spec/ruby/core/marshal/shared/load.rb
deleted file mode 100644
index f599042529..0000000000
--- a/spec/ruby/core/marshal/shared/load.rb
+++ /dev/null
@@ -1,1254 +0,0 @@
-# -*- encoding: binary -*-
-require_relative '../fixtures/marshal_data'
-
-describe :marshal_load, shared: true do
- before :all do
- @num_self_class = 1
- end
-
- it "raises an ArgumentError when the dumped data is truncated" do
- obj = {first: 1, second: 2, third: 3}
- -> { Marshal.send(@method, Marshal.dump(obj)[0, 5]) }.should raise_error(ArgumentError, "marshal data too short")
- end
-
- it "raises an ArgumentError when the argument is empty String" do
- -> { Marshal.send(@method, "") }.should raise_error(ArgumentError, "marshal data too short")
- end
-
- it "raises an ArgumentError when the dumped class is missing" do
- Object.send(:const_set, :KaBoom, Class.new)
- kaboom = Marshal.dump(KaBoom.new)
- Object.send(:remove_const, :KaBoom)
-
- -> { Marshal.send(@method, kaboom) }.should raise_error(ArgumentError)
- end
-
- ruby_version_is "3.1" do
- describe "when called with freeze: true" do
- it "returns frozen strings" do
- string = Marshal.send(@method, Marshal.dump("foo"), freeze: true)
- string.should == "foo"
- string.should.frozen?
-
- utf8_string = "foo".encode(Encoding::UTF_8)
- string = Marshal.send(@method, Marshal.dump(utf8_string), freeze: true)
- string.should == utf8_string
- string.should.frozen?
- end
-
- it "returns frozen arrays" do
- array = Marshal.send(@method, Marshal.dump([1, 2, 3]), freeze: true)
- array.should == [1, 2, 3]
- array.should.frozen?
- end
-
- it "returns frozen hashes" do
- hash = Marshal.send(@method, Marshal.dump({foo: 42}), freeze: true)
- hash.should == {foo: 42}
- hash.should.frozen?
- end
-
- it "returns frozen regexps" do
- regexp = Marshal.send(@method, Marshal.dump(/foo/), freeze: true)
- regexp.should == /foo/
- regexp.should.frozen?
- end
-
- it "returns frozen structs" do
- struct = Marshal.send(@method, Marshal.dump(MarshalSpec::StructToDump.new(1, 2)), freeze: true)
- struct.should == MarshalSpec::StructToDump.new(1, 2)
- struct.should.frozen?
- end
-
- it "returns frozen objects" do
- source_object = Object.new
-
- object = Marshal.send(@method, Marshal.dump(source_object), freeze: true)
- object.should.frozen?
- end
-
- describe "deep freezing" do
- it "returns hashes with frozen keys and values" do
- key = Object.new
- value = Object.new
- source_object = {key => value}
-
- hash = Marshal.send(@method, Marshal.dump(source_object), freeze: true)
- hash.size.should == 1
- hash.keys[0].should.frozen?
- hash.values[0].should.frozen?
- end
-
- it "returns arrays with frozen elements" do
- object = Object.new
- source_object = [object]
-
- array = Marshal.send(@method, Marshal.dump(source_object), freeze: true)
- array.size.should == 1
- array[0].should.frozen?
- end
-
- it "returns structs with frozen members" do
- object1 = Object.new
- object2 = Object.new
- source_object = MarshalSpec::StructToDump.new(object1, object2)
-
- struct = Marshal.send(@method, Marshal.dump(source_object), freeze: true)
- struct.a.should.frozen?
- struct.b.should.frozen?
- end
-
- it "returns objects with frozen instance variables" do
- source_object = Object.new
- instance_variable = Object.new
- source_object.instance_variable_set(:@a, instance_variable)
-
- object = Marshal.send(@method, Marshal.dump(source_object), freeze: true)
- object.instance_variable_get(:@a).should != nil
- object.instance_variable_get(:@a).should.frozen?
- end
-
- it "deduplicates frozen strings" do
- source_object = ["foo" + "bar", "foobar"]
- object = Marshal.send(@method, Marshal.dump(source_object), freeze: true)
-
- object[0].should equal(object[1])
- end
- end
-
- it "does not freeze modules" do
- object = Marshal.send(@method, Marshal.dump(Kernel), freeze: true)
- object.should_not.frozen?
- Kernel.should_not.frozen?
- end
-
- it "does not freeze classes" do
- object = Marshal.send(@method, Marshal.dump(Object), freeze: true)
- object.should_not.frozen?
- Object.should_not.frozen?
- end
-
- ruby_bug "#19427", ""..."3.3" do
- it "does freeze extended objects" do
- object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", freeze: true)
- object.should.frozen?
- end
-
- it "does freeze extended objects with instance variables" do
- object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x06:\n@ivarT", freeze: true)
- object.should.frozen?
- end
- end
-
- ruby_bug "#19427", "3.1"..."3.3" do
- it "returns frozen object having #_dump method" do
- object = Marshal.send(@method, Marshal.dump(UserDefined.new), freeze: true)
- object.should.frozen?
- end
-
- it "returns frozen object responding to #marshal_dump and #marshal_load" do
- object = Marshal.send(@method, Marshal.dump(UserMarshal.new), freeze: true)
- object.should.frozen?
- end
-
- it "returns frozen object extended by a module" do
- object = Object.new
- object.extend(MarshalSpec::ModuleToExtendBy)
-
- object = Marshal.send(@method, Marshal.dump(object), freeze: true)
- object.should.frozen?
- end
- end
-
- it "does not call freeze method" do
- object = MarshalSpec::ObjectWithFreezeRaisingException.new
- object = Marshal.send(@method, Marshal.dump(object), freeze: true)
- object.should.frozen?
- end
-
- it "returns frozen object even if object does not respond to freeze method" do
- object = MarshalSpec::ObjectWithoutFreeze.new
- object = Marshal.send(@method, Marshal.dump(object), freeze: true)
- object.should.frozen?
- end
-
- it "returns a frozen object when is an instance of String/Array/Regexp/Hash subclass and has instance variables" do
- source_object = UserString.new
- source_object.instance_variable_set(:@foo, "bar")
-
- object = Marshal.send(@method, Marshal.dump(source_object), freeze: true)
- object.should.frozen?
- end
-
- describe "when called with a proc" do
- it "call the proc with frozen objects" do
- arr = []
- s = +'hi'
- s.instance_variable_set(:@foo, 5)
- st = Struct.new("Brittle", :a).new
- st.instance_variable_set(:@clue, 'none')
- st.a = 0.0
- h = Hash.new('def')
- h['nine'] = 9
- a = [:a, :b, :c]
- a.instance_variable_set(:@two, 2)
- obj = [s, 10, s, s, st, a]
- obj.instance_variable_set(:@zoo, 'ant')
- proc = Proc.new { |o| arr << o; o}
-
- Marshal.send(
- @method,
- "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F",
- proc,
- freeze: true,
- )
-
- arr.should == [
- false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st,
- :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]],
- ]
-
- arr.each do |v|
- v.should.frozen?
- end
-
- Struct.send(:remove_const, :Brittle)
- end
-
- it "does not freeze the object returned by the proc" do
- string = Marshal.send(@method, Marshal.dump("foo"), proc { |o| o.upcase }, freeze: true)
- string.should == "FOO"
- string.should_not.frozen?
- end
- end
- end
- end
-
- describe "when called with a proc" do
- ruby_bug "#18141", ""..."3.1" do
- it "call the proc with fully initialized strings" do
- utf8_string = "foo".encode(Encoding::UTF_8)
- Marshal.send(@method, Marshal.dump(utf8_string), proc { |arg|
- if arg.is_a?(String)
- arg.should == utf8_string
- arg.encoding.should == Encoding::UTF_8
- end
- arg
- })
- end
-
- it "no longer mutate the object after it was passed to the proc" do
- string = Marshal.load(Marshal.dump("foo"), :freeze.to_proc)
- string.should.frozen?
- end
- end
-
- ruby_bug "#19427", ""..."3.3" do
- it "call the proc with extended objects" do
- objs = []
- obj = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", Proc.new { |o| objs << o; o })
- objs.should == [obj]
- end
- end
-
- it "returns the value of the proc" do
- Marshal.send(@method, Marshal.dump([1,2]), proc { [3,4] }).should == [3,4]
- end
-
- ruby_bug "#18141", ""..."3.1" do
- it "calls the proc for recursively visited data" do
- a = [1]
- a << a
- ret = []
- Marshal.send(@method, Marshal.dump(a), proc { |arg| ret << arg.inspect; arg })
- ret[0].should == 1.inspect
- ret[1].should == a.inspect
- ret.size.should == 2
- end
-
- it "loads an Array with proc" do
- arr = []
- s = +'hi'
- s.instance_variable_set(:@foo, 5)
- st = Struct.new("Brittle", :a).new
- st.instance_variable_set(:@clue, 'none')
- st.a = 0.0
- h = Hash.new('def')
- h['nine'] = 9
- a = [:a, :b, :c]
- a.instance_variable_set(:@two, 2)
- obj = [s, 10, s, s, st, a]
- obj.instance_variable_set(:@zoo, 'ant')
- proc = Proc.new { |o| arr << o.dup; o}
-
- Marshal.send(@method, "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", proc)
-
- arr.should == [
- false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st,
- :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]],
- ]
- Struct.send(:remove_const, :Brittle)
- end
- end
- end
-
- describe "when called with nil for the proc argument" do
- it "behaves as if no proc argument was passed" do
- a = [1]
- a << a
- b = Marshal.send(@method, Marshal.dump(a), nil)
- b.should == a
- end
- end
-
- describe "when called on objects with custom _dump methods" do
- it "does not set instance variables of an object with user-defined _dump/_load" do
- # this string represents: <#UserPreviouslyDefinedWithInitializedIvar @field2=7 @field1=6>
- dump_str = "\004\bu:-UserPreviouslyDefinedWithInitializedIvar\a:\f@field2i\f:\f@field1i\v"
-
- UserPreviouslyDefinedWithInitializedIvar.should_receive(:_load).and_return(UserPreviouslyDefinedWithInitializedIvar.new)
- marshaled_obj = Marshal.send(@method, dump_str)
-
- marshaled_obj.should be_an_instance_of(UserPreviouslyDefinedWithInitializedIvar)
- marshaled_obj.field1.should be_nil
- marshaled_obj.field2.should be_nil
- end
-
- it "loads the String in non US-ASCII and non UTF-8 encoding" do
- source_object = UserDefinedString.new("a".encode("windows-1251"))
- object = Marshal.send(@method, Marshal.dump(source_object))
- object.string.should == "a".encode("windows-1251")
- end
-
- it "loads the String in multibyte encoding" do
- source_object = UserDefinedString.new("a".encode("utf-32le"))
- object = Marshal.send(@method, Marshal.dump(source_object))
- object.string.should == "a".encode("utf-32le")
- end
-
- describe "that returns an immediate value" do
- it "loads an array containing an instance of the object, followed by multiple instances of another object" do
- str = "string"
-
- # this string represents: [<#UserDefinedImmediate A>, <#String "string">, <#String "string">]
- marshaled_obj = Marshal.send(@method, "\004\b[\bu:\031UserDefinedImmediate\000\"\vstring@\a")
-
- marshaled_obj.should == [nil, str, str]
- end
-
- it "loads any structure with multiple references to the same object, followed by multiple instances of another object" do
- str = "string"
-
- # this string represents: {a: <#UserDefinedImmediate A>, b: <#UserDefinedImmediate A>, c: <#String "string">, d: <#String "string">}
- hash_dump = "\x04\b{\t:\x06aIu:\x19UserDefinedImmediate\x00\x06:\x06ET:\x06b@\x06:\x06cI\"\vstring\x06;\aT:\x06d@\a"
-
- marshaled_obj = Marshal.send(@method, hash_dump)
- marshaled_obj.should == {a: nil, b: nil, c: str, d: str}
-
- # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate A>, <#String "string">, <#String "string">]
- array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ET@\x06I\"\vstring\x06;\x06T@\a"
-
- marshaled_obj = Marshal.send(@method, array_dump)
- marshaled_obj.should == [nil, nil, str, str]
- end
-
- it "loads an array containing references to multiple instances of the object, followed by multiple instances of another object" do
- str = "string"
-
- # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate B>, <#String "string">, <#String "string">]
- array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ETIu;\x00\x00\x06;\x06TI\"\vstring\x06;\x06T@\b"
-
- marshaled_obj = Marshal.send(@method, array_dump)
- marshaled_obj.should == [nil, nil, str, str]
- end
- end
- end
-
- ruby_bug "#18141", ""..."3.1" do
- it "loads an array containing objects having _dump method, and with proc" do
- arr = []
- myproc = Proc.new { |o| arr << o.dup; o }
- o1 = UserDefined.new;
- o2 = UserDefinedWithIvar.new
- obj = [o1, o2, o1, o2]
-
- Marshal.send(@method, "\x04\b[\tu:\x10UserDefined\x18\x04\b[\aI\"\nstuff\x06:\x06EF@\x06u:\x18UserDefinedWithIvar>\x04\b[\bI\"\nstuff\a:\x06EF:\t@foo:\x18UserDefinedWithIvarI\"\tmore\x06;\x00F@\a@\x06@\a", myproc)
-
- arr[0].should == o1
- arr[1].should == o2
- arr[2].should == obj
- arr.size.should == 3
- end
-
- it "loads an array containing objects having marshal_dump method, and with proc" do
- arr = []
- proc = Proc.new { |o| arr << o.dup; o }
- o1 = UserMarshal.new
- o2 = UserMarshalWithIvar.new
-
- Marshal.send(@method, "\004\b[\tU:\020UserMarshal\"\nstuffU:\030UserMarshalWithIvar[\006\"\fmy data@\006@\b", proc)
-
- arr[0].should == 'stuff'
- arr[1].should == o1
- arr[2].should == 'my data'
- arr[3].should == ['my data']
- arr[4].should == o2
- arr[5].should == [o1, o2, o1, o2]
-
- arr.size.should == 6
- end
- end
-
- it "assigns classes to nested subclasses of Array correctly" do
- arr = ArraySub.new(ArraySub.new)
- arr_dump = Marshal.dump(arr)
- Marshal.send(@method, arr_dump).class.should == ArraySub
- end
-
- it "loads subclasses of Array with overridden << and push correctly" do
- arr = ArraySubPush.new
- arr[0] = '1'
- arr_dump = Marshal.dump(arr)
- Marshal.send(@method, arr_dump).should == arr
- end
-
- it "raises a TypeError with bad Marshal version" do
- marshal_data = +'\xff\xff'
- marshal_data[0] = (Marshal::MAJOR_VERSION).chr
- marshal_data[1] = (Marshal::MINOR_VERSION + 1).chr
-
- -> { Marshal.send(@method, marshal_data) }.should raise_error(TypeError)
-
- marshal_data = +'\xff\xff'
- marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr
- marshal_data[1] = (Marshal::MINOR_VERSION).chr
-
- -> { Marshal.send(@method, marshal_data) }.should raise_error(TypeError)
- end
-
- it "raises EOFError on loading an empty file" do
- temp_file = tmp("marshal.rubyspec.tmp.#{Process.pid}")
- file = File.new(temp_file, "w+")
- begin
- -> { Marshal.send(@method, file) }.should raise_error(EOFError)
- ensure
- file.close
- rm_r temp_file
- end
- end
-
- # Note: Ruby 1.9 should be compatible with older marshal format
- MarshalSpec::DATA.each do |description, (object, marshal, attributes)|
- it "loads a #{description}" do
- Marshal.send(@method, marshal).should == object
- end
- end
-
- MarshalSpec::DATA_19.each do |description, (object, marshal, attributes)|
- it "loads a #{description}" do
- Marshal.send(@method, marshal).should == object
- end
- end
-
- describe "for an Array" do
- it "loads an array containing the same objects" do
- s = 'oh'
- b = 'hi'
- r = //
- d = [b, :no, s, :go]
- c = String
- f = 1.0
-
- o1 = UserMarshalWithIvar.new; o2 = UserMarshal.new
-
- obj = [:so, 'hello', 100, :so, :so, d, :so, o2, :so, :no, o2,
- :go, c, nil, Struct::Pyramid.new, f, :go, :no, s, b, r,
- :so, 'huh', o1, true, b, b, 99, r, b, s, :so, f, c, :no, o1, d]
-
- Marshal.send(@method, "\004\b[*:\aso\"\nhelloii;\000;\000[\t\"\ahi:\ano\"\aoh:\ago;\000U:\020UserMarshal\"\nstuff;\000;\006@\n;\ac\vString0S:\024Struct::Pyramid\000f\0061;\a;\006@\t@\b/\000\000;\000\"\bhuhU:\030UserMarshalWithIvar[\006\"\fmy dataT@\b@\bih@\017@\b@\t;\000@\016@\f;\006@\021@\a").should ==
- obj
- end
-
- it "loads an array having ivar" do
- s = +'well'
- s.instance_variable_set(:@foo, 10)
- obj = ['5', s, 'hi'].extend(Meths, MethsMore)
- obj.instance_variable_set(:@mix, s)
- new_obj = Marshal.send(@method, "\004\bI[\b\"\0065I\"\twell\006:\t@fooi\017\"\ahi\006:\t@mix@\a")
- new_obj.should == obj
- new_obj.instance_variable_get(:@mix).should equal new_obj[1]
- new_obj[1].instance_variable_get(:@foo).should == 10
- end
-
- it "loads an extended Array object containing a user-marshaled object" do
- obj = [UserMarshal.new, UserMarshal.new].extend(Meths)
- dump = "\x04\be:\nMeths[\ao:\x10UserMarshal\x06:\n@dataI\"\nstuff\x06:\x06ETo;\x06\x06;\aI\"\nstuff\x06;\bT"
- new_obj = Marshal.send(@method, dump)
-
- new_obj.should == obj
- obj_ancestors = class << obj; ancestors[1..-1]; end
- new_obj_ancestors = class << new_obj; ancestors[1..-1]; end
- obj_ancestors.should == new_obj_ancestors
- end
- end
-
- describe "for a Hash" do
- it "loads an extended_user_hash with a parameter to initialize" do
- obj = UserHashInitParams.new(:abc).extend(Meths)
-
- new_obj = Marshal.send(@method, "\004\bIe:\nMethsC:\027UserHashInitParams{\000\006:\a@a:\babc")
-
- new_obj.should == obj
- new_obj_metaclass_ancestors = class << new_obj; ancestors; end
- new_obj_metaclass_ancestors[@num_self_class].should == Meths
- new_obj_metaclass_ancestors[@num_self_class+1].should == UserHashInitParams
- end
-
- it "loads an extended hash object containing a user-marshaled object" do
- obj = {a: UserMarshal.new}.extend(Meths)
-
- new_obj = Marshal.send(@method, "\004\be:\nMeths{\006:\006aU:\020UserMarshal\"\nstuff")
-
- new_obj.should == obj
- new_obj_metaclass_ancestors = class << new_obj; ancestors; end
- new_obj_metaclass_ancestors[@num_self_class].should == Meths
- new_obj_metaclass_ancestors[@num_self_class+1].should == Hash
- end
-
- it "preserves hash ivars when hash contains a string having ivar" do
- s = +'string'
- s.instance_variable_set :@string_ivar, 'string ivar'
- h = { key: s }
- h.instance_variable_set :@hash_ivar, 'hash ivar'
-
- unmarshalled = Marshal.send(@method, Marshal.dump(h))
- unmarshalled.instance_variable_get(:@hash_ivar).should == 'hash ivar'
- unmarshalled[:key].instance_variable_get(:@string_ivar).should == 'string ivar'
- end
-
- ruby_version_is "3.1" do
- it "preserves compare_by_identity behaviour" do
- h = { a: 1 }
- h.compare_by_identity
- unmarshalled = Marshal.send(@method, Marshal.dump(h))
- unmarshalled.should.compare_by_identity?
-
- h = { a: 1 }
- unmarshalled = Marshal.send(@method, Marshal.dump(h))
- unmarshalled.should_not.compare_by_identity?
- end
-
- it "preserves compare_by_identity behaviour for a Hash subclass" do
- h = UserHash.new({ a: 1 })
- h.compare_by_identity
- unmarshalled = Marshal.send(@method, Marshal.dump(h))
- unmarshalled.should.compare_by_identity?
-
- h = UserHash.new({ a: 1 })
- unmarshalled = Marshal.send(@method, Marshal.dump(h))
- unmarshalled.should_not.compare_by_identity?
- end
- end
-
- it "allocates an instance of the proper class when Hash subclass with compare_by_identity behaviour" do
- h = UserHash.new({ a: 1 })
- h.compare_by_identity
-
- unmarshalled = Marshal.send(@method, Marshal.dump(h))
- unmarshalled.should.kind_of?(UserHash)
- end
- end
-
- describe "for a Symbol" do
- it "loads a Symbol" do
- sym = Marshal.send(@method, "\004\b:\vsymbol")
- sym.should == :symbol
- sym.encoding.should == Encoding::US_ASCII
- end
-
- it "loads a big Symbol" do
- sym = ('big' * 100).to_sym
- Marshal.send(@method, "\004\b:\002,\001#{'big' * 100}").should == sym
- end
-
- it "loads an encoded Symbol" do
- s = "\u2192"
-
- sym = Marshal.send(@method, "\x04\bI:\b\xE2\x86\x92\x06:\x06ET")
- sym.should == s.encode("utf-8").to_sym
- sym.encoding.should == Encoding::UTF_8
-
- sym = Marshal.send(@method, "\x04\bI:\t\xFE\xFF!\x92\x06:\rencoding\"\vUTF-16")
- sym.should == s.encode("utf-16").to_sym
- sym.encoding.should == Encoding::UTF_16
-
- sym = Marshal.send(@method, "\x04\bI:\a\x92!\x06:\rencoding\"\rUTF-16LE")
- sym.should == s.encode("utf-16le").to_sym
- sym.encoding.should == Encoding::UTF_16LE
-
- sym = Marshal.send(@method, "\x04\bI:\a!\x92\x06:\rencoding\"\rUTF-16BE")
- sym.should == s.encode("utf-16be").to_sym
- sym.encoding.should == Encoding::UTF_16BE
-
- sym = Marshal.send(@method, "\x04\bI:\a\xA2\xAA\x06:\rencoding\"\vEUC-JP")
- sym.should == s.encode("euc-jp").to_sym
- sym.encoding.should == Encoding::EUC_JP
-
- sym = Marshal.send(@method, "\x04\bI:\a\x81\xA8\x06:\rencoding\"\x10Windows-31J")
- sym.should == s.encode("sjis").to_sym
- sym.encoding.should == Encoding::SJIS
- end
-
- it "loads a binary encoded Symbol" do
- s = "\u2192".dup.force_encoding("binary").to_sym
- sym = Marshal.send(@method, "\x04\b:\b\xE2\x86\x92")
- sym.should == s
- sym.encoding.should == Encoding::BINARY
- end
-
- it "loads multiple Symbols sharing the same encoding" do
- # Note that the encoding is a link for the second Symbol
- symbol1 = "I:\t\xE2\x82\xACa\x06:\x06ET"
- symbol2 = "I:\t\xE2\x82\xACb\x06;\x06T"
- dump = "\x04\b[\a#{symbol1}#{symbol2}"
- value = Marshal.send(@method, dump)
- value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8]
- expected = [
- "€a".dup.force_encoding(Encoding::UTF_8).to_sym,
- "€b".dup.force_encoding(Encoding::UTF_8).to_sym
- ]
- value.should == expected
-
- value = Marshal.send(@method, "\x04\b[\b#{symbol1}#{symbol2};\x00")
- value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8, Encoding::UTF_8]
- value.should == [*expected, expected[0]]
- end
-
- it "raises ArgumentError when end of byte sequence reached before symbol characters end" do
- Marshal.dump(:hello).should == "\x04\b:\nhello"
-
- -> {
- Marshal.send(@method, "\x04\b:\nhel")
- }.should raise_error(ArgumentError, "marshal data too short")
- end
- end
-
- describe "for a String" do
- it "loads a string having ivar with ref to self" do
- obj = +'hi'
- obj.instance_variable_set(:@self, obj)
- Marshal.send(@method, "\004\bI\"\ahi\006:\n@self@\000").should == obj
- end
-
- it "loads a string through StringIO stream" do
- require 'stringio'
- obj = "This is a string which should be unmarshalled through StringIO stream!"
- Marshal.send(@method, StringIO.new(Marshal.dump(obj))).should == obj
- end
-
- it "sets binmode if it is loading through StringIO stream" do
- io = StringIO.new("\004\b:\vsymbol")
- def io.binmode; raise "binmode"; end
- -> { Marshal.load(io) }.should raise_error(RuntimeError, "binmode")
- end
-
- it "loads a string with an ivar" do
- str = Marshal.send(@method, "\x04\bI\"\x00\x06:\t@fooI\"\bbar\x06:\x06EF")
- str.instance_variable_get("@foo").should == "bar"
- end
-
- it "loads a String subclass with custom constructor" do
- str = Marshal.send(@method, "\x04\bC: UserCustomConstructorString\"\x00")
- str.should be_an_instance_of(UserCustomConstructorString)
- end
-
- it "loads a US-ASCII String" do
- str = "abc".dup.force_encoding("us-ascii")
- data = "\x04\bI\"\babc\x06:\x06EF"
- result = Marshal.send(@method, data)
- result.should == str
- result.encoding.should equal(Encoding::US_ASCII)
- end
-
- it "loads a UTF-8 String" do
- str = "\x6d\xc3\xb6\x68\x72\x65".dup.force_encoding("utf-8")
- data = "\x04\bI\"\vm\xC3\xB6hre\x06:\x06ET"
- result = Marshal.send(@method, data)
- result.should == str
- result.encoding.should equal(Encoding::UTF_8)
- end
-
- it "loads a String in another encoding" do
- str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".dup.force_encoding("utf-16le")
- data = "\x04\bI\"\x0Fm\x00\xF6\x00h\x00r\x00e\x00\x06:\rencoding\"\rUTF-16LE"
- result = Marshal.send(@method, data)
- result.should == str
- result.encoding.should equal(Encoding::UTF_16LE)
- end
-
- it "loads a String as BINARY if no encoding is specified at the end" do
- str = "\xC3\xB8".dup.force_encoding("BINARY")
- data = "\x04\b\"\a\xC3\xB8".dup.force_encoding("UTF-8")
- result = Marshal.send(@method, data)
- result.encoding.should == Encoding::BINARY
- result.should == str
- end
-
- it "raises ArgumentError when end of byte sequence reached before string characters end" do
- Marshal.dump("hello").should == "\x04\b\"\nhello"
-
- -> {
- Marshal.send(@method, "\x04\b\"\nhel")
- }.should raise_error(ArgumentError, "marshal data too short")
- end
- end
-
- describe "for a Struct" do
- it "loads a extended_struct having fields with same objects" do
- s = 'hi'
- obj = Struct.new("Extended", :a, :b).new.extend(Meths)
- dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a0:\006b0"
- Marshal.send(@method, dump).should == obj
-
- obj.a = [:a, s]
- obj.b = [:Meths, s]
- dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a"
- Marshal.send(@method, dump).should == obj
- Struct.send(:remove_const, :Extended)
- end
-
- it "loads a struct having ivar" do
- obj = Struct.new("Thick").new
- obj.instance_variable_set(:@foo, 5)
- reloaded = Marshal.send(@method, "\004\bIS:\022Struct::Thick\000\006:\t@fooi\n")
- reloaded.should == obj
- reloaded.instance_variable_get(:@foo).should == 5
- Struct.send(:remove_const, :Thick)
- end
-
- it "loads a struct having fields" do
- obj = Struct.new("Ure1", :a, :b).new
- Marshal.send(@method, "\004\bS:\021Struct::Ure1\a:\006a0:\006b0").should == obj
- Struct.send(:remove_const, :Ure1)
- end
-
- it "does not call initialize on the unmarshaled struct" do
- threadlocal_key = MarshalSpec::StructWithUserInitialize::THREADLOCAL_KEY
-
- s = MarshalSpec::StructWithUserInitialize.new('foo')
- Thread.current[threadlocal_key].should == ['foo']
- s.a.should == 'foo'
-
- Thread.current[threadlocal_key] = nil
-
- dumped = Marshal.dump(s)
- loaded = Marshal.send(@method, dumped)
-
- Thread.current[threadlocal_key].should == nil
- loaded.a.should == 'foo'
- end
- end
-
- describe "for an Exception" do
- it "loads a marshalled exception with no message" do
- obj = Exception.new
- loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt0:\tmesg0")
- loaded.message.should == obj.message
- loaded.backtrace.should == obj.backtrace
- loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesg0:\abt0")
- loaded.message.should == obj.message
- loaded.backtrace.should == obj.backtrace
- end
-
- it "loads a marshalled exception with a message" do
- obj = Exception.new("foo")
- loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt0:\tmesg\"\bfoo")
- loaded.message.should == obj.message
- loaded.backtrace.should == obj.backtrace
- loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt0")
- loaded.message.should == obj.message
- loaded.backtrace.should == obj.backtrace
- end
-
- it "loads a marshalled exception with a backtrace" do
- obj = Exception.new("foo")
- obj.set_backtrace(["foo/bar.rb:10"])
- loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt[\006\"\022foo/bar.rb:10:\tmesg\"\bfoo")
- loaded.message.should == obj.message
- loaded.backtrace.should == obj.backtrace
- loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt[\x06I\"\x12foo/bar.rb:10\x06;\aF")
- loaded.message.should == obj.message
- loaded.backtrace.should == obj.backtrace
- end
-
- it "loads an marshalled exception with ivars" do
- s = 'hi'
- arr = [:so, :so, s, s]
- obj = Exception.new("foo")
- obj.instance_variable_set :@arr, arr
-
- loaded = Marshal.send(@method, "\x04\bo:\x0EException\b:\tmesg\"\bfoo:\abt0:\t@arr[\t:\aso;\t\"\ahi@\b")
- new_arr = loaded.instance_variable_get :@arr
-
- loaded.message.should == obj.message
- new_arr.should == arr
- end
- end
-
- describe "for an Object" do
- it "loads an object" do
- Marshal.send(@method, "\004\bo:\vObject\000").should be_kind_of(Object)
- end
-
- it "loads an extended Object" do
- obj = Object.new.extend(Meths)
-
- new_obj = Marshal.send(@method, "\004\be:\nMethso:\vObject\000")
-
- new_obj.class.should == obj.class
- new_obj_metaclass_ancestors = class << new_obj; ancestors; end
- new_obj_metaclass_ancestors[@num_self_class, 2].should == [Meths, Object]
- end
-
- it "loads an object having ivar" do
- s = 'hi'
- arr = [:so, :so, s, s]
- obj = Object.new
- obj.instance_variable_set :@str, arr
-
- new_obj = Marshal.send(@method, "\004\bo:\vObject\006:\t@str[\t:\aso;\a\"\ahi@\a")
- new_str = new_obj.instance_variable_get :@str
-
- new_str.should == arr
- end
-
- it "loads an Object with a non-US-ASCII instance variable" do
- ivar = "@é".dup.force_encoding(Encoding::UTF_8).to_sym
- obj = Marshal.send(@method, "\x04\bo:\vObject\x06I:\b@\xC3\xA9\x06:\x06ETi\x06")
- obj.instance_variables.should == [ivar]
- obj.instance_variables[0].encoding.should == Encoding::UTF_8
- obj.instance_variable_get(ivar).should == 1
- end
-
- it "raises ArgumentError if the object from an 'o' stream is not dumpable as 'o' type user class" do
- -> do
- Marshal.send(@method, "\x04\bo:\tFile\001\001:\001\005@path\"\x10/etc/passwd")
- end.should raise_error(ArgumentError)
- end
-
- it "raises ArgumentError when end of byte sequence reached before class name end" do
- Marshal.dump(Object.new).should == "\x04\bo:\vObject\x00"
-
- -> {
- Marshal.send(@method, "\x04\bo:\vObj")
- }.should raise_error(ArgumentError, "marshal data too short")
- end
- end
-
- describe "for an object responding to #marshal_dump and #marshal_load" do
- it "loads a user-marshaled object" do
- obj = UserMarshal.new
- obj.data = :data
- value = [obj, :data]
- dump = Marshal.dump(value)
- dump.should == "\x04\b[\aU:\x10UserMarshal:\tdata;\x06"
- reloaded = Marshal.load(dump)
- reloaded.should == value
- end
- end
-
- describe "for a user object" do
- it "loads a user-marshaled extended object" do
- obj = UserMarshal.new.extend(Meths)
-
- new_obj = Marshal.send(@method, "\004\bU:\020UserMarshal\"\nstuff")
-
- new_obj.should == obj
- new_obj_metaclass_ancestors = class << new_obj; ancestors; end
- new_obj_metaclass_ancestors[@num_self_class].should == UserMarshal
- end
-
- it "loads a UserObject" do
- Marshal.send(@method, "\004\bo:\017UserObject\000").should be_kind_of(UserObject)
- end
-
- describe "that extends a core type other than Object or BasicObject" do
- after :each do
- MarshalSpec.reset_swapped_class
- end
-
- it "raises ArgumentError if the resulting class does not extend the same type" do
- MarshalSpec.set_swapped_class(Class.new(Hash))
- data = Marshal.dump(MarshalSpec::SwappedClass.new)
-
- MarshalSpec.set_swapped_class(Class.new(Array))
- -> { Marshal.send(@method, data) }.should raise_error(ArgumentError)
-
- MarshalSpec.set_swapped_class(Class.new)
- -> { Marshal.send(@method, data) }.should raise_error(ArgumentError)
- end
- end
- end
-
- describe "for a Regexp" do
- it "loads an extended Regexp" do
- obj = /[a-z]/.dup.extend(Meths, MethsMore)
- new_obj = Marshal.send(@method, "\004\be:\nMethse:\016MethsMore/\n[a-z]\000")
-
- new_obj.should == obj
- new_obj_metaclass_ancestors = class << new_obj; ancestors; end
- new_obj_metaclass_ancestors[@num_self_class, 3].should ==
- [Meths, MethsMore, Regexp]
- end
-
- it "loads a Regexp subclass instance variables when it is extended with a module" do
- obj = UserRegexp.new('').extend(Meths)
- obj.instance_variable_set(:@noise, 'much')
-
- new_obj = Marshal.send(@method, "\004\bIe:\nMethsC:\017UserRegexp/\000\000\006:\v@noise\"\tmuch")
-
- new_obj.should == obj
- new_obj.instance_variable_get(:@noise).should == 'much'
- new_obj_metaclass_ancestors = class << new_obj; ancestors; end
- new_obj_metaclass_ancestors[@num_self_class, 3].should ==
- [Meths, UserRegexp, Regexp]
- end
-
- ruby_bug "#19439", ""..."3.3" do
- it "restore the regexp instance variables" do
- obj = Regexp.new("hello")
- obj.instance_variable_set(:@regexp_ivar, [42])
-
- new_obj = Marshal.send(@method, "\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/")
- new_obj.instance_variables.should == [:@regexp_ivar]
- new_obj.instance_variable_get(:@regexp_ivar).should == [42]
- end
- end
-
- it "preserves Regexp encoding" do
- source_object = Regexp.new("a".encode("utf-32le"))
- regexp = Marshal.send(@method, Marshal.dump(source_object))
-
- regexp.encoding.should == Encoding::UTF_32LE
- regexp.source.should == "a".encode("utf-32le")
- end
-
- it "raises ArgumentError when end of byte sequence reached before source string end" do
- Marshal.dump(/hello world/).should == "\x04\bI/\x10hello world\x00\x06:\x06EF"
-
- -> {
- Marshal.send(@method, "\x04\bI/\x10hel")
- }.should raise_error(ArgumentError, "marshal data too short")
- end
- end
-
- describe "for a Float" do
- it "loads a Float NaN" do
- obj = 0.0 / 0.0
- Marshal.send(@method, "\004\bf\bnan").to_s.should == obj.to_s
- end
-
- it "loads a Float 1.3" do
- Marshal.send(@method, "\004\bf\v1.3\000\314\315").should == 1.3
- end
-
- it "loads a Float -5.1867345e-22" do
- obj = -5.1867345e-22
- Marshal.send(@method, "\004\bf\037-5.1867345000000008e-22\000\203_").should be_close(obj, 1e-30)
- end
-
- it "loads a Float 1.1867345e+22" do
- obj = 1.1867345e+22
- Marshal.send(@method, "\004\bf\0361.1867344999999999e+22\000\344@").should == obj
- end
-
- it "raises ArgumentError when end of byte sequence reached before float string representation end" do
- Marshal.dump(1.3).should == "\x04\bf\b1.3"
-
- -> {
- Marshal.send(@method, "\004\bf\v1")
- }.should raise_error(ArgumentError, "marshal data too short")
- end
- end
-
- describe "for an Integer" do
- it "loads 0" do
- Marshal.send(@method, "\004\bi\000").should == 0
- Marshal.send(@method, "\004\bi\005").should == 0
- end
-
- it "loads an Integer 8" do
- Marshal.send(@method, "\004\bi\r" ).should == 8
- end
-
- it "loads and Integer -8" do
- Marshal.send(@method, "\004\bi\363" ).should == -8
- end
-
- it "loads an Integer 1234" do
- Marshal.send(@method, "\004\bi\002\322\004").should == 1234
- end
-
- it "loads an Integer -1234" do
- Marshal.send(@method, "\004\bi\376.\373").should == -1234
- end
-
- it "loads an Integer 4611686018427387903" do
- Marshal.send(@method, "\004\bl+\t\377\377\377\377\377\377\377?").should == 4611686018427387903
- end
-
- it "loads an Integer -4611686018427387903" do
- Marshal.send(@method, "\004\bl-\t\377\377\377\377\377\377\377?").should == -4611686018427387903
- end
-
- it "loads an Integer 2361183241434822606847" do
- Marshal.send(@method, "\004\bl+\n\377\377\377\377\377\377\377\377\177\000").should == 2361183241434822606847
- end
-
- it "loads an Integer -2361183241434822606847" do
- Marshal.send(@method, "\004\bl-\n\377\377\377\377\377\377\377\377\177\000").should == -2361183241434822606847
- end
-
- it "raises ArgumentError if the input is too short" do
- ["\004\bi",
- "\004\bi\001",
- "\004\bi\002",
- "\004\bi\002\0",
- "\004\bi\003",
- "\004\bi\003\0",
- "\004\bi\003\0\0",
- "\004\bi\004",
- "\004\bi\004\0",
- "\004\bi\004\0\0",
- "\004\bi\004\0\0\0"].each do |invalid|
- -> { Marshal.send(@method, invalid) }.should raise_error(ArgumentError)
- end
- end
-
- if 0.size == 8 # for platforms like x86_64
- it "roundtrips 4611686018427387903 from dump/load correctly" do
- Marshal.send(@method, Marshal.dump(4611686018427387903)).should == 4611686018427387903
- end
- end
- end
-
- describe "for a Rational" do
- it "loads" do
- r = Marshal.send(@method, Marshal.dump(Rational(1, 3)))
- r.should == Rational(1, 3)
- r.should.frozen?
- end
- end
-
- describe "for a Complex" do
- it "loads" do
- c = Marshal.send(@method, Marshal.dump(Complex(4, 3)))
- c.should == Complex(4, 3)
- c.should.frozen?
- end
- end
-
- describe "for a Bignum" do
- platform_is wordsize: 64 do
- context "that is Bignum on 32-bit platforms but Fixnum on 64-bit" do
- it "dumps a Fixnum" do
- val = Marshal.send(@method, "\004\bl+\ab:wU")
- val.should == 1433877090
- val.class.should == Integer
- end
-
- it "dumps an array containing multiple references to the Bignum as an array of Fixnum" do
- arr = Marshal.send(@method, "\004\b[\al+\a\223BwU@\006")
- arr.should == [1433879187, 1433879187]
- arr.each { |v| v.class.should == Integer }
- end
- end
- end
- end
-
- describe "for a Time" do
- it "loads" do
- Marshal.send(@method, Marshal.dump(Time.at(1))).should == Time.at(1)
- end
-
- it "loads serialized instance variables" do
- t = Time.new
- t.instance_variable_set(:@foo, 'bar')
-
- Marshal.send(@method, Marshal.dump(t)).instance_variable_get(:@foo).should == 'bar'
- end
-
- it "loads Time objects stored as links" do
- t = Time.new
-
- t1, t2 = Marshal.send(@method, Marshal.dump([t, t]))
- t1.should equal t2
- end
-
- it "keeps the local zone" do
- with_timezone 'AST', 3 do
- t = Time.local(2012, 1, 1)
- Marshal.send(@method, Marshal.dump(t)).zone.should == t.zone
- end
- end
-
- it "keeps UTC zone" do
- t = Time.now.utc
- t2 = Marshal.send(@method, Marshal.dump(t))
- t2.should.utc?
- end
-
- it "keeps the zone" do
- t = nil
-
- with_timezone 'AST', 4 do
- t = Time.local(2012, 1, 1)
- end
-
- with_timezone 'EET', -2 do
- Marshal.send(@method, Marshal.dump(t)).zone.should == 'AST'
- end
- end
-
- it "keeps utc offset" do
- t = Time.new(2007,11,1,15,25,0, "+09:00")
- t2 = Marshal.send(@method, Marshal.dump(t))
- t2.utc_offset.should == 32400
- end
-
- it "keeps nanoseconds" do
- t = Time.now
- Marshal.send(@method, Marshal.dump(t)).nsec.should == t.nsec
- end
-
- it "does not add any additional instance variable" do
- t = Time.now
- t2 = Marshal.send(@method, Marshal.dump(t))
- t2.instance_variables.should.empty?
- end
- end
-
- describe "for nil" do
- it "loads" do
- Marshal.send(@method, "\x04\b0").should be_nil
- end
- end
-
- describe "for true" do
- it "loads" do
- Marshal.send(@method, "\x04\bT").should be_true
- end
- end
-
- describe "for false" do
- it "loads" do
- Marshal.send(@method, "\x04\bF").should be_false
- end
- end
-
- describe "for a Class" do
- it "loads" do
- Marshal.send(@method, "\x04\bc\vString").should == String
- end
-
- it "raises ArgumentError if given the name of a non-Module" do
- -> { Marshal.send(@method, "\x04\bc\vKernel") }.should raise_error(ArgumentError)
- end
-
- it "raises ArgumentError if given a nonexistent class" do
- -> { Marshal.send(@method, "\x04\bc\vStrung") }.should raise_error(ArgumentError)
- end
-
- it "raises ArgumentError when end of byte sequence reached before class name end" do
- Marshal.dump(String).should == "\x04\bc\vString"
-
- -> {
- Marshal.send(@method, "\x04\bc\vStr")
- }.should raise_error(ArgumentError, "marshal data too short")
- end
- end
-
- describe "for a Module" do
- it "loads a module" do
- Marshal.send(@method, "\x04\bm\vKernel").should == Kernel
- end
-
- it "raises ArgumentError if given the name of a non-Class" do
- -> { Marshal.send(@method, "\x04\bm\vString") }.should raise_error(ArgumentError)
- end
-
- it "loads an old module" do
- Marshal.send(@method, "\x04\bM\vKernel").should == Kernel
- end
-
- it "raises ArgumentError when end of byte sequence reached before module name end" do
- Marshal.dump(Kernel).should == "\x04\bm\vKernel"
-
- -> {
- Marshal.send(@method, "\x04\bm\vKer")
- }.should raise_error(ArgumentError, "marshal data too short")
- end
- end
-
- describe "for a wrapped C pointer" do
- it "loads" do
- class DumpableDir < Dir
- def _dump_data
- path
- end
- def _load_data path
- initialize(path)
- end
- end
-
- data = "\x04\bd:\x10DumpableDirI\"\x06.\x06:\x06ET"
-
- dir = Marshal.send(@method, data)
- begin
- dir.path.should == '.'
- ensure
- dir.close
- end
- end
-
- it "raises TypeError when the local class is missing _load_data" do
- class UnloadableDumpableDir < Dir
- def _dump_data
- path
- end
- # no _load_data
- end
-
- data = "\x04\bd:\x1AUnloadableDumpableDirI\"\x06.\x06:\x06ET"
-
- -> { Marshal.send(@method, data) }.should raise_error(TypeError)
- end
-
- it "raises ArgumentError when the local class is a regular object" do
- data = "\004\bd:\020UserDefined\0"
-
- -> { Marshal.send(@method, data) }.should raise_error(ArgumentError)
- end
- end
-
- describe "when a class does not exist in the namespace" do
- before :each do
- NamespaceTest.send(:const_set, :SameName, Class.new)
- @data = Marshal.dump(NamespaceTest::SameName.new)
- NamespaceTest.send(:remove_const, :SameName)
- end
-
- it "raises an ArgumentError" do
- message = "undefined class/module NamespaceTest::SameName"
- -> { Marshal.send(@method, @data) }.should raise_error(ArgumentError, message)
- end
- end
-
- it "raises an ArgumentError with full constant name when the dumped constant is missing" do
- NamespaceTest.send(:const_set, :KaBoom, Class.new)
- @data = Marshal.dump(NamespaceTest::KaBoom.new)
- NamespaceTest.send(:remove_const, :KaBoom)
-
- -> { Marshal.send(@method, @data) }.should raise_error(ArgumentError, /NamespaceTest::KaBoom/)
- end
-end