~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to testsweet.py

  • Committer: Aaron Bentley
  • Date: 2005-09-19 02:52:24 UTC
  • mto: (1185.1.29)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: aaron.bentley@utoronto.ca-20050919025224-1cc3c70640086e09
TODO re tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
39
39
 
40
40
import unittest
41
41
import sys
 
42
from bzrlib.selftest import TestUtil
42
43
 
43
44
# XXX: Don't need this anymore now we depend on python2.4
44
45
def _need_subprocess():
55
56
    # XXX: Not used yet
56
57
 
57
58
 
58
 
class TestCase(unittest.TestCase):
59
 
    """Base class for bzr unit tests.
60
 
    
61
 
    Tests that need access to disk resources should subclass 
62
 
    FunctionalTestCase not TestCase.
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
 
 
71
 
    def setUp(self):
72
 
        super(TestCase, self).setUp()
73
 
        # setup a temporary log for the test 
74
 
        import time
75
 
        import os
76
 
        import tempfile
77
 
        self.TEST_LOG = tempfile.NamedTemporaryFile(mode='wt', bufsize=0)
78
 
        # save stdout & stderr so there's no leakage from code-under-test
79
 
        self.real_stdout = sys.stdout
80
 
        self.real_stderr = sys.stderr
81
 
        sys.stdout = sys.stderr = self.TEST_LOG
82
 
        self.log("%s setup" % self.id())
83
 
 
84
 
    def tearDown(self):
85
 
        sys.stdout = self.real_stdout
86
 
        sys.stderr = self.real_stderr
87
 
        self.log("%s teardown" % self.id())
88
 
        self.log('')
89
 
        super(TestCase, self).tearDown()
90
 
 
91
 
    def log(self, msg):
92
 
        """Log a message to a progress file"""
93
 
        print >>self.TEST_LOG, msg
94
 
 
95
 
    def check_inventory_shape(self, inv, shape):
96
 
        """
97
 
        Compare an inventory to a list of expected names.
98
 
 
99
 
        Fail if they are not precisely equal.
100
 
        """
101
 
        extras = []
102
 
        shape = list(shape)             # copy
103
 
        for path, ie in inv.entries():
104
 
            name = path.replace('\\', '/')
105
 
            if ie.kind == 'dir':
106
 
                name = name + '/'
107
 
            if name in shape:
108
 
                shape.remove(name)
109
 
            else:
110
 
                extras.append(name)
111
 
        if shape:
112
 
            self.fail("expected paths not found in inventory: %r" % shape)
113
 
        if extras:
114
 
            self.fail("unexpected paths found in inventory: %r" % extras)
115
 
     
116
 
    def _get_log(self):
117
 
        """Get the log the test case used. This can only be called once,
118
 
        after which an exception will be raised.
119
 
        """
120
 
        self.TEST_LOG.flush()
121
 
        log = open(self.TEST_LOG.name, 'rt').read()
122
 
        self.TEST_LOG.close()
123
 
        return log
124
 
 
125
 
 
126
 
class FunctionalTestCase(TestCase):
127
 
    """Base class for tests that perform function testing - running bzr,
128
 
    using files on disk, and similar activities.
129
 
 
130
 
    InTempDir is an old alias for FunctionalTestCase.
