Visitar URL original
Issue #114: Test suite: test all pickle protocols · akruis/cpython@851b549 · GitHub
Skip to content

Commit 851b549

Browse files
author
Anselm Kruis
committed
Issue python#114: Test suite: test all pickle protocols
Enhance the test suite to test all pickle protocols using both - the C and the Python - pickle implementations. https://bitbucket.org/stackless-dev/stackless/issues/114 (grafted from 5da071b503d30801d5eaf3f44b5d3854f9b2c176)
1 parent ba641f7 commit 851b549

4 files changed

Lines changed: 125 additions & 111 deletions

File tree

‎Stackless/unittests/support.py‎

Lines changed: 105 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@
2929
import weakref
3030
import pickle
3131
import inspect
32-
from io import StringIO
32+
import io
3333
import contextlib
3434
import gc
3535

3636
# emit warnings about uncollectable objects
3737
gc.set_debug(gc.DEBUG_UNCOLLECTABLE)
3838
try:
39-
long
39+
long # @UndefinedVariable
4040
except NameError:
4141
long = int # @ReservedAssignment
4242

@@ -50,7 +50,7 @@
5050
@contextlib.contextmanager
5151
def captured_stderr():
5252
old = sys.stderr
53-
new = StringIO()
53+
new = io.StringIO()
5454
sys.stderr = new
5555
try:
5656
yield new
@@ -172,6 +172,77 @@ def __init__(cls, *args, **kw):
172172

173173
class StacklessTestCase(unittest.TestCase, StacklessTestCaseMixin, metaclass=StacklessTestCaseMeta):
174174

