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,258 @@
1
+ require 'spec_helper'
2
+ require 'python/pickle'
3
+
4
+ describe Python::Pickle do
5
+ let(:fixtures_dir) { File.join(__dir__,'..','..','fixtures') }
6
+
7
+ describe ".load" do
8
+ context "when given a Pickle protocol 4 stream" do
9
+ let(:io) { File.open(file) }
10
+
11
+ context "and it contains a serialized None" do
12
+ let(:file) { File.join(fixtures_dir,'none_v4.pkl') }
13
+
14
+ it "must return nil" do
15
+ expect(subject.load(io)).to be(nil)
16
+ end
17
+ end
18
+
19
+ context "and it contains a serialized True" do
20
+ let(:file) { File.join(fixtures_dir,'true_v4.pkl') }
21
+
22
+ it "must return true" do
23
+ expect(subject.load(io)).to be(true)
24
+ end
25
+ end
26
+
27
+ context "and it contains a serialized False" do
28
+ let(:file) { File.join(fixtures_dir,'false_v4.pkl') }
29
+
30
+ it "must return false" do
31
+ expect(subject.load(io)).to be(false)
32
+ end
33
+ end
34
+
35
+ context "and it contains a serialized integer" do
36
+ let(:file) { File.join(fixtures_dir,'int_v4.pkl') }
37
+
38
+ it "must return an Integer object" do
39
+ expect(subject.load(io)).to eq(42)
40
+ end
41
+ end
42
+
43
+ context "and it contains a long integer" do
44
+ let(:file) { File.join(fixtures_dir,'long_v4.pkl') }
45
+
46
+ it "must return an Integer object" do
47
+ expect(subject.load(io)).to eq((2 ** 64) - 1)
48
+ end
49
+ end
50
+
51
+ context "and it contains a serialized floating-point number" do
52
+ let(:float) { 3.141592653589793 }
53
+ let(:file) { File.join(fixtures_dir,'float_v4.pkl') }
54
+
55
+ it "must return a Float object" do
56
+ expect(subject.load(io)).to eq(float)
57
+ end
58
+ end
59
+
60
+ context "and it contains a plain string" do
61
+ let(:string) { "ABC" }
62
+ let(:file) { File.join(fixtures_dir,'str_v4.pkl') }
63
+
64
+ it "must return a String object" do
65
+ expect(subject.load(io)).to eq(string)
66
+ end
67
+
68
+ context "and it contains a UTF string" do
69
+ let(:string) { "ABC\u265E\u265F\u{1F600}" }
70
+ let(:file) { File.join(fixtures_dir,'unicode_str_v4.pkl') }
71
+
72
+ it "must return a UTF-8 encoded String object" do
73
+ expect(subject.load(io).encoding).to be(Encoding::UTF_8)
74
+ end
75
+ end
76
+ end
77
+
78
+ context "and it contains a bytearray type" do
79
+ let(:string) { "ABC" }
80
+ let(:file) { File.join(fixtures_dir,'bytearray_v4.pkl') }
81
+
82
+ it "must return a Python::Pickle::ByteArray object" do
83
+ expect(subject.load(io)).to eq(Python::Pickle::ByteArray.new(string))
84
+ end
85
+ end
86
+
87
+ context "and it contains a list type" do
88
+ let(:file) { File.join(fixtures_dir,'list_v4.pkl') }
89
+
90
+ it "must return an Array object" do
91
+ expect(subject.load(io)).to eq(
92
+ [nil, true, false, 42, "ABC"]
93
+ )
94
+ end
95
+
96
+ context "but the list object contains other list objects" do
97
+ let(:file) { File.join(fixtures_dir,'nested_list_v4.pkl') }
98
+
99
+ it "must return a nested Array object" do
100
+ expect(subject.load(io)).to eq(
101
+ [1, [2, [3, [4]]]]
102
+ )
103
+ end
104
+ end
105
+ end
106
+
107
+ context "and it contains a dict type" do
108
+ let(:file) { File.join(fixtures_dir,'dict_v4.pkl') }
109
+
110
+ it "must return an Array object" do
111
+ expect(subject.load(io)).to eq(
112
+ {"foo" => "bar"}
113
+ )
114
+ end
115
+
116
+ context "but the dict object contains other dict objects" do
117
+ let(:file) { File.join(fixtures_dir,'nested_dict_v4.pkl') }
118
+
119
+ it "must return a nested Hash object" do
120
+ expect(subject.load(io)).to eq(
121
+ {"a" => {"b" => {"c" => "d"}}}
122
+ )
123
+ end
124
+ end
125
+ end
126
+
127
+ context "and it contains a function type" do
128
+ let(:file) { File.join(fixtures_dir,'function_v4.pkl') }
129
+
130
+ context "but there is no constant mapping for the function name" do
131
+ it "must return a Python::Pickle::PyClass object of the given name" do
132
+ object = subject.load(io)
133
+
134
+ expect(object).to be_kind_of(Python::Pickle::PyClass)
135
+ expect(object.namespace).to eq('__main__')
136
+ expect(object.name).to eq('func')
137
+ end
138
+ end
139
+
140
+ context "but there is a constant mapping for the function name" do
141
+ module TestPickleLoad
142
+ def self.func
143
+ end
144
+ end
145
+
146
+ it "must return a mapped Ruby method" do
147
+ object = subject.load(io, constants: {
148
+ '__main__' => {
149
+ 'func' => TestPickleLoad.method(:func)
150
+ }
151
+ })
152
+
153
+ expect(object).to eq(TestPickleLoad.method(:func))
154
+ end
155
+ end
156
+ end
157
+
158
+ context "and it contains a class type" do
159
+ let(:file) { File.join(fixtures_dir,'class_v4.pkl') }
160
+
161
+ context "but there is no constant mapping for the class name" do
162
+ it "must return a Python::Pickle::PyClass object of the given name" do
163
+ object = subject.load(io)
164
+
165
+ expect(object).to be_kind_of(Python::Pickle::PyClass)
166
+ expect(object.namespace).to eq('__main__')
167
+ expect(object.name).to eq('MyClass')
168
+ end
169
+ end
170
+
171
+ context "but there is a constant mapping for the class name" do
172
+ module TestPickleLoad
173
+ class MyClass
174
+ end
175
+ end
176
+
177
+ it "must return a new Ruby object for the mapped class" do
178
+ object = subject.load(io, constants: {
179
+ '__main__' => {
180
+ 'MyClass' => TestPickleLoad::MyClass
181
+ }
182
+ })
183
+
184
+ expect(object).to be(TestPickleLoad::MyClass)
185
+ end
186
+ end
187
+ end
188
+
189
+ context "and it contains an object type" do
190
+ let(:file) { File.join(fixtures_dir,'object_v1.pkl') }
191
+
192
+ context "but there is no constant mapping for the class name" do
193
+ it "must return a Python::Pickle::PyObject object of the given name" do
194
+ object = subject.load(io)
195
+
196
+ expect(object).to be_kind_of(Python::Pickle::PyObject)
197
+ expect(object.py_class).to be_kind_of(Python::Pickle::PyClass)
198
+ expect(object.py_class.namespace).to eq('__main__')
199
+ expect(object.py_class.name).to eq('MyClass')
200
+ expect(object.attributes).to eq(
201
+ {
202
+ 'x' => 0x41,
203
+ 'y' => 0x42
204
+ }
205
+ )
206
+ end
207
+ end
208
+
209
+ context "but there is a constant mapping for the class name" do
210
+ context "and the Ruby class defines a __setstate__ method" do
211
+ module TestPickleLoad
212
+ class MyClassWithSetState
213
+ attr_reader :x
214
+ attr_reader :y
215
+
216
+ def __setstate__(attributes)
217
+ @x = attributes['x']
218
+ @y = attributes['y']
219
+ end
220
+ end
221
+ end
222
+
223
+ it "must return a new Ruby object for the mapped class" do
224
+ object = subject.load(io, constants: {
225
+ '__main__' => {
226
+ 'MyClass' => TestPickleLoad::MyClassWithSetState
227
+ }
228
+ })
229
+
230
+ expect(object).to be_kind_of(TestPickleLoad::MyClassWithSetState)
231
+ expect(object.x).to eq(0x41)
232
+ expect(object.y).to eq(0x42)
233
+ end
234
+ end
235
+
236
+ context "but the Ruby class does not define a __setstate__ method" do
237
+ module TestPickleLoad
238
+ class MyClassWithoutSetState
239
+ end
240
+ end
241
+
242
+ it do
243
+ expect {
244
+ subject.load(io, constants: {
245
+ '__main__' => {
246
+ 'MyClass' => TestPickleLoad::MyClassWithoutSetState
247
+ }
248
+ })
249
+ }.to raise_error(Python::Pickle::DeserializationError).with_message(
250
+ /\Acannot execute BUILD on an object that does not define a __setstate__ method or is not a Hash: #<TestPickleLoad::MyClassWithoutSetState:0x[0-9a-f]+>\z/
251
+ )
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,258 @@
1
+ require 'spec_helper'
2
+ require 'python/pickle'
3
+
4
+ describe Python::Pickle do
5
+ let(:fixtures_dir) { File.join(__dir__,'..','..','fixtures') }
6
+
7
+ describe ".load" do
8
+ context "when given a Pickle protocol 5 stream" do
9
+ let(:io) { File.open(file) }
10
+
11
+ context "and it contains a serialized None" do
12
+ let(:file) { File.join(fixtures_dir,'none_v5.pkl') }
13
+
14
+ it "must return nil" do
15
+ expect(subject.load(io)).to be(nil)
16
+ end
17
+ end
18
+
19
+ context "and it contains a serialized True" do
20
+ let(:file) { File.join(fixtures_dir,'true_v5.pkl') }
21
+
22
+ it "must return true" do
23
+ expect(subject.load(io)).to be(true)
24
+ end
25
+ end
26
+
27
+ context "and it contains a serialized False" do
28
+ let(:file) { File.join(fixtures_dir,'false_v5.pkl') }
29
+
30
+ it "must return false" do
31
+ expect(subject.load(io)).to be(false)
32
+ end
33
+ end
34
+
35
+ context "and it contains a serialized integer" do
36
+ let(:file) { File.join(fixtures_dir,'int_v5.pkl') }
37
+
38
+ it "must return an Integer object" do
39
+ expect(subject.load(io)).to eq(42)
40
+ end
41
+ end
42
+
43
+ context "and it contains a long integer" do
44
+ let(:file) { File.join(fixtures_dir,'long_v5.pkl') }
45
+
46
+ it "must return an Integer object" do
47
+ expect(subject.load(io)).to eq((2 ** 64) - 1)
48
+ end
49
+ end
50
+
51
+ context "and it contains a serialized floating-point number" do
52
+ let(:float) { 3.141592653589793 }
53
+ let(:file) { File.join(fixtures_dir,'float_v5.pkl') }
54
+
55
+ it "must return a Float object" do
56
+ expect(subject.load(io)).to eq(float)
57
+ end
58
+ end
59
+
60
+ context "and it contains a plain string" do
61
+ let(:string) { "ABC" }
62
+ let(:file) { File.join(fixtures_dir,'str_v5.pkl') }
63
+
64
+ it "must return a String object" do
65
+ expect(subject.load(io)).to eq(string)
66
+ end
67
+
68
+ context "and it contains a UTF string" do
69
+ let(:string) { "ABC\u265E\u265F\u{1F600}" }
70
+ let(:file) { File.join(fixtures_dir,'unicode_str_v5.pkl') }
71
+
72
+ it "must return a UTF-8 encoded String object" do
73
+ expect(subject.load(io).encoding).to be(Encoding::UTF_8)
74
+ end
75
+ end
76
+ end
77
+
78
+ context "and it contains a bytearray type" do
79
+ let(:string) { "ABC" }
80
+ let(:file) { File.join(fixtures_dir,'bytearray_v5.pkl') }
81
+
82
+ it "must return a Python::Pickle::ByteArray object" do
83
+ expect(subject.load(io)).to eq(Python::Pickle::ByteArray.new(string))
84
+ end
85
+ end
86
+
87
+ context "and it contains a list type" do
88
+ let(:file) { File.join(fixtures_dir,'list_v5.pkl') }
89
+
90
+ it "must return an Array object" do
91
+ expect(subject.load(io)).to eq(
92
+ [nil, true, false, 42, "ABC"]
93
+ )
94
+ end
95
+
96
+ context "but the list object contains other list objects" do
97
+ let(:file) { File.join(fixtures_dir,'nested_list_v5.pkl') }
98
+
99
+ it "must return a nested Array object" do
100
+ expect(subject.load(io)).to eq(
101
+ [1, [2, [3, [4]]]]
102
+ )
103
+ end
104
+ end
105
+ end
106
+
107
+ context "and it contains a dict type" do
108
+ let(:file) { File.join(fixtures_dir,'dict_v5.pkl') }
109
+
110
+ it "must return an Array object" do
111
+ expect(subject.load(io)).to eq(
112
+ {"foo" => "bar"}
113
+ )
114
+ end
115
+
116
+ context "but the dict object contains other dict objects" do
117
+ let(:file) { File.join(fixtures_dir,'nested_dict_v5.pkl') }
118
+
119
+ it "must return a nested Hash object" do
120
+ expect(subject.load(io)).to eq(
121
+ {"a" => {"b" => {"c" => "d"}}}
122
+ )
123
+ end
124
+ end
125
+ end
126
+
127
+ context "and it contains a function type" do
128
+ let(:file) { File.join(fixtures_dir,'function_v5.pkl') }
129
+
130
+ context "but there is no constant mapping for the function name" do
131
+ it "must return a Python::Pickle::PyClass object of the given name" do
132
+ object = subject.load(io)
133
+
134
+ expect(object).to be_kind_of(Python::Pickle::PyClass)
135
+ expect(object.namespace).to eq('__main__')
136
+ expect(object.name).to eq('func')
137
+ end
138
+ end
139
+
140
+ context "but there is a constant mapping for the function name" do
141
+ module TestPickleLoad
142
+ def self.func
143
+ end
144
+ end
145
+
146
+ it "must return a mapped Ruby method" do
147
+ object = subject.load(io, constants: {
148
+ '__main__' => {
149
+ 'func' => TestPickleLoad.method(:func)
150
+ }
151
+ })
152
+
153
+ expect(object).to eq(TestPickleLoad.method(:func))
154
+ end
155
+ end
156
+ end
157
+
158
+ context "and it contains a class type" do
159
+ let(:file) { File.join(fixtures_dir,'class_v5.pkl') }
160
+
161
+ context "but there is no constant mapping for the class name" do
162
+ it "must return a Python::Pickle::PyClass object of the given name" do
163
+ object = subject.load(io)
164
+
165
+ expect(object).to be_kind_of(Python::Pickle::PyClass)
166
+ expect(object.namespace).to eq('__main__')
167
+ expect(object.name).to eq('MyClass')
168
+ end
169
+ end
170
+
171
+ context "but there is a constant mapping for the class name" do
172
+ module TestPickleLoad
173
+ class MyClass
174
+ end
175
+ end
176
+
177
+ it "must return a new Ruby object for the mapped class" do
178
+ object = subject.load(io, constants: {
179
+ '__main__' => {
180
+ 'MyClass' => TestPickleLoad::MyClass
181
+ }
182
+ })
183
+
184
+ expect(object).to be(TestPickleLoad::MyClass)
185
+ end
186
+ end
187
+ end
188
+
189
+ context "and it contains an object type" do
190
+ let(:file) { File.join(fixtures_dir,'object_v1.pkl') }
191
+
192
+ context "but there is no constant mapping for the class name" do
193
+ it "must return a Python::Pickle::PyObject object of the given name" do
194
+ object = subject.load(io)
195
+
196
+ expect(object).to be_kind_of(Python::Pickle::PyObject)
197
+ expect(object.py_class).to be_kind_of(Python::Pickle::PyClass)
198
+ expect(object.py_class.namespace).to eq('__main__')
199
+ expect(object.py_class.name).to eq('MyClass')
200
+ expect(object.attributes).to eq(
201
+ {
202
+ 'x' => 0x41,
203
+ 'y' => 0x42
204
+ }
205
+ )
206
+ end
207
+ end
208
+
209
+ context "but there is a constant mapping for the class name" do
210
+ context "and the Ruby class defines a __setstate__ method" do
211
+ module TestPickleLoad
212
+ class MyClassWithSetState
213
+ attr_reader :x
214
+ attr_reader :y
215
+
216
+ def __setstate__(attributes)
217
+ @x = attributes['x']
218
+ @y = attributes['y']
219
+ end
220
+ end
221
+ end
222
+
223
+ it "must return a new Ruby object for the mapped class" do
224
+ object = subject.load(io, constants: {
225
+ '__main__' => {
226
+ 'MyClass' => TestPickleLoad::MyClassWithSetState
227
+ }
228
+ })
229
+
230
+ expect(object).to be_kind_of(TestPickleLoad::MyClassWithSetState)
231
+ expect(object.x).to eq(0x41)
232
+ expect(object.y).to eq(0x42)
233
+ end
234
+ end
235
+
236
+ context "but the Ruby class does not define a __setstate__ method" do
237
+ module TestPickleLoad
238
+ class MyClassWithoutSetState
239
+ end
240
+ end
241
+
242
+ it do
243
+ expect {
244
+ subject.load(io, constants: {
245
+ '__main__' => {
246
+ 'MyClass' => TestPickleLoad::MyClassWithoutSetState
247
+ }
248
+ })
249
+ }.to raise_error(Python::Pickle::DeserializationError).with_message(
250
+ /\Acannot execute BUILD on an object that does not define a __setstate__ method or is not a Hash: #<TestPickleLoad::MyClassWithoutSetState:0x[0-9a-f]+>\z/
251
+ )
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end