python-pickle 0.1.0 → 0.2.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +17 -0
  3. data/README.md +4 -1
  4. data/lib/python/pickle/deserializer.rb +142 -80
  5. data/lib/python/pickle/instructions/bin_persid.rb +31 -0
  6. data/lib/python/pickle/instructions/global.rb +11 -41
  7. data/lib/python/pickle/instructions/has_namespace_and_name.rb +61 -0
  8. data/lib/python/pickle/instructions/inst.rb +34 -0
  9. data/lib/python/pickle/instructions/next_buffer.rb +5 -1
  10. data/lib/python/pickle/instructions/obj.rb +30 -0
  11. data/lib/python/pickle/instructions/persid.rb +31 -0
  12. data/lib/python/pickle/instructions/readonly_buffer.rb +4 -0
  13. data/lib/python/pickle/instructions.rb +64 -0
  14. data/lib/python/pickle/protocol0.rb +313 -68
  15. data/lib/python/pickle/protocol1.rb +225 -93
  16. data/lib/python/pickle/protocol2.rb +205 -124
  17. data/lib/python/pickle/protocol3.rb +92 -123
  18. data/lib/python/pickle/protocol4.rb +188 -165
  19. data/lib/python/pickle/protocol5.rb +98 -166
  20. data/lib/python/pickle/version.rb +1 -1
  21. data/lib/python/pickle.rb +71 -39
  22. data/spec/deserializer_spec.rb +359 -0
  23. data/spec/fixtures/set_v0.pkl +11 -0
  24. data/spec/fixtures/set_v1.pkl +0 -0
  25. data/spec/fixtures/set_v2.pkl +0 -0
  26. data/spec/fixtures/set_v3.pkl +0 -0
  27. data/spec/fixtures/set_v4.pkl +0 -0
  28. data/spec/fixtures/set_v5.pkl +0 -0
  29. data/spec/generate_pickles2.py +1 -0
  30. data/spec/generate_pickles3.py +1 -0
  31. data/spec/integration/load/protocol0_spec.rb +10 -0
  32. data/spec/integration/load/protocol1_spec.rb +10 -0
  33. data/spec/integration/load/protocol2_spec.rb +10 -0
  34. data/spec/integration/load/protocol3_spec.rb +10 -0
  35. data/spec/integration/load/protocol4_spec.rb +10 -0
  36. data/spec/integration/load/protocol5_spec.rb +10 -0
  37. data/spec/pickle_spec.rb +61 -0
  38. data/spec/protocol0_read_instruction_examples.rb +44 -0
  39. metadata +14 -2
@@ -2,6 +2,11 @@ require 'spec_helper'
2
2
  require 'python/pickle/deserializer'
3
3
 
4
4
  describe Python::Pickle::Deserializer do
5
+ module TestDeserializer
6
+ class MyClass
7
+ end
8
+ end
9
+
5
10
  describe "#initialize" do
6
11
  it "must initialize #meta_stack to an empty Array" do
7
12
  expect(subject.meta_stack).to eq([])
@@ -26,6 +31,10 @@ describe Python::Pickle::Deserializer do
26
31
  expect(subject.constants['__builtin__']['object']).to be(described_class::OBJECT_CLASS)
27
32
  end
28
33
 
34
+ it "must contain '__builtin__.set' for Python 2.x support" do
35
+ expect(subject.constants['__builtin__']['set']).to be(Set)
36
+ end
37
+
29
38
  it "must contain '__builtin__.bytearray' for Python 2.x support" do
30
39
  expect(subject.constants['__builtin__']['bytearray']).to be(Python::Pickle::ByteArray)
31
40
  end
@@ -34,6 +43,10 @@ describe Python::Pickle::Deserializer do
34
43
  expect(subject.constants['builtins']['object']).to be(described_class::OBJECT_CLASS)
35
44
  end
36
45
 
46
+ it "must contain 'builtins.set' for Python 2.x support" do
47
+ expect(subject.constants['builtins']['set']).to be(Set)
48
+ end
49
+
37
50
  it "must contain 'builtins.bytearray' for Python 2.x support" do
38
51
  expect(subject.constants['builtins']['bytearray']).to be(Python::Pickle::ByteArray)
39
52
  end
@@ -44,9 +57,75 @@ describe Python::Pickle::Deserializer do
44
57
  end
45
58
 
46
59
  context "when initialized with the `extensions:` keyword argument" do
