~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/__init__.py

  • Committer: Martin Pool
  • Date: 2005-09-22 06:19:33 UTC
  • Revision ID: mbp@sourcefrog.net-20050922061933-4b71d0f1e205b153
- keep track of number of checked revisions

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
 
from testsweet import TestBase, run_suite, InTempDir
 
18
import logging
 
19
import unittest
 
20
import tempfile
 
21
import os
 
22
import sys
 
23
import errno
 
24
import subprocess
 
25
import shutil
 
26
 
 
27
import testsweet
 
28
import bzrlib.commands
 
29
 
 
30
import bzrlib.trace
 
31
import bzrlib.fetch
 
32
 
19
33
 
20
34
MODULES_TO_TEST = []
21
35
MODULES_TO_DOCTEST = []
22
36
 
23
 
def selftest():
24
 
    from unittest import TestLoader, TestSuite
 
37
from logging import debug, warning, error
 
38
 
 
39
class CommandFailed(Exception):
 
40
    pass
 
41
 
 
42
class TestCase(unittest.TestCase):
 
43
    """Base class for bzr unit tests.
 
44
    
 
45
    Tests that need access to disk resources should subclass 
 
46
    TestCaseInTempDir not TestCase.
 
47
 
 
48
    Error and debug log messages are redirected from their usual
 
49
    location into a temporary file, the contents of which can be
 
50
    retrieved by _get_log().
 
51
       
 
52
    There are also convenience functions to invoke bzr's command-line
 
53
    routine, and to build and check bzr trees."""
 
54
 
 
55
    BZRPATH = 'bzr'
 
56
 
 
57
    def setUp(self):
 
58
        # this replaces the default testsweet.TestCase; we don't want logging changed
 
59
        unittest.TestCase.setUp(self)
 
60
        bzrlib.trace.disable_default_logging()
 
61
        self._enable_file_logging()
 
62
 
 
63
 
 
64
    def _enable_file_logging(self):
 
65
        fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
 
66
 
 
67
        self._log_file = os.fdopen(fileno, 'w+')
 
68
 
 
69
        hdlr = logging.StreamHandler(self._log_file)
 
70
        hdlr.setLevel(logging.DEBUG)
 
71
        hdlr.setFormatter(logging.Formatter('%(levelname)8s  %(message)s'))
 
72
        logging.getLogger('').addHandler(hdlr)
 
73
        logging.getLogger('').setLevel(logging.DEBUG)
 
74
        self._log_hdlr = hdlr
 
75
        debug('opened log file %s', name)
 
76
        
 
77
        self._log_file_name = name
 
78
 
 
79
        
 
80
    def tearDown(self):
 
81
        logging.getLogger('').removeHandler(self._log_hdlr)
 
82
        bzrlib.trace.enable_default_logging()
 
83
        logging.debug('%s teardown', self.id())
 
84
        self._log_file.close()
 
85
        unittest.TestCase.tearDown(self)
 
86
 
 
87
 
 
88
    def log(self, *args):
 
89
        logging.debug(*args)
 
90
 
 
91
    def _get_log(self):
 
92
        """Return as a string the log for this test"""
 
93
        return open(self._log_file_name).read()
 
94
 
 
95
    def run_bzr(self, *args, **kwargs):
 
96
        """Invoke bzr, as if it were run from the command line.
 
97
 
 
98
        This should be the main method for tests that want to exercise the
 
99
        overall behavior of the bzr application (rather than a unit test
 
100
        or a functional test of the library.)
 
101
 
 
102
        Much of the old code runs bzr by forking a new copy of Python, but
 
103
        that is slower, harder to debug, and generally not necessary.
 
104
        """
 
105
        retcode = kwargs.get('retcode', 0)
 
106
        result = self.apply_redirected(None, None, None,
 
107
                                       bzrlib.commands.run_bzr, args)
 
108
        self.assertEquals(result, retcode)
 
109
        
 
110
        
 
111
    def check_inventory_shape(self, inv, shape):
 
112
        """Compare an inventory to a list of expected names.
 
113
 
 
114
        Fail if they are not precisely equal.
 
115
        """
 
116
        extras = []
 
117
        shape = list(shape)             # copy
 
118
        for path, ie in inv.entries():
 
119
            name = path.replace('\\', '/')
 
