summaryrefslogtreecommitdiffstats
path: root/test/runner/lib/sanity/pep8.py
blob: bba262bf20a34ff6d33017366a79fcb8c4a04bc8 (plain)
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
"""Sanity test for PEP 8 style guidelines using pycodestyle."""
from __future__ import absolute_import, print_function

import os
import re

from lib.sanity import (
    SanitySingleVersion,
    SanityMessage,
    SanityFailure,
    SanitySuccess,
)

from lib.util import (
    SubprocessError,
    display,
    run_command,
    read_lines_without_comments,
    parse_to_list_of_dict,
    INSTALL_ROOT,
)

from lib.config import (
    SanityConfig,
)

from lib.test import (
    calculate_best_confidence,
)

PEP8_SKIP_PATH = 'test/sanity/pep8/skip.txt'
PEP8_LEGACY_PATH = 'test/sanity/pep8/legacy-files.txt'


class Pep8Test(SanitySingleVersion):
    """Sanity test for PEP 8 style guidelines using pycodestyle."""
    def test(self, args, targets):
        """
        :type args: SanityConfig
        :type targets: SanityTargets
        :rtype: TestResult
        """
        skip_paths = read_lines_without_comments(PEP8_SKIP_PATH, optional=True)
        legacy_paths = read_lines_without_comments(PEP8_LEGACY_PATH, optional=True)

        legacy_ignore_file = os.path.join(INSTALL_ROOT, 'test/sanity/pep8/legacy-ignore.txt')
        legacy_ignore = set(read_lines_without_comments(legacy_ignore_file, remove_blank_lines=True))

        current_ignore_file = os.path.join(INSTALL_ROOT, 'test/sanity/pep8/current-ignore.txt')
        current_ignore = sorted(read_lines_without_comments(current_ignore_file, remove_blank_lines=True))

        skip_paths_set = set(skip_paths)
        legacy_paths_set = set(legacy_paths)

        paths = sorted(i.path for i in targets.include if (os.path.splitext(i.path)[1] == '.py' or i.path.startswith('bin/')) and i.path not in skip_paths_set)

        cmd = [
            args.python_executable,
            '-m', 'pycodestyle',
            '--max-line-length', '160',
            '--config', '/dev/null',
            '--ignore', ','.join(sorted(current_ignore)),
        ] + paths

        if paths:
            try:
                stdout, stderr = run_command(args, cmd, capture=True)
                status = 0
            except SubprocessError as ex:
                stdout = ex.stdout
                stderr = ex.stderr
                status = ex.status

            if stderr:
                raise SubprocessError(cmd=cmd, status=status, stderr=stderr, stdout=stdout)
        else:
            stdout = None

        if args.explain:
            return SanitySuccess(self.name)

        if stdout:
            pattern = '^(?P<path>[^:]*):(?P<line>[0-9]+):(?P<column>[0-9]+): (?P<code>[WE][0-9]{3}) (?P<message>.*)$'

            results = parse_to_list_of_dict(pattern, stdout)
        else:
            results = []

        results = [SanityMessage(
            message=r['message'],
            path=r['path'],
            line=int(r['line']),
            column=int(r['column']),
            level='warning' if r['code'].startswith('W') else 'error',
            code=r['code'],
        ) for r in results]

        failed_result_paths = set([result.path for result in results])
        used_paths = set(paths)

        errors = []
        summary = {}

        line = 0

        for path in legacy_paths:
            line += 1

            if not path:
                continue

            if not os.path.exists(path):
                # Keep files out of the list which no longer exist in the repo.
                errors.append(SanityMessage(
                    code='A101',
                    message='Remove "%s" since it does not exist' % path,
                    path=PEP8_LEGACY_PATH,
                    line=line,
                    column=1,
                    confidence=calculate_best_confidence(((PEP8_LEGACY_PATH, line), (path, 0)), args.metadata) if args.metadata.changes else None,
                ))

            if path in used_paths and path not in failed_result_paths:
                # Keep files out of the list which no longer require the relaxed rule set.
                errors.append(SanityMessage(
                    code='A201',
                    message='Remove "%s" since it passes the current rule set' % path,
                    path=PEP8_LEGACY_PATH,
                    line=line,
                    column=1,
                    confidence=calculate_best_confidence(((PEP8_LEGACY_PATH, line), (path, 0)), args.metadata) if args.metadata.changes else None,
                ))

        line = 0

        for path in skip_paths:
            line += 1

            if not path:
                continue

            if not os.path.exists(path):
                # Keep files out of the list which no longer exist in the repo.
                errors.append(SanityMessage(
                    code='A101',
                    message='Remove "%s" since it does not exist' % path,
                    path=PEP8_SKIP_PATH,
                    line=line,
                    column=1,
                    confidence=calculate_best_confidence(((PEP8_SKIP_PATH, line), (path, 0)), args.metadata) if args.metadata.changes else None,
                ))

        for result in results:
            if result.path in legacy_paths_set and result.code in legacy_ignore:
                # Files on the legacy list are permitted to have errors on the legacy ignore list.
                # However, we want to report on their existence to track progress towards eliminating these exceptions.
                display.info('PEP 8: %s (legacy)' % result, verbosity=3)

                key = '%s %s' % (result.code, re.sub('[0-9]+', 'NNN', result.message))

                if key not in summary:
                    summary[key] = 0

                summary[key] += 1
            else:
                # Files not on the legacy list and errors not on the legacy ignore list are PEP 8 policy errors.
                errors.append(result)

        if summary:
            lines = []
            count = 0

            for key in sorted(summary):
                count += summary[key]
                lines.append('PEP 8: %5d %s' % (summary[key], key))

            display.info('PEP 8: There were %d different legacy issues found (%d total):' % (len(summary), count), verbosity=1)
            display.info('PEP 8: Count Code Message', verbosity=1)

            for line in lines:
                display.info(line, verbosity=1)

        if errors:
            return SanityFailure(self.name, messages=errors)

        return SanitySuccess(self.name)