60
+ let(:extensions) do
61
+ {
62
+ 0x41 => Object.new,
63
+ 0x42 => Object.new,
64
+ 0x43 => Object.new
65
+ }
66
+ end
67
+
68
+ subject { described_class.new(extensions: extensions) }
69
+
70
+ it "must add the extensions: values to #extensions" do
71
+ expect(subject.extensions).to eq(extensions)
72
+ end
47
73
  end
48
74
 
49
75
  context "when initialized with the `constants:` keyword argument" do
76
+ let(:constants) do
77
+ {
78
+ '__main__' => {
79
+ 'MyClass' => TestDeserializer::MyClass
80
+ }
81
+ }
82
+ end
83
+
84
+ subject { described_class.new(constants: constants) }
85
+
86
+ it "must merge the constants: keyword argument with the default constants" do
87
+ expect(subject.constants).to eq(
88
+ {
89
+ 'copy_reg' => {
90
+ '_reconstructor' => subject.method(:copyreg_reconstructor)
91
+ },
92
+
93
+ '__builtin__' => {
94
+ 'object' => described_class::OBJECT_CLASS,
95
+ 'set' => Set,
96
+ 'bytearray' => Python::Pickle::ByteArray
97
+ },
98
+
99
+ 'builtins' => {
100
+ 'object' => described_class::OBJECT_CLASS,
101
+ 'set' => Set,
102
+ 'bytearray' => Python::Pickle::ByteArray
103
+ },
104
+
105
+ '__main__' => {
106
+ 'MyClass' => TestDeserializer::MyClass
107
+ }
108
+ }
109
+ )
110
+ end
111
+ end
112
+
113
+ context "when initialized with the `buffers:` keyword argument" do
114
+ let(:buffer1) { "hello world" }
115
+ let(:buffer2) { "foo bar" }
116
+ let(:buffers) do
117
+ [
118
+ buffer1,
119
+ buffer2
120
+ ]
121
+ end
122
+
123
+ subject { described_class.new(buffers: buffers) }
124
+
125
+ it "must set #buffers to an Enumerator of the buffers" do
126
+ expect(subject.buffers).to be_kind_of(Enumerator)
127
+ expect(subject.buffers.to_a).to eq(buffers)
128
+ end
50
129
  end
51
130
  end
52
131
 
@@ -364,6 +443,37 @@ describe Python::Pickle::Deserializer do
364
443
  end
365
444
  end
366
445
 
446
+ context "when given a Python::Pickle::Instructions::EMPTY_SET" do
447
+ let(:instruction) { Python::Pickle::Instructions::EMPTY_SET }
448
+
449
+ before do
450
+ subject.execute(instruction)
451
+ end
452
+
453
+ it "must push an empty Set object onto the #stack" do
454
+ expect(subject.stack).to eq([ Set.new ])
455
+ end
456
+ end
457
+
458
+ context "when given a Python::Pickle::Instructions::FROZENSET" do
459
+ let(:instruction) { Python::Pickle::Instructions::FROZENSET }
460
+
461
+ before do
462
+ subject.meta_stack << []
463
+ subject.stack << 1 << 2 << 3
464
+ subject.execute(instruction)
465
+ end
466
+
467
+ it "must pop the #meta_stack and create a frozen Set from the previous #stack and push the frozen Set onto the new #stack" do
468
+ expect(subject.stack.length).to eq(1)
469
+
470
+ set = subject.stack[-1]
471
+
472
+ expect(set).to be_frozen
473
+ expect(set).to eq(Set[1,2,3])
474
+ end
475
+ end
476
+
367
477
  context "when given a Python::Pickle::Instructions::APPEND" do
368
478
  context "and when the previous element on the stack is an Array" do
369
479
  let(:instruction) { Python::Pickle::Instructions::APPEND }
@@ -378,6 +488,19 @@ describe Python::Pickle::Deserializer do
378
488
  end
379
489
  end
380
490
 
491
+ context "and when the previous element on the stack is a Set" do
492
+ let(:instruction) { Python::Pickle::Instructions::APPEND }
493
+
494
+ before do
495
+ subject.stack << Set.new << 2
496
+ subject.execute(instruction)
497
+ end
498
+
499
+ it "must pop the last element from the #stack and push it onto the next list element" do
500
+ expect(subject.stack).to eq([ Set[2] ])
501
+ end
502
+ end
503
+
381
504
  context "but when the previous element on the stack is not an Array" do
