tests:docs: remove explicit exceptions for parametric options
[metze/samba/wip.git] / python / samba / tests / docs.py
1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2012
3 #
4 # Tests for documentation.
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 """Tests for presence of documentation."""
21
22 import samba
23 import samba.tests
24
25 import os
26 import re
27 import subprocess
28 import xml.etree.ElementTree as ET
29
30 class TestCase(samba.tests.TestCaseInTempDir):
31
32     def _format_message(self, parameters, message):
33         parameters = list(parameters)
34         parameters = map(str, parameters)
35         parameters.sort()
36         return message + '\n\n    %s' % ('\n    '.join(parameters))
37
38
39 def get_documented_parameters(sourcedir):
40     path = os.path.join(sourcedir, "bin", "default", "docs-xml", "smbdotconf")
41     if not os.path.exists(os.path.join(path, "parameters.all.xml")):
42         raise Exception("Unable to find parameters.all.xml")
43     try:
44         p = open(os.path.join(path, "parameters.all.xml"), 'r')
45     except IOError, e:
46         raise Exception("Error opening parameters file")
47     out = p.read()
48
49     root = ET.fromstring(out)
50     for parameter in root:
51         name = parameter.attrib.get('name')
52         if parameter.attrib.get('removed') == "1":
53            continue
54         yield name
55         syn = parameter.findall('synonym')
56         if syn is not None:
57             for sy in syn:
58                 yield sy.text
59     p.close()
60
61
62 def get_documented_tuples(sourcedir, omit_no_default=True):
63     path = os.path.join(sourcedir, "bin", "default", "docs-xml", "smbdotconf")
64     if not os.path.exists(os.path.join(path, "parameters.all.xml")):
65         raise Exception("Unable to find parameters.all.xml")
66     try:
67         p = open(os.path.join(path, "parameters.all.xml"), 'r')
68     except IOError, e:
69         raise Exception("Error opening parameters file")
70     out = p.read()
71
72     root = ET.fromstring(out)
73     for parameter in root:
74         name = parameter.attrib.get("name")
75         param_type = parameter.attrib.get("type")
76         if parameter.attrib.get('removed') == "1":
77            continue
78         values = parameter.findall("value")
79         defaults = []
80         for value in values:
81             if value.attrib.get("type") == "default":
82                 defaults.append(value)
83
84         default_text = None
85         if len(defaults) == 0:
86             if omit_no_default:
87                 continue
88         elif len(defaults) > 1:
89             raise Exception("More than one default found for parameter %s" % name)
90         else:
91             default_text = defaults[0].text
92
93         if default_text is None:
94             default_text = ""
95         context = parameter.attrib.get("context")
96         yield name, default_text, context, param_type
97     p.close()
98
99 class SmbDotConfTests(TestCase):
100
101     # defines the cases where the defaults may differ from the documentation
102     special_cases = set(['log level', 'path',
103                          'panic action', 'homedir map', 'NIS homedir',
104                          'server string', 'netbios name', 'socket options', 'use mmap',
105                          'ctdbd socket', 'printing', 'printcap name', 'queueresume command',
106                          'queuepause command','lpresume command', 'lppause command',
107                          'lprm command', 'lpq command', 'print command', 'template homedir',
108                          'max open files',
109                          'include system krb5 conf', 'rpc server dynamic port range',
110                          'mit kdc command'])
111
112     def setUp(self):
113         super(SmbDotConfTests, self).setUp()
114         # create a minimal smb.conf file for testparm
115         self.smbconf = os.path.join(self.tempdir, "paramtestsmb.conf")
116         f = open(self.smbconf, 'w')
117         try:
118             f.write("""
119 [test]
120    path = /
121 """)
122         finally:
123             f.close()
124
125         self.blankconf = os.path.join(self.tempdir, "emptytestsmb.conf")
126         f = open(self.blankconf, 'w')
127         try:
128             f.write("")
129         finally:
130             f.close()
131
132         self.topdir = os.path.abspath(samba.source_tree_topdir())
133
134         try:
135             self.documented = set(get_documented_parameters(self.topdir))
136         except:
137             self.fail("Unable to load documented parameters")
138
139         try:
140             self.defaults = set(get_documented_tuples(self.topdir))
141         except:
142             self.fail("Unable to load parameters")
143
144         try:
145             self.defaults_all = set(get_documented_tuples(self.topdir, False))
146         except:
147             self.fail("Unable to load parameters")
148
149
150     def tearDown(self):
151         super(SmbDotConfTests, self).tearDown()
152         os.unlink(self.smbconf)
153         os.unlink(self.blankconf)
154
155     def test_default_s3(self):
156         self._test_default(['bin/testparm'])
157         self._set_defaults(['bin/testparm'])
158
159         # registry shares appears to need sudo
160         self._set_arbitrary(['bin/testparm'],
161             exceptions = ['client lanman auth',
162                           'client plaintext auth',
163                           'registry shares',
164                           'smb ports',
165                           'rpc server dynamic port range',
166                           'name resolve order'])
167         self._test_empty(['bin/testparm'])
168
169     def test_default_s4(self):
170         self._test_default(['bin/samba-tool', 'testparm'])
171         self._set_defaults(['bin/samba-tool', 'testparm'])
172         self._set_arbitrary(['bin/samba-tool', 'testparm'],
173             exceptions = ['smb ports',
174                           'rpc server dynamic port range',
175                           'name resolve order'])
176         self._test_empty(['bin/samba-tool', 'testparm'])
177
178     def _test_default(self, program):
179         failset = set()
180         count = 0
181
182         for tuples in self.defaults:
183             param, default, context, param_type = tuples
184
185             if param in self.special_cases:
186                 continue
187             # bad, bad parametric options - we don't have their default values
188             if ':' in param:
189                 continue
190             section = None
191             if context == "G":
192                 section = "global"
193             elif context == "S":
194                 section = "test"
195             else:
196                  self.fail("%s has no valid context" % param)
197             p = subprocess.Popen(program + ["-s", self.smbconf,
198                     "--section-name", section, "--parameter-name", param],
199                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
200             if p[0].upper().strip() != default.upper():
201                 if not (p[0].upper().strip() == "" and default == '""'):
202                     doc_triple = "%s\n      Expected: %s" % (param, default)
203                     failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
204
205         if len(failset) > 0:
206             self.fail(self._format_message(failset,
207                 "Parameters that do not have matching defaults:"))
208
209     def _set_defaults(self, program):
210         failset = set()
211         count = 0
212
213         for tuples in self.defaults:
214             param, default, context, param_type = tuples
215
216             if param in ['printing', 'rpc server dynamic port range']:
217                 continue
218
219             section = None
220             if context == "G":
221                 section = "global"
222             elif context == "S":
223                 section = "test"
224             else:
225                  self.fail("%s has no valid context" % param)
226             p = subprocess.Popen(program + ["-s", self.smbconf,
227                     "--section-name", section, "--parameter-name", param,
228                     "--option", "%s = %s" % (param, default)],
229                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
230             if p[0].upper().strip() != default.upper():
231                 if not (p[0].upper().strip() == "" and default == '""'):
232                     doc_triple = "%s\n      Expected: %s" % (param, default)
233                     failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
234
235         if len(failset) > 0:
236             self.fail(self._format_message(failset,
237                 "Parameters that do not have matching defaults:"))
238
239     def _set_arbitrary(self, program, exceptions=None):
240         arbitrary = {'string': 'string', 'boolean': 'yes', 'integer': '5',
241                      'boolean-rev': 'yes',
242                      'cmdlist': 'a b c',
243                      'bytes': '10',
244                      'octal': '0123',
245                      'ustring': 'ustring',
246                      'enum':'', 'boolean-auto': '', 'char': 'a', 'list': 'a, b, c'}
247         opposite_arbitrary = {'string': 'string2', 'boolean': 'no', 'integer': '6',
248                               'boolean-rev': 'no',
249                               'cmdlist': 'd e f',
250                               'bytes': '11',
251                               'octal': '0567',
252                               'ustring': 'ustring2',
253                               'enum':'', 'boolean-auto': '', 'char': 'b', 'list': 'd, e, f'}
254
255         failset = set()
256         count = 0
257
258         for tuples in self.defaults_all:
259             param, default, context, param_type = tuples
260
261             if param in ['printing', 'copy', 'include', 'log level']:
262                 continue
263
264             # currently no easy way to set an arbitrary value for these
265             if param_type in ['enum', 'boolean-auto']:
266                 continue
267
268             if exceptions is not None:
269                 if param in exceptions:
270                     continue
271
272             section = None
273             if context == "G":
274                 section = "global"
275             elif context == "S":
276                 section = "test"
277             else:
278                  self.fail("%s has no valid context" % param)
279
280             value_to_use = arbitrary.get(param_type)
281             if value_to_use is None:
282                 self.fail("%s has an invalid type" % param)
283
284             p = subprocess.Popen(program + ["-s", self.smbconf,
285                     "--section-name", section, "--parameter-name", param,
286                     "--option", "%s = %s" % (param, value_to_use)],
287                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
288             if p[0].upper().strip() != value_to_use.upper():
289                 # currently no way to distinguish command lists
290                 if param_type == 'list':
291                     if ", ".join(p[0].upper().strip().split()) == value_to_use.upper():
292                         continue
293
294                 # currently no way to identify octal
295                 if param_type == 'integer':
296                     try:
297                         if int(value_to_use, 8) == int(p[0].strip(), 8):
298                             continue
299                     except:
300                         pass
301
302                 doc_triple = "%s\n      Expected: %s" % (param, value_to_use)
303                 failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
304
305             opposite_value = opposite_arbitrary.get(param_type)
306             tempconf = os.path.join(self.tempdir, "tempsmb.conf")
307             g = open(tempconf, 'w')
308             try:
309                 towrite = section + "\n"
310                 towrite += param + " = " + opposite_value
311                 g.write(towrite)
312             finally:
313                 g.close()
314
315             p = subprocess.Popen(program + ["-s", tempconf, "--suppress-prompt",
316                     "--option", "%s = %s" % (param, value_to_use)],
317                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
318
319             os.unlink(tempconf)
320
321             # testparm doesn't display a value if they are equivalent
322             if (value_to_use.lower() != opposite_value.lower()):
323                 for line in p[0].splitlines():
324                     if not line.strip().startswith(param):
325                         continue
326
327                     value_found = line.split("=")[1].upper().strip()
328                     if value_found != value_to_use.upper():
329                         # currently no way to distinguish command lists
330                         if param_type == 'list':
331                             if ", ".join(value_found.split()) == value_to_use.upper():
332                                 continue
333
334                         # currently no way to identify octal
335                         if param_type == 'integer':
336                             try:
337                                 if int(value_to_use, 8) == int(value_found, 8):
338                                     continue
339                             except:
340                                 pass
341
342                         doc_triple = "%s\n      Expected: %s" % (param, value_to_use)
343                         failset.add("%s\n      Got: %s" % (doc_triple, value_found))
344
345
346         if len(failset) > 0:
347             self.fail(self._format_message(failset,
348                 "Parameters that were unexpectedly not set:"))
349
350     def _test_empty(self, program):
351         p = subprocess.Popen(program + ["-s", self.blankconf, "--suppress-prompt"],
352                 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
353         output = ""
354
355         for line in p[0].splitlines():
356             if line.strip().startswith('#'):
357                 continue
358             if line.strip().startswith("idmap config *"):
359                 continue
360             output += line.strip().lower() + '\n'
361
362         if output.strip() != '[global]' and output.strip() != '[globals]':
363             self.fail("Testparm returned unexpected output on an empty smb.conf.")