~bzr-pqm/bzr/bzr.dev

4634.85.1 by Andrew Bennetts
Begin defining cleanup helpers and their tests.
1
# Copyright (C) 2009 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
4634.85.5 by Andrew Bennetts
Add unit test for -Dcleanup behaviour.
17
from cStringIO import StringIO
4634.85.1 by Andrew Bennetts
Begin defining cleanup helpers and their tests.
18
import re
19
4634.85.3 by Andrew Bennetts
Lots more tests.
20
from bzrlib.cleanup import (
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
21
    _do_with_cleanups,
4744.3.1 by Andrew Bennetts
Merge do_with_cleanups from cleanup-hof, and drop (or at least make private) everything else from that branch.
22
    _run_cleanup,
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
23
    OperationWithCleanups,
4634.85.3 by Andrew Bennetts
Lots more tests.
24
    )
4634.85.5 by Andrew Bennetts
Add unit test for -Dcleanup behaviour.
25
from bzrlib.tests import TestCase
26
from bzrlib import (
27
    debug,
28
    trace,
29
    )
4634.85.3 by Andrew Bennetts
Lots more tests.
30
31
32
class CleanupsTestCase(TestCase):
33
34
    def setUp(self):
35
        super(CleanupsTestCase, self).setUp()
36
        self.call_log = []
4634.85.1 by Andrew Bennetts
Begin defining cleanup helpers and their tests.
37
38
    def no_op_cleanup(self):
4634.85.3 by Andrew Bennetts
Lots more tests.
39
        self.call_log.append('no_op_cleanup')
40
41
    def assertLogContains(self, regex):
4794.1.15 by Robert Collins
Review feedback.
42
        self.assertContainsRe(self.get_log(), regex, re.DOTALL)
4634.85.3 by Andrew Bennetts
Lots more tests.
43
44
    def failing_cleanup(self):
45
        self.call_log.append('failing_cleanup')
46
        raise Exception("failing_cleanup goes boom!")
47
48
49
class TestRunCleanup(CleanupsTestCase):
4634.85.1 by Andrew Bennetts
Begin defining cleanup helpers and their tests.
50
51
    def test_no_errors(self):
4744.3.1 by Andrew Bennetts
Merge do_with_cleanups from cleanup-hof, and drop (or at least make private) everything else from that branch.
52
        """The function passed to _run_cleanup is run."""
53
        self.assertTrue(_run_cleanup(self.no_op_cleanup))
4634.85.3 by Andrew Bennetts
Lots more tests.
54
        self.assertEqual(['no_op_cleanup'], self.call_log)
4634.85.1 by Andrew Bennetts
Begin defining cleanup helpers and their tests.
55
4634.85.4 by Andrew Bennetts
Another test.
56
    def test_cleanup_with_args_kwargs(self):
57
        def func_taking_args_kwargs(*args, **kwargs):
58
            self.call_log.append(('func', args, kwargs))
4744.3.1 by Andrew Bennetts
Merge do_with_cleanups from cleanup-hof, and drop (or at least make private) everything else from that branch.
59
        _run_cleanup(func_taking_args_kwargs, 'an arg', kwarg='foo')
4634.85.4 by Andrew Bennetts
Another test.
60
        self.assertEqual(
61
            [('func', ('an arg',), {'kwarg': 'foo'})], self.call_log)
4634.85.1 by Andrew Bennetts
Begin defining cleanup helpers and their tests.
62
63
    def test_cleanup_error(self):
4744.3.1 by Andrew Bennetts
Merge do_with_cleanups from cleanup-hof, and drop (or at least make private) everything else from that branch.
64
        """An error from the cleanup function is logged by _run_cleanup, but not
4634.85.3 by Andrew Bennetts
Lots more tests.
65
        propagated.
66
4744.3.1 by Andrew Bennetts
Merge do_with_cleanups from cleanup-hof, and drop (or at least make private) everything else from that branch.
67
        This is there's no way for _run_cleanup to know if there's an existing
4634.85.3 by Andrew Bennetts
Lots more tests.
68
        exception in this situation::
69
            try:
70
              some_func()
71
            finally:
4744.3.1 by Andrew Bennetts
Merge do_with_cleanups from cleanup-hof, and drop (or at least make private) everything else from that branch.
72
              _run_cleanup(cleanup_func)
73
        So, the best _run_cleanup can do is always log errors but never raise
4634.85.3 by Andrew Bennetts
Lots more tests.
74
        them.
75
        """
