/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
1
# Copyright (C) 2005 by 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
18
"""Enhanced layer on unittest.
19
20
This does several things:
21
22
* nicer reporting as tests run
23
24
* test code can log messages into a buffer that is recorded to disk
25
  and displayed if the test fails
26
27
* tests can be run in a separate directory, which is useful for code that
28
  wants to create files
29
30
* utilities to run external commands and check their return code
31
  and/or output
32
1092.1.2 by Robert Collins
switch testsweet.TestBase to TestCase which is convention for xUnit
33
Test cases should normally subclass testsweet.TestCase.  The test runner should
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
34
call runsuite().
35
36
This is meant to become independent of bzr, though that's not quite
37
true yet.
38
"""  
39
1092.1.2 by Robert Collins
switch testsweet.TestBase to TestCase which is convention for xUnit
40
import unittest
1092.1.4 by Robert Collins
move error printing into the TestResult and use the TextTestRunner from unittest
41
import sys
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
42
992 by Martin Pool
doc
43
# XXX: Don't need this anymore now we depend on python2.4
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
44
def _need_subprocess():
45
    sys.stderr.write("sorry, this test suite requires the subprocess module\n"
46
                     "this is shipped with python2.4 and available separately for 2.3\n")
47
    
48
49
class CommandFailed(Exception):
50
    pass
51
52
53
class TestSkipped(Exception):
54
    """Indicates that a test was intentionally skipped, rather than failing."""
55
    # XXX: Not used yet
56
57
1092.1.2 by Robert Collins
switch testsweet.TestBase to TestCase which is convention for xUnit
58
class TestCase(unittest.TestCase):
1092.1.12 by Robert Collins
rename InTempDir to FunctionalTestCase providing InTempDir for compatability
59
    """Base class for bzr unit tests.
60
    
61
    Tests that need access to disk resources should subclass 
62
    FunctionalTestCase not TestCase.
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
63
    """
64
    
65
    # TODO: Special methods to invoke bzr, so that we can run it
66
    # through a specified Python intepreter
67
68
    OVERRIDE_PYTHON = None # to run with alternative python 'python'
69
    BZRPATH = 'bzr'
70
1092.1.16 by Robert Collins
provide a helper to redirect output as desired
71
    def apply_redirected(self, stdin=None, stdout=None, stderr=None,
72
                         a_callable=None, *args, **kwargs):
73
        """Call callable with redirected std io pipes.
74
75
        Returns the return code."""
76
        from StringIO import StringIO
77
        if not callable(a_callable):
78
            raise ValueError("a_callable must be callable.")
79
        if stdin is None:
80
            stdin = StringIO("")
81
        if stdout is None:
82
            stdout = self.TEST_LOG
83
        if stderr is None:
84
            stderr = self.TEST_LOG
85
        real_stdin = sys.stdin
86
        real_stdout = sys.stdout
87
        real_stderr = sys.stderr
88
        result = None
89
        try:
90
            sys.stdout = stdout
91
            sys.stderr = stderr
92
            sys.stdin = stdin
93
            result = a_callable(*args, **kwargs)
94
        finally:
95
            sys.stdout = real_stdout
96
            sys.stderr = real_stderr
97
            sys.stdin = real_stdin
98
        return result
99
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
100
    def setUp(self):
1092.1.2 by Robert Collins
switch testsweet.TestBase to TestCase which is convention for xUnit
101
        super(TestCase, self).setUp()
1092.1.7 by Robert Collins
make test log files per case so we get all the content from the test, not just the stuff logged to the tests log buffer
102
        # setup a temporary log for the test 
103
        import tempfile
104
        self.TEST_LOG = tempfile.NamedTemporaryFile(mode='wt', bufsize=0)
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
105
        self.log("%s setup" % self.id())
106
107
    def tearDown(self):
108
        self.log("%s teardown" % self.id())
109
        self.log('')
1092.1.6 by Robert Collins
move io redirection into each test case from the global runner
110
        super(TestCase, self).tearDown()
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
111
112
    def log(self, msg):
113
        """Log a message to a progress file"""
114
        print >>self.TEST_LOG, msg
115
116
    def check_inventory_shape(self, inv, shape):
