~bzr-pqm/bzr/bzr.dev

0.1.2 by Martin Pool
Import testsweet module adapted from bzr.
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
from unittest import TestResult, TestCase
19
0.1.67 by Martin Pool
More fixes to try to run on python2.3
20
def _need_subprocess():
0.1.2 by Martin Pool
Import testsweet module adapted from bzr.
21
    sys.stderr.write("sorry, this test suite requires the subprocess module\n"
22
                     "this is shipped with python2.4 and available separately for 2.3\n")
0.1.67 by Martin Pool
More fixes to try to run on python2.3
23
    
0.1.2 by Martin Pool
Import testsweet module adapted from bzr.
24
25
class CommandFailed(Exception):
26
    pass
27
28
0.1.12 by Martin Pool
Add stubbed-out TestSkipped
29
30
class TestSkipped(Exception):
31
    """Indicates that a test was intentionally skipped, rather than failing."""
32
    # XXX: Not used yet
33
34
0.1.2 by Martin Pool
Import testsweet module adapted from bzr.
35
class TestBase(TestCase):
36
    """Base class for bzr test cases.
37
38
    Just defines some useful helper functions; doesn't actually test
39
    anything.
40
    """
41
    
42
    # TODO: Special methods to invoke bzr, so that we can run it
43
    # through a specified Python intepreter
44
45
    OVERRIDE_PYTHON = None # to run with alternative python 'python'
46
    BZRPATH = 'bzr'
47
48
    _log_buf = ""
49
50
0.1.10 by Martin Pool
Log messages when test is starting and finishing
51
    def setUp(self):
52
        super(TestBase, self).setUp()
53
        self.log("%s setup" % self.id())
54
55
56
    def tearDown(self):
57
        super(TestBase, self).tearDown()
58
        self.log("%s teardown" % self.id())
59
        self.log('')
60
        
61
0.1.2 by Martin Pool
Import testsweet module adapted from bzr.
62
    def formcmd(self, cmd):
63
        if isinstance(cmd, basestring):
64
            cmd = cmd.split()
65
66
        if cmd[0] == 'bzr':
67
            cmd[0] = self.BZRPATH
68
            if self.OVERRIDE_PYTHON:
69
                cmd.insert(0, self.OVERRIDE_PYTHON)
70
71
        self.log('$ %r' % cmd)
72
73
        return cmd
74
75
76
    def runcmd(self, cmd, retcode=0):
77
        """Run one command and check the return code.
78
79
        Returns a tuple of (stdout,stderr) strings.
80
81
        If a single string is based, it is split into words.
82
        For commands that are not simple space-separated words, please
83
        pass a list instead."""
0.1.67 by Martin Pool
More fixes to try to run on python2.3
84
        try:
85
            from subprocess import call, Popen, PIPE
86
        except ImportError, e:
87
            _need_subprocess
88
            raise
89
90
0.1.2 by Martin Pool
Import testsweet module adapted from bzr.
91
        cmd = self.formcmd(cmd)
92
93
        self.log('$ ' + ' '.join(cmd))
94
        actual_retcode = call(cmd, stdout=self.TEST_LOG, stderr=self.TEST_LOG)
95
96
        if retcode != actual_retcode:
97
            raise CommandFailed("test failed: %r returned %d, expected %d"
98
                                % (cmd, actual_retcode, retcode))
99
100
101
    def backtick(self, cmd, retcode=0):
102
        """Run a command and return its output"""
0.1.67 by Martin Pool
More fixes to try to run on python2.3
103
        try:
104
            from subprocess import call, Popen, PIPE
105
        except ImportError, e:
106
            _need_subprocess()
107
            raise
108
0.1.2 by Martin Pool
Import testsweet module adapted from bzr.
109
        cmd = self.formcmd(cmd)
110
        child = Popen(cmd, stdout=PIPE, stderr=self.TEST_LOG)
111
        outd, errd = child.communicate()
112
        self.log(outd)
113
        actual_retcode = child.wait()
114
115
        outd = outd.replace('\r', '')
116
117
        if retcode != actual_retcode:
118
            raise CommandFailed("test failed: %r returned %d, expected %d"
119
                                % (cmd, actual_retcode, retcode))
120
121
        return outd
122
123
124
125
    def build_tree(self, shape):
126
        """Build a test tree according to a pattern.
127
128
        shape is a sequence of file specifications.  If the final
129
        character is '/', a directory is created.
130
131
        This doesn't add anything to a branch.
132
        """
133
        # XXX: It's OK to just create them using forward slashes on windows?
134
        import os
135
        for name in shape:
136
            assert isinstance(name, basestring)
137
            if name[-1] == '/':
138
                os.mkdir(name[:-1])
139
            else:
140
                f = file(name, 'wt')
141
                print >>f, "contents of", name
142
                f.close()
143
144
145
    def log(self, msg):
146
        """Log a message to a progress file"""
147
        self._log_buf = self._log_buf + str(msg) + '\n'
148
        print >>self.TEST_LOG, msg
149
150
151
    def check_inventory_shape(self, inv, shape):
152
        """
153
        Compare an inventory to a list of expected names.
154
155
        Fail if they are not precisely equal.
156
        """
157
        extras = []
158
        shape = list(shape)             # copy
159
        for path, ie in inv.entries():
160
            name = path.replace('\\', '/')
161
            if ie.kind == 'dir':
162
                name = name + '/'
163
            if name in shape:
164
                shape.remove(name)
165
            else:
166
                extras.append(name)
167
        if shape:
168
            self.fail("expected paths not found in inventory: %r" % shape)
169
        if extras:
170
            self.fail("unexpected paths found in inventory: %r" % extras)
171
172
173
    def check_file_contents(self, filename, expect):
174
        self.log("check contents of file %s" % filename)
175
        contents = file(filename, 'r').read()
176
        if contents != expect:
177
            self.log("expected: %r" % expected)
178
            self.log("actually: %r" % contents)
179
            self.fail("contents of %s not as expected")
180
            
181
182
183
class InTempDir(TestBase):
184
    """Base class for tests run in a temporary branch."""
185
    def setUp(self):
186
        import os
187
        self.test_dir = os.path.join(self.TEST_ROOT, self.__class__.__name__)
188
        os.mkdir(self.test_dir)
189
        os.chdir(self.test_dir)
190
        
191
    def tearDown(self):
192
        import os
193
        os.chdir(self.TEST_ROOT)
194
195
196
197
198
199
class _MyResult(TestResult):
200
    """
201
    Custom TestResult.
202
203
    No special behaviour for now.
204
    """
205
    def __init__(self, out):
206
        self.out = out
207
        TestResult.__init__(self)
208
209
    def startTest(self, test):
210
        # TODO: Maybe show test.shortDescription somewhere?
211
        print >>self.out, '%-60.60s' % test.id(),
212
        self.out.flush()
213
        TestResult.startTest(self, test)
214
215
    def stopTest(self, test):
216
        # print
217
        TestResult.stopTest(self, test)
218
219
220
    def addError(self, test, err):
221
        print >>self.out, 'ERROR'
222
        TestResult.addError(self, test, err)
223
        _show_test_failure('error', test, err, self.out)
224
225
    def addFailure(self, test, err):
226
        print >>self.out, 'FAILURE'
227
        TestResult.addFailure(self, test, err)
228
        _show_test_failure('failure', test, err, self.out)
229
230
    def addSuccess(self, test):
231
        print >>self.out, 'OK'
232
        TestResult.addSuccess(self, test)
233
234
235
236
def selftest():
237
    from unittest import TestLoader, TestSuite
238
    import bzrlib
239
    import bzrlib.selftest.whitebox
240
    import bzrlib.selftest.blackbox
241
    import bzrlib.selftest.versioning
242
    from doctest import DocTestSuite
243
    import os
244
    import shutil
245
    import time
246
    import sys
247
248
    suite = TestSuite()
249
    tl = TestLoader()
250
251
    for m in bzrlib.selftest.whitebox, \
252
            bzrlib.selftest.versioning:
253
        suite.addTest(tl.loadTestsFromModule(m))
254
255
    for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
256
            bzrlib.commands:
257
        suite.addTest(DocTestSuite(m))
258
259
    suite.addTest(bzrlib.selftest.blackbox.suite())
260
261
    return run_suite(suite)
262
263
264
def run_suite(suite):
265
    import os
266
    import shutil
267
    import time
268
    import sys
269
    
270
    _setup_test_log()
271
    _setup_test_dir()
272
    print
273
274
    # save stdout & stderr so there's no leakage from code-under-test
275
    real_stdout = sys.stdout
276
    real_stderr = sys.stderr
277
    sys.stdout = sys.stderr = TestBase.TEST_LOG
278
    try:
279
        result = _MyResult(real_stdout)
280
        suite.run(result)
281
    finally:
282
        sys.stdout = real_stdout
283
        sys.stderr = real_stderr
284
285
    _show_results(result)
286
287
    return result.wasSuccessful()
288
289
290
291
def _setup_test_log():
292
    import time
293
    import os
294
    
295
    log_filename = os.path.abspath('test.log')
296
    TestBase.TEST_LOG = open(log_filename, 'wt', buffering=1) # line buffered
297
0.1.19 by Martin Pool
remove a reference to bzr from testsweet
298
    print >>TestBase.TEST_LOG, "tests run at " + time.ctime()
0.1.2 by Martin Pool
Import testsweet module adapted from bzr.
299
    print '%-30s %s' % ('test log', log_filename)
300
301
302
def _setup_test_dir():
303
    import os
304
    import shutil
305
    
306
    TestBase.ORIG_DIR = os.getcwdu()
307
    TestBase.TEST_ROOT = os.path.abspath("test.tmp")
308
309
    print '%-30s %s' % ('running tests in', TestBase.TEST_ROOT)
310
311
    if os.path.exists(TestBase.TEST_ROOT):
312
        shutil.rmtree(TestBase.TEST_ROOT)
313
    os.mkdir(TestBase.TEST_ROOT)
314
    os.chdir(TestBase.TEST_ROOT)
315
316
    # make a fake bzr directory there to prevent any tests propagating
317
    # up onto the source directory's real branch
318
    os.mkdir(os.path.join(TestBase.TEST_ROOT, '.bzr'))
319
320
    
321
322
def _show_results(result):
323
     print
324
     print '%4d tests run' % result.testsRun
325
     print '%4d errors' % len(result.errors)
326
     print '%4d failures' % len(result.failures)
327
328
329
330
def _show_test_failure(kind, case, exc_info, out):
331
    from traceback import print_exception
332
    
333
    print >>out, '-' * 60
334
    print >>out, case
335
    
336
    desc = case.shortDescription()
337
    if desc:
338
        print >>out, '   (%s)' % desc
339
         
340
    print_exception(exc_info[0], exc_info[1], exc_info[2], None, out)
341
        
342
    if isinstance(case, TestBase):
343
        print >>out
344
        print >>out, 'log from this test:'
345
        print >>out, case._log_buf
346
         
347
    print >>out, '-' * 60
348
    
349