131
 
    """
132
 
 
133
 
    TEST_ROOT = None
134
 
    _TEST_NAME = 'test'
135
 
 
136
 
    def check_file_contents(self, filename, expect):
137
 
        self.log("check contents of file %s" % filename)
138
 
        contents = file(filename, 'r').read()
139
 
        if contents != expect:
140
 
            self.log("expected: %r" % expect)
141
 
            self.log("actually: %r" % contents)
142
 
            self.fail("contents of %s not as expected")
143
 
 
144
 
    def _make_test_root(self):
145
 
        import os
146
 
        import shutil
147
 
        import tempfile
148
 
        
149
 
        if FunctionalTestCase.TEST_ROOT is not None:
150
 
            return
151
 
        FunctionalTestCase.TEST_ROOT = os.path.abspath(
152
 
                                 tempfile.mkdtemp(suffix='.tmp',
153
 
                                                  prefix=self._TEST_NAME + '-',
154
 
                                                  dir=os.curdir))
155
 
    
156
 
        # make a fake bzr directory there to prevent any tests propagating
157
 
        # up onto the source directory's real branch
158
 
        os.mkdir(os.path.join(FunctionalTestCase.TEST_ROOT, '.bzr'))
159
 
 
160
 
    def setUp(self):
161
 
        super(FunctionalTestCase, self).setUp()
162
 
        import os
163
 
        self._make_test_root()
164
 
        self._currentdir = os.getcwdu()
165
 
        self.test_dir = os.path.join(self.TEST_ROOT, self.id())
166
 
        os.mkdir(self.test_dir)
167
 
        os.chdir(self.test_dir)
168
 
        
169
 
    def tearDown(self):
170
 
        import os
171
 
        os.chdir(self._currentdir)
172
 
        super(FunctionalTestCase, self).tearDown()
173
 
 
174
 
    def formcmd(self, cmd):
175
 
        if isinstance(cmd, basestring):
176
 
            cmd = cmd.split()
177
 
        if cmd[0] == 'bzr':
178
 
            cmd[0] = self.BZRPATH
179
 
            if self.OVERRIDE_PYTHON:
180
 
                cmd.insert(0, self.OVERRIDE_PYTHON)
181
 
        self.log('$ %r' % cmd)
182
 
        return cmd
183
 
 
184
 
    def runcmd(self, cmd, retcode=0):
185
 
        """Run one command and check the return code.
186
 
 
187
 
        Returns a tuple of (stdout,stderr) strings.
188
 
 
189
 
        If a single string is based, it is split into words.
190
 
        For commands that are not simple space-separated words, please
191
 
        pass a list instead."""
192
 
        try:
193
 
            import shutil
194
 
            from subprocess import call
195
 
        except ImportError, e:
196
 
            _need_subprocess()
197
 
            raise
198
 
        cmd = self.formcmd(cmd)
199
 
        self.log('$ ' + ' '.join(cmd))
200
 
        actual_retcode = call(cmd, stdout=self.TEST_LOG, stderr=self.TEST_LOG)
201
 
        if retcode != actual_retcode:
202
 
            raise CommandFailed("test failed: %r returned %d, expected %d"
203
 
                                % (cmd, actual_retcode, retcode))
204
 
 
205
 
    def backtick(self, cmd, retcode=0):
206
 
        """Run a command and return its output"""
207
 
        try:
208
 
            import shutil
209
 
            from subprocess import Popen, PIPE
210
 
        except ImportError, e:
211
 
            _need_subprocess()
212
 
            raise
213
 
 
214
 
        cmd = self.formcmd(cmd)
215
 
        child = Popen(cmd, stdout=PIPE, stderr=self.TEST_LOG)
216
 
        outd, errd = child.communicate()
217
 
        self.log(outd)
218
 
        actual_retcode = child.wait()
219
 
 
220
 
        outd = outd.replace('\r', '')
221
 
 
222
 
        if retcode != actual_retcode:
223
 
            raise CommandFailed("test failed: %r returned %d, expected %d"
224
 
                                % (cmd, actual_retcode, retcode))
225
 
 
226
 
        return outd
227
 
 
228
 
 
229
 
 
230
 
    def build_tree(self, shape):
231
 
        """Build a test tree according to a pattern.
232
 
 
233
 
        shape is a sequence of file specifications.  If the final
234
 
        character is '/', a directory is created.
235
 
 
236
 
        This doesn't add anything to a branch.