120
            if ie.kind == 'dir':
 
121
                name = name + '/'
 
122
            if name in shape:
 
123
                shape.remove(name)
 
124
            else:
 
125
                extras.append(name)
 
126
        if shape:
 
127
            self.fail("expected paths not found in inventory: %r" % shape)
 
128
        if extras:
 
129
            self.fail("unexpected paths found in inventory: %r" % extras)
 
130
 
 
131
    def apply_redirected(self, stdin=None, stdout=None, stderr=None,
 
132
                         a_callable=None, *args, **kwargs):
 
133
        """Call callable with redirected std io pipes.
 
134
 
 
135
        Returns the return code."""
 
136
        from StringIO import StringIO
 
137
        if not callable(a_callable):
 
138
            raise ValueError("a_callable must be callable.")
 
139
        if stdin is None:
 
140
            stdin = StringIO("")
 
141
        if stdout is None:
 
142
            stdout = self._log_file
 
143
        if stderr is None:
 
144
            stderr = self._log_file
 
145
        real_stdin = sys.stdin
 
146
        real_stdout = sys.stdout
 
147
        real_stderr = sys.stderr
 
148
        try:
 
149
            sys.stdout = stdout
 
150
            sys.stderr = stderr
 
151
            sys.stdin = stdin
 
152
            return a_callable(*args, **kwargs)
 
153
        finally:
 
154
            sys.stdout = real_stdout
 
155
            sys.stderr = real_stderr
 
156
            sys.stdin = real_stdin
 
157
 
 
158
 
 
159
BzrTestBase = TestCase
 
160
 
 
161
     
 
162
class TestCaseInTempDir(TestCase):
 
163
    """Derived class that runs a test within a temporary directory.
 
164
 
 
165
    This is useful for tests that need to create a branch, etc.
 
166
 
 
167
    The directory is created in a slightly complex way: for each
 
168
    Python invocation, a new temporary top-level directory is created.
 
169
    All test cases create their own directory within that.  If the
 
170
    tests complete successfully, the directory is removed.
 
171
 
 
172
    InTempDir is an old alias for FunctionalTestCase.
 
173
    """
 
174
 
 
175
    TEST_ROOT = None
 
176
    _TEST_NAME = 'test'
 
177
    OVERRIDE_PYTHON = 'python'
 
178
 
 
179
    def check_file_contents(self, filename, expect):
 
180
        self.log("check contents of file %s" % filename)
 
181
        contents = file(filename, 'r').read()
 
182
        if contents != expect:
 
183
            self.log("expected: %r" % expect)
 
184
            self.log("actually: %r" % contents)
 
185
            self.fail("contents of %s not as expected")
 
186
 
 
187
    def _make_test_root(self):
 
188
        if TestCaseInTempDir.TEST_ROOT is not None:
 
189
            return
 
190
        i = 0
 
191
        while True:
 
192
            root = 'test%04d.tmp' % i
 
193
            try:
 
194
                os.mkdir(root)
 
195
            except OSError, e:
 
196
                if e.errno == errno.EEXIST:
 
197
                    i += 1
 
198
                    continue
 
199
                else:
 
200
                    raise
 
201
            # successfully created
 
202
            TestCaseInTempDir.TEST_ROOT = os.path.abspath(root)
 
203
            break
 
204
        # make a fake bzr directory there to prevent any tests propagating
 
205
        # up onto the source directory's real branch
 
206
        os.mkdir(os.path.join(TestCaseInTempDir.TEST_ROOT, '.bzr'))
 
207
 
 
208
    def setUp(self):
 
209
        super(TestCaseInTempDir, self).setUp()
 
210
        import os
 
211
        self._make_test_root()
 
212
        self._currentdir = os.getcwdu()
 
213
        short_id = self.id().replace('bzrlib.selftest.', '') \
 
214
                   .replace('__main__.', '')
 
215
        self.test_dir = os.path.join(self.TEST_ROOT, short_id)
 
216
        os.mkdir(self.test_dir)
 
217
        os.chdir(self.test_dir)
 
218
        
 
219
    def tearDown(self):
 
220
        import os
 
221
        os.chdir(self._currentdir)
 
222
        super(TestCaseInTempDir, self).tearDown()
 
