python-pickle 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.github/workflows/ruby.yml +27 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +14 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +20 -0
- data/README.md +149 -0
- data/Rakefile +13 -0
- data/gemspec.yml +25 -0
- data/lib/python/pickle/byte_array.rb +40 -0
- data/lib/python/pickle/deserializer.rb +595 -0
- data/lib/python/pickle/exceptions.rb +12 -0
- data/lib/python/pickle/instruction.rb +52 -0
- data/lib/python/pickle/instructions/add_items.rb +26 -0
- data/lib/python/pickle/instructions/append.rb +24 -0
- data/lib/python/pickle/instructions/appends.rb +26 -0
- data/lib/python/pickle/instructions/bin_bytes.rb +32 -0
- data/lib/python/pickle/instructions/bin_bytes8.rb +32 -0
- data/lib/python/pickle/instructions/bin_float.rb +29 -0
- data/lib/python/pickle/instructions/bin_get.rb +27 -0
- data/lib/python/pickle/instructions/bin_int1.rb +29 -0
- data/lib/python/pickle/instructions/bin_put.rb +29 -0
- data/lib/python/pickle/instructions/bin_string.rb +32 -0
- data/lib/python/pickle/instructions/bin_unicode.rb +32 -0
- data/lib/python/pickle/instructions/bin_unicode8.rb +32 -0
- data/lib/python/pickle/instructions/build.rb +24 -0
- data/lib/python/pickle/instructions/byte_array8.rb +32 -0
- data/lib/python/pickle/instructions/dict.rb +17 -0
- data/lib/python/pickle/instructions/dup.rb +24 -0
- data/lib/python/pickle/instructions/empty_dict.rb +26 -0
- data/lib/python/pickle/instructions/empty_list.rb +26 -0
- data/lib/python/pickle/instructions/empty_set.rb +26 -0
- data/lib/python/pickle/instructions/empty_tuple.rb +26 -0
- data/lib/python/pickle/instructions/ext1.rb +29 -0
- data/lib/python/pickle/instructions/ext2.rb +29 -0
- data/lib/python/pickle/instructions/ext4.rb +29 -0
- data/lib/python/pickle/instructions/float.rb +24 -0
- data/lib/python/pickle/instructions/frame.rb +29 -0
- data/lib/python/pickle/instructions/frozen_set.rb +26 -0
- data/lib/python/pickle/instructions/get.rb +27 -0
- data/lib/python/pickle/instructions/global.rb +62 -0
- data/lib/python/pickle/instructions/has_length_and_value.rb +58 -0
- data/lib/python/pickle/instructions/has_value.rb +50 -0
- data/lib/python/pickle/instructions/int.rb +24 -0
- data/lib/python/pickle/instructions/list.rb +24 -0
- data/lib/python/pickle/instructions/long.rb +24 -0
- data/lib/python/pickle/instructions/long1.rb +32 -0
- data/lib/python/pickle/instructions/long4.rb +32 -0
- data/lib/python/pickle/instructions/long_bin_get.rb +27 -0
- data/lib/python/pickle/instructions/mark.rb +24 -0
- data/lib/python/pickle/instructions/memoize.rb +26 -0
- data/lib/python/pickle/instructions/new_false.rb +24 -0
- data/lib/python/pickle/instructions/new_obj.rb +26 -0
- data/lib/python/pickle/instructions/new_obj_ex.rb +26 -0
- data/lib/python/pickle/instructions/new_true.rb +24 -0
- data/lib/python/pickle/instructions/next_buffer.rb +26 -0
- data/lib/python/pickle/instructions/none.rb +24 -0
- data/lib/python/pickle/instructions/pop.rb +24 -0
- data/lib/python/pickle/instructions/pop_mark.rb +24 -0
- data/lib/python/pickle/instructions/proto.rb +29 -0
- data/lib/python/pickle/instructions/put.rb +24 -0
- data/lib/python/pickle/instructions/readonly_buffer.rb +26 -0
- data/lib/python/pickle/instructions/reduce.rb +24 -0
- data/lib/python/pickle/instructions/set_item.rb +24 -0
- data/lib/python/pickle/instructions/set_items.rb +26 -0
- data/lib/python/pickle/instructions/short_bin_bytes.rb +32 -0
- data/lib/python/pickle/instructions/short_bin_string.rb +32 -0
- data/lib/python/pickle/instructions/short_bin_unicode.rb +32 -0
- data/lib/python/pickle/instructions/stack_global.rb +26 -0
- data/lib/python/pickle/instructions/stop.rb +24 -0
- data/lib/python/pickle/instructions/string.rb +24 -0
- data/lib/python/pickle/instructions/tuple.rb +24 -0
- data/lib/python/pickle/instructions/tuple1.rb +24 -0
- data/lib/python/pickle/instructions/tuple2.rb +24 -0
- data/lib/python/pickle/instructions/tuple3.rb +24 -0
- data/lib/python/pickle/protocol.rb +56 -0
- data/lib/python/pickle/protocol0.rb +399 -0
- data/lib/python/pickle/protocol1.rb +183 -0
- data/lib/python/pickle/protocol2.rb +229 -0
- data/lib/python/pickle/protocol3.rb +163 -0
- data/lib/python/pickle/protocol4.rb +285 -0
- data/lib/python/pickle/protocol5.rb +218 -0
- data/lib/python/pickle/py_class.rb +75 -0
- data/lib/python/pickle/py_object.rb +141 -0
- data/lib/python/pickle/tuple.rb +19 -0
- data/lib/python/pickle/version.rb +6 -0
- data/lib/python/pickle.rb +226 -0
- data/python-pickle.gemspec +62 -0
- data/spec/byte_array_spec.rb +54 -0
- data/spec/deserializer_spec.rb +1201 -0
- data/spec/fixtures/ascii_str_v3.pkl +0 -0
- data/spec/fixtures/ascii_str_v4.pkl +0 -0
- data/spec/fixtures/ascii_str_v5.pkl +0 -0
- data/spec/fixtures/bin_str_v0.pkl +3 -0
- data/spec/fixtures/bin_str_v1.pkl +0 -0
- data/spec/fixtures/bin_str_v2.pkl +0 -0
- data/spec/fixtures/bin_str_v3.pkl +0 -0
- data/spec/fixtures/bin_str_v4.pkl +0 -0
- data/spec/fixtures/bin_str_v5.pkl +0 -0
- data/spec/fixtures/bytearray_v0.pkl +10 -0
- data/spec/fixtures/bytearray_v1.pkl +0 -0
- data/spec/fixtures/bytearray_v2.pkl +0 -0
- data/spec/fixtures/bytearray_v3.pkl +0 -0
- data/spec/fixtures/bytearray_v4.pkl +0 -0
- data/spec/fixtures/bytearray_v5.pkl +0 -0
- data/spec/fixtures/class_v0.pkl +4 -0
- data/spec/fixtures/class_v1.pkl +0 -0
- data/spec/fixtures/class_v2.pkl +0 -0
- data/spec/fixtures/class_v3.pkl +0 -0
- data/spec/fixtures/class_v4.pkl +0 -0
- data/spec/fixtures/class_v5.pkl +0 -0
- data/spec/fixtures/dict_v0.pkl +6 -0
- data/spec/fixtures/dict_v1.pkl +0 -0
- data/spec/fixtures/dict_v2.pkl +0 -0
- data/spec/fixtures/dict_v3.pkl +0 -0
- data/spec/fixtures/dict_v4.pkl +0 -0
- data/spec/fixtures/dict_v5.pkl +0 -0
- data/spec/fixtures/escaped_str_v0.pkl +3 -0
- data/spec/fixtures/escaped_str_v1.pkl +0 -0
- data/spec/fixtures/escaped_str_v2.pkl +0 -0
- data/spec/fixtures/false_v0.pkl +2 -0
- data/spec/fixtures/false_v1.pkl +2 -0
- data/spec/fixtures/false_v2.pkl +1 -0
- data/spec/fixtures/false_v3.pkl +1 -0
- data/spec/fixtures/false_v4.pkl +1 -0
- data/spec/fixtures/false_v5.pkl +1 -0
- data/spec/fixtures/float_v0.pkl +2 -0
- data/spec/fixtures/float_v1.pkl +1 -0
- data/spec/fixtures/float_v2.pkl +1 -0
- data/spec/fixtures/float_v3.pkl +1 -0
- data/spec/fixtures/float_v4.pkl +0 -0
- data/spec/fixtures/float_v5.pkl +0 -0
- data/spec/fixtures/function_v0.pkl +4 -0
- data/spec/fixtures/function_v1.pkl +0 -0
- data/spec/fixtures/function_v2.pkl +0 -0
- data/spec/fixtures/function_v3.pkl +0 -0
- data/spec/fixtures/function_v4.pkl +0 -0
- data/spec/fixtures/function_v5.pkl +0 -0
- data/spec/fixtures/hex_str_v0.pkl +3 -0
- data/spec/fixtures/hex_str_v1.pkl +0 -0
- data/spec/fixtures/hex_str_v2.pkl +0 -0
- data/spec/fixtures/int_v0.pkl +2 -0
- data/spec/fixtures/int_v1.pkl +1 -0
- data/spec/fixtures/int_v2.pkl +1 -0
- data/spec/fixtures/int_v3.pkl +1 -0
- data/spec/fixtures/int_v4.pkl +1 -0
- data/spec/fixtures/int_v5.pkl +1 -0
- data/spec/fixtures/list_v0.pkl +7 -0
- data/spec/fixtures/list_v1.pkl +0 -0
- data/spec/fixtures/list_v2.pkl +0 -0
- data/spec/fixtures/list_v3.pkl +0 -0
- data/spec/fixtures/list_v4.pkl +0 -0
- data/spec/fixtures/list_v5.pkl +0 -0
- data/spec/fixtures/long_v0.pkl +2 -0
- data/spec/fixtures/long_v1.pkl +2 -0
- data/spec/fixtures/long_v2.pkl +0 -0
- data/spec/fixtures/long_v3.pkl +0 -0
- data/spec/fixtures/long_v4.pkl +0 -0
- data/spec/fixtures/long_v5.pkl +0 -0
- data/spec/fixtures/nested_dict_v0.pkl +12 -0
- data/spec/fixtures/nested_dict_v1.pkl +0 -0
- data/spec/fixtures/nested_dict_v2.pkl +0 -0
- data/spec/fixtures/nested_dict_v3.pkl +0 -0
- data/spec/fixtures/nested_dict_v4.pkl +0 -0
- data/spec/fixtures/nested_dict_v5.pkl +0 -0
- data/spec/fixtures/nested_list_v0.pkl +9 -0
- data/spec/fixtures/nested_list_v1.pkl +0 -0
- data/spec/fixtures/nested_list_v2.pkl +0 -0
- data/spec/fixtures/nested_list_v3.pkl +0 -0
- data/spec/fixtures/nested_list_v4.pkl +0 -0
- data/spec/fixtures/nested_list_v5.pkl +0 -0
- data/spec/fixtures/none_v0.pkl +1 -0
- data/spec/fixtures/none_v1.pkl +1 -0
- data/spec/fixtures/none_v2.pkl +1 -0
- data/spec/fixtures/none_v3.pkl +1 -0
- data/spec/fixtures/none_v4.pkl +1 -0
- data/spec/fixtures/none_v5.pkl +1 -0
- data/spec/fixtures/object_v0.pkl +19 -0
- data/spec/fixtures/object_v1.pkl +0 -0
- data/spec/fixtures/object_v2.pkl +0 -0
- data/spec/fixtures/object_v3.pkl +0 -0
- data/spec/fixtures/object_v4.pkl +0 -0
- data/spec/fixtures/object_v5.pkl +0 -0
- data/spec/fixtures/str_v0.pkl +3 -0
- data/spec/fixtures/str_v1.pkl +0 -0
- data/spec/fixtures/str_v2.pkl +0 -0
- data/spec/fixtures/str_v3.pkl +0 -0
- data/spec/fixtures/str_v4.pkl +0 -0
- data/spec/fixtures/str_v5.pkl +0 -0
- data/spec/fixtures/true_v0.pkl +2 -0
- data/spec/fixtures/true_v1.pkl +2 -0
- data/spec/fixtures/true_v2.pkl +1 -0
- data/spec/fixtures/true_v3.pkl +1 -0
- data/spec/fixtures/true_v4.pkl +1 -0
- data/spec/fixtures/true_v5.pkl +1 -0
- data/spec/fixtures/unicode_str_v0.pkl +3 -0
- data/spec/fixtures/unicode_str_v1.pkl +0 -0
- data/spec/fixtures/unicode_str_v2.pkl +0 -0
- data/spec/fixtures/unicode_str_v3.pkl +0 -0
- data/spec/fixtures/unicode_str_v4.pkl +0 -0
- data/spec/fixtures/unicode_str_v5.pkl +0 -0
- data/spec/generate_pickles2.py +41 -0
- data/spec/generate_pickles3.py +40 -0
- data/spec/integration/load/protocol0_spec.rb +258 -0
- data/spec/integration/load/protocol1_spec.rb +258 -0
- data/spec/integration/load/protocol2_spec.rb +258 -0
- data/spec/integration/load/protocol3_spec.rb +258 -0
- data/spec/integration/load/protocol4_spec.rb +258 -0
- data/spec/integration/load/protocol5_spec.rb +258 -0
- data/spec/integration/parse/protocol0_spec.rb +467 -0
- data/spec/integration/parse/protocol1_spec.rb +459 -0
- data/spec/integration/parse/protocol2_spec.rb +471 -0
- data/spec/integration/parse/protocol3_spec.rb +407 -0
- data/spec/integration/parse/protocol4_spec.rb +439 -0
- data/spec/integration/parse/protocol5_spec.rb +419 -0
- data/spec/pickle_spec.rb +163 -0
- data/spec/protocol0_read_instruction_examples.rb +211 -0
- data/spec/protocol0_spec.rb +445 -0
- data/spec/protocol1_read_instruction_examples.rb +156 -0
- data/spec/protocol1_spec.rb +59 -0
- data/spec/protocol2_read_instruction_examples.rb +135 -0
- data/spec/protocol2_spec.rb +128 -0
- data/spec/protocol3_read_instruction_examples.rb +29 -0
- data/spec/protocol3_spec.rb +32 -0
- data/spec/protocol4_read_instruction_examples.rb +142 -0
- data/spec/protocol4_spec.rb +58 -0
- data/spec/protocol5_spec.rb +68 -0
- data/spec/py_class_spec.rb +62 -0
- data/spec/py_object_spec.rb +149 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/tuple_spec.rb +18 -0
- metadata +325 -0
@@ -0,0 +1,1201 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'python/pickle/deserializer'
|
3
|
+
|
4
|
+
describe Python::Pickle::Deserializer do
|
5
|
+
describe "#initialize" do
|
6
|
+
it "must initialize #meta_stack to an empty Array" do
|
7
|
+
expect(subject.meta_stack).to eq([])
|
8
|
+
end
|
9
|
+
|
10
|
+
it "must initialize #stack to an empty Array" do
|
11
|
+
expect(subject.stack).to eq([])
|
12
|
+
end
|
13
|
+
|
14
|
+
it "must initialize #memo to an empty Array" do
|
15
|
+
expect(subject.memo).to eq([])
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#constants" do
|
19
|
+
it "must contain 'copy_reg._reconstructor' for protocol 0 support" do
|
20
|
+
expect(subject.constants['copy_reg']['_reconstructor']).to eq(
|
21
|
+
subject.method(:copyreg_reconstructor)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "must contain '__builtin__.object' for Python 2.x support" do
|
26
|
+
expect(subject.constants['__builtin__']['object']).to be(described_class::OBJECT_CLASS)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "must contain '__builtin__.bytearray' for Python 2.x support" do
|
30
|
+
expect(subject.constants['__builtin__']['bytearray']).to be(Python::Pickle::ByteArray)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "must contain 'builtins.object' for Python 3.x support" do
|
34
|
+
expect(subject.constants['builtins']['object']).to be(described_class::OBJECT_CLASS)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "must contain 'builtins.bytearray' for Python 2.x support" do
|
38
|
+
expect(subject.constants['builtins']['bytearray']).to be(Python::Pickle::ByteArray)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "must initialize #extensionsto an empty Hash" do
|
43
|
+
expect(subject.extensions).to eq({})
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when initialized with the `extensions:` keyword argument" do
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when initialized with the `constants:` keyword argument" do
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#push_meta_stack" do
|
54
|
+
before do
|
55
|
+
subject.stack << 1 << 2 << 3
|
56
|
+
|
57
|
+
subject.push_meta_stack
|
58
|
+
end
|
59
|
+
|
60
|
+
it "must push #stack onto #meta_stack and set #stack to a new empty Array" do
|
61
|
+
expect(subject.meta_stack).to eq([ [1,2,3] ])
|
62
|
+
expect(subject.stack).to eq([])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#pop_meta_stack" do
|
67
|
+
before do
|
68
|
+
subject.stack << 3 << 4 << 5
|
69
|
+
subject.meta_stack << [1,2,3]
|
70
|
+
end
|
71
|
+
|
72
|
+
it "must pop #meta_stack and reset #stack, and then return the previous #stack value" do
|
73
|
+
expect(subject.pop_meta_stack).to eq([3,4,5])
|
74
|
+
expect(subject.stack).to eq([1,2,3])
|
75
|
+
expect(subject.meta_stack).to eq([])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "#execute" do
|
80
|
+
context "when given a Python::Pickle::Instructions::Proto object" do
|
81
|
+
let(:instruction) { Python::Pickle::Instructions::Proto.new(4) }
|
82
|
+
|
83
|
+
it "must return nil" do
|
84
|
+
expect(subject.execute(instruction)).to be(nil)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "when given a Python::Pickle::Instructions::Frame object" do
|
89
|
+
let(:instruction) { Python::Pickle::Instructions::Frame.new(16) }
|
90
|
+
|
91
|
+
it "must return nil" do
|
92
|
+
expect(subject.execute(instruction)).to be(nil)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
[
|
97
|
+
Python::Pickle::Instructions::Get,
|
98
|
+
Python::Pickle::Instructions::BinGet,
|
99
|
+
Python::Pickle::Instructions::LongBinGet
|
100
|
+
].each do |instruction_class|
|
101
|
+
context "when given a #{instruction_class} object" do
|
102
|
+
let(:instruction1) { instruction_class.new(1) }
|
103
|
+
let(:instruction2) { instruction_class.new(2) }
|
104
|
+
|
105
|
+
before do
|
106
|
+
subject.memo << 'A' << 'B' << 'C'
|
107
|
+
subject.execute(instruction1)
|
108
|
+
subject.execute(instruction2)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "must push the value from #memo at the given index onto #stack" do
|
112
|
+
expect(subject.stack).to eq(['B', 'C'])
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "when given a Python::Pickle::Instructions::MARK object" do
|
118
|
+
let(:instruction) { Python::Pickle::Instructions::MARK }
|
119
|
+
|
120
|
+
before do
|
121
|
+
subject.stack << 1 << 2 << 3
|
122
|
+
subject.execute(instruction)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "must push #stack onto #meta_stack and reset #stack" do
|
126
|
+
expect(subject.meta_stack).to eq([ [1,2,3] ])
|
127
|
+
expect(subject.stack).to eq([])
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context "when given a Python::Pickle::Instructions::POP_MARK object" do
|
132
|
+
let(:instruction) { Python::Pickle::Instructions::POP_MARK }
|
133
|
+
|
134
|
+
before do
|
135
|
+
subject.meta_stack << [1,2,3]
|
136
|
+
subject.stack << 'A' << 'B' << 'C'
|
137
|
+
subject.execute(instruction)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "must push #stack onto #meta_stack and reset #stack" do
|
141
|
+
expect(subject.meta_stack).to eq([])
|
142
|
+
expect(subject.stack).to eq([1,2,3])
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context "when given a Python::Pickle::Instructions::DUP object" do
|
147
|
+
let(:instruction) { Python::Pickle::Instructions::DUP }
|
148
|
+
|
149
|
+
before do
|
150
|
+
subject.stack << 1 << 2 << 3
|
151
|
+
subject.execute(instruction)
|
152
|
+
end
|
153
|
+
|
154
|
+
it "must push a copy of the last element on #stack back onto the #stack" do
|
155
|
+
expect(subject.stack).to eq([1,2,3,3])
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
[
|
160
|
+
Python::Pickle::Instructions::Put,
|
161
|
+
Python::Pickle::Instructions::BinPut
|
162
|
+
].each do |instruction_class|
|
163
|
+
context "when given a #{instruction_class} object" do
|
164
|
+
let(:instruction) { instruction_class.new(1) }
|
165
|
+
|
166
|
+
before do
|
167
|
+
subject.stack << 1 << 2 << 3
|
168
|
+
subject.execute(instruction)
|
169
|
+
end
|
170
|
+
|
171
|
+
it "must set the index in #memo with the last element in the #stack" do
|
172
|
+
expect(subject.memo).to eq([nil, 3])
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context "when given a Python::Pickle::Instructions::POP" do
|
178
|
+
let(:instruction) { Python::Pickle::Instructions::POP }
|
179
|
+
|
180
|
+
before do
|
181
|
+
subject.stack << 1 << 2 << 3
|
182
|
+
subject.execute(instruction)
|
183
|
+
end
|
184
|
+
|
185
|
+
it "must pop the last element off of the #stack" do
|
186
|
+
expect(subject.stack).to eq([1,2])
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context "when given a Python::Pickle::Instructions::MEMOIZE" do
|
191
|
+
let(:instruction) { Python::Pickle::Instructions::MEMOIZE }
|
192
|
+
|
193
|
+
before do
|
194
|
+
subject.stack << 1 << 2 << 3
|
195
|
+
subject.execute(instruction)
|
196
|
+
end
|
197
|
+
|
198
|
+
it "must push the last element of the #stack onto #memo" do
|
199
|
+
expect(subject.memo).to eq([3])
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
[
|
204
|
+
Python::Pickle::Instructions::Ext1,
|
205
|
+
Python::Pickle::Instructions::Ext2,
|
206
|
+
Python::Pickle::Instructions::Ext4
|
207
|
+
].each do |instruction_class|
|
208
|
+
context "when given a #{instruction_class} object" do
|
209
|
+
let(:ext_id) { 0x42 }
|
210
|
+
let(:instruction) { instruction_class.new(ext_id) }
|
211
|
+
|
212
|
+
context "when #extensions contains the extension ID" do
|
213
|
+
let(:ext_object) { double('extension object') }
|
214
|
+
subject do
|
215
|
+
described_class.new(extensions: {ext_id => ext_object})
|
216
|
+
end
|
217
|
+
|
218
|
+
before do
|
219
|
+
subject.stack << 1 << 2 << 3
|
220
|
+
subject.execute(instruction)
|
221
|
+
end
|
222
|
+
|
223
|
+
it "must push the extension object onto the #stack" do
|
224
|
+
expect(subject.stack).to eq([1,2,3, ext_object])
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
context "when #extensions does not contain the extension ID" do
|
229
|
+
it do
|
230
|
+
expect {
|
231
|
+
subject.execute(instruction)
|
232
|
+
}.to raise_error(Python::Pickle::DeserializationError,"unknown extension ID: #{ext_id.inspect}")
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
context "when given a Python::Pickle::Instructions::NONE" do
|
239
|
+
let(:instruction) { Python::Pickle::Instructions::NONE }
|
240
|
+
|
241
|
+
before do
|
242
|
+
subject.execute(instruction)
|
243
|
+
end
|
244
|
+
|
245
|
+
it "must push nil onto the #stack" do
|
246
|
+
expect(subject.stack).to eq([nil])
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context "when given a Python::Pickle::Instructions::NEWTRUE" do
|
251
|
+
let(:instruction) { Python::Pickle::Instructions::NEWTRUE}
|
252
|
+
|
253
|
+
before do
|
254
|
+
subject.execute(instruction)
|
255
|
+
end
|
256
|
+
|
257
|
+
it "must push true onto the #stack" do
|
258
|
+
expect(subject.stack).to eq([true])
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
context "when given a Python::Pickle::Instructions::NEWFALSE" do
|
263
|
+
let(:instruction) { Python::Pickle::Instructions::NEWFALSE}
|
264
|
+
|
265
|
+
before do
|
266
|
+
subject.execute(instruction)
|
267
|
+
end
|
268
|
+
|
269
|
+
it "must push false onto the #stack" do
|
270
|
+
expect(subject.stack).to eq([false])
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
[
|
275
|
+
Python::Pickle::Instructions::Float.new(1.234),
|
276
|
+
Python::Pickle::Instructions::BinFloat.new(1.234),
|
277
|
+
Python::Pickle::Instructions::Int.new(42),
|
278
|
+
Python::Pickle::Instructions::BinInt1.new(42),
|
279
|
+
Python::Pickle::Instructions::Long.new(42),
|
280
|
+
Python::Pickle::Instructions::Long1.new(1,42),
|
281
|
+
Python::Pickle::Instructions::Long4.new(1,42),
|
282
|
+
Python::Pickle::Instructions::BinBytes.new(3,"ABC"),
|
283
|
+
Python::Pickle::Instructions::ShortBinBytes.new(3,"ABC"),
|
284
|
+
Python::Pickle::Instructions::BinBytes8.new(3,"ABC"),
|
285
|
+
Python::Pickle::Instructions::String.new("ABC"),
|
286
|
+
Python::Pickle::Instructions::BinString.new(3,"ABC"),
|
287
|
+
Python::Pickle::Instructions::ShortBinString.new(3,"ABC"),
|
288
|
+
Python::Pickle::Instructions::BinUnicode.new(3,"ABC"),
|
289
|
+
].each do |instruction|
|
290
|
+
context "when given a #{instruction.class} object" do
|
291
|
+
let(:instruction) { instruction }
|
292
|
+
|
293
|
+
before do
|
294
|
+
subject.execute(instruction)
|
295
|
+
end
|
296
|
+
|
297
|
+
it "must push the instruction's value onto the #stack" do
|
298
|
+
expect(subject.stack).to eq([instruction.value])
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
context "when given a Python::Pickle::Instructions::ByteArray8 object" do
|
304
|
+
let(:bytes) { "ABC" }
|
305
|
+
let(:instruction) { Python::Pickle::Instructions::ByteArray8.new(3,bytes) }
|
306
|
+
|
307
|
+
before do
|
308
|
+
subject.execute(instruction)
|
309
|
+
end
|
310
|
+
|
311
|
+
it "must push a Python::Pickle::ByteArray object onto the #stack" do
|
312
|
+
expect(subject.stack).to eq([Python::Pickle::ByteArray.new(bytes)])
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
context "when given a Python::Pickle::Instructions::EMPTY_LIST" do
|
317
|
+
let(:instruction) { Python::Pickle::Instructions::EMPTY_LIST }
|
318
|
+
|
319
|
+
before do
|
320
|
+
subject.execute(instruction)
|
321
|
+
end
|
322
|
+
|
323
|
+
it "must push an empty Array onto the #stack" do
|
324
|
+
expect(subject.stack).to eq([ [] ])
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
context "when given a Python::Pickle::Instructions::EMPTY_TUPLE" do
|
329
|
+
let(:instruction) { Python::Pickle::Instructions::EMPTY_TUPLE }
|
330
|
+
|
331
|
+
before do
|
332
|
+
subject.execute(instruction)
|
333
|
+
end
|
334
|
+
|
335
|
+
it "must push an empty Python::Pickle::Tuple object onto the #stack" do
|
336
|
+
expect(subject.stack).to eq([ Python::Pickle::Tuple.new ])
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
context "when given a Python::Pickle::Instructions::TUPLE " do
|
341
|
+
let(:instruction) { Python::Pickle::Instructions::TUPLE }
|
342
|
+
|
343
|
+
before do
|
344
|
+
subject.stack << 4 << 5 << 6
|
345
|
+
subject.meta_stack << [1,2,3]
|
346
|
+
subject.execute(instruction)
|
347
|
+
end
|
348
|
+
|
349
|
+
it "must pop the #meta_stack, convert the items into a Python::Pickle::Tuple object, and push it onto the #stack" do
|
350
|
+
expect(subject.meta_stack).to eq([])
|
351
|
+
expect(subject.stack).to eq([ 1,2,3, Python::Pickle::Tuple[4,5,6] ])
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
context "when given a Python::Pickle::Instructions::EMPTY_DICT" do
|
356
|
+
let(:instruction) { Python::Pickle::Instructions::EMPTY_DICT }
|
357
|
+
|
358
|
+
before do
|
359
|
+
subject.execute(instruction)
|
360
|
+
end
|
361
|
+
|
362
|
+
it "must push an empty Hash object onto the #stack" do
|
363
|
+
expect(subject.stack).to eq([ {} ])
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
context "when given a Python::Pickle::Instructions::APPEND" do
|
368
|
+
context "and when the previous element on the stack is an Array" do
|
369
|
+
let(:instruction) { Python::Pickle::Instructions::APPEND }
|
370
|
+
|
371
|
+
before do
|
372
|
+
subject.stack << [] << 2
|
373
|
+
subject.execute(instruction)
|
374
|
+
end
|
375
|
+
|
376
|
+
it "must pop the last element from the #stack and push it onto the next list element" do
|
377
|
+
expect(subject.stack).to eq([ [2] ])
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
context "but when the previous element on the stack is not an Array" do
|
382
|
+
let(:instruction) { Python::Pickle::Instructions::APPEND }
|
383
|
+
let(:item) { 2 }
|
384
|
+
let(:list) { "XXX" }
|
385
|
+
|
386
|
+
before do
|
387
|
+
subject.stack << list << item
|
388
|
+
end
|
389
|
+
|
390
|
+
it do
|
391
|
+
expect {
|
392
|
+
subject.execute(instruction)
|
393
|
+
}.to raise_error(Python::Pickle::DeserializationError,"cannot append element #{item.inspect} onto a non-Array: #{list.inspect}")
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
context "when given a Python::Pickle::Instructions::APPENDS" do
|
399
|
+
context "and when the previous element on the stack is an Array" do
|
400
|
+
let(:instruction) { Python::Pickle::Instructions::APPENDS }
|
401
|
+
|
402
|
+
before do
|
403
|
+
subject.meta_stack << [ [1,2,3] ]
|
404
|
+
subject.stack << 4 << 5 << 6
|
405
|
+
subject.execute(instruction)
|
406
|
+
end
|
407
|
+
|
408
|
+
it "must pop the #meta_stack, store the #stack, and concat the previous #stack onto the last element of the new #stack" do
|
409
|
+
expect(subject.stack).to eq([ [1,2,3,4,5,6] ])
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
context "but when the previous element on the stack is not an Array" do
|
414
|
+
let(:instruction) { Python::Pickle::Instructions::APPENDS }
|
415
|
+
let(:items) { [3,4,5] }
|
416
|
+
let(:list) { "XXX" }
|
417
|
+
|
418
|
+
before do
|
419
|
+
subject.meta_stack << [ list ]
|
420
|
+
subject.stack << items[0] << items[1] << items[2]
|
421
|
+
end
|
422
|
+
|
423
|
+
it do
|
424
|
+
expect {
|
425
|
+
subject.execute(instruction)
|
426
|
+
}.to raise_error(Python::Pickle::DeserializationError,"cannot append elements #{items.inspect} onto a non-Array: #{list.inspect}")
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
context "when given a Python::Pickle::Instructions::LIST" do
|
432
|
+
let(:instruction) { Python::Pickle::Instructions::LIST }
|
433
|
+
|
434
|
+
before do
|
435
|
+
subject.meta_stack << [ [1,2,3] ]
|
436
|
+
subject.stack << 4 << 5 << 6
|
437
|
+
subject.execute(instruction)
|
438
|
+
end
|
439
|
+
|
440
|
+
it "must pop the #meta_stack, restore the #stack, and push the previous #stack onto the new #stack as a new Array" do
|
441
|
+
expect(subject.stack).to eq([ [1,2,3], [4,5,6] ])
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
context "when given a Python::Pickle::Instructions::TUPLE1" do
|
446
|
+
let(:instruction) { Python::Pickle::Instructions::TUPLE1 }
|
447
|
+
|
448
|
+
before do
|
449
|
+
subject.stack << 1 << 2 << 3
|
450
|
+
subject.execute(instruction)
|
451
|
+
end
|
452
|
+
|
453
|
+
it "must pop one element from the #stack nad push a new Python::Pickle::Tuple onto the #stack" do
|
454
|
+
expect(subject.stack).to eq([ 1, 2, Python::Pickle::Tuple[3] ])
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
context "when given a Python::Pickle::Instructions::TUPLE2" do
|
459
|
+
let(:instruction) { Python::Pickle::Instructions::TUPLE2 }
|
460
|
+
|
461
|
+
before do
|
462
|
+
subject.stack << 1 << 2 << 3
|
463
|
+
subject.execute(instruction)
|
464
|
+
end
|
465
|
+
|
466
|
+
it "must pop two elements from the #stack nad push a new Python::Pickle::Tuple onto the #stack" do
|
467
|
+
expect(subject.stack).to eq([ 1, Python::Pickle::Tuple[2, 3] ])
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
context "when given a Python::Pickle::Instructions::TUPLE3" do
|
472
|
+
let(:instruction) { Python::Pickle::Instructions::TUPLE3 }
|
473
|
+
|
474
|
+
before do
|
475
|
+
subject.stack << 1 << 2 << 3
|
476
|
+
subject.execute(instruction)
|
477
|
+
end
|
478
|
+
|
479
|
+
it "must pop three elements from the #stack nad push a new Python::Pickle::Tuple onto the #stack" do
|
480
|
+
expect(subject.stack).to eq([ Python::Pickle::Tuple[1, 2, 3] ])
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
context "when given a Python::Pickle::Instructions::DICT" do
|
485
|
+
let(:instruction) { Python::Pickle::Instructions::DICT }
|
486
|
+
|
487
|
+
before do
|
488
|
+
subject.meta_stack << []
|
489
|
+
subject.stack << 'a' << 1 << 'b' << 2
|
490
|
+
subject.execute(instruction)
|
491
|
+
end
|
492
|
+
|
493
|
+
it "must pop the #meta_stack, create a new Hash using the key:value pairs on the previous #stack, and push the new Hash onto the new #stack" do
|
494
|
+
expect(subject.stack).to eq([ {'a' => 1, 'b' => 2} ])
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
context "when given a Python::Pickle::Instructions::Global object" do
|
499
|
+
let(:namespace) { '__main__' }
|
500
|
+
let(:name) { 'MyClass' }
|
501
|
+
let(:instruction) { Python::Pickle::Instructions::Global.new(namespace,name) }
|
502
|
+
|
503
|
+
before do
|
504
|
+
subject.execute(instruction)
|
505
|
+
end
|
506
|
+
|
507
|
+
context "when the constant can be resolved" do
|
508
|
+
module TestGlobalInstruction
|
509
|
+
class MyClass
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
subject do
|
514
|
+
described_class.new(
|
515
|
+
constants: {
|
516
|
+
'__main__' => {
|
517
|
+
'MyClass' => TestGlobalInstruction::MyClass
|
518
|
+
}
|
519
|
+
}
|
520
|
+
)
|
521
|
+
end
|
522
|
+
|
523
|
+
it "must push the constant onto the #stack" do
|
524
|
+
expect(subject.stack).to eq([ TestGlobalInstruction::MyClass ])
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
context "but the constant cannot be resolved" do
|
529
|
+
it "must push a new Python::Pickle::PyClass object onto the #stack" do
|
530
|
+
constant = subject.stack[-1]
|
531
|
+
|
532
|
+
expect(constant).to be_kind_of(Python::Pickle::PyClass)
|
533
|
+
expect(constant.namespace).to eq(namespace)
|
534
|
+
expect(constant.name).to eq(name)
|
535
|
+
end
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
context "when given a Python::Pickle::Instructions::STACK_GLOBAL" do
|
540
|
+
let(:namespace) { '__main__' }
|
541
|
+
let(:name) { 'MyClass' }
|
542
|
+
let(:instruction) { Python::Pickle::Instructions::STACK_GLOBAL }
|
543
|
+
|
544
|
+
before do
|
545
|
+
subject.stack << namespace << name
|
546
|
+
subject.execute(instruction)
|
547
|
+
end
|
548
|
+
|
549
|
+
context "when the constant can be resolved" do
|
550
|
+
module TestStackGlobalInstruction
|
551
|
+
class MyClass
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
subject do
|
556
|
+
described_class.new(
|
557
|
+
constants: {
|
558
|
+
'__main__' => {
|
559
|
+
'MyClass' => TestStackGlobalInstruction::MyClass
|
560
|
+
}
|
561
|
+
}
|
562
|
+
)
|
563
|
+
end
|
564
|
+
|
565
|
+
it "must pop off the namespace and name from the #stack, and push the constant onto the #stack" do
|
566
|
+
expect(subject.stack).to eq([ TestStackGlobalInstruction::MyClass ])
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
context "but the constant cannot be resolved" do
|
571
|
+
it "must push a new Python::Pickle::PyClass object onto the #stack" do
|
572
|
+
constant = subject.stack[-1]
|
573
|
+
|
574
|
+
expect(constant).to be_kind_of(Python::Pickle::PyClass)
|
575
|
+
expect(constant.namespace).to eq(namespace)
|
576
|
+
expect(constant.name).to eq(name)
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
context "when given a Python::Pickle::Instructions::NEWOBJ" do
|
582
|
+
let(:instruction) { Python::Pickle::Instructions::NEWOBJ }
|
583
|
+
|
584
|
+
context "and when the constant on the #stack is a Ruby class" do
|
585
|
+
module TestNewObjInstruction
|
586
|
+
class MyClass
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
context "and the second argument is nil" do
|
591
|
+
before do
|
592
|
+
subject.stack << TestNewObjInstruction::MyClass << nil
|
593
|
+
subject.execute(instruction)
|
594
|
+
end
|
595
|
+
|
596
|
+
it "must pop off the two last elements, and initialize a new instance of the constant, and push the new instance onto the #stack" do
|
597
|
+
expect(subject.stack.length).to eq(1)
|
598
|
+
expect(subject.stack[-1]).to be_kind_of(TestNewObjInstruction::MyClass)
|
599
|
+
end
|
600
|
+
end
|
601
|
+
|
602
|
+
context "and the second argument is Python::Pickle::Tuple" do
|
603
|
+
context "but it's empty" do
|
604
|
+
let(:tuple) { Python::Pickle::Tuple.new }
|
605
|
+
|
606
|
+
before do
|
607
|
+
subject.stack << TestNewObjInstruction::MyClass << tuple
|
608
|
+
subject.execute(instruction)
|
609
|
+
end
|
610
|
+
|
611
|
+
it "must pop off the two last elements, and initialize a new instance of the constant, and push the new instance onto the #stack" do
|
612
|
+
expect(subject.stack.length).to eq(1)
|
613
|
+
expect(subject.stack[-1]).to be_kind_of(TestNewObjInstruction::MyClass)
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
context "but it's not empty" do
|
618
|
+
module TestNewObjInstruction
|
619
|
+
class MyClassWithArgs
|
620
|
+
attr_reader :x, :y
|
621
|
+
|
622
|
+
def initialize(x,y)
|
623
|
+
@x = x
|
624
|
+
@y = y
|
625
|
+
end
|
626
|
+
end
|
627
|
+
end
|
628
|
+
|
629
|
+
let(:tuple) { Python::Pickle::Tuple[1,2] }
|
630
|
+
|
631
|
+
before do
|
632
|
+
subject.stack << TestNewObjInstruction::MyClassWithArgs << tuple
|
633
|
+
subject.execute(instruction)
|
634
|
+
end
|
635
|
+
|
636
|
+
it "must call #initialize with the splatted tuple's arguments" do
|
637
|
+
object = subject.stack[-1]
|
638
|
+
|
639
|
+
expect(object.x).to eq(tuple[0])
|
640
|
+
expect(object.y).to eq(tuple[1])
|
641
|
+
end
|
642
|
+
end
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
context "and when the constant on the #stack is a PyClass" do
|
647
|
+
let(:namespace) { '__main__' }
|
648
|
+
let(:name) { 'MyClass' }
|
649
|
+
let(:py_class) { Python::Pickle::PyClass.new(namespace,name) }
|
650
|
+
|
651
|
+
context "and the second argument is nil" do
|
652
|
+
before do
|
653
|
+
subject.stack << py_class << nil
|
654
|
+
subject.execute(instruction)
|
655
|
+
end
|
656
|
+
|
657
|
+
it "must pop off the two last elements and push the new Python::Pickle::PyObject onto the #stack" do
|
658
|
+
expect(subject.stack.length).to eq(1)
|
659
|
+
expect(subject.stack[-1]).to be_kind_of(Python::Pickle::PyObject)
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
context "and the second argument is Python::Pickle::Tuple" do
|
664
|
+
context "but it's empty" do
|
665
|
+
let(:tuple) { Python::Pickle::Tuple.new }
|
666
|
+
|
667
|
+
before do
|
668
|
+
subject.stack << py_class << tuple
|
669
|
+
subject.execute(instruction)
|
670
|
+
end
|
671
|
+
|
672
|
+
it "must pop off the two last elements and push the new Python::Pickle::PyObject onto the #stack" do
|
673
|
+
expect(subject.stack.length).to eq(1)
|
674
|
+
expect(subject.stack[-1]).to be_kind_of(Python::Pickle::PyObject)
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
context "but it's not empty" do
|
679
|
+
let(:tuple) { Python::Pickle::Tuple[1,2] }
|
680
|
+
|
681
|
+
before do
|
682
|
+
subject.stack << py_class << tuple
|
683
|
+
subject.execute(instruction)
|
684
|
+
end
|
685
|
+
|
686
|
+
it "must set the object's #init_args to the tuple's elements" do
|
687
|
+
object = subject.stack[-1]
|
688
|
+
|
689
|
+
expect(object.init_args).to eq(tuple)
|
690
|
+
end
|
691
|
+
end
|
692
|
+
end
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
context "when given a Python::Pickle::Instructions::NEWOBJ_EX" do
|
697
|
+
let(:instruction) { Python::Pickle::Instructions::NEWOBJ_EX }
|
698
|
+
|
699
|
+
context "and when the constant on the #stack is a Ruby class" do
|
700
|
+
module TestNewObjExInstruction
|
701
|
+
class MyClass
|
702
|
+
end
|
703
|
+
end
|
704
|
+
|
705
|
+
context "and the second argument is nil" do
|
706
|
+
before do
|
707
|
+
subject.stack << TestNewObjExInstruction::MyClass << nil << nil
|
708
|
+
subject.execute(instruction)
|
709
|
+
end
|
710
|
+
|
711
|
+
it "must pop off the two last elements, and initialize a new instance of the constant, and push the new instance onto the #stack" do
|
712
|
+
expect(subject.stack.length).to eq(1)
|
713
|
+
expect(subject.stack[-1]).to be_kind_of(TestNewObjExInstruction::MyClass)
|
714
|
+
end
|
715
|
+
end
|
716
|
+
|
717
|
+
context "and the second argument is Python::Pickle::Tuple" do
|
718
|
+
context "but it's empty" do
|
719
|
+
let(:tuple) { Python::Pickle::Tuple.new }
|
720
|
+
|
721
|
+
before do
|
722
|
+
subject.stack << TestNewObjExInstruction::MyClass << tuple << nil
|
723
|
+
subject.execute(instruction)
|
724
|
+
end
|
725
|
+
|
726
|
+
it "must pop off the two last elements, and initialize a new instance of the constant, and push the new instance onto the #stack" do
|
727
|
+
expect(subject.stack.length).to eq(1)
|
728
|
+
expect(subject.stack[-1]).to be_kind_of(TestNewObjExInstruction::MyClass)
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
context "but it's not empty" do
|
733
|
+
module TestNewObjExInstruction
|
734
|
+
class MyClassWithArgs
|
735
|
+
attr_reader :x, :y
|
736
|
+
|
737
|
+
def initialize(x,y)
|
738
|
+
@x = x
|
739
|
+
@y = y
|
740
|
+
end
|
741
|
+
end
|
742
|
+
end
|
743
|
+
|
744
|
+
let(:tuple) { Python::Pickle::Tuple[1,2] }
|
745
|
+
|
746
|
+
before do
|
747
|
+
subject.stack << TestNewObjExInstruction::MyClassWithArgs << tuple << nil
|
748
|
+
subject.execute(instruction)
|
749
|
+
end
|
750
|
+
|
751
|
+
it "must call #initialize with the splatted tuple's arguments" do
|
752
|
+
object = subject.stack[-1]
|
753
|
+
|
754
|
+
expect(object.x).to eq(tuple[0])
|
755
|
+
expect(object.y).to eq(tuple[1])
|
756
|
+
end
|
757
|
+
end
|
758
|
+
end
|
759
|
+
|
760
|
+
context "and the third argument is nil" do
|
761
|
+
before do
|
762
|
+
subject.stack << TestNewObjExInstruction::MyClass << [] << nil
|
763
|
+
subject.execute(instruction)
|
764
|
+
end
|
765
|
+
|
766
|
+
it "must pop off the two last elements, and initialize a new instance of the constant, and push the new instance onto the #stack" do
|
767
|
+
expect(subject.stack.length).to eq(1)
|
768
|
+
expect(subject.stack[-1]).to be_kind_of(TestNewObjExInstruction::MyClass)
|
769
|
+
end
|
770
|
+
end
|
771
|
+
|
772
|
+
context "and the third argument is a Hash" do
|
773
|
+
context "but it's empty" do
|
774
|
+
let(:tuple) { Python::Pickle::Tuple.new }
|
775
|
+
|
776
|
+
before do
|
777
|
+
subject.stack << TestNewObjExInstruction::MyClass << [] << {}
|
778
|
+
subject.execute(instruction)
|
779
|
+
end
|
780
|
+
|
781
|
+
it "must pop off the two last elements, and initialize a new instance of the constant, and push the new instance onto the #stack" do
|
782
|
+
expect(subject.stack.length).to eq(1)
|
783
|
+
expect(subject.stack[-1]).to be_kind_of(TestNewObjExInstruction::MyClass)
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
context "but it's not empty" do
|
788
|
+
module TestNewObjExInstruction
|
789
|
+
class MyClassWithKWArgs
|
790
|
+
attr_reader :x, :y
|
791
|
+
|
792
|
+
def initialize(x: , y: )
|
793
|
+
@x = x
|
794
|
+
@y = y
|
795
|
+
end
|
796
|
+
end
|
797
|
+
end
|
798
|
+
|
799
|
+
let(:hash) { {"x" => 1, "y" => 2} }
|
800
|
+
|
801
|
+
before do
|
802
|
+
subject.stack << TestNewObjExInstruction::MyClassWithKWArgs << [] << hash
|
803
|
+
subject.execute(instruction)
|
804
|
+
end
|
805
|
+
|
806
|
+
it "must call #initialize with the splatted tuple's arguments" do
|
807
|
+
object = subject.stack[-1]
|
808
|
+
|
809
|
+
expect(object.x).to eq(hash['x'])
|
810
|
+
expect(object.y).to eq(hash['y'])
|
811
|
+
end
|
812
|
+
end
|
813
|
+
end
|
814
|
+
end
|
815
|
+
|
816
|
+
context "and when the constant on the #stack is a PyClass" do
|
817
|
+
let(:namespace) { '__main__' }
|
818
|
+
let(:name) { 'MyClass' }
|
819
|
+
let(:py_class) { Python::Pickle::PyClass.new(namespace,name) }
|
820
|
+
|
821
|
+
context "and the second argument is nil" do
|
822
|
+
before do
|
823
|
+
subject.stack << py_class << nil << nil
|
824
|
+
subject.execute(instruction)
|
825
|
+
end
|
826
|
+
|
827
|
+
it "must pop off the two last elements and push the new Python::Pickle::PyObject onto the #stack" do
|
828
|
+
expect(subject.stack.length).to eq(1)
|
829
|
+
expect(subject.stack[-1]).to be_kind_of(Python::Pickle::PyObject)
|
830
|
+
end
|
831
|
+
end
|
832
|
+
|
833
|
+
context "and the second argument is Python::Pickle::Tuple" do
|
834
|
+
context "but it's empty" do
|
835
|
+
let(:tuple) { Python::Pickle::Tuple.new }
|
836
|
+
|
837
|
+
before do
|
838
|
+
subject.stack << py_class << tuple << nil
|
839
|
+
subject.execute(instruction)
|
840
|
+
end
|
841
|
+
|
842
|
+
it "must pop off the two last elements and push the new Python::Pickle::PyObject onto the #stack" do
|
843
|
+
expect(subject.stack.length).to eq(1)
|
844
|
+
expect(subject.stack[-1]).to be_kind_of(Python::Pickle::PyObject)
|
845
|
+
end
|
846
|
+
end
|
847
|
+
|
848
|
+
context "but it's not empty" do
|
849
|
+
let(:tuple) { Python::Pickle::Tuple[1,2] }
|
850
|
+
|
851
|
+
before do
|
852
|
+
subject.stack << py_class << tuple << nil
|
853
|
+
subject.execute(instruction)
|
854
|
+
end
|
855
|
+
|
856
|
+
it "must set the object's #init_args to the tuple's elements" do
|
857
|
+
object = subject.stack[-1]
|
858
|
+
|
859
|
+
expect(object.init_args).to eq(tuple)
|
860
|
+
end
|
861
|
+
end
|
862
|
+
end
|
863
|
+
end
|
864
|
+
end
|
865
|
+
|
866
|
+
context "when given a Python::Pickle::Instructions::REDUCE" do
|
867
|
+
let(:instruction) { Python::Pickle::Instructions::REDUCE }
|
868
|
+
|
869
|
+
context "when the first argument on the #stack is a PyClass" do
|
870
|
+
let(:namespace) { '__main__' }
|
871
|
+
let(:name) { 'MyClass' }
|
872
|
+
let(:py_class) { Python::Pickle::PyClass.new(namespace,name) }
|
873
|
+
|
874
|
+
context "and the second argument is nil" do
|
875
|
+
before do
|
876
|
+
subject.stack << py_class << nil
|
877
|
+
subject.execute(instruction)
|
878
|
+
end
|
879
|
+
|
880
|
+
it "must pop two elements off of the #stack, create a PyObject from the PyClass, and push the new PyObject onto the #stack" do
|
881
|
+
expect(subject.stack.length).to eq(1)
|
882
|
+
expect(subject.stack[-1]).to be_kind_of(Python::Pickle::PyObject)
|
883
|
+
expect(subject.stack[-1].py_class).to eq(py_class)
|
884
|
+
end
|
885
|
+
end
|
886
|
+
|
887
|
+
context "and the second argument is Python::Pickle::Tuple" do
|
888
|
+
context "but it's empty" do
|
889
|
+
let(:tuple) { Python::Pickle::Tuple.new }
|
890
|
+
|
891
|
+
before do
|
892
|
+
subject.stack << py_class << tuple
|
893
|
+
subject.execute(instruction)
|
894
|
+
end
|
895
|
+
|
896
|
+
it "must pop two elements off of the #stack, create a PyObject from the PyClass, and push the new PyObject onto the #stack" do
|
897
|
+
expect(subject.stack.length).to eq(1)
|
898
|
+
expect(subject.stack[-1]).to be_kind_of(Python::Pickle::PyObject)
|
899
|
+
expect(subject.stack[-1].py_class).to eq(py_class)
|
900
|
+
end
|
901
|
+
end
|
902
|
+
|
903
|
+
context "but it's not empty" do
|
904
|
+
let(:tuple) { Python::Pickle::Tuple[1,2] }
|
905
|
+
|
906
|
+
before do
|
907
|
+
subject.stack << py_class << tuple
|
908
|
+
subject.execute(instruction)
|
909
|
+
end
|
910
|
+
|
911
|
+
it "must set #init_args" do
|
912
|
+
object = subject.stack[-1]
|
913
|
+
|
914
|
+
expect(object.init_args).to eq( [tuple[0], tuple[1]] )
|
915
|
+
end
|
916
|
+
end
|
917
|
+
end
|
918
|
+
end
|
919
|
+
|
920
|
+
context "when the first argument on the #stack is a Ruby class" do
|
921
|
+
module TestReduceInstruction
|
922
|
+
class MyClass
|
923
|
+
end
|
924
|
+
end
|
925
|
+
|
926
|
+
context "and the second argument is nil" do
|
927
|
+
before do
|
928
|
+
subject.stack << TestReduceInstruction::MyClass << nil
|
929
|
+
subject.execute(instruction)
|
930
|
+
end
|
931
|
+
|
932
|
+
it "must pop two elements off of the #stack, initialize the Ruby class, and push the new instance onto the #stack" do
|
933
|
+
expect(subject.stack.length).to eq(1)
|
934
|
+
expect(subject.stack[-1]).to be_kind_of(TestReduceInstruction::MyClass)
|
935
|
+
end
|
936
|
+
end
|
937
|
+
|
938
|
+
context "and the second argument is Python::Pickle::Tuple" do
|
939
|
+
context "but it's empty" do
|
940
|
+
let(:tuple) { Python::Pickle::Tuple.new }
|
941
|
+
|
942
|
+
before do
|
943
|
+
subject.stack << TestReduceInstruction::MyClass << tuple
|
944
|
+
subject.execute(instruction)
|
945
|
+
end
|
946
|
+
|
947
|
+
it "must pop two elements off of the #stack, initialize the Ruby class, and push the new instance onto the #stack" do
|
948
|
+
expect(subject.stack.length).to eq(1)
|
949
|
+
expect(subject.stack[-1]).to be_kind_of(TestReduceInstruction::MyClass)
|
950
|
+
end
|
951
|
+
end
|
952
|
+
|
953
|
+
context "but it's not empty" do
|
954
|
+
module TestReduceInstruction
|
955
|
+
class MyClassWithArgs
|
956
|
+
attr_reader :x, :y
|
957
|
+
|
958
|
+
def initialize(x,y)
|
959
|
+
@x = x
|
960
|
+
@y = y
|
961
|
+
end
|
962
|
+
end
|
963
|
+
end
|
964
|
+
|
965
|
+
let(:tuple) { Python::Pickle::Tuple[1,2] }
|
966
|
+
|
967
|
+
before do
|
968
|
+
subject.stack << TestReduceInstruction::MyClassWithArgs << tuple
|
969
|
+
subject.execute(instruction)
|
970
|
+
end
|
971
|
+
|
972
|
+
it "must call #initialize with the arguments of the tuple" do
|
973
|
+
object = subject.stack[-1]
|
974
|
+
|
975
|
+
expect(object.x).to eq(tuple[0])
|
976
|
+
expect(object.y).to eq(tuple[1])
|
977
|
+
end
|
978
|
+
end
|
979
|
+
end
|
980
|
+
end
|
981
|
+
|
982
|
+
context "but the first argument on the #stack is a Ruby Method" do
|
983
|
+
module TestReduceInstruction
|
984
|
+
def self.func(x,y)
|
985
|
+
x + y
|
986
|
+
end
|
987
|
+
end
|
988
|
+
|
989
|
+
let(:tuple) { Python::Pickle::Tuple[1,2] }
|
990
|
+
|
991
|
+
before do
|
992
|
+
subject.stack << TestReduceInstruction.method(:func) << tuple
|
993
|
+
subject.execute(instruction)
|
994
|
+
end
|
995
|
+
|
996
|
+
it "must pop the two arguments off of the stack, call the Method with the tuple arguments, and push the result back onto the #stack" do
|
997
|
+
expect(subject.stack.length).to eq(1)
|
998
|
+
expect(subject.stack[-1]).to eq(tuple[0] + tuple[1])
|
999
|
+
end
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
context "when the first argument on the #stack is not a Class or a Method" do
|
1003
|
+
let(:callable) { Object.new }
|
1004
|
+
let(:tuple) { Python::Pickle::Tuple[1,2] }
|
1005
|
+
|
1006
|
+
before do
|
1007
|
+
subject.stack << callable << nil
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
it do
|
1011
|
+
expect {
|
1012
|
+
subject.execute(instruction)
|
1013
|
+
}.to raise_error(Python::Pickle::DeserializationError,"cannot execute REDUCE on a non-class: #{callable.inspect}")
|
1014
|
+
end
|
1015
|
+
end
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
context "when given a Python::Pickle::Instructions::BUILD" do
|
1019
|
+
let(:instruction) { Python::Pickle::Instructions::BUILD }
|
1020
|
+
|
1021
|
+
context "when the first argument on the #stack is a Hash" do
|
1022
|
+
let(:hash1) { {'x' => 1} }
|
1023
|
+
let(:hash2) { {'y' => 2} }
|
1024
|
+
|
1025
|
+
before do
|
1026
|
+
subject.stack << hash1 << hash2
|
1027
|
+
subject.execute(instruction)
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
it "must pop the last element off the #stack and merge the other Hash into the first Hash" do
|
1031
|
+
expect(subject.stack.length).to eq(1)
|
1032
|
+
expect(subject.stack[-1]).to eq(hash1.merge(hash2))
|
1033
|
+
end
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
context "when the first argument on the #stack is an Object" do
|
1037
|
+
context "and it defines a __setstate__ method" do
|
1038
|
+
module TestBuildInstruction
|
1039
|
+
class MyClass
|
1040
|
+
attr_reader :x, :y
|
1041
|
+
|
1042
|
+
def __setstate__(attributes)
|
1043
|
+
@x = attributes['x']
|
1044
|
+
@y = attributes['y']
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
let(:object) { TestBuildInstruction::MyClass.new }
|
1050
|
+
let(:args) { {'x' => 1, 'y' => 2} }
|
1051
|
+
|
1052
|
+
before do
|
1053
|
+
subject.stack << object << args
|
1054
|
+
subject.execute(instruction)
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
it "must pop the last element off the #stack, call the #__setstate__ method on the first element on the #stack" do
|
1058
|
+
expect(subject.stack.length).to eq(1)
|
1059
|
+
expect(object.x).to eq(args['x'])
|
1060
|
+
expect(object.y).to eq(args['y'])
|
1061
|
+
end
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
context "but it does not define a __setstate__ method" do
|
1065
|
+
let(:object) { Object.new }
|
1066
|
+
let(:args) { {'x' => 1, 'y' => 2} }
|
1067
|
+
|
1068
|
+
before do
|
1069
|
+
subject.stack << object << args
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
it do
|
1073
|
+
expect {
|
1074
|
+
subject.execute(instruction)
|
1075
|
+
}.to raise_error(Python::Pickle::DeserializationError,"cannot execute BUILD on an object that does not define a __setstate__ method or is not a Hash: #{object.inspect}")
|
1076
|
+
end
|
1077
|
+
end
|
1078
|
+
end
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
context "when given a Python::Pickle::Instructions::SETITEM" do
|
1082
|
+
let(:instruction) { Python::Pickle::Instructions::SETITEM }
|
1083
|
+
|
1084
|
+
let(:key) { 'x' }
|
1085
|
+
let(:value) { 1 }
|
1086
|
+
|
1087
|
+
context "and the first argument on the #stack is a Hash" do
|
1088
|
+
let(:hash) { {} }
|
1089
|
+
|
1090
|
+
before do
|
1091
|
+
subject.stack << hash << key << value
|
1092
|
+
subject.execute(instruction)
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
it "must pop two elements off the #stack, and set the key vand value in the last element on the #stack" do
|
1096
|
+
expect(subject.stack).to eq([ {key => value} ])
|
1097
|
+
end
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
context "and the first argument on the #stack is not a Hash" do
|
1101
|
+
let(:object) { Object.new }
|
1102
|
+
|
1103
|
+
before do
|
1104
|
+
subject.stack << object << key << value
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
it do
|
1108
|
+
expect {
|
1109
|
+
subject.execute(instruction)
|
1110
|
+
}.to raise_error(Python::Pickle::DeserializationError,"cannot set key (#{key.inspect}) and value (#{value.inspect}) into non-Hash: #{object.inspect}")
|
1111
|
+
end
|
1112
|
+
end
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
context "when given a Python::Pickle::Instructions::SETITEMS" do
|
1116
|
+
let(:instruction) { Python::Pickle::Instructions::SETITEMS }
|
1117
|
+
|
1118
|
+
let(:key1) { 'x' }
|
1119
|
+
let(:value1) { 1 }
|
1120
|
+
let(:key2) { 'y' }
|
1121
|
+
let(:value2) { 2 }
|
1122
|
+
|
1123
|
+
context "and the first argument on the #stack is a Hash" do
|
1124
|
+
let(:hash) { {} }
|
1125
|
+
|
1126
|
+
before do
|
1127
|
+
subject.meta_stack << [hash]
|
1128
|
+
subject.stack << key1 << value1 << key2 << value2
|
1129
|
+
subject.execute(instruction)
|
1130
|
+
end
|
1131
|
+
|
1132
|
+
it "must pop the #meta_stack and use the previous stack's values to populate the Hash at the end of the #stack" do
|
1133
|
+
expect(subject.stack).to eq([ {key1 => value1, key2 => value2} ])
|
1134
|
+
end
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
context "and the first argument on the #stack is not a Hash" do
|
1138
|
+
let(:object) { Object.new }
|
1139
|
+
let(:pairs) { [key1, value1, key2, value2] }
|
1140
|
+
|
1141
|
+
before do
|
1142
|
+
subject.meta_stack << [object]
|
1143
|
+
subject.stack << key1 << value1 << key2 << value2
|
1144
|
+
end
|
1145
|
+
|
1146
|
+
it do
|
1147
|
+
expect {
|
1148
|
+
subject.execute(instruction)
|
1149
|
+
}.to raise_error(Python::Pickle::DeserializationError,"cannot set key value pairs (#{pairs.inspect}) into non-Hash: #{object.inspect}")
|
1150
|
+
end
|
1151
|
+
end
|
1152
|
+
end
|
1153
|
+
end
|
1154
|
+
|
1155
|
+
describe "#copyreg_reconstructor" do
|
1156
|
+
let(:klass) { double('class') }
|
1157
|
+
let(:super_class) { double('super class') }
|
1158
|
+
let(:instance) { double('instance of class') }
|
1159
|
+
|
1160
|
+
context "when the initialization argument is nil" do
|
1161
|
+
it "must call .new on the given class with no arguments" do
|
1162
|
+
expect(klass).to receive(:new).with(no_args).and_return(instance)
|
1163
|
+
|
1164
|
+
expect(subject.copyreg_reconstructor(klass,super_class,nil)).to be(instance)
|
1165
|
+
end
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
context "when the initialization argument is not nil" do
|
1169
|
+
let(:value1) { 1 }
|
1170
|
+
let(:value2) { 2 }
|
1171
|
+
let(:init_arg) { Python::Pickle::Tuple[value1, value2] }
|
1172
|
+
|
1173
|
+
it "must call .new on the given class with the given initialization argument" do
|
1174
|
+
expect(klass).to receive(:new).with(value1,value2).and_return(instance)
|
1175
|
+
|
1176
|
+
expect(subject.copyreg_reconstructor(klass,super_class,init_arg)).to be(instance)
|
1177
|
+
end
|
1178
|
+
end
|
1179
|
+
end
|
1180
|
+
|
1181
|
+
describe "#resolv_constant" do
|
1182
|
+
it "must lookup the constant with the given name in the given namespace" do
|
1183
|
+
expect(subject.resolve_constant('__builtin__','object')).to eq(
|
1184
|
+
subject.constants.fetch('__builtin__').fetch('object')
|
1185
|
+
)
|
1186
|
+
end
|
1187
|
+
|
1188
|
+
context "when the constant does not exist in #constants" do
|
1189
|
+
let(:namespace) { '__main__' }
|
1190
|
+
let(:name) { 'object' }
|
1191
|
+
|
1192
|
+
it "must return a new Python::Pickle::PyClass instance with the given namespace and name" do
|
1193
|
+
constant = subject.resolve_constant(namespace,name)
|
1194
|
+
|
1195
|
+
expect(constant).to be_kind_of(Python::Pickle::PyClass)
|
1196
|
+
expect(constant.namespace).to eq(namespace)
|
1197
|
+
expect(constant.name).to eq(name)
|
1198
|
+
end
|
1199
|
+
end
|
1200
|
+
end
|
1201
|
+
end
|