117
        """
118
        Compare an inventory to a list of expected names.
119
120
        Fail if they are not precisely equal.
121
        """
122
        extras = []
123
        shape = list(shape)             # copy
124
        for path, ie in inv.entries():
125
            name = path.replace('\\', '/')
126
            if ie.kind == 'dir':
127
                name = name + '/'
128
            if name in shape:
129
                shape.remove(name)
130
            else:
131
                extras.append(name)
132
        if shape:
133
            self.fail("expected paths not found in inventory: %r" % shape)
134
        if extras:
135
            self.fail("unexpected paths found in inventory: %r" % extras)
1092.1.7 by Robert Collins
make test log files per case so we get all the content from the test, not just the stuff logged to the tests log buffer
136
     
137
    def _get_log(self):
138
        """Get the log the test case used. This can only be called once,
139
        after which an exception will be raised.
140
        """
141
        self.TEST_LOG.flush()
142
        log = open(self.TEST_LOG.name, 'rt').read()
143
        self.TEST_LOG.close()
144
        return log
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
145
146
1092.1.12 by Robert Collins
rename InTempDir to FunctionalTestCase providing InTempDir for compatability
147
class FunctionalTestCase(TestCase):
148
    """Base class for tests that perform function testing - running bzr,
149
    using files on disk, and similar activities.
150
151
    InTempDir is an old alias for FunctionalTestCase.
1092.1.9 by Robert Collins
move working dir creation into InTempDir and nuke the TestSuite subclass as unneeded
152
    """
153
154
    TEST_ROOT = None
155
    _TEST_NAME = 'test'
156
1092.1.12 by Robert Collins
rename InTempDir to FunctionalTestCase providing InTempDir for compatability
157
    def check_file_contents(self, filename, expect):
158
        self.log("check contents of file %s" % filename)
159
        contents = file(filename, 'r').read()
160
        if contents != expect:
161
            self.log("expected: %r" % expect)
162
            self.log("actually: %r" % contents)
163
            self.fail("contents of %s not as expected")
164
1092.1.9 by Robert Collins
move working dir creation into InTempDir and nuke the TestSuite subclass as unneeded
165
    def _make_test_root(self):
166
        import os
167
        import shutil
168
        import tempfile
169
        
1092.1.12 by Robert Collins
rename InTempDir to FunctionalTestCase providing InTempDir for compatability
170
        if FunctionalTestCase.TEST_ROOT is not None:
1092.1.9 by Robert Collins
move working dir creation into InTempDir and nuke the TestSuite subclass as unneeded
171
            return
1092.1.12 by Robert Collins
rename InTempDir to FunctionalTestCase providing InTempDir for compatability
172
        FunctionalTestCase.TEST_ROOT = os.path.abspath(
1092.1.9 by Robert Collins
move working dir creation into InTempDir and nuke the TestSuite subclass as unneeded
173
                                 tempfile.mkdtemp(suffix='.tmp',
174
                                                  prefix=self._TEST_NAME + '-',
175
                                                  dir=os.curdir))
176
    
177
        # make a fake bzr directory there to prevent any tests propagating
178
        # up onto the source directory's real branch
1092.1.12 by Robert Collins
rename InTempDir to FunctionalTestCase providing InTempDir for compatability
179
        os.mkdir(os.path.join(FunctionalTestCase.TEST_ROOT, '.bzr'))
1092.1.9 by Robert Collins
move working dir creation into InTempDir and nuke the TestSuite subclass as unneeded
180
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
181
    def setUp(self):
1092.1.12 by Robert Collins
rename InTempDir to FunctionalTestCase providing InTempDir for compatability
182
        super(FunctionalTestCase, self).setUp()
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
183
        import os
1092.1.9 by Robert Collins
move working dir creation into InTempDir and nuke the TestSuite subclass as unneeded
184
        self._make_test_root()
1092.1.12 by Robert Collins
rename InTempDir to FunctionalTestCase providing InTempDir for compatability
185
        self._currentdir = os.getcwdu()
1092.1.14 by Robert Collins
relace runTest with test_foo in blackbox tests
186
        self.test_dir = os.path.join(self.TEST_ROOT, self.id())
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
187
        os.mkdir(self.test_dir)
