~percona-dev/percona-server/5.1.59-innodb_log_archiving

« back to all changes in this revision

Viewing changes to python-for-subunit2junitxml/testtools/compat.py

  • Committer: Stewart Smith
  • Date: 2011-10-06 06:45:16 UTC
  • Revision ID: stewart@flamingspork.com-20111006064516-rrjg17x7wwn9vr6w
add subunit support to mtr

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2008-2011 testtools developers. See LICENSE for details.
 
2
 
 
3
"""Compatibility support for python 2 and 3."""
 
4
 
 
5
__metaclass__ = type
 
6
__all__ = [
 
7
    '_b',
 
8
    '_u',
 
9
    'advance_iterator',
 
10
    'str_is_unicode',
 
11
    'StringIO',
 
12
    'BytesIO',
 
13
    'unicode_output_stream',
 
14
    ]
 
15
 
 
16
import codecs
 
17
import linecache
 
18
import locale
 
19
import os
 
20
import re
 
21
import sys
 
22
import traceback
 
23
 
 
24
from testtools.helpers import try_imports
 
25
 
 
26
StringIO = try_imports(['StringIO.StringIO', 'io.StringIO'])
 
27
 
 
28
BytesIO = try_imports(['io.BytesIO', 'BytesIO'])
 
29
 
 
30
 
 
31
__u_doc = """A function version of the 'u' prefix.
 
32
 
 
33
This is needed becayse the u prefix is not usable in Python 3 but is required
 
34
in Python 2 to get a unicode object.
 
35
 
 
36
To migrate code that was written as u'\u1234' in Python 2 to 2+3 change
 
37
it to be _u('\u1234'). The Python 3 interpreter will decode it
 
38
appropriately and the no-op _u for Python 3 lets it through, in Python
 
39
2 we then call unicode-escape in the _u function.
 
40
"""
 
41
 
 
42
if sys.version_info > (3, 0):
 
43
    def _u(s):
 
44
        return s
 
45
    _r = ascii
 
46
    def _b(s):
 
47
        """A byte literal."""
 
48
        return s.encode("latin-1")
 
49
    advance_iterator = next
 
50
    def istext(x):
 
51
        return isinstance(x, str)
 
52
    def classtypes():
 
53
        return (type,)
 
54
    str_is_unicode = True
 
55
else:
 
56
    def _u(s):
 
57
        # The double replace mangling going on prepares the string for
 
58
        # unicode-escape - \foo is preserved, \u and \U are decoded.
 
59
        return (s.replace("\\", "\\\\").replace("\\\\u", "\\u")
 
60
            .replace("\\\\U", "\\U").decode("unicode-escape"))
 
61
    _r = repr
 
62
    def _b(s):
 
63
        return s
 
64
    advance_iterator = lambda it: it.next()
 
65
    def istext(x):
 
66
        return isinstance(x, basestring)
 
67
    def classtypes():
 
68
        import types
 
69
        return (type, types.ClassType)
 
70
    str_is_unicode = sys.platform == "cli"
 
71
 
 
72
_u.__doc__ = __u_doc
 
73
 
 
74
 
 
75
if sys.version_info > (2, 5):
 
76
    all = all
 
77
    _error_repr = BaseException.__repr__
 
78
    def isbaseexception(exception):
 
79
        """Return whether exception inherits from BaseException only"""
 
80
        return (isinstance(exception, BaseException)
 
81
            and not isinstance(exception, Exception))
 
82
else:
 
83
    def all(iterable):
 
84
        """If contents of iterable all evaluate as boolean True"""
 
85
        for obj in iterable:
 
86
            if not obj:
 
87
                return False
 
88
        return True
 
89
    def _error_repr(exception):
 
90
        """Format an exception instance as Python 2.5 and later do"""
 
91
        return exception.__class__.__name__ + repr(exception.args)
 
92
    def isbaseexception(exception):
 
93
        """Return whether exception would inherit from BaseException only
 
94
 
 
95
        This approximates the hierarchy in Python 2.5 and later, compare the
 
96
        difference between the diagrams at the bottom of the pages:
 
97
        <http://docs.python.org/release/2.4.4/lib/module-exceptions.html>
 
98
        <http://docs.python.org/release/2.5.4/lib/module-exceptions.html>
 
99
        """
 
100
        return isinstance(exception, (KeyboardInterrupt, SystemExit))
 
