require File.expand_path('../../fixtures/marshal_data', __FILE__)
require 'stringio'

describe :marshal_load, :shared => true do
  ruby_version_is ""..."2.1" do
    before :all do
      @num_self_class = 0
    end
  end

  ruby_version_is "2.1" do
    before :all do
      @num_self_class = 1
    end
  end

  it "raises an ArgumentError when the dumped data is truncated" do
    obj = {:first => 1, :second => 2, :third => 3}
    lambda { Marshal.send(@method, Marshal.dump(obj)[0, 5]) }.should raise_error(ArgumentError)
  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)

    lambda { Marshal.send(@method, kaboom) }.should raise_error(ArgumentError)
  end

  ruby_version_is "1.9" do
    it "returns the value of the proc when called with a proc" do
      Marshal.send(@method, Marshal.dump([1,2]), proc { [3,4] }).should ==  [3,4]
    end
  end

  ruby_version_is ""..."1.9" do
    it "ignores the value of the proc when called with a proc" do
      Marshal.send(@method, Marshal.dump([1,2]), proc { [3,4] }).should ==  [1,2]
    end
  end

  ruby_version_is "1.9" 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; arg })
      ret.first.should == 1
      ret[1].should == [1,a]
      ret[2].should == a
      ret.size.should == 3
    end
  end

  ruby_version_is ""..."1.9" do
    it "doesn't call the proc for recursively visited data" do
      a = [1]
      a << a
      ret = []
      Marshal.send(@method, Marshal.dump(a), proc { |arg| ret << arg })
      ret.first.should == 1
      ret.size.should == 2
    end
  end

  ruby_version_is ""..."1.9" do
    it "loads a array containing objects having _dump method, and with proc" do
      arr = []
      proc = Proc.new { |o| arr << o }
      o1 = UserDefined.new;
      o2 = UserDefinedWithIvar.new
      obj = [o1, o2, o1, o2]

      Marshal.send(@method, "\004\b[\tu:\020UserDefined\022\004\b[\a\"\nstuff@\006u:\030UserDefinedWithIvar5\004\b[\bI\"\nstuff\006:\t@foo:\030UserDefinedWithIvar\"\tmore@\a@\006@\a", proc)

      arr.should == [o1, o2, obj]
    end
  end

  ruby_version_is "1.9" do
    it "loads a array containing objects having _dump method, and with proc" do
      arr = []
      myproc = Proc.new { |o| arr << o; 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.should == [o1, o2, o1, o2, obj]
    end
  end

  ruby_version_is ""..."1.9" do
    it "loads an array containing objects having marshal_dump method, and with proc" do
      arr = []
      proc = Proc.new { |o| arr << o }
      o1 = UserMarshal.new
      o2 = UserMarshalWithIvar.new
      obj = [o1, o2, o1, o2]

      Marshal.send(@method, "\004\b[\tU:\020UserMarshal\"\nstuffU:\030UserMarshalWithIvar[\006\"\fmy data@\006@\b", proc)

      arr.should == ['stuff', o1, 'my data', ['my data'], o2, obj]
    end
  end

  ruby_version_is "1.9" do
    it "loads an array containing objects having marshal_dump method, and with proc" do
      arr = []
      proc = Proc.new { |o| arr << o; o }
      o1 = UserMarshal.new
      o2 = UserMarshalWithIvar.new
      obj = [o1, o2, o1, o2]

      Marshal.send(@method, "\004\b[\tU:\020UserMarshal\"\nstuffU:\030UserMarshalWithIvar[\006\"\fmy data@\006@\b", proc)

      arr.should == ['stuff', o1, 'my data', ['my data'], o2, o1, o2, obj]
    end
  end

  ruby_version_is ""..."1.9" do
    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, h, a]
      obj.instance_variable_set(:@zoo, 'ant')
      proc = Proc.new { |o| arr << o }

      new_obj = Marshal.send(@method, "\004\bI[\fI\"\ahi\006:\t@fooi\ni\017@\006@\006IS:\024Struct::Brittle\006:\006af\0060\006:\n@clue\"\tnone}\006\"\tninei\016\"\bdefI[\b;\a:\006b:\006c\006:\t@twoi\a\006:\t@zoo\"\bant", proc)

      new_obj.should == obj
      new_obj.instance_variable_get(:@zoo).should == 'ant'

      arr.should ==
        [5, s, 10, 0.0, 'none', st, 'nine', 9, 'def', h, :b, :c, 2, a, 'ant', obj]
    end
  end

  ruby_version_is "1.9" do
    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; 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 == ["hi", false, 5, 10, "hi", "hi", 0.0, st, "none", false,
        :b, :c, a, 2, ["hi", 10, "hi", "hi", st, [:a, :b, :c]], "ant", false]
    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

    lambda { 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

    lambda { 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
      lambda { Marshal.send(@method, file) }.should raise_error(EOFError)
    ensure
      file.close
      rm_r temp_file
    end
  end

  it "returns an untainted object if source is untainted" do
    x = Object.new
    y = Marshal.send(@method, Marshal.dump(x))
    y.tainted?.should be_false
  end

  it "returns a tainted object if source is tainted" do
    x = Object.new
    x.taint
    s = Marshal.dump(x)
    y = Marshal.send(@method, s)
    y.tainted?.should be_true

    # note that round-trip via Marshal does not preserve
    # the taintedness at each level of the nested structure
    y = Marshal.send(@method, Marshal.dump([[x]]))
    y.tainted?.should be_true
    y.first.tainted?.should be_true
    y.first.first.tainted?.should be_true

  end

  it "preserves taintedness of nested structure" do
    x = Object.new
    a = [[x]]
    x.taint
    y = Marshal.send(@method, Marshal.dump(a))
    y.tainted?.should be_true
    y.first.tainted?.should be_true
    y.first.first.tainted?.should be_true
  end

  ruby_version_is "1.9" do

    it "returns a trusted object if source is trusted" do
      x = Object.new
      y = Marshal.send(@method, Marshal.dump(x))
      y.untrusted?.should be_false
    end

    it "returns an untrusted object if source is untrusted" do
      x = Object.new
      x.untrust
      y = Marshal.send(@method, Marshal.dump(x))
      y.untrusted?.should be_true

      # note that round-trip via Marshal does not preserve
      # the untrustedness at each level of the nested structure
      y = Marshal.send(@method, Marshal.dump([[x]]))
      y.untrusted?.should be_true
      y.first.untrusted?.should be_true
      y.first.first.untrusted?.should be_true

    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

  ruby_version_is "1.9" do
    MarshalSpec::DATA_19.each do |description, (object, marshal, attributes)|
      it "loads a #{description}" do
        Marshal.send(@method, marshal).should == object
      end
    end
  end

  describe "for an Array" do
    it "loads a 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)
      Marshal.send(@method, "\004\bI[\b\"\0065I\"\twell\006:\t@fooi\017\"\ahi\006:\t@mix@\a").should ==
        obj
    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 "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
  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
        obj = "This is a string which should be unmarshalled through StringIO stream!"
        Marshal.send(@method, StringIO.new(Marshal.dump(obj))).should == obj
    end

    with_feature :encoding do
      it "loads a US-ASCII String" do
        str = "abc".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".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".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
    end
  end

  describe "for a Struct" do
    it "loads a extended_struct having fields with same objects" do
      s = 'hi'
      obj = Struct.new("Ure2", :a, :b).new.extend(Meths)
      obj.a = [:a, s]
      obj.b = [:Meths, s]

      Marshal.send(@method, "\004\be:\nMethsS:\021Struct::Ure2\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a").should ==
        obj
    end

    it "loads a struct having ivar" do
      obj = Struct.new("Thick").new
      obj.instance_variable_set(:@foo, 5)
      Marshal.send(@method, "\004\bIS:\022Struct::Thick\000\006:\t@fooi\n").should == obj
    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
    end

    ruby_version_is ""..."1.9" do
      it "calls initialize on the unmarshaled struct" do
        s = MarshalSpec::StructWithUserInitialize.new('foo')
        Thread.current[MarshalSpec::StructWithUserInitialize::THREADLOCAL_KEY].should == ['foo']
        s.a.should == 'foo'

        dumped = Marshal.dump(s)
        loaded = Marshal.send(@method, dumped)

        Thread.current[MarshalSpec::StructWithUserInitialize::THREADLOCAL_KEY].should == [nil]
        loaded.a.should == 'foo'
      end
    end

    ruby_version_is "1.9" do
      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
  end

  describe "for a user Class" 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 user_object" do
      obj = UserObject.new
      Marshal.send(@method, "\004\bo:\017UserObject\000").should be_kind_of(UserObject)
    end

    it "loads a object" do
      Marshal.send(@method, "\004\bo:\vObject\000").should be_kind_of(Object)
    end

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

    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))
        lambda { Marshal.send(@method, data) }.should raise_error(ArgumentError)

        MarshalSpec.set_swapped_class(Class.new)
        lambda { Marshal.send(@method, data) }.should raise_error(ArgumentError)
      end
    end

    it "loads a 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
  end

  describe "for a Regexp" do
    it "loads an extended Regexp" do
      obj = /[a-z]/.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 extended_user_regexp having ivar" 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
  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
  end

  describe "for a 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|
        lambda { 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 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
      lambda { Marshal.send(@method, "\x04\bc\vKernel") }.should raise_error(ArgumentError)
    end

    it "raises ArgumentError if given a nonexistent class" do
      lambda { Marshal.send(@method, "\x04\bc\vStrung") }.should raise_error(ArgumentError)
    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
      lambda { 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
  end

  describe "for a wrapped C pointer" do
    it "loads" do
      data = "\004\bd:\rUserData" \
             "[\a[\b\"\aCN\"\vnobodyi\021[\b\"\aDC\"\fexamplei\e"

      expected = UserData.parse 'CN=nobody/DC=example'

      Marshal.send(@method, data).to_a.should == expected.to_a
    end

    it "raises TypeError when the local class is missing _data_load" do
      data = "\004\bd:\027UserDataUnloadable" \
             "[\a[\b\"\aCN\"\vnobodyi\021[\b\"\aDC\"\fexamplei\e"

      lambda { 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"

      lambda { Marshal.send(@method, data) }.should raise_error(ArgumentError)
    end
  end
end