188
        os.chdir(self.test_dir)
189
        
190
    def tearDown(self):
191
        import os
1092.1.12 by Robert Collins
rename InTempDir to FunctionalTestCase providing InTempDir for compatability
192
        os.chdir(self._currentdir)
193
        super(FunctionalTestCase, self).tearDown()
194
195
    def formcmd(self, cmd):
196
        if isinstance(cmd, basestring):
197
            cmd = cmd.split()
198
        if cmd[0] == 'bzr':
199
            cmd[0] = self.BZRPATH
200
            if self.OVERRIDE_PYTHON:
201
                cmd.insert(0, self.OVERRIDE_PYTHON)
202
        self.log('$ %r' % cmd)
203
        return cmd
204
205
    def runcmd(self, cmd, retcode=0):
206
        """Run one command and check the return code.
207
208
        Returns a tuple of (stdout,stderr) strings.
209
210
        If a single string is based, it is split into words.
211
        For commands that are not simple space-separated words, please
212
        pass a list instead."""
213
        try:
214
            import shutil
215
            from subprocess import call
216
        except ImportError, e:
217
            _need_subprocess()
218
            raise
219
        cmd = self.formcmd(cmd)
220
        self.log('$ ' + ' '.join(cmd))
221
        actual_retcode = call(cmd, stdout=self.TEST_LOG, stderr=self.TEST_LOG)
222
        if retcode != actual_retcode:
223
            raise CommandFailed("test failed: %r returned %d, expected %d"
224
                                % (cmd, actual_retcode, retcode))
225
226
    def backtick(self, cmd, retcode=0):
227
        """Run a command and return its output"""
228
        try:
229
            import shutil
230
            from subprocess import Popen, PIPE
231
        except ImportError, e:
232
            _need_subprocess()
233
            raise
234
        cmd = self.formcmd(cmd)
235
        child = Popen(cmd, stdout=PIPE, stderr=self.TEST_LOG)
236
        outd, errd = child.communicate()
237
        self.log(outd)
238
        actual_retcode = child.wait()
239
        outd = outd.replace('\r', '')
240
        if retcode != actual_retcode:
241
            raise CommandFailed("test failed: %r returned %d, expected %d"
242
                                % (cmd, actual_retcode, retcode))
243
        return outd
244
245
    def build_tree(self, shape):
246
        """Build a test tree according to a pattern.
247
248
        shape is a sequence of file specifications.  If the final
249
        character is '/', a directory is created.
250
251
        This doesn't add anything to a branch.
252
        """
253
        # XXX: It's OK to just create them using forward slashes on windows?
254
        import os
255
        for name in shape:
256
            assert isinstance(name, basestring)
257
            if name[-1] == '/':
258
                os.mkdir(name[:-1])
259
            else:
260
                f = file(name, 'wt')
261
                print >>f, "contents of", name
262
                f.close()
263
                
264
InTempDir = FunctionalTestCase
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
265
266
1092.1.4 by Robert Collins
move error printing into the TestResult and use the TextTestRunner from unittest
267
class _MyResult(unittest._TextTestResult):
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
268
    """
269
    Custom TestResult.
270
271
    No special behaviour for now.
272
    """
965 by Martin Pool
- selftest is less verbose by default, and takes a -v option if you want it
273
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
274
    def startTest(self, test):
1092.1.11 by Robert Collins
consolidate MyResult more closely with TextTestResult
275
        unittest.TestResult.startTest(self, test)
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
276
        # TODO: Maybe show test.shortDescription somewhere?
842 by Martin Pool
- don't say runit when running tests under python2.3 dammit
277
        what = test.id()
278
        # python2.3 has the bad habit of just "runit" for doctests
279
        if what == 'runit':
280
            what = test.shortDescription()
1092.1.11 by Robert Collins
consolidate MyResult more closely with TextTestResult
281
        if self.showAll:
282
            self.stream.write('%-60.60s' % what)
283
        self.stream.flush()
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
284
285
    def addError(self, test, err):
1092.1.4 by Robert Collins
move error printing into the TestResult and use the TextTestRunner from unittest
286
        super(_MyResult, self).addError(test, err)