101
 
 
102
 
 
103
def unicode_output_stream(stream):
 
104
    """Get wrapper for given stream that writes any unicode without exception
 
105
 
 
106
    Characters that can't be coerced to the encoding of the stream, or 'ascii'
 
107
    if valid encoding is not found, will be replaced. The original stream may
 
108
    be returned in situations where a wrapper is determined unneeded.
 
109
 
 
110
    The wrapper only allows unicode to be written, not non-ascii bytestrings,
 
111
    which is a good thing to ensure sanity and sanitation.
 
112
    """
 
113
    if sys.platform == "cli":
 
114
        # Best to never encode before writing in IronPython
 
115
        return stream
 
116
    try:
 
117
        writer = codecs.getwriter(stream.encoding or "")
 
118
    except (AttributeError, LookupError):
 
119
        # GZ 2010-06-16: Python 3 StringIO ends up here, but probably needs
 
120
        #                different handling as it doesn't want bytestrings
 
121
        return codecs.getwriter("ascii")(stream, "replace")
 
122
    if writer.__module__.rsplit(".", 1)[1].startswith("utf"):
 
123
        # The current stream has a unicode encoding so no error handler is needed
 
124
        return stream
 
125
    if sys.version_info > (3, 0):
 
126
        # Python 3 doesn't seem to make this easy, handle a common case
 
127
        try:
 
128
            return stream.__class__(stream.buffer, stream.encoding, "replace",
 
129
                stream.newlines, stream.line_buffering)
 
130
        except AttributeError:
 
131
            pass
 
132
    return writer(stream, "replace")    
 
133
 
 
134
 
 
135
# The default source encoding is actually "iso-8859-1" until Python 2.5 but
 
136
# using non-ascii causes a deprecation warning in 2.4 and it's cleaner to
 
137
# treat all versions the same way
 
138
_default_source_encoding = "ascii"
 
139
 
 
140
# Pattern specified in <http://www.python.org/dev/peps/pep-0263/>
 
141
_cookie_search=re.compile("coding[:=]\s*([-\w.]+)").search
 
142
 
 
143
def _detect_encoding(lines):
 
144
    """Get the encoding of a Python source file from a list of lines as bytes
 
145
 
 
146
    This function does less than tokenize.detect_encoding added in Python 3 as
 
147
    it does not attempt to raise a SyntaxError when the interpreter would, it
 
148
    just wants the encoding of a source file Python has already compiled and
 
149
    determined is valid.
 
150
    """
 
151
    if not lines:
 
152
        return _default_source_encoding
 
153
    if lines[0].startswith("\xef\xbb\xbf"):
 
154
        # Source starting with UTF-8 BOM is either UTF-8 or a SyntaxError
 
155
        return "utf-8"
 
156
    # Only the first two lines of the source file are examined
 
157
    magic = _cookie_search("".join(lines[:2]))
 
158
    if magic is None:
 
159
        return _default_source_encoding
 
160
    encoding = magic.group(1)
 
161
    try:
 
162
        codecs.lookup(encoding)
 
163
    except LookupError:
 
164
        # Some codecs raise something other than LookupError if they don't
 
165
        # support the given error handler, but not the text ones that could
 
166
        # actually be used for Python source code
 
167
        return _default_source_encoding
 
168
    return encoding
 
169
 
 
170
 
 
171
class _EncodingTuple(tuple):
 
172
    """A tuple type that can have an encoding attribute smuggled on"""
 
173
 
 
174
 
 
175
def _get_source_encoding(filename):
 
176
    """Detect, cache and return the encoding of Python source at filename"""
 
177
    try:
 
178
        return linecache.cache[filename].encoding
 
179
    except (AttributeError, KeyError):
 
180
        encoding = _detect_encoding(linecache.getlines(filename))
 
181
        if filename in linecache.cache:
 
182
            newtuple = _EncodingTuple(linecache.cache[filename])
 
183
            newtuple.encoding = encoding
 
184
            linecache.cache[filename] = newtuple
 
185
        return encoding
 
186
 
 
187
 
 
188
def _get_exception_encoding():
 
189
    """Return the encoding we expect messages from the OS to be encoded in"""
 
190
    if os.name == "nt":
 
191
        # GZ 2010-05-24: Really want the codepage number instead, the error
 
192
        #                handling of standard codecs is more deterministic
 
193
        return "mbcs"
 