4744.3.1 by Andrew Bennetts
Merge do_with_cleanups from cleanup-hof, and drop (or at least make private) everything else from that branch.
76
        self.assertFalse(_run_cleanup(self.failing_cleanup))
4634.85.1 by Andrew Bennetts
Begin defining cleanup helpers and their tests.
77
        self.assertLogContains('Cleanup failed:.*failing_cleanup goes boom')
78
4634.85.5 by Andrew Bennetts
Add unit test for -Dcleanup behaviour.
79
    def test_cleanup_error_debug_flag(self):
4634.85.6 by Andrew Bennetts
More tests.
80
        """The -Dcleanup debug flag causes cleanup errors to be reported to the
81
        user.
82
        """
4634.85.5 by Andrew Bennetts
Add unit test for -Dcleanup behaviour.
83
        log = StringIO()
84
        trace.push_log_file(log)
85
        debug.debug_flags.add('cleanup')
4744.3.1 by Andrew Bennetts
Merge do_with_cleanups from cleanup-hof, and drop (or at least make private) everything else from that branch.
86
        self.assertFalse(_run_cleanup(self.failing_cleanup))
4634.85.5 by Andrew Bennetts
Add unit test for -Dcleanup behaviour.
87
        self.assertContainsRe(
88
            log.getvalue(),
89
            "bzr: warning: Cleanup failed:.*failing_cleanup goes boom")
90
4634.85.1 by Andrew Bennetts
Begin defining cleanup helpers and their tests.
91
    def test_prior_error_cleanup_succeeds(self):
4744.3.1 by Andrew Bennetts
Merge do_with_cleanups from cleanup-hof, and drop (or at least make private) everything else from that branch.
92
        """Calling _run_cleanup from a finally block will not interfere with an
4634.85.3 by Andrew Bennetts
Lots more tests.
93
        exception from the try block.
94
        """
4634.85.1 by Andrew Bennetts
Begin defining cleanup helpers and their tests.
95
        def failing_operation():
96
            try:
97
                1/0
98
            finally:
4744.3.1 by Andrew Bennetts
Merge do_with_cleanups from cleanup-hof, and drop (or at least make private) everything else from that branch.
99
                _run_cleanup(self.no_op_cleanup)
4634.85.1 by Andrew Bennetts
Begin defining cleanup helpers and their tests.
100
        self.assertRaises(ZeroDivisionError, failing_operation)
4634.85.3 by Andrew Bennetts
Lots more tests.
101
        self.assertEqual(['no_op_cleanup'], self.call_log)
4634.85.1 by Andrew Bennetts
Begin defining cleanup helpers and their tests.
102
103
    def test_prior_error_cleanup_fails(self):
4744.3.1 by Andrew Bennetts
Merge do_with_cleanups from cleanup-hof, and drop (or at least make private) everything else from that branch.
104
        """Calling _run_cleanup from a finally block will not interfere with an
4634.85.3 by Andrew Bennetts
Lots more tests.
105
        exception from the try block even when the cleanup itself raises an
106
        exception.
107
108
        The cleanup exception will be logged.
109
        """
4634.85.1 by Andrew Bennetts
Begin defining cleanup helpers and their tests.
110
        def failing_operation():
111
            try:
112
                1/0
113
            finally:
4744.3.1 by Andrew Bennetts
Merge do_with_cleanups from cleanup-hof, and drop (or at least make private) everything else from that branch.
114
                _run_cleanup(self.failing_cleanup)
4634.85.1 by Andrew Bennetts
Begin defining cleanup helpers and their tests.
115
        self.assertRaises(ZeroDivisionError, failing_operation)
116
        self.assertLogContains('Cleanup failed:.*failing_cleanup goes boom')
117
4634.85.3 by Andrew Bennetts
Lots more tests.
118
119
class TestDoWithCleanups(CleanupsTestCase):
120
121
    def trivial_func(self):
122
        self.call_log.append('trivial_func')
123
        return 'trivial result'
