1
# Copyright (C) 2005-2010 Canonical Ltd
1
# Copyright (C) 2005-2011 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
114
112
# Avoid the case when bzrlib is packaged in a zip file
115
113
if not os.path.isdir(source_dir):
116
raise TestSkipped('Cannot find bzrlib source directory. Expected %s'
115
'Cannot find bzrlib source directory. Expected %s'
118
117
return source_dir
120
119
def get_source_files(self, extensions=None):
154
153
yield fname, text
156
155
def is_our_code(self, fname):
157
"""Return true if it's a "real" part of bzrlib rather than external code"""
156
"""True if it's a "real" part of bzrlib rather than external code"""
158
157
if '/util/' in fname or '/plugins/' in fname:
195
194
copyright_re = re.compile('#\\s*copyright.*(?=\n)', re.I)
196
195
copyright_canonical_re = re.compile(
197
r'# Copyright \(C\) ' # Opening "# Copyright (C)"
198
r'(\d+)(, \d+)*' # Followed by a series of dates
199
r'.*Canonical Ltd' # And containing 'Canonical Ltd'
196
r'# Copyright \(C\) ' # Opening "# Copyright (C)"
197
r'(\d+)(, \d+)*' # followed by a series of dates
198
r'.*Canonical Ltd') # and containing 'Canonical Ltd'.
202
200
for fname, text in self.get_source_file_contents(
203
201
extensions=('.py', '.pyx')):
231
229
for fname, comment in incorrect:
232
230
help_text.append(fname)
233
help_text.append((' '*4) + comment)
231
help_text.append((' ' * 4) + comment)
235
233
self.fail('\n'.join(help_text))
269
267
" LICENSE_EXCEPTIONS in"
270
268
" bzrlib/tests/test_source.py",
271
269
"Or add the following text to the beginning:",
274
271
for fname in incorrect:
275
help_text.append((' '*4) + fname)
272
help_text.append((' ' * 4) + fname)
277
274
self.fail('\n'.join(help_text))
283
280
dict_[fname].append(line_no)
285
282
def _format_message(self, dict_, message):
286
files = ["%s: %s" % (f, ', '.join([str(i+1) for i in lines]))
283
files = ["%s: %s" % (f, ', '.join([str(i + 1) for i in lines]))
287
284
for f, lines in dict_.items()]
289
286
return message + '\n\n %s' % ('\n '.join(files))
291
288
def test_coding_style(self):
292
289
"""Check if bazaar code conforms to some coding style conventions.
294
Currently we assert that the following is not present:
297
* no newline at end of files
291
Generally we expect PEP8, but we do not generally strictly enforce
292
this, and there are existing files that do not comply. The 'pep8'
293
tool, available separately, will check for more cases.
299
Print how many files have
300
* trailing white space
301
* lines longer than 79 chars
295
This test only enforces conditions that are globally true at the
296
moment, and that should cause a patch to be rejected: spaces rather
297
than tabs, unix newlines, and a newline at the end of the file.
305
300
illegal_newlines = {}
307
301
no_newline_at_eof = []
308
302
for fname, text in self.get_source_file_contents(
309
303
extensions=('.py', '.pyx')):
316
310
self._push_file(tabs, fname, line_no)
317
311
if not line.endswith('\n') or line.endswith('\r\n'):
318
if line_no != last_line_no: # not no_newline_at_eof
312
if line_no != last_line_no: # not no_newline_at_eof
319
313
self._push_file(illegal_newlines, fname, line_no)
320
if line.endswith(' \n'):
321
self._push_file(trailing_ws, fname, line_no)
323
self._push_file(long_lines, fname, line_no)
324
314
if not lines[-1].endswith('\n'):
325
315
no_newline_at_eof.append(fname)
328
318
problems.append(self._format_message(tabs,
329
319
'Tab characters were found in the following source files.'
330
320
'\nThey should either be replaced by "\\t" or by spaces:'))
332
print ("There are %i lines with trailing white space in %i files."
333
% (sum([len(lines) for f, lines in trailing_ws.items()]),
335
321
if illegal_newlines:
336
322
problems.append(self._format_message(illegal_newlines,
337
323
'Non-unix newlines were found in the following source files:'))
339
print ("There are %i lines longer than 79 characters in %i files."
340
% (sum([len(lines) for f, lines in long_lines.items()]),
342
324
if no_newline_at_eof:
343
325
no_newline_at_eof.sort()
344
326
problems.append("The following source files doesn't have a "
382
364
def test_extension_exceptions(self):
383
365
"""Extension functions should propagate exceptions.
385
Either they should return an object, have an 'except' clause, or have a
386
"# cannot_raise" to indicate that we've audited them and defined them as not
367
Either they should return an object, have an 'except' clause, or
368
have a "# cannot_raise" to indicate that we've audited them and
369
defined them as not raising exceptions.
389
371
both_exc_and_no_exc = []
390
372
missing_except = []
392
374
r'(api\s+)?class (\w+).*:', re.MULTILINE)
393
375
extern_class_re = re.compile(r'## extern cdef class (\w+)',
395
except_re = re.compile(r'cdef\s+' # start with cdef
396
r'([\w *]*?)\s*' # this is the return signature
397
r'(\w+)\s*\(' # the function name
398
r'[^)]*\)\s*' # parameters
399
r'(.*)\s*:' # the except clause
400
r'\s*(#\s*cannot[- _]raise)?' # cannot raise comment
377
except_re = re.compile(
378
r'cdef\s+' # start with cdef
379
r'([\w *]*?)\s*' # this is the return signature
380
r'(\w+)\s*\(' # the function name
381
r'[^)]*\)\s*' # parameters
382
r'(.*)\s*:' # the except clause
383
r'\s*(#\s*cannot[- _]raise)?') # cannot raise comment
402
384
for fname, text in self.get_source_file_contents(
403
385
extensions=('.pyx',)):
404
386
known_classes = set([m[-1] for m in class_re.findall(text)])
417
399
missing_except.append((fname, func))
419
401
if both_exc_and_no_exc:
420
error_msg.append('The following functions had "cannot raise" comments'
421
' but did have an except clause set:')
403
'The following functions had "cannot raise" comments'
404
' but did have an except clause set:')
422
405
for fname, func in both_exc_and_no_exc:
423
406
error_msg.append('%s:%s' % (fname, func))
424
407
error_msg.extend(('', ''))
425
408
if missing_except:
426
error_msg.append('The following functions have fixed return types,'
427
' but no except clause.')
428
error_msg.append('Either add an except or append "# cannot_raise".')
410
'The following functions have fixed return types,'
411
' but no except clause.')
413
'Either add an except or append "# cannot_raise".')
429
414
for fname, func in missing_except:
430
415
error_msg.append('%s:%s' % (fname, func))
431
416
error_msg.extend(('', ''))
433
418
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))