|
| 1 | +from __future__ import absolute_import, division, print_function |
| 2 | + |
| 3 | +import unittest |
| 4 | +import sys |
| 5 | +import stackless |
| 6 | +import contextlib |
| 7 | + |
| 8 | +from support import test_main # @UnusedImport |
| 9 | +from support import StacklessTestCase, testcase_leaks_references |
| 10 | + |
| 11 | + |
| 12 | +class TestStackTransferFailures(StacklessTestCase): |
| 13 | + """Test the error handling of failures in slp_transfer() |
| 14 | +
|
| 15 | + In theory, slp_transfer can't fail, but in reality there bugs. Unfortunately |
| 16 | + it is usually not possible to recover from slp_transfer failures. But we try to avoid |
| 17 | + undefined behavior. |
| 18 | +
|
| 19 | + The test coverage is incomplete, patches are welcome. Especially failed soft-to-hard switches |
| 20 | + result in Py_FatalError("Soft-to-hard tasklet switch failed."). |
| 21 | + """ |
| 22 | + def setUp(self): |
| 23 | + StacklessTestCase.setUp(self) |
| 24 | + |
| 25 | + @contextlib.contextmanager |
| 26 | + def corrupted_cstack_startaddr(self, tasklet): |
| 27 | + """Increment the cstack startaddr of this tasklet. |
| 28 | +
|
| 29 | + Hard-switching to this C-stack will fail. This context is used |
| 30 | + by error handling tests |
| 31 | + """ |
| 32 | + self.assertTrue(tasklet.alive) |
| 33 | + self.assertEqual(tasklet.thread_id, stackless.current.thread_id) |
| 34 | + self.assertFalse(tasklet.restorable) |
| 35 | + self.assertFalse(tasklet.is_current) |
| 36 | + cstate = tasklet.cstate |
| 37 | + self.assertNotEqual(cstate.startaddr, 0) # not invalidated |
| 38 | + self.assertIs(cstate.task, tasklet) |
| 39 | + test_cframe_nr = stackless.test_cframe_nr |
| 40 | + # Otherwise undocumented and only for this test. |
| 41 | + # Add 1 to the startaddr of cstate |
| 42 | + test_cframe_nr(1, cstate_add_addr=cstate) |
| 43 | + try: |
| 44 | + yield |
| 45 | + finally: |
| 46 | + # Restore the pprevious value |
| 47 | + test_cframe_nr(-1, cstate_add_addr=cstate) |
| 48 | + |
| 49 | + def __task(self): |
| 50 | + stackless.schedule_remove(None) |
| 51 | + self.tasklet_done = True |
| 52 | + |
| 53 | + def prepare_tasklet(self, task=None): |
| 54 | + if task is None: |
| 55 | + task = self.__task |
| 56 | + |
| 57 | + self.tasklet_done = False |
| 58 | + t = stackless.tasklet(self.__task)() |
| 59 | + sw = stackless.enable_softswitch(False) |
| 60 | + try: |
| 61 | + t.run() |
| 62 | + finally: |
| 63 | + stackless.enable_softswitch(sw) |
| 64 | + return t |
| 65 | + |
| 66 | + def end(self, t): |
| 67 | + # if t.alive: |
| 68 | + t.run() |
| 69 | + self.assertTrue(self.tasklet_done) |
| 70 | + |
| 71 | + @testcase_leaks_references("unknown") |
| 72 | + def test_scheduler(self): |
| 73 | + t = self.prepare_tasklet() |
| 74 | + with self.corrupted_cstack_startaddr(t): |
| 75 | + t.insert() |
| 76 | + self.assertRaisesRegex(SystemError, 'bad stack reference in transfer', stackless.run) |
| 77 | + self.end(t) |
| 78 | + test_scheduler.enable_softswitch = False # soft switching causes soft-to-hard |
| 79 | + |
| 80 | + @testcase_leaks_references("unknown") |
| 81 | + def test_run(self): |
| 82 | + t = self.prepare_tasklet() |
| 83 | + with self.corrupted_cstack_startaddr(t): |
| 84 | + self.assertRaisesRegex(SystemError, 'bad stack reference in transfer', t.run) |
| 85 | + self.end(t) |
| 86 | + test_run.enable_softswitch = False # soft switching causes soft-to-hard |
| 87 | + |
| 88 | + @testcase_leaks_references("unknown") |
| 89 | + def test_switch(self): |
| 90 | + t = self.prepare_tasklet() |
| 91 | + with self.corrupted_cstack_startaddr(t): |
| 92 | + self.assertRaisesRegex(SystemError, 'bad stack reference in transfer', t.switch) |
| 93 | + self.end(t) |
| 94 | + test_switch.enable_softswitch = False # soft switching causes soft-to-hard |
| 95 | + |
| 96 | + @testcase_leaks_references("unknown") |
| 97 | + def test_kill(self): |
| 98 | + t = self.prepare_tasklet() |
| 99 | + with self.corrupted_cstack_startaddr(t): |
| 100 | + self.assertRaisesRegex(SystemError, 'bad stack reference in transfer', t.kill) |
| 101 | + self.end(t) |
| 102 | + test_kill.enable_softswitch = False # soft switching causes soft-to-hard |
| 103 | + |
| 104 | + |
| 105 | +#/////////////////////////////////////////////////////////////////////////////// |
| 106 | + |
| 107 | +if __name__ == '__main__': |
| 108 | + if not sys.argv[1:]: |
| 109 | + sys.argv.append('-v') |
| 110 | + |
| 111 | + unittest.main() |
0 commit comments