382
505
  let(:instruction) { Python::Pickle::Instructions::APPEND }
383
506
  let(:item) { 2 }
@@ -410,6 +533,20 @@ describe Python::Pickle::Deserializer do
410
533
  end
411
534
  end
412
535
 
536
+ context "and when the previous element on the stack is a Set" do
537
+ let(:instruction) { Python::Pickle::Instructions::APPENDS }
538
+
539
+ before do
540
+ subject.meta_stack << [ Set[1,2,3] ]
541
+ subject.stack << 4 << 5 << 6
542
+ subject.execute(instruction)
543
+ end
544
+
545
+ it "must pop the #meta_stack, store the #stack, and concat the previous #stack onto the last element of the new #stack" do
546
+ expect(subject.stack).to eq([ Set[1,2,3,4,5,6] ])
547
+ end
548
+ end
549
+
413
550
  context "but when the previous element on the stack is not an Array" do
414
551
  let(:instruction) { Python::Pickle::Instructions::APPENDS }
415
552
  let(:items) { [3,4,5] }
@@ -428,6 +565,39 @@ describe Python::Pickle::Deserializer do
428
565
  end
429
566
  end
430
567
 
568
+ context "when given a Python::Pickle::Instructions::ADDITEMS" do
569
+ context "and when the previous element on the stack is a Set" do
570
+ let(:instruction) { Python::Pickle::Instructions::ADDITEMS }
571
+
572
+ before do
573
+ subject.meta_stack << [ Set[1,2,3] ]
574
+ subject.stack << 4 << 5 << 6
575
+ subject.execute(instruction)
576
+ end
577
+
578
+ it "must pop the #meta_stack, store the #stack, and concat the previous #stack onto the last element of the new #stack" do
579
+ expect(subject.stack).to eq([ Set[1,2,3,4,5,6] ])
580
+ end
581
+ end
582
+
583
+ context "but when the previous element on the stack is not a Set" do
584
+ let(:instruction) { Python::Pickle::Instructions::ADDITEMS }
585
+ let(:items) { [3,4,5] }
586
+ let(:set) { [] }
587
+
588
+ before do
589
+ subject.meta_stack << [ set ]
590
+ subject.stack << items[0] << items[1] << items[2]
591
+ end
592
+
593
+ it do
594
+ expect {
595
+ subject.execute(instruction)
596
+ }.to raise_error(Python::Pickle::DeserializationError,"cannot add items #{items.inspect} to a non-Set object: #{set.inspect}")
597
+ end
598
+ end
599
+ end
600
+
431
601
  context "when given a Python::Pickle::Instructions::LIST" do
432
602
  let(:instruction) { Python::Pickle::Instructions::LIST }
433
603
 
@@ -578,6 +748,142 @@ describe Python::Pickle::Deserializer do
578
748
  end
579
749
  end
580
750
 
