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.

unittest_checker_base.py 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2013-2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
  3. # Copyright (c) 2013-2014 Google, Inc.
  4. # Copyright (c) 2015-2017 Claudiu Popa <pcmanticore@gmail.com>
  5. # Copyright (c) 2015 Dmitry Pribysh <dmand@yandex.ru>
  6. # Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
  7. # Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
  8. # Copyright (c) 2016 Yannack <yannack@users.noreply.github.com>
  9. # Copyright (c) 2017 ttenhoeve-aa <ttenhoeve@appannie.com>
  10. # Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
  11. # Copyright (c) 2017 Ville Skyttä <ville.skytta@iki.fi>
  12. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  13. # For details: https://github.com/PyCQA/pylint/blob/master/COPYING
  14. """Unittest for the base checker."""
  15. import re
  16. import sys
  17. import unittest
  18. import astroid
  19. from pylint.checkers import base
  20. from pylint.testutils import CheckerTestCase, Message, set_config
  21. class TestDocstring(CheckerTestCase):
  22. CHECKER_CLASS = base.DocStringChecker
  23. def test_missing_docstring_module(self):
  24. module = astroid.parse("something")
  25. message = Message('missing-docstring', node=module, args=('module',))
  26. with self.assertAddsMessages(message):
  27. self.checker.visit_module(module)
  28. def test_missing_docstring_empty_module(self):
  29. module = astroid.parse("")
  30. with self.assertNoMessages():
  31. self.checker.visit_module(module)
  32. def test_empty_docstring_module(self):
  33. module = astroid.parse("''''''")
  34. message = Message('empty-docstring', node=module, args=('module',))
  35. with self.assertAddsMessages(message):
  36. self.checker.visit_module(module)
  37. def test_empty_docstring_function(self):
  38. func = astroid.extract_node("""
  39. def func(tion):
  40. pass""")
  41. message = Message('missing-docstring', node=func, args=('function',))
  42. with self.assertAddsMessages(message):
  43. self.checker.visit_functiondef(func)
  44. @set_config(docstring_min_length=2)
  45. def test_short_function_no_docstring(self):
  46. func = astroid.extract_node("""
  47. def func(tion):
  48. pass""")
  49. with self.assertNoMessages():
  50. self.checker.visit_functiondef(func)
  51. @set_config(docstring_min_length=2)
  52. def test_long_function_no_docstring(self):
  53. func = astroid.extract_node("""
  54. def func(tion):
  55. pass
  56. pass
  57. """)
  58. message = Message('missing-docstring', node=func, args=('function',))
  59. with self.assertAddsMessages(message):
  60. self.checker.visit_functiondef(func)
  61. @set_config(docstring_min_length=2)
  62. def test_long_function_nested_statements_no_docstring(self):
  63. func = astroid.extract_node("""
  64. def func(tion):
  65. try:
  66. pass
  67. except:
  68. pass
  69. """)
  70. message = Message('missing-docstring', node=func, args=('function',))
  71. with self.assertAddsMessages(message):
  72. self.checker.visit_functiondef(func)
  73. @set_config(docstring_min_length=2)
  74. def test_function_no_docstring_by_name(self):
  75. func = astroid.extract_node("""
  76. def __fun__(tion):
  77. pass""")
  78. with self.assertNoMessages():
  79. self.checker.visit_functiondef(func)
  80. def test_class_no_docstring(self):
  81. klass = astroid.extract_node("""
  82. class Klass(object):
  83. pass""")
  84. message = Message('missing-docstring', node=klass, args=('class',))
  85. with self.assertAddsMessages(message):
  86. self.checker.visit_classdef(klass)
  87. class TestNameChecker(CheckerTestCase):
  88. CHECKER_CLASS = base.NameChecker
  89. CONFIG = {
  90. 'bad_names': set(),
  91. }
  92. @set_config(attr_rgx=re.compile('[A-Z]+'),
  93. property_classes=('abc.abstractproperty', '.custom_prop'))
  94. def test_property_names(self):
  95. # If a method is annotated with @property, it's name should
  96. # match the attr regex. Since by default the attribute regex is the same
  97. # as the method regex, we override it here.
  98. methods = astroid.extract_node("""
  99. import abc
  100. def custom_prop(f):
  101. return property(f)
  102. class FooClass(object):
  103. @property
  104. def FOO(self): #@
  105. pass
  106. @property
  107. def bar(self): #@
  108. pass
  109. @abc.abstractproperty
  110. def BAZ(self): #@
  111. pass
  112. @custom_prop
  113. def QUX(self): #@
  114. pass
  115. """)
  116. with self.assertNoMessages():
  117. self.checker.visit_functiondef(methods[0])
  118. self.checker.visit_functiondef(methods[2])
  119. self.checker.visit_functiondef(methods[3])
  120. with self.assertAddsMessages(Message('invalid-name', node=methods[1],
  121. args=('Attribute', 'bar',
  122. "'[A-Z]+' pattern"))):
  123. self.checker.visit_functiondef(methods[1])
  124. @set_config(attr_rgx=re.compile('[A-Z]+'))
  125. def test_property_setters(self):
  126. method = astroid.extract_node("""
  127. class FooClass(object):
  128. @property
  129. def foo(self): pass
  130. @foo.setter
  131. def FOOSETTER(self): #@
  132. pass
  133. """)
  134. with self.assertNoMessages():
  135. self.checker.visit_functiondef(method)
  136. def test_module_level_names(self):
  137. assign = astroid.extract_node("""
  138. import collections
  139. Class = collections.namedtuple("a", ("b", "c")) #@
  140. """)
  141. with self.assertNoMessages():
  142. self.checker.visit_assignname(assign.targets[0])
  143. assign = astroid.extract_node("""
  144. class ClassA(object):
  145. pass
  146. ClassB = ClassA
  147. """)
  148. with self.assertNoMessages():
  149. self.checker.visit_assignname(assign.targets[0])
  150. module = astroid.parse("""
  151. def A():
  152. return 1, 2, 3
  153. CONSTA, CONSTB, CONSTC = A()
  154. CONSTD = A()""")
  155. with self.assertNoMessages():
  156. self.checker.visit_assignname(module.body[1].targets[0].elts[0])
  157. self.checker.visit_assignname(module.body[2].targets[0])
  158. assign = astroid.extract_node("""
  159. CONST = "12 34 ".rstrip().split()""")
  160. with self.assertNoMessages():
  161. self.checker.visit_assignname(assign.targets[0])
  162. @unittest.skipIf(sys.version_info >= (3, 0), reason="Needs Python 2.x")
  163. @set_config(const_rgx=re.compile(".+"))
  164. @set_config(function_rgx=re.compile(".+"))
  165. @set_config(class_rgx=re.compile(".+"))
  166. def test_assign_to_new_keyword_py2(self):
  167. ast = astroid.extract_node("""
  168. True = 0 #@
  169. False = 1 #@
  170. def True(): #@
  171. pass
  172. class True: #@
  173. pass
  174. """)
  175. with self.assertAddsMessages(
  176. Message(msg_id='assign-to-new-keyword', node=ast[0].targets[0], args=('True', '3.0'))
  177. ):
  178. self.checker.visit_assignname(ast[0].targets[0])
  179. with self.assertAddsMessages(
  180. Message(msg_id='assign-to-new-keyword', node=ast[1].targets[0], args=('False', '3.0'))
  181. ):
  182. self.checker.visit_assignname(ast[1].targets[0])
  183. with self.assertAddsMessages(
  184. Message(msg_id='assign-to-new-keyword', node=ast[2], args=('True', '3.0'))
  185. ):
  186. self.checker.visit_functiondef(ast[2])
  187. with self.assertAddsMessages(
  188. Message(msg_id='assign-to-new-keyword', node=ast[3], args=('True', '3.0'))
  189. ):
  190. self.checker.visit_classdef(ast[3])
  191. @unittest.skipIf(sys.version_info >= (3, 7), reason="Needs Python 3.6 or earlier")
  192. @set_config(const_rgx=re.compile(".+"))
  193. @set_config(function_rgx=re.compile(".+"))
  194. @set_config(class_rgx=re.compile(".+"))
  195. def test_assign_to_new_keyword_py3(self):
  196. ast = astroid.extract_node("""
  197. async = "foo" #@
  198. await = "bar" #@
  199. def async(): #@
  200. pass
  201. class async: #@
  202. pass
  203. """)
  204. with self.assertAddsMessages(
  205. Message(msg_id='assign-to-new-keyword', node=ast[0].targets[0], args=('async', '3.7'))
  206. ):
  207. self.checker.visit_assignname(ast[0].targets[0])
  208. with self.assertAddsMessages(
  209. Message(msg_id='assign-to-new-keyword', node=ast[1].targets[0], args=('await', '3.7'))
  210. ):
  211. self.checker.visit_assignname(ast[1].targets[0])
  212. with self.assertAddsMessages(
  213. Message(msg_id='assign-to-new-keyword', node=ast[2], args=('async', '3.7'))
  214. ):
  215. self.checker.visit_functiondef(ast[2])
  216. with self.assertAddsMessages(
  217. Message(msg_id='assign-to-new-keyword', node=ast[3], args=('async', '3.7'))
  218. ):
  219. self.checker.visit_classdef(ast[3])
  220. class TestMultiNamingStyle(CheckerTestCase):
  221. CHECKER_CLASS = base.NameChecker
  222. MULTI_STYLE_RE = re.compile('(?:(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$')
  223. @set_config(class_rgx=MULTI_STYLE_RE)
  224. def test_multi_name_detection_majority(self):
  225. classes = astroid.extract_node("""
  226. class classb(object): #@
  227. pass
  228. class CLASSA(object): #@
  229. pass
  230. class CLASSC(object): #@
  231. pass
  232. """)
  233. message = Message('invalid-name',
  234. node=classes[0],
  235. args=('Class', 'classb',
  236. "'(?:(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$' pattern"))
  237. with self.assertAddsMessages(message):
  238. for cls in classes:
  239. self.checker.visit_classdef(cls)
  240. self.checker.leave_module(cls.root)
  241. @set_config(class_rgx=MULTI_STYLE_RE)
  242. def test_multi_name_detection_first_invalid(self):
  243. classes = astroid.extract_node("""
  244. class class_a(object): #@
  245. pass
  246. class classb(object): #@
  247. pass
  248. class CLASSC(object): #@
  249. pass
  250. """)
  251. messages = [
  252. Message('invalid-name', node=classes[0],
  253. args=('Class', 'class_a', "'(?:(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$' pattern")),
  254. Message('invalid-name', node=classes[2],
  255. args=('Class', 'CLASSC', "'(?:(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$' pattern"))
  256. ]
  257. with self.assertAddsMessages(*messages):
  258. for cls in classes:
  259. self.checker.visit_classdef(cls)
  260. self.checker.leave_module(cls.root)
  261. @set_config(method_rgx=MULTI_STYLE_RE,
  262. function_rgx=MULTI_STYLE_RE,
  263. name_group=('function:method',))
  264. def test_multi_name_detection_group(self):
  265. function_defs = astroid.extract_node("""
  266. class First(object):
  267. def func(self): #@
  268. pass
  269. def FUNC(): #@
  270. pass
  271. """, module_name='test')
  272. message = Message('invalid-name', node=function_defs[1],
  273. args=('Function', 'FUNC',
  274. "'(?:(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$' pattern"))
  275. with self.assertAddsMessages(message):
  276. for func in function_defs:
  277. self.checker.visit_functiondef(func)
  278. self.checker.leave_module(func.root)
  279. @set_config(function_rgx=re.compile('(?:(?P<ignore>FOO)|(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$'))
  280. def test_multi_name_detection_exempt(self):
  281. function_defs = astroid.extract_node("""
  282. def FOO(): #@
  283. pass
  284. def lower(): #@
  285. pass
  286. def FOO(): #@
  287. pass
  288. def UPPER(): #@
  289. pass
  290. """)
  291. message = Message('invalid-name', node=function_defs[3],
  292. args=('Function', 'UPPER',
  293. "'(?:(?P<ignore>FOO)|(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$' pattern"))
  294. with self.assertAddsMessages(message):
  295. for func in function_defs:
  296. self.checker.visit_functiondef(func)
  297. self.checker.leave_module(func.root)
  298. class TestComparison(CheckerTestCase):
  299. CHECKER_CLASS = base.ComparisonChecker
  300. def test_comparison(self):
  301. node = astroid.extract_node("foo == True")
  302. message = Message('singleton-comparison',
  303. node=node,
  304. args=(True, "just 'expr' or 'expr is True'"))
  305. with self.assertAddsMessages(message):
  306. self.checker.visit_compare(node)
  307. node = astroid.extract_node("foo == False")
  308. message = Message('singleton-comparison',
  309. node=node,
  310. args=(False, "'not expr' or 'expr is False'"))
  311. with self.assertAddsMessages(message):
  312. self.checker.visit_compare(node)
  313. node = astroid.extract_node("foo == None")
  314. message = Message('singleton-comparison',
  315. node=node,
  316. args=(None, "'expr is None'"))
  317. with self.assertAddsMessages(message):
  318. self.checker.visit_compare(node)
  319. node = astroid.extract_node("True == foo")
  320. messages = (Message('misplaced-comparison-constant',
  321. node=node,
  322. args=('foo == True',)),
  323. Message('singleton-comparison',
  324. node=node,
  325. args=(True, "just 'expr' or 'expr is True'")))
  326. with self.assertAddsMessages(*messages):
  327. self.checker.visit_compare(node)
  328. node = astroid.extract_node("False == foo")
  329. messages = (Message('misplaced-comparison-constant',
  330. node=node,
  331. args=('foo == False',)),
  332. Message('singleton-comparison',
  333. node=node,
  334. args=(False, "'not expr' or 'expr is False'")))
  335. with self.assertAddsMessages(*messages):
  336. self.checker.visit_compare(node)
  337. node = astroid.extract_node("None == foo")
  338. messages = (Message('misplaced-comparison-constant',
  339. node=node,
  340. args=('foo == None',)),
  341. Message('singleton-comparison',
  342. node=node,
  343. args=(None, "'expr is None'")))
  344. with self.assertAddsMessages(*messages):
  345. self.checker.visit_compare(node)
  346. class TestNamePresets(unittest.TestCase):
  347. SNAKE_CASE_NAMES = {'test_snake_case', 'test_snake_case11', 'test_https_200'}
  348. CAMEL_CASE_NAMES = {'testCamelCase', 'testCamelCase11', 'testHTTP200'}
  349. UPPER_CASE_NAMES = {'TEST_UPPER_CASE', 'TEST_UPPER_CASE11', 'TEST_HTTP_200'}
  350. PASCAL_CASE_NAMES = {'TestPascalCase', 'TestPascalCase11', 'TestHTTP200'}
  351. ALL_NAMES = SNAKE_CASE_NAMES | CAMEL_CASE_NAMES | UPPER_CASE_NAMES | PASCAL_CASE_NAMES
  352. def _test_name_is_correct_for_all_name_types(self, naming_style, name):
  353. for name_type in base.KNOWN_NAME_TYPES:
  354. self._test_is_correct(naming_style, name, name_type)
  355. def _test_name_is_incorrect_for_all_name_types(self, naming_style, name):
  356. for name_type in base.KNOWN_NAME_TYPES:
  357. self._test_is_incorrect(naming_style, name, name_type)
  358. def _test_should_always_pass(self, naming_style):
  359. always_pass_data = [
  360. ('__add__', 'method'),
  361. ('__set_name__', 'method'),
  362. ('__version__', 'const'),
  363. ('__author__', 'const')
  364. ]
  365. for name, name_type in always_pass_data:
  366. self._test_is_correct(naming_style, name, name_type)
  367. def _test_is_correct(self, naming_style, name, name_type):
  368. rgx = naming_style.get_regex(name_type)
  369. self.assertTrue(rgx.match(name),
  370. "{!r} does not match pattern {!r} (style: {}, type: {})".
  371. format(name, rgx, naming_style, name_type))
  372. def _test_is_incorrect(self, naming_style, name, name_type):
  373. rgx = naming_style.get_regex(name_type)
  374. self.assertFalse(rgx.match(name),
  375. "{!r} match pattern {!r} but shouldn't (style: {}, type: {})".
  376. format(name, rgx, naming_style, name_type))
  377. def test_snake_case(self):
  378. naming_style = base.SnakeCaseStyle
  379. for name in self.SNAKE_CASE_NAMES:
  380. self._test_name_is_correct_for_all_name_types(naming_style, name)
  381. for name in self.ALL_NAMES - self.SNAKE_CASE_NAMES:
  382. self._test_name_is_incorrect_for_all_name_types(naming_style, name)
  383. self._test_should_always_pass(naming_style)
  384. def test_camel_case(self):
  385. naming_style = base.CamelCaseStyle
  386. for name in self.CAMEL_CASE_NAMES:
  387. self._test_name_is_correct_for_all_name_types(naming_style, name)
  388. for name in self.ALL_NAMES - self.CAMEL_CASE_NAMES:
  389. self._test_name_is_incorrect_for_all_name_types(naming_style, name)
  390. self._test_should_always_pass(naming_style)
  391. def test_upper_case(self):
  392. naming_style = base.UpperCaseStyle
  393. for name in self.UPPER_CASE_NAMES:
  394. self._test_name_is_correct_for_all_name_types(naming_style, name)
  395. for name in self.ALL_NAMES - self.UPPER_CASE_NAMES:
  396. self._test_name_is_incorrect_for_all_name_types(naming_style, name)
  397. self._test_should_always_pass(naming_style)
  398. def test_pascal_case(self):
  399. naming_style = base.PascalCaseStyle
  400. for name in self.PASCAL_CASE_NAMES:
  401. self._test_name_is_correct_for_all_name_types(naming_style, name)
  402. for name in self.ALL_NAMES - self.PASCAL_CASE_NAMES:
  403. self._test_name_is_incorrect_for_all_name_types(naming_style, name)
  404. self._test_should_always_pass(naming_style)