diff options
Diffstat (limited to 'python/samba/tests/source.py')
-rw-r--r-- | python/samba/tests/source.py | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/python/samba/tests/source.py b/python/samba/tests/source.py new file mode 100644 index 0000000000..2612ae68cf --- /dev/null +++ b/python/samba/tests/source.py @@ -0,0 +1,264 @@ +# Unix SMB/CIFS implementation. +# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2011 +# +# Loosely based on bzrlib's test_source.py +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +"""Source level Python tests.""" + +import errno +import os +import re +import warnings + +import samba +samba.ensure_external_module("pep8", "pep8") +import pep8 + +from samba.tests import ( + TestCase, + ) + + +def get_python_source_files(): + """Iterate over all Python source files.""" + library_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "samba")) + assert os.path.isdir(library_dir), library_dir + + for root, dirs, files in os.walk(library_dir): + for f in files: + if f.endswith(".py"): + yield os.path.abspath(os.path.join(root, f)) + + bindir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "bin")) + assert os.path.isdir(bindir), bindir + for f in os.listdir(bindir): + p = os.path.abspath(os.path.join(bindir, f)) + if not os.path.islink(p): + continue + target = os.readlink(p) + if os.path.dirname(target).endswith("scripting/bin"): + yield p + wafsambadir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "buildtools", "wafsamba")) + assert os.path.isdir(wafsambadir), wafsambadir + for root, dirs, files in os.walk(wafsambadir): + for f in files: + if f.endswith(".py"): + yield os.path.abspath(os.path.join(root, f)) + + +def get_source_file_contents(): + """Iterate over the contents of all python files.""" + for fname in get_python_source_files(): + try: + f = open(fname, 'rb') + except IOError, e: + if e.errno == errno.ENOENT: + warnings.warn("source file %s broken link?" % fname) + continue + else: + raise + try: + text = f.read() + finally: + f.close() + yield fname, text + + +class TestSource(TestCase): + + def test_copyright(self): + """Test that all Python files have a valid copyright statement.""" + incorrect = [] + + copyright_re = re.compile('#\\s*copyright.*(?=\n)', re.I) + + for fname, text in get_source_file_contents(): + if fname.endswith("ms_schema.py"): + # FIXME: Not sure who holds copyright on ms_schema.py + continue + if "wafsamba" in fname: + # FIXME: No copyright headers in wafsamba + continue + match = copyright_re.search(text) + if not match: + incorrect.append((fname, 'no copyright line found\n')) + + if incorrect: + help_text = ["Some files have missing or incorrect copyright" + " statements.", + "", + ] + for fname, comment in incorrect: + help_text.append(fname) + help_text.append((' ' * 4) + comment) + + self.fail('\n'.join(help_text)) + + def test_gpl(self): + """Test that all .py files have a GPL disclaimer.""" + incorrect = [] + + gpl_txt = """ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" + gpl_re = re.compile(re.escape(gpl_txt), re.MULTILINE) + + for fname, text in get_source_file_contents(): + if "wafsamba" in fname: + # FIXME: License to wafsamba hasn't been clarified yet + continue + if not gpl_re.search(text): + incorrect.append(fname) + + if incorrect: + help_text = ['Some files have missing or incomplete GPL statement', + gpl_txt] + for fname in incorrect: + help_text.append((' ' * 4) + fname) + + self.fail('\n'.join(help_text)) + + def _push_file(self, dict_, fname, line_no): + if fname not in dict_: + dict_[fname] = [line_no] + else: + dict_[fname].append(line_no) + + def _format_message(self, dict_, message): + files = ["%s: %s" % (f, ', '.join([str(i + 1) for i in lines])) + for f, lines in dict_.items()] + files.sort() + return message + '\n\n %s' % ('\n '.join(files)) + + def _iter_source_files_lines(self): + for fname, text in get_source_file_contents(): + lines = text.splitlines(True) + last_line_no = len(lines) - 1 + for line_no, line in enumerate(lines): + yield fname, line_no, line + + def test_no_tabs(self): + """Check that there are no tabs in Python files.""" + tabs = {} + for fname, line_no, line in self._iter_source_files_lines(): + if '\t' in line: + self._push_file(tabs, fname, line_no) + if tabs: + self.fail(self._format_message(tabs, + 'Tab characters were found in the following source files.' + '\nThey should either be replaced by "\\t" or by spaces:')) + + def test_unix_newlines(self): + """Check for unix new lines.""" + illegal_newlines = {} + for fname, line_no, line in self._iter_source_files_lines(): + if not line.endswith('\n') or line.endswith('\r\n'): + self._push_file(illegal_newlines, fname, line_no) + if illegal_newlines: + self.fail(self._format_message(illegal_newlines, + 'Non-unix newlines were found in the following source files:')) + + def test_trailing_whitespace(self): + """Check that there is not trailing whitespace in Python files.""" + trailing_whitespace = {} + for fname, line_no, line in self._iter_source_files_lines(): + if line.rstrip("\n").endswith(" "): + self._push_file(trailing_whitespace, fname, line_no) + if trailing_whitespace: + self.fail(self._format_message(trailing_whitespace, + 'Trailing whitespace was found in the following source files.')) + + def test_shebang_lines(self): + """Check that files with shebang lines and only those are executable.""" + files_with_shebang = {} + files_without_shebang= {} + for fname, line_no, line in self._iter_source_files_lines(): + if line_no >= 1: + continue + executable = (os.stat(fname).st_mode & 0111) + has_shebang = line.startswith("#!") + if has_shebang and not executable: + self._push_file(files_with_shebang, fname, line_no) + if not has_shebang and executable: + self._push_file(files_without_shebang, fname, line_no) + if files_with_shebang: + self.fail(self._format_message(files_with_shebang, + 'Files with shebang line that are not executable:')) + if files_without_shebang: + self.fail(self._format_message(files_without_shebang, + 'Files without shebang line that are executable:')) + + pep8_ignore = [ + 'E401', # multiple imports on one line + 'E501', # line too long + 'E251', # no spaces around keyword / parameter equals + 'E201', # whitespace after '[' + 'E202', # whitespace before ')' + 'E302', # expected 2 blank lines, found 1 + 'E231', # missing whitespace after ',' + 'E225', # missing whitespace around operator + 'E111', # indentation is not a multiple of four + 'E261', # at least two spaces before inline comment + 'E702', # multiple statements on one line (semicolon) + 'E221', # multiple spaces before operator + 'E303', # too many blank lines (2) + 'E203', # whitespace before ':' + 'E222', # multiple spaces after operator + 'E301', # expected 1 blank line, found 0 + 'E211', # whitespace before '(' + 'E701', # multiple statements on one line (colon) + ] + + def test_pep8(self): + pep8.process_options() + pep8.options.repeat = True + pep8_errors = [] + pep8_warnings = [] + for fname, text in get_source_file_contents(): + def report_error(line_number, offset, text, check): + code = text[:4] + if code in self.pep8_ignore: + code = 'W' + code[1:] + text = code + text[4:] + print "%s:%s: %s" % (fname, line_number, text) + summary = (fname, line_number, offset, text, check) + if code[0] == 'W': + pep8_warnings.append(summary) + else: + pep8_errors.append(summary) + lines = text.splitlines(True) + checker = pep8.Checker(fname, lines) + checker.report_error = report_error + checker.check_all() + if len(pep8_errors) > 0: + d = {} + for (fname, line_no, offset, text, check) in pep8_errors: + d.setdefault(fname, []).append(line_no - 1) + self.fail(self._format_message(d, + 'There were %d PEP8 errors:' % len(pep8_errors))) + |