~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_source.py

  • Committer: Robert Collins
  • Date: 2005-08-23 06:52:09 UTC
  • mto: (974.1.50) (1185.1.10) (1092.3.1)
  • mto: This revision was merged to the branch mainline in revision 1139.
  • Revision ID: robertc@robertcollins.net-20050823065209-81cd5962c401751b
move io redirection into each test case from the global runner

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
2
 
#   Authors: Robert Collins <robert.collins@canonical.com>
3
 
#            and others
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
26
 
import parser
27
 
import re
28
 
import symbol
29
 
import sys
30
 
import token
31
 
 
32
 
#import bzrlib specific imports here
33
 
from bzrlib import (
34
 
    osutils,
35
 
    )
36
 
import bzrlib.branch
37
 
from bzrlib.tests import (
38
 
    KnownFailure,
39
 
    TestCase,
40
 
    TestSkipped,
41
 
    )
42
 
 
43
 
 
44
 
# Files which are listed here will be skipped when testing for Copyright (or
45
 
# GPL) statements.
46
 
COPYRIGHT_EXCEPTIONS = ['bzrlib/lsprof.py']
47
 
 
48
 
LICENSE_EXCEPTIONS = ['bzrlib/lsprof.py']
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.
52
 
 
53
 
 
54
 
class TestSourceHelper(TestCase):
55
 
 
56
 
    def source_file_name(self, package):
57
 
        """Return the path of the .py file for package."""
58
 
        if getattr(sys, "frozen", None) is not None:
59
 
            raise TestSkipped("can't test sources in frozen distributions.")
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):
68
 
 
69
 
    def find_occurences(self, rule, filename):
70
 
        """Find the number of occurences of rule in a file."""
71
 
        occurences = 0
72
 
        source = file(filename, 'r')
73
 
        for line in source:
74
 
            if line.find(rule) > -1:
75
 
                occurences += 1
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()',
81
 
                                          self.source_file_name(bzrlib.branch))
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.
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)
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',
92
 
                                          self.source_file_name(bzrlib.branch))
93
 
        # Do not even think of increasing this number. If you think you need to
94
 
        # increase it, then you almost certainly are doing something wrong as
95
 
        # the relationship from working_tree to branch is one way.
96
 
        # As of 20070809, there are no longer any mentions at all.
97
 
        self.assertEqual(0, occurences)
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):
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
 
        """
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):
126
 
            for d in dirs:
127
 
                if d.endswith('.tmp'):
128
 
                    dirs.remove(d)
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
 
 
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
 
 
150
 
    def is_copyright_exception(self, fname):
151
 
        """Certain files are allowed to be different"""
152
 
        if not self.is_our_code(fname):
153
 
            # We don't ask that external utilities or plugins be
154
 
            # (C) Canonical Ltd
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"""
163
 
        if not self.is_our_code(fname):
164
 
            return True
165
 
        for exc in LICENSE_EXCEPTIONS:
166
 
            if fname.endswith(exc):
167
 
                return True
168
 
        return False
169
 
 
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
 
 
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
 
 
184
 
        copyright_re = re.compile('#\\s*copyright.*(?=\n)', re.I)
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():
192
 
            if self.is_copyright_exception(fname):
193
 
                continue
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(),)))
199
 
                else:
200
 
                    incorrect.append((fname, 'no copyright line found\n'))
201
 
            else:
202
 
                if 'by Canonical' in match.group():
203
 
                    incorrect.append((fname,
204
 
                        'should not have: "by Canonical": %s'
205
 
                        % (match.group(),)))
206
 
 
207
 
        if incorrect:
208
 
            help_text = ["Some files have missing or incorrect copyright"
209
 
                         " statements.",
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)"
216
 
                         " 2007 Canonical Ltd' to these 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():
247
 
            if self.is_license_exception(fname):
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',
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:",
259
 
                         gpl_txt
260
 
                        ]
261
 
            for fname in incorrect:
262
 
                help_text.append((' '*4) + fname)
263
 
 
264
 
            self.fail('\n'.join(help_text))
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():
271
 
            if not self.is_our_code(fname):
272
 
                continue
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)))
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:
306
 
            self.fail(
307
 
                "these files contain an assert statement and should not:\n%s"
308
 
                % '\n'.join(badfiles))