/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_source.py

  • Committer: Frank Aspell
  • Date: 2009-02-22 16:54:02 UTC
  • mto: This revision was merged to the branch mainline in revision 4256.
  • Revision ID: frankaspell@googlemail.com-20090222165402-2myrucnu7er5w4ha
Fixing various typos

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
 
2
#   Authors: Robert Collins <robert.collins@canonical.com>
 
3
#            and others
2
4
#
3
5
# This program is free software; you can redistribute it and/or modify
4
6
# it under the terms of the GNU General Public License as published by
12
14
#
13
15
# You should have received a copy of the GNU General Public License
14
16
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
18
 
17
19
"""These tests are tests about the source code of bzrlib itself.
18
20
 
23
25
import os
24
26
import parser
25
27
import re
 
28
from cStringIO import StringIO
26
29
import symbol
27
30
import sys
28
31
import token
29
32
 
30
33
#import bzrlib specific imports here
31
34
from bzrlib import (
 
35
    diff,
32
36
    osutils,
 
37
    patiencediff,
 
38
    textfile,
33
39
    )
34
40
import bzrlib.branch
35
41
from bzrlib.tests import (
 
42
    KnownFailure,
36
43
    TestCase,
37
44
    TestSkipped,
38
45
    )
 
46
from bzrlib.workingtree import WorkingTree
39
47
 
40
48
 
41
49
# Files which are listed here will be skipped when testing for Copyright (or
42
50
# GPL) statements.
43
 
COPYRIGHT_EXCEPTIONS = ['bzrlib/lsprof.py', 'bzrlib/_bencode_py.py',
44
 
    'bzrlib/doc_generate/sphinx_conf.py']
 
51
COPYRIGHT_EXCEPTIONS = ['bzrlib/lsprof.py']
45
52
 
46
 
LICENSE_EXCEPTIONS = ['bzrlib/lsprof.py', 'bzrlib/_bencode_py.py',
47
 
    'bzrlib/doc_generate/sphinx_conf.py']
 
53
LICENSE_EXCEPTIONS = ['bzrlib/lsprof.py']
48
54
# Technically, 'bzrlib/lsprof.py' should be 'bzrlib/util/lsprof.py',
49
55
# (we do not check bzrlib/util/, since that is code bundled from elsewhere)
50
56
# but for compatibility with previous releases, we don't want to move it.
51
 
#
52
 
# sphinx_conf is semi-autogenerated.
 
57
 
 
58
 
 
59
def check_coding_style(old_filename, oldlines, new_filename, newlines, to_file,
 
60
                  allow_binary=False, sequence_matcher=None,
 
61
                  path_encoding='utf8'):
 
62
    """text_differ to be passed to diff.DiffText, which checks code style """
 
63
    if allow_binary is False:
 
64
        textfile.check_text_lines(oldlines)
 
65
        textfile.check_text_lines(newlines)
 
66
 
 
67
    if sequence_matcher is None:
 
68
        sequence_matcher = patiencediff.PatienceSequenceMatcher
 
69
 
 
70
    started = [False] #trick to access parent scoped variable
 
71
    def start_if_needed():
 
72
        if not started[0]:
 
73
            to_file.write('+++ %s\n' % new_filename)
 
74
            started[0] = True
 
75
 
 
76
    def check_newlines(j1, j2):
 
77
        for i, line in enumerate(newlines[j1:j2]):
 
78
            bad_ws_match = re.match(r'^(([\t]*)(.*?)([\t ]*))(\r?\n)?$', line)
 
79
            if bad_ws_match:
 
80
                line_content = bad_ws_match.group(1)
 
81
                has_leading_tabs = bool(bad_ws_match.group(2))
 
82
                has_trailing_whitespace = bool(bad_ws_match.group(4))
 
83
                if has_leading_tabs:
 
84
                    start_if_needed()
 
85
                    to_file.write('line %i has leading tabs: "%s"\n'% (
 
86
                        i+1+j1, line_content))
 
87
                if has_trailing_whitespace:
 
88
                    start_if_needed()
 
89
                    to_file.write('line %i has trailing whitespace: "%s"\n'% (
 
90
                        i+1+j1, line_content))
 
91
                if len(line_content) > 79:
 
92
                    print (
 
93
                        '\nFile %s\nline %i is longer than 79 characters:'
 
94
                        '\n"%s"'% (new_filename, i+1+j1, line_content))
 
95
 
 
96
    for group in sequence_matcher(None, oldlines, newlines
 
97
            ).get_grouped_opcodes(0):
 
98
        for tag, i1, i2, j1, j2 in group:
 
99
            if tag == 'replace' or tag == 'insert':
 
100
                check_newlines(j1, j2)
 
101
 
 
102
    if len(newlines) == j2 and not newlines[j2-1].endswith('\n'):
 
103
        start_if_needed()
 
104
        to_file.write("\\ No newline at end of file\n")
53
105
 
54
106
 
55
107
class TestSourceHelper(TestCase):
83
135
        # do not even think of increasing this number. If you think you need to
84
136
        # increase it, then you almost certainly are doing something wrong as
85
137
        # the relationship from working_tree to branch is one way.
86
 
        # Note that this is an exact equality so that when the number drops,
 
138
        # Note that this is an exact equality so that when the number drops, 
87
139
        #it is not given a buffer but rather has this test updated immediately.
88
140
        self.assertEqual(0, occurences)
89
141
 
111
163
                              % source_dir)
112
164
        return source_dir
113
165
 
114
 
    def get_source_files(self, extensions=None):
 
166
    def get_source_files(self):
115
167
        """Yield all source files for bzr and bzrlib
