Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(300)

Side by Side Diff: third_party/pylint/testutils.py

Issue 739393004: Revert "Revert "pylint: upgrade to 1.3.1"" (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools/
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/pylint/reporters/text.py ('k') | third_party/pylint/utils.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr
3 #
4 # This program is free software; you can redistribute it and/or modify it under
5 # the terms of the GNU General Public License as published by the Free Software
6 # Foundation; either version 2 of the License, or (at your option) any later
7 # version.
8 #
9 # This program is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License along with
14 # this program; if not, write to the Free Software Foundation, Inc.,
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 """functional/non regression tests for pylint"""
17 from __future__ import with_statement
18
19 import collections
20 import contextlib
21 import functools
22 import sys
23 import re
24
25 from glob import glob
26 from os import linesep
27 from os.path import abspath, basename, dirname, isdir, join, splitext
28 from cStringIO import StringIO
29
30 from logilab.common import testlib
31
32 from pylint import checkers
33 from pylint.utils import PyLintASTWalker
34 from pylint.reporters import BaseReporter
35 from pylint.interfaces import IReporter
36 from pylint.lint import PyLinter
37
38
39 # Utils
40
41 SYS_VERS_STR = '%d%d%d' % sys.version_info[:3]
42 TITLE_UNDERLINES = ['', '=', '-', '.']
43 PREFIX = abspath(dirname(__file__))
44 PY3K = sys.version_info[0] == 3
45
46 def fix_path():
47 sys.path.insert(0, PREFIX)
48
49 def get_tests_info(input_dir, msg_dir, prefix, suffix):
50 """get python input examples and output messages
51
52 We use following conventions for input files and messages:
53 for different inputs:
54 test for python >= x.y -> input = <name>_pyxy.py
55 test for python < x.y -> input = <name>_py_xy.py
56 for one input and different messages:
57 message for python >= x.y -> message = <name>_pyxy.txt
58 lower versions -> message with highest num
59 """
60 result = []
61 for fname in glob(join(input_dir, prefix + '*' + suffix)):
62 infile = basename(fname)
63 fbase = splitext(infile)[0]
64 # filter input files :
65 pyrestr = fbase.rsplit('_py', 1)[-1] # like _26 or 26
66 if pyrestr.isdigit(): # '24', '25'...
67 if SYS_VERS_STR < pyrestr:
68 continue
69 if pyrestr.startswith('_') and pyrestr[1:].isdigit():
70 # skip test for higher python versions
71 if SYS_VERS_STR >= pyrestr[1:]:
72 continue
73 messages = glob(join(msg_dir, fbase + '*.txt'))
74 # the last one will be without ext, i.e. for all or upper versions:
75 if messages:
76 for outfile in sorted(messages, reverse=True):
77 py_rest = outfile.rsplit('_py', 1)[-1][:-4]
78 if py_rest.isdigit() and SYS_VERS_STR >= py_rest:
79 break
80 else:
81 # This will provide an error message indicating the missing filename .
82 outfile = join(msg_dir, fbase + '.txt')
83 result.append((infile, outfile))
84 return result
85
86
87 class TestReporter(BaseReporter):
88 """reporter storing plain text messages"""
89
90 __implements____ = IReporter
91
92 def __init__(self):
93 self.message_ids = {}
94 self.reset()
95
96 def reset(self):
97 self.out = StringIO()
98 self.messages = []
99
100 def add_message(self, msg_id, location, msg):
101 """manage message of different type and in the context of path """
102 _, _, obj, line, _ = location
103 self.message_ids[msg_id] = 1
104 if obj:
105 obj = ':%s' % obj
106 sigle = msg_id[0]
107 if PY3K and linesep != '\n':
108 # 2to3 writes os.linesep instead of using
109 # the previosly used line separators
110 msg = msg.replace('\r\n', '\n')
111 self.messages.append('%s:%3s%s: %s' % (sigle, line, obj, msg))
112
113 def finalize(self):
114 self.messages.sort()
115 for msg in self.messages:
116 print >> self.out, msg
117 result = self.out.getvalue()
118 self.reset()
119 return result
120
121 def display_results(self, layout):
122 """ignore layouts"""
123
124
125 if sys.version_info < (2, 6):
126 class Message(tuple):
127 def __new__(cls, msg_id, line=None, node=None, args=None):
128 return tuple.__new__(cls, (msg_id, line, node, args))
129
130 @property
131 def msg_id(self):
132 return self[0]
133 @property
134 def line(self):
135 return self[1]
136 @property
137 def node(self):
138 return self[2]
139 @property
140 def args(self):
141 return self[3]
142
143
144 else:
145 class Message(collections.namedtuple('Message',
146 ['msg_id', 'line', 'node', 'args'])):
147 def __new__(cls, msg_id, line=None, node=None, args=None):
148 return tuple.__new__(cls, (msg_id, line, node, args))
149
150
151 class UnittestLinter(object):
152 """A fake linter class to capture checker messages."""
153
154 def __init__(self):
155 self._messages = []
156 self.stats = {}
157
158 def release_messages(self):
159 try:
160 return self._messages
161 finally:
162 self._messages = []
163
164 def add_message(self, msg_id, line=None, node=None, args=None):
165 self._messages.append(Message(msg_id, line, node, args))
166
167 def is_message_enabled(self, *unused_args):
168 return True
169
170 def add_stats(self, **kwargs):
171 for name, value in kwargs.iteritems():
172 self.stats[name] = value
173 return self.stats
174
175 @property
176 def options_providers(self):
177 return linter.options_providers
178
179 def set_config(**kwargs):
180 """Decorator for setting config values on a checker."""
181 def _Wrapper(fun):
182 @functools.wraps(fun)
183 def _Forward(self):
184 for key, value in kwargs.iteritems():
185 setattr(self.checker.config, key, value)
186 if isinstance(self, CheckerTestCase):
187 # reopen checker in case, it may be interested in configuration change
188 self.checker.open()
189 fun(self)
190
191 return _Forward
192 return _Wrapper
193
194
195 class CheckerTestCase(testlib.TestCase):
196 """A base testcase class for unittesting individual checker classes."""
197 CHECKER_CLASS = None
198 CONFIG = {}
199
200 def setUp(self):
201 self.linter = UnittestLinter()
202 self.checker = self.CHECKER_CLASS(self.linter) # pylint: disable=not-cal lable
203 for key, value in self.CONFIG.iteritems():
204 setattr(self.checker.config, key, value)
205 self.checker.open()
206
207 @contextlib.contextmanager
208 def assertNoMessages(self):
209 """Assert that no messages are added by the given method."""
210 with self.assertAddsMessages():
211 yield
212
213 @contextlib.contextmanager
214 def assertAddsMessages(self, *messages):
215 """Assert that exactly the given method adds the given messages.
216
217 The list of messages must exactly match *all* the messages added by the
218 method. Additionally, we check to see whether the args in each message c an
219 actually be substituted into the message string.
220 """
221 yield
222 got = self.linter.release_messages()
223 msg = ('Expected messages did not match actual.\n'
224 'Expected:\n%s\nGot:\n%s' % ('\n'.join(repr(m) for m in messages) ,
225 '\n'.join(repr(m) for m in got)))
226 self.assertEqual(list(messages), got, msg)
227
228 def walk(self, node):
229 """recursive walk on the given node"""
230 walker = PyLintASTWalker(linter)
231 walker.add_checker(self.checker)
232 walker.walk(node)
233
234
235 # Init
236 test_reporter = TestReporter()
237 linter = PyLinter()
238 linter.set_reporter(test_reporter)
239 linter.config.persistent = 0
240 checkers.initialize(linter)
241 linter.global_set_option('required-attributes', ('__revision__',))
242
243 if linesep != '\n':
244 LINE_RGX = re.compile(linesep)
245 def ulines(string):
246 return LINE_RGX.sub('\n', string)
247 else:
248 def ulines(string):
249 return string
250
251 INFO_TEST_RGX = re.compile(r'^func_i\d\d\d\d$')
252
253 def exception_str(self, ex):
254 """function used to replace default __str__ method of exception instances"""
255 return 'in %s\n:: %s' % (ex.file, ', '.join(ex.args))
256
257 # Test classes
258
259 class LintTestUsingModule(testlib.TestCase):
260 INPUT_DIR = None
261 DEFAULT_PACKAGE = 'input'
262 package = DEFAULT_PACKAGE
263 linter = linter
264 module = None
265 depends = None
266 output = None
267 _TEST_TYPE = 'module'
268
269 def shortDescription(self):
270 values = {'mode' : self._TEST_TYPE,
271 'input': self.module,
272 'pkg': self.package,
273 'cls': self.__class__.__name__}
274
275 if self.package == self.DEFAULT_PACKAGE:
276 msg = '%(mode)s test of input file "%(input)s" (%(cls)s)'
277 else:
278 msg = '%(mode)s test of input file "%(input)s" in "%(pkg)s" (%(cls)s )'
279 return msg % values
280
281 def test_functionality(self):
282 tocheck = [self.package+'.'+self.module]
283 if self.depends:
284 tocheck += [self.package+'.%s' % name.replace('.py', '')
285 for name, _ in self.depends]
286 self._test(tocheck)
287
288 def _check_result(self, got):
289 self.assertMultiLineEqual(self._get_expected().strip()+'\n',
290 got.strip()+'\n')
291
292 def _test(self, tocheck):
293 if INFO_TEST_RGX.match(self.module):
294 self.linter.enable('I')
295 else:
296 self.linter.disable('I')
297 try:
298 self.linter.check(tocheck)
299 except Exception, ex:
300 # need finalization to restore a correct state
301 self.linter.reporter.finalize()
302 ex.file = tocheck
303 print ex
304 ex.__str__ = exception_str
305 raise
306 self._check_result(self.linter.reporter.finalize())
307
308 def _has_output(self):
309 return not self.module.startswith('func_noerror_')
310
311 def _get_expected(self):
312 if self._has_output() and self.output:
313 with open(self.output, 'U') as fobj:
314 return fobj.read().strip() + '\n'
315 else:
316 return ''
317
318 class LintTestUsingFile(LintTestUsingModule):
319
320 _TEST_TYPE = 'file'
321
322 def test_functionality(self):
323 importable = join(self.INPUT_DIR, self.module)
324 # python also prefers packages over simple modules.
325 if not isdir(importable):
326 importable += '.py'
327 tocheck = [importable]
328 if self.depends:
329 tocheck += [join(self.INPUT_DIR, name) for name, _file in self.depen ds]
330 self._test(tocheck)
331
332 class LintTestUpdate(LintTestUsingModule):
333
334 _TEST_TYPE = 'update'
335
336 def _check_result(self, got):
337 if self._has_output():
338 try:
339 expected = self._get_expected()
340 except IOError:
341 expected = ''
342 if got != expected:
343 with open(self.output, 'w') as fobj:
344 fobj.write(got)
345
346 # Callback
347
348 def cb_test_gen(base_class):
349 def call(input_dir, msg_dir, module_file, messages_file, dependencies):
350 class LintTC(base_class):
351 module = module_file.replace('.py', '')
352 output = messages_file
353 depends = dependencies or None
354 tags = testlib.Tags(('generated', 'pylint_input_%s' % module))
355 INPUT_DIR = input_dir
356 MSG_DIR = msg_dir
357 return LintTC
358 return call
359
360 # Main function
361
362 def make_tests(input_dir, msg_dir, filter_rgx, callbacks):
363 """generate tests classes from test info
364
365 return the list of generated test classes
366 """
367 if filter_rgx:
368 is_to_run = re.compile(filter_rgx).search
369 else:
370 is_to_run = lambda x: 1
371 tests = []
372 for module_file, messages_file in (
373 get_tests_info(input_dir, msg_dir, 'func_', '')
374 ):
375 if not is_to_run(module_file):
376 continue
377 base = module_file.replace('func_', '').replace('.py', '')
378
379 dependencies = get_tests_info(input_dir, msg_dir, base, '.py')
380
381 for callback in callbacks:
382 test = callback(input_dir, msg_dir, module_file, messages_file,
383 dependencies)
384 if test:
385 tests.append(test)
386 return tests
OLDNEW
« no previous file with comments | « third_party/pylint/reporters/text.py ('k') | third_party/pylint/utils.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698