OLD | NEW |
1 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). | 1 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). |
2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr | 2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # | 3 # |
4 # This program is free software; you can redistribute it and/or modify it under | 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 | 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 | 6 # Foundation; either version 2 of the License, or (at your option) any later |
7 # version. | 7 # version. |
8 # | 8 # |
9 # This program is distributed in the hope that it will be useful, but WITHOUT | 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 | 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. | 11 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
12 # | 12 # |
13 # You should have received a copy of the GNU General Public License along with | 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., | 14 # this program; if not, write to the Free Software Foundation, Inc., |
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 """functional/non regression tests for pylint""" | 16 """functional/non regression tests for pylint""" |
17 from __future__ import with_statement | 17 from __future__ import print_function |
18 | 18 |
19 import collections | 19 import collections |
20 import contextlib | 20 import contextlib |
21 import functools | 21 import functools |
| 22 import os |
22 import sys | 23 import sys |
23 import re | 24 import re |
| 25 import unittest |
| 26 import tempfile |
| 27 import tokenize |
24 | 28 |
25 from glob import glob | 29 from glob import glob |
26 from os import linesep | 30 from os import linesep, getcwd, sep |
27 from os.path import abspath, basename, dirname, isdir, join, splitext | 31 from os.path import abspath, basename, dirname, isdir, join, splitext |
28 from cStringIO import StringIO | |
29 | 32 |
30 from logilab.common import testlib | 33 from astroid import test_utils |
31 | 34 |
32 from pylint import checkers | 35 from pylint import checkers |
33 from pylint.utils import PyLintASTWalker | 36 from pylint.utils import PyLintASTWalker |
34 from pylint.reporters import BaseReporter | 37 from pylint.reporters import BaseReporter |
35 from pylint.interfaces import IReporter | 38 from pylint.interfaces import IReporter |
36 from pylint.lint import PyLinter | 39 from pylint.lint import PyLinter |
37 | 40 |
| 41 import six |
| 42 from six.moves import StringIO |
| 43 |
38 | 44 |
39 # Utils | 45 # Utils |
40 | 46 |
41 SYS_VERS_STR = '%d%d%d' % sys.version_info[:3] | 47 SYS_VERS_STR = '%d%d%d' % sys.version_info[:3] |
42 TITLE_UNDERLINES = ['', '=', '-', '.'] | 48 TITLE_UNDERLINES = ['', '=', '-', '.'] |
43 PREFIX = abspath(dirname(__file__)) | 49 PREFIX = abspath(dirname(__file__)) |
44 PY3K = sys.version_info[0] == 3 | 50 PY3K = sys.version_info[0] == 3 |
45 | 51 |
46 def fix_path(): | 52 def fix_path(): |
47 sys.path.insert(0, PREFIX) | 53 sys.path.insert(0, PREFIX) |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
82 outfile = join(msg_dir, fbase + '.txt') | 88 outfile = join(msg_dir, fbase + '.txt') |
83 result.append((infile, outfile)) | 89 result.append((infile, outfile)) |
84 return result | 90 return result |
85 | 91 |
86 | 92 |
87 class TestReporter(BaseReporter): | 93 class TestReporter(BaseReporter): |
88 """reporter storing plain text messages""" | 94 """reporter storing plain text messages""" |
89 | 95 |
90 __implements____ = IReporter | 96 __implements____ = IReporter |
91 | 97 |
92 def __init__(self): | 98 def __init__(self): # pylint: disable=super-init-not-called |
| 99 |
93 self.message_ids = {} | 100 self.message_ids = {} |
94 self.reset() | 101 self.reset() |
| 102 self.path_strip_prefix = getcwd() + sep |
95 | 103 |
96 def reset(self): | 104 def reset(self): |
97 self.out = StringIO() | 105 self.out = StringIO() |
98 self.messages = [] | 106 self.messages = [] |
99 | 107 |
100 def add_message(self, msg_id, location, msg): | 108 def add_message(self, msg_id, location, msg): |
101 """manage message of different type and in the context of path """ | 109 """manage message of different type and in the context of path """ |
102 _, _, obj, line, _ = location | 110 _, _, obj, line, _ = location |
103 self.message_ids[msg_id] = 1 | 111 self.message_ids[msg_id] = 1 |
104 if obj: | 112 if obj: |
105 obj = ':%s' % obj | 113 obj = ':%s' % obj |
106 sigle = msg_id[0] | 114 sigle = msg_id[0] |
107 if PY3K and linesep != '\n': | 115 if PY3K and linesep != '\n': |
108 # 2to3 writes os.linesep instead of using | 116 # 2to3 writes os.linesep instead of using |
109 # the previosly used line separators | 117 # the previosly used line separators |
110 msg = msg.replace('\r\n', '\n') | 118 msg = msg.replace('\r\n', '\n') |
111 self.messages.append('%s:%3s%s: %s' % (sigle, line, obj, msg)) | 119 self.messages.append('%s:%3s%s: %s' % (sigle, line, obj, msg)) |
112 | 120 |
113 def finalize(self): | 121 def finalize(self): |
114 self.messages.sort() | 122 self.messages.sort() |
115 for msg in self.messages: | 123 for msg in self.messages: |
116 print >> self.out, msg | 124 print(msg, file=self.out) |
117 result = self.out.getvalue() | 125 result = self.out.getvalue() |
118 self.reset() | 126 self.reset() |
119 return result | 127 return result |
120 | 128 |
121 def display_results(self, layout): | 129 def display_results(self, layout): |
122 """ignore layouts""" | 130 """ignore layouts""" |
123 | 131 |
124 | 132 |
125 if sys.version_info < (2, 6): | 133 class Message(collections.namedtuple('Message', |
126 class Message(tuple): | 134 ['msg_id', 'line', 'node', 'args'])): |
127 def __new__(cls, msg_id, line=None, node=None, args=None): | 135 def __new__(cls, msg_id, line=None, node=None, args=None): |
128 return tuple.__new__(cls, (msg_id, line, node, args)) | 136 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 | 137 |
150 | 138 |
151 class UnittestLinter(object): | 139 class UnittestLinter(object): |
152 """A fake linter class to capture checker messages.""" | 140 """A fake linter class to capture checker messages.""" |
| 141 # pylint: disable=unused-argument, no-self-use |
153 | 142 |
154 def __init__(self): | 143 def __init__(self): |
155 self._messages = [] | 144 self._messages = [] |
156 self.stats = {} | 145 self.stats = {} |
157 | 146 |
158 def release_messages(self): | 147 def release_messages(self): |
159 try: | 148 try: |
160 return self._messages | 149 return self._messages |
161 finally: | 150 finally: |
162 self._messages = [] | 151 self._messages = [] |
163 | 152 |
164 def add_message(self, msg_id, line=None, node=None, args=None): | 153 def add_message(self, msg_id, line=None, node=None, args=None, |
| 154 confidence=None): |
165 self._messages.append(Message(msg_id, line, node, args)) | 155 self._messages.append(Message(msg_id, line, node, args)) |
166 | 156 |
167 def is_message_enabled(self, *unused_args): | 157 def is_message_enabled(self, *unused_args): |
168 return True | 158 return True |
169 | 159 |
170 def add_stats(self, **kwargs): | 160 def add_stats(self, **kwargs): |
171 for name, value in kwargs.iteritems(): | 161 for name, value in six.iteritems(kwargs): |
172 self.stats[name] = value | 162 self.stats[name] = value |
173 return self.stats | 163 return self.stats |
174 | 164 |
175 @property | 165 @property |
176 def options_providers(self): | 166 def options_providers(self): |
177 return linter.options_providers | 167 return linter.options_providers |
178 | 168 |
179 def set_config(**kwargs): | 169 def set_config(**kwargs): |
180 """Decorator for setting config values on a checker.""" | 170 """Decorator for setting config values on a checker.""" |
181 def _Wrapper(fun): | 171 def _Wrapper(fun): |
182 @functools.wraps(fun) | 172 @functools.wraps(fun) |
183 def _Forward(self): | 173 def _Forward(self): |
184 for key, value in kwargs.iteritems(): | 174 for key, value in six.iteritems(kwargs): |
185 setattr(self.checker.config, key, value) | 175 setattr(self.checker.config, key, value) |
186 if isinstance(self, CheckerTestCase): | 176 if isinstance(self, CheckerTestCase): |
187 # reopen checker in case, it may be interested in configuration
change | 177 # reopen checker in case, it may be interested in configuration
change |
188 self.checker.open() | 178 self.checker.open() |
189 fun(self) | 179 fun(self) |
190 | 180 |
191 return _Forward | 181 return _Forward |
192 return _Wrapper | 182 return _Wrapper |
193 | 183 |
194 | 184 |
195 class CheckerTestCase(testlib.TestCase): | 185 class CheckerTestCase(unittest.TestCase): |
196 """A base testcase class for unittesting individual checker classes.""" | 186 """A base testcase class for unittesting individual checker classes.""" |
197 CHECKER_CLASS = None | 187 CHECKER_CLASS = None |
198 CONFIG = {} | 188 CONFIG = {} |
199 | 189 |
200 def setUp(self): | 190 def setUp(self): |
201 self.linter = UnittestLinter() | 191 self.linter = UnittestLinter() |
202 self.checker = self.CHECKER_CLASS(self.linter) # pylint: disable=not-cal
lable | 192 self.checker = self.CHECKER_CLASS(self.linter) # pylint: disable=not-cal
lable |
203 for key, value in self.CONFIG.iteritems(): | 193 for key, value in six.iteritems(self.CONFIG): |
204 setattr(self.checker.config, key, value) | 194 setattr(self.checker.config, key, value) |
205 self.checker.open() | 195 self.checker.open() |
206 | 196 |
207 @contextlib.contextmanager | 197 @contextlib.contextmanager |
208 def assertNoMessages(self): | 198 def assertNoMessages(self): |
209 """Assert that no messages are added by the given method.""" | 199 """Assert that no messages are added by the given method.""" |
210 with self.assertAddsMessages(): | 200 with self.assertAddsMessages(): |
211 yield | 201 yield |
212 | 202 |
213 @contextlib.contextmanager | 203 @contextlib.contextmanager |
(...skipping 29 matching lines...) Expand all Loading... |
243 if linesep != '\n': | 233 if linesep != '\n': |
244 LINE_RGX = re.compile(linesep) | 234 LINE_RGX = re.compile(linesep) |
245 def ulines(string): | 235 def ulines(string): |
246 return LINE_RGX.sub('\n', string) | 236 return LINE_RGX.sub('\n', string) |
247 else: | 237 else: |
248 def ulines(string): | 238 def ulines(string): |
249 return string | 239 return string |
250 | 240 |
251 INFO_TEST_RGX = re.compile(r'^func_i\d\d\d\d$') | 241 INFO_TEST_RGX = re.compile(r'^func_i\d\d\d\d$') |
252 | 242 |
253 def exception_str(self, ex): | 243 def exception_str(self, ex): # pylint: disable=unused-argument |
254 """function used to replace default __str__ method of exception instances""" | 244 """function used to replace default __str__ method of exception instances""" |
255 return 'in %s\n:: %s' % (ex.file, ', '.join(ex.args)) | 245 return 'in %s\n:: %s' % (ex.file, ', '.join(ex.args)) |
256 | 246 |
257 # Test classes | 247 # Test classes |
258 | 248 |
259 class LintTestUsingModule(testlib.TestCase): | 249 class LintTestUsingModule(unittest.TestCase): |
260 INPUT_DIR = None | 250 INPUT_DIR = None |
261 DEFAULT_PACKAGE = 'input' | 251 DEFAULT_PACKAGE = 'input' |
262 package = DEFAULT_PACKAGE | 252 package = DEFAULT_PACKAGE |
263 linter = linter | 253 linter = linter |
264 module = None | 254 module = None |
265 depends = None | 255 depends = None |
266 output = None | 256 output = None |
267 _TEST_TYPE = 'module' | 257 _TEST_TYPE = 'module' |
| 258 maxDiff = None |
268 | 259 |
269 def shortDescription(self): | 260 def shortDescription(self): |
270 values = {'mode' : self._TEST_TYPE, | 261 values = {'mode' : self._TEST_TYPE, |
271 'input': self.module, | 262 'input': self.module, |
272 'pkg': self.package, | 263 'pkg': self.package, |
273 'cls': self.__class__.__name__} | 264 'cls': self.__class__.__name__} |
274 | 265 |
275 if self.package == self.DEFAULT_PACKAGE: | 266 if self.package == self.DEFAULT_PACKAGE: |
276 msg = '%(mode)s test of input file "%(input)s" (%(cls)s)' | 267 msg = '%(mode)s test of input file "%(input)s" (%(cls)s)' |
277 else: | 268 else: |
(...skipping 11 matching lines...) Expand all Loading... |
289 self.assertMultiLineEqual(self._get_expected().strip()+'\n', | 280 self.assertMultiLineEqual(self._get_expected().strip()+'\n', |
290 got.strip()+'\n') | 281 got.strip()+'\n') |
291 | 282 |
292 def _test(self, tocheck): | 283 def _test(self, tocheck): |
293 if INFO_TEST_RGX.match(self.module): | 284 if INFO_TEST_RGX.match(self.module): |
294 self.linter.enable('I') | 285 self.linter.enable('I') |
295 else: | 286 else: |
296 self.linter.disable('I') | 287 self.linter.disable('I') |
297 try: | 288 try: |
298 self.linter.check(tocheck) | 289 self.linter.check(tocheck) |
299 except Exception, ex: | 290 except Exception as ex: |
300 # need finalization to restore a correct state | 291 # need finalization to restore a correct state |
301 self.linter.reporter.finalize() | 292 self.linter.reporter.finalize() |
302 ex.file = tocheck | 293 ex.file = tocheck |
303 print ex | 294 print(ex) |
304 ex.__str__ = exception_str | 295 ex.__str__ = exception_str |
305 raise | 296 raise |
306 self._check_result(self.linter.reporter.finalize()) | 297 self._check_result(self.linter.reporter.finalize()) |
307 | 298 |
308 def _has_output(self): | 299 def _has_output(self): |
309 return not self.module.startswith('func_noerror_') | 300 return not self.module.startswith('func_noerror_') |
310 | 301 |
311 def _get_expected(self): | 302 def _get_expected(self): |
312 if self._has_output() and self.output: | 303 if self._has_output() and self.output: |
313 with open(self.output, 'U') as fobj: | 304 with open(self.output, 'U') as fobj: |
(...skipping 26 matching lines...) Expand all Loading... |
340 except IOError: | 331 except IOError: |
341 expected = '' | 332 expected = '' |
342 if got != expected: | 333 if got != expected: |
343 with open(self.output, 'w') as fobj: | 334 with open(self.output, 'w') as fobj: |
344 fobj.write(got) | 335 fobj.write(got) |
345 | 336 |
346 # Callback | 337 # Callback |
347 | 338 |
348 def cb_test_gen(base_class): | 339 def cb_test_gen(base_class): |
349 def call(input_dir, msg_dir, module_file, messages_file, dependencies): | 340 def call(input_dir, msg_dir, module_file, messages_file, dependencies): |
| 341 # pylint: disable=no-init |
350 class LintTC(base_class): | 342 class LintTC(base_class): |
351 module = module_file.replace('.py', '') | 343 module = module_file.replace('.py', '') |
352 output = messages_file | 344 output = messages_file |
353 depends = dependencies or None | 345 depends = dependencies or None |
354 tags = testlib.Tags(('generated', 'pylint_input_%s' % module)) | |
355 INPUT_DIR = input_dir | 346 INPUT_DIR = input_dir |
356 MSG_DIR = msg_dir | 347 MSG_DIR = msg_dir |
357 return LintTC | 348 return LintTC |
358 return call | 349 return call |
359 | 350 |
360 # Main function | 351 # Main function |
361 | 352 |
362 def make_tests(input_dir, msg_dir, filter_rgx, callbacks): | 353 def make_tests(input_dir, msg_dir, filter_rgx, callbacks): |
363 """generate tests classes from test info | 354 """generate tests classes from test info |
364 | 355 |
365 return the list of generated test classes | 356 return the list of generated test classes |
366 """ | 357 """ |
367 if filter_rgx: | 358 if filter_rgx: |
368 is_to_run = re.compile(filter_rgx).search | 359 is_to_run = re.compile(filter_rgx).search |
369 else: | 360 else: |
370 is_to_run = lambda x: 1 | 361 is_to_run = lambda x: 1 |
371 tests = [] | 362 tests = [] |
372 for module_file, messages_file in ( | 363 for module_file, messages_file in ( |
373 get_tests_info(input_dir, msg_dir, 'func_', '') | 364 get_tests_info(input_dir, msg_dir, 'func_', '') |
374 ): | 365 ): |
375 if not is_to_run(module_file): | 366 if not is_to_run(module_file) or module_file.endswith('.pyc'): |
376 continue | 367 continue |
377 base = module_file.replace('func_', '').replace('.py', '') | 368 base = module_file.replace('func_', '').replace('.py', '') |
378 | 369 |
379 dependencies = get_tests_info(input_dir, msg_dir, base, '.py') | 370 dependencies = get_tests_info(input_dir, msg_dir, base, '.py') |
380 | 371 |
381 for callback in callbacks: | 372 for callback in callbacks: |
382 test = callback(input_dir, msg_dir, module_file, messages_file, | 373 test = callback(input_dir, msg_dir, module_file, messages_file, |
383 dependencies) | 374 dependencies) |
384 if test: | 375 if test: |
385 tests.append(test) | 376 tests.append(test) |
386 return tests | 377 return tests |
| 378 |
| 379 def tokenize_str(code): |
| 380 return list(tokenize.generate_tokens(StringIO(code).readline)) |
| 381 |
| 382 @contextlib.contextmanager |
| 383 def create_tempfile(content=None): |
| 384 """Create a new temporary file. |
| 385 |
| 386 If *content* parameter is given, then it will be written |
| 387 in the temporary file, before passing it back. |
| 388 This is a context manager and should be used with a *with* statement. |
| 389 """ |
| 390 # Can't use tempfile.NamedTemporaryFile here |
| 391 # because on Windows the file must be closed before writing to it, |
| 392 # see http://bugs.python.org/issue14243 |
| 393 fd, tmp = tempfile.mkstemp() |
| 394 if content: |
| 395 if sys.version_info >= (3, 0): |
| 396 # erff |
| 397 os.write(fd, bytes(content, 'ascii')) |
| 398 else: |
| 399 os.write(fd, content) |
| 400 try: |
| 401 yield tmp |
| 402 finally: |
| 403 os.close(fd) |
| 404 os.remove(tmp) |
| 405 |
| 406 @contextlib.contextmanager |
| 407 def create_file_backed_module(code): |
| 408 """Create an astroid module for the given code, backed by a real file.""" |
| 409 with create_tempfile() as temp: |
| 410 module = test_utils.build_module(code) |
| 411 module.file = temp |
| 412 yield module |
OLD | NEW |