/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
1
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
2
#   Authors: Robert Collins <robert.collins@canonical.com>
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
3
#            and others
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
4
#
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
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
18
19
"""These tests are tests about the source code of bzrlib itself.
20
21
They are useful for testing code quality, checking coverage metric etc.
22
"""
23
24
# import system imports here
25
import os
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
26
import parser
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
27
import re
3572.1.10 by John Arbash Meinel
Use cStringIO instead of StringIO
28
from cStringIO import StringIO
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
29
import symbol
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
30
import sys
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
31
import token
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
32
33
#import bzrlib specific imports here
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
34
from bzrlib import (
3572.1.3 by Marius Kruger
* move test_coding_style into test_source
35
    diff,
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
36
    osutils,
3572.1.3 by Marius Kruger
* move test_coding_style into test_source
37
    patiencediff,
38
    textfile,
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
39
    )
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
40
import bzrlib.branch
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
41
from bzrlib.tests import (
42
    KnownFailure,
43
    TestCase,
44
    TestSkipped,
45
    )
3572.1.3 by Marius Kruger
* move test_coding_style into test_source
46
from bzrlib.workingtree import WorkingTree
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
47
48
2052.3.8 by John Arbash Meinel
Better documentation about the exception variables
49
# Files which are listed here will be skipped when testing for Copyright (or
50
# GPL) statements.
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
51
COPYRIGHT_EXCEPTIONS = ['bzrlib/lsprof.py']
52
53
LICENSE_EXCEPTIONS = ['bzrlib/lsprof.py']
2052.3.8 by John Arbash Meinel
Better documentation about the exception variables
54
# Technically, 'bzrlib/lsprof.py' should be 'bzrlib/util/lsprof.py',
55
# (we do not check bzrlib/util/, since that is code bundled from elsewhere)
56
# but for compatibility with previous releases, we don't want to move it.
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
57
3572.1.7 by Marius Kruger
Code style and minor changes as per review.
58
3572.1.3 by Marius Kruger
* move test_coding_style into test_source
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:
3572.1.4 by Marius Kruger
don't emit warning, but just print a message for long lines
92
                    print (
3572.1.3 by Marius Kruger
* move test_coding_style into test_source
93
                        '\nFile %s\nline %i is longer than 79 characters:'
3572.1.4 by Marius Kruger
don't emit warning, but just print a message for long lines
94
                        '\n"%s"'% (new_filename, i+1+j1, line_content))
3572.1.3 by Marius Kruger
* move test_coding_style into test_source
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
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
106
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
107
class TestSourceHelper(TestCase):
108
109
    def source_file_name(self, package):
110
        """Return the path of the .py file for package."""
3616.2.7 by Mark Hammond
prefer getattr() over hasattr()
111
        if getattr(sys, "frozen", None) is not None:
3616.2.5 by Mark Hammond
don't try and test source code when we are frozen.
112
            raise TestSkipped("can't test sources in frozen distributions.")
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
113
        path = package.__file__
114
        if path[-1] in 'co':
115
            return path[:-1]
116
        else:
117
            return path
118
119
120
class TestApiUsage(TestSourceHelper):
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
121
1524 by Robert Collins
Test the uses of WorkingTree from branch.py
122
    def find_occurences(self, rule, filename):
123
        """Find the number of occurences of rule in a file."""
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
124
        occurences = 0
1524 by Robert Collins
Test the uses of WorkingTree from branch.py
125
        source = file(filename, 'r')
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
126
        for line in source:
1524 by Robert Collins
Test the uses of WorkingTree from branch.py
127
            if line.find(rule) > -1:
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
128
                occurences += 1
1524 by Robert Collins
Test the uses of WorkingTree from branch.py
129
        return occurences
130
131
    def test_branch_working_tree(self):
132
        """Test that the number of uses of working_tree in branch is stable."""
133
        occurences = self.find_occurences('self.working_tree()',
1526 by Robert Collins
Bugfix to source testing logic to get the right path when .py is returned by __file__
134
                                          self.source_file_name(bzrlib.branch))
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
135
        # do not even think of increasing this number. If you think you need to
136
        # increase it, then you almost certainly are doing something wrong as
137
        # the relationship from working_tree to branch is one way.
1534.4.35 by Robert Collins
Give branch its own basis tree and last_revision methods; deprecated branch.working_tree()
138
        # Note that this is an exact equality so that when the number drops, 
139
        #it is not given a buffer but rather has this test updated immediately.
140
        self.assertEqual(0, occurences)
1524 by Robert Collins
Test the uses of WorkingTree from branch.py
141
142
    def test_branch_WorkingTree(self):
143
        """Test that the number of uses of working_tree in branch is stable."""
144
        occurences = self.find_occurences('WorkingTree',
1526 by Robert Collins
Bugfix to source testing logic to get the right path when .py is returned by __file__
145
                                          self.source_file_name(bzrlib.branch))
2696.1.1 by Martin Pool
Remove things deprecated in 0.11 and earlier
146
        # Do not even think of increasing this number. If you think you need to