116
 
 
 
168
        
117
169
        :param our_files_only: If true, exclude files from included libraries
118
170
            or plugins.
119
171
        """
120
172
        bzrlib_dir = self.get_bzrlib_dir()
121
 
        if extensions is None:
122
 
            extensions = ('.py',)
123
173
 
124
174
        # This is the front-end 'bzr' script
125
175
        bzr_path = self.get_bzr_path()
130
180
                if d.endswith('.tmp'):
131
181
                    dirs.remove(d)
132
182
            for f in files:
133
 
                for extension in extensions:
134
 
                    if f.endswith(extension):
135
 
                        break
136
 
                else:
137
 
                    # Did not match the accepted extensions
 
183
                if not f.endswith('.py'):
138
184
                    continue
139
185
                yield osutils.pathjoin(root, f)
140
186
 
141
 
    def get_source_file_contents(self, extensions=None):
142
 
        for fname in self.get_source_files(extensions=extensions):
 
187
    def get_source_file_contents(self):
 
188
        for fname in self.get_source_files():
143
189
            f = open(fname, 'rb')
144
190
            try:
145
191
                text = f.read()
183
229
                          % filename)
184
230
 
185
231
    def test_copyright(self):
186
 
        """Test that all .py and .pyx files have a valid copyright statement"""
 
232
        """Test that all .py files have a valid copyright statement"""
 
233
        # These are files which contain a different copyright statement
 
234
        # and that is okay.
187
235
        incorrect = []
188
236
 
189
237
        copyright_re = re.compile('#\\s*copyright.*(?=\n)', re.I)
193
241
            r'.*Canonical Ltd' # And containing 'Canonical Ltd'
194
242
            )
195
243
 
196
 
        for fname, text in self.get_source_file_contents(
197
 
                extensions=('.py', '.pyx')):
 
244
        for fname, text in self.get_source_file_contents():
198
245
            if self.is_copyright_exception(fname):
199
246
                continue
200
247
            match = copyright_canonical_re.search(text)
229
276
            self.fail('\n'.join(help_text))
230
277
 
231
278
    def test_gpl(self):
232
 
        """Test that all .py and .pyx files have a GPL disclaimer."""
 
279
        """Test that all .py files have a GPL disclaimer"""
233
280
        incorrect = []
234
281
 
235
282
        gpl_txt = """
