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