~plane1/maus/devel_624

« back to all changes in this revision

Viewing changes to third_party/nose-0.11.3/lib/python/nose/inspector.py

  • Committer: tunnell
  • Date: 2010-09-30 13:56:05 UTC
  • Revision ID: tunnell@itchy-20100930135605-wxbkfgy75p0sndk3
add third party

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Simple traceback introspection. Used to add additional information to
 
2
AssertionErrors in tests, so that failure messages may be more informative.
 
3
"""
 
4
import inspect
 
5
import logging
 
6
import re
 
7
import sys
 
8
import textwrap
 
9
import tokenize
 
10
 
 
11
try:
 
12
    from cStringIO import StringIO
 
13
except ImportError:
 
14
    from StringIO import StringIO
 
15
 
 
16
log = logging.getLogger(__name__)
 
17
 
 
18
def inspect_traceback(tb):
 
19
    """Inspect a traceback and its frame, returning source for the expression
 
20
    where the exception was raised, with simple variable replacement performed
 
21
    and the line on which the exception was raised marked with '>>'
 
22
    """
 
23
    log.debug('inspect traceback %s', tb)
 
24
 
 
25
    # we only want the innermost frame, where the exception was raised
 
26
    while tb.tb_next:
 
27
        tb = tb.tb_next
 
28
        
 
29
    frame = tb.tb_frame
 
30
    lines, exc_line = tbsource(tb)
 
31
        
 
32
    # figure out the set of lines to grab.
 
33
    inspect_lines, mark_line = find_inspectable_lines(lines, exc_line)
 
34
    src = StringIO(textwrap.dedent(''.join(inspect_lines)))
 
35
    exp = Expander(frame.f_locals, frame.f_globals)
 
36
 
 
37
    while inspect_lines:
 
38
        try:
 
39
            tokenize.tokenize(src.readline, exp)
 
40
        except tokenize.TokenError, e:
 
41
            # this can happen if our inspectable region happens to butt up
 
42
            # against the end of a construct like a docstring with the closing
 
43
            # """ on separate line
 
44
            log.debug("Tokenizer error: %s", e)
 
45
            inspect_lines.pop(0)
 
46
            mark_line -= 1
 
47
            src = StringIO(textwrap.dedent(''.join(inspect_lines)))
 
48
            exp = Expander(frame.f_locals, frame.f_globals)
 
49
            continue
 
50
        break
 
51
    padded = []
 
52
    if exp.expanded_source:
 
53
        exp_lines = exp.expanded_source.split('\n')
 
54
        ep = 0
 
55
        for line in exp_lines:
 
56
            if ep == mark_line:
 
57
                padded.append('>>  ' + line)
 
58
            else:
 
59
                padded.append('    ' + line)
 
60
            ep += 1
 
61
    return '\n'.join(padded)
 
62
 
 
63
 
 
64
def tbsource(tb, context=6):
 
65
    """Get source from  a traceback object.
 
66
 
 
67
    A tuple of two things is returned: a list of lines of context from
 
68
    the source code, and the index of the current line within that list.
 
69
    The optional second argument specifies the number of lines of context
 
70
    to return, which are centered around the current line.
 
71
 
 
72
    .. Note ::
 
73
       This is adapted from inspect.py in the python 2.4 standard library, 
 
74
       since a bug in the 2.3 version of inspect prevents it from correctly
 
75
       locating source lines in a traceback frame.
 
76
    """
 
77
    
 
78
    lineno = tb.tb_lineno
 
79
    frame = tb.tb_frame
 
80
 
 
81
    if context > 0:
 
82
        start = lineno - 1 - context//2
 
83
        log.debug("lineno: %s start: %s", lineno, start)
 
84
        
 
85
        try:
 
86
            lines, dummy = inspect.findsource(frame)
 
87
        except IOError:
 
88
            lines, index = [''], 0
 
89
        else:
 
90
            all_lines = lines
 
91
            start = max(start, 1)
 
92
            start = max(0, min(start, len(lines) - context))
 
93
            lines = lines[start:start+context]
 
94
            index = lineno - 1 - start
 
95
            
 
96
            # python 2.5 compat: if previous line ends in a continuation,
 
97
            # decrement start by 1 to match 2.4 behavior                
 
98
            if sys.version_info >= (2, 5) and index > 0:
 