245
292
#
246
293
# You should have received a copy of the GNU General Public License
247
294
# along with this program; if not, write to the Free Software
248
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
295
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
249
296
"""
250
297
        gpl_re = re.compile(re.escape(gpl_txt), re.MULTILINE)
251
298
 
252
 
        for fname, text in self.get_source_file_contents(
253
 
                extensions=('.py', '.pyx')):
 
299
        for fname, text in self.get_source_file_contents():
254
300
            if self.is_license_exception(fname):
255
301
                continue
256
302
            if not gpl_re.search(text):
270
316
 
271
317
            self.fail('\n'.join(help_text))
272
318
 
273
 
    def _push_file(self, dict_, fname, line_no):
274
 
        if fname not in dict_:
275
 
            dict_[fname] = [line_no]
276
 
        else:
277
 
            dict_[fname].append(line_no)
278
 
 
279
 
    def _format_message(self, dict_, message):
280
 
        files = ["%s: %s" % (f, ', '.join([str(i+1) for i in lines]))
281
 
                for f, lines in dict_.items()]
282
 
        files.sort()
283
 
        return message + '\n\n    %s' % ('\n    '.join(files))
 
319
    def test_no_tabs(self):
 
320
        """bzrlib source files should not contain any tab characters."""
 
321
        incorrect = []
 
322
 
 
323
        for fname, text in self.get_source_file_contents():
 
324
            if not self.is_our_code(fname):
 
325
                continue
 
326
            if '\t' in text:
 
327
                incorrect.append(fname)
 
328
 
 
329
        if incorrect:
 
330
            self.fail('Tab characters were found in the following source files.'
 
331
              '\nThey should either be replaced by "\\t" or by spaces:'
 
332
              '\n\n    %s'
 
333
              % ('\n    '.join(incorrect)))
284
334
 
285
335
    def test_coding_style(self):
286
 
        """Check if bazaar code conforms to some coding style conventions.
 
336
        """ Check if bazaar code conforms to some coding style conventions.
287
337
 
288
 
        Currently we assert that the following is not present:
289
 
         * any tab characters
290
 
         * non-unix newlines
 
338
        Currently we check all .py files for:
 
339
         * new trailing white space
 
340
         * new leading tabs
 
341
         * new long lines (give warning only)
291
342
         * no newline at end of files
292
 
 
293
 
        Print how many files have
294
 
         * trailing white space
295
 
         * lines longer than 79 chars
296
343
        """
297
 
        tabs = {}
298
 
        trailing_ws = {}
299
 
        illegal_newlines = {}
300
 
        long_lines = {}
301
 
        no_newline_at_eof = []
302
 
        for fname, text in self.get_source_file_contents(
303
 
                extensions=('.py', '.pyx')):
304
 
            if not self.is_our_code(fname):
305
 
                continue
306
 
            lines = text.splitlines(True)
307
 
            last_line_no = len(lines) - 1
308
 
            for line_no, line in enumerate(lines):
309
 
                if '\t' in line:
310
 
                    self._push_file(tabs, fname, line_no)
311
 
                if not line.endswith('\n') or line.endswith('\r\n'):
312
 
                    if line_no != last_line_no: # not no_newline_at_eof
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)
318
 
            if not lines[-1].endswith('\n'):
319
 
                no_newline_at_eof.append(fname)
320
 
        problems = []
321
 
        if tabs:
322
 
            problems.append(self._format_message(tabs,
323
 
                'Tab characters were found in the following source files.'
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)))
329
 
        if illegal_newlines:
330
 
            problems.append(self._format_message(illegal_newlines,
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)))
336
 
        if no_newline_at_eof:
337
 
            no_newline_at_eof.sort()
338
 
            problems.append("The following source files doesn't have a "
339
 
                "newline at the end:"
340
 
               '\n\n    %s'
341
 
               % ('\n    '.join(no_newline_at_eof)))
342
 
        if problems:
343
 
            self.fail('\n\n'.join(problems))
 
344
        bzr_dir = osutils.dirname(self.get_bzrlib_dir())
 
345
        try:
 
346
            wt = WorkingTree.open(bzr_dir)
 
347
        except:
 
