spec/deserializer_spec.rb in python-pickle-0.1.1 vs spec/deserializer_spec.rb in python-pickle-0.2.0

- old
+ new

@@ -29,18 +29,26 @@ it "must contain '__builtin__.object' for Python 2.x support" do expect(subject.constants['__builtin__']['object']).to be(described_class::OBJECT_CLASS) end + it "must contain '__builtin__.set' for Python 2.x support" do + expect(subject.constants['__builtin__']['set']).to be(Set) + end + it "must contain '__builtin__.bytearray' for Python 2.x support" do expect(subject.constants['__builtin__']['bytearray']).to be(Python::Pickle::ByteArray) end it "must contain 'builtins.object' for Python 3.x support" do expect(subject.constants['builtins']['object']).to be(described_class::OBJECT_CLASS) end + it "must contain 'builtins.set' for Python 2.x support" do + expect(subject.constants['builtins']['set']).to be(Set) + end + it "must contain 'builtins.bytearray' for Python 2.x support" do expect(subject.constants['builtins']['bytearray']).to be(Python::Pickle::ByteArray) end end @@ -82,25 +90,45 @@ '_reconstructor' => subject.method(:copyreg_reconstructor) }, '__builtin__' => { 'object' => described_class::OBJECT_CLASS, + 'set' => Set, 'bytearray' => Python::Pickle::ByteArray }, 'builtins' => { 'object' => described_class::OBJECT_CLASS, + 'set' => Set, 'bytearray' => Python::Pickle::ByteArray }, '__main__' => { 'MyClass' => TestDeserializer::MyClass } } ) end end + + context "when initialized with the `buffers:` keyword argument" do + let(:buffer1) { "hello world" } + let(:buffer2) { "foo bar" } + let(:buffers) do + [ + buffer1, + buffer2 + ] + end + + subject { described_class.new(buffers: buffers) } + + it "must set #buffers to an Enumerator of the buffers" do + expect(subject.buffers).to be_kind_of(Enumerator) + expect(subject.buffers.to_a).to eq(buffers) + end + end end describe "#push_meta_stack" do before do subject.stack << 1 << 2 << 3 @@ -413,10 +441,41 @@ it "must push an empty Hash object onto the #stack" do expect(subject.stack).to eq([ {} ]) end end + context "when given a Python::Pickle::Instructions::EMPTY_SET" do + let(:instruction) { Python::Pickle::Instructions::EMPTY_SET } + + before do + subject.execute(instruction) + end + + it "must push an empty Set object onto the #stack" do + expect(subject.stack).to eq([ Set.new ]) + end + end + + context "when given a Python::Pickle::Instructions::FROZENSET" do + let(:instruction) { Python::Pickle::Instructions::FROZENSET } + + before do + subject.meta_stack << [] + subject.stack << 1 << 2 << 3 + subject.execute(instruction) + end + + it "must pop the #meta_stack and create a frozen Set from the previous #stack and push the frozen Set onto the new #stack" do + expect(subject.stack.length).to eq(1) + + set = subject.stack[-1] + + expect(set).to be_frozen + expect(set).to eq(Set[1,2,3]) + end + end + context "when given a Python::Pickle::Instructions::APPEND" do context "and when the previous element on the stack is an Array" do let(:instruction) { Python::Pickle::Instructions::APPEND } before do @@ -427,10 +486,23 @@ it "must pop the last element from the #stack and push it onto the next list element" do expect(subject.stack).to eq([ [2] ]) end end + context "and when the previous element on the stack is a Set" do + let(:instruction) { Python::Pickle::Instructions::APPEND } + + before do + subject.stack << Set.new << 2 + subject.execute(instruction) + end + + it "must pop the last element from the #stack and push it onto the next list element" do + expect(subject.stack).to eq([ Set[2] ]) + end + end + context "but when the previous element on the stack is not an Array" do let(:instruction) { Python::Pickle::Instructions::APPEND } let(:item) { 2 } let(:list) { "XXX" } @@ -459,10 +531,24 @@ it "must pop the #meta_stack, store the #stack, and concat the previous #stack onto the last element of the new #stack" do expect(subject.stack).to eq([ [1,2,3,4,5,6] ]) end end + context "and when the previous element on the stack is a Set" do + let(:instruction) { Python::Pickle::Instructions::APPENDS } + + before do + subject.meta_stack << [ Set[1,2,3] ] + subject.stack << 4 << 5 << 6 + subject.execute(instruction) + end + + it "must pop the #meta_stack, store the #stack, and concat the previous #stack onto the last element of the new #stack" do + expect(subject.stack).to eq([ Set[1,2,3,4,5,6] ]) + end + end + context "but when the previous element on the stack is not an Array" do let(:instruction) { Python::Pickle::Instructions::APPENDS } let(:items) { [3,4,5] } let(:list) { "XXX" } @@ -477,10 +563,43 @@ }.to raise_error(Python::Pickle::DeserializationError,"cannot append elements #{items.inspect} onto a non-Array: #{list.inspect}") end end end + context "when given a Python::Pickle::Instructions::ADDITEMS" do + context "and when the previous element on the stack is a Set" do + let(:instruction) { Python::Pickle::Instructions::ADDITEMS } + + before do + subject.meta_stack << [ Set[1,2,3] ] + subject.stack << 4 << 5 << 6 + subject.execute(instruction) + end + + it "must pop the #meta_stack, store the #stack, and concat the previous #stack onto the last element of the new #stack" do + expect(subject.stack).to eq([ Set[1,2,3,4,5,6] ]) + end + end + + context "but when the previous element on the stack is not a Set" do + let(:instruction) { Python::Pickle::Instructions::ADDITEMS } + let(:items) { [3,4,5] } + let(:set) { [] } + + before do + subject.meta_stack << [ set ] + subject.stack << items[0] << items[1] << items[2] + end + + it do + expect { + subject.execute(instruction) + }.to raise_error(Python::Pickle::DeserializationError,"cannot add items #{items.inspect} to a non-Set object: #{set.inspect}") + end + end + end + context "when given a Python::Pickle::Instructions::LIST" do let(:instruction) { Python::Pickle::Instructions::LIST } before do subject.meta_stack << [ [1,2,3] ] @@ -627,10 +746,146 @@ expect(constant.name).to eq(name) end end end + context "when given a Python::Pickle::Instructions::Inst object" do + let(:namespace) { '__main__' } + let(:name) { 'MyClass' } + let(:instruction) { Python::Pickle::Instructions::Inst.new(namespace,name) } + + before do + subject.meta_stack << [] + subject.stack << 1 << 2 + subject.execute(instruction) + end + + context "when the constant can be resolved" do + module TestInstInstruction + class MyClass + attr_reader :x, :y + + def initialize(x,y) + @x = x + @y = y + end + end + end + + subject do + described_class.new( + constants: { + '__main__' => { + 'MyClass' => TestInstInstruction::MyClass + } + } + ) + end + + it "must resolve the class from the INST instruction's #namespace and #name, pop the meta stack, use the previous sstack as the initialization arguments, initialize a new instance of the class, and push it onto the new #stack" do + expect(subject.stack.length).to eq(1) + + object = subject.stack[-1] + + expect(object).to be_kind_of(TestInstInstruction::MyClass) + expect(object.x).to eq(1) + expect(object.y).to eq(2) + end + end + + context "but the constant cannot be resolved" do + it "must push a new Python::Pickle::PyClass object onto the #stack" do + py_object = subject.stack[-1] + + expect(py_object).to be_kind_of(Python::Pickle::PyObject) + expect(py_object.py_class).to be_kind_of(Python::Pickle::PyClass) + expect(py_object.py_class.namespace).to eq(namespace) + expect(py_object.py_class.name).to eq(name) + expect(py_object.init_args).to eq([1,2]) + end + end + end + + context "when given a Python::Pickle::Instructions::OBJ" do + let(:instruction) { Python::Pickle::Instructions::OBJ } + + context "and when the constant on the #stack is a Ruby class" do + module TestObjInstruction + class MyClass + end + end + + context "but there are no additional arguments on the #stack after the class" do + before do + subject.stack << TestObjInstruction::MyClass + subject.execute(instruction) + end + + it "must pop off first element, and initialize a new instance of the class, and push the new instance onto the #stack" do + expect(subject.stack.length).to eq(1) + expect(subject.stack[-1]).to be_kind_of(TestObjInstruction::MyClass) + end + end + + context "but there are additional arguments on the #stack after the class" do + module TestObjInstruction + class MyClassWithArgs + attr_reader :x, :y + + def initialize(x,y) + @x = x + @y = y + end + end + end + + before do + subject.stack << TestObjInstruction::MyClassWithArgs << 1 << 2 + subject.execute(instruction) + end + + it "must call #initialize with the splatted tuple's arguments" do + object = subject.stack[-1] + + expect(object.x).to eq(1) + expect(object.y).to eq(2) + end + end + end + + context "and when the constant on the #stack is a PyClass" do + let(:namespace) { '__main__' } + let(:name) { 'MyClass' } + let(:py_class) { Python::Pickle::PyClass.new(namespace,name) } + + context "but there are no additional arguments on the #stack after the class" do + before do + subject.stack << py_class + subject.execute(instruction) + end + + it "must pop off the two last elements and push the new Python::Pickle::PyObject onto the #stack" do + expect(subject.stack.length).to eq(1) + expect(subject.stack[-1]).to be_kind_of(Python::Pickle::PyObject) + end + end + + context "but there are additional arguments on the #stack after the class" do + before do + subject.stack << py_class << 1 << 2 + subject.execute(instruction) + end + + it "must set the object's #init_args to the tuple's elements" do + object = subject.stack[-1] + + expect(object.init_args).to eq([1,2]) + end + end + end + end + context "when given a Python::Pickle::Instructions::NEWOBJ" do let(:instruction) { Python::Pickle::Instructions::NEWOBJ } context "and when the constant on the #stack is a Ruby class" do module TestNewObjInstruction @@ -1197,9 +1452,62 @@ it do expect { subject.execute(instruction) }.to raise_error(Python::Pickle::DeserializationError,"cannot set key value pairs (#{pairs.inspect}) into non-Hash: #{object.inspect}") end + end + end + + context "when given a Python::Pickle::Instructions::NEXT_BUFFER" do + let(:instruction) { Python::Pickle::Instructions::NEXT_BUFFER } + + context "and the #{described_class} was initialized with the buffers: keyword argument" do + let(:buffer1) { String.new("hello world") } + let(:buffer2) { String.new("foo bar") } + let(:buffers) do + [ + buffer1, + buffer2 + ] + end + + subject { described_class.new(buffers: buffers) } + + before do + subject.execute(instruction) + end + + it "must take the next element from #buffers and push it onto the #stack" do + expect(subject.stack).to eq([buffer1]) + end + + it "must not modify the underlying buffers Array" do + expect(buffers).to eq([buffer1, buffer2]) + end + end + + context "but the #{described_class} was not initialized with the buffers: keyword argument" do + it do + expect { + subject.execute(instruction) + }.to raise_error(Python::Pickle::DeserializationError,"pickle stream includes a NEXT_BUFFER instruction, but no buffers were provided") + end + end + end + + context "when given a Python::Pickle::Instructions::READONLY_BUFFER" do + let(:instruction) { Python::Pickle::Instructions::READONLY_BUFFER } + + let(:buffer1) { String.new("hello world") } + let(:buffer2) { String.new("foo bar") } + + before do + subject.stack << buffer1 << buffer2 + subject.execute(instruction) + end + + it "must freeze the buffer at the top of the #stack" do + expect(subject.stack[-1]).to be_frozen end end end describe "#copyreg_reconstructor" do