~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_source.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-05-28 00:25:32 UTC
  • mfrom: (5264.1.2 command-help-bug-177500)
  • Revision ID: pqm@pqm.ubuntu.com-20100528002532-9bzj1fajyxckd1rg
(lifeless) Stop raising at runtime when a command has no help,
 instead have a test in the test suite that checks all known command objects.
 (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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 "
364
376
    def test_extension_exceptions(self):
365
377
        """Extension functions should propagate exceptions.
366
378
 
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.
 
379
        Either they should return an object, have an 'except' clause, or have a
 
380
        "# cannot_raise" to indicate that we've audited them and defined them as not
 
381
        raising exceptions.
370
382
        """
371
383
        both_exc_and_no_exc = []
372
384
        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
 
385
        class_re = re.compile(r'^(cdef\s+)?(public\s+)?(api\s+)?class (\w+).*:',
 
386
                              re.MULTILINE)
 
387
        except_re = re.compile(r'cdef\s+' # start with cdef
 
388
                               r'([\w *]*?)\s*' # this is the return signature
 
389
                               r'(\w+)\s*\(' # the function name
 
390
                               r'[^)]*\)\s*' # parameters
 
391
                               r'(.*)\s*:' # the except clause
 
392
                               r'\s*(#\s*cannot[- _]raise)?' # cannot raise comment
 
393
                              )
384
394
        for fname, text in self.get_source_file_contents(
385
395
                extensions=('.pyx',)):
386
396
            known_classes = set([m[-1] for m in class_re.findall(text)])
387
 
            known_classes.update(extern_class_re.findall(text))
388
397
            cdefs = except_re.findall(text)
389
398
            for sig, func, exc_clause, no_exc_comment in cdefs:
390
399
                if sig.startswith('api '):
399
408
                    missing_except.append((fname, func))
400
409
        error_msg = []
401
410
        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:')
 
411
            error_msg.append('The following functions had "cannot raise" comments'
 
412
                             ' but did have an except clause set:')
405
413
            for fname, func in both_exc_and_no_exc:
406
414
                error_msg.append('%s:%s' % (fname, func))
407
415
            error_msg.extend(('', ''))
408
416
        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".')
 
417
            error_msg.append('The following functions have fixed return types,'
 
418
                             ' but no except clause.')
 
419
            error_msg.append('Either add an except or append "# cannot_raise".')
414
420
            for fname, func in missing_except:
415
421
                error_msg.append('%s:%s' % (fname, func))
416
422
            error_msg.extend(('', ''))