~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_source.py

  • Committer: Matt Nordhoff
  • Date: 2009-04-04 02:50:01 UTC
  • mfrom: (4253 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4256.
  • Revision ID: mnordhoff@mattnordhoff.com-20090404025001-z1403k0tatmc8l91
Merge bzr.dev, fixing conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
#
15
15
# You should have received a copy of the GNU General Public License
16
16
# along with this program; if not, write to the Free Software
17
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
17
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
 
19
19
"""These tests are tests about the source code of bzrlib itself.
20
20
 
25
25
import os
26
26
import parser
27
27
import re
28
 
from cStringIO import StringIO
29
28
import symbol
30
29
import sys
31
30
import token
32
31
 
33
32
#import bzrlib specific imports here
34
33
from bzrlib import (
35
 
    diff,
36
34
    osutils,
37
 
    patiencediff,
38
 
    textfile,
39
35
    )
40
36
import bzrlib.branch
41
37
from bzrlib.tests import (
43
39
    TestCase,
44
40
    TestSkipped,
45
41
    )
46
 
from bzrlib.workingtree import WorkingTree
47
42
 
48
43
 
49
44
# Files which are listed here will be skipped when testing for Copyright (or
56
51
# but for compatibility with previous releases, we don't want to move it.
57
52
 
58
53
 
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")
105
 
 
106
 
 
107
54
class TestSourceHelper(TestCase):
108
55
 
109
56
    def source_file_name(self, package):
135
82
        # do not even think of increasing this number. If you think you need to
136
83
        # increase it, then you almost certainly are doing something wrong as
137
84
        # the relationship from working_tree to branch is one way.
138
 
        # Note that this is an exact equality so that when the number drops, 
 
85
        # Note that this is an exact equality so that when the number drops,
139
86
        #it is not given a buffer but rather has this test updated immediately.
140
87
        self.assertEqual(0, occurences)
141
88
 
163
110
                              % source_dir)
164
111
        return source_dir
165
112
 
166
 
    def get_source_files(self):
 
113
    def get_source_files(self, extensions=None):
167
114
        """Yield all source files for bzr and bzrlib
168
 
        
 
115
 
169
116
        :param our_files_only: If true, exclude files from included libraries
170
117
            or plugins.
171
118
        """
172
119
        bzrlib_dir = self.get_bzrlib_dir()
 
120
        if extensions is None:
 
121
            extensions = ('.py',)
173
122
 
174
123
        # This is the front-end 'bzr' script
175
124
        bzr_path = self.get_bzr_path()
180
129
                if d.endswith('.tmp'):
181
130
                    dirs.remove(d)
182
131
            for f in files:
183
 
                if not f.endswith('.py'):
 
132
                for extension in extensions:
 
133
                    if f.endswith(extension):
 
134
                        break
 
135
                else:
 
136
                    # Did not match the accepted extensions
184
137
                    continue
185
138
                yield osutils.pathjoin(root, f)
186
139
 
187
 
    def get_source_file_contents(self):
188
 
        for fname in self.get_source_files():
 
140
    def get_source_file_contents(self, extensions=None):
 
141
        for fname in self.get_source_files(extensions=extensions):
189
142
            f = open(fname, 'rb')
190
143
            try:
191
144
                text = f.read()
229
182
                          % filename)
230
183
 
231
184
    def test_copyright(self):
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.
 
185
        """Test that all .py and .pyx files have a valid copyright statement"""
235
186
        incorrect = []
236
187
 
237
188
        copyright_re = re.compile('#\\s*copyright.*(?=\n)', re.I)
241
192
            r'.*Canonical Ltd' # And containing 'Canonical Ltd'
242
193
            )
243
194
 
244
 
        for fname, text in self.get_source_file_contents():
 
195
        for fname, text in self.get_source_file_contents(
 
196
                extensions=('.py', '.pyx')):
245
197
            if self.is_copyright_exception(fname):
246
198
                continue
247
199
            match = copyright_canonical_re.search(text)
276
228
            self.fail('\n'.join(help_text))
277
229
 
278
230
    def test_gpl(self):
279
 
        """Test that all .py files have a GPL disclaimer"""
 
231
        """Test that all .py and .pyx files have a GPL disclaimer."""
280
232
        incorrect = []
281
233
 
282
234
        gpl_txt = """
292
244
#
293
245
# You should have received a copy of the GNU General Public License
294
246
# along with this program; if not, write to the Free Software
295
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
247
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
296
248
"""
297
249
        gpl_re = re.compile(re.escape(gpl_txt), re.MULTILINE)
298
250
 
299
 
        for fname, text in self.get_source_file_contents():
 
251
        for fname, text in self.get_source_file_contents(
 
252
                extensions=('.py', '.pyx')):
300
253
            if self.is_license_exception(fname):
301
254
                continue
302
255
            if not gpl_re.search(text):
316
269
 
317
270
            self.fail('\n'.join(help_text))
318
271
 
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)))
 
272
    def _push_file(self, dict_, fname, line_no):
 
273
        if fname not in dict_:
 
274
            dict_[fname] = [line_no]
 
275
        else:
 
276
            dict_[fname].append(line_no)
 
277
 
 
278
    def _format_message(self, dict_, message):
 
279
        files = ["%s: %s" % (f, ', '.join([str(i+1) for i in lines]))
 
280
                for f, lines in dict_.items()]
 
281
        files.sort()
 
282
        return message + '\n\n    %s' % ('\n    '.join(files))
334
283
 
335
284
    def test_coding_style(self):
336
 
        """ Check if bazaar code conforms to some coding style conventions.
 
285
        """Check if bazaar code conforms to some coding style conventions.
337
286
 
338
 
        Currently we check all .py files for:
339
 
         * new trailing white space
340
 
         * new leading tabs
341
 
         * new long lines (give warning only)
 
287
        Currently we check for:
 
288
         * any tab characters
 
289
         * trailing white space
 
290
         * non-unix newlines
342
291
         * no newline at end of files
 
292
         * lines longer than 79 chars
 
293
           (only print how many files and lines are in violation)
343
294
        """
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())
 
