param: move the actual table out into param_table_static.c
[Samba.git] / python / samba / tests / docs.py
blobded0c20cba579c786c60b479553d4d4bba0e9656
1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2012
4 # Tests for documentation.
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.
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.
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/>.
20 """Tests for presence of documentation."""
22 import samba
23 import samba.tests
25 import os
26 import re
27 import subprocess
28 import xml.etree.ElementTree as ET
30 class TestCase(samba.tests.TestCaseInTempDir):
32 def _format_message(self, parameters, message):
33 parameters = list(parameters)
34 parameters.sort()
35 return message + '\n\n %s' % ('\n '.join(parameters))
38 def get_documented_parameters(sourcedir):
39 path = os.path.join(sourcedir, "bin", "default", "docs-xml", "smbdotconf")
40 if not os.path.exists(os.path.join(path, "parameters.all.xml")):
41 raise Exception("Unable to find parameters.all.xml")
42 try:
43 p = open(os.path.join(path, "parameters.all.xml"), 'r')
44 except IOError, e:
45 raise Exception("Error opening parameters file")
46 out = p.read()
48 root = ET.fromstring(out)
49 for parameter in root:
50 name = parameter.attrib.get('name')
51 if parameter.attrib.get('removed') == "1":
52 continue
53 yield name
54 syn = parameter.findall('synonym')
55 if syn is not None:
56 for sy in syn:
57 yield sy.text
58 p.close()
61 def get_implementation_parameters(sourcedir):
62 # Reading entries from source code
63 f = open(os.path.join(sourcedir, "lib/param/param_table_static.c"), "r")
64 try:
65 # burn through the preceding lines
66 while True:
67 l = f.readline()
68 if l.startswith("struct parm_struct parm_table"):
69 break
71 for l in f.readlines():
72 if re.match("^\s*\}\;\s*$", l):
73 break
74 # pull in the param names only
75 m = re.match("\s*\.label\s*=\s*\"(.*)\".*", l)
76 if not m:
77 continue
79 name = m.group(1)
80 yield name
81 finally:
82 f.close()
84 def get_documented_tuples(sourcedir, omit_no_default=True):
85 path = os.path.join(sourcedir, "bin", "default", "docs-xml", "smbdotconf")
86 if not os.path.exists(os.path.join(path, "parameters.all.xml")):
87 raise Exception("Unable to find parameters.all.xml")
88 try:
89 p = open(os.path.join(path, "parameters.all.xml"), 'r')
90 except IOError, e:
91 raise Exception("Error opening parameters file")
92 out = p.read()
94 root = ET.fromstring(out)
95 for parameter in root:
96 name = parameter.attrib.get("name")
97 param_type = parameter.attrib.get("type")
98 if parameter.attrib.get('removed') == "1":
99 continue
100 values = parameter.findall("value")
101 defaults = []
102 for value in values:
103 if value.attrib.get("type") == "default":
104 defaults.append(value)
106 default_text = None
107 if len(defaults) == 0:
108 if omit_no_default:
109 continue
110 elif len(defaults) > 1:
111 raise Exception("More than one default found for parameter %s" % name)
112 else:
113 default_text = defaults[0].text
115 if default_text is None:
116 default_text = ""
117 context = parameter.attrib.get("context")
118 yield name, default_text, context, param_type
119 p.close()
121 class SmbDotConfTests(TestCase):
123 # defines the cases where the defaults may differ from the documentation
124 special_cases = set(['log level', 'path', 'ldapsam:trusted', 'spoolss: architecture',
125 'share:fake_fscaps', 'ldapsam:editposix', 'rpc_daemon:DAEMON',
126 'rpc_server:SERVER', 'panic action', 'homedir map', 'NIS homedir',
127 'server string', 'netbios name', 'socket options', 'use mmap',
128 'ctdbd socket', 'printing', 'printcap name', 'queueresume command',
129 'queuepause command','lpresume command', 'lppause command',
130 'lprm command', 'lpq command', 'print command', 'template homedir',
131 'spoolss: os_major', 'spoolss: os_minor', 'spoolss: os_build',
132 'max open files', 'fss: prune stale', 'fss: sequence timeout'])
134 def setUp(self):
135 super(SmbDotConfTests, self).setUp()
136 # create a minimal smb.conf file for testparm
137 self.smbconf = os.path.join(self.tempdir, "paramtestsmb.conf")
138 f = open(self.smbconf, 'w')
139 try:
140 f.write("""
141 [test]
142 path = /
143 """)
144 finally:
145 f.close()
147 self.blankconf = os.path.join(self.tempdir, "emptytestsmb.conf")
148 f = open(self.blankconf, 'w')
149 try:
150 f.write("")
151 finally:
152 f.close()
154 def tearDown(self):
155 super(SmbDotConfTests, self).tearDown()
156 os.unlink(self.smbconf)
157 os.unlink(self.blankconf)
159 def test_unknown(self):
160 topdir = os.path.abspath(samba.source_tree_topdir())
161 try:
162 documented = set(get_documented_parameters(topdir))
163 except e:
164 self.fail("Unable to load parameters")
165 parameters = set(get_implementation_parameters(topdir))
166 # Filter out parametric options, since we can't find them in the parm
167 # table
168 documented = set([p for p in documented if not ":" in p])
169 unknown = documented.difference(parameters)
170 if len(unknown) > 0:
171 self.fail(self._format_message(unknown,
172 "Parameters that are documented but not in the implementation:"))
174 def test_undocumented(self):
175 topdir = os.path.abspath(samba.source_tree_topdir())
176 try:
177 documented = set(get_documented_parameters(topdir))
178 except:
179 self.fail("Unable to load parameters")
180 parameters = set(get_implementation_parameters(topdir))
181 undocumented = parameters.difference(documented)
182 if len(undocumented) > 0:
183 self.fail(self._format_message(undocumented,
184 "Parameters that are in the implementation but undocumented:"))
186 def test_default_s3(self):
187 self._test_default(['bin/testparm'])
188 self._set_defaults(['bin/testparm'])
190 # registry shares appears to need sudo
191 self._set_arbitrary(['bin/testparm'],
192 exceptions = ['client lanman auth',
193 'client plaintext auth',
194 'registry shares',
195 'smb ports'])
196 self._test_empty(['bin/testparm'])
198 def test_default_s4(self):
199 self._test_default(['bin/samba-tool', 'testparm'])
200 self._set_defaults(['bin/samba-tool', 'testparm'])
201 self._set_arbitrary(['bin/samba-tool', 'testparm'],
202 exceptions = ['smb ports'])
203 self._test_empty(['bin/samba-tool', 'testparm'])
205 def _test_default(self, program):
206 topdir = os.path.abspath(samba.source_tree_topdir())
207 try:
208 defaults = set(get_documented_tuples(topdir))
209 except:
210 self.fail("Unable to load parameters")
211 bindir = os.path.join(topdir, "bin")
212 failset = set()
213 count = 0
215 for tuples in defaults:
216 param, default, context, param_type = tuples
217 if param in self.special_cases:
218 continue
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 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
229 if p[0].upper().strip() != default.upper():
230 if not (p[0].upper().strip() == "" and default == '""'):
231 doc_triple = "%s\n Expected: %s" % (param, default)
232 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
234 if len(failset) > 0:
235 self.fail(self._format_message(failset,
236 "Parameters that do not have matching defaults:"))
238 def _set_defaults(self, program):
239 topdir = os.path.abspath(samba.source_tree_topdir())
240 try:
241 defaults = set(get_documented_tuples(topdir))
242 except:
243 self.fail("Unable to load parameters")
244 bindir = os.path.join(topdir, "bin")
245 failset = set()
246 count = 0
248 for tuples in defaults:
249 param, default, context, param_type = tuples
251 if param in ['printing']:
252 continue
254 section = None
255 if context == "G":
256 section = "global"
257 elif context == "S":
258 section = "test"
259 else:
260 self.fail("%s has no valid context" % param)
261 p = subprocess.Popen(program + ["-s", self.smbconf,
262 "--section-name", section, "--parameter-name", param,
263 "--option", "%s = %s" % (param, default)],
264 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
265 if p[0].upper().strip() != default.upper():
266 if not (p[0].upper().strip() == "" and default == '""'):
267 doc_triple = "%s\n Expected: %s" % (param, default)
268 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
270 if len(failset) > 0:
271 self.fail(self._format_message(failset,
272 "Parameters that do not have matching defaults:"))
274 def _set_arbitrary(self, program, exceptions=None):
275 arbitrary = {'string': 'string', 'boolean': 'yes', 'integer': '5',
276 'boolean-rev': 'yes',
277 'cmdlist': 'a b c',
278 'bytes': '10',
279 'octal': '0123',
280 'ustring': 'ustring',
281 'enum':'', 'boolean-auto': '', 'char': 'a', 'list': 'a, b, c'}
282 opposite_arbitrary = {'string': 'string2', 'boolean': 'no', 'integer': '6',
283 'boolean-rev': 'no',
284 'cmdlist': 'd e f',
285 'bytes': '11',
286 'octal': '0567',
287 'ustring': 'ustring2',
288 'enum':'', 'boolean-auto': '', 'char': 'b', 'list': 'd, e, f'}
289 topdir = os.path.abspath(samba.source_tree_topdir())
290 try:
291 defaults = set(get_documented_tuples(topdir, False))
292 except Exception,e:
293 self.fail("Unable to load parameters" + e)
294 bindir = os.path.join(topdir, "bin")
295 failset = set()
296 count = 0
298 for tuples in defaults:
299 param, default, context, param_type = tuples
301 if param in ['printing', 'copy', 'include', 'log level']:
302 continue
304 # currently no easy way to set an arbitrary value for these
305 if param_type in ['enum', 'boolean-auto']:
306 continue
308 if exceptions is not None:
309 if param in exceptions:
310 continue
312 section = None
313 if context == "G":
314 section = "global"
315 elif context == "S":
316 section = "test"
317 else:
318 self.fail("%s has no valid context" % param)
320 value_to_use = arbitrary.get(param_type)
321 if value_to_use is None:
322 self.fail("%s has an invalid type" % param)
324 p = subprocess.Popen(program + ["-s", self.smbconf,
325 "--section-name", section, "--parameter-name", param,
326 "--option", "%s = %s" % (param, value_to_use)],
327 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
328 if p[0].upper().strip() != value_to_use.upper():
329 # currently no way to distinguish command lists
330 if param_type == 'list':
331 if ", ".join(p[0].upper().strip().split()) == value_to_use.upper():
332 continue
334 # currently no way to identify octal
335 if param_type == 'integer':
336 try:
337 if int(value_to_use, 8) == int(p[0].strip(), 8):
338 continue
339 except:
340 pass
342 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
343 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
345 opposite_value = opposite_arbitrary.get(param_type)
346 tempconf = os.path.join(self.tempdir, "tempsmb.conf")
347 g = open(tempconf, 'w')
348 try:
349 towrite = section + "\n"
350 towrite += param + " = " + opposite_value
351 g.write(towrite)
352 finally:
353 g.close()
355 p = subprocess.Popen(program + ["-s", tempconf, "--suppress-prompt",
356 "--option", "%s = %s" % (param, value_to_use)],
357 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
359 os.unlink(tempconf)
361 # testparm doesn't display a value if they are equivalent
362 if (value_to_use.lower() != opposite_value.lower()):
363 for line in p[0].splitlines():
364 if not line.strip().startswith(param):
365 continue
367 value_found = line.split("=")[1].upper().strip()
368 if value_found != value_to_use.upper():
369 # currently no way to distinguish command lists
370 if param_type == 'list':
371 if ", ".join(value_found.split()) == value_to_use.upper():
372 continue
374 # currently no way to identify octal
375 if param_type == 'integer':
376 try:
377 if int(value_to_use, 8) == int(value_found, 8):
378 continue
379 except:
380 pass
382 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
383 failset.add("%s\n Got: %s" % (doc_triple, value_found))
386 if len(failset) > 0:
387 self.fail(self._format_message(failset,
388 "Parameters that were unexpectedly not set:"))
390 def _test_empty(self, program):
391 topdir = os.path.abspath(samba.source_tree_topdir())
393 p = subprocess.Popen(program + ["-s", self.blankconf, "--suppress-prompt"],
394 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
395 output = ""
397 for line in p[0].splitlines():
398 if line.strip().startswith('#'):
399 continue
400 if line.strip().startswith("idmap config *"):
401 continue
402 output += line.strip().lower() + '\n'
404 if output.strip() != '[global]' and output.strip() != '[globals]':
405 self.fail("Testparm returned unexpected output on an empty smb.conf.")