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