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