]> crepu.dev Git - config.git/blob - djavu-asus/elpy/rpc-venv/lib/python3.11/site-packages/pyflakes/test/test_api.py
ActualizaciĆ³n de Readme
[config.git] / djavu-asus / elpy / rpc-venv / lib / python3.11 / site-packages / pyflakes / test / test_api.py
1 """
2 Tests for L{pyflakes.scripts.pyflakes}.
3 """
4
5 import contextlib
6 import io
7 import os
8 import sys
9 import shutil
10 import subprocess
11 import tempfile
12
13 from pyflakes.checker import PYPY
14 from pyflakes.messages import UnusedImport
15 from pyflakes.reporter import Reporter
16 from pyflakes.api import (
17 main,
18 check,
19 checkPath,
20 checkRecursive,
21 iterSourceCode,
22 )
23 from pyflakes.test.harness import TestCase, skipIf
24
25
26 def withStderrTo(stderr, f, *args, **kwargs):
27 """
28 Call C{f} with C{sys.stderr} redirected to C{stderr}.
29 """
30 (outer, sys.stderr) = (sys.stderr, stderr)
31 try:
32 return f(*args, **kwargs)
33 finally:
34 sys.stderr = outer
35
36
37 class Node:
38 """
39 Mock an AST node.
40 """
41 def __init__(self, lineno, col_offset=0):
42 self.lineno = lineno
43 self.col_offset = col_offset
44
45
46 class SysStreamCapturing:
47 """Context manager capturing sys.stdin, sys.stdout and sys.stderr.
48
49 The file handles are replaced with a StringIO object.
50 """
51
52 def __init__(self, stdin):
53 self._stdin = io.StringIO(stdin or '', newline=os.linesep)
54
55 def __enter__(self):
56 self._orig_stdin = sys.stdin
57 self._orig_stdout = sys.stdout
58 self._orig_stderr = sys.stderr
59
60 sys.stdin = self._stdin
61 sys.stdout = self._stdout_stringio = io.StringIO(newline=os.linesep)
62 sys.stderr = self._stderr_stringio = io.StringIO(newline=os.linesep)
63
64 return self
65
66 def __exit__(self, *args):
67 self.output = self._stdout_stringio.getvalue()
68 self.error = self._stderr_stringio.getvalue()
69
70 sys.stdin = self._orig_stdin
71 sys.stdout = self._orig_stdout
72 sys.stderr = self._orig_stderr
73
74
75 class LoggingReporter:
76 """
77 Implementation of Reporter that just appends any error to a list.
78 """
79
80 def __init__(self, log):
81 """
82 Construct a C{LoggingReporter}.
83
84 @param log: A list to append log messages to.
85 """
86 self.log = log
87
88 def flake(self, message):
89 self.log.append(('flake', str(message)))
90
91 def unexpectedError(self, filename, message):
92 self.log.append(('unexpectedError', filename, message))
93
94 def syntaxError(self, filename, msg, lineno, offset, line):
95 self.log.append(('syntaxError', filename, msg, lineno, offset, line))
96
97
98 class TestIterSourceCode(TestCase):
99 """
100 Tests for L{iterSourceCode}.
101 """
102
103 def setUp(self):
104 self.tempdir = tempfile.mkdtemp()
105
106 def tearDown(self):
107 shutil.rmtree(self.tempdir)
108
109 def makeEmptyFile(self, *parts):
110 assert parts
111 fpath = os.path.join(self.tempdir, *parts)
112 open(fpath, 'a').close()
113 return fpath
114
115 def test_emptyDirectory(self):
116 """
117 There are no Python files in an empty directory.
118 """
119 self.assertEqual(list(iterSourceCode([self.tempdir])), [])
120
121 def test_singleFile(self):
122 """
123 If the directory contains one Python file, C{iterSourceCode} will find
124 it.
125 """
126 childpath = self.makeEmptyFile('foo.py')
127 self.assertEqual(list(iterSourceCode([self.tempdir])), [childpath])
128
129 def test_onlyPythonSource(self):
130 """
131 Files that are not Python source files are not included.
132 """
133 self.makeEmptyFile('foo.pyc')
134 self.assertEqual(list(iterSourceCode([self.tempdir])), [])
135
136 def test_recurses(self):
137 """
138 If the Python files are hidden deep down in child directories, we will
139 find them.
140 """
141 os.mkdir(os.path.join(self.tempdir, 'foo'))
142 apath = self.makeEmptyFile('foo', 'a.py')
143 self.makeEmptyFile('foo', 'a.py~')
144 os.mkdir(os.path.join(self.tempdir, 'bar'))
145 bpath = self.makeEmptyFile('bar', 'b.py')
146 cpath = self.makeEmptyFile('c.py')
147 self.assertEqual(
148 sorted(iterSourceCode([self.tempdir])),
149 sorted([apath, bpath, cpath]))
150
151 def test_shebang(self):
152 """
153 Find Python files that don't end with `.py`, but contain a Python
154 shebang.
155 """
156 python = os.path.join(self.tempdir, 'a')
157 with open(python, 'w') as fd:
158 fd.write('#!/usr/bin/env python\n')
159
160 self.makeEmptyFile('b')
161
162 with open(os.path.join(self.tempdir, 'c'), 'w') as fd:
163 fd.write('hello\nworld\n')
164
165 python3 = os.path.join(self.tempdir, 'e')
166 with open(python3, 'w') as fd:
167 fd.write('#!/usr/bin/env python3\n')
168
169 pythonw = os.path.join(self.tempdir, 'f')
170 with open(pythonw, 'w') as fd:
171 fd.write('#!/usr/bin/env pythonw\n')
172
173 python3args = os.path.join(self.tempdir, 'g')
174 with open(python3args, 'w') as fd:
175 fd.write('#!/usr/bin/python3 -u\n')
176
177 python3d = os.path.join(self.tempdir, 'i')
178 with open(python3d, 'w') as fd:
179 fd.write('#!/usr/local/bin/python3d\n')
180
181 python38m = os.path.join(self.tempdir, 'j')
182 with open(python38m, 'w') as fd:
183 fd.write('#! /usr/bin/env python3.8m\n')
184
185 # Should NOT be treated as Python source
186 notfirst = os.path.join(self.tempdir, 'l')
187 with open(notfirst, 'w') as fd:
188 fd.write('#!/bin/sh\n#!/usr/bin/python\n')
189
190 self.assertEqual(
191 sorted(iterSourceCode([self.tempdir])),
192 sorted([
193 python, python3, pythonw, python3args, python3d,
194 python38m,
195 ]))
196
197 def test_multipleDirectories(self):
198 """
199 L{iterSourceCode} can be given multiple directories. It will recurse
200 into each of them.
201 """
202 foopath = os.path.join(self.tempdir, 'foo')
203 barpath = os.path.join(self.tempdir, 'bar')
204 os.mkdir(foopath)
205 apath = self.makeEmptyFile('foo', 'a.py')
206 os.mkdir(barpath)
207 bpath = self.makeEmptyFile('bar', 'b.py')
208 self.assertEqual(
209 sorted(iterSourceCode([foopath, barpath])),
210 sorted([apath, bpath]))
211
212 def test_explicitFiles(self):
213 """
214 If one of the paths given to L{iterSourceCode} is not a directory but
215 a file, it will include that in its output.
216 """
217 epath = self.makeEmptyFile('e.py')
218 self.assertEqual(list(iterSourceCode([epath])),
219 [epath])
220
221
222 class TestReporter(TestCase):
223 """
224 Tests for L{Reporter}.
225 """
226
227 def test_syntaxError(self):
228 """
229 C{syntaxError} reports that there was a syntax error in the source
230 file. It reports to the error stream and includes the filename, line
231 number, error message, actual line of source and a caret pointing to
232 where the error is.
233 """
234 err = io.StringIO()
235 reporter = Reporter(None, err)
236 reporter.syntaxError('foo.py', 'a problem', 3, 8, 'bad line of source')
237 self.assertEqual(
238 ("foo.py:3:8: a problem\n"
239 "bad line of source\n"
240 " ^\n"),
241 err.getvalue())
242
243 def test_syntaxErrorNoOffset(self):
244 """
245 C{syntaxError} doesn't include a caret pointing to the error if
246 C{offset} is passed as C{None}.
247 """
248 err = io.StringIO()
249 reporter = Reporter(None, err)
250 reporter.syntaxError('foo.py', 'a problem', 3, None,
251 'bad line of source')
252 self.assertEqual(
253 ("foo.py:3: a problem\n"
254 "bad line of source\n"),
255 err.getvalue())
256
257 def test_syntaxErrorNoText(self):
258 """
259 C{syntaxError} doesn't include text or nonsensical offsets if C{text} is C{None}.
260
261 This typically happens when reporting syntax errors from stdin.
262 """
263 err = io.StringIO()
264 reporter = Reporter(None, err)
265 reporter.syntaxError('<stdin>', 'a problem', 0, 0, None)
266 self.assertEqual(("<stdin>:1:1: a problem\n"), err.getvalue())
267
268 def test_multiLineSyntaxError(self):
269 """
270 If there's a multi-line syntax error, then we only report the last
271 line. The offset is adjusted so that it is relative to the start of
272 the last line.
273 """
274 err = io.StringIO()
275 lines = [
276 'bad line of source',
277 'more bad lines of source',
278 ]
279 reporter = Reporter(None, err)
280 reporter.syntaxError('foo.py', 'a problem', 3, len(lines[0]) + 7,
281 '\n'.join(lines))
282 self.assertEqual(
283 ("foo.py:3:25: a problem\n" +
284 lines[-1] + "\n" +
285 " " * 24 + "^\n"),
286 err.getvalue())
287
288 def test_unexpectedError(self):
289 """
290 C{unexpectedError} reports an error processing a source file.
291 """
292 err = io.StringIO()
293 reporter = Reporter(None, err)
294 reporter.unexpectedError('source.py', 'error message')
295 self.assertEqual('source.py: error message\n', err.getvalue())
296
297 def test_flake(self):
298 """
299 C{flake} reports a code warning from Pyflakes. It is exactly the
300 str() of a L{pyflakes.messages.Message}.
301 """
302 out = io.StringIO()
303 reporter = Reporter(out, None)
304 message = UnusedImport('foo.py', Node(42), 'bar')
305 reporter.flake(message)
306 self.assertEqual(out.getvalue(), f"{message}\n")
307
308
309 class CheckTests(TestCase):
310 """
311 Tests for L{check} and L{checkPath} which check a file for flakes.
312 """
313
314 @contextlib.contextmanager
315 def makeTempFile(self, content):
316 """
317 Make a temporary file containing C{content} and return a path to it.
318 """
319 fd, name = tempfile.mkstemp()
320 try:
321 with os.fdopen(fd, 'wb') as f:
322 if not hasattr(content, 'decode'):
323 content = content.encode('ascii')
324 f.write(content)
325 yield name
326 finally:
327 os.remove(name)
328
329 def assertHasErrors(self, path, errorList):
330 """
331 Assert that C{path} causes errors.
332
333 @param path: A path to a file to check.
334 @param errorList: A list of errors expected to be printed to stderr.
335 """
336 err = io.StringIO()
337 count = withStderrTo(err, checkPath, path)
338 self.assertEqual(
339 (count, err.getvalue()), (len(errorList), ''.join(errorList)))
340
341 def getErrors(self, path):
342 """
343 Get any warnings or errors reported by pyflakes for the file at C{path}.
344
345 @param path: The path to a Python file on disk that pyflakes will check.
346 @return: C{(count, log)}, where C{count} is the number of warnings or
347 errors generated, and log is a list of those warnings, presented
348 as structured data. See L{LoggingReporter} for more details.
349 """
350 log = []
351 reporter = LoggingReporter(log)
352 count = checkPath(path, reporter)
353 return count, log
354
355 def test_legacyScript(self):
356 from pyflakes.scripts import pyflakes as script_pyflakes
357 self.assertIs(script_pyflakes.checkPath, checkPath)
358
359 def test_missingTrailingNewline(self):
360 """
361 Source which doesn't end with a newline shouldn't cause any
362 exception to be raised nor an error indicator to be returned by
363 L{check}.
364 """
365 with self.makeTempFile("def foo():\n\tpass\n\t") as fName:
366 self.assertHasErrors(fName, [])
367
368 def test_checkPathNonExisting(self):
369 """
370 L{checkPath} handles non-existing files.
371 """
372 count, errors = self.getErrors('extremo')
373 self.assertEqual(count, 1)
374 self.assertEqual(
375 errors,
376 [('unexpectedError', 'extremo', 'No such file or directory')])
377
378 def test_multilineSyntaxError(self):
379 """
380 Source which includes a syntax error which results in the raised
381 L{SyntaxError.text} containing multiple lines of source are reported
382 with only the last line of that source.
383 """
384 source = """\
385 def foo():
386 '''
387
388 def bar():
389 pass
390
391 def baz():
392 '''quux'''
393 """
394
395 # Sanity check - SyntaxError.text should be multiple lines, if it
396 # isn't, something this test was unprepared for has happened.
397 def evaluate(source):
398 exec(source)
399 try:
400 evaluate(source)
401 except SyntaxError as e:
402 if not PYPY and sys.version_info < (3, 10):
403 self.assertTrue(e.text.count('\n') > 1)
404 else:
405 self.fail()
406
407 with self.makeTempFile(source) as sourcePath:
408 if PYPY:
409 message = 'end of file (EOF) while scanning triple-quoted string literal'
410 elif sys.version_info >= (3, 10):
411 message = 'unterminated triple-quoted string literal (detected at line 8)' # noqa: E501
412 else:
413 message = 'invalid syntax'
414
415 if PYPY or sys.version_info >= (3, 10):
416 column = 12
417 else:
418 column = 8
419 self.assertHasErrors(
420 sourcePath,
421 ["""\
422 %s:8:%d: %s
423 '''quux'''
424 %s^
425 """ % (sourcePath, column, message, ' ' * (column - 1))])
426
427 def test_eofSyntaxError(self):
428 """
429 The error reported for source files which end prematurely causing a
430 syntax error reflects the cause for the syntax error.
431 """
432 with self.makeTempFile("def foo(") as sourcePath:
433 if PYPY:
434 msg = 'parenthesis is never closed'
435 elif sys.version_info >= (3, 10):
436 msg = "'(' was never closed"
437 else:
438 msg = 'unexpected EOF while parsing'
439
440 if PYPY or sys.version_info >= (3, 10):
441 column = 8
442 else:
443 column = 9
444
445 spaces = ' ' * (column - 1)
446 expected = '{}:1:{}: {}\ndef foo(\n{}^\n'.format(
447 sourcePath, column, msg, spaces
448 )
449
450 self.assertHasErrors(sourcePath, [expected])
451
452 def test_eofSyntaxErrorWithTab(self):
453 """
454 The error reported for source files which end prematurely causing a
455 syntax error reflects the cause for the syntax error.
456 """
457 with self.makeTempFile("if True:\n\tfoo =") as sourcePath:
458 self.assertHasErrors(
459 sourcePath,
460 [f"""\
461 {sourcePath}:2:7: invalid syntax
462 \tfoo =
463 \t ^
464 """])
465
466 def test_nonDefaultFollowsDefaultSyntaxError(self):
467 """
468 Source which has a non-default argument following a default argument
469 should include the line number of the syntax error. However these
470 exceptions do not include an offset.
471 """
472 source = """\
473 def foo(bar=baz, bax):
474 pass
475 """
476 with self.makeTempFile(source) as sourcePath:
477 if sys.version_info >= (3, 12):
478 msg = 'parameter without a default follows parameter with a default' # noqa: E501
479 else:
480 msg = 'non-default argument follows default argument'
481
482 if PYPY and sys.version_info >= (3, 9):
483 column = 18
484 elif PYPY:
485 column = 8
486 elif sys.version_info >= (3, 10):
487 column = 18
488 elif sys.version_info >= (3, 9):
489 column = 21
490 else:
491 column = 9
492 last_line = ' ' * (column - 1) + '^\n'
493 self.assertHasErrors(
494 sourcePath,
495 [f"""\
496 {sourcePath}:1:{column}: {msg}
497 def foo(bar=baz, bax):
498 {last_line}"""]
499 )
500
501 def test_nonKeywordAfterKeywordSyntaxError(self):
502 """
503 Source which has a non-keyword argument after a keyword argument should
504 include the line number of the syntax error. However these exceptions
505 do not include an offset.
506 """
507 source = """\
508 foo(bar=baz, bax)
509 """
510 with self.makeTempFile(source) as sourcePath:
511 if sys.version_info >= (3, 9):
512 column = 17
513 elif not PYPY:
514 column = 14
515 else:
516 column = 13
517 last_line = ' ' * (column - 1) + '^\n'
518 columnstr = '%d:' % column
519
520 message = 'positional argument follows keyword argument'
521
522 self.assertHasErrors(
523 sourcePath,
524 ["""\
525 {}:1:{} {}
526 foo(bar=baz, bax)
527 {}""".format(sourcePath, columnstr, message, last_line)])
528
529 def test_invalidEscape(self):
530 """
531 The invalid escape syntax raises ValueError in Python 2
532 """
533 # ValueError: invalid \x escape
534 with self.makeTempFile(r"foo = '\xyz'") as sourcePath:
535 position_end = 1
536 if PYPY and sys.version_info >= (3, 9):
537 column = 7
538 elif PYPY:
539 column = 6
540 elif (3, 9) <= sys.version_info < (3, 12):
541 column = 13
542 else:
543 column = 7
544
545 last_line = '%s^\n' % (' ' * (column - 1))
546
547 decoding_error = """\
548 %s:1:%d: (unicode error) 'unicodeescape' codec can't decode bytes \
549 in position 0-%d: truncated \\xXX escape
550 foo = '\\xyz'
551 %s""" % (sourcePath, column, position_end, last_line)
552
553 self.assertHasErrors(
554 sourcePath, [decoding_error])
555
556 @skipIf(sys.platform == 'win32', 'unsupported on Windows')
557 def test_permissionDenied(self):
558 """
559 If the source file is not readable, this is reported on standard
560 error.
561 """
562 if os.getuid() == 0:
563 self.skipTest('root user can access all files regardless of '
564 'permissions')
565 with self.makeTempFile('') as sourcePath:
566 os.chmod(sourcePath, 0)
567 count, errors = self.getErrors(sourcePath)
568 self.assertEqual(count, 1)
569 self.assertEqual(
570 errors,
571 [('unexpectedError', sourcePath, "Permission denied")])
572
573 def test_pyflakesWarning(self):
574 """
575 If the source file has a pyflakes warning, this is reported as a
576 'flake'.
577 """
578 with self.makeTempFile("import foo") as sourcePath:
579 count, errors = self.getErrors(sourcePath)
580 self.assertEqual(count, 1)
581 self.assertEqual(
582 errors, [('flake', str(UnusedImport(sourcePath, Node(1), 'foo')))])
583
584 def test_encodedFileUTF8(self):
585 """
586 If source file declares the correct encoding, no error is reported.
587 """
588 SNOWMAN = chr(0x2603)
589 source = ("""\
590 # coding: utf-8
591 x = "%s"
592 """ % SNOWMAN).encode('utf-8')
593 with self.makeTempFile(source) as sourcePath:
594 self.assertHasErrors(sourcePath, [])
595
596 def test_CRLFLineEndings(self):
597 """
598 Source files with Windows CR LF line endings are parsed successfully.
599 """
600 with self.makeTempFile("x = 42\r\n") as sourcePath:
601 self.assertHasErrors(sourcePath, [])
602
603 def test_misencodedFileUTF8(self):
604 """
605 If a source file contains bytes which cannot be decoded, this is
606 reported on stderr.
607 """
608 SNOWMAN = chr(0x2603)
609 source = ("""\
610 # coding: ascii
611 x = "%s"
612 """ % SNOWMAN).encode('utf-8')
613 with self.makeTempFile(source) as sourcePath:
614 self.assertHasErrors(
615 sourcePath,
616 [f"{sourcePath}:1:1: 'ascii' codec can't decode byte 0xe2 in position 21: ordinal not in range(128)\n"]) # noqa: E501
617
618 def test_misencodedFileUTF16(self):
619 """
620 If a source file contains bytes which cannot be decoded, this is
621 reported on stderr.
622 """
623 SNOWMAN = chr(0x2603)
624 source = ("""\
625 # coding: ascii
626 x = "%s"
627 """ % SNOWMAN).encode('utf-16')
628 with self.makeTempFile(source) as sourcePath:
629 if sys.version_info < (3, 11, 4):
630 expected = f"{sourcePath}: problem decoding source\n"
631 else:
632 expected = f"{sourcePath}:1: source code string cannot contain null bytes\n" # noqa: E501
633
634 self.assertHasErrors(sourcePath, [expected])
635
636 def test_checkRecursive(self):
637 """
638 L{checkRecursive} descends into each directory, finding Python files
639 and reporting problems.
640 """
641 tempdir = tempfile.mkdtemp()
642 try:
643 os.mkdir(os.path.join(tempdir, 'foo'))
644 file1 = os.path.join(tempdir, 'foo', 'bar.py')
645 with open(file1, 'wb') as fd:
646 fd.write(b"import baz\n")
647 file2 = os.path.join(tempdir, 'baz.py')
648 with open(file2, 'wb') as fd:
649 fd.write(b"import contraband")
650 log = []
651 reporter = LoggingReporter(log)
652 warnings = checkRecursive([tempdir], reporter)
653 self.assertEqual(warnings, 2)
654 self.assertEqual(
655 sorted(log),
656 sorted([('flake', str(UnusedImport(file1, Node(1), 'baz'))),
657 ('flake',
658 str(UnusedImport(file2, Node(1), 'contraband')))]))
659 finally:
660 shutil.rmtree(tempdir)
661
662 def test_stdinReportsErrors(self):
663 """
664 L{check} reports syntax errors from stdin
665 """
666 source = "max(1 for i in range(10), key=lambda x: x+1)\n"
667 err = io.StringIO()
668 count = withStderrTo(err, check, source, "<stdin>")
669 self.assertEqual(count, 1)
670 errlines = err.getvalue().split("\n")[:-1]
671
672 if sys.version_info >= (3, 9):
673 expected_error = [
674 "<stdin>:1:5: Generator expression must be parenthesized",
675 "max(1 for i in range(10), key=lambda x: x+1)",
676 " ^",
677 ]
678 elif PYPY:
679 expected_error = [
680 "<stdin>:1:4: Generator expression must be parenthesized if not sole argument", # noqa: E501
681 "max(1 for i in range(10), key=lambda x: x+1)",
682 " ^",
683 ]
684 else:
685 expected_error = [
686 "<stdin>:1:5: Generator expression must be parenthesized",
687 ]
688
689 self.assertEqual(errlines, expected_error)
690
691
692 class IntegrationTests(TestCase):
693 """
694 Tests of the pyflakes script that actually spawn the script.
695 """
696 def setUp(self):
697 self.tempdir = tempfile.mkdtemp()
698 self.tempfilepath = os.path.join(self.tempdir, 'temp')
699
700 def tearDown(self):
701 shutil.rmtree(self.tempdir)
702
703 def getPyflakesBinary(self):
704 """
705 Return the path to the pyflakes binary.
706 """
707 import pyflakes
708 package_dir = os.path.dirname(pyflakes.__file__)
709 return os.path.join(package_dir, '..', 'bin', 'pyflakes')
710
711 def runPyflakes(self, paths, stdin=None):
712 """
713 Launch a subprocess running C{pyflakes}.
714
715 @param paths: Command-line arguments to pass to pyflakes.
716 @param stdin: Text to use as stdin.
717 @return: C{(returncode, stdout, stderr)} of the completed pyflakes
718 process.
719 """
720 env = dict(os.environ)
721 env['PYTHONPATH'] = os.pathsep.join(sys.path)
722 command = [sys.executable, self.getPyflakesBinary()]
723 command.extend(paths)
724 if stdin:
725 p = subprocess.Popen(command, env=env, stdin=subprocess.PIPE,
726 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
727 (stdout, stderr) = p.communicate(stdin.encode('ascii'))
728 else:
729 p = subprocess.Popen(command, env=env,
730 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
731 (stdout, stderr) = p.communicate()
732 rv = p.wait()
733 stdout = stdout.decode('utf-8')
734 stderr = stderr.decode('utf-8')
735 return (stdout, stderr, rv)
736
737 def test_goodFile(self):
738 """
739 When a Python source file is all good, the return code is zero and no
740 messages are printed to either stdout or stderr.
741 """
742 open(self.tempfilepath, 'a').close()
743 d = self.runPyflakes([self.tempfilepath])
744 self.assertEqual(d, ('', '', 0))
745
746 def test_fileWithFlakes(self):
747 """
748 When a Python source file has warnings, the return code is non-zero
749 and the warnings are printed to stdout.
750 """
751 with open(self.tempfilepath, 'wb') as fd:
752 fd.write(b"import contraband\n")
753 d = self.runPyflakes([self.tempfilepath])
754 expected = UnusedImport(self.tempfilepath, Node(1), 'contraband')
755 self.assertEqual(d, (f"{expected}{os.linesep}", '', 1))
756
757 def test_errors_io(self):
758 """
759 When pyflakes finds errors with the files it's given, (if they don't
760 exist, say), then the return code is non-zero and the errors are
761 printed to stderr.
762 """
763 d = self.runPyflakes([self.tempfilepath])
764 error_msg = '{}: No such file or directory{}'.format(self.tempfilepath,
765 os.linesep)
766 self.assertEqual(d, ('', error_msg, 1))
767
768 def test_errors_syntax(self):
769 """
770 When pyflakes finds errors with the files it's given, (if they don't
771 exist, say), then the return code is non-zero and the errors are
772 printed to stderr.
773 """
774 with open(self.tempfilepath, 'wb') as fd:
775 fd.write(b"import")
776 d = self.runPyflakes([self.tempfilepath])
777 error_msg = '{0}:1:7: invalid syntax{1}import{1} ^{1}'.format(
778 self.tempfilepath, os.linesep)
779 self.assertEqual(d, ('', error_msg, 1))
780
781 def test_readFromStdin(self):
782 """
783 If no arguments are passed to C{pyflakes} then it reads from stdin.
784 """
785 d = self.runPyflakes([], stdin='import contraband')
786 expected = UnusedImport('<stdin>', Node(1), 'contraband')
787 self.assertEqual(d, (f"{expected}{os.linesep}", '', 1))
788
789
790 class TestMain(IntegrationTests):
791 """
792 Tests of the pyflakes main function.
793 """
794 def runPyflakes(self, paths, stdin=None):
795 try:
796 with SysStreamCapturing(stdin) as capture:
797 main(args=paths)
798 except SystemExit as e:
799 self.assertIsInstance(e.code, bool)
800 rv = int(e.code)
801 return (capture.output, capture.error, rv)
802 else:
803 raise RuntimeError('SystemExit not raised')