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.table_gen = set(get_param_table_full(self.topdir,
233 "bin/default/lib/param/param_table_gen.c"))
235 self.fail("Unable to load generated parameter table")
238 self.defaults = set(get_documented_tuples(self.topdir))
240 self.fail("Unable to load parameters")
243 self.defaults_all = set(get_documented_tuples(self.topdir, False))
245 self.fail("Unable to load parameters")
249 super(SmbDotConfTests, self).tearDown()
250 os.unlink(self.smbconf)
251 os.unlink(self.blankconf)
253 def test_unknown(self):
254 # Filter out parametric options, since we can't find them in the parm
256 documented = set([p for p in self.documented if not ":" in p])
257 unknown = documented.difference(self.parameters)
259 self.fail(self._format_message(unknown,
260 "Parameters that are documented but not in the implementation:"))
262 def test_undocumented(self):
263 undocumented = self.parameters.difference(self.documented)
264 if len(undocumented) > 0:
265 self.fail(self._format_message(undocumented,
266 "Parameters that are in the implementation but undocumented:"))
268 def test_default_s3(self):
269 self._test_default(['bin/testparm'])
270 self._set_defaults(['bin/testparm'])
272 # registry shares appears to need sudo
273 self._set_arbitrary(['bin/testparm'],
274 exceptions = ['client lanman auth',
275 'client plaintext auth',
278 self._test_empty(['bin/testparm'])
280 def test_default_s4(self):
281 self._test_default(['bin/samba-tool', 'testparm'])
282 self._set_defaults(['bin/samba-tool', 'testparm'])
283 self._set_arbitrary(['bin/samba-tool', 'testparm'],
284 exceptions = ['smb ports'])
285 self._test_empty(['bin/samba-tool', 'testparm'])
287 def _test_default(self, program):
291 for tuples in self.defaults:
292 param, default, context, param_type = tuples
293 if param in self.special_cases:
301 self.fail("%s has no valid context" % param)
302 p = subprocess.Popen(program + ["-s", self.smbconf,
303 "--section-name", section, "--parameter-name", param],
304 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
305 if p[0].upper().strip() != default.upper():
306 if not (p[0].upper().strip() == "" and default == '""'):
307 doc_triple = "%s\n Expected: %s" % (param, default)
308 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
311 self.fail(self._format_message(failset,
312 "Parameters that do not have matching defaults:"))
314 def _set_defaults(self, program):
318 for tuples in self.defaults:
319 param, default, context, param_type = tuples
321 if param in ['printing']:
330 self.fail("%s has no valid context" % param)
331 p = subprocess.Popen(program + ["-s", self.smbconf,
332 "--section-name", section, "--parameter-name", param,
333 "--option", "%s = %s" % (param, default)],
334 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
335 if p[0].upper().strip() != default.upper():
336 if not (p[0].upper().strip() == "" and default == '""'):
337 doc_triple = "%s\n Expected: %s" % (param, default)
338 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
341 self.fail(self._format_message(failset,
342 "Parameters that do not have matching defaults:"))
344 def _set_arbitrary(self, program, exceptions=None):
345 arbitrary = {'string': 'string', 'boolean': 'yes', 'integer': '5',
346 'boolean-rev': 'yes',
350 'ustring': 'ustring',
351 'enum':'', 'boolean-auto': '', 'char': 'a', 'list': 'a, b, c'}
352 opposite_arbitrary = {'string': 'string2', 'boolean': 'no', 'integer': '6',
357 'ustring': 'ustring2',
358 'enum':'', 'boolean-auto': '', 'char': 'b', 'list': 'd, e, f'}
363 for tuples in self.defaults_all:
364 param, default, context, param_type = tuples
366 if param in ['printing', 'copy', 'include', 'log level']:
369 # currently no easy way to set an arbitrary value for these
370 if param_type in ['enum', 'boolean-auto']:
373 if exceptions is not None:
374 if param in exceptions:
383 self.fail("%s has no valid context" % param)
385 value_to_use = arbitrary.get(param_type)
386 if value_to_use is None:
387 self.fail("%s has an invalid type" % param)
389 p = subprocess.Popen(program + ["-s", self.smbconf,
390 "--section-name", section, "--parameter-name", param,
391 "--option", "%s = %s" % (param, value_to_use)],
392 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
393 if p[0].upper().strip() != value_to_use.upper():
394 # currently no way to distinguish command lists
395 if param_type == 'list':
396 if ", ".join(p[0].upper().strip().split()) == value_to_use.upper():
399 # currently no way to identify octal
400 if param_type == 'integer':
402 if int(value_to_use, 8) == int(p[0].strip(), 8):
407 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
408 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
410 opposite_value = opposite_arbitrary.get(param_type)
411 tempconf = os.path.join(self.tempdir, "tempsmb.conf")
412 g = open(tempconf, 'w')
414 towrite = section + "\n"
415 towrite += param + " = " + opposite_value
420 p = subprocess.Popen(program + ["-s", tempconf, "--suppress-prompt",
421 "--option", "%s = %s" % (param, value_to_use)],
422 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
426 # testparm doesn't display a value if they are equivalent
427 if (value_to_use.lower() != opposite_value.lower()):
428 for line in p[0].splitlines():
429 if not line.strip().startswith(param):
432 value_found = line.split("=")[1].upper().strip()
433 if value_found != value_to_use.upper():
434 # currently no way to distinguish command lists
435 if param_type == 'list':
436 if ", ".join(value_found.split()) == value_to_use.upper():
439 # currently no way to identify octal
440 if param_type == 'integer':
442 if int(value_to_use, 8) == int(value_found, 8):
447 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
448 failset.add("%s\n Got: %s" % (doc_triple, value_found))
452 self.fail(self._format_message(failset,
453 "Parameters that were unexpectedly not set:"))
455 def _test_empty(self, program):
456 p = subprocess.Popen(program + ["-s", self.blankconf, "--suppress-prompt"],
457 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
460 for line in p[0].splitlines():
461 if line.strip().startswith('#'):
463 if line.strip().startswith("idmap config *"):
465 output += line.strip().lower() + '\n'
467 if output.strip() != '[global]' and output.strip() != '[globals]':
468 self.fail("Testparm returned unexpected output on an empty smb.conf.")