194
    # GZ 2010-05-23: We need this call to be after initialisation, but there's
 
195
    #                no benefit in asking more than once as it's a global
 
196
    #                setting that can change after the message is formatted.
 
197
    return locale.getlocale(locale.LC_MESSAGES)[1] or "ascii"
 
198
 
 
199
 
 
200
def _exception_to_text(evalue):
 
201
    """Try hard to get a sensible text value out of an exception instance"""
 
202
    try:
 
203
        return unicode(evalue)
 
204
    except KeyboardInterrupt:
 
205
        raise
 
206
    except:
 
207
        # Apparently this is what traceback._some_str does. Sigh - RBC 20100623
 
208
        pass
 
209
    try:
 
210
        return str(evalue).decode(_get_exception_encoding(), "replace")
 
211
    except KeyboardInterrupt:
 
212
        raise
 
213
    except:
 
214
        # Apparently this is what traceback._some_str does. Sigh - RBC 20100623
 
215
        pass
 
216
    # Okay, out of ideas, let higher level handle it
 
217
    return None
 
218
 
 
219
 
 
220
# GZ 2010-05-23: This function is huge and horrible and I welcome suggestions
 
221
#                on the best way to break it up
 
222
_TB_HEADER = _u('Traceback (most recent call last):\n')
 
223
def _format_exc_info(eclass, evalue, tb, limit=None):
 
224
    """Format a stack trace and the exception information as unicode
 
225
 
 
226
    Compatibility function for Python 2 which ensures each component of a
 
227
    traceback is correctly decoded according to its origins.
 
228
 
 
229
    Based on traceback.format_exception and related functions.
 
230
    """
 
231
    fs_enc = sys.getfilesystemencoding()
 
232
    if tb:
 
233
        list = [_TB_HEADER]
 
234
        extracted_list = []
 
235
        for filename, lineno, name, line in traceback.extract_tb(tb, limit):
 
236
            extracted_list.append((
 
237
                filename.decode(fs_enc, "replace"),
 
238
                lineno,
 
239
                name.decode("ascii", "replace"),
 
240
                line and line.decode(
 
241
                    _get_source_encoding(filename), "replace")))
 
242
        list.extend(traceback.format_list(extracted_list))
 
243
    else:
 
244
        list = []
 
245
    if evalue is None:
 
246
        # Is a (deprecated) string exception
 
247
        list.append((eclass + "\n").decode("ascii", "replace"))
 
248
        return list
 
249
    if isinstance(evalue, SyntaxError):
 
250
        # Avoid duplicating the special formatting for SyntaxError here,
 
251
        # instead create a new instance with unicode filename and line
 
252
        # Potentially gives duff spacing, but that's a pre-existing issue
 
253
        try:
 
254
            msg, (filename, lineno, offset, line) = evalue
 
255
        except (TypeError, ValueError):
 
256
            pass # Strange exception instance, fall through to generic code
 
257
        else:
 
258
            # Errors during parsing give the line from buffer encoded as
 
259
            # latin-1 or utf-8 or the encoding of the file depending on the
 
260
            # coding and whether the patch for issue #1031213 is applied, so
 
261
            # give up on trying to decode it and just read the file again
 
262
            if line:
 
263
                bytestr = linecache.getline(filename, lineno)
 
264
                if bytestr:
 
265
                    if lineno == 1 and bytestr.startswith("\xef\xbb\xbf"):
 
266
                        bytestr = bytestr[3:]
 
267
                    line = bytestr.decode(
 
268
                        _get_source_encoding(filename), "replace")
 
269
                    del linecache.cache[filename]
 
270
                else:
 
271
                    line = line.decode("ascii", "replace")
 
272
            if filename:
 
273
                filename = filename.decode(fs_enc, "replace")
 
274
            evalue = eclass(msg, (filename, lineno, offset, line))
 
275
            list.extend(traceback.format_exception_only(eclass, evalue))
 
276
            return list
 
277
    sclass = eclass.__name__
 
278
    svalue = _exception_to_text(evalue)
 
279
    if svalue:
 
280
        list.append("%s: %s\n" % (sclass, svalue))
 
281
    elif svalue is None:
 
282
        # GZ 2010-05-24: Not a great fallback message, but keep for the moment
 
283
        list.append("%s: <unprintable %s object>\n" % (sclass, sclass))
 
284
    else:
 
285
        list.append("%s\n" % sclass)
 
286
    return list