175+
@classmethod
176+
def prepare_test_method(cls, func, name):
177+
"""Called after class creation
178+
179+
This method creates the _H methods, which run without
180+
soft switching
181+
"""
182+
if hasattr(func, "enable_softswitch") and not getattr(func, "_H_created", False):
183+
return ((func, name), )
184+
185+
def wrapper_hardswitch(self, method=func):
186+
self.assertTrue(self.__setup_called, "Broken test case: it didn't call super(..., self).setUp()")
187+
self.assertFalse(stackless.enable_softswitch(None), "softswitch is enabled")
188+
return method(self)
189+
wrapper_hardswitch.enable_softswitch = False
190+
wrapper_hardswitch.__name__ = name + "_H"
191+
if func.__doc__:
192+
doc = func.__doc__
193+
if doc.startswith("(soft) "):
194+
doc = doc[7:]
195+
wrapper_hardswitch.__doc__ = "(hard) " + doc
196+
setattr(cls, wrapper_hardswitch.__name__, wrapper_hardswitch)
197+
198+
if not hasattr(func, "_H_created"):
199+
func._H_created = True
200+
func.enable_softswitch = True
201+
if func.__doc__:
202+
func.__doc__ = "(soft) " + func.__doc__
203+
return ((func, name), (wrapper_hardswitch, wrapper_hardswitch.__name__))
204+
205+
@classmethod
206+
def prepare_pickle_test_method(cls, func, name=None):
207+
"""Called after class creation
208+
209+
This method creates the Py0...n C0...n methods, which run with
210+
the Python or C implementation of the enumerated pickle protocol.
211+
212+
This method also acts as a method decorator.
213+
"""
214+
if name is None:
215+
# used as a decorator
216+
func.prepare = cls.prepare_pickle_test_method
217+
return func
218+
219+
if hasattr(func, "_pickle_created"):
220+
return StacklessTestCase.prepare_test_method.__func__(cls, func, name)
221+
setattr(cls, name, None)
222+
r = []
223+
for i in range(0, pickle.HIGHEST_PROTOCOL + 1):
224+
for p_letter in ("C", "P"):
225+
def test(self, method=func, proto=i, pickle_module=p_letter, unpickle_module=p_letter):
226+
self.assertTrue(self._StacklessTestCase__setup_called, "Broken test case: it didn't call super(..., self).setUp()")
227+
self._pickle_protocol = proto
228+
self._pickle_module = pickle_module
229+
self._unpickle_module = unpickle_module
230+
return method(self)
231+
if i == 0 and p_letter == "C":
232+
test.__name__ = name
233+
else:
234+
test.__name__ = "{:s}_{:s}{:d}".format(name, p_letter, i)
235+
test._pickle_created = True
236+
if func.__doc__:
237+
doc = func.__doc__
238+
match = re.match(r"\([PC][0-{:d}]\)".format(pickle.HIGHEST_PROTOCOL), doc)
239+
if match:
240+
doc = match.string[match.end():]
241+
test.__doc__ = "({:s}{:d}) {:s}".format(p_letter, i, doc)
242+
setattr(cls, test.__name__, test)
243+
r.extend(StacklessTestCase.prepare_test_method.__func__(cls, test, test.__name__))
244+
return r
245+
175246
@classmethod
176247
def prepare_test_methods(cls):
177248
"""Called after class creation
@@ -183,29 +254,10 @@ def prepare_test_methods(cls):
183254
for n in names:
184255
m = getattr(cls, n)
185256
if inspect.ismethod(m):
186-
m = m.im_func
187-
if hasattr(m, "enable_softswitch") and not getattr(m, "_H_created", False):
188-
continue
189-
190-
def wrapper_hardswitch(self, method=m):
191-
self.assertTrue(self.__setup_called, "Broken test case: it didn't call super(..., self).setUp()")
192-
self.assertFalse(stackless.enable_softswitch(None), "softswitch is enabled")
193-
return method(self)
194-
wrapper_hardswitch.enable_softswitch = False
195-
wrapper_hardswitch.__name__ = n + "_H"
196-
if m.__doc__:
197-
doc = m.__doc__
198-
if doc.startswith("(soft) "):
199-
doc = doc[7:]
200-
wrapper_hardswitch.__doc__ = "(hard) " + doc
201-
setattr(cls, wrapper_hardswitch.__name__, wrapper_hardswitch)
202-
203-
if hasattr(m, "_H_created"):
204-
continue
205-
m._H_created = True
206-
m.enable_softswitch = True
207-
if m.__doc__:
208-
m.__doc__ = "(soft) " + m.__doc__
257+
m = m.__func__
258+
prepare = getattr(m, "prepare", cls.prepare_test_method)
259+
for x in prepare.__func__(cls, m, n):
260+
pass
209261

210262
__setup_called = False
211263
__preexisting_threads = None
@@ -233,7 +285,7 @@ def setUpStacklessTestCase(self):
233285
234286
This method must be called from :meth:`setUp`.
235287
"""
236-
self.__setup_called = True
288+
self._StacklessTestCase__setup_called = True
237289
self.addCleanup(stackless.enable_softswitch, stackless.enable_softswitch(self.__enable_softswitch))
238290

239291
self.__active_test_cases[id(self)] = self
@@ -278,6 +330,24 @@ def tearDown(self):
278330
self.assertEqual(active_count, expected_thread_count, "Leakage from other threads, with %d threads running (%d expected)" % (active_count, expected_thread_count))
279331
gc.collect() # emits warnings about uncollectable objects after each test
280332

333+
def dumps(self, obj, protocol=None, *, fix_imports=True):
334+
if self._pickle_module == "P":
335+
f = io.BytesIO()
336+
pickle._Pickler(f, protocol, fix_imports=fix_imports).dump(obj)
337+
return f.getvalue()
338+
elif self._pickle_module == "C":
339+
return pickle.dumps(obj, protocol=protocol, fix_imports=fix_imports)
340+
raise ValueError("Invalid pickle module")
341+
342+
def loads(self, s, *, fix_imports=True, encoding="ASCII", errors="strict"):
343+
if self._pickle_module == "P":
344+
file = io.BytesIO(s)
345+
return pickle._Unpickler(file, fix_imports=fix_imports,
346+
encoding=encoding, errors=errors).load()
347+
elif self._pickle_module == "C":
348+
return pickle.loads(s, fix_imports=fix_imports, encoding=encoding, errors=errors)
349+
raise ValueError("Invalid pickle module")
350+
281351
# limited pickling support for test cases
282352
# Between setUp() and tearDown() the test-case has a
283353
# working __reduce__ method. Later the test case gets pickled by
@@ -331,6 +401,14 @@ def _addSkip(self, result, reason):
331401
del _tc
332402

