2612ae68cf5b4a7aac885a6ea29b625a75d078b2
[bbaumbach/samba-autobuild/.git] / source4 / scripting / python / samba / tests / source.py
1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2011
3 #
4 # Loosely based on bzrlib's test_source.py
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 """Source level Python tests."""
21
22 import errno
23 import os
24 import re
25 import warnings
26
27 import samba
28 samba.ensure_external_module("pep8", "pep8")
29 import pep8
30
31 from samba.tests import (
32     TestCase,
33     )
34
35
36 def get_python_source_files():
37     """Iterate over all Python source files."""
38     library_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "samba"))
39     assert os.path.isdir(library_dir), library_dir
40
41     for root, dirs, files in os.walk(library_dir):
42         for f in files:
43             if f.endswith(".py"):
44                 yield os.path.abspath(os.path.join(root, f))
45
46     bindir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "bin"))
47     assert os.path.isdir(bindir), bindir
48     for f in os.listdir(bindir):
49         p = os.path.abspath(os.path.join(bindir, f))
50         if not os.path.islink(p):
51             continue
52         target = os.readlink(p)
53         if os.path.dirname(target).endswith("scripting/bin"):
54             yield p
55     wafsambadir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "buildtools", "wafsamba"))
56     assert os.path.isdir(wafsambadir), wafsambadir
57     for root, dirs, files in os.walk(wafsambadir):
58         for f in files:
59             if f.endswith(".py"):
60                 yield os.path.abspath(os.path.join(root, f))
61
62
63 def get_source_file_contents():
64     """Iterate over the contents of all python files."""
65     for fname in get_python_source_files():
66         try:
67             f = open(fname, 'rb')
68         except IOError, e:
69             if e.errno == errno.ENOENT:
70                 warnings.warn("source file %s broken link?" % fname)
71                 continue
72             else:
73                 raise
74         try:
75             text = f.read()
76         finally:
77             f.close()
78         yield fname, text
79
80
81 class TestSource(TestCase):
82
83     def test_copyright(self):
84         """Test that all Python files have a valid copyright statement."""
85         incorrect = []
86
87         copyright_re = re.compile('#\\s*copyright.*(?=\n)', re.I)
88
89         for fname, text in get_source_file_contents():
90             if fname.endswith("ms_schema.py"):
91                 # FIXME: Not sure who holds copyright on ms_schema.py
92                 continue
93             if "wafsamba" in fname:
94                 # FIXME: No copyright headers in wafsamba
95                 continue
96             match = copyright_re.search(text)
97             if not match:
98                 incorrect.append((fname, 'no copyright line found\n'))
99
100         if incorrect:
101             help_text = ["Some files have missing or incorrect copyright"
102                          " statements.",
103                          "",
104                         ]
105             for fname, comment in incorrect:
106                 help_text.append(fname)
107                 help_text.append((' ' * 4) + comment)
108
109             self.fail('\n'.join(help_text))
110
111     def test_gpl(self):
112         """Test that all .py files have a GPL disclaimer."""
113         incorrect = []
114
115         gpl_txt = """
116 # This program is free software; you can redistribute it and/or modify
117 # it under the terms of the GNU General Public License as published by
118 # the Free Software Foundation; either version 3 of the License, or
119 # (at your option) any later version.
120 #
121 # This program is distributed in the hope that it will be useful,
122 # but WITHOUT ANY WARRANTY; without even the implied warranty of
123 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
124 # GNU General Public License for more details.
125 #
126 # You should have received a copy of the GNU General Public License
127 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
128 """
129         gpl_re = re.compile(re.escape(gpl_txt), re.MULTILINE)
130
131         for fname, text in get_source_file_contents():
132             if "wafsamba" in fname:
133                 # FIXME: License to wafsamba hasn't been clarified yet
134                 continue
135             if not gpl_re.search(text):
136                 incorrect.append(fname)
137
138         if incorrect:
139             help_text = ['Some files have missing or incomplete GPL statement',
140                          gpl_txt]
141             for fname in incorrect:
142                 help_text.append((' ' * 4) + fname)
143
144             self.fail('\n'.join(help_text))
145
146     def _push_file(self, dict_, fname, line_no):
147         if fname not in dict_:
148             dict_[fname] = [line_no]
149         else:
150             dict_[fname].append(line_no)
151
152     def _format_message(self, dict_, message):
153         files = ["%s: %s" % (f, ', '.join([str(i + 1) for i in lines]))
154                 for f, lines in dict_.items()]
155         files.sort()
156         return message + '\n\n    %s' % ('\n    '.join(files))
157
158     def _iter_source_files_lines(self):
159         for fname, text in get_source_file_contents():
160             lines = text.splitlines(True)
161             last_line_no = len(lines) - 1
162             for line_no, line in enumerate(lines):
163                 yield fname, line_no, line
164
165     def test_no_tabs(self):
166         """Check that there are no tabs in Python files."""
167         tabs = {}
168         for fname, line_no, line in self._iter_source_files_lines():
169             if '\t' in line:
170                 self._push_file(tabs, fname, line_no)
171         if tabs:
172             self.fail(self._format_message(tabs,
173                 'Tab characters were found in the following source files.'
174                 '\nThey should either be replaced by "\\t" or by spaces:'))
175
176     def test_unix_newlines(self):
177         """Check for unix new lines."""
178         illegal_newlines = {}
179         for fname, line_no, line in self._iter_source_files_lines():
180             if not line.endswith('\n') or line.endswith('\r\n'):
181                 self._push_file(illegal_newlines, fname, line_no)
182         if illegal_newlines:
183             self.fail(self._format_message(illegal_newlines,
184                 'Non-unix newlines were found in the following source files:'))
185
186     def test_trailing_whitespace(self):
187         """Check that there is not trailing whitespace in Python files."""
188         trailing_whitespace = {}
189         for fname, line_no, line in self._iter_source_files_lines():
190             if line.rstrip("\n").endswith(" "):
191                 self._push_file(trailing_whitespace, fname, line_no)
192         if trailing_whitespace:
193             self.fail(self._format_message(trailing_whitespace,
194                 'Trailing whitespace was found in the following source files.'))
195
196     def test_shebang_lines(self):
197         """Check that files with shebang lines and only those are executable."""
198         files_with_shebang = {}
199         files_without_shebang= {}
200         for fname, line_no, line in self._iter_source_files_lines():
201             if line_no >= 1:
202                 continue
203             executable = (os.stat(fname).st_mode & 0111)
204             has_shebang = line.startswith("#!")
205             if has_shebang and not executable:
206                 self._push_file(files_with_shebang, fname, line_no)
207             if not has_shebang and executable:
208                 self._push_file(files_without_shebang, fname, line_no)
209         if files_with_shebang:
210             self.fail(self._format_message(files_with_shebang,
211                 'Files with shebang line that are not executable:'))
212         if files_without_shebang:
213             self.fail(self._format_message(files_without_shebang,
214                 'Files without shebang line that are executable:'))
215
216     pep8_ignore = [
217         'E401',      # multiple imports on one line
218         'E501',      # line too long
219         'E251',      # no spaces around keyword / parameter equals
220         'E201',      # whitespace after '['
221         'E202',      # whitespace before ')'
222         'E302',      # expected 2 blank lines, found 1
223         'E231',      # missing whitespace after ','
224         'E225',      # missing whitespace around operator
225         'E111',      # indentation is not a multiple of four
226         'E261',      # at least two spaces before inline comment
227         'E702',      # multiple statements on one line (semicolon)
228         'E221',      # multiple spaces before operator
229         'E303',      # too many blank lines (2)
230         'E203',      # whitespace before ':'
231         'E222',      # multiple spaces after operator
232         'E301',      # expected 1 blank line, found 0
233         'E211',      # whitespace before '('
234         'E701',      # multiple statements on one line (colon)
235         ]
236
237     def test_pep8(self):
238         pep8.process_options()
239         pep8.options.repeat = True
240         pep8_errors = []
241         pep8_warnings = []
242         for fname, text in get_source_file_contents():
243             def report_error(line_number, offset, text, check):
244                 code = text[:4]
245                 if code in self.pep8_ignore:
246                     code = 'W' + code[1:]
247                 text = code + text[4:]
248                 print "%s:%s: %s" % (fname, line_number, text)
249                 summary = (fname, line_number, offset, text, check)
250                 if code[0] == 'W':
251                     pep8_warnings.append(summary)
252                 else:
253                     pep8_errors.append(summary)
254             lines = text.splitlines(True)
255             checker = pep8.Checker(fname, lines)
256             checker.report_error = report_error
257             checker.check_all()
258         if len(pep8_errors) > 0:
259             d = {}
260             for (fname, line_no, offset, text, check) in pep8_errors:
261                 d.setdefault(fname, []).append(line_no - 1)
262             self.fail(self._format_message(d,
263                 'There were %d PEP8 errors:' % len(pep8_errors)))
264