# 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

  describe "when called with freeze: true" do
    it "returns frozen strings" do
      NATFIXME 'returns frozen strings', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' 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
    end

    it "returns frozen arrays" do
      NATFIXME 'returns frozen arrays', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
        array = Marshal.send(@method, Marshal.dump([1, 2, 3]), freeze: true)
        array.should == [1, 2, 3]
        array.should.frozen?
      end
    end

    it "returns frozen hashes" do
      NATFIXME 'returns frozen hashes', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
        hash = Marshal.send(@method, Marshal.dump({foo: 42}), freeze: true)
        hash.should == {foo: 42}
        hash.should.frozen?
      end
    end

    it "returns frozen regexps" do
      NATFIXME 'returns frozen regexps', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
        regexp = Marshal.send(@method, Marshal.dump(/foo/), freeze: true)
        regexp.should == /foo/
        regexp.should.frozen?
      end
    end

    it "returns frozen structs" do
      NATFIXME 'returns frozen structs', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' 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
    end

    it "returns frozen objects" do
      source_object = Object.new

      NATFIXME 'returns frozen objects', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
        object = Marshal.send(@method, Marshal.dump(source_object), freeze: true)
        object.should.frozen?
      end
    end

    describe "deep freezing" do
      it "returns hashes with frozen keys and values" do
        key = Object.new
        value = Object.new
        source_object = {key => value}

        NATFIXME 'returns hashes with frozen keys and values', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
          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
      end

      it "returns arrays with frozen elements" do
        object = Object.new
        source_object = [object]

        NATFIXME 'returns arrays with frozen elements', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
          array = Marshal.send(@method, Marshal.dump(source_object), freeze: true)
          array.size.should == 1
          array[0].should.frozen?
        end
      end

      it "returns structs with frozen members" do
        object1 = Object.new
        object2 = Object.new
        source_object = MarshalSpec::StructToDump.new(object1, object2)

        NATFIXME 'returns structs with frozen members', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
          struct = Marshal.send(@method, Marshal.dump(source_object), freeze: true)
          struct.a.should.frozen?
          struct.b.should.frozen?
        end
      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)

        NATFIXME 'returns objects with frozen instance variables', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
          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
      end

      it "deduplicates frozen strings" do
        source_object = ["foo" + "bar", "foobar"]
        NATFIXME 'deduplicates frozen strings', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
          object = Marshal.send(@method, Marshal.dump(source_object), freeze: true)

          object[0].should equal(object[1])
        end
      end
    end

    it "does not freeze modules" do
      NATFIXME 'does not freeze modules', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
        object = Marshal.send(@method, Marshal.dump(Kernel), freeze: true)
        object.should_not.frozen?
        Kernel.should_not.frozen?
      end
    end

    it "does not freeze classes" do
      NATFIXME 'does not freeze classes', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
        object = Marshal.send(@method, Marshal.dump(Object), freeze: true)
        object.should_not.frozen?
        Object.should_not.frozen?
      end
    end

    ruby_bug "#19427", ""..."3.3" do
      it "does freeze extended objects" do
        NATFIXME 'does freeze extended objects', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
          object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", freeze: true)
          object.should.frozen?
        end
      end

      it "does freeze extended objects with instance variables" do
        NATFIXME 'does freeze extended objects with instance variables', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
          object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x06:\n@ivarT", freeze: true)
          object.should.frozen?
        end
      end
    end

    ruby_bug "#19427", ""..."3.3" do
      it "returns frozen object having #_dump method" do
        NATFIXME 'returns frozen object having #_dump method', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
          object = Marshal.send(@method, Marshal.dump(UserDefined.new), freeze: true)
          object.should.frozen?
        end
      end

      it "returns frozen object responding to #marshal_dump and #marshal_load" do
        NATFIXME 'returns frozen object responding to #marshal_dump and #marshal_load', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
          object = Marshal.send(@method, Marshal.dump(UserMarshal.new), freeze: true)
          object.should.frozen?
        end
      end

      it "returns frozen object extended by a module" do
        object = Object.new
        object.extend(MarshalSpec::ModuleToExtendBy)

        NATFIXME 'returns frozen object extended by a module', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1' do
          object = Marshal.send(@method, Marshal.dump(object), freeze: true)
          object.should.frozen?
        end
      end
    end

    it "does not call freeze method" do
      object = MarshalSpec::ObjectWithFreezeRaisingException.new
      NATFIXME 'does not call freeze method', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1)' do
        object = Marshal.send(@method, Marshal.dump(object), freeze: true)
        object.should.frozen?
      end
    end

    it "returns frozen object even if object does not respond to freeze method" do
      object = MarshalSpec::ObjectWithoutFreeze.new
      NATFIXME 'returns frozen object even if object does not respond to freeze method', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1)' do
        object = Marshal.send(@method, Marshal.dump(object), freeze: true)
        object.should.frozen?
      end
    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")

      NATFIXME 'returns a frozen object when is an instance of String/Array/Regexp/Hash subclass and has instance variables', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1)' do
        object = Marshal.send(@method, Marshal.dump(source_object), freeze: true)
        object.should.frozen?
      end
    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}

        NATFIXME 'call the proc with frozen objects', exception: ArgumentError, message: 'wrong number of arguments (given 3, expected 1)' do
          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
        end

        Struct.send(:remove_const, :Brittle)
      end

      it "does not freeze the object returned by the proc" do
        NATFIXME 'does not freeze the object returned by the proc', exception: ArgumentError, message: 'wrong number of arguments (given 3, expected 1)' 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
    it "call the proc with fully initialized strings" do
      utf8_string = "foo".encode(Encoding::UTF_8)
      NATFIXME 'Support proc argument', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1)' do
        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
    end

    it "no longer mutate the object after it was passed to the proc" do
      NATFIXME 'Support proc argument', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1)' 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
        NATFIXME 'Support proc argument', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1)' do
          objs = []
          obj = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", Proc.new { |o| objs << o; o })
          objs.should == [obj]
        end
      end
    end

    it "returns the value of the proc" do
      NATFIXME 'returns the value of the proc', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1)' do
        Marshal.send(@method, Marshal.dump([1,2]), proc { [3,4] }).should ==  [3,4]
      end
    end

    it "calls the proc for recursively visited data" do
      a = [1]
      a << a
      ret = []
      NATFIXME 'Support proc argument', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1)' do
        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
    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}

      NATFIXME 'Support proc argument', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1)' do
        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]],
        ]
      end
      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
      NATFIXME 'Support proc argument', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1)' do
        b = Marshal.send(@method, Marshal.dump(a), nil)
        b.should == a
      end
    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"))
      NATFIXME 'loads the String in multibyte encoding', exception: SpecFailedException do
        object = Marshal.send(@method, Marshal.dump(source_object))
        object.string.should == "a".encode("utf-32le")
      end
    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"

        NATFIXME 'loads any structure with multiple references to the same object, followed by multiple instances of another object', exception: NoMethodError, message: "undefined method 'force_encoding' for nil" do
          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
      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"

        NATFIXME 'loads an array containing references to multiple instances of the object, followed by multiple instances of another object', exception: NoMethodError, message: "undefined method 'force_encoding' for nil" do
          marshaled_obj = Marshal.send(@method, array_dump)
          marshaled_obj.should == [nil, nil, str, str]
        end
      end
    end
  end

  it "loads an array containing objects having _dump method, and with proc" do
    NATFIXME 'Support proc argument', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1)' 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
  end

  it "loads an array containing objects having marshal_dump method, and with proc" do
    NATFIXME 'Support proc argument', exception: ArgumentError, message: 'wrong number of arguments (given 2, expected 1)' 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)
    NATFIXME 'assigns classes to nested subclasses of Array correctly', exception: SpecFailedException do
      Marshal.send(@method, arr_dump).class.should == ArraySub
    end
  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
      NATFIXME 'raises EOFError on loading an empty file', exception: SpecFailedException, message: 'marshal data too short' do
        -> { Marshal.send(@method, file) }.should raise_error(EOFError)
      end
    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)|
    # NATFIXME: Skip for now, the vast majority of them fails
    xit "loads a #{description}" do
      Marshal.send(@method, marshal).should == object
    end
  end

  MarshalSpec::DATA_19.each do |description, (object, marshal, attributes)|
    # NATFIXME: Skip for now, the vast majority of them fails
    xit "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]

      NATFIXME 'loads an array containing the same objects', exception: ArgumentError, message: 'dump format error' do
        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
    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"
      NATFIXME 'loads an extended Array object containing a user-marshaled object', exception: ArgumentError, message: 'dump format error' do
        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
  end

  describe "for a Hash" do
    it "loads an extended_user_hash with a parameter to initialize" do
      obj = UserHashInitParams.new(:abc).extend(Meths)

      NATFIXME 'loads an extended_user_hash with a parameter to initialize', exception: ArgumentError, message: 'dump format error' do
        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
    end

    it "loads an extended hash object containing a user-marshaled object" do
      obj = {a: UserMarshal.new}.extend(Meths)

      NATFIXME 'loads an extended hash object containing a user-marshaled object', exception: ArgumentError, message: 'dump format error' do
        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
    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

    it "preserves compare_by_identity behaviour" do
      h = { a: 1 }
      h.compare_by_identity
      unmarshalled = Marshal.send(@method, Marshal.dump(h))
      NATFIXME 'preserves compare_by_identity behaviour', exception: SpecFailedException do
        unmarshalled.should.compare_by_identity?

        h = { a: 1 }
        unmarshalled = Marshal.send(@method, Marshal.dump(h))
        unmarshalled.should_not.compare_by_identity?
      end
    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))
      NATFIXME 'preserves compare_by_identity behaviour for a Hash subclass', exception: SpecFailedException do
        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"

      NATFIXME 'Support non-ASCII instance variables', exception: NoMethodError, message: "undefined method 'force_encoding' for an instance of Symbol" do
        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
    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}"
      NATFIXME 'Support non-ASCII instance variables', exception: NoMethodError, message: "undefined method 'force_encoding' for an instance of Symbol" do
        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
    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
      NATFIXME 'sets binmode if it is loading through StringIO stream', exception: SpecFailedException do
        -> { Marshal.load(io) }.should raise_error(RuntimeError, "binmode")
      end
    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
      NATFIXME 'loads a String subclass with custom constructor', exception: ArgumentError, message: 'dump format error' do
        str = Marshal.send(@method, "\x04\bC: UserCustomConstructorString\"\x00")
        str.should be_an_instance_of(UserCustomConstructorString)
      end
    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"
      NATFIXME 'loads a extended_struct having fields with same objects', exception: ArgumentError, message: 'dump format error' do
        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
    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 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.send(@method, 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.send(@method, 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.send(@method, dumped).should.frozen?
    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)

      NATFIXME 'Support extended Object', exception: ArgumentError, message: 'dump format error' do
        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
    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
      NATFIXME 'Support non-ASCII instance variables', exception: NoMethodError, message: "undefined method 'force_encoding' for an instance of Symbol" do
        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
    end

    it "raises ArgumentError if the object from an 'o' stream is not dumpable as 'o' type user class" do
      NATFIXME "raises ArgumentError if the object from an 'o' stream is not dumpable as 'o' type user class", exception: SpecFailedException do
        -> do
          Marshal.send(@method, "\x04\bo:\tFile\001\001:\001\005@path\"\x10/etc/passwd")
        end.should raise_error(ArgumentError)
      end
    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))
        NATFIXME 'raises ArgumentError if the resulting class does not extend the same type', exception: SpecFailedException do
          -> { 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
  end

  describe "for a Regexp" do
    it "loads an extended Regexp" do
      obj = /[a-z]/.dup.extend(Meths, MethsMore)
      NATFIXME 'Support regexp dump', exception: ArgumentError, message: 'dump format error' do
        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
    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')

      NATFIXME 'Support regexp dump', exception: ArgumentError, message: 'dump format error' do
        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
    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/")
        NATFIXME 'restore the regexp instance variables', exception: SpecFailedException do
          new_obj.instance_variables.should == [:@regexp_ivar]
          new_obj.instance_variable_get(:@regexp_ivar).should == [42]
        end
      end
    end

    it "preserves Regexp encoding" do
      source_object = Regexp.new("a".encode("utf-32le"))
      NATFIXME 'Support encoding in regexp dump', exception: SpecFailedException do
        regexp = Marshal.send(@method, Marshal.dump(source_object))

        regexp.encoding.should == Encoding::UTF_32LE
        regexp.source.should == "a".encode("utf-32le")
      end
    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 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.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
          NATFIXME 'Support references', exception: ArgumentError, message: 'dump format error' 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
  end

  describe "for a Time" do
    it "loads" do
      NATFIXME 'Implement Time#_dump', exception: NotImplementedError do
        Marshal.send(@method, Marshal.dump(Time.at(1))).should == Time.at(1)
      end
    end

    it "loads serialized instance variables" do
      t = Time.new
      t.instance_variable_set(:@foo, 'bar')

      NATFIXME 'Implement Time#_dump', exception: NotImplementedError do
        Marshal.send(@method, Marshal.dump(t)).instance_variable_get(:@foo).should == 'bar'
      end
    end

    it "loads Time objects stored as links" do
      t = Time.new

      NATFIXME 'Implement Time#_dump', exception: NotImplementedError do
        t1, t2 = Marshal.send(@method, Marshal.dump([t, t]))
        t1.should equal t2
      end
    end

    it "keeps the local zone" do
      with_timezone 'AST', 3 do
        t = Time.local(2012, 1, 1)
        NATFIXME 'Implement Time#_dump', exception: NotImplementedError do
          Marshal.send(@method, Marshal.dump(t)).zone.should == t.zone
        end
      end
    end

    it "keeps UTC zone" do
      t = Time.now.utc
      NATFIXME 'Implement Time#_dump', exception: NotImplementedError do
        t2 = Marshal.send(@method, Marshal.dump(t))
        t2.should.utc?
      end
    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
        NATFIXME 'Implement Time#_dump', exception: NotImplementedError do
          Marshal.send(@method, Marshal.dump(t)).zone.should == 'AST'
        end
      end
    end

    it "keeps utc offset" do
      t = Time.new(2007,11,1,15,25,0, "+09:00")
      NATFIXME 'Implement Time#_dump', exception: NotImplementedError do
        t2 = Marshal.send(@method, Marshal.dump(t))
        t2.utc_offset.should == 32400
      end
    end

    it "keeps nanoseconds" do
      t = Time.now
      NATFIXME 'Implement Time#_dump', exception: NotImplementedError do
        Marshal.send(@method, Marshal.dump(t)).nsec.should == t.nsec
      end
    end

    it "does not add any additional instance variable" do
      t = Time.now
      NATFIXME 'Implement Time#_dump', exception: NotImplementedError do
        t2 = Marshal.send(@method, Marshal.dump(t))
        t2.instance_variables.should.empty?
      end
    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
      NATFIXME 'loads an old module', exception: ArgumentError, message: 'dump format error' do
        Marshal.send(@method, "\x04\bM\vKernel").should == Kernel
      end
    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"

      NATFIXME 'it loads a wrapped C pointer', exception: ArgumentError, message: 'dump format error' do
        dir = Marshal.send(@method, data)
        begin
          dir.path.should == '.'
        ensure
          dir.close
        end
      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"

      NATFIXME 'it loads a wrapped C pointer', exception: SpecFailedException, message: 'dump format error' do
        -> { Marshal.send(@method, data) }.should raise_error(TypeError)
      end
    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
