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