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."""
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 = map(str, parameters)
36 return message + '\n\n %s' % ('\n '.join(parameters))
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")
44 p = open(os.path.join(path, "parameters.all.xml"), 'r')
46 raise Exception("Error opening parameters file")
49 root = ET.fromstring(out)
50 for parameter in root:
51 name = parameter.attrib.get('name')
52 if parameter.attrib.get('removed') == "1":
55 syn = parameter.findall('synonym')
62 def get_implementation_parameters(sourcedir):
63 # Reading entries from source code
64 f = open(os.path.join(sourcedir, "lib/param/param_table_static.c"), "r")
66 # burn through the preceding lines
69 if l.startswith("struct parm_struct parm_table"):
72 for l in f.readlines():
73 if re.match("^\s*\}\;\s*$", l):
75 # pull in the param names only
76 m = re.match("\s*\.label\s*=\s*\"(.*)\".*", l)
85 def get_param_table_full(sourcedir, filename="lib/param/param_table_static.c"):
86 # Reading entries from source code
87 f = open(os.path.join(sourcedir, filename), "r")
89 # burn through the preceding lines
92 if l.startswith("struct parm_struct parm_table"):
95 for l in f.readlines():
97 if re.match("^\s*\}\;\s*$", l):
98 # end of the table reached
101 if re.match("^\s*\{\s*$", l):
112 if re.match("^\s*\},\s*$", l):
114 yield _label, _type, _class, _offset, _special, _enum_list, _flags
117 m = re.match("^\s*\.([^\s]+)\s*=\s*(.*),.*", l)
124 if attrib == "label":
126 elif attrib == "type":
128 elif attrib == "p_class":
130 elif attrib == "offset":
132 elif attrib == "special":
134 elif attrib == "enum_list":
136 elif attrib == "flags":
143 def get_documented_tuples(sourcedir, omit_no_default=True):
144 path = os.path.join(sourcedir, "bin", "default", "docs-xml", "smbdotconf")
145 if not os.path.exists(os.path.join(path, "parameters.all.xml")):
146 raise Exception("Unable to find parameters.all.xml")
148 p = open(os.path.join(path, "parameters.all.xml"), 'r')
150 raise Exception("Error opening parameters file")
153 root = ET.fromstring(out)
154 for parameter in root:
155 name = parameter.attrib.get("name")
156 param_type = parameter.attrib.get("type")
157 if parameter.attrib.get('removed') == "1":
159 values = parameter.findall("value")
162 if value.attrib.get("type") == "default":
163 defaults.append(value)
166 if len(defaults) == 0:
169 elif len(defaults) > 1:
170 raise Exception("More than one default found for parameter %s" % name)
172 default_text = defaults[0].text
174 if default_text is None:
176 context = parameter.attrib.get("context")
177 yield name, default_text, context, param_type
180 class SmbDotConfTests(TestCase):
182 # defines the cases where the defaults may differ from the documentation
183 special_cases = set(['log level', 'path', 'ldapsam:trusted', 'spoolss: architecture',
184 'share:fake_fscaps', 'ldapsam:editposix', 'rpc_daemon:DAEMON',
185 'rpc_server:SERVER', 'panic action', 'homedir map', 'NIS homedir',
186 'server string', 'netbios name', 'socket options', 'use mmap',
187 'ctdbd socket', 'printing', 'printcap name', 'queueresume command',
188 'queuepause command','lpresume command', 'lppause command',
189 'lprm command', 'lpq command', 'print command', 'template homedir',
190 'spoolss: os_major', 'spoolss: os_minor', 'spoolss: os_build',
191 'max open files', 'fss: prune stale', 'fss: sequence timeout'])
194 super(SmbDotConfTests, self).setUp()
195 # create a minimal smb.conf file for testparm
196 self.smbconf = os.path.join(self.tempdir, "paramtestsmb.conf")
197 f = open(self.smbconf, 'w')
206 self.blankconf = os.path.join(self.tempdir, "emptytestsmb.conf")
207 f = open(self.blankconf, 'w')
213 self.topdir = os.path.abspath(samba.source_tree_topdir())
216 self.documented = set(get_documented_parameters(self.topdir))
218 self.fail("Unable to load documented parameters")
221 self.parameters = set(get_implementation_parameters(self.topdir))
223 self.fail("Unable to load implemented parameters")
226 self.table_static = set(get_param_table_full(self.topdir,
227 "lib/param/param_table_static.c"))
229 self.fail("Unable to load static parameter table")
232 self.defaults = set(get_documented_tuples(self.topdir))
234 self.fail("Unable to load parameters")
237 self.defaults_all = set(get_documented_tuples(self.topdir, False))
239 self.fail("Unable to load parameters")
243 super(SmbDotConfTests, self).tearDown()
244 os.unlink(self.smbconf)
245 os.unlink(self.blankconf)
247 def test_unknown(self):
248 # Filter out parametric options, since we can't find them in the parm
250 documented = set([p for p in self.documented if not ":" in p])
251 unknown = documented.difference(self.parameters)
253 self.fail(self._format_message(unknown,
254 "Parameters that are documented but not in the implementation:"))
256 def test_undocumented(self):
257 undocumented = self.parameters.difference(self.documented)
258 if len(undocumented) > 0:
259 self.fail(self._format_message(undocumented,
260 "Parameters that are in the implementation but undocumented:"))
262 def test_default_s3(self):
263 self._test_default(['bin/testparm'])
264 self._set_defaults(['bin/testparm'])
266 # registry shares appears to need sudo
267 self._set_arbitrary(['bin/testparm'],
268 exceptions = ['client lanman auth',
269 'client plaintext auth',
272 self._test_empty(['bin/testparm'])
274 def test_default_s4(self):
275 self._test_default(['bin/samba-tool', 'testparm'])
276 self._set_defaults(['bin/samba-tool', 'testparm'])
277 self._set_arbitrary(['bin/samba-tool', 'testparm'],
278 exceptions = ['smb ports'])
279 self._test_empty(['bin/samba-tool', 'testparm'])
281 def _test_default(self, program):
285 for tuples in self.defaults:
286 param, default, context, param_type = tuples
287 if param in self.special_cases:
295 self.fail("%s has no valid context" % param)
296 p = subprocess.Popen(program + ["-s", self.smbconf,
297 "--section-name", section, "--parameter-name", param],
298 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
299 if p[0].upper().strip() != default.upper():
300 if not (p[0].upper().strip() == "" and default == '""'):
301 doc_triple = "%s\n Expected: %s" % (param, default)
302 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
305 self.fail(self._format_message(failset,
306 "Parameters that do not have matching defaults:"))
308 def _set_defaults(self, program):
312 for tuples in self.defaults:
313 param, default, context, param_type = tuples
315 if param in ['printing']:
324 self.fail("%s has no valid context" % param)
325 p = subprocess.Popen(program + ["-s", self.smbconf,
326 "--section-name", section, "--parameter-name", param,
327 "--option", "%s = %s" % (param, default)],
328 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
329 if p[0].upper().strip() != default.upper():
330 if not (p[0].upper().strip() == "" and default == '""'):
331 doc_triple = "%s\n Expected: %s" % (param, default)
332 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
335 self.fail(self._format_message(failset,
336 "Parameters that do not have matching defaults:"))
338 def _set_arbitrary(self, program, exceptions=None):
339 arbitrary = {'string': 'string', 'boolean': 'yes', 'integer': '5',
340 'boolean-rev': 'yes',
344 'ustring': 'ustring',
345 'enum':'', 'boolean-auto': '', 'char': 'a', 'list': 'a, b, c'}
346 opposite_arbitrary = {'string': 'string2', 'boolean': 'no', 'integer': '6',
351 'ustring': 'ustring2',
352 'enum':'', 'boolean-auto': '', 'char': 'b', 'list': 'd, e, f'}
357 for tuples in self.defaults_all:
358 param, default, context, param_type = tuples
360 if param in ['printing', 'copy', 'include', 'log level']:
363 # currently no easy way to set an arbitrary value for these
364 if param_type in ['enum', 'boolean-auto']:
367 if exceptions is not None:
368 if param in exceptions:
377 self.fail("%s has no valid context" % param)
379 value_to_use = arbitrary.get(param_type)
380 if value_to_use is None:
381 self.fail("%s has an invalid type" % param)
383 p = subprocess.Popen(program + ["-s", self.smbconf,
384 "--section-name", section, "--parameter-name", param,
385 "--option", "%s = %s" % (param, value_to_use)],
386 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
387 if p[0].upper().strip() != value_to_use.upper():
388 # currently no way to distinguish command lists
389 if param_type == 'list':
390 if ", ".join(p[0].upper().strip().split()) == value_to_use.upper():
393 # currently no way to identify octal
394 if param_type == 'integer':
396 if int(value_to_use, 8) == int(p[0].strip(), 8):
401 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
402 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
404 opposite_value = opposite_arbitrary.get(param_type)
405 tempconf = os.path.join(self.tempdir, "tempsmb.conf")
406 g = open(tempconf, 'w')
408 towrite = section + "\n"
409 towrite += param + " = " + opposite_value
414 p = subprocess.Popen(program + ["-s", tempconf, "--suppress-prompt",
415 "--option", "%s = %s" % (param, value_to_use)],
416 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
420 # testparm doesn't display a value if they are equivalent
421 if (value_to_use.lower() != opposite_value.lower()):
422 for line in p[0].splitlines():
423 if not line.strip().startswith(param):
426 value_found = line.split("=")[1].upper().strip()
427 if value_found != value_to_use.upper():
428 # currently no way to distinguish command lists
429 if param_type == 'list':
430 if ", ".join(value_found.split()) == value_to_use.upper():
433 # currently no way to identify octal
434 if param_type == 'integer':
436 if int(value_to_use, 8) == int(value_found, 8):
441 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
442 failset.add("%s\n Got: %s" % (doc_triple, value_found))
446 self.fail(self._format_message(failset,
447 "Parameters that were unexpectedly not set:"))
449 def _test_empty(self, program):
450 p = subprocess.Popen(program + ["-s", self.blankconf, "--suppress-prompt"],
451 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
454 for line in p[0].splitlines():
455 if line.strip().startswith('#'):
457 if line.strip().startswith("idmap config *"):
459 output += line.strip().lower() + '\n'
461 if output.strip() != '[global]' and output.strip() != '[globals]':
462 self.fail("Testparm returned unexpected output on an empty smb.conf.")