124
125
    def test_runs_func(self):
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
126
        """_do_with_cleanups runs the function it is given, and returns the
4634.85.3 by Andrew Bennetts
Lots more tests.
127
        result.
128
        """
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
129
        result = _do_with_cleanups([], self.trivial_func)
4634.85.3 by Andrew Bennetts
Lots more tests.
130
        self.assertEqual('trivial result', result)
131
132
    def test_runs_cleanups(self):
133
        """Cleanup functions are run (in the given order)."""
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
134
        cleanup_func_1 = (self.call_log.append, ('cleanup 1',), {})
135
        cleanup_func_2 = (self.call_log.append, ('cleanup 2',), {})
136
        _do_with_cleanups([cleanup_func_1, cleanup_func_2], self.trivial_func)
4634.85.3 by Andrew Bennetts
Lots more tests.
137
        self.assertEqual(
138
            ['trivial_func', 'cleanup 1', 'cleanup 2'], self.call_log)
139
140
    def failing_func(self):
141
        self.call_log.append('failing_func')
142
        1/0
143
144
    def test_func_error_propagates(self):
145
        """Errors from the main function are propagated (after running
146
        cleanups).
147
        """
148
        self.assertRaises(
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
149
            ZeroDivisionError, _do_with_cleanups,
150
            [(self.no_op_cleanup, (), {})], self.failing_func)
4634.85.3 by Andrew Bennetts
Lots more tests.
151
        self.assertEqual(['failing_func', 'no_op_cleanup'], self.call_log)
152
153
    def test_func_error_trumps_cleanup_error(self):
154
        """Errors from the main function a propagated even if a cleanup raises
155
        an error.
156
157
        The cleanup error is be logged.
158
        """
159
        self.assertRaises(
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
160
            ZeroDivisionError, _do_with_cleanups,
161
            [(self.failing_cleanup, (), {})], self.failing_func)
4634.85.3 by Andrew Bennetts
Lots more tests.
162
        self.assertLogContains('Cleanup failed:.*failing_cleanup goes boom')
163
164
    def test_func_passes_and_error_from_cleanup(self):
165
        """An error from a cleanup is propagated when the main function doesn't
166
        raise an error.  Later cleanups are still executed.
167
        """
168
        exc = self.assertRaises(
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
169
            Exception, _do_with_cleanups,
170
            [(self.failing_cleanup, (), {}), (self.no_op_cleanup, (), {})],
171
            self.trivial_func)
4634.85.3 by Andrew Bennetts
Lots more tests.
172
        self.assertEqual('failing_cleanup goes boom!', exc.args[0])
173
        self.assertEqual(
174
            ['trivial_func', 'failing_cleanup', 'no_op_cleanup'],
175
            self.call_log)
176
177
    def test_multiple_cleanup_failures(self):
178
        """When multiple cleanups fail (as tends to happen when something has
179
        gone wrong), the first error is propagated, and subsequent errors are
180
        logged.
181
        """
4634.85.6 by Andrew Bennetts
More tests.
182
        cleanups = self.make_two_failing_cleanup_funcs()
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
183
        self.assertRaises(ErrorA, _do_with_cleanups, cleanups,
184
            self.trivial_func)
4634.85.3 by Andrew Bennetts
Lots more tests.
185
        self.assertLogContains('Cleanup failed:.*ErrorB')
4794.1.15 by Robert Collins
Review feedback.
186
        self.assertFalse('ErrorA' in self.get_log())
4634.85.3 by Andrew Bennetts
Lots more tests.
187
4634.85.6 by Andrew Bennetts
More tests.
188
    def make_two_failing_cleanup_funcs(self):
189
        def raise_a():
190
            raise ErrorA('Error A')
191
        def raise_b():
192
            raise ErrorB('Error B')
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
193
        return [(raise_a, (), {}), (raise_b, (), {})]
4634.85.6 by Andrew Bennetts
More tests.
194
195
    def test_multiple_cleanup_failures_debug_flag(self):
196
        log = StringIO()
197
        trace.push_log_file(log)
198
        debug.debug_flags.add('cleanup')
199
        cleanups = self.make_two_failing_cleanup_funcs()
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
200
        self.assertRaises(ErrorA, _do_with_cleanups, cleanups,
201
            self.trivial_func)
