~bzr-pqm/bzr/bzr.dev

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
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
28
import symbol
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
29
import sys
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
30
import token
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
31
32
#import bzrlib specific imports here
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
33
from bzrlib import (
34
    osutils,
35
    )
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
36
import bzrlib.branch
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
37
from bzrlib.tests import (
38
    KnownFailure,
39
    TestCase,
40
    TestSkipped,
41
    )
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
42
43
2052.3.8 by John Arbash Meinel
Better documentation about the exception variables
44
# Files which are listed here will be skipped when testing for Copyright (or
45
# GPL) statements.
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
46
COPYRIGHT_EXCEPTIONS = ['bzrlib/lsprof.py']
47
48
LICENSE_EXCEPTIONS = ['bzrlib/lsprof.py']
2052.3.8 by John Arbash Meinel
Better documentation about the exception variables
49
# Technically, 'bzrlib/lsprof.py' should be 'bzrlib/util/lsprof.py',
50
# (we do not check bzrlib/util/, since that is code bundled from elsewhere)
51
# 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
52
53
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
54
class TestSourceHelper(TestCase):
55
56
    def source_file_name(self, package):
57
        """Return the path of the .py file for package."""
58
        path = package.__file__
59
        if path[-1] in 'co':
60
            return path[:-1]
61
        else:
62
            return path
63
64
65
class TestApiUsage(TestSourceHelper):
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
66
1524 by Robert Collins
Test the uses of WorkingTree from branch.py
67
    def find_occurences(self, rule, filename):
68
        """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
69
        occurences = 0
1524 by Robert Collins
Test the uses of WorkingTree from branch.py
70
        source = file(filename, 'r')
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
71
        for line in source:
1524 by Robert Collins
Test the uses of WorkingTree from branch.py
72
            if line.find(rule) > -1:
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
73
                occurences += 1
1524 by Robert Collins
Test the uses of WorkingTree from branch.py
74
        return occurences
75
76
    def test_branch_working_tree(self):
77
        """Test that the number of uses of working_tree in branch is stable."""
78
        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__
79
                                          self.source_file_name(bzrlib.branch))
1523 by Robert Collins
Test for the number of uses of self.working_tree() in branch.py
80
        # do not even think of increasing this number. If you think you need to
81
        # increase it, then you almost certainly are doing something wrong as
82
        # 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()
83
        # Note that this is an exact equality so that when the number drops, 
84
        #it is not given a buffer but rather has this test updated immediately.
85
        self.assertEqual(0, occurences)
1524 by Robert Collins
Test the uses of WorkingTree from branch.py
86
87
    def test_branch_WorkingTree(self):
88
        """Test that the number of uses of working_tree in branch is stable."""
89
        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__
90
                                          self.source_file_name(bzrlib.branch))
2696.1.1 by Martin Pool
Remove things deprecated in 0.11 and earlier
91
        # 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
92
        # increase it, then you almost certainly are doing something wrong as
93
        # the relationship from working_tree to branch is one way.
2696.1.1 by Martin Pool
Remove things deprecated in 0.11 and earlier
94
        # As of 20070809, there are no longer any mentions at all.
95
        self.assertEqual(0, occurences)
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
96
97
98
class TestSource(TestSourceHelper):
99
100
    def get_bzrlib_dir(self):
101
        """Get the path to the root of bzrlib"""
102
        source = self.source_file_name(bzrlib)
103
        source_dir = os.path.dirname(source)
104
105
        # Avoid the case when bzrlib is packaged in a zip file
106
        if not os.path.isdir(source_dir):
107
            raise TestSkipped('Cannot find bzrlib source directory. Expected %s'
108
                              % source_dir)
109
        return source_dir
110
111
    def get_source_files(self):
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
112
        """Yield all source files for bzr and bzrlib
113
        
114
        :param our_files_only: If true, exclude files from included libraries
115
            or plugins.
116
        """
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
117
        bzrlib_dir = self.get_bzrlib_dir()
118
119
        # This is the front-end 'bzr' script
120
        bzr_path = self.get_bzr_path()
121
        yield bzr_path
122
123
        for root, dirs, files in os.walk(bzrlib_dir):
2102.3.1 by mbp at sourcefrog
test_source should avoid walking into tempdirs
124
            for d in dirs:
125
                if d.endswith('.tmp'):
126
                    dirs.remove(d)
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
127
            for f in files:
128
                if not f.endswith('.py'):
129
                    continue
130
                yield osutils.pathjoin(root, f)
131
132
    def get_source_file_contents(self):
133
        for fname in self.get_source_files():
134
            f = open(fname, 'rb')
135
            try:
136
                text = f.read()
137
            finally:
138
                f.close()
139
            yield fname, text
140
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
141
    def is_our_code(self, fname):
142
        """Return true if it's a "real" part of bzrlib rather than external code"""
143
        if '/util/' in fname or '/plugins/' in fname:
144
            return False
145
        else:
146
            return True
147
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
148
    def is_copyright_exception(self, fname):
149
        """Certain files are allowed to be different"""
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
150
        if not self.is_our_code(fname):
2084.1.5 by John Arbash Meinel
Don't check plugins for copyright or license
151
            # We don't ask that external utilities or plugins be
152
            # (C) Canonical Ltd
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
153
            return True
154
        for exc in COPYRIGHT_EXCEPTIONS:
155
            if fname.endswith(exc):
156
                return True
157
        return False
158
159
    def is_license_exception(self, fname):
160
        """Certain files are allowed to be different"""
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
161
        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
162
            return True
163
        for exc in LICENSE_EXCEPTIONS:
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
164
            if fname.endswith(exc):
165
                return True
166
        return False
