# Unix SMB/CIFS implementation. # Copyright (C) Jelmer Vernooij 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 . # """Source level Python tests.""" import io import errno import os import re import warnings 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 = io.open(fname, mode='r', encoding='utf-8') except IOError as 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 if fname.endswith("python/samba/tests/krb5/kcrypto.py"): # Imported from MIT testing repo continue if fname.endswith("python/samba/tests/krb5/rfc4120_pyasn1_generated.py"): # Autogenerated 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_txts = [ """ # 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 . """, """ # 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 . """, ] gpl_re = f'(?:{"|".join(map(re.escape, gpl_txts))})' gpl_re = re.compile(gpl_re, 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 fname.endswith("/python/samba/subunit/run.py"): # Imported from subunit/testtools, which are dual # Apache2/BSD-3. continue if fname.endswith("python/samba/tests/krb5/kcrypto.py"): # Imported from MIT testing repo continue if fname.endswith("python/samba/tests/krb5/rfc4120_pyasn1_generated.py"): # Autogenerated continue if not gpl_re.search(text): incorrect.append(fname) if incorrect: help_text = ['Some files have missing or incomplete GPL statement', gpl_txts[-1]] 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) 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 & 0o111) 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:'))