99
                while lines[index-1].strip().endswith('\\'):
 
100
                    start -= 1
 
101
                    lines = all_lines[start:start+context]
 
102
    else:
 
103
        lines, index = [''], 0
 
104
    log.debug("tbsource lines '''%s''' around index %s", lines, index)
 
105
    return (lines, index)    
 
106
 
 
107
    
 
108
def find_inspectable_lines(lines, pos):
 
109
    """Find lines in home that are inspectable.
 
110
    
 
111
    Walk back from the err line up to 3 lines, but don't walk back over
 
112
    changes in indent level.
 
113
 
 
114
    Walk forward up to 3 lines, counting \ separated lines as 1. Don't walk
 
115
    over changes in indent level (unless part of an extended line)
 
116
    """
 
117
    cnt = re.compile(r'\\[\s\n]*$')
 
118
    df = re.compile(r':[\s\n]*$')
 
119
    ind = re.compile(r'^(\s*)')
 
120
    toinspect = []
 
121
    home = lines[pos]
 
122
    home_indent = ind.match(home).groups()[0]
 
123
    
 
124
    before = lines[max(pos-3, 0):pos]
 
125
    before.reverse()
 
126
    after = lines[pos+1:min(pos+4, len(lines))]
 
127
 
 
128
    for line in before:
 
129
        if ind.match(line).groups()[0] == home_indent:
 
130
            toinspect.append(line)
 
131
        else:
 
132
            break
 
133
    toinspect.reverse()
 
134
    toinspect.append(home)
 
135
    home_pos = len(toinspect)-1
 
136
    continued = cnt.search(home)
 
137
    for line in after:
 
138
        if ((continued or ind.match(line).groups()[0] == home_indent)
 
139
            and not df.search(line)):
 
140
            toinspect.append(line)
 
141
            continued = cnt.search(line)
 
142
        else:
 
143
            break
 
144
    log.debug("Inspecting lines '''%s''' around %s", toinspect, home_pos)
 
145
    return toinspect, home_pos
 
146
 
 
147
 
 
148
class Expander:
 
149
    """Simple expression expander. Uses tokenize to find the names and
 
150
    expands any that can be looked up in the frame.
 
151
    """
 
152
    def __init__(self, locals, globals):
 
153
        self.locals = locals
 
154
        self.globals = globals
 
155
        self.lpos = None
 
156
        self.expanded_source = ''
 
157
         
 
158
    def __call__(self, ttype, tok, start, end, line):
 
159
        # TODO
 
160
        # deal with unicode properly
 
161
        
 
162
        # TODO
 
163
        # Dealing with instance members
 
164
        #   always keep the last thing seen  
 
165
        #   if the current token is a dot,
 
166
        #      get ready to getattr(lastthing, this thing) on the
 
167
        #      next call.
 
168
        
 
169
        if self.lpos is not None and start[1] >= self.lpos:
 
170
            self.expanded_source += ' ' * (start[1]-self.lpos)
 
171
        elif start[1] < self.lpos:
 
172
            # newline, indent correctly
 
173
            self.expanded_source += ' ' * start[1]
 
174
        self.lpos = end[1]
 
175
      
 
176
        if ttype == tokenize.INDENT:
 
177
            pass
 
178
        elif ttype == tokenize.NAME:
 
179
            # Clean this junk up
 
180
            try:
 
181
                val = self.locals[tok]
 
182
                if callable(val):
 
183
                    val = tok
 
184
                else:
 
185
                    val = repr(val)
 
186
            except KeyError:
 
187
                try:
 
188
                    val = self.globals[tok]
 
189
                    if callable(val):
 
190
                        val = tok
 
191
                    else:
 
192
                        val = repr(val)
 
193
 
 
194
                except KeyError:
 
195
                    val = tok
 
196
            # FIXME... not sure how to handle things like funcs, classes
 
197
            # FIXME this is broken for some unicode strings
 
198
            self.expanded_source += val
 
199
        else:
 
200
            self.expanded_source += tok
 
201
        # if this is the end of the line and the line ends with
 
202
        # \, then tack a \ and newline onto the output
 
203
        # print line[end[1]:]
 
204
        if re.match(r'\s+\\\n', line[end[1]:]):
 
205
            self.expanded_source += ' \\\n'