~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-13 05:22:41 UTC
  • Revision ID: mbp@sourcefrog.net-20050913052241-52dbd8e8ced620f6
- better BZR_DEBUG trace output

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