summaryrefslogtreecommitdiff
path: root/testutils.py
diff options
context:
space:
mode:
Diffstat (limited to 'testutils.py')
-rw-r--r--testutils.py246
1 files changed, 246 insertions, 0 deletions
diff --git a/testutils.py b/testutils.py
new file mode 100644
index 0000000..c61c2e4
--- /dev/null
+++ b/testutils.py
@@ -0,0 +1,246 @@
+# Copyright (c) 2003-2008 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""functional/non regression tests for pylint"""
+
+import unittest
+import sys
+import re
+
+from glob import glob
+from os import linesep
+from os.path import exists, abspath, dirname, join, basename, splitext
+from cStringIO import StringIO
+
+from logilab.common import testlib
+
+from logilab.astng import MANAGER
+from pylint.reporters import BaseReporter
+from pylint.interfaces import IReporter
+from pylint.lint import PyLinter
+from pylint import checkers
+
+
+# Utils
+
+SYS_VERS_STR = '%d%d' % sys.version_info[:2]
+TITLE_UNDERLINES = ['', '=', '-', '.']
+PREFIX = abspath(dirname(__file__))
+
+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:
+ don't test for python < x.y -> input = <name>_pyxy.py
+ don't 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
+ higher 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:
+ outfile = None
+ result.append((infile, outfile))
+ return result
+
+class TestReporter(BaseReporter):
+ """ store 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 """
+ fpath, module, object, line, _ = location
+ self.message_ids[msg_id] = 1
+ if object:
+ object = ':%s' % object
+ sigle = msg_id[0]
+ self.messages.append('%s:%3s%s: %s' % (sigle, line, object, 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"""
+
+# 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('^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):
+ DEFAULT_PACKAGE = 'input'
+ package = DEFAULT_PACKAGE
+ linter = linter
+ module = None
+ depends = 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, file in self.depends]
+ self._test(tocheck)
+
+ 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
+ got = self.linter.reporter.finalize()
+ self.assertMultiLineEqual(got, self._get_expected())
+
+
+ def _get_expected(self):
+ if self.module.startswith('func_noerror_'):
+ expected = ''
+ else:
+ output = open(self.output)
+ expected = output.read().strip() + '\n'
+ output.close()
+ return expected
+
+class LintTestUsingFile(LintTestUsingModule):
+
+ _TEST_TYPE = 'file'
+
+ def test_functionality(self):
+ tocheck = [join(self.INPUT_DIR, self.module + '.py')]
+ if self.depends:
+ tocheck += [join(self.INPUT_DIR, name) for name, _file in self.depends]
+ self._test(tocheck)
+
+# 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_', '.py'):
+ 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