python-pickle 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (235) hide show
  1. checksums.yaml +7 -0
  2. data/.document +3 -0
  3. data/.github/workflows/ruby.yml +27 -0
  4. data/.gitignore +5 -0
  5. data/.rspec +1 -0
  6. data/.yardopts +1 -0
  7. data/ChangeLog.md +14 -0
  8. data/Gemfile +15 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.md +149 -0
  11. data/Rakefile +13 -0
  12. data/gemspec.yml +25 -0
  13. data/lib/python/pickle/byte_array.rb +40 -0
  14. data/lib/python/pickle/deserializer.rb +595 -0
  15. data/lib/python/pickle/exceptions.rb +12 -0
  16. data/lib/python/pickle/instruction.rb +52 -0
  17. data/lib/python/pickle/instructions/add_items.rb +26 -0
  18. data/lib/python/pickle/instructions/append.rb +24 -0
  19. data/lib/python/pickle/instructions/appends.rb +26 -0
  20. data/lib/python/pickle/instructions/bin_bytes.rb +32 -0
  21. data/lib/python/pickle/instructions/bin_bytes8.rb +32 -0
  22. data/lib/python/pickle/instructions/bin_float.rb +29 -0
  23. data/lib/python/pickle/instructions/bin_get.rb +27 -0
  24. data/lib/python/pickle/instructions/bin_int1.rb +29 -0
  25. data/lib/python/pickle/instructions/bin_put.rb +29 -0
  26. data/lib/python/pickle/instructions/bin_string.rb +32 -0
  27. data/lib/python/pickle/instructions/bin_unicode.rb +32 -0
  28. data/lib/python/pickle/instructions/bin_unicode8.rb +32 -0
  29. data/lib/python/pickle/instructions/build.rb +24 -0
  30. data/lib/python/pickle/instructions/byte_array8.rb +32 -0
  31. data/lib/python/pickle/instructions/dict.rb +17 -0
  32. data/lib/python/pickle/instructions/dup.rb +24 -0
  33. data/lib/python/pickle/instructions/empty_dict.rb +26 -0
  34. data/lib/python/pickle/instructions/empty_list.rb +26 -0
  35. data/lib/python/pickle/instructions/empty_set.rb +26 -0
  36. data/lib/python/pickle/instructions/empty_tuple.rb +26 -0
  37. data/lib/python/pickle/instructions/ext1.rb +29 -0
  38. data/lib/python/pickle/instructions/ext2.rb +29 -0
  39. data/lib/python/pickle/instructions/ext4.rb +29 -0
  40. data/lib/python/pickle/instructions/float.rb +24 -0
  41. data/lib/python/pickle/instructions/frame.rb +29 -0
  42. data/lib/python/pickle/instructions/frozen_set.rb +26 -0
  43. data/lib/python/pickle/instructions/get.rb +27 -0
  44. data/lib/python/pickle/instructions/global.rb +62 -0
  45. data/lib/python/pickle/instructions/has_length_and_value.rb +58 -0
  46. data/lib/python/pickle/instructions/has_value.rb +50 -0
  47. data/lib/python/pickle/instructions/int.rb +24 -0
  48. data/lib/python/pickle/instructions/list.rb +24 -0
  49. data/lib/python/pickle/instructions/long.rb +24 -0
  50. data/lib/python/pickle/instructions/long1.rb +32 -0
  51. data/lib/python/pickle/instructions/long4.rb +32 -0
  52. data/lib/python/pickle/instructions/long_bin_get.rb +27 -0
  53. data/lib/python/pickle/instructions/mark.rb +24 -0
  54. data/lib/python/pickle/instructions/memoize.rb +26 -0
  55. data/lib/python/pickle/instructions/new_false.rb +24 -0
  56. data/lib/python/pickle/instructions/new_obj.rb +26 -0
  57. data/lib/python/pickle/instructions/new_obj_ex.rb +26 -0
  58. data/lib/python/pickle/instructions/new_true.rb +24 -0
  59. data/lib/python/pickle/instructions/next_buffer.rb +26 -0
  60. data/lib/python/pickle/instructions/none.rb +24 -0
  61. data/lib/python/pickle/instructions/pop.rb +24 -0
  62. data/lib/python/pickle/instructions/pop_mark.rb +24 -0
  63. data/lib/python/pickle/instructions/proto.rb +29 -0
  64. data/lib/python/pickle/instructions/put.rb +24 -0
  65. data/lib/python/pickle/instructions/readonly_buffer.rb +26 -0
  66. data/lib/python/pickle/instructions/reduce.rb +24 -0
  67. data/lib/python/pickle/instructions/set_item.rb +24 -0
  68. data/lib/python/pickle/instructions/set_items.rb +26 -0
  69. data/lib/python/pickle/instructions/short_bin_bytes.rb +32 -0
  70. data/lib/python/pickle/instructions/short_bin_string.rb +32 -0
  71. data/lib/python/pickle/instructions/short_bin_unicode.rb +32 -0
  72. data/lib/python/pickle/instructions/stack_global.rb +26 -0
  73. data/lib/python/pickle/instructions/stop.rb +24 -0
  74. data/lib/python/pickle/instructions/string.rb +24 -0
  75. data/lib/python/pickle/instructions/tuple.rb +24 -0
  76. data/lib/python/pickle/instructions/tuple1.rb +24 -0
  77. data/lib/python/pickle/instructions/tuple2.rb +24 -0
  78. data/lib/python/pickle/instructions/tuple3.rb +24 -0
  79. data/lib/python/pickle/protocol.rb +56 -0
  80. data/lib/python/pickle/protocol0.rb +399 -0
  81. data/lib/python/pickle/protocol1.rb +183 -0
  82. data/lib/python/pickle/protocol2.rb +229 -0
  83. data/lib/python/pickle/protocol3.rb +163 -0
  84. data/lib/python/pickle/protocol4.rb +285 -0
  85. data/lib/python/pickle/protocol5.rb +218 -0
  86. data/lib/python/pickle/py_class.rb +75 -0
  87. data/lib/python/pickle/py_object.rb +141 -0
  88. data/lib/python/pickle/tuple.rb +19 -0
  89. data/lib/python/pickle/version.rb +6 -0
  90. data/lib/python/pickle.rb +226 -0
  91. data/python-pickle.gemspec +62 -0
  92. data/spec/byte_array_spec.rb +54 -0
  93. data/spec/deserializer_spec.rb +1201 -0
  94. data/spec/fixtures/ascii_str_v3.pkl +0 -0
  95. data/spec/fixtures/ascii_str_v4.pkl +0 -0
  96. data/spec/fixtures/ascii_str_v5.pkl +0 -0
  97. data/spec/fixtures/bin_str_v0.pkl +3 -0
  98. data/spec/fixtures/bin_str_v1.pkl +0 -0
  99. data/spec/fixtures/bin_str_v2.pkl +0 -0
  100. data/spec/fixtures/bin_str_v3.pkl +0 -0
  101. data/spec/fixtures/bin_str_v4.pkl +0 -0
  102. data/spec/fixtures/bin_str_v5.pkl +0 -0
  103. data/spec/fixtures/bytearray_v0.pkl +10 -0
  104. data/spec/fixtures/bytearray_v1.pkl +0 -0
  105. data/spec/fixtures/bytearray_v2.pkl +0 -0
  106. data/spec/fixtures/bytearray_v3.pkl +0 -0
  107. data/spec/fixtures/bytearray_v4.pkl +0 -0
  108. data/spec/fixtures/bytearray_v5.pkl +0 -0
  109. data/spec/fixtures/class_v0.pkl +4 -0
  110. data/spec/fixtures/class_v1.pkl +0 -0
  111. data/spec/fixtures/class_v2.pkl +0 -0
  112. data/spec/fixtures/class_v3.pkl +0 -0
  113. data/spec/fixtures/class_v4.pkl +0 -0
  114. data/spec/fixtures/class_v5.pkl +0 -0
  115. data/spec/fixtures/dict_v0.pkl +6 -0
  116. data/spec/fixtures/dict_v1.pkl +0 -0
  117. data/spec/fixtures/dict_v2.pkl +0 -0
  118. data/spec/fixtures/dict_v3.pkl +0 -0
  119. data/spec/fixtures/dict_v4.pkl +0 -0
  120. data/spec/fixtures/dict_v5.pkl +0 -0
  121. data/spec/fixtures/escaped_str_v0.pkl +3 -0
  122. data/spec/fixtures/escaped_str_v1.pkl +0 -0
  123. data/spec/fixtures/escaped_str_v2.pkl +0 -0
  124. data/spec/fixtures/false_v0.pkl +2 -0
  125. data/spec/fixtures/false_v1.pkl +2 -0
  126. data/spec/fixtures/false_v2.pkl +1 -0
  127. data/spec/fixtures/false_v3.pkl +1 -0
  128. data/spec/fixtures/false_v4.pkl +1 -0
  129. data/spec/fixtures/false_v5.pkl +1 -0
  130. data/spec/fixtures/float_v0.pkl +2 -0
  131. data/spec/fixtures/float_v1.pkl +1 -0
  132. data/spec/fixtures/float_v2.pkl +1 -0
  133. data/spec/fixtures/float_v3.pkl +1 -0
  134. data/spec/fixtures/float_v4.pkl +0 -0
  135. data/spec/fixtures/float_v5.pkl +0 -0
  136. data/spec/fixtures/function_v0.pkl +4 -0
  137. data/spec/fixtures/function_v1.pkl +0 -0
  138. data/spec/fixtures/function_v2.pkl +0 -0
  139. data/spec/fixtures/function_v3.pkl +0 -0
  140. data/spec/fixtures/function_v4.pkl +0 -0
  141. data/spec/fixtures/function_v5.pkl +0 -0
  142. data/spec/fixtures/hex_str_v0.pkl +3 -0
  143. data/spec/fixtures/hex_str_v1.pkl +0 -0
  144. data/spec/fixtures/hex_str_v2.pkl +0 -0
  145. data/spec/fixtures/int_v0.pkl +2 -0
  146. data/spec/fixtures/int_v1.pkl +1 -0
  147. data/spec/fixtures/int_v2.pkl +1 -0
  148. data/spec/fixtures/int_v3.pkl +1 -0
  149. data/spec/fixtures/int_v4.pkl +1 -0
  150. data/spec/fixtures/int_v5.pkl +1 -0
  151. data/spec/fixtures/list_v0.pkl +7 -0
  152. data/spec/fixtures/list_v1.pkl +0 -0
  153. data/spec/fixtures/list_v2.pkl +0 -0
  154. data/spec/fixtures/list_v3.pkl +0 -0
  155. data/spec/fixtures/list_v4.pkl +0 -0
  156. data/spec/fixtures/list_v5.pkl +0 -0
  157. data/spec/fixtures/long_v0.pkl +2 -0
  158. data/spec/fixtures/long_v1.pkl +2 -0
  159. data/spec/fixtures/long_v2.pkl +0 -0
  160. data/spec/fixtures/long_v3.pkl +0 -0
  161. data/spec/fixtures/long_v4.pkl +0 -0
  162. data/spec/fixtures/long_v5.pkl +0 -0
  163. data/spec/fixtures/nested_dict_v0.pkl +12 -0
  164. data/spec/fixtures/nested_dict_v1.pkl +0 -0
  165. data/spec/fixtures/nested_dict_v2.pkl +0 -0
  166. data/spec/fixtures/nested_dict_v3.pkl +0 -0
  167. data/spec/fixtures/nested_dict_v4.pkl +0 -0
  168. data/spec/fixtures/nested_dict_v5.pkl +0 -0
  169. data/spec/fixtures/nested_list_v0.pkl +9 -0
  170. data/spec/fixtures/nested_list_v1.pkl +0 -0
  171. data/spec/fixtures/nested_list_v2.pkl +0 -0
  172. data/spec/fixtures/nested_list_v3.pkl +0 -0
  173. data/spec/fixtures/nested_list_v4.pkl +0 -0
  174. data/spec/fixtures/nested_list_v5.pkl +0 -0
  175. data/spec/fixtures/none_v0.pkl +1 -0
  176. data/spec/fixtures/none_v1.pkl +1 -0
  177. data/spec/fixtures/none_v2.pkl +1 -0
  178. data/spec/fixtures/none_v3.pkl +1 -0
  179. data/spec/fixtures/none_v4.pkl +1 -0
  180. data/spec/fixtures/none_v5.pkl +1 -0
  181. data/spec/fixtures/object_v0.pkl +19 -0
  182. data/spec/fixtures/object_v1.pkl +0 -0
  183. data/spec/fixtures/object_v2.pkl +0 -0
  184. data/spec/fixtures/object_v3.pkl +0 -0
  185. data/spec/fixtures/object_v4.pkl +0 -0
  186. data/spec/fixtures/object_v5.pkl +0 -0
  187. data/spec/fixtures/str_v0.pkl +3 -0
  188. data/spec/fixtures/str_v1.pkl +0 -0
  189. data/spec/fixtures/str_v2.pkl +0 -0
  190. data/spec/fixtures/str_v3.pkl +0 -0
  191. data/spec/fixtures/str_v4.pkl +0 -0
  192. data/spec/fixtures/str_v5.pkl +0 -0
  193. data/spec/fixtures/true_v0.pkl +2 -0
  194. data/spec/fixtures/true_v1.pkl +2 -0
  195. data/spec/fixtures/true_v2.pkl +1 -0
  196. data/spec/fixtures/true_v3.pkl +1 -0
  197. data/spec/fixtures/true_v4.pkl +1 -0
  198. data/spec/fixtures/true_v5.pkl +1 -0
  199. data/spec/fixtures/unicode_str_v0.pkl +3 -0
  200. data/spec/fixtures/unicode_str_v1.pkl +0 -0
  201. data/spec/fixtures/unicode_str_v2.pkl +0 -0
  202. data/spec/fixtures/unicode_str_v3.pkl +0 -0
  203. data/spec/fixtures/unicode_str_v4.pkl +0 -0
  204. data/spec/fixtures/unicode_str_v5.pkl +0 -0
  205. data/spec/generate_pickles2.py +41 -0
  206. data/spec/generate_pickles3.py +40 -0
  207. data/spec/integration/load/protocol0_spec.rb +258 -0
  208. data/spec/integration/load/protocol1_spec.rb +258 -0
  209. data/spec/integration/load/protocol2_spec.rb +258 -0
  210. data/spec/integration/load/protocol3_spec.rb +258 -0
  211. data/spec/integration/load/protocol4_spec.rb +258 -0
  212. data/spec/integration/load/protocol5_spec.rb +258 -0
  213. data/spec/integration/parse/protocol0_spec.rb +467 -0
  214. data/spec/integration/parse/protocol1_spec.rb +459 -0
  215. data/spec/integration/parse/protocol2_spec.rb +471 -0
  216. data/spec/integration/parse/protocol3_spec.rb +407 -0
  217. data/spec/integration/parse/protocol4_spec.rb +439 -0
  218. data/spec/integration/parse/protocol5_spec.rb +419 -0
  219. data/spec/pickle_spec.rb +163 -0
  220. data/spec/protocol0_read_instruction_examples.rb +211 -0
  221. data/spec/protocol0_spec.rb +445 -0
  222. data/spec/protocol1_read_instruction_examples.rb +156 -0
  223. data/spec/protocol1_spec.rb +59 -0
  224. data/spec/protocol2_read_instruction_examples.rb +135 -0
  225. data/spec/protocol2_spec.rb +128 -0
  226. data/spec/protocol3_read_instruction_examples.rb +29 -0
  227. data/spec/protocol3_spec.rb +32 -0
  228. data/spec/protocol4_read_instruction_examples.rb +142 -0
  229. data/spec/protocol4_spec.rb +58 -0
  230. data/spec/protocol5_spec.rb +68 -0
  231. data/spec/py_class_spec.rb +62 -0
  232. data/spec/py_object_spec.rb +149 -0
  233. data/spec/spec_helper.rb +3 -0
  234. data/spec/tuple_spec.rb +18 -0
  235. 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