test_doctests.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. import textwrap
  2. from pyflakes import messages as m
  3. from pyflakes.checker import (
  4. PYPY,
  5. DoctestScope,
  6. FunctionScope,
  7. ModuleScope,
  8. )
  9. from pyflakes.test.test_other import Test as TestOther
  10. from pyflakes.test.test_imports import Test as TestImports
  11. from pyflakes.test.test_undefined_names import Test as TestUndefinedNames
  12. from pyflakes.test.harness import TestCase, skip
  13. class _DoctestMixin:
  14. withDoctest = True
  15. def doctestify(self, input):
  16. lines = []
  17. for line in textwrap.dedent(input).splitlines():
  18. if line.strip() == '':
  19. pass
  20. elif (line.startswith(' ') or
  21. line.startswith('except:') or
  22. line.startswith('except ') or
  23. line.startswith('finally:') or
  24. line.startswith('else:') or
  25. line.startswith('elif ') or
  26. (lines and lines[-1].startswith(('>>> @', '... @')))):
  27. line = "... %s" % line
  28. else:
  29. line = ">>> %s" % line
  30. lines.append(line)
  31. doctestificator = textwrap.dedent('''\
  32. def doctest_something():
  33. """
  34. %s
  35. """
  36. ''')
  37. return doctestificator % "\n ".join(lines)
  38. def flakes(self, input, *args, **kw):
  39. return super().flakes(self.doctestify(input), *args, **kw)
  40. class Test(TestCase):
  41. withDoctest = True
  42. def test_scope_class(self):
  43. """Check that a doctest is given a DoctestScope."""
  44. checker = self.flakes("""
  45. m = None
  46. def doctest_stuff():
  47. '''
  48. >>> d = doctest_stuff()
  49. '''
  50. f = m
  51. return f
  52. """)
  53. scopes = checker.deadScopes
  54. module_scopes = [
  55. scope for scope in scopes if scope.__class__ is ModuleScope]
  56. doctest_scopes = [
  57. scope for scope in scopes if scope.__class__ is DoctestScope]
  58. function_scopes = [
  59. scope for scope in scopes if scope.__class__ is FunctionScope]
  60. self.assertEqual(len(module_scopes), 1)
  61. self.assertEqual(len(doctest_scopes), 1)
  62. module_scope = module_scopes[0]
  63. doctest_scope = doctest_scopes[0]
  64. self.assertIsInstance(doctest_scope, DoctestScope)
  65. self.assertIsInstance(doctest_scope, ModuleScope)
  66. self.assertNotIsInstance(doctest_scope, FunctionScope)
  67. self.assertNotIsInstance(module_scope, DoctestScope)
  68. self.assertIn('m', module_scope)
  69. self.assertIn('doctest_stuff', module_scope)
  70. self.assertIn('d', doctest_scope)
  71. self.assertEqual(len(function_scopes), 1)
  72. self.assertIn('f', function_scopes[0])
  73. def test_nested_doctest_ignored(self):
  74. """Check that nested doctests are ignored."""
  75. checker = self.flakes("""
  76. m = None
  77. def doctest_stuff():
  78. '''
  79. >>> def function_in_doctest():
  80. ... \"\"\"
  81. ... >>> ignored_undefined_name
  82. ... \"\"\"
  83. ... df = m
  84. ... return df
  85. ...
  86. >>> function_in_doctest()
  87. '''
  88. f = m
  89. return f
  90. """)
  91. scopes = checker.deadScopes
  92. module_scopes = [
  93. scope for scope in scopes if scope.__class__ is ModuleScope]
  94. doctest_scopes = [
  95. scope for scope in scopes if scope.__class__ is DoctestScope]
  96. function_scopes = [
  97. scope for scope in scopes if scope.__class__ is FunctionScope]
  98. self.assertEqual(len(module_scopes), 1)
  99. self.assertEqual(len(doctest_scopes), 1)
  100. module_scope = module_scopes[0]
  101. doctest_scope = doctest_scopes[0]
  102. self.assertIn('m', module_scope)
  103. self.assertIn('doctest_stuff', module_scope)
  104. self.assertIn('function_in_doctest', doctest_scope)
  105. self.assertEqual(len(function_scopes), 2)
  106. self.assertIn('f', function_scopes[0])
  107. self.assertIn('df', function_scopes[1])
  108. def test_global_module_scope_pollution(self):
  109. """Check that global in doctest does not pollute module scope."""
  110. checker = self.flakes("""
  111. def doctest_stuff():
  112. '''
  113. >>> def function_in_doctest():
  114. ... global m
  115. ... m = 50
  116. ... df = 10
  117. ... m = df
  118. ...
  119. >>> function_in_doctest()
  120. '''
  121. f = 10
  122. return f
  123. """)
  124. scopes = checker.deadScopes
  125. module_scopes = [
  126. scope for scope in scopes if scope.__class__ is ModuleScope]
  127. doctest_scopes = [
  128. scope for scope in scopes if scope.__class__ is DoctestScope]
  129. function_scopes = [
  130. scope for scope in scopes if scope.__class__ is FunctionScope]
  131. self.assertEqual(len(module_scopes), 1)
  132. self.assertEqual(len(doctest_scopes), 1)
  133. module_scope = module_scopes[0]
  134. doctest_scope = doctest_scopes[0]
  135. self.assertIn('doctest_stuff', module_scope)
  136. self.assertIn('function_in_doctest', doctest_scope)
  137. self.assertEqual(len(function_scopes), 2)
  138. self.assertIn('f', function_scopes[0])
  139. self.assertIn('df', function_scopes[1])
  140. self.assertIn('m', function_scopes[1])
  141. self.assertNotIn('m', module_scope)
  142. def test_global_undefined(self):
  143. self.flakes("""
  144. global m
  145. def doctest_stuff():
  146. '''
  147. >>> m
  148. '''
  149. """, m.UndefinedName)
  150. def test_nested_class(self):
  151. """Doctest within nested class are processed."""
  152. self.flakes("""
  153. class C:
  154. class D:
  155. '''
  156. >>> m
  157. '''
  158. def doctest_stuff(self):
  159. '''
  160. >>> m
  161. '''
  162. return 1
  163. """, m.UndefinedName, m.UndefinedName)
  164. def test_ignore_nested_function(self):
  165. """Doctest module does not process doctest in nested functions."""
  166. # 'syntax error' would cause a SyntaxError if the doctest was processed.
  167. # However doctest does not find doctest in nested functions
  168. # (https://bugs.python.org/issue1650090). If nested functions were
  169. # processed, this use of m should cause UndefinedName, and the
  170. # name inner_function should probably exist in the doctest scope.
  171. self.flakes("""
  172. def doctest_stuff():
  173. def inner_function():
  174. '''
  175. >>> syntax error
  176. >>> inner_function()
  177. 1
  178. >>> m
  179. '''
  180. return 1
  181. m = inner_function()
  182. return m
  183. """)
  184. def test_inaccessible_scope_class(self):
  185. """Doctest may not access class scope."""
  186. self.flakes("""
  187. class C:
  188. def doctest_stuff(self):
  189. '''
  190. >>> m
  191. '''
  192. return 1
  193. m = 1
  194. """, m.UndefinedName)
  195. def test_importBeforeDoctest(self):
  196. self.flakes("""
  197. import foo
  198. def doctest_stuff():
  199. '''
  200. >>> foo
  201. '''
  202. """)
  203. @skip("todo")
  204. def test_importBeforeAndInDoctest(self):
  205. self.flakes('''
  206. import foo
  207. def doctest_stuff():
  208. """
  209. >>> import foo
  210. >>> foo
  211. """
  212. foo
  213. ''', m.RedefinedWhileUnused)
  214. def test_importInDoctestAndAfter(self):
  215. self.flakes('''
  216. def doctest_stuff():
  217. """
  218. >>> import foo
  219. >>> foo
  220. """
  221. import foo
  222. foo()
  223. ''')
  224. def test_offsetInDoctests(self):
  225. exc = self.flakes('''
  226. def doctest_stuff():
  227. """
  228. >>> x # line 5
  229. """
  230. ''', m.UndefinedName).messages[0]
  231. self.assertEqual(exc.lineno, 5)
  232. self.assertEqual(exc.col, 12)
  233. def test_offsetInLambdasInDoctests(self):
  234. exc = self.flakes('''
  235. def doctest_stuff():
  236. """
  237. >>> lambda: x # line 5
  238. """
  239. ''', m.UndefinedName).messages[0]
  240. self.assertEqual(exc.lineno, 5)
  241. self.assertEqual(exc.col, 20)
  242. def test_offsetAfterDoctests(self):
  243. exc = self.flakes('''
  244. def doctest_stuff():
  245. """
  246. >>> x = 5
  247. """
  248. x
  249. ''', m.UndefinedName).messages[0]
  250. self.assertEqual(exc.lineno, 8)
  251. self.assertEqual(exc.col, 0)
  252. def test_syntaxErrorInDoctest(self):
  253. exceptions = self.flakes(
  254. '''
  255. def doctest_stuff():
  256. """
  257. >>> from # line 4
  258. >>> fortytwo = 42
  259. >>> except Exception:
  260. """
  261. ''',
  262. m.DoctestSyntaxError,
  263. m.DoctestSyntaxError,
  264. m.DoctestSyntaxError).messages
  265. exc = exceptions[0]
  266. self.assertEqual(exc.lineno, 4)
  267. if not PYPY:
  268. self.assertEqual(exc.col, 18)
  269. else:
  270. self.assertEqual(exc.col, 26)
  271. # PyPy error column offset is 0,
  272. # for the second and third line of the doctest
  273. # i.e. at the beginning of the line
  274. exc = exceptions[1]
  275. self.assertEqual(exc.lineno, 5)
  276. if PYPY:
  277. self.assertEqual(exc.col, 13)
  278. else:
  279. self.assertEqual(exc.col, 16)
  280. exc = exceptions[2]
  281. self.assertEqual(exc.lineno, 6)
  282. self.assertEqual(exc.col, 13)
  283. def test_indentationErrorInDoctest(self):
  284. exc = self.flakes('''
  285. def doctest_stuff():
  286. """
  287. >>> if True:
  288. ... pass
  289. """
  290. ''', m.DoctestSyntaxError).messages[0]
  291. self.assertEqual(exc.lineno, 5)
  292. self.assertEqual(exc.col, 13)
  293. def test_offsetWithMultiLineArgs(self):
  294. (exc1, exc2) = self.flakes(
  295. '''
  296. def doctest_stuff(arg1,
  297. arg2,
  298. arg3):
  299. """
  300. >>> assert
  301. >>> this
  302. """
  303. ''',
  304. m.DoctestSyntaxError,
  305. m.UndefinedName).messages
  306. self.assertEqual(exc1.lineno, 6)
  307. self.assertEqual(exc1.col, 19)
  308. self.assertEqual(exc2.lineno, 7)
  309. self.assertEqual(exc2.col, 12)
  310. def test_doctestCanReferToFunction(self):
  311. self.flakes("""
  312. def foo():
  313. '''
  314. >>> foo
  315. '''
  316. """)
  317. def test_doctestCanReferToClass(self):
  318. self.flakes("""
  319. class Foo():
  320. '''
  321. >>> Foo
  322. '''
  323. def bar(self):
  324. '''
  325. >>> Foo
  326. '''
  327. """)
  328. def test_noOffsetSyntaxErrorInDoctest(self):
  329. exceptions = self.flakes(
  330. '''
  331. def buildurl(base, *args, **kwargs):
  332. """
  333. >>> buildurl('/blah.php', ('a', '&'), ('b', '=')
  334. '/blah.php?a=%26&b=%3D'
  335. >>> buildurl('/blah.php', a='&', 'b'='=')
  336. '/blah.php?b=%3D&a=%26'
  337. """
  338. pass
  339. ''',
  340. m.DoctestSyntaxError,
  341. m.DoctestSyntaxError).messages
  342. exc = exceptions[0]
  343. self.assertEqual(exc.lineno, 4)
  344. exc = exceptions[1]
  345. self.assertEqual(exc.lineno, 6)
  346. def test_singleUnderscoreInDoctest(self):
  347. self.flakes('''
  348. def func():
  349. """A docstring
  350. >>> func()
  351. 1
  352. >>> _
  353. 1
  354. """
  355. return 1
  356. ''')
  357. def test_globalUnderscoreInDoctest(self):
  358. self.flakes("""
  359. from gettext import ugettext as _
  360. def doctest_stuff():
  361. '''
  362. >>> pass
  363. '''
  364. """, m.UnusedImport)
  365. class TestOther(_DoctestMixin, TestOther):
  366. """Run TestOther with each test wrapped in a doctest."""
  367. class TestImports(_DoctestMixin, TestImports):
  368. """Run TestImports with each test wrapped in a doctest."""
  369. class TestUndefinedNames(_DoctestMixin, TestUndefinedNames):
  370. """Run TestUndefinedNames with each test wrapped in a doctest."""