751
+ context "when given a Python::Pickle::Instructions::Inst object" do
752
+ let(:namespace) { '__main__' }
753
+ let(:name) { 'MyClass' }
754
+ let(:instruction) { Python::Pickle::Instructions::Inst.new(namespace,name) }
755
+
756
+ before do
757
+ subject.meta_stack << []
758
+ subject.stack << 1 << 2
759
+ subject.execute(instruction)
760
+ end
761
+
762
+ context "when the constant can be resolved" do
763
+ module TestInstInstruction
764
+ class MyClass
765
+ attr_reader :x, :y
766
+
767
+ def initialize(x,y)
768
+ @x = x
769
+ @y = y
770
+ end
771
+ end
772
+ end
773
+
774
+ subject do
775
+ described_class.new(
776
+ constants: {
777
+ '__main__' => {
778
+ 'MyClass' => TestInstInstruction::MyClass
779
+ }
780
+ }
781
+ )
782
+ end
783
+
784
+ it "must resolve the class from the INST instruction's #namespace and #name, pop the meta stack, use the previous sstack as the initialization arguments, initialize a new instance of the class, and push it onto the new #stack" do
785
+ expect(subject.stack.length).to eq(1)
786
+
787
+ object = subject.stack[-1]
788
+
789
+ expect(object).to be_kind_of(TestInstInstruction::MyClass)
790
+ expect(object.x).to eq(1)
791
+ expect(object.y).to eq(2)
792
+ end
793
+ end
794
+
795
+ context "but the constant cannot be resolved" do
796
+ it "must push a new Python::Pickle::PyClass object onto the #stack" do
797
+ py_object = subject.stack[-1]
798
+
799
+ expect(py_object).to be_kind_of(Python::Pickle::PyObject)
800
+ expect(py_object.py_class).to be_kind_of(Python::Pickle::PyClass)
801
+ expect(py_object.py_class.namespace).to eq(namespace)
802
+ expect(py_object.py_class.name).to eq(name)
803
+ expect(py_object.init_args).to eq([1,2])
804
+ end
805
+ end
806
+ end
807
+
808
+ context "when given a Python::Pickle::Instructions::OBJ" do
809
+ let(:instruction) { Python::Pickle::Instructions::OBJ }
810
+
811
+ context "and when the constant on the #stack is a Ruby class" do
812
+ module TestObjInstruction
813
+ class MyClass
814
+ end
815
+ end
816
+
817
+ context "but there are no additional arguments on the #stack after the class" do
818
+ before do
819
+ subject.stack << TestObjInstruction::MyClass
820
+ subject.execute(instruction)
821
+ end
822
+
823
+ it "must pop off first element, and initialize a new instance of the class, and push the new instance onto the #stack" do
824
+ expect(subject.stack.length).to eq(1)
825
+ expect(subject.stack[-1]).to be_kind_of(TestObjInstruction::MyClass)
826
+ end
827
+ end
828
+
829
+ context "but there are additional arguments on the #stack after the class" do
830
+ module TestObjInstruction
831
+ class MyClassWithArgs
832
+ attr_reader :x, :y
833
+
834
+ def initialize(x,y)
835
+ @x = x
836
+ @y = y
837
+ end
838
+ end
839
+ end
840
+
841
+ before do
842
+ subject.stack << TestObjInstruction::MyClassWithArgs << 1 << 2
843
+ subject.execute(instruction)
844
+ end
845
+
846
+ it "must call #initialize with the splatted tuple's arguments" do
847
+ object = subject.stack[-1]
848
+
849
+ expect(object.x).to eq(1)
850
+ expect(object.y).to eq(2)
851
+ end
852
+ end
853
+ end
854
+
855
+ context "and when the constant on the #stack is a PyClass" do
856
+ let(:namespace) { '__main__' }
857
+ let(:name) { 'MyClass' }
858
+ let(:py_class) { Python::Pickle::PyClass.new(namespace,name) }
859
+
860
+ context "but there are no additional arguments on the #stack after the class" do
861
+ before do
862
+ subject.stack << py_class
863
+ subject.execute(instruction)
864
+ end
865
+
866
+ it "must pop off the two last elements and push the new Python::Pickle::PyObject onto the #stack" do
867
+ expect(subject.stack.length).to eq(1)
868
+ expect(subject.stack[-1]).to be_kind_of(Python::Pickle::PyObject)
869
+ end
870
+ end
871
+
872
+ context "but there are additional arguments on the #stack after the class" do
873
+ before do
874
+ subject.stack << py_class << 1 << 2
875
+ subject.execute(instruction)
876
+ end
877
+
878
+ it "must set the object's #init_args to the tuple's elements" do
879
+ object = subject.stack[-1]
880
+
881
+ expect(object.init_args).to eq([1,2])
882
+ end
883
+ end
884
+ end
885
+ end
886
+
581
887
  context "when given a Python::Pickle::Instructions::NEWOBJ" do
582
888
  let(:instruction) { Python::Pickle::Instructions::NEWOBJ }
583
889
 
@@ -1150,6 +1456,59 @@ describe Python::Pickle::Deserializer do
1150
1456
  end
1151
1457
  end
1152
1458
  end
