~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_source.py

  • Committer: Andrew Bennetts
  • Date: 2009-09-15 03:00:23 UTC
  • mto: This revision was merged to the branch mainline in revision 4702.
  • Revision ID: andrew.bennetts@canonical.com-20090915030023-36kgbugmn9xv140m
Refactor bzr_serve more so that it is possible to use a function other than os.path.expanduser for the userdir expansion.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2008, 2009 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
19
19
They are useful for testing code quality, checking coverage metric etc.
20
20
"""
21
21
 
 
22
# import system imports here
22
23
import os
23
24
import parser
24
25
import re
26
27
import sys
27
28
import token
28
29
 
 
30
#import bzrlib specific imports here
29
31
from bzrlib import (
30
32
    osutils,
31
33
    )
38
40
 
39
41
# Files which are listed here will be skipped when testing for Copyright (or
40
42
# GPL) statements.
41
 
COPYRIGHT_EXCEPTIONS = [
42
 
    'bzrlib/_bencode_py.py',
43
 
    'bzrlib/doc_generate/conf.py',
44
 
    'bzrlib/lsprof.py',
45
 
    ]
 
43
COPYRIGHT_EXCEPTIONS = ['bzrlib/lsprof.py', 'bzrlib/_bencode_py.py',
 
44
    'bzrlib/doc_generate/sphinx_conf.py']
46
45
 
47
 
LICENSE_EXCEPTIONS = [
48
 
    'bzrlib/_bencode_py.py',
49
 
    'bzrlib/doc_generate/conf.py',
50
 
    'bzrlib/lsprof.py',
51
 
    ]
 
46
LICENSE_EXCEPTIONS = ['bzrlib/lsprof.py', 'bzrlib/_bencode_py.py',
 
47
    'bzrlib/doc_generate/sphinx_conf.py']
52
48
# Technically, 'bzrlib/lsprof.py' should be 'bzrlib/util/lsprof.py',
53
49
# (we do not check bzrlib/util/, since that is code bundled from elsewhere)
54
50
# but for compatibility with previous releases, we don't want to move it.
111
107
 
112
108
        # Avoid the case when bzrlib is packaged in a zip file
113
109
        if not os.path.isdir(source_dir):
114
 
            raise TestSkipped(
115
 
                'Cannot find bzrlib source directory. Expected %s'
116
 
                % source_dir)
 
110
            raise TestSkipped('Cannot find bzrlib source directory. Expected %s'
 
111
                              % source_dir)
117
112
        return source_dir
118
113
 
119
114
    def get_source_files(self, extensions=None):
153
148
            yield fname, text
154
149
 
155
150
    def is_our_code(self, fname):
156
 
        """True if it's a "real" part of bzrlib rather than external code"""
 
151
        """Return true if it's a "real" part of bzrlib rather than external code"""
157
152
        if '/util/' in fname or '/plugins/' in fname:
158
153
            return False
159
154
        else:
193
188
 
194
189
        copyright_re = re.compile('#\\s*copyright.*(?=\n)', re.I)
195
190
        copyright_canonical_re = re.compile(
196
 
            r'# Copyright \(C\) '  # Opening "# Copyright (C)"
197
 
            r'(\d+)(, \d+)*'       # followed by a series of dates
198
 
            r'.*Canonical Ltd')    # and containing 'Canonical Ltd'.
 
191
            r'# Copyright \(C\) ' # Opening "# Copyright (C)"
 
192
            r'(\d+)(, \d+)*' # Followed by a series of dates
 
193
            r'.*Canonical Ltd' # And containing 'Canonical Ltd'
 
194
            )
199
195
 
200
196
        for fname, text in self.get_source_file_contents(
201
197
                extensions=('.py', '.pyx')):
228
224
                        ]
229
225
            for fname, comment in incorrect:
230
226
                help_text.append(fname)
231
 
                help_text.append((' ' * 4) + comment)
 
227
                help_text.append((' '*4) + comment)
232
228
 
233
229
            self.fail('\n'.join(help_text))
234
230
 
267
263
                         " LICENSE_EXCEPTIONS in"
268
264
                         " bzrlib/tests/test_source.py",
269
265
                         "Or add the following text to the beginning:",
270
 
                         gpl_txt]
 
266
                         gpl_txt
 
267
                        ]
271
268
            for fname in incorrect:
272
 
                help_text.append((' ' * 4) + fname)
 
269
                help_text.append((' '*4) + fname)
273
270
 
274
271
            self.fail('\n'.join(help_text))
275
272
 
280
277
            dict_[fname].append(line_no)
281
278
 
282
279
    def _format_message(self, dict_, message):
