14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""These tests are tests about the source code of bzrlib itself.
17
"""These tests are tests about the source code of breezy itself.
19
19
They are useful for testing code quality, checking coverage metric etc.
39
40
# Files which are listed here will be skipped when testing for Copyright (or
41
42
COPYRIGHT_EXCEPTIONS = [
42
'bzrlib/_bencode_py.py',
43
'bzrlib/doc_generate/conf.py',
43
'breezy/_bencode_py.py',
44
'breezy/doc_generate/conf.py',
47
48
LICENSE_EXCEPTIONS = [
48
'bzrlib/_bencode_py.py',
49
'bzrlib/doc_generate/conf.py',
49
'breezy/_bencode_py.py',
50
'breezy/doc_generate/conf.py',
52
# Technically, 'bzrlib/lsprof.py' should be 'bzrlib/util/lsprof.py',
53
# (we do not check bzrlib/util/, since that is code bundled from elsewhere)
53
# Technically, 'breezy/lsprof.py' should be 'breezy/util/lsprof.py',
54
# (we do not check breezy/util/, since that is code bundled from elsewhere)
54
55
# but for compatibility with previous releases, we don't want to move it.
56
57
# sphinx_conf is semi-autogenerated.
74
75
def find_occurences(self, rule, filename):
75
76
"""Find the number of occurences of rule in a file."""
77
source = file(filename, 'r')
78
source = open(filename, 'r')
78
79
for line in source:
79
80
if line.find(rule) > -1:
83
84
def test_branch_working_tree(self):
84
85
"""Test that the number of uses of working_tree in branch is stable."""
85
86
occurences = self.find_occurences('self.working_tree()',
86
self.source_file_name(bzrlib.branch))
87
self.source_file_name(breezy.branch))
87
88
# do not even think of increasing this number. If you think you need to
88
89
# increase it, then you almost certainly are doing something wrong as
89
90
# the relationship from working_tree to branch is one way.
90
91
# Note that this is an exact equality so that when the number drops,
91
#it is not given a buffer but rather has this test updated immediately.
92
# it is not given a buffer but rather has this test updated immediately.
92
93
self.assertEqual(0, occurences)
94
95
def test_branch_WorkingTree(self):
95
96
"""Test that the number of uses of working_tree in branch is stable."""
96
97
occurences = self.find_occurences('WorkingTree',
97
self.source_file_name(bzrlib.branch))
98
self.source_file_name(breezy.branch))
98
99
# Do not even think of increasing this number. If you think you need to
99
100
# increase it, then you almost certainly are doing something wrong as
100
101
# the relationship from working_tree to branch is one way.
105
106
class TestSource(TestSourceHelper):
107
def get_bzrlib_dir(self):
108
"""Get the path to the root of bzrlib"""
109
source = self.source_file_name(bzrlib)
108
def get_breezy_dir(self):
109
"""Get the path to the root of breezy"""
110
source = self.source_file_name(breezy)
110
111
source_dir = os.path.dirname(source)
112
# Avoid the case when bzrlib is packaged in a zip file
113
# Avoid the case when breezy is packaged in a zip file
113
114
if not os.path.isdir(source_dir):
114
115
raise TestSkipped(
115
'Cannot find bzrlib source directory. Expected %s'
116
'Cannot find breezy source directory. Expected %s'
117
118
return source_dir
119
120
def get_source_files(self, extensions=None):
120
"""Yield all source files for bzr and bzrlib
121
"""Yield all source files for bzr and breezy
122
123
:param our_files_only: If true, exclude files from included libraries
125
bzrlib_dir = self.get_bzrlib_dir()
126
breezy_dir = self.get_breezy_dir()
126
127
if extensions is None:
127
128
extensions = ('.py',)
129
130
# This is the front-end 'bzr' script
130
bzr_path = self.get_bzr_path()
131
bzr_path = self.get_brz_path()
133
for root, dirs, files in os.walk(bzrlib_dir):
134
for root, dirs, files in os.walk(breezy_dir):
135
136
if d.endswith('.tmp'):
146
147
def get_source_file_contents(self, extensions=None):
147
148
for fname in self.get_source_files(extensions=extensions):
148
f = open(fname, 'rb')
149
with open(fname, 'r') as f:
150
yield fname, f.read()
155
152
def is_our_code(self, fname):
156
"""True if it's a "real" part of bzrlib rather than external code"""
153
"""True if it's a "real" part of breezy rather than external code"""
157
154
if '/util/' in fname or '/plugins/' in fname:
162
159
def is_copyright_exception(self, fname):
163
160
"""Certain files are allowed to be different"""
164
161
if not self.is_our_code(fname):
165
# We don't ask that external utilities or plugins be
168
163
for exc in COPYRIGHT_EXCEPTIONS:
169
164
if fname.endswith(exc):
194
189
copyright_re = re.compile('#\\s*copyright.*(?=\n)', re.I)
195
copyright_canonical_re = re.compile(
190
copyright_statement_re = re.compile(
196
191
r'# Copyright \(C\) ' # Opening "# Copyright (C)"
197
r'(\d+)(, \d+)*' # followed by a series of dates
198
r'.*Canonical Ltd') # and containing 'Canonical Ltd'.
192
r'(\d+?)((, |-)\d+)*' # followed by a series of dates
193
r' [^ ]*') # and then whoever.
200
195
for fname, text in self.get_source_file_contents(
201
196
extensions=('.py', '.pyx')):
202
197
if self.is_copyright_exception(fname):
204
match = copyright_canonical_re.search(text)
199
match = copyright_statement_re.search(text)
206
201
match = copyright_re.search(text)
221
216
"Please either add them to the list of"
222
217
" COPYRIGHT_EXCEPTIONS in"
223
" bzrlib/tests/test_source.py",
218
" breezy/tests/test_source.py",
224
219
# this is broken to prevent a false match
225
220
"or add '# Copyright (C)"
226
" 2007 Canonical Ltd' to these files:",
221
" 2007 Bazaar hackers' to these files:",
229
224
for fname, comment in incorrect:
230
225
help_text.append(fname)
231
226
help_text.append((' ' * 4) + comment)
266
261
"Please either add them to the list of"
267
262
" LICENSE_EXCEPTIONS in"
268
" bzrlib/tests/test_source.py",
263
" breezy/tests/test_source.py",
269
264
"Or add the following text to the beginning:",
271
266
for fname in incorrect:
280
275
dict_[fname].append(line_no)
282
277
def _format_message(self, dict_, message):
283
files = ["%s: %s" % (f, ', '.join([str(i + 1) for i in lines]))
284
for f, lines in dict_.items()]
278
files = sorted(["%s: %s" % (f, ', '.join([str(i + 1) for i in lines]))
279
for f, lines in dict_.items()])
286
280
return message + '\n\n %s' % ('\n '.join(files))
288
282
def test_coding_style(self):
318
312
problems.append(self._format_message(tabs,
319
'Tab characters were found in the following source files.'
320
'\nThey should either be replaced by "\\t" or by spaces:'))
313
'Tab characters were found in the following source files.'
314
'\nThey should either be replaced by "\\t" or by spaces:'))
321
315
if illegal_newlines:
322
316
problems.append(self._format_message(illegal_newlines,
323
'Non-unix newlines were found in the following source files:'))
317
'Non-unix newlines were found in the following source files:'))
324
318
if no_newline_at_eof:
325
319
no_newline_at_eof.sort()
326
320
problems.append("The following source files doesn't have a "
327
"newline at the end:"
329
% ('\n '.join(no_newline_at_eof)))
321
"newline at the end:"
323
% ('\n '.join(no_newline_at_eof)))
331
325
self.fail('\n\n'.join(problems))
327
def test_flake8(self):
328
self.requireFeature(features.flake8)
329
# Older versions of flake8 don't support the 'paths'
331
new_path = list(sys.path)
333
0, os.path.join(os.path.dirname(__file__), '..', '..', 'tools'))
334
self.overrideAttr(sys, 'path', new_path)
336
from flake8.main.application import Application
337
from flake8.formatting.base import BaseFormatter
339
app.config = u'setup.cfg'
342
class Formatter(BaseFormatter):
351
app.file_checker_manager.report()
353
def handle(self, error):
354
self.errors.append(error)
358
except argparse.ArgumentError as e:
359
self.skipTest('broken flake8: %r' % e)
360
app.formatter = Formatter()
363
self.assertEqual(app.formatter.errors, [])
333
365
def test_no_asserts(self):
334
366
"""bzr shouldn't use the 'assert' statement."""
335
367
# assert causes too much variation between -O and not, and tends to
371
404
both_exc_and_no_exc = []
372
405
missing_except = []
406
common_classes = ('StaticTuple',)
373
407
class_re = re.compile(r'^(cdef\s+)?(public\s+)?'
374
408
r'(api\s+)?class (\w+).*:', re.MULTILINE)
375
extern_class_re = re.compile(r'## extern cdef class (\w+)',
377
409
except_re = re.compile(
378
410
r'cdef\s+' # start with cdef
379
411
r'([\w *]*?)\s*' # this is the return signature
383
415
r'\s*(#\s*cannot[- _]raise)?') # cannot raise comment
384
416
for fname, text in self.get_source_file_contents(
385
417
extensions=('.pyx',)):
386
known_classes = set([m[-1] for m in class_re.findall(text)])
387
known_classes.update(extern_class_re.findall(text))
418
known_classes = {m[-1] for m in class_re.findall(text)}
419
known_classes.update(common_classes)
388
420
cdefs = except_re.findall(text)
389
421
for sig, func, exc_clause, no_exc_comment in cdefs:
390
422
if sig.startswith('api '):
416
448
error_msg.extend(('', ''))
418
450
self.fail('\n'.join(error_msg))
420
def test_feature_absolute_import(self):
421
"""Using absolute imports means avoiding unnecesary stat and
424
Make sure that all non-test files have absolute imports enabled.
426
missing_absolute_import = []
427
for fname, text in self.get_source_file_contents(
428
extensions=('.py', )):
429
if "/tests/" in fname or "test_" in fname:
430
# We don't really care about tests
432
if not "from __future__ import absolute_import" in text:
433
missing_absolute_import.append(fname)
435
if missing_absolute_import:
437
'The following files do not have absolute_import enabled:\n'
438
'\n' + '\n'.join(missing_absolute_import))