1092.1.11 by Robert Collins
consolidate MyResult more closely with TextTestResult
287
        self.stream.flush()
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
288
289
    def addFailure(self, test, err):
1092.1.4 by Robert Collins
move error printing into the TestResult and use the TextTestRunner from unittest
290
        super(_MyResult, self).addFailure(test, err)
1092.1.11 by Robert Collins
consolidate MyResult more closely with TextTestResult
291
        self.stream.flush()
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
292
293
    def addSuccess(self, test):
1092.1.11 by Robert Collins
consolidate MyResult more closely with TextTestResult
294
        if self.showAll:
295
            self.stream.writeln('OK')
296
        elif self.dots:
1092.1.4 by Robert Collins
move error printing into the TestResult and use the TextTestRunner from unittest
297
            self.stream.write('~')
298
        self.stream.flush()
1092.1.11 by Robert Collins
consolidate MyResult more closely with TextTestResult
299
        unittest.TestResult.addSuccess(self, test)
1092.1.4 by Robert Collins
move error printing into the TestResult and use the TextTestRunner from unittest
300
301
    def printErrorList(self, flavour, errors):
302
        for test, err in errors:
303
            self.stream.writeln(self.separator1)
304
            self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
305
            self.stream.writeln(self.separator2)
306
            self.stream.writeln("%s" % err)
307
            if isinstance(test, TestCase):
308
                self.stream.writeln()
309
                self.stream.writeln('log from this test:')
1092.1.7 by Robert Collins
make test log files per case so we get all the content from the test, not just the stuff logged to the tests log buffer
310
                print >>self.stream, test._get_log()
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
311
312
1092.1.4 by Robert Collins
move error printing into the TestResult and use the TextTestRunner from unittest
313
class TextTestRunner(unittest.TextTestRunner):
314
315
    def _makeResult(self):
1092.1.11 by Robert Collins
consolidate MyResult more closely with TextTestResult
316
        return _MyResult(self.stream, self.descriptions, self.verbosity)
1092.1.4 by Robert Collins
move error printing into the TestResult and use the TextTestRunner from unittest
317
318
1092.1.9 by Robert Collins
move working dir creation into InTempDir and nuke the TestSuite subclass as unneeded
319
def run_suite(suite, name='test', verbose=False):
320
    import shutil
1092.1.12 by Robert Collins
rename InTempDir to FunctionalTestCase providing InTempDir for compatability
321
    FunctionalTestCase._TEST_NAME = name
1092.1.3 by Robert Collins
turn runsuite into a smaller method and a TestSuite decorator
322
    if verbose:
1092.1.10 by Robert Collins
start unifying _MyResult and _TextTestResult
323
        verbosity = 2
1092.1.3 by Robert Collins
turn runsuite into a smaller method and a TestSuite decorator
324
    else:
1092.1.10 by Robert Collins
start unifying _MyResult and _TextTestResult
325
        verbosity = 1
1092.1.13 by Robert Collins
override descriptions in our TextTestRunner construction rather than overriding __init__
326
    runner = TextTestRunner(stream=sys.stdout,
327
                            descriptions=0,
328
                            verbosity=verbosity)
1092.1.4 by Robert Collins
move error printing into the TestResult and use the TextTestRunner from unittest
329
    result = runner.run(suite)
1092.1.9 by Robert Collins
move working dir creation into InTempDir and nuke the TestSuite subclass as unneeded
330
    # This is still a little bogus, 
331
    # but only a little. Folk not using our testrunner will
332
    # have to delete their temp directories themselves.
333
    if result.wasSuccessful():
1092.1.12 by Robert Collins
rename InTempDir to FunctionalTestCase providing InTempDir for compatability
334
        shutil.rmtree(FunctionalTestCase.TEST_ROOT) 
1092.1.9 by Robert Collins
move working dir creation into InTempDir and nuke the TestSuite subclass as unneeded
335
    else:
1092.1.12 by Robert Collins
rename InTempDir to FunctionalTestCase providing InTempDir for compatability
336
        print "Failed tests working directories are in '%s'\n" % FunctionalTestCase.TEST_ROOT
841 by Martin Pool
- Start splitting bzr-independent parts of the test framework into
337
    return result.wasSuccessful()