~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#!/usr/bin/python
# Copyright 2008 Canonical Ltd.  All rights reserved.
# pylint: disable-msg=F0401
"""Perform pyflakes checks on doctests."""

import compiler
import doctest
import operator
import os
import sys

import pyflakes
from pyflakes.checker import Checker


# Names we define in the globals for our doctests
GLOBAL_NAMES = set([
    # for system documentation
    'ANONYMOUS',
    'ILaunchBag',
    'bugtarget',
    'canonical_url',
    'commit',
    'create_view',
    'create_initialized_view',
    'flush_database_updates',
    'getUtility',
    'login',
    'login_person',
    'logout',
    'transaction',
    'LaunchpadObjectFactory',
    # for page tests
    'admin_browser',
    'anon_browser',
    'browser',
    'extract_link_from_tag',
    'extract_text',
    'factory',
    'filebug',
    'find_main_content',
    'find_portlet',
    'find_tag_by_id',
    'find_tags_by_class',
    'first_tag_by_class',
    'get_feedback_messages',
    'http',
    'mailinglist_api',
    'parse_relationship_section',
    'pretty',
    'print_action_links',
    'print_batch_header',
    'print_comments',
    'print_errors',
    'print_location',
    'print_location_apps',
    'print_navigation_links',
    'print_portlet_links',
    'print_ppa_packages',
    'print_radio_button_field',
    'print_self_link_of_entries',
    'print_submit_buttons',
    'print_tab_links',
    'print_tag_with_id',
    'setupBrowser',
    'user_browser',
    'webservice',
    'public_webservice',
    'user_webservice',
    'verifyObject',
    # For OpenID per-version tests
    'PROTOCOL_URI',
    # For buildd tests
    'test_dbuser',
    # For Mailman tests
    'xmlrpc_watcher',
    # For archiveuploader tests.
    'getUploadForSource',
    'getUploadForBinary',
    # For answers linked to bugs tests
    'get_bugtask_linked_to_question',
    ])


def extract_script(data):
    """Process a doctest into an equivalent Python script.

    This code is based on doctest.script_from_examples() but has been
    modified not to insert or remove lines of content.  This should
    make line numbers in the output script match those in the input.

        >>> text = '''
        ...
        ... Some text
        ...     >>> 2 + 2
        ...     5
        ...
        ... More text
        ...
        ...     >>> if False:
        ...     ...     whatever
        ...
        ... end.
        ... '''

        >>> print extract_script(text)
        #
        # Some text
        2 + 2
        ## 5
        #
        # More text
        #
        if False:
            whatever
        #
        # end.
        <BLANKLINE>
    """
    output = []
    for piece in doctest.DocTestParser().parse(data):
        if isinstance(piece, doctest.Example):
            # Add the example's source code (strip trailing NL)
            output.append(piece.source[:-1])
            # Add the expected output:
            want = piece.want
            if want:
                output += ['## '+l for l in want.split('\n')[:-1]]
        else:
            # Add non-example text.
            output += [doctest._comment_line(l)
                       for l in piece.split('\n')[:-1]]
    # Combine the output, and return it.
    # Add a courtesy newline to prevent exec from choking
    return '\n'.join(output) + '\n'


def suppress_warning(warning):
    """Returns True if a particular warning should be supressed."""
    if isinstance(warning, pyflakes.messages.UndefinedName):
        # Suppress warnings due to names that are defined as globals.
        if warning.message_args[0] in GLOBAL_NAMES:
            return True
    return False


def check_doctest(filename):
    """Create a PyFlakes object from a doctest."""
    data = open(filename, 'r').read()
    try:
        script = extract_script(data)
    except ValueError:
        print >> sys.__stderr__, 'PARSING:', filename
        raise

    try:
        tree = compiler.parse(script)
    except (SyntaxError, IndentationError), exc:
        (lineno, offset, line) = exc[1][1:]
        if line.endswith("\n"):
            line = line[:-1]
        print >> sys.stderr, 'could not compile %r:%d' % (filename, lineno)
        print >> sys.stderr, line
        print >> sys.stderr, " " * (offset-1), "^"
    else:
        w = Checker(tree, filename)
        for warning in sorted(w.messages, key=operator.attrgetter('lineno')):
            if suppress_warning(warning):
                continue
            print warning


def main(argv):
    """Check the files passed on the command line."""
    for arg in argv[1:]:
        if os.path.isdir(arg):
            for dirpath, dirnames, filenames in os.walk(arg):
                for filename in filenames:
                    if filename.endswith('.txt'):
                        check_doctest(os.path.join(dirpath, filename))
        else:
            check_doctest(arg)


if __name__ == '__main__':
    sys.exit(main(sys.argv))