~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_source.py

  • Committer: Andrew Bennetts
  • Date: 2010-10-13 00:26:41 UTC
  • mto: This revision was merged to the branch mainline in revision 5498.
  • Revision ID: andrew.bennetts@canonical.com-20101013002641-9tlh9k89mlj1666m
Keep docs-plain working.

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
    )
111
113
 
112
114
        # Avoid the case when bzrlib is packaged in a zip file
113
115
        if not os.path.isdir(source_dir):
114
 
            raise TestSkipped(
115
 
                'Cannot find bzrlib source directory. Expected %s'
116
 
                % source_dir)
 
116
            raise TestSkipped('Cannot find bzrlib source directory. Expected %s'
 
117
                              % source_dir)
117
118
        return source_dir
118
119
 
119
120
    def get_source_files(self, extensions=None):
153
154
            yield fname, text
154
155
 
155
156
    def is_our_code(self, fname):
156
 
        """True if it's a "real" part of bzrlib rather than external code"""
 
157
        """Return true if it's a "real" part of bzrlib rather than external code"""
157
158
        if '/util/' in fname or '/plugins/' in fname:
158
159
            return False
159
160
        else:
193
194
 
194
195
        copyright_re = re.compile('#\\s*copyright.*(?=\n)', re.I)
195
196
        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'.
 
197
            r'# Copyright \(C\) ' # Opening "# Copyright (C)"
 
198
            r'(\d+)(, \d+)*' # Followed by a series of dates
 
199
            r'.*Canonical Ltd' # And containing 'Canonical Ltd'
 
200
            )
199
201
 
200
202
        for fname, text in self.get_source_file_contents(
201
203
                extensions=('.py', '.pyx')):
228
230
                        ]
229
231
            for fname, comment in incorrect:
230
232
                help_text.append(fname)
231
 
                help_text.append((' ' * 4) + comment)
 
233
                help_text.append((' '*4) + comment)
232
234
 
233
235
            self.fail('\n'.join(help_text))
234
236
 
267
269
                         " LICENSE_EXCEPTIONS in"
268
270
                         " bzrlib/tests/test_source.py",
269
271
                         "Or add the following text to the beginning:",
270
 
                         gpl_txt]
 
272
                         gpl_txt
 
273
                        ]
271
274
            for fname in incorrect:
272
 
                help_text.append((' ' * 4) + fname)
 
275
                help_text.append((' '*4) + fname)
273
276
 
274
277
            self.fail('\n'.join(help_text))
275
278
 
280
283
            dict_[fname].append(line_no)
281
284
 
282
285
    def _format_message(self, dict_, message):
283
 
        files = ["%s: %s" % (f, ', '.join([str(i + 1) for i in lines]))
 
286
        files = ["%s: %s" % (f, ', '.join([str(i+1) for i in lines]))
284
287
                for f, lines in dict_.items()]
285
288
        files.sort()
286
289
        return message + '\n\n    %s' % ('\n    '.join(files))
288
291
    def test_coding_style(self):
289
292
        """Check if bazaar code conforms to some coding style conventions.
290
293
 
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.
 
294
        Currently we assert that the following is not present:
 
295
         * any tab characters
 
296
         * non-unix newlines
 
297
         * no newline at end of files
294
298
 
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.
 
299
        Print how many files have
 
300
         * trailing white space
 
301
         * lines longer than 79 chars
298
302
        """
299
303
        tabs = {}
 
304
        trailing_ws = {}
300
305
        illegal_newlines = {}
 
306
        long_lines = {}
301
307
        no_newline_at_eof = []
302
308
        for fname, text in self.get_source_file_contents(
303
309
                extensions=('.py', '.pyx')):
309
315
                if '\t' in line:
310
316
                    self._push_file(tabs, fname, line_no)
311
317
                if not line.endswith('\n') or line.endswith('\r\n'):
312
 
                    if line_no != last_line_no:  # not no_newline_at_eof
 
318
                    if line_no != last_line_no: # not no_newline_at_eof
313
319
                        self._push_file(illegal_newlines, fname, line_no)
 
320
                if line.endswith(' \n'):
 
321
                    self._push_file(trailing_ws, fname, line_no)
 
322
                if len(line) > 80:
 
323
                    self._push_file(long_lines, fname, line_no)
314
324
            if not lines[-1].endswith('\n'):
315
325
                no_newline_at_eof.append(fname)
316
326
        problems = []
318
328
            problems.append(self._format_message(tabs,
319
329
                'Tab characters were found in the following source files.'
320
330
                '\nThey should either be replaced by "\\t" or by spaces:'))
 
331
        if trailing_ws:
 
332
            print ("There are %i lines with trailing white space in %i files."
 
333
                % (sum([len(lines) for f, lines in trailing_ws.items()]),
 
334
                    len(trailing_ws)))
321
335
        if illegal_newlines:
322
336
            problems.append(self._format_message(illegal_newlines,
323
337
                'Non-unix newlines were found in the following source files:'))
 
338
        if long_lines:
 
339
            print ("There are %i lines longer than 79 characters in %i files."
 
340
                % (sum([len(lines) for f, lines in long_lines.items()]),
 
341
                    len(long_lines)))
324
342
        if no_newline_at_eof:
325
343
            no_newline_at_eof.sort()
326
344
            problems.append("The following source files doesn't have a "
364
382
    def test_extension_exceptions(self):
365
383
        """Extension functions should propagate exceptions.
366
384
 
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.
 
385
        Either they should return an object, have an 'except' clause, or have a
 
386
        "# cannot_raise" to indicate that we've audited them and defined them as not
 
387
        raising exceptions.
370
388
        """
371
389
        both_exc_and_no_exc = []
372
390
        missing_except = []
374
392
                              r'(api\s+)?class (\w+).*:', re.MULTILINE)
375
393
        extern_class_re = re.compile(r'## extern cdef class (\w+)',
376
394
                                     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
 
395
        except_re = re.compile(r'cdef\s+' # start with cdef
 
396
                               r'([\w *]*?)\s*' # this is the return signature
 
397
                               r'(\w+)\s*\(' # the function name
 
398
                               r'[^)]*\)\s*' # parameters
 
399
                               r'(.*)\s*:' # the except clause
 
400
                               r'\s*(#\s*cannot[- _]raise)?' # cannot raise comment
 
401
                              )
384
402
        for fname, text in self.get_source_file_contents(
385
403
                extensions=('.pyx',)):
386
404
            known_classes = set([m[-1] for m in class_re.findall(text)])
399
417
                    missing_except.append((fname, func))
400
418
        error_msg = []
401
419
        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:')
 
420
            error_msg.append('The following functions had "cannot raise" comments'
 
421
                             ' but did have an except clause set:')
405
422
            for fname, func in both_exc_and_no_exc:
406
423
                error_msg.append('%s:%s' % (fname, func))
407
424
            error_msg.extend(('', ''))
408
425
        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".')
 
426
            error_msg.append('The following functions have fixed return types,'
 
427
                             ' but no except clause.')
 
428
            error_msg.append('Either add an except or append "# cannot_raise".')
414
429
            for fname, func in missing_except:
415
430
                error_msg.append('%s:%s' % (fname, func))
416
431
            error_msg.extend(('', ''))