~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_trace.py

  • Committer: Robert Collins
  • Date: 2009-05-14 09:42:07 UTC
  • mto: This revision was merged to the branch mainline in revision 4362.
  • Revision ID: robertc@robertcollins.net-20090514094207-a0pqfjtw4oujd86m
Add bug info.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007, 2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
# "weren't nothing promised to you.  do i look like i got a promise face?"
 
18
 
 
19
"""Tests for trace library"""
 
20
 
 
21
from cStringIO import StringIO
 
22
import errno
 
23
import os
 
24
import re
 
25
import sys
 
26
import tempfile
 
27
 
 
28
from bzrlib import (
 
29
    errors,
 
30
    )
 
31
from bzrlib.tests import TestCaseInTempDir, TestCase
 
32
from bzrlib.trace import (
 
33
    mutter, mutter_callsite, report_exception,
 
34
    set_verbosity_level, get_verbosity_level, is_quiet, is_verbose, be_quiet,
 
35
    pop_log_file,
 
36
    push_log_file,
 
37
    _rollover_trace_maybe,
 
38
    )
 
39
 
 
40
 
 
41
def _format_exception():
 
42
    """Format an exception as it would normally be displayed to the user"""
 
43
    buf = StringIO()
 
44
    report_exception(sys.exc_info(), buf)
 
45
    return buf.getvalue()
 
46
 
 
47
 
 
48
class TestTrace(TestCase):
 
49
 
 
50
    def test_format_sys_exception(self):
 
51
        try:
 
52
            raise NotImplementedError, "time travel"
 
53
        except NotImplementedError:
 
54
            pass
 
55
        err = _format_exception()
 
56
        self.assertEqualDiff(err.splitlines()[0],
 
57
                'bzr: ERROR: exceptions.NotImplementedError: time travel')
 
58
        self.assertContainsRe(err,
 
59
                r'File.*test_trace.py')
 
60
 
 
61
    def test_format_interrupt_exception(self):
 
62
        try:
 
63
            raise KeyboardInterrupt()
 
64
        except KeyboardInterrupt:
 
65
            # XXX: Some risk that a *real* keyboard interrupt won't be seen
 
66
            pass
 
67
        msg = _format_exception()
 
68
        self.assertTrue(len(msg) > 0)
 
69
        self.assertEqualDiff(msg, 'bzr: interrupted\n')
 
70
 
 
71
    def test_format_os_error(self):
 
72
        try:
 
73
            os.rmdir('nosuchfile22222')
 
74
        except OSError:
 
75
            pass
 
76
        msg = _format_exception()
 
77
        self.assertContainsRe(msg,
 
78
            r'^bzr: ERROR: \[Errno .*\] No such file.*nosuchfile22222')
 
79
 
 
80
    def test_format_io_error(self):
 
81
        try:
 
82
            file('nosuchfile22222')
 
83
        except IOError:
 
84
            pass
 
85
        msg = _format_exception()
 
86
        self.assertContainsRe(msg, r'^bzr: ERROR: \[Errno .*\] No such file.*nosuchfile')
 
87
 
 
88
    def test_format_unicode_error(self):
 
89
        try:
 
90
            raise errors.BzrCommandError(u'argument foo\xb5 does not exist')
 
91
        except errors.BzrCommandError:
 
92
            pass
 
93
        msg = _format_exception()
 
94
 
 
95
    def test_format_exception(self):
 
96
        """Short formatting of bzr exceptions"""
 
97
        try:
 
98
            raise errors.NotBranchError('wibble')
 
99
        except errors.NotBranchError:
 
100
            pass
 
101
        msg = _format_exception()
 
102
        self.assertTrue(len(msg) > 0)
 
103
        self.assertEqualDiff(msg, 'bzr: ERROR: Not a branch: \"wibble\".\n')
 
104
 
 
105
    def test_report_external_import_error(self):
 
106
        """Short friendly message for missing system modules."""
 
107
        try:
 
108
            import ImaginaryModule
 
109
        except ImportError, e:
 
110
            pass
 
111
        else:
 
112
            self.fail("somehow succeeded in importing %r" % ImaginaryModule)
 
113
        msg = _format_exception()
 
114
        self.assertEqual(msg,
 
115
            'bzr: ERROR: No module named ImaginaryModule\n'
 
116
            'You may need to install this Python library separately.\n')
 
117
 
 
118
    def test_report_import_syntax_error(self):
 
119
        try:
 
120
            raise ImportError("syntax error")
 
121
        except ImportError, e:
 
122
            pass
 
123
        msg = _format_exception()
 
124
        self.assertContainsRe(msg,
 
125
            r"Traceback \(most recent call last\)")
 
126
 
 
127
    def test_trace_unicode(self):
 
128
        """Write Unicode to trace log"""
 
129
        self.log(u'the unicode character for benzene is \N{BENZENE RING}')
 
130
        self.assertContainsRe(self._get_log(keep_log_file=True),
 
131
                              "the unicode character for benzene is")
 
132
 
 
133
    def test_trace_argument_unicode(self):
 
134
        """Write a Unicode argument to the trace log"""
 
135
        mutter(u'the unicode character for benzene is %s', u'\N{BENZENE RING}')
 
136
        self.assertContainsRe(self._get_log(keep_log_file=True),
 
137
                              'the unicode character')
 
138
 
 
139
    def test_trace_argument_utf8(self):
 
140
        """Write a Unicode argument to the trace log"""
 
141
        mutter(u'the unicode character for benzene is %s',
 
142
               u'\N{BENZENE RING}'.encode('utf-8'))
 
143
        self.assertContainsRe(self._get_log(keep_log_file=True),
 
144
                              'the unicode character')
 
