~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to lib/testsweet.py

  • Committer: Martin Pool
  • Date: 2005-07-07 10:22:02 UTC
  • Revision ID: mbp@sourcefrog.net-20050707102201-2d2a13a25098b101
- rearrange and clear up merged weave

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
 
20
 
def _need_subprocess():
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")
23
 
    
24
 
 
25
 
class CommandFailed(Exception):
26
 
    pass
27
 
 
28
 
 
29
 
 
30
 
class TestSkipped(Exception):
31
 
    """Indicates that a test was intentionally skipped, rather than failing."""
32
 
    # XXX: Not used yet
33
 
 
34
 
 
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
 
 
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
 
 
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."""
84
 
        try:
85
 
            from subprocess import call, Popen, PIPE
86
 
        except ImportError, e:
87
 
            _need_subprocess
88
 
            raise
89
 
 
90
 
 
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"""
103
 
        try:
104
 
            from subprocess import call, Popen, PIPE
105
 
        except ImportError, e:
106
 
            _need_subprocess()
107
 
            raise
108
 
 
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
 
 
298
 
    print >>TestBase.TEST_LOG, "tests run at " + time.ctime()
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