1524 by Robert Collins
Test the uses of WorkingTree from branch.py
147
        # increase it, then you almost certainly are doing something wrong as
148
        # the relationship from working_tree to branch is one way.
2696.1.1 by Martin Pool
Remove things deprecated in 0.11 and earlier
149
        # As of 20070809, there are no longer any mentions at all.
150
        self.assertEqual(0, occurences)
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
151
152
153
class TestSource(TestSourceHelper):
154
155
    def get_bzrlib_dir(self):
156
        """Get the path to the root of bzrlib"""
157
        source = self.source_file_name(bzrlib)
158
        source_dir = os.path.dirname(source)
159
160
        # Avoid the case when bzrlib is packaged in a zip file
161
        if not os.path.isdir(source_dir):
162
            raise TestSkipped('Cannot find bzrlib source directory. Expected %s'
163
                              % source_dir)
164
        return source_dir
165
166
    def get_source_files(self):
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
167
        """Yield all source files for bzr and bzrlib
168
        
169
        :param our_files_only: If true, exclude files from included libraries
170
            or plugins.
171
        """
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
172
        bzrlib_dir = self.get_bzrlib_dir()
173
174
        # This is the front-end 'bzr' script
175
        bzr_path = self.get_bzr_path()
176
        yield bzr_path
177
178
        for root, dirs, files in os.walk(bzrlib_dir):
2102.3.1 by mbp at sourcefrog
test_source should avoid walking into tempdirs
179
            for d in dirs:
180
                if d.endswith('.tmp'):
181
                    dirs.remove(d)
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
182
            for f in files:
183
                if not f.endswith('.py'):
184
                    continue
185
                yield osutils.pathjoin(root, f)
186
187
    def get_source_file_contents(self):
188
        for fname in self.get_source_files():
189
            f = open(fname, 'rb')
190
            try:
191
                text = f.read()
192
            finally:
193
                f.close()
194
            yield fname, text
195
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
196
    def is_our_code(self, fname):
197
        """Return true if it's a "real" part of bzrlib rather than external code"""
198
        if '/util/' in fname or '/plugins/' in fname:
199
            return False
200
        else:
201
            return True
202
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
203
    def is_copyright_exception(self, fname):
204
        """Certain files are allowed to be different"""
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
205
        if not self.is_our_code(fname):
2084.1.5 by John Arbash Meinel
Don't check plugins for copyright or license
206
            # We don't ask that external utilities or plugins be
207
            # (C) Canonical Ltd
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
208
            return True
209
        for exc in COPYRIGHT_EXCEPTIONS:
210
            if fname.endswith(exc):
211
                return True
212
        return False
213
214
    def is_license_exception(self, fname):
215
        """Certain files are allowed to be different"""
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
216
        if not self.is_our_code(fname):
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
217
            return True
218
        for exc in LICENSE_EXCEPTIONS:
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
219
            if fname.endswith(exc):
220
                return True
221
        return False
222
2102.3.1 by mbp at sourcefrog
test_source should avoid walking into tempdirs
223
    def test_tmpdir_not_in_source_files(self):
224
        """When scanning for source files, we don't descend test tempdirs"""
225
        for filename in self.get_source_files():
226
            if re.search(r'test....\.tmp', filename):
227
                self.fail("get_source_file() returned filename %r "
228
                          "from within a temporary directory"
229
                          % filename)
230
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
231
    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.
235
        incorrect = []
236
2052.3.7 by John Arbash Meinel
Use positive lookahead to avoid extra newlines
237
        copyright_re = re.compile('#\\s*copyright.*(?=\n)', re.I)
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
238
        copyright_canonical_re = re.compile(
239
            r'# Copyright \(C\) ' # Opening "# Copyright (C)"
240
            r'(\d+)(, \d+)*' # Followed by a series of dates
241
            r'.*Canonical Ltd' # And containing 'Canonical Ltd'
242
            )
243
244
        for fname, text in self.get_source_file_contents():
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
245
            if self.is_copyright_exception(fname):
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
246
                continue
2052.3.2 by John Arbash Meinel
Change Copyright .. by Canonical to Copyright ... Canonical
247
            match = copyright_canonical_re.search(text)
248
            if not match:
249
                match = copyright_re.search(text)
250
                if match:
251
                    incorrect.append((fname, 'found: %s' % (match.group(),)))
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
252
                else:
253
                    incorrect.append((fname, 'no copyright line found\n'))
2052.3.2 by John Arbash Meinel
Change Copyright .. by Canonical to Copyright ... Canonical
254
            else:
255
                if 'by Canonical' in match.group():
256
                    incorrect.append((fname,
257
                        'should not have: "by Canonical": %s'
258
                        % (match.group(),)))
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
259
260
        if incorrect:
