| Index: third_party/pylint/testutils.py
|
| diff --git a/third_party/pylint/testutils.py b/third_party/pylint/testutils.py
|
| deleted file mode 100644
|
| index 1dd083fe78eec50527c9a2cf31102ebfa36cfbdc..0000000000000000000000000000000000000000
|
| --- a/third_party/pylint/testutils.py
|
| +++ /dev/null
|
| @@ -1,386 +0,0 @@
|
| -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
|
| -# http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
| -#
|
| -# This program is free software; you can redistribute it and/or modify it under
|
| -# the terms of the GNU General Public License as published by the Free Software
|
| -# Foundation; either version 2 of the License, or (at your option) any later
|
| -# version.
|
| -#
|
| -# This program is distributed in the hope that it will be useful, but WITHOUT
|
| -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
| -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
| -#
|
| -# You should have received a copy of the GNU General Public License along with
|
| -# this program; if not, write to the Free Software Foundation, Inc.,
|
| -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
| -"""functional/non regression tests for pylint"""
|
| -from __future__ import with_statement
|
| -
|
| -import collections
|
| -import contextlib
|
| -import functools
|
| -import sys
|
| -import re
|
| -
|
| -from glob import glob
|
| -from os import linesep
|
| -from os.path import abspath, basename, dirname, isdir, join, splitext
|
| -from cStringIO import StringIO
|
| -
|
| -from logilab.common import testlib
|
| -
|
| -from pylint import checkers
|
| -from pylint.utils import PyLintASTWalker
|
| -from pylint.reporters import BaseReporter
|
| -from pylint.interfaces import IReporter
|
| -from pylint.lint import PyLinter
|
| -
|
| -
|
| -# Utils
|
| -
|
| -SYS_VERS_STR = '%d%d%d' % sys.version_info[:3]
|
| -TITLE_UNDERLINES = ['', '=', '-', '.']
|
| -PREFIX = abspath(dirname(__file__))
|
| -PY3K = sys.version_info[0] == 3
|
| -
|
| -def fix_path():
|
| - sys.path.insert(0, PREFIX)
|
| -
|
| -def get_tests_info(input_dir, msg_dir, prefix, suffix):
|
| - """get python input examples and output messages
|
| -
|
| - We use following conventions for input files and messages:
|
| - for different inputs:
|
| - test for python >= x.y -> input = <name>_pyxy.py
|
| - test for python < x.y -> input = <name>_py_xy.py
|
| - for one input and different messages:
|
| - message for python >= x.y -> message = <name>_pyxy.txt
|
| - lower versions -> message with highest num
|
| - """
|
| - result = []
|
| - for fname in glob(join(input_dir, prefix + '*' + suffix)):
|
| - infile = basename(fname)
|
| - fbase = splitext(infile)[0]
|
| - # filter input files :
|
| - pyrestr = fbase.rsplit('_py', 1)[-1] # like _26 or 26
|
| - if pyrestr.isdigit(): # '24', '25'...
|
| - if SYS_VERS_STR < pyrestr:
|
| - continue
|
| - if pyrestr.startswith('_') and pyrestr[1:].isdigit():
|
| - # skip test for higher python versions
|
| - if SYS_VERS_STR >= pyrestr[1:]:
|
| - continue
|
| - messages = glob(join(msg_dir, fbase + '*.txt'))
|
| - # the last one will be without ext, i.e. for all or upper versions:
|
| - if messages:
|
| - for outfile in sorted(messages, reverse=True):
|
| - py_rest = outfile.rsplit('_py', 1)[-1][:-4]
|
| - if py_rest.isdigit() and SYS_VERS_STR >= py_rest:
|
| - break
|
| - else:
|
| - # This will provide an error message indicating the missing filename.
|
| - outfile = join(msg_dir, fbase + '.txt')
|
| - result.append((infile, outfile))
|
| - return result
|
| -
|
| -
|
| -class TestReporter(BaseReporter):
|
| - """reporter storing plain text messages"""
|
| -
|
| - __implements____ = IReporter
|
| -
|
| - def __init__(self):
|
| - self.message_ids = {}
|
| - self.reset()
|
| -
|
| - def reset(self):
|
| - self.out = StringIO()
|
| - self.messages = []
|
| -
|
| - def add_message(self, msg_id, location, msg):
|
| - """manage message of different type and in the context of path """
|
| - _, _, obj, line, _ = location
|
| - self.message_ids[msg_id] = 1
|
| - if obj:
|
| - obj = ':%s' % obj
|
| - sigle = msg_id[0]
|
| - if PY3K and linesep != '\n':
|
| - # 2to3 writes os.linesep instead of using
|
| - # the previosly used line separators
|
| - msg = msg.replace('\r\n', '\n')
|
| - self.messages.append('%s:%3s%s: %s' % (sigle, line, obj, msg))
|
| -
|
| - def finalize(self):
|
| - self.messages.sort()
|
| - for msg in self.messages:
|
| - print >> self.out, msg
|
| - result = self.out.getvalue()
|
| - self.reset()
|
| - return result
|
| -
|
| - def display_results(self, layout):
|
| - """ignore layouts"""
|
| -
|
| -
|
| -if sys.version_info < (2, 6):
|
| - class Message(tuple):
|
| - def __new__(cls, msg_id, line=None, node=None, args=None):
|
| - return tuple.__new__(cls, (msg_id, line, node, args))
|
| -
|
| - @property
|
| - def msg_id(self):
|
| - return self[0]
|
| - @property
|
| - def line(self):
|
| - return self[1]
|
| - @property
|
| - def node(self):
|
| - return self[2]
|
| - @property
|
| - def args(self):
|
| - return self[3]
|
| -
|
| -
|
| -else:
|
| - class Message(collections.namedtuple('Message',
|
| - ['msg_id', 'line', 'node', 'args'])):
|
| - def __new__(cls, msg_id, line=None, node=None, args=None):
|
| - return tuple.__new__(cls, (msg_id, line, node, args))
|
| -
|
| -
|
| -class UnittestLinter(object):
|
| - """A fake linter class to capture checker messages."""
|
| -
|
| - def __init__(self):
|
| - self._messages = []
|
| - self.stats = {}
|
| -
|
| - def release_messages(self):
|
| - try:
|
| - return self._messages
|
| - finally:
|
| - self._messages = []
|
| -
|
| - def add_message(self, msg_id, line=None, node=None, args=None):
|
| - self._messages.append(Message(msg_id, line, node, args))
|
| -
|
| - def is_message_enabled(self, *unused_args):
|
| - return True
|
| -
|
| - def add_stats(self, **kwargs):
|
| - for name, value in kwargs.iteritems():
|
| - self.stats[name] = value
|
| - return self.stats
|
| -
|
| - @property
|
| - def options_providers(self):
|
| - return linter.options_providers
|
| -
|
| -def set_config(**kwargs):
|
| - """Decorator for setting config values on a checker."""
|
| - def _Wrapper(fun):
|
| - @functools.wraps(fun)
|
| - def _Forward(self):
|
| - for key, value in kwargs.iteritems():
|
| - setattr(self.checker.config, key, value)
|
| - if isinstance(self, CheckerTestCase):
|
| - # reopen checker in case, it may be interested in configuration change
|
| - self.checker.open()
|
| - fun(self)
|
| -
|
| - return _Forward
|
| - return _Wrapper
|
| -
|
| -
|
| -class CheckerTestCase(testlib.TestCase):
|
| - """A base testcase class for unittesting individual checker classes."""
|
| - CHECKER_CLASS = None
|
| - CONFIG = {}
|
| -
|
| - def setUp(self):
|
| - self.linter = UnittestLinter()
|
| - self.checker = self.CHECKER_CLASS(self.linter) # pylint: disable=not-callable
|
| - for key, value in self.CONFIG.iteritems():
|
| - setattr(self.checker.config, key, value)
|
| - self.checker.open()
|
| -
|
| - @contextlib.contextmanager
|
| - def assertNoMessages(self):
|
| - """Assert that no messages are added by the given method."""
|
| - with self.assertAddsMessages():
|
| - yield
|
| -
|
| - @contextlib.contextmanager
|
| - def assertAddsMessages(self, *messages):
|
| - """Assert that exactly the given method adds the given messages.
|
| -
|
| - The list of messages must exactly match *all* the messages added by the
|
| - method. Additionally, we check to see whether the args in each message can
|
| - actually be substituted into the message string.
|
| - """
|
| - yield
|
| - got = self.linter.release_messages()
|
| - msg = ('Expected messages did not match actual.\n'
|
| - 'Expected:\n%s\nGot:\n%s' % ('\n'.join(repr(m) for m in messages),
|
| - '\n'.join(repr(m) for m in got)))
|
| - self.assertEqual(list(messages), got, msg)
|
| -
|
| - def walk(self, node):
|
| - """recursive walk on the given node"""
|
| - walker = PyLintASTWalker(linter)
|
| - walker.add_checker(self.checker)
|
| - walker.walk(node)
|
| -
|
| -
|
| -# Init
|
| -test_reporter = TestReporter()
|
| -linter = PyLinter()
|
| -linter.set_reporter(test_reporter)
|
| -linter.config.persistent = 0
|
| -checkers.initialize(linter)
|
| -linter.global_set_option('required-attributes', ('__revision__',))
|
| -
|
| -if linesep != '\n':
|
| - LINE_RGX = re.compile(linesep)
|
| - def ulines(string):
|
| - return LINE_RGX.sub('\n', string)
|
| -else:
|
| - def ulines(string):
|
| - return string
|
| -
|
| -INFO_TEST_RGX = re.compile(r'^func_i\d\d\d\d$')
|
| -
|
| -def exception_str(self, ex):
|
| - """function used to replace default __str__ method of exception instances"""
|
| - return 'in %s\n:: %s' % (ex.file, ', '.join(ex.args))
|
| -
|
| -# Test classes
|
| -
|
| -class LintTestUsingModule(testlib.TestCase):
|
| - INPUT_DIR = None
|
| - DEFAULT_PACKAGE = 'input'
|
| - package = DEFAULT_PACKAGE
|
| - linter = linter
|
| - module = None
|
| - depends = None
|
| - output = None
|
| - _TEST_TYPE = 'module'
|
| -
|
| - def shortDescription(self):
|
| - values = {'mode' : self._TEST_TYPE,
|
| - 'input': self.module,
|
| - 'pkg': self.package,
|
| - 'cls': self.__class__.__name__}
|
| -
|
| - if self.package == self.DEFAULT_PACKAGE:
|
| - msg = '%(mode)s test of input file "%(input)s" (%(cls)s)'
|
| - else:
|
| - msg = '%(mode)s test of input file "%(input)s" in "%(pkg)s" (%(cls)s)'
|
| - return msg % values
|
| -
|
| - def test_functionality(self):
|
| - tocheck = [self.package+'.'+self.module]
|
| - if self.depends:
|
| - tocheck += [self.package+'.%s' % name.replace('.py', '')
|
| - for name, _ in self.depends]
|
| - self._test(tocheck)
|
| -
|
| - def _check_result(self, got):
|
| - self.assertMultiLineEqual(self._get_expected().strip()+'\n',
|
| - got.strip()+'\n')
|
| -
|
| - def _test(self, tocheck):
|
| - if INFO_TEST_RGX.match(self.module):
|
| - self.linter.enable('I')
|
| - else:
|
| - self.linter.disable('I')
|
| - try:
|
| - self.linter.check(tocheck)
|
| - except Exception, ex:
|
| - # need finalization to restore a correct state
|
| - self.linter.reporter.finalize()
|
| - ex.file = tocheck
|
| - print ex
|
| - ex.__str__ = exception_str
|
| - raise
|
| - self._check_result(self.linter.reporter.finalize())
|
| -
|
| - def _has_output(self):
|
| - return not self.module.startswith('func_noerror_')
|
| -
|
| - def _get_expected(self):
|
| - if self._has_output() and self.output:
|
| - with open(self.output, 'U') as fobj:
|
| - return fobj.read().strip() + '\n'
|
| - else:
|
| - return ''
|
| -
|
| -class LintTestUsingFile(LintTestUsingModule):
|
| -
|
| - _TEST_TYPE = 'file'
|
| -
|
| - def test_functionality(self):
|
| - importable = join(self.INPUT_DIR, self.module)
|
| - # python also prefers packages over simple modules.
|
| - if not isdir(importable):
|
| - importable += '.py'
|
| - tocheck = [importable]
|
| - if self.depends:
|
| - tocheck += [join(self.INPUT_DIR, name) for name, _file in self.depends]
|
| - self._test(tocheck)
|
| -
|
| -class LintTestUpdate(LintTestUsingModule):
|
| -
|
| - _TEST_TYPE = 'update'
|
| -
|
| - def _check_result(self, got):
|
| - if self._has_output():
|
| - try:
|
| - expected = self._get_expected()
|
| - except IOError:
|
| - expected = ''
|
| - if got != expected:
|
| - with open(self.output, 'w') as fobj:
|
| - fobj.write(got)
|
| -
|
| -# Callback
|
| -
|
| -def cb_test_gen(base_class):
|
| - def call(input_dir, msg_dir, module_file, messages_file, dependencies):
|
| - class LintTC(base_class):
|
| - module = module_file.replace('.py', '')
|
| - output = messages_file
|
| - depends = dependencies or None
|
| - tags = testlib.Tags(('generated', 'pylint_input_%s' % module))
|
| - INPUT_DIR = input_dir
|
| - MSG_DIR = msg_dir
|
| - return LintTC
|
| - return call
|
| -
|
| -# Main function
|
| -
|
| -def make_tests(input_dir, msg_dir, filter_rgx, callbacks):
|
| - """generate tests classes from test info
|
| -
|
| - return the list of generated test classes
|
| - """
|
| - if filter_rgx:
|
| - is_to_run = re.compile(filter_rgx).search
|
| - else:
|
| - is_to_run = lambda x: 1
|
| - tests = []
|
| - for module_file, messages_file in (
|
| - get_tests_info(input_dir, msg_dir, 'func_', '')
|
| - ):
|
| - if not is_to_run(module_file):
|
| - continue
|
| - base = module_file.replace('func_', '').replace('.py', '')
|
| -
|
| - dependencies = get_tests_info(input_dir, msg_dir, base, '.py')
|
| -
|
| - for callback in callbacks:
|
| - test = callback(input_dir, msg_dir, module_file, messages_file,
|
| - dependencies)
|
| - if test:
|
| - tests.append(test)
|
| - return tests
|
|
|