295
        tabs = {}
 
296
        trailing_ws = {}
 
297
        illegal_newlines = {}
 
298
        long_lines = {}
 
299
        no_newline_at_eof = []
 
300
        for fname, text in self.get_source_file_contents(
 
301
                extensions=('.py', '.pyx')):
 
302
            if not self.is_our_code(fname):
 
303
                continue
 
304
            lines = text.splitlines(True)
 
305
            last_line_no = len(lines) - 1
 
306
            for line_no, line in enumerate(lines):
 
307
                if '\t' in line:
 
308
                    self._push_file(tabs, fname, line_no)
 
309
                if not line.endswith('\n') or line.endswith('\r\n'):
 
310
                    if line_no != last_line_no: # not no_newline_at_eof
 
311
                        self._push_file(illegal_newlines, fname, line_no)
 
312
                if line.endswith(' \n'):
 
313
                    self._push_file(trailing_ws, fname, line_no)
 
314
                if len(line) > 80:
 
315
                    self._push_file(long_lines, fname, line_no)
 
316
            if not lines[-1].endswith('\n'):
 
317
                no_newline_at_eof.append(fname)
 
318
        problems = []
 
319
        if tabs:
 
320
            problems.append(self._format_message(tabs,
 
321
                'Tab characters were found in the following source files.'
 
322
                '\nThey should either be replaced by "\\t" or by spaces:'))
 
323
        if trailing_ws:
 
324
            problems.append(self._format_message(trailing_ws,
 
325
                'Trailing white space was found in the following source files:'
 
326
                ))
 
327
        if illegal_newlines:
 
328
            problems.append(self._format_message(illegal_newlines,
 
329
                'Non-unix newlines were found in the following source files:'))
 
330
        if long_lines:
 
331
            print ("There are %i lines longer than 79 characters in %i files."
 
332
                % (sum([len(lines) for f, lines in long_lines.items()]),
 
333
                    len(long_lines)))
 
334
        if no_newline_at_eof:
 
335
            no_newline_at_eof.sort()
 
336
            problems.append("The following source files doesn't have a "
 
337
                "newline at the end:"
 
338
               '\n\n    %s'
 
339
               % ('\n    '.join(no_newline_at_eof)))
 
340
        if problems:
 
341
            raise KnownFailure("test_coding_style has failed")
 
342
            self.fail('\n\n'.join(problems))
381
343
 
382
344
    def test_no_asserts(self):
383
345
        """bzr shouldn't use the 'assert' statement."""