237
 
        """
238
 
        # XXX: It's OK to just create them using forward slashes on windows?
239
 
        import os
240
 
        for name in shape:
241
 
            assert isinstance(name, basestring)
242
 
            if name[-1] == '/':
243
 
                os.mkdir(name[:-1])
244
 
            else:
245
 
                f = file(name, 'wt')
246
 
                print >>f, "contents of", name
247
 
                f.close()
248
 
                
249
 
InTempDir = FunctionalTestCase
 
59
class EarlyStoppingTestResultAdapter(object):
 
60
    """An adapter for TestResult to stop at the first first failure or error"""
 
61
 
 
62
    def __init__(self, result):
 
63
        self._result = result
 
64
 
 
65
    def addError(self, test, err):
 
66
        self._result.addError(test, err)
 
67
        self._result.stop()
 
68
 
 
69
    def addFailure(self, test, err):
 
70
        self._result.addFailure(test, err)
 
71
        self._result.stop()
 
72
 
 
73
    def __getattr__(self, name):
 
74
        return getattr(self._result, name)
 
75
 
 
76
    def __setattr__(self, name, value):
 
77
        if name == '_result':
 
78
            object.__setattr__(self, name, value)
 
79
        return setattr(self._result, name, value)
250
80
 
251
81
 
252
82
class _MyResult(unittest._TextTestResult):
259
89
    def startTest(self, test):
260
90
        unittest.TestResult.startTest(self, test)
261
91
        # TODO: Maybe show test.shortDescription somewhere?
262
 
        what = test.id()
263
 
        # python2.3 has the bad habit of just "runit" for doctests
264
 
        if what == 'runit':
265
 
            what = test.shortDescription()
 
92
        what = test.shortDescription() or test.id()        
266
93
        if self.showAll:
267
 
            self.stream.write('%-60.60s' % what)
 
94
            self.stream.write('%-70.70s' % what)
268
95
        self.stream.flush()
269
96
 
270
97
    def addError(self, test, err):
289
116
            self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
290
117
            self.stream.writeln(self.separator2)
291
118
            self.stream.writeln("%s" % err)
292
 
            if isinstance(test, TestCase):
 
119
            if hasattr(test, '_get_log'):
293
120
                self.stream.writeln()
294
121
                self.stream.writeln('log from this test:')
295
122
                print >>self.stream, test._get_log()
298
125
class TextTestRunner(unittest.TextTestRunner):
299
126
 
300
127
    def _makeResult(self):
301
 
        return _MyResult(self.stream, self.descriptions, self.verbosity)
302
 
 
303
 
 
304
 
def run_suite(suite, name='test', verbose=False):
 
128
        result = _MyResult(self.stream, self.descriptions, self.verbosity)
 
129
        return EarlyStoppingTestResultAdapter(result)
 
130
 
 
131
 
 
132
class filteringVisitor(TestUtil.TestVisitor):
 
133
    """I accruse all the testCases I visit that pass a regexp filter on id
 
134
    into my suite
 
135
    """
 
136
 
 
137
    def __init__(self, filter):
 
138
        import re
 
139
        TestUtil.TestVisitor.__init__(self)
 
140
        self._suite=None
 
141
        self.filter=re.compile(filter)
 
142
 
 
143
    def suite(self):
 
144
        """answer the suite we are building"""
 
145
        if self._suite is None:
 
146
            self._suite=TestUtil.TestSuite()
 
147
        return self._suite
 
148
 
 
149
    def visitCase(self, aCase):
 
150
        if self.filter.match(aCase.id()):
 
151
            self.suite().addTest(aCase)
 
152
 
 
153
 
 
154
def run_suite(suite, name='test', verbose=False, pattern=".*"):
305
155
    import shutil
306
 
    FunctionalTestCase._TEST_NAME = name
 
156
    from bzrlib.selftest import TestCaseInTempDir
 
157
    TestCaseInTempDir._TEST_NAME = name
307
158
    if verbose:
308
159
        verbosity = 2
309
160
    else:
311
162
    runner = TextTestRunner(stream=sys.stdout,
312
163
                            descriptions=0,
313
164
                            verbosity=verbosity)
314
 
    result = runner.run(suite)
 
165
    visitor = filteringVisitor(pattern)
 
166
    suite.visit(visitor)
 
167
    result = runner.run(visitor.suite())
315
168
    # This is still a little bogus, 
316
169
    # but only a little. Folk not using our testrunner will
317
170
    # have to delete their temp directories themselves.
318
171
    if result.wasSuccessful():
319
 
        shutil.rmtree(FunctionalTestCase.TEST_ROOT) 
 
172
        if TestCaseInTempDir.TEST_ROOT is not None:
 
173
            shutil.rmtree(TestCaseInTempDir.TEST_ROOT) 
320
174
    else:
321
 
        print "Failed tests working directories are in '%s'\n" % FunctionalTestCase.TEST_ROOT
 
175
        print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
322
176
    return result.wasSuccessful()