~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to testsweet.py

[merge] from robert

 - fix handling of symlinks in tree

 - improved executable bits

 - cache pull over http and test for this

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