348
            raise TestSkipped(
 
349
                'Could not open bazaar working tree %s'
 
350
                % bzr_dir)
 
351
        diff_output = StringIO()
 
352
        wt.lock_read()
 
353
        try:
 
354
            new_tree = wt
 
355
            old_tree = new_tree.basis_tree()
 
356
 
 
357
            old_tree.lock_read()
 
358
            new_tree.lock_read()
 
359
            try:
 
360
                iterator = new_tree.iter_changes(old_tree)
 
361
                for (file_id, paths, changed_content, versioned, parent,
 
362
                    name, kind, executable) in iterator:
 
363
                    if (changed_content and paths[1].endswith('.py')):
 
364
                        if kind == ('file', 'file'):
 
365
                            diff_text = diff.DiffText(old_tree, new_tree,
 
366
                                to_file=diff_output,
 
367
                                text_differ=check_coding_style)
 
368
                            diff_text.diff(file_id, paths[0], paths[1],
 
369
                                kind[0], kind[1])
 
370
                        else:
 
371
                            check_coding_style(name[0], (), name[1],
 
372
                                new_tree.get_file(file_id).readlines(),
 
373
                                diff_output)
 
374
            finally:
 
375
                old_tree.unlock()
 
376
                new_tree.unlock()
 
377
        finally:
 
378
            wt.unlock()
 
379
        if len(diff_output.getvalue()) > 0:
 
380
            self.fail("Unacceptable coding style:\n" + diff_output.getvalue())
344
381
 
345
382
    def test_no_asserts(self):
346
383
        """bzr shouldn't use the 'assert' statement."""
359
396
                    return True
360
397
            return False
361
398
        badfiles = []
362
 
        assert_re = re.compile(r'\bassert\b')
363
399
        for fname, text in self.get_source_file_contents():
364
400
            if not self.is_our_code(fname):
365
401
                continue
366
 
            if not assert_re.search(text):
367
 
                continue
368
 
            ast = parser.ast2tuple(parser.suite(text))
 
402
            ast = parser.ast2tuple(parser.suite(''.join(text)))
369
403
            if search(ast):
370
404
                badfiles.append(fname)
371
405
        if badfiles:
372
406
            self.fail(
373
407
                "these files contain an assert statement and should not:\n%s"
374
408
                % '\n'.join(badfiles))
375
 
 
376
 
    def test_extension_exceptions(self):
377
 
        """Extension functions should propagate exceptions.
378
 
 
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.
382
 
        """
383
 
        both_exc_and_no_exc = []
384
 
        missing_except = []
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
 
                              )
394
 
        for fname, text in self.get_source_file_contents(
395
 
                extensions=('.pyx',)):
396
 
            known_classes = set([m[-1] for m in class_re.findall(text)])
397
 
            cdefs = except_re.findall(text)
398
 
            for sig, func, exc_clause, no_exc_comment in cdefs:
399
 
                if sig.startswith('api '):
400
 
                    sig = sig[4:]
401
 
                if not sig or sig in known_classes:
402
 
                    sig = 'object'
403
 
                if 'nogil' in exc_clause:
404
 
                    exc_clause = exc_clause.replace('nogil', '').strip()
405
 
                if exc_clause and no_exc_comment:
406
 
                    both_exc_and_no_exc.append((fname, func))
407
 
                if sig != 'object' and not (exc_clause or no_exc_comment):
408
 
                    missing_except.append((fname, func))
409
 
        error_msg = []
410
 
        if both_exc_and_no_exc:
411
 
            error_msg.append('The following functions had "cannot raise" comments'
412
 
                             ' but did have an except clause set:')
413
 
            for fname, func in both_exc_and_no_exc:
414
 
                error_msg.append('%s:%s' % (fname, func))
415
 
            error_msg.extend(('', ''))
416
 
        if missing_except:
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".')
420
 
            for fname, func in missing_except:
421
 
                error_msg.append('%s:%s' % (fname, func))
422
 
            error_msg.extend(('', ''))
423
 
        if error_msg:
424
 
            self.fail('\n'.join(error_msg))