1459
+
1460
+ context "when given a Python::Pickle::Instructions::NEXT_BUFFER" do
1461
+ let(:instruction) { Python::Pickle::Instructions::NEXT_BUFFER }
1462
+
1463
+ context "and the #{described_class} was initialized with the buffers: keyword argument" do
1464
+ let(:buffer1) { String.new("hello world") }
1465
+ let(:buffer2) { String.new("foo bar") }
1466
+ let(:buffers) do
1467
+ [
1468
+ buffer1,
1469
+ buffer2
1470
+ ]
1471
+ end
1472
+
1473
+ subject { described_class.new(buffers: buffers) }
1474
+
1475
+ before do
1476
+ subject.execute(instruction)
1477
+ end
1478
+
1479
+ it "must take the next element from #buffers and push it onto the #stack" do
1480
+ expect(subject.stack).to eq([buffer1])
1481
+ end
1482
+
1483
+ it "must not modify the underlying buffers Array" do
1484
+ expect(buffers).to eq([buffer1, buffer2])
1485
+ end
1486
+ end
1487
+
1488
+ context "but the #{described_class} was not initialized with the buffers: keyword argument" do
1489
+ it do
1490
+ expect {
1491
+ subject.execute(instruction)
1492
+ }.to raise_error(Python::Pickle::DeserializationError,"pickle stream includes a NEXT_BUFFER instruction, but no buffers were provided")
1493
+ end
1494
+ end
1495
+ end
1496
+
1497
+ context "when given a Python::Pickle::Instructions::READONLY_BUFFER" do
1498
+ let(:instruction) { Python::Pickle::Instructions::READONLY_BUFFER }
1499
+
1500
+ let(:buffer1) { String.new("hello world") }
1501
+ let(:buffer2) { String.new("foo bar") }
1502
+
1503
+ before do
1504
+ subject.stack << buffer1 << buffer2
1505
+ subject.execute(instruction)
1506
+ end
1507
+
1508
+ it "must freeze the buffer at the top of the #stack" do
1509
+ expect(subject.stack[-1]).to be_frozen
1510
+ end
1511
+ end
1153
1512
  end
1154
1513
 
1155
1514
  describe "#copyreg_reconstructor" do
