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

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

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

Powered by Google App Engine
This is Rietveld 408576698