~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to testsweet.py

  • Committer: Martin Pool
  • Date: 2005-07-01 03:24:23 UTC
  • mto: This revision was merged to the branch mainline in revision 852.
  • Revision ID: mbp@sourcefrog.net-20050701032423-e6c004adced0d82a
Remove redundant 'v' lines from weave file

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