1
# Copyright (C) 2005-2011 Canonical Ltd
1
# Copyright (C) 2005-2010 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
112
114
# Avoid the case when bzrlib is packaged in a zip file
113
115
if not os.path.isdir(source_dir):
115
'Cannot find bzrlib source directory. Expected %s'
116
raise TestSkipped('Cannot find bzrlib source directory. Expected %s'
117
118
return source_dir
119
120
def get_source_files(self, extensions=None):
153
154
yield fname, text
155
156
def is_our_code(self, fname):
156
"""True if it's a "real" part of bzrlib rather than external code"""
157
"""Return true if it's a "real" part of bzrlib rather than external code"""
157
158
if '/util/' in fname or '/plugins/' in fname:
194
195
copyright_re = re.compile('#\\s*copyright.*(?=\n)', re.I)
195
196
copyright_canonical_re = re.compile(
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'.
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'
200
202
for fname, text in self.get_source_file_contents(
201
203
extensions=('.py', '.pyx')):
229
231
for fname, comment in incorrect:
230
232
help_text.append(fname)
231
help_text.append((' ' * 4) + comment)
233
help_text.append((' '*4) + comment)
233
235
self.fail('\n'.join(help_text))
267
269
" LICENSE_EXCEPTIONS in"
268
270
" bzrlib/tests/test_source.py",
269
271
"Or add the following text to the beginning:",
271
274
for fname in incorrect:
272
help_text.append((' ' * 4) + fname)
275
help_text.append((' '*4) + fname)
274
277
self.fail('\n'.join(help_text))
280
283
dict_[fname].append(line_no)
282
285
def _format_message(self, dict_, message):
283
files = ["%s: %s" % (f, ', '.join([str(i + 1) for i in lines]))
286
files = ["%s: %s" % (f, ', '.join([str(i+1) for i in lines]))
284
287
for f, lines in dict_.items()]
286
289
return message + '\n\n %s' % ('\n '.join(files))
288
291
def test_coding_style(self):
289
292
"""Check if bazaar code conforms to some coding style conventions.
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.
294
Currently we assert that the following is not present:
297
* no newline at end of files
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.
299
Print how many files have
300
* trailing white space
301
* lines longer than 79 chars
300
305
illegal_newlines = {}
301
307
no_newline_at_eof = []
302
308
for fname, text in self.get_source_file_contents(
303
309
extensions=('.py', '.pyx')):
310
316
self._push_file(tabs, fname, line_no)
311
317
if not line.endswith('\n') or line.endswith('\r\n'):
312
if line_no != last_line_no: # not no_newline_at_eof
318
if line_no != last_line_no: # not no_newline_at_eof
313
319
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)
314
324
if not lines[-1].endswith('\n'):
315
325
no_newline_at_eof.append(fname)
318
328
problems.append(self._format_message(tabs,
319
329
'Tab characters were found in the following source files.'
320
330
'\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()]),
321
335
if illegal_newlines:
322
336
problems.append(self._format_message(illegal_newlines,
323
337
'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()]),
324
342
if no_newline_at_eof:
325
343
no_newline_at_eof.sort()
326
344
problems.append("The following source files doesn't have a "
364
382
def test_extension_exceptions(self):
365
383
"""Extension functions should propagate exceptions.
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.
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
371
389
both_exc_and_no_exc = []
372
390
missing_except = []
374
392
r'(api\s+)?class (\w+).*:', re.MULTILINE)
375
393
extern_class_re = re.compile(r'## extern cdef class (\w+)',
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
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
384
402
for fname, text in self.get_source_file_contents(
385
403
extensions=('.pyx',)):
386
404
known_classes = set([m[-1] for m in class_re.findall(text)])
399
417
missing_except.append((fname, func))
401
419
if both_exc_and_no_exc:
403
'The following functions had "cannot raise" comments'
404
' but did have an except clause set:')
420
error_msg.append('The following functions had "cannot raise" comments'
421
' but did have an except clause set:')
405
422
for fname, func in both_exc_and_no_exc:
406
423
error_msg.append('%s:%s' % (fname, func))
407
424
error_msg.extend(('', ''))
408
425
if missing_except:
410
'The following functions have fixed return types,'
411
' but no except clause.')
413
'Either add an except or append "# cannot_raise".')
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".')
414
429
for fname, func in missing_except:
415
430
error_msg.append('%s:%s' % (fname, func))
416
431
error_msg.extend(('', ''))
418
433
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))