tests:docs: print more complicated structures than strings in the message.
[Samba.git] / python / samba / tests / docs.py
blobab105e03a3752a00dcd1261215204afb5e9105eb
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 = map(str, parameters)
35 parameters.sort()
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")
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()
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()
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")
65 try:
66 # burn through the preceding lines
67 while True:
68 l = f.readline()
69 if l.startswith("struct parm_struct parm_table"):
70 break
72 for l in f.readlines():
73 if re.match("^\s*\}\;\s*$", l):
74 break
75 # pull in the param names only
76 m = re.match("\s*\.label\s*=\s*\"(.*)\".*", l)
77 if not m:
78 continue
80 name = m.group(1)
81 yield name
82 finally:
83 f.close()
85 def get_documented_tuples(sourcedir, omit_no_default=True):
86 path = os.path.join(sourcedir, "bin", "default", "docs-xml", "smbdotconf")
87 if not os.path.exists(os.path.join(path, "parameters.all.xml")):
88 raise Exception("Unable to find parameters.all.xml")
89 try:
90 p = open(os.path.join(path, "parameters.all.xml"), 'r')
91 except IOError, e:
92 raise Exception("Error opening parameters file")
93 out = p.read()
95 root = ET.fromstring(out)
96 for parameter in root:
97 name = parameter.attrib.get("name")
98 param_type = parameter.attrib.get("type")
99 if parameter.attrib.get('removed') == "1":
100 continue
101 values = parameter.findall("value")
102 defaults = []
103 for value in values:
104 if value.attrib.get("type") == "default":
105 defaults.append(value)
107 default_text = None
108 if len(defaults) == 0:
109 if omit_no_default:
110 continue
111 elif len(defaults) > 1:
112 raise Exception("More than one default found for parameter %s" % name)
113 else:
114 default_text = defaults[0].text
116 if default_text is None:
117 default_text = ""
118 context = parameter.attrib.get("context")
119 yield name, default_text, context, param_type
120 p.close()
122 class SmbDotConfTests(TestCase):
124 # defines the cases where the defaults may differ from the documentation
125 special_cases = set(['log level', 'path', 'ldapsam:trusted', 'spoolss: architecture',
126 'share:fake_fscaps', 'ldapsam:editposix', 'rpc_daemon:DAEMON',
127 'rpc_server:SERVER', 'panic action', 'homedir map', 'NIS homedir',
128 'server string', 'netbios name', 'socket options', 'use mmap',
129 'ctdbd socket', 'printing', 'printcap name', 'queueresume command',
130 'queuepause command','lpresume command', 'lppause command',
131 'lprm command', 'lpq command', 'print command', 'template homedir',
132 'spoolss: os_major', 'spoolss: os_minor', 'spoolss: os_build',
133 'max open files', 'fss: prune stale', 'fss: sequence timeout'])
135 def setUp(self):
136 super(SmbDotConfTests, self).setUp()
137 # create a minimal smb.conf file for testparm
138 self.smbconf = os.path.join(self.tempdir, "paramtestsmb.conf")
139 f = open(self.smbconf, 'w')
140 try:
141 f.write("""
142 [test]
143 path = /
144 """)
145 finally:
146 f.close()
148 self.blankconf = os.path.join(self.tempdir, "emptytestsmb.conf")
149 f = open(self.blankconf, 'w')
150 try:
151 f.write("")
152 finally:
153 f.close()
155 self.topdir = os.path.abspath(samba.source_tree_topdir())
157 try:
158 self.documented = set(get_documented_parameters(self.topdir))
159 except:
160 self.fail("Unable to load documented parameters")
162 try:
163 self.parameters = set(get_implementation_parameters(self.topdir))
164 except:
165 self.fail("Unable to load implemented parameters")
167 try:
168 self.defaults = set(get_documented_tuples(self.topdir))
169 except:
170 self.fail("Unable to load parameters")
172 try:
173 self.defaults_all = set(get_documented_tuples(self.topdir, False))
174 except:
175 self.fail("Unable to load parameters")
178 def tearDown(self):
179 super(SmbDotConfTests, self).tearDown()
180 os.unlink(self.smbconf)
181 os.unlink(self.blankconf)
183 def test_unknown(self):
184 # Filter out parametric options, since we can't find them in the parm
185 # table
186 documented = set([p for p in self.documented if not ":" in p])
187 unknown = documented.difference(self.parameters)
188 if len(unknown) > 0:
189 self.fail(self._format_message(unknown,
190 "Parameters that are documented but not in the implementation:"))
192 def test_undocumented(self):
193 undocumented = self.parameters.difference(self.documented)
194 if len(undocumented) > 0:
195 self.fail(self._format_message(undocumented,
196 "Parameters that are in the implementation but undocumented:"))
198 def test_default_s3(self):
199 self._test_default(['bin/testparm'])
200 self._set_defaults(['bin/testparm'])
202 # registry shares appears to need sudo
203 self._set_arbitrary(['bin/testparm'],
204 exceptions = ['client lanman auth',
205 'client plaintext auth',
206 'registry shares',
207 'smb ports'])
208 self._test_empty(['bin/testparm'])
210 def test_default_s4(self):
211 self._test_default(['bin/samba-tool', 'testparm'])
212 self._set_defaults(['bin/samba-tool', 'testparm'])
213 self._set_arbitrary(['bin/samba-tool', 'testparm'],
214 exceptions = ['smb ports'])
215 self._test_empty(['bin/samba-tool', 'testparm'])
217 def _test_default(self, program):
218 failset = set()
219 count = 0
221 for tuples in self.defaults:
222 param, default, context, param_type = tuples
223 if param in self.special_cases:
224 continue
225 section = None
226 if context == "G":
227 section = "global"
228 elif context == "S":
229 section = "test"
230 else:
231 self.fail("%s has no valid context" % param)
232 p = subprocess.Popen(program + ["-s", self.smbconf,
233 "--section-name", section, "--parameter-name", param],
234 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
235 if p[0].upper().strip() != default.upper():
236 if not (p[0].upper().strip() == "" and default == '""'):
237 doc_triple = "%s\n Expected: %s" % (param, default)
238 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
240 if len(failset) > 0:
241 self.fail(self._format_message(failset,
242 "Parameters that do not have matching defaults:"))
244 def _set_defaults(self, program):
245 failset = set()
246 count = 0
248 for tuples in self.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=self.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'}
290 failset = set()
291 count = 0
293 for tuples in self.defaults_all:
294 param, default, context, param_type = tuples
296 if param in ['printing', 'copy', 'include', 'log level']:
297 continue
299 # currently no easy way to set an arbitrary value for these
300 if param_type in ['enum', 'boolean-auto']:
301 continue
303 if exceptions is not None:
304 if param in exceptions:
305 continue
307 section = None
308 if context == "G":
309 section = "global"
310 elif context == "S":
311 section = "test"
312 else:
313 self.fail("%s has no valid context" % param)
315 value_to_use = arbitrary.get(param_type)
316 if value_to_use is None:
317 self.fail("%s has an invalid type" % param)
319 p = subprocess.Popen(program + ["-s", self.smbconf,
320 "--section-name", section, "--parameter-name", param,
321 "--option", "%s = %s" % (param, value_to_use)],
322 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
323 if p[0].upper().strip() != value_to_use.upper():
324 # currently no way to distinguish command lists
325 if param_type == 'list':
326 if ", ".join(p[0].upper().strip().split()) == value_to_use.upper():
327 continue
329 # currently no way to identify octal
330 if param_type == 'integer':
331 try:
332 if int(value_to_use, 8) == int(p[0].strip(), 8):
333 continue
334 except:
335 pass
337 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
338 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
340 opposite_value = opposite_arbitrary.get(param_type)
341 tempconf = os.path.join(self.tempdir, "tempsmb.conf")
342 g = open(tempconf, 'w')
343 try:
344 towrite = section + "\n"
345 towrite += param + " = " + opposite_value
346 g.write(towrite)
347 finally:
348 g.close()
350 p = subprocess.Popen(program + ["-s", tempconf, "--suppress-prompt",
351 "--option", "%s = %s" % (param, value_to_use)],
352 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
354 os.unlink(tempconf)
356 # testparm doesn't display a value if they are equivalent
357 if (value_to_use.lower() != opposite_value.lower()):
358 for line in p[0].splitlines():
359 if not line.strip().startswith(param):
360 continue
362 value_found = line.split("=")[1].upper().strip()
363 if value_found != value_to_use.upper():
364 # currently no way to distinguish command lists
365 if param_type == 'list':
366 if ", ".join(value_found.split()) == value_to_use.upper():
367 continue
369 # currently no way to identify octal
370 if param_type == 'integer':
371 try:
372 if int(value_to_use, 8) == int(value_found, 8):
373 continue
374 except:
375 pass
377 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
378 failset.add("%s\n Got: %s" % (doc_triple, value_found))
381 if len(failset) > 0:
382 self.fail(self._format_message(failset,
383 "Parameters that were unexpectedly not set:"))
385 def _test_empty(self, program):
386 p = subprocess.Popen(program + ["-s", self.blankconf, "--suppress-prompt"],
387 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
388 output = ""
390 for line in p[0].splitlines():
391 if line.strip().startswith('#'):
392 continue
393 if line.strip().startswith("idmap config *"):
394 continue
395 output += line.strip().lower() + '\n'
397 if output.strip() != '[global]' and output.strip() != '[globals]':
398 self.fail("Testparm returned unexpected output on an empty smb.conf.")