223
 
 
224
    def _formcmd(self, cmd):
 
225
        if isinstance(cmd, basestring):
 
226
            cmd = cmd.split()
 
227
        if cmd[0] == 'bzr':
 
228
            cmd[0] = self.BZRPATH
 
229
            if self.OVERRIDE_PYTHON:
 
230
                cmd.insert(0, self.OVERRIDE_PYTHON)
 
231
        self.log('$ %r' % cmd)
 
232
        return cmd
 
233
 
 
234
    def runcmd(self, cmd, retcode=0):
 
235
        """Run one command and check the return code.
 
236
 
 
237
        Returns a tuple of (stdout,stderr) strings.
 
238
 
 
239
        If a single string is based, it is split into words.
 
240
        For commands that are not simple space-separated words, please
 
241
        pass a list instead."""
 
242
        cmd = self._formcmd(cmd)
 
243
        self.log('$ ' + ' '.join(cmd))
 
244
        actual_retcode = subprocess.call(cmd, stdout=self._log_file,
 
245
                                         stderr=self._log_file)
 
246
        if retcode != actual_retcode:
 
247
            raise CommandFailed("test failed: %r returned %d, expected %d"
 
248
                                % (cmd, actual_retcode, retcode))
 
249
 
 
250
    def backtick(self, cmd, retcode=0):
 
251
        """Run a command and return its output"""
 
252
        cmd = self._formcmd(cmd)
 
253
        child = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=self._log_file)
 
254
        outd, errd = child.communicate()
 
255
        self.log(outd)
 
256
        actual_retcode = child.wait()
 
257
 
 
258
        outd = outd.replace('\r', '')
 
259
 
 
260
        if retcode != actual_retcode:
 
261
            raise CommandFailed("test failed: %r returned %d, expected %d"
 
262
                                % (cmd, actual_retcode, retcode))
 
263
 
 
264
        return outd
 
265
 
 
266
 
 
267
 
 
268
    def build_tree(self, shape):
 
269
        """Build a test tree according to a pattern.
 
270
 
 
271
        shape is a sequence of file specifications.  If the final
 
272
        character is '/', a directory is created.
 
273
 
 
274
        This doesn't add anything to a branch.
 
275
        """
 
276
        # XXX: It's OK to just create them using forward slashes on windows?
 
277
        import os
 
278
        for name in shape:
 
279
            assert isinstance(name, basestring)
 
280
            if name[-1] == '/':
 
281
                os.mkdir(name[:-1])
 
282
            else:
 
283
                f = file(name, 'wt')
 
284
                print >>f, "contents of", name
 
285
                f.close()
 
286
                
 
287
 
 
288
 
 
289
class MetaTestLog(TestCase):
 
290
    def test_logging(self):
 
291
        """Test logs are captured when a test fails."""
 
292
        logging.info('an info message')
 
293
        warning('something looks dodgy...')
 
294
        logging.debug('hello, test is running')
 
295
        ##assert 0
 
296
 
 
297
 
 
298
def selftest(verbose=False, pattern=".*"):
 
299
    """Run the whole test suite under the enhanced runner"""
 
300
    return testsweet.run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern)
 
301
 
 
302
 
 
303
def test_suite():
 
304
    """Build and return TestSuite for the whole program."""
 
305
    from bzrlib.selftest.TestUtil import TestLoader, TestSuite
25
306
    import bzrlib, bzrlib.store, bzrlib.inventory, bzrlib.branch
26
307
    import bzrlib.osutils, bzrlib.commands, bzrlib.merge3, bzrlib.plugin
27
 
    global MODULES_TO_TEST, MODULES_TO_DOCTEST
28
 
 
29
 
    import bzrlib.selftest.whitebox
30
 
    import bzrlib.selftest.blackbox
31
 
    import bzrlib.selftest.versioning
32
 
    import bzrlib.selftest.testmerge3
33
 
    import bzrlib.selftest.testhashcache
34
 
    import bzrlib.selftest.testrevisionnamespaces
35
 
    import bzrlib.selftest.testbranch
36
 
    import bzrlib.selftest.teststatus
37
 
    import bzrlib.selftest.testinv
38
 
    import bzrlib.merge_core
39
308
    from doctest import DocTestSuite
40
309
    import os
41
310
    import shutil