283
 
        files = ["%s: %s" % (f, ', '.join([str(i + 1) for i in lines]))
 
280
        files = ["%s: %s" % (f, ', '.join([str(i+1) for i in lines]))
284
281
                for f, lines in dict_.items()]
285
282
        files.sort()
286
283
        return message + '\n\n    %s' % ('\n    '.join(files))
288
285
    def test_coding_style(self):
289
286
        """Check if bazaar code conforms to some coding style conventions.
290
287
 
291
 
        Generally we expect PEP8, but we do not generally strictly enforce
292
 
        this, and there are existing files that do not comply.  The 'pep8'
293
 
        tool, available separately, will check for more cases.
 
288
        Currently we assert that the following is not present:
 
289
         * any tab characters
 
290
         * non-unix newlines
 
291
         * no newline at end of files
294
292
 
295
 
        This test only enforces conditions that are globally true at the
296
 
        moment, and that should cause a patch to be rejected: spaces rather
297
 
        than tabs, unix newlines, and a newline at the end of the file.
 
293
        Print how many files have
 
294
         * trailing white space
 
295
         * lines longer than 79 chars
298
296
        """
299
297
        tabs = {}
 
298
        trailing_ws = {}
300
299
        illegal_newlines = {}
 
300
        long_lines = {}
301
301
        no_newline_at_eof = []
302
302
        for fname, text in self.get_source_file_contents(
303
303
                extensions=('.py', '.pyx')):
309
309
                if '\t' in line:
310
310
                    self._push_file(tabs, fname, line_no)
311
311
                if not line.endswith('\n') or line.endswith('\r\n'):
312
 
                    if line_no != last_line_no:  # not no_newline_at_eof
 
312
                    if line_no != last_line_no: # not no_newline_at_eof
313
313
                        self._push_file(illegal_newlines, fname, line_no)
 
314
                if line.endswith(' \n'):
 
315
                    self._push_file(trailing_ws, fname, line_no)
 
316
                if len(line) > 80:
 
317
                    self._push_file(long_lines, fname, line_no)
314
318
            if not lines[-1].endswith('\n'):
315
319
                no_newline_at_eof.append(fname)
316
320
        problems = []
318
322
            problems.append(self._format_message(tabs,
319
323
                'Tab characters were found in the following source files.'
320
324
                '\nThey should either be replaced by "\\t" or by spaces:'))
 
325
        if trailing_ws:
 
326
            print ("There are %i lines with trailing white space in %i files."
 
327
                % (sum([len(lines) for f, lines in trailing_ws.items()]),
 
328
                    len(trailing_ws)))
321
329
        if illegal_newlines:
322
330
            problems.append(self._format_message(illegal_newlines,
323
331
                'Non-unix newlines were found in the following source files:'))
 
332
        if long_lines:
 
333
            print ("There are %i lines longer than 79 characters in %i files."
 
334
                % (sum([len(lines) for f, lines in long_lines.items()]),
 
335
                    len(long_lines)))
324
336
        if no_newline_at_eof:
325
337
            no_newline_at_eof.sort()
326
338
            problems.append("The following source files doesn't have a "
347
359
                    return True
348
360
            return False
349
361
        badfiles = []
350
 
        assert_re = re.compile(r'\bassert\b')
351
362
        for fname, text in self.get_source_file_contents():
352
363
            if not self.is_our_code(fname):
353
364
                continue
354
 
            if not assert_re.search(text):
355
 
                continue
356
 
            ast = parser.ast2tuple(parser.suite(text))
 
365
            ast = parser.ast2tuple(parser.suite(''.join(text)))
357
366
            if search(ast):
358
367
                badfiles.append(fname)
359
368
        if badfiles:
360
369
            self.fail(
361
370
                "these files contain an assert statement and should not:\n%s"
362
371
                % '\n'.join(badfiles))
363
 
 
364
 
    def test_extension_exceptions(self):
365
 
        """Extension functions should propagate exceptions.
366
 
 
367
 
        Either they should return an object, have an 'except' clause, or
368
 
        have a "# cannot_raise" to indicate that we've audited them and
369
 
        defined them as not raising exceptions.
370
 
        """
371
 
        both_exc_and_no_exc = []
372
 
        missing_except = []
373
 
        class_re = re.compile(r'^(cdef\s+)?(public\s+)?'
374
 
                              r'(api\s+)?class (\w+).*:', re.MULTILINE)
375
 
        extern_class_re = re.compile(r'## extern cdef class (\w+)',
376
 
                                     re.MULTILINE)
377
 
        except_re = re.compile(
378
 
            r'cdef\s+'        # start with cdef
379
 
            r'([\w *]*?)\s*'  # this is the return signature
380
 
            r'(\w+)\s*\('     # the function name
381
 
            r'[^)]*\)\s*'     # parameters
382
 
            r'(.*)\s*:'       # the except clause
383
 
            r'\s*(#\s*cannot[- _]raise)?')  # cannot raise comment
384
 
        for fname, text in self.get_source_file_contents(
385
 
                extensions=('.pyx',)):
386
 
            known_classes = set([m[-1] for m in class_re.findall(text)])
387
 
            known_classes.update(extern_class_re.findall(text))
388
 
            cdefs = except_re.findall(text)
389
 
            for sig, func, exc_clause, no_exc_comment in cdefs:
390
 
                if sig.startswith('api '):
391
 
                    sig = sig[4:]
392
 
                if not sig or sig in known_classes:
393
 
                    sig = 'object'
394
 
                if 'nogil' in exc_clause:
395
 
                    exc_clause = exc_clause.replace('nogil', '').strip()
396
 
                if exc_clause and no_exc_comment:
397
 
                    both_exc_and_no_exc.append((fname, func))
398
 
                if sig != 'object' and not (exc_clause or no_exc_comment):
399
 
                    missing_except.append((fname, func))
400
 
        error_msg = []
401
 
        if both_exc_and_no_exc:
402
 
            error_msg.append(
403
 
                'The following functions had "cannot raise" comments'
404
 
                ' but did have an except clause set:')
405
 
            for fname, func in both_exc_and_no_exc:
406
 
                error_msg.append('%s:%s' % (fname, func))
407
 
            error_msg.extend(('', ''))
408
 
        if missing_except:
409
 
            error_msg.append(
410
 
                'The following functions have fixed return types,'
411
 
                ' but no except clause.')
412
 
            error_msg.append(
413
 
                'Either add an except or append "# cannot_raise".')
414
 
            for fname, func in missing_except:
415
 
                error_msg.append('%s:%s' % (fname, func))
416
 
            error_msg.extend(('', ''))
417
 
        if error_msg:
418
 
            self.fail('\n'.join(error_msg))