4634.85.6 by Andrew Bennetts
More tests.
202
        self.assertContainsRe(
203
            log.getvalue(), "bzr: warning: Cleanup failed:.*Error B\n")
204
        self.assertEqual(1, log.getvalue().count('bzr: warning:'),
205
                log.getvalue())
206
207
    def test_func_and_cleanup_errors_debug_flag(self):
208
        log = StringIO()
209
        trace.push_log_file(log)
210
        debug.debug_flags.add('cleanup')
211
        cleanups = self.make_two_failing_cleanup_funcs()
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
212
        self.assertRaises(ZeroDivisionError, _do_with_cleanups, cleanups,
213
            self.failing_func)
4634.85.6 by Andrew Bennetts
More tests.
214
        self.assertContainsRe(
215
            log.getvalue(), "bzr: warning: Cleanup failed:.*Error A\n")
216
        self.assertContainsRe(
217
            log.getvalue(), "bzr: warning: Cleanup failed:.*Error B\n")
218
        self.assertEqual(2, log.getvalue().count('bzr: warning:'))
219
4634.85.3 by Andrew Bennetts
Lots more tests.
220
    def test_func_may_mutate_cleanups(self):
221
        """The main func may mutate the cleanups before it returns.
222
        
223
        This allows a function to gradually add cleanups as it acquires
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
224
        resources, rather than planning all the cleanups up-front.  The
225
        OperationWithCleanups helper relies on this working.
4634.85.3 by Andrew Bennetts
Lots more tests.
226
        """
227
        cleanups_list = []
228
        def func_that_adds_cleanups():
229
            self.call_log.append('func_that_adds_cleanups')
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
230
            cleanups_list.append((self.no_op_cleanup, (), {}))
4634.85.3 by Andrew Bennetts
Lots more tests.
231
            return 'result'
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
232
        result = _do_with_cleanups(cleanups_list, func_that_adds_cleanups)
4634.85.3 by Andrew Bennetts
Lots more tests.
233
        self.assertEqual('result', result)
234
        self.assertEqual(
235
            ['func_that_adds_cleanups', 'no_op_cleanup'], self.call_log)
4634.85.6 by Andrew Bennetts
More tests.
236
237
    def test_cleanup_error_debug_flag(self):
238
        """The -Dcleanup debug flag causes cleanup errors to be reported to the
239
        user.
240
        """
241
        log = StringIO()
242
        trace.push_log_file(log)
243
        debug.debug_flags.add('cleanup')
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
244
        self.assertRaises(ZeroDivisionError, _do_with_cleanups,
245
            [(self.failing_cleanup, (), {})], self.failing_func)
4634.85.6 by Andrew Bennetts
More tests.
246
        self.assertContainsRe(
247
            log.getvalue(),
248
            "bzr: warning: Cleanup failed:.*failing_cleanup goes boom")
249
        self.assertEqual(1, log.getvalue().count('bzr: warning:'))
250
251
252
class ErrorA(Exception): pass
253
class ErrorB(Exception): pass
4744.3.4 by Andrew Bennetts
Make OperationWithCleanups the only public API in bzrlib.cleanup, add test for it, add support for *args and **kwargs for func and for cleanups, use deque.appendleft rather than list.insert(0, ...).
254
255
256
class TestOperationWithCleanups(CleanupsTestCase):
257
258
    def test_cleanup_ordering(self):
259
        """Cleanups are added in LIFO order.
260
261
        So cleanups added before run is called are run last, and the last
262
        cleanup added during the func is run first.
263
        """
264
        call_log = []
265
        def func(op, foo):
266
            call_log.append(('func called', foo))
267
            op.add_cleanup(call_log.append, 'cleanup 2')
268
            op.add_cleanup(call_log.append, 'cleanup 1')
269
            return 'result'
270
        owc = OperationWithCleanups(func)
271
        owc.add_cleanup(call_log.append, 'cleanup 4')
272
        owc.add_cleanup(call_log.append, 'cleanup 3')
273
        result = owc.run('foo')
274
        self.assertEqual('result', result)
275
        self.assertEqual(
276
            [('func called', 'foo'), 'cleanup 1', 'cleanup 2', 'cleanup 3',
277
            'cleanup 4'], call_log)
278