99import io
1010import _pyio as pyio
1111import sys
12+ import pickle
1213
1314class MemorySeekTestMixin :
1415
@@ -346,6 +347,42 @@ def test_instance_dict_leak(self):
346347 memio = self .ioclass ()
347348 memio .foo = 1
348349
350+ def test_pickling (self ):
351+ buf = self .buftype ("1234567890" )
352+ memio = self .ioclass (buf )
353+ memio .foo = 42
354+ memio .seek (2 )
355+
356+ class PickleTestMemIO (self .ioclass ):
357+ def __init__ (me , initvalue , foo ):
358+ self .ioclass .__init__ (me , initvalue )
359+ me .foo = foo
360+ # __getnewargs__ is undefined on purpose. This checks that PEP 307
361+ # is used to provide pickling support.
362+
363+ # Pickle expects the class to be on the module level. Here we use a
364+ # little hack to allow the PickleTestMemIO class to derive from
365+ # self.ioclass without having to define all combinations explictly on
366+ # the module-level.
367+ import __main__
368+ PickleTestMemIO .__module__ = '__main__'
369+ __main__ .PickleTestMemIO = PickleTestMemIO
370+ submemio = PickleTestMemIO (buf , 80 )
371+ submemio .seek (2 )
372+
373+ # We only support pickle protocol 2 and onward since we use extended
374+ # __reduce__ API of PEP 307 to provide pickling support.
375+ for proto in range (2 , pickle .HIGHEST_PROTOCOL ):
376+ for obj in (memio , submemio ):
377+ obj2 = pickle .loads (pickle .dumps (obj , protocol = proto ))
378+ self .assertEqual (obj .getvalue (), obj2 .getvalue ())
379+ self .assertEqual (obj .__class__ , obj2 .__class__ )
380+ self .assertEqual (obj .foo , obj2 .foo )
381+ self .assertEqual (obj .tell (), obj2 .tell ())
382+ obj .close ()
383+ self .assertRaises (ValueError , pickle .dumps , obj , proto )
384+ del __main__ .PickleTestMemIO
385+
349386
350387class PyBytesIOTest (MemoryTestMixin , MemorySeekTestMixin , unittest .TestCase ):
351388
@@ -425,13 +462,26 @@ def test_bytes_array(self):
425462 self .assertEqual (memio .getvalue (), buf )
426463
427464
428- class PyStringIOTest (MemoryTestMixin , MemorySeekTestMixin , unittest .TestCase ):
429- buftype = str
430- ioclass = pyio .StringIO
431- UnsupportedOperation = pyio .UnsupportedOperation
432- EOF = ""
465+ class TextIOTestMixin :
433466
434- # TextIO-specific behaviour.
467+ def test_relative_seek (self ):
468+ memio = self .ioclass ()
469+
470+ self .assertRaises (IOError , memio .seek , - 1 , 1 )
471+ self .assertRaises (IOError , memio .seek , 3 , 1 )
472+ self .assertRaises (IOError , memio .seek , - 3 , 1 )
473+ self .assertRaises (IOError , memio .seek , - 1 , 2 )
474+ self .assertRaises (IOError , memio .seek , 1 , 1 )
475+ self .assertRaises (IOError , memio .seek , 1 , 2 )
476+
477+ def test_textio_properties (self ):
478+ memio = self .ioclass ()
479+
480+ # These are just dummy values but we nevertheless check them for fear
481+ # of unexpected breakage.
482+ self .assertTrue (memio .encoding is None )
483+ self .assertEqual (memio .errors , "strict" )
484+ self .assertEqual (memio .line_buffering , False )
435485
436486 def test_newlines_property (self ):
437487 memio = self .ioclass (newline = None )
@@ -513,15 +563,13 @@ def test_newline_lf(self):
513563 def test_newline_cr (self ):
514564 # newline="\r"
515565 memio = self .ioclass ("a\n b\r \n c\r d" , newline = "\r " )
516- memio .seek (0 )
517566 self .assertEqual (memio .read (), "a\r b\r \r c\r d" )
518567 memio .seek (0 )
519568 self .assertEqual (list (memio ), ["a\r " , "b\r " , "\r " , "c\r " , "d" ])
520569
521570 def test_newline_crlf (self ):
522571 # newline="\r\n"
523572 memio = self .ioclass ("a\n b\r \n c\r d" , newline = "\r \n " )
524- memio .seek (0 )
525573 self .assertEqual (memio .read (), "a\r \n b\r \r \n c\r d" )
526574 memio .seek (0 )
527575 self .assertEqual (list (memio ), ["a\r \n " , "b\r \r \n " , "c\r d" ])
@@ -539,10 +587,59 @@ def test_newline_argument(self):
539587 self .ioclass (newline = newline )
540588
541589
590+ class PyStringIOTest (MemoryTestMixin , MemorySeekTestMixin ,
591+ TextIOTestMixin , unittest .TestCase ):
592+ buftype = str
593+ ioclass = pyio .StringIO
594+ UnsupportedOperation = pyio .UnsupportedOperation
595+ EOF = ""
596+
597+
598+ class PyStringIOPickleTest (TextIOTestMixin , unittest .TestCase ):
599+ """Test if pickle restores properly the internal state of StringIO.
600+ """
601+ buftype = str
602+ UnsupportedOperation = pyio .UnsupportedOperation
603+ EOF = ""
604+
605+ class ioclass (pyio .StringIO ):
606+ def __new__ (cls , * args , ** kwargs ):
607+ return pickle .loads (pickle .dumps (pyio .StringIO (* args , ** kwargs )))
608+ def __init__ (self , * args , ** kwargs ):
609+ pass
610+
611+
542612class CBytesIOTest (PyBytesIOTest ):
543613 ioclass = io .BytesIO
544614 UnsupportedOperation = io .UnsupportedOperation
545615
616+ def test_getstate (self ):
617+ memio = self .ioclass ()
618+ state = memio .__getstate__ ()
619+ self .assertEqual (len (state ), 3 )
620+ bytearray (state [0 ]) # Check if state[0] supports the buffer interface.
621+ self .assert_ (isinstance (state [1 ], int ))
622+ self .assert_ (isinstance (state [2 ], dict ) or state [2 ] is None )
623+ memio .close ()
624+ self .assertRaises (ValueError , memio .__getstate__ )
625+
626+ def test_setstate (self ):
627+ # This checks whether __setstate__ does proper input validation.
628+ memio = self .ioclass ()
629+ memio .__setstate__ ((b"no error" , 0 , None ))
630+ memio .__setstate__ ((bytearray (b"no error" ), 0 , None ))
631+ memio .__setstate__ ((b"no error" , 0 , {'spam' : 3 }))
632+ self .assertRaises (ValueError , memio .__setstate__ , (b"" , - 1 , None ))
633+ self .assertRaises (TypeError , memio .__setstate__ , ("unicode" , 0 , None ))
634+ self .assertRaises (TypeError , memio .__setstate__ , (b"" , 0.0 , None ))
635+ self .assertRaises (TypeError , memio .__setstate__ , (b"" , 0 , 0 ))
636+ self .assertRaises (TypeError , memio .__setstate__ , (b"len-test" , 0 ))
637+ self .assertRaises (TypeError , memio .__setstate__ )
638+ self .assertRaises (TypeError , memio .__setstate__ , 0 )
639+ memio .close ()
640+ self .assertRaises (ValueError , memio .__setstate__ , (b"closed" , 0 , None ))
641+
642+
546643class CStringIOTest (PyStringIOTest ):
547644 ioclass = io .StringIO
548645 UnsupportedOperation = io .UnsupportedOperation
@@ -561,9 +658,48 @@ def test_widechar(self):
561658 self .assertEqual (memio .tell (), len (buf ) * 2 )
562659 self .assertEqual (memio .getvalue (), buf + buf )
563660
661+ def test_getstate (self ):
662+ memio = self .ioclass ()
663+ state = memio .__getstate__ ()
664+ self .assertEqual (len (state ), 4 )
665+ self .assert_ (isinstance (state [0 ], str ))
666+ self .assert_ (isinstance (state [1 ], str ))
667+ self .assert_ (isinstance (state [2 ], int ))
668+ self .assert_ (isinstance (state [3 ], dict ) or state [3 ] is None )
669+ memio .close ()
670+ self .assertRaises (ValueError , memio .__getstate__ )
671+
672+ def test_setstate (self ):
673+ # This checks whether __setstate__ does proper input validation.
674+ memio = self .ioclass ()
675+ memio .__setstate__ (("no error" , "\n " , 0 , None ))
676+ memio .__setstate__ (("no error" , "" , 0 , {'spam' : 3 }))
677+ self .assertRaises (ValueError , memio .__setstate__ , ("" , "f" , 0 , None ))
678+ self .assertRaises (ValueError , memio .__setstate__ , ("" , "" , - 1 , None ))
679+ self .assertRaises (TypeError , memio .__setstate__ , (b"" , "" , 0 , None ))
680+ self .assertRaises (TypeError , memio .__setstate__ , ("" , b"" , 0 , None ))
681+ self .assertRaises (TypeError , memio .__setstate__ , ("" , "" , 0.0 , None ))
682+ self .assertRaises (TypeError , memio .__setstate__ , ("" , "" , 0 , 0 ))
683+ self .assertRaises (TypeError , memio .__setstate__ , ("len-test" , 0 ))
684+ self .assertRaises (TypeError , memio .__setstate__ )
685+ self .assertRaises (TypeError , memio .__setstate__ , 0 )
686+ memio .close ()
687+ self .assertRaises (ValueError , memio .__setstate__ , ("closed" , "" , 0 , None ))
688+
689+
690+ class CStringIOPickleTest (PyStringIOPickleTest ):
691+ UnsupportedOperation = io .UnsupportedOperation
692+
693+ class ioclass (io .StringIO ):
694+ def __new__ (cls , * args , ** kwargs ):
695+ return pickle .loads (pickle .dumps (io .StringIO (* args , ** kwargs )))
696+ def __init__ (self , * args , ** kwargs ):
697+ pass
698+
564699
565700def test_main ():
566- tests = [PyBytesIOTest , PyStringIOTest , CBytesIOTest , CStringIOTest ]
701+ tests = [PyBytesIOTest , PyStringIOTest , CBytesIOTest , CStringIOTest ,
702+ PyStringIOPickleTest , CStringIOPickleTest ]
567703 support .run_unittest (* tests )
568704
569705if __name__ == '__main__' :
0 commit comments