test_doctests.py 13 KB

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