261
            help_text = ["Some files have missing or incorrect copyright"
262
                         " statements.",
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
263
                         "",
264
                         "Please either add them to the list of"
265
                         " COPYRIGHT_EXCEPTIONS in"
266
                         " bzrlib/tests/test_source.py",
267
                         # this is broken to prevent a false match
268
                         "or add '# Copyright (C)"
2613.1.2 by Martin Pool
Move bencode tests into util.tests
269
                         " 2007 Canonical Ltd' to these files:",
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
270
                         "",
271
                        ]
272
            for fname, comment in incorrect:
273
                help_text.append(fname)
274
                help_text.append((' '*4) + comment)
275
276
            self.fail('\n'.join(help_text))
277
278
    def test_gpl(self):
279
        """Test that all .py files have a GPL disclaimer"""
280
        incorrect = []
281
282
        gpl_txt = """
283
# This program is free software; you can redistribute it and/or modify
284
# it under the terms of the GNU General Public License as published by
285
# the Free Software Foundation; either version 2 of the License, or
286
# (at your option) any later version.
287
#
288
# This program is distributed in the hope that it will be useful,
289
# but WITHOUT ANY WARRANTY; without even the implied warranty of
290
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
291
# GNU General Public License for more details.
292
#
293
# You should have received a copy of the GNU General Public License
294
# along with this program; if not, write to the Free Software
295
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
296
"""
297
        gpl_re = re.compile(re.escape(gpl_txt), re.MULTILINE)
298
299
        for fname, text in self.get_source_file_contents():
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
300
            if self.is_license_exception(fname):
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
301
                continue
302
            if not gpl_re.search(text):
303
                incorrect.append(fname)
304
305
        if incorrect:
306
            help_text = ['Some files have missing or incomplete GPL statement',
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
307
                         "",
308
                         "Please either add them to the list of"
309
                         " LICENSE_EXCEPTIONS in"
310
                         " bzrlib/tests/test_source.py",
311
                         "Or add the following text to the beginning:",
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
312
                         gpl_txt
313
                        ]
314
            for fname in incorrect:
315
                help_text.append((' '*4) + fname)
316
317
            self.fail('\n'.join(help_text))
2120.2.1 by John Arbash Meinel
Remove tabs from source files, and add a test to keep it that way.
318
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():
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
324
            if not self.is_our_code(fname):
2234.4.1 by Wouter van Heyst
(John Arbash Meinel) Fix selftest for installed bzr (#80330)
325
                continue
2120.2.1 by John Arbash Meinel
Remove tabs from source files, and add a test to keep it that way.
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)))
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
334
3572.1.3 by Marius Kruger
* move test_coding_style into test_source
335
    def test_coding_style(self):
336
        """ Check if bazaar code conforms to some coding style conventions.
337
3572.1.7 by Marius Kruger
Code style and minor changes as per review.
338
        Currently we check all .py files for:
339
         * new trailing white space
340
         * new leading tabs
341
         * new long lines (give warning only)
342
         * no newline at end of files
3572.1.3 by Marius Kruger
* move test_coding_style into test_source
343
        """
3572.1.9 by Marius Kruger
more minor changes as per review
344
        bzr_dir = osutils.dirname(self.get_bzrlib_dir())
3572.1.5 by Marius Kruger
* remove reference to old bzrlib.tests.test_coding_style
345
        try:
346
            wt = WorkingTree.open(bzr_dir)
347
        except:
348
            raise TestSkipped(
349
                'Could not open bazaar working tree %s'
350
                % bzr_dir)
3572.1.3 by Marius Kruger
* move test_coding_style into test_source
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:
3572.1.7 by Marius Kruger
Code style and minor changes as per review.
360
                iterator = new_tree.iter_changes(old_tree)
3572.1.3 by Marius Kruger
* move test_coding_style into test_source
361
                for (file_id, paths, changed_content, versioned, parent,
362
                    name, kind, executable) in iterator:
3572.1.8 by Marius Kruger
fix bug where type changed to file
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)
3572.1.3 by Marius Kruger
* move test_coding_style into test_source
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())
381
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
382
    def test_no_asserts(self):
383
        """bzr shouldn't use the 'assert' statement."""
384
        # assert causes too much variation between -O and not, and tends to
385
        # give bad errors to the user
386
        def search(x):
387
            # scan down through x for assert statements, report any problems
388
            # this is a bit cheesy; it may get some false positives?
389
            if x[0] == symbol.assert_stmt:
390
                return True
391
            elif x[0] == token.NAME:
392
                # can't search further down
393
                return False
394
            for sub in x[1:]:
395
                if sub and search(sub):
396
                    return True
397
            return False
398
        badfiles = []
399
        for fname, text in self.get_source_file_contents():
400
            if not self.is_our_code(fname):
401
                continue
402
            ast = parser.ast2tuple(parser.suite(''.join(text)))
403
            if search(ast):
404
                badfiles.append(fname)
405
        if badfiles:
3376.2.7 by Martin Pool
Treat assert statements in our code as a hard error
406
            self.fail(
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
407
                "these files contain an assert statement and should not:\n%s"
408
                % '\n'.join(badfiles))