@@ -0,0 +1,11 @@
1
+ c__builtin__
2
+ set
3
+ p0
4
+ ((lp1
5
+ I1
6
+ aI2
7
+ aI3
8
+ aI4
9
+ atp2
10
+ Rp3
11
+ .
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -26,6 +26,7 @@ objects = {
26
26
  "bin_str": b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
27
27
  "list": [None, True, False, 42, 'ABC'],
28
28
  "nested_list": [1, [2, [3, [4]]]],
29
+ "set": set([1,2,3,4]),
29
30
  "dict": {"foo": "bar"},
30
31
  "nested_dict": {"a": {"b": {"c": "d"}}},
31
32
  "function": func,
@@ -25,6 +25,7 @@ objects = {
25
25
  "bin_str": b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
26
26
  "list": [None, True, False, 42, 'ABC'],
27
27
  "nested_list": [1, [2, [3, [4]]]],
28
+ "set": set([1,2,3,4]),
28
29
  "dict": {"foo": "bar"},
29
30
  "nested_dict": {"a": {"b": {"c": "d"}}},
30
31
  "function": func,
@@ -104,6 +104,16 @@ describe Python::Pickle do
104
104
  end
105
105
  end
106
106
 
107
+ context "and it contains a set type" do
108
+ let(:file) { File.join(fixtures_dir,'set_v0.pkl') }
109
+
110
+ it "must return a Set object" do
111
+ expect(subject.load(io)).to eq(
112
+ Set[1, 2, 3, 4]
113
+ )
114
+ end
115
+ end
116
+
107
117
  context "and it contains a dict type" do
108
118
  let(:file) { File.join(fixtures_dir,'dict_v0.pkl') }
109
119
 
@@ -104,6 +104,16 @@ describe Python::Pickle do
104
104
  end
105
105
  end
106
106
 
107
+ context "and it contains a set type" do
108
+ let(:file) { File.join(fixtures_dir,'set_v1.pkl') }
109
+
110
+ it "must return a Set object" do
111
+ expect(subject.load(io)).to eq(
112
+ Set[1, 2, 3, 4]
113
+ )
114
+ end
115
+ end
116
+
107
117
  context "and it contains a dict type" do
108
118
  let(:file) { File.join(fixtures_dir,'dict_v1.pkl') }
109
119
 
@@ -104,6 +104,16 @@ describe Python::Pickle do
104
104
  end
105
105
  end
106
106
 
107
+ context "and it contains a set type" do
108
+ let(:file) { File.join(fixtures_dir,'set_v2.pkl') }
109
+
110
+ it "must return a Set object" do
111
+ expect(subject.load(io)).to eq(
112
+ Set[1, 2, 3, 4]
113
+ )
114
+ end
115
+ end
116
+
107
117
  context "and it contains a dict type" do
108
118
  let(:file) { File.join(fixtures_dir,'dict_v2.pkl') }
109
119
 
@@ -104,6 +104,16 @@ describe Python::Pickle do
104
104
  end
105
105
  end
106
106
 
107
+ context "and it contains a set type" do
108
+ let(:file) { File.join(fixtures_dir,'set_v3.pkl') }
109
+
110
+ it "must return a Set object" do
111
+ expect(subject.load(io)).to eq(
112
+ Set[1, 2, 3, 4]
113
+ )
114
+ end
115
+ end
116
+
107
117
  context "and it contains a dict type" do
108
118
  let(:file) { File.join(fixtures_dir,'dict_v3.pkl') }
109
119
 
@@ -104,6 +104,16 @@ describe Python::Pickle do
104
104
  end
105
105
  end
106
106
 
107
+ context "and it contains a set type" do
108
+ let(:file) { File.join(fixtures_dir,'set_v4.pkl') }
109
+
110
+ it "must return a Set object" do
111
+ expect(subject.load(io)).to eq(
112
+ Set[1, 2, 3, 4]
113
+ )
114
+ end
115
+ end
116
+
107
117
  context "and it contains a dict type" do
108
118
  let(:file) { File.join(fixtures_dir,'dict_v4.pkl') }
109
119
 
@@ -104,6 +104,16 @@ describe Python::Pickle do
104
104
  end
105
105
  end
106
106
 
107
+ context "and it contains a set type" do
108
+ let(:file) { File.join(fixtures_dir,'set_v5.pkl') }
109
+
110
+ it "must return a Set object" do
111
+ expect(subject.load(io)).to eq(
112
+ Set[1, 2, 3, 4]
113
+ )
114
+ end
115
+ end
116
+
107
117
  context "and it contains a dict type" do
108
118
  let(:file) { File.join(fixtures_dir,'dict_v5.pkl') }
109
119
 
data/spec/pickle_spec.rb CHANGED
@@ -52,6 +52,33 @@ describe Python::Pickle do
52
52
  it "must deserialize the Python Pickle data in the given file" do
53
53
  expect(subject.load(data)).to eq({"foo" => "bar"})
54
54
  end
55
+
56
+ context "when the constants: keyword argument is given" do
57
+ let(:path) { File.join(fixtures_dir,'object_v4.pkl') }
58
+
59
+ module TestLoad
60
+ class MyClass
61
+ attr_reader :x, :y
62
+
63
+ def __setstate__(attributes)
64
+ @x = attributes['x']
65
+ @y = attributes['y']
66
+ end
67
+ end
68
+ end
69
+
70
+ let(:constants) do
71
+ {
72
+ '__main__' => {
73
+ 'MyClass' => TestLoad::MyClass
74
+ }
75
+ }
76
+ end
77
+
78
+ it "must map the Python classes to the Ruby classes" do
79
+ expect(subject.load(data, constants: constants)).to be_kind_of(TestLoad::MyClass)
80
+ end
81
+ end
55
82
  end
56
83
 
57
84
  describe ".load_file" do
@@ -60,9 +87,43 @@ describe Python::Pickle do
60
87
  it "must deserialize the Python Pickle data in the given file" do
61
88
  expect(subject.load_file(path)).to eq({"foo" => "bar"})
62
89
  end
90
+
91
+ context "when the constants: keyword argument is given" do
92
+ let(:path) { File.join(fixtures_dir,'object_v4.pkl') }
93
+
94
+ module TestLoad
95
+ class MyClass
96
+ attr_reader :x, :y
97
+
98
+ def __setstate__(attributes)
99
+ @x = attributes['x']
100
+ @y = attributes['y']
101
+ end
102
+ end
103
+ end
104
+
105
+ let(:constants) do
106
+ {
107
+ '__main__' => {
108
+ 'MyClass' => TestLoad::MyClass
109
+ }
110
+ }
111
+ end
112
+
113
+ it "must map the Python classes to the Ruby classes" do
114
+ expect(subject.load_file(path, constants: constants)).to be_kind_of(TestLoad::MyClass)
115
+ end
116
+ end
63
117
  end
64
118
 
65
119
  describe ".dump" do
120
+ let(:object) { Object.new }
121
+
122
+ it do
123
+ expect {
124
+ subject.dump(object)
125
+ }.to raise_error(NotImplementedError,"pickle serializing is currently not supported")
126
+ end
66
127
  end
67
128
 
68
129
  describe ".infer_protocol_version" do