~ubuntu-branches/ubuntu/precise/checkbox/precise

« back to all changes in this revision

Viewing changes to checkbox/contrib/REThread.py

  • Committer: Bazaar Package Importer
  • Author(s): Marc Tardif
  • Date: 2009-01-20 16:46:15 UTC
  • Revision ID: james.westby@ubuntu.com-20090120164615-7iz6nmlef41h4vx2
Tags: 0.4
* Setup bzr-builddeb in native mode.
* Removed LGPL notice from the copyright file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
'''Enhanced threading.Thread which can deliver a return value and propagate
 
2
exceptions from the called thread to the calling thread.
 
3
 
 
4
Copyright (C) 2007 Canonical Ltd.
 
5
Author: Martin Pitt <martin.pitt@ubuntu.com>
 
6
 
 
7
This program is free software; you can redistribute it and/or modify it
 
8
under the terms of the GNU General Public License as published by the
 
9
Free Software Foundation; either version 2 of the License, or (at your
 
10
option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
 
11
the full text of the license.
 
12
'''
 
13
 
 
14
import threading, sys
 
15
 
 
16
class REThread(threading.Thread):
 
17
    '''Enhanced threading.Thread which can deliver a return value and propagate
 
18
    exceptions from the called thread to the calling thread.'''
 
19
 
 
20
    def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
 
21
            verbose=None):
 
22
        '''Initialize Thread, identical to threading.Thread.__init__().'''
 
23
 
 
24
        threading.Thread.__init__(self, group, target, name, args, kwargs,
 
25
            verbose)
 
26
        self.__target = target
 
27
        self.__args = args
 
28
        self.__kwargs = kwargs
 
29
        self._retval = None
 
30
        self._exception = None
 
31
 
 
32
    def run(self):
 
33
        '''Run target function, identical to threading.Thread.run().'''
 
34
 
 
35
        if self.__target:
 
36
            try:
 
37
                self._retval = self.__target(*self.__args, **self.__kwargs)
 
38
            except:
 
39
                self._exception = sys.exc_info()
 
40
 
 
41
    def return_value(self):
 
42
        '''Return value from target function.
 
43
 
 
44
        This can only be called after the thread has finished, i. e. when
 
45
        isAlive() is False and did not terminate with an exception.'''
 
46
 
 
47
        assert not self.isAlive()
 
48
        assert not self._exception
 
49
        return self._retval
 
50
 
 
51
    def exc_info(self):
 
52
        '''Return a tuple (type, value, traceback) of the exception caught in
 
53
        run().'''
 
54
 
 
55
        return self._exception
 
56
 
 
57
    def exc_raise(self):
 
58
        '''Raises the exception caught in the thread.
 
59
 
 
60
        Does nothing if no exception was caught.'''
 
61
 
 
62
        if self._exception:
 
63
            raise self._exception[0], self._exception[1], self._exception[2]
 
64
 
 
65
#
 
66
# Unit test
 
67
#
 
68
 
 
69
if __name__ == '__main__':
 
70
    import unittest, time, traceback, exceptions
 
71
 
 
72
    def idle(seconds):
 
73
        '''Test thread to just wait a bit.'''
 
74
 
 
75
        time.sleep(seconds)
 
76
 
 
77
    def div(x, y):
 
78
        '''Test thread to divide two numbers.'''
 
79
 
 
80
        return x / y
 
81
 
 
82
    class _REThreadTest(unittest.TestCase):
 
83
        def test_return_value(self):
 
84
            '''Test that return value works properly.'''
 
85
 
 
86
            t = REThread(target=div, args=(42, 2))
 
87
            t.start()
 
88
            t.join()
 
89
            # exc_raise() should be a no-op on successful functions
 
90
            t.exc_raise()
 
91
            self.assertEqual(t.return_value(), 21)
 
92
            self.assertEqual(t.exc_info(), None)
 
93
 
 
94
        def test_no_return_value(self):
 
95
            '''Test that REThread works if run() does not return anything.'''
 
96
 
 
97
            t = REThread(target=idle, args=(0.5,))
 
98
            t.start()
 
99
            # thread must be joined first
 
100
            self.assertRaises(AssertionError, t.return_value)
 
101
            t.join()
 
102
            self.assertEqual(t.return_value(), None)
 
103
            self.assertEqual(t.exc_info(), None)
 
104
 
 
105
        def test_exception(self):
 
106
            '''Test that exception in thread is caught and passed.'''
 
107
 
 
108
            t = REThread(target=div, args=(1, 0))
 
109
            t.start()
 
110
            t.join()
 
111
            # thread did not terminate normally, no return value
 
112
            self.assertRaises(AssertionError, t.return_value)
 
113
            self.assert_(t.exc_info()[0] == exceptions.ZeroDivisionError)
 
114
            exc = traceback.format_exception(t.exc_info()[0], t.exc_info()[1],
 
115
                t.exc_info()[2])
 
116
            self.assert_(exc[-1].startswith('ZeroDivisionError'))
 
117
            self.assert_(exc[-2].endswith('return x / y\n'))
 
118
 
 
119
        def test_exc_raise(self):
 
120
            '''Test that exc_raise() raises caught thread exception.'''
 
121
 
 
122
            t = REThread(target=div, args=(1, 0))
 
123
            t.start()
 
124
            t.join()
 
125
            # thread did not terminate normally, no return value
 
126
            self.assertRaises(AssertionError, t.return_value)
 
127
            raised = False
 
128
            try:
 
129
                t.exc_raise()
 
130
            except:
 
131
                raised = True
 
132
                e = sys.exc_info()
 
133
                exc = traceback.format_exception(e[0], e[1], e[2])
 
134
                self.assert_(exc[-1].startswith('ZeroDivisionError'))
 
135
                self.assert_(exc[-2].endswith('return x / y\n'))
 
136
            self.assert_(raised)
 
137
 
 
138
    unittest.main()
 
139