Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

testutils.py 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. # Copyright (c) 2012-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
  2. # Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
  3. # Copyright (c) 2013-2017 Claudiu Popa <pcmanticore@gmail.com>
  4. # Copyright (c) 2013-2014 Google, Inc.
  5. # Copyright (c) 2013 buck@yelp.com <buck@yelp.com>
  6. # Copyright (c) 2014 LCD 47 <lcd047@gmail.com>
  7. # Copyright (c) 2014 Brett Cannon <brett@python.org>
  8. # Copyright (c) 2014 Ricardo Gemignani <ricardo.gemignani@gmail.com>
  9. # Copyright (c) 2014 Arun Persaud <arun@nubati.net>
  10. # Copyright (c) 2015 Pavel Roskin <proski@gnu.org>
  11. # Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
  12. # Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
  13. # Copyright (c) 2016 Roy Williams <roy.williams.iii@gmail.com>
  14. # Copyright (c) 2016 xmo-odoo <xmo-odoo@users.noreply.github.com>
  15. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  16. # For details: https://github.com/PyCQA/pylint/blob/master/COPYING
  17. """functional/non regression tests for pylint"""
  18. from __future__ import print_function
  19. import collections
  20. import contextlib
  21. import functools
  22. from glob import glob
  23. import os
  24. from os import linesep, getcwd, sep
  25. from os.path import abspath, basename, dirname, join, splitext
  26. import sys
  27. import tempfile
  28. import tokenize
  29. import six
  30. from six.moves import StringIO
  31. import astroid
  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. # Utils
  38. SYS_VERS_STR = '%d%d%d' % sys.version_info[:3]
  39. TITLE_UNDERLINES = ['', '=', '-', '.']
  40. PREFIX = abspath(dirname(__file__))
  41. PY3K = sys.version_info[0] == 3
  42. def _get_tests_info(input_dir, msg_dir, prefix, suffix):
  43. """get python input examples and output messages
  44. We use following conventions for input files and messages:
  45. for different inputs:
  46. test for python >= x.y -> input = <name>_pyxy.py
  47. test for python < x.y -> input = <name>_py_xy.py
  48. for one input and different messages:
  49. message for python >= x.y -> message = <name>_pyxy.txt
  50. lower versions -> message with highest num
  51. """
  52. result = []
  53. for fname in glob(join(input_dir, prefix + '*' + suffix)):
  54. infile = basename(fname)
  55. fbase = splitext(infile)[0]
  56. # filter input files :
  57. pyrestr = fbase.rsplit('_py', 1)[-1] # like _26 or 26
  58. if pyrestr.isdigit(): # '24', '25'...
  59. if SYS_VERS_STR < pyrestr:
  60. continue
  61. if pyrestr.startswith('_') and pyrestr[1:].isdigit():
  62. # skip test for higher python versions
  63. if SYS_VERS_STR >= pyrestr[1:]:
  64. continue
  65. messages = glob(join(msg_dir, fbase + '*.txt'))
  66. # the last one will be without ext, i.e. for all or upper versions:
  67. if messages:
  68. for outfile in sorted(messages, reverse=True):
  69. py_rest = outfile.rsplit('_py', 1)[-1][:-4]
  70. if py_rest.isdigit() and SYS_VERS_STR >= py_rest:
  71. break
  72. else:
  73. # This will provide an error message indicating the missing filename.
  74. outfile = join(msg_dir, fbase + '.txt')
  75. result.append((infile, outfile))
  76. return result
  77. class TestReporter(BaseReporter):
  78. """reporter storing plain text messages"""
  79. __implements__ = IReporter
  80. def __init__(self): # pylint: disable=super-init-not-called
  81. self.message_ids = {}
  82. self.reset()
  83. self.path_strip_prefix = getcwd() + sep
  84. def reset(self):
  85. self.out = StringIO()
  86. self.messages = []
  87. def handle_message(self, msg):
  88. """manage message of different type and in the context of path """
  89. obj = msg.obj
  90. line = msg.line
  91. msg_id = msg.msg_id
  92. msg = msg.msg
  93. self.message_ids[msg_id] = 1
  94. if obj:
  95. obj = ':%s' % obj
  96. sigle = msg_id[0]
  97. if PY3K and linesep != '\n':
  98. # 2to3 writes os.linesep instead of using
  99. # the previosly used line separators
  100. msg = msg.replace('\r\n', '\n')
  101. self.messages.append('%s:%3s%s: %s' % (sigle, line, obj, msg))
  102. def finalize(self):
  103. self.messages.sort()
  104. for msg in self.messages:
  105. print(msg, file=self.out)
  106. result = self.out.getvalue()
  107. self.reset()
  108. return result
  109. # pylint: disable=unused-argument
  110. def on_set_current_module(self, module, filepath):
  111. pass
  112. # pylint: enable=unused-argument
  113. def display_reports(self, layout):
  114. """ignore layouts"""
  115. _display = None
  116. class MinimalTestReporter(BaseReporter):
  117. def handle_message(self, msg):
  118. self.messages.append(msg)
  119. def on_set_current_module(self, module, filepath):
  120. self.messages = []
  121. _display = None
  122. class Message(collections.namedtuple('Message',
  123. ['msg_id', 'line', 'node', 'args', 'confidence'])):
  124. def __new__(cls, msg_id, line=None, node=None, args=None, confidence=None):
  125. return tuple.__new__(cls, (msg_id, line, node, args, confidence))
  126. def __eq__(self, other):
  127. if isinstance(other, Message):
  128. if self.confidence and other.confidence:
  129. return super(Message, self).__eq__(other)
  130. return self[:-1] == other[:-1]
  131. return NotImplemented # pragma: no cover
  132. __hash__ = None
  133. class UnittestLinter(object):
  134. """A fake linter class to capture checker messages."""
  135. # pylint: disable=unused-argument, no-self-use
  136. def __init__(self):
  137. self._messages = []
  138. self.stats = {}
  139. def release_messages(self):
  140. try:
  141. return self._messages
  142. finally:
  143. self._messages = []
  144. def add_message(self, msg_id, line=None, node=None, args=None, confidence=None):
  145. self._messages.append(Message(msg_id, line, node, args, confidence))
  146. def is_message_enabled(self, *unused_args, **unused_kwargs):
  147. return True
  148. def add_stats(self, **kwargs):
  149. for name, value in six.iteritems(kwargs):
  150. self.stats[name] = value
  151. return self.stats
  152. @property
  153. def options_providers(self):
  154. return linter.options_providers
  155. def set_config(**kwargs):
  156. """Decorator for setting config values on a checker."""
  157. def _wrapper(fun):
  158. @functools.wraps(fun)
  159. def _forward(self):
  160. for key, value in six.iteritems(kwargs):
  161. setattr(self.checker.config, key, value)
  162. if isinstance(self, CheckerTestCase):
  163. # reopen checker in case, it may be interested in configuration change
  164. self.checker.open()
  165. fun(self)
  166. return _forward
  167. return _wrapper
  168. class CheckerTestCase(object):
  169. """A base testcase class for unit testing individual checker classes."""
  170. CHECKER_CLASS = None
  171. CONFIG = {}
  172. def setup_method(self):
  173. self.linter = UnittestLinter()
  174. self.checker = self.CHECKER_CLASS(self.linter) # pylint: disable=not-callable
  175. for key, value in six.iteritems(self.CONFIG):
  176. setattr(self.checker.config, key, value)
  177. self.checker.open()
  178. @contextlib.contextmanager
  179. def assertNoMessages(self):
  180. """Assert that no messages are added by the given method."""
  181. with self.assertAddsMessages():
  182. yield
  183. @contextlib.contextmanager
  184. def assertAddsMessages(self, *messages):
  185. """Assert that exactly the given method adds the given messages.
  186. The list of messages must exactly match *all* the messages added by the
  187. method. Additionally, we check to see whether the args in each message can
  188. actually be substituted into the message string.
  189. """
  190. yield
  191. got = self.linter.release_messages()
  192. msg = ('Expected messages did not match actual.\n'
  193. 'Expected:\n%s\nGot:\n%s' % ('\n'.join(repr(m) for m in messages),
  194. '\n'.join(repr(m) for m in got)))
  195. assert list(messages) == got, msg
  196. def walk(self, node):
  197. """recursive walk on the given node"""
  198. walker = PyLintASTWalker(linter)
  199. walker.add_checker(self.checker)
  200. walker.walk(node)
  201. # Init
  202. test_reporter = TestReporter()
  203. linter = PyLinter()
  204. linter.set_reporter(test_reporter)
  205. linter.config.persistent = 0
  206. checkers.initialize(linter)
  207. def _tokenize_str(code):
  208. return list(tokenize.generate_tokens(StringIO(code).readline))
  209. @contextlib.contextmanager
  210. def _create_tempfile(content=None):
  211. """Create a new temporary file.
  212. If *content* parameter is given, then it will be written
  213. in the temporary file, before passing it back.
  214. This is a context manager and should be used with a *with* statement.
  215. """
  216. # Can't use tempfile.NamedTemporaryFile here
  217. # because on Windows the file must be closed before writing to it,
  218. # see http://bugs.python.org/issue14243
  219. file_handle, tmp = tempfile.mkstemp()
  220. if content:
  221. if sys.version_info >= (3, 0):
  222. # erff
  223. os.write(file_handle, bytes(content, 'ascii'))
  224. else:
  225. os.write(file_handle, content)
  226. try:
  227. yield tmp
  228. finally:
  229. os.close(file_handle)
  230. os.remove(tmp)
  231. @contextlib.contextmanager
  232. def _create_file_backed_module(code):
  233. """Create an astroid module for the given code, backed by a real file."""
  234. with _create_tempfile() as temp:
  235. module = astroid.parse(code)
  236. module.file = temp
  237. yield module