42
311
    import time
43
312
    import sys
44
 
    import unittest
 
313
 
 
314
    global MODULES_TO_TEST, MODULES_TO_DOCTEST
 
315
 
 
316
    testmod_names = \
 
317
                  ['bzrlib.selftest.MetaTestLog',
 
318
                   'bzrlib.selftest.testinv',
 
319
                   'bzrlib.selftest.test_commit',
 
320
                   'bzrlib.selftest.test_commit_merge',
 
321
                   'bzrlib.selftest.versioning',
 
322
                   'bzrlib.selftest.testmerge3',
 
323
                   'bzrlib.selftest.testhashcache',
 
324
                   'bzrlib.selftest.teststatus',
 
325
                   'bzrlib.selftest.testlog',
 
326
                   'bzrlib.selftest.testrevisionnamespaces',
 
327
                   'bzrlib.selftest.testbranch',
 
328
                   'bzrlib.selftest.testrevision',
 
329
                   'bzrlib.selftest.test_merge_core',
 
330
                   'bzrlib.selftest.test_smart_add',
 
331
                   'bzrlib.selftest.testdiff',
 
332
                   'bzrlib.selftest.test_parent',
 
333
                   'bzrlib.selftest.test_xml',
 
334
                   'bzrlib.selftest.test_weave',
 
335
                   'bzrlib.selftest.testfetch',
 
336
                   'bzrlib.selftest.whitebox',
 
337
                   'bzrlib.selftest.teststore',
 
338
                   'bzrlib.selftest.blackbox',
 
339
                   ]
45
340
 
46
341
    for m in (bzrlib.store, bzrlib.inventory, bzrlib.branch,
47
342
              bzrlib.osutils, bzrlib.commands, bzrlib.merge3):
48
343
        if m not in MODULES_TO_DOCTEST:
49
344
            MODULES_TO_DOCTEST.append(m)
50
 
            
51
 
    for m in (bzrlib.selftest.whitebox,
52
 
              bzrlib.selftest.versioning,
53
 
              bzrlib.selftest.testinv,
54
 
              bzrlib.selftest.testmerge3,
55
 
              bzrlib.selftest.testhashcache,
56
 
              bzrlib.selftest.teststatus,
57
 
              bzrlib.selftest.blackbox,
58
 
              bzrlib.selftest.testhashcache,
59
 
              bzrlib.selftest.testrevisionnamespaces,
60
 
              bzrlib.selftest.testbranch,
61
 
              ):
62
 
        if m not in MODULES_TO_TEST:
63
 
            MODULES_TO_TEST.append(m)
64
 
 
65
 
 
66
 
    TestBase.BZRPATH = os.path.join(os.path.realpath(os.path.dirname(bzrlib.__path__[0])), 'bzr')
67
 
    print '%-30s %s' % ('bzr binary', TestBase.BZRPATH)
68
 
 
 
345
 
 
346
    TestCase.BZRPATH = os.path.join(os.path.realpath(os.path.dirname(bzrlib.__path__[0])), 'bzr')
 
347
    print '%-30s %s' % ('bzr binary', TestCase.BZRPATH)
69
348
    print
70
 
 
71
349
    suite = TestSuite()
72
 
 
73
 
    # should also test bzrlib.merge_core, but they seem to be out of date with
74
 
    # the code.
75
 
 
76
 
 
77
 
    # XXX: python2.3's TestLoader() doesn't seem to find all the
78
 
    # tests; don't know why
 
350
    suite.addTest(TestLoader().loadTestsFromNames(testmod_names))
79
351
    for m in MODULES_TO_TEST:
80
352
         suite.addTest(TestLoader().loadTestsFromModule(m))
81
 
 
82
353
    for m in (MODULES_TO_DOCTEST):
83
354
        suite.addTest(DocTestSuite(m))
84
 
 
85
355
    for p in bzrlib.plugin.all_plugins:
86
356
        if hasattr(p, 'test_suite'):
87
357
            suite.addTest(p.test_suite())
88
 
 
89
 
    suite.addTest(unittest.makeSuite(bzrlib.merge_core.MergeTest, 'test_'))
90
 
 
91
 
    return run_suite(suite, 'testbzr')
92
 
 
93
 
 
 
358
    return suite
94
359