167
2102.3.1 by mbp at sourcefrog
test_source should avoid walking into tempdirs
168
    def test_tmpdir_not_in_source_files(self):
169
        """When scanning for source files, we don't descend test tempdirs"""
170
        for filename in self.get_source_files():
171
            if re.search(r'test....\.tmp', filename):
172
                self.fail("get_source_file() returned filename %r "
173
                          "from within a temporary directory"
174
                          % filename)
175
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
176
    def test_copyright(self):
177
        """Test that all .py files have a valid copyright statement"""
178
        # These are files which contain a different copyright statement
179
        # and that is okay.
180
        incorrect = []
181
2052.3.7 by John Arbash Meinel
Use positive lookahead to avoid extra newlines
182
        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
183
        copyright_canonical_re = re.compile(
184
            r'# Copyright \(C\) ' # Opening "# Copyright (C)"
185
            r'(\d+)(, \d+)*' # Followed by a series of dates
186
            r'.*Canonical Ltd' # And containing 'Canonical Ltd'
187
            )
188
189
        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
190
            if self.is_copyright_exception(fname):
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
191
                continue
2052.3.2 by John Arbash Meinel
Change Copyright .. by Canonical to Copyright ... Canonical
192
            match = copyright_canonical_re.search(text)
193
            if not match:
194
                match = copyright_re.search(text)
195
                if match:
196
                    incorrect.append((fname, 'found: %s' % (match.group(),)))
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
197
                else:
198
                    incorrect.append((fname, 'no copyright line found\n'))
2052.3.2 by John Arbash Meinel
Change Copyright .. by Canonical to Copyright ... Canonical
199
            else:
200
                if 'by Canonical' in match.group():
201
                    incorrect.append((fname,
202
                        'should not have: "by Canonical": %s'
203
                        % (match.group(),)))
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
204
205
        if incorrect:
206
            help_text = ["Some files have missing or incorrect copyright"
207
                         " statements.",
2052.3.5 by John Arbash Meinel
Guide people to how to add files to the list of exceptions
208
                         "",
209
                         "Please either add them to the list of"
210
                         " COPYRIGHT_EXCEPTIONS in"
211
                         " bzrlib/tests/test_source.py",
212
                         # this is broken to prevent a false match
213
                         "or add '# Copyright (C)"
2613.1.2 by Martin Pool
Move bencode tests into util.tests
214
                         " 2007 Canonical Ltd' to these files:",
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
215
                         "",
216
                        ]
217
            for fname, comment in incorrect:
218
                help_text.append(fname)
219
                help_text.append((' '*4) + comment)
220
221
            self.fail('\n'.join(help_text))
222
223
    def test_gpl(self):
224
        """Test that all .py files have a GPL disclaimer"""
225
        incorrect = []
226
227
        gpl_txt = """
228
# This program is free software; you can redistribute it and/or modify
229
# it under the terms of the GNU General Public License as published by
230
# the Free Software Foundation; either version 2 of the License, or
231
# (at your option) any later version.
232
#
233
# This program is distributed in the hope that it will be useful,
234
# but WITHOUT ANY WARRANTY; without even the implied warranty of
235
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
236
# GNU General Public License for more details.
237
#
238
# You should have received a copy of the GNU General Public License
239
# along with this program; if not, write to the Free Software
240
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
241
"""
242
        gpl_re = re.compile(re.escape(gpl_txt), re.MULTILINE)
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_license_exception(fname):
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
246
                continue
247
            if not gpl_re.search(text):
248
                incorrect.append(fname)
249
250
        if incorrect:
251
            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
252
                         "",
253
                         "Please either add them to the list of"
254
                         " LICENSE_EXCEPTIONS in"
255
                         " bzrlib/tests/test_source.py",
256
                         "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
257
                         gpl_txt
258
                        ]
259
            for fname in incorrect:
260
                help_text.append((' '*4) + fname)
261
262
            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.
263
264
    def test_no_tabs(self):
265
        """bzrlib source files should not contain any tab characters."""
266
        incorrect = []
267
268
        for fname, text in self.get_source_file_contents():
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
269
            if not self.is_our_code(fname):
2234.4.1 by Wouter van Heyst
(John Arbash Meinel) Fix selftest for installed bzr (#80330)
270
                continue
2120.2.1 by John Arbash Meinel
Remove tabs from source files, and add a test to keep it that way.
271
            if '\t' in text:
272
                incorrect.append(fname)
273
274
        if incorrect:
275
            self.fail('Tab characters were found in the following source files.'
276
              '\nThey should either be replaced by "\\t" or by spaces:'
277
              '\n\n    %s'
278
              % ('\n    '.join(incorrect)))
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
279
280
    def test_no_asserts(self):
281
        """bzr shouldn't use the 'assert' statement."""
282
        # assert causes too much variation between -O and not, and tends to
283
        # give bad errors to the user
284
        def search(x):
285
            # scan down through x for assert statements, report any problems
286
            # this is a bit cheesy; it may get some false positives?
287
            if x[0] == symbol.assert_stmt:
288
                return True
289
            elif x[0] == token.NAME:
290
                # can't search further down
291
                return False
292
            for sub in x[1:]:
293
                if sub and search(sub):
294
                    return True
295
            return False
296
        badfiles = []
297
        for fname, text in self.get_source_file_contents():
298
            if not self.is_our_code(fname):
299
                continue
300
            ast = parser.ast2tuple(parser.suite(''.join(text)))
301
            if search(ast):
302
                badfiles.append(fname)
303
        if badfiles:
3376.2.7 by Martin Pool
Treat assert statements in our code as a hard error
304
            self.fail(
3376.2.1 by Martin Pool
Add test_no_asserts and cleanup test_source
305
                "these files contain an assert statement and should not:\n%s"
306
                % '\n'.join(badfiles))