~ubuntu-branches/ubuntu/saucy/pyflakes/saucy

« back to all changes in this revision

Viewing changes to .pc/rev104.patch/pyflakes/test/test_api.py

  • Committer: Package Import Robot
  • Author(s): Dmitrijs Ledkovs
  • Date: 2013-08-04 23:49:01 UTC
  • mfrom: (1.1.9) (3.3.2 sid)
  • Revision ID: package-import@ubuntu.com-20130804234901-0sf9rh5q216p1zfo
Tags: 0.7.3-1
* Switch to dh-python.
* New upstream release (Closes: #718728)
  - "Fix undefined name for generator expression and dict/set
  comprehension at class level"
  - drop all patches

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