~anitanayak/charms/trusty/ibm-mq/devel

« back to all changes in this revision

Viewing changes to .tox/py35/lib/python3.5/site-packages/pyflakes/test/test_api.py

  • Committer: Anita Nayak
  • Date: 2016-10-24 07:11:28 UTC
  • Revision ID: anitanayak@in.ibm.com-20161024071128-oufsbvyx8x344p2j
checking in after fixing lint errors

Show diffs side-by-side

added added

removed removed

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