145
 
 
146
    def test_report_broken_pipe(self):
 
147
        try:
 
148
            raise IOError(errno.EPIPE, 'broken pipe foofofo')
 
149
        except IOError, e:
 
150
            msg = _format_exception()
 
151
            self.assertEquals(msg, "bzr: broken pipe\n")
 
152
        else:
 
153
            self.fail("expected error not raised")
 
154
 
 
155
    def assertLogStartsWith(self, log, string):
 
156
        """Like assertStartsWith, but skips the log timestamp."""
 
157
        self.assertContainsRe(log,
 
158
            '^\\d+\\.\\d+  ' + re.escape(string))
 
159
 
 
160
    def test_mutter_callsite_1(self):
 
161
        """mutter_callsite can capture 1 level of stack frame."""
 
162
        mutter_callsite(1, "foo %s", "a string")
 
163
        log = self._get_log(keep_log_file=True)
 
164
        # begin with the message
 
165
        self.assertLogStartsWith(log, 'foo a string\nCalled from:\n')
 
166
        # should show two frame: this frame and the one above
 
167
        self.assertContainsRe(log,
 
168
            'test_trace\\.py", line \\d+, in test_mutter_callsite_1\n')
 
169
        # this frame should be the final one
 
170
        self.assertEndsWith(log, ' "a string")\n')
 
171
 
 
172
    def test_mutter_callsite_2(self):
 
173
        """mutter_callsite can capture 2 levels of stack frame."""
 
174
        mutter_callsite(2, "foo %s", "a string")
 
175
        log = self._get_log(keep_log_file=True)
 
176
        # begin with the message
 
177
        self.assertLogStartsWith(log, 'foo a string\nCalled from:\n')
 
178
        # should show two frame: this frame and the one above
 
179
        self.assertContainsRe(log,
 
180
            'test_trace.py", line \d+, in test_mutter_callsite_2\n')
 
181
        # this frame should be the final one
 
182
        self.assertEndsWith(log, ' "a string")\n')
 
183
 
 
184
    def test_mutter_never_fails(self):
 
185
        # Even if the decode/encode stage fails, mutter should not
 
186
        # raise an exception
 
187
        mutter(u'Writing a greek mu (\xb5) works in a unicode string')
 
188
        mutter('But fails in an ascii string \xb5')
 
189
        mutter('and in an ascii argument: %s', '\xb5')
 
190
        log = self._get_log(keep_log_file=True)
 
191
        self.assertContainsRe(log, 'Writing a greek mu')
 
192
        self.assertContainsRe(log, "But fails in an ascii string")
 
193
        self.assertContainsRe(log, u"ascii argument: \xb5")
 
194
 
 
195
    def test_push_log_file(self):
 
196
        """Can push and pop log file, and this catches mutter messages.
 
197
 
 
198
        This is primarily for use in the test framework.
 
199
        """
 
200
        tmp1 = tempfile.NamedTemporaryFile()
 
201
        tmp2 = tempfile.NamedTemporaryFile()
 
202
        try:
 
203
            memento1 = push_log_file(tmp1)
 
204
            mutter("comment to file1")
 
205
            try:
 
206
                memento2 = push_log_file(tmp2)
 
207
                try:
 
208
                    mutter("comment to file2")
 
209
                finally:
 
210
                    pop_log_file(memento2)
 
211
                mutter("again to file1")
 
212
            finally:
 
213
                pop_log_file(memento1)
 
214
            # the files were opened in binary mode, so should have exactly
 
215
            # these bytes.  and removing the file as the log target should
 
216
            # have caused them to be flushed out.  need to match using regexps
 
217
            # as there's a timestamp at the front.
 
218
            tmp1.seek(0)
 
219
            self.assertContainsRe(tmp1.read(),
 
220
                r"\d+\.\d+  comment to file1\n\d+\.\d+  again to file1\n")
 
221
            tmp2.seek(0)
 
222
            self.assertContainsRe(tmp2.read(),
 
223
                r"\d+\.\d+  comment to file2\n")
 
224
        finally:
 
225
            tmp1.close()
 
226
            tmp2.close()
 
227
 
 
228
 
 
229
class TestVerbosityLevel(TestCase):
 
230
 
 
231
    def test_verbosity_level(self):
 
232
        set_verbosity_level(1)
 
233
        self.assertEqual(1, get_verbosity_level())
 
234
        self.assertTrue(is_verbose())
 
235
        self.assertFalse(is_quiet())
 
236
        set_verbosity_level(-1)
 
237
        self.assertEqual(-1, get_verbosity_level())
 
238
        self.assertFalse(is_verbose())
 
239
        self.assertTrue(is_quiet())
 
240
        set_verbosity_level(0)
 
241
        self.assertEqual(0, get_verbosity_level())
 
242
        self.assertFalse(is_verbose())
 
243
        self.assertFalse(is_quiet())
 
244
 
 
245
    def test_be_quiet(self):
 
246
        # Confirm the old API still works
 
247
        be_quiet(True)
 
248
        self.assertEqual(-1, get_verbosity_level())
 
249
        be_quiet(False)
 
250
        self.assertEqual(0, get_verbosity_level())
 
251
 
 
252
 
 
253
class TestBzrLog(TestCaseInTempDir):
 
254
 
 
255
    def test_log_rollover(self):
 
256
        temp_log_name = 'test-log'
 
257
        trace_file = open(temp_log_name, 'at')
 
258
        trace_file.write('test_log_rollover padding\n' * 1000000)
 
259
        trace_file.close()
 
260
        _rollover_trace_maybe(temp_log_name)
 
261
        # should have been rolled over
 
262
        self.assertFalse(os.access(temp_log_name, os.R_OK))