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.
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