333403

404+
class StacklessPickleTestCase(StacklessTestCase):
405+
"""A test case class for pickle tests"""
406+
407+
@classmethod
408+
def prepare_test_method(cls, func, name):
409+
return cls.prepare_pickle_test_method(func, name)
410+
411+
334412
def restore_testcase_from_id(id_):
335413
return StacklessTestCase.restore_testcase_from_id(id_)
336414

‎Stackless/unittests/test_miscell.py‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# import common
22

3-
import pickle
43
import unittest
54
import stackless
65
import sys
@@ -82,14 +81,16 @@ def test_aliveness1(self):
8281
t = stackless.tasklet(runtask)()
8382
self.lifecycle(t)
8483

84+
@StacklessTestCase.prepare_pickle_test_method
8585
def test_aliveness2(self):
8686
""" Same as 1, but with a pickled unrun tasklet. """
8787
t = stackless.tasklet(runtask)()
88-
t_new = pickle.loads(pickle.dumps((t)))
88+
t_new = self.loads(self.dumps((t)))
8989
t.remove()
9090
t_new.insert()
9191
self.lifecycle(t_new)
9292

93+
@StacklessTestCase.prepare_pickle_test_method
9394
def test_aliveness3(self):
9495
""" Same as 1, but with a pickled run(slightly) tasklet. """
9596

@@ -114,8 +115,8 @@ def test_aliveness3(self):
114115
self.assertEqual(t.recursion_depth, softSwitching and 1 or 2)
115116

116117
# Now save & load
117-
dumped = pickle.dumps(t)
118-
t_new = pickle.loads(dumped)
118+
dumped = self.dumps(t)
119+
t_new = self.loads(dumped)
119120

120121
# Remove and insert & swap names around a bit
121122
t.remove()

‎Stackless/unittests/test_pickle.py‎

Lines changed: 8 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import sys
22
import types
33
import unittest
4-
import pickle
54
import gc
6-
import io
75
import inspect
86

97
from stackless import schedule, tasklet, stackless
108

11-
from support import StacklessTestCase
9+
from support import StacklessTestCase, StacklessPickleTestCase
1210

1311

1412
# because test runner instances in the testsuite contain copies of the old stdin/stdout thingies,
@@ -245,32 +243,10 @@ def is_soft():
245243
return softswitch
246244

247245

248-
class PyPickleMixin(object):
249-
250-
def dumps(self, obj, protocol=None, *, fix_imports=True):
251-
f = io.BytesIO()
252-
pickle._Pickler(f, protocol, fix_imports=fix_imports).dump(obj)
253-
return f.getvalue()
254-
255-
def loads(self, s, *, fix_imports=True, encoding="ASCII", errors="strict"):
256-
file = io.BytesIO(s)
257-
return pickle._Unpickler(file, fix_imports=fix_imports,
258-
encoding=encoding, errors=errors).load()
259-
260-
261-
class CPickleMixin(object):
262-
263-
def dumps(self, obj, protocol=None, *, fix_imports=True):
264-
return pickle.dumps(obj, protocol=protocol, fix_imports=fix_imports)
265-
266-
def loads(self, s, *, fix_imports=True, encoding="ASCII", errors="strict"):
267-
return pickle.loads(s, fix_imports=fix_imports, encoding=encoding, errors=errors)
268-
269-
270-
class AbstractTestPickledTasklets(StacklessTestCase):
246+
class TestPickledTasklets(StacklessPickleTestCase):
271247

272248
def setUp(self):
273-
super(AbstractTestPickledTasklets, self).setUp()
249+
super(TestPickledTasklets, self).setUp()
274250
self.verbose = VERBOSE
275251

276252
def tearDown(self):
@@ -283,7 +259,7 @@ def tearDown(self):
283259
current.kill()
284260
current = next
285261

286-
super(AbstractTestPickledTasklets, self).tearDown()
262+
super(TestPickledTasklets, self).tearDown()
287263

288264
del self.verbose
289265

@@ -330,24 +306,6 @@ def run_pickled(self, func, *args):
330306
self.assertNotEqual(new_ident, old_ident)
331307
self.assertEqual(is_empty(), True)
332308

333-
# compatibility to 2.2.3
334-
global have_enumerate
335-
try:
336-
enumerate
337-
have_enumerate = True
338-
except NameError:
339-
have_enumerate = False
340-
341-
global have_fromkeys
342-
try:
343-
{}.fromkeys
344-
have_fromkeys = True
345-
except AttributeError:
346-
have_fromkeys = False
347-
348-
349-
class PickledTaskletTestCases(object):
350-
351309
def testClassPersistence(self):
352310
t1 = CustomTasklet(nothing)()
353311
s = self.dumps(t1)
@@ -387,9 +345,8 @@ def testDictIteritems(self):
387345
def testSet(self):
388346
self.run_pickled(settest, 20, 13)
389347

390-
if have_enumerate:
391-
def testEnumerate(self):
392-
self.run_pickled(enumeratetest, 20, 13)
348+
def testEnumerate(self):
349+
self.run_pickled(enumeratetest, 20, 13)
393350

394351
def testTuple(self):
395352
self.run_pickled(tupletest, 20, 13)
@@ -535,14 +492,6 @@ def testFunctionModulePreservation(self):
535492
self.assertEqual(f1.__module__, f2.__module__)
536493

537494

538-
class TestPickledTaskletsPy(AbstractTestPickledTasklets, PickledTaskletTestCases, PyPickleMixin):
539-
pass
540-
541-
542-
class TestPickledTaskletsC(AbstractTestPickledTasklets, PickledTaskletTestCases, CPickleMixin):
543-
pass
544-
545-
546495
class TestFramePickling(StacklessTestCase):
547496

548497
def testLocalplus(self):
@@ -583,7 +532,7 @@ def func(current):
583532
self.assertIs(cell.cell_contents, result)
584533

585534

586-
class DictViewPicklingTestCases(object):
535+
class TestDictViewPickling(StacklessPickleTestCase):
587536

588537
def testDictKeyViewPickling(self):
589538
# stackless python prior to 2.7.3 used to register its own __reduce__
@@ -605,15 +554,7 @@ def testDictValueViewPickling(self):
605554
self.assertEqual(list(view1), list(view2))
606555

607556

608-
class TestDictViewPicklingPy(AbstractTestPickledTasklets, DictViewPicklingTestCases, PyPickleMixin):
609-
pass
610-
611-
612-
class TestDictViewPicklingC(AbstractTestPickledTasklets, DictViewPicklingTestCases, CPickleMixin):
613-
pass
614-
615-
616-
class Traceback_TestCases(object):
557+
class TestTraceback(StacklessPickleTestCase):
617558
def testTracebackFrameLinkage(self):
618559
def a():
619560
# raise an exception
@@ -649,13 +590,6 @@ def c():
649590
self.assertListEqual([i[1:] for i in all_outerframes_orig[:l - 1]], [i[1:] for i in all_outerframes[:l - 1]])
650591

651592

652-
class TestTracebackPy(StacklessTestCase, Traceback_TestCases, PyPickleMixin):
653-
pass
654-
655-
656-
class TestTracebackC(StacklessTestCase, Traceback_TestCases, CPickleMixin):
657-
pass
658-
659593
if __name__ == '__main__':
660594
if not sys.argv[1:]:
661595
sys.argv.append('-v')

0 commit comments

Comments
 (0)