param: Amend docs.py test to check dumping of flagged parameters
[Samba.git] / python / samba / tests / docs.py
blob0d71e685ab7fc0e028d0b002080f029fb9d3f839
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
24 from samba.tests import TestSkipped, TestCaseInTempDir
26 import errno
27 import os
28 import re
29 import subprocess
30 import xml.etree.ElementTree as ET
32 class TestCase(samba.tests.TestCaseInTempDir):
34 def _format_message(self, parameters, message):
35 parameters = list(parameters)
36 parameters.sort()
37 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.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 if re.match(".*P_SEPARATOR.*", l):
77 continue
78 m = re.match("\s*\.label\s*=\s*\"(.*)\".*", l)
79 if not m:
80 continue
82 name = m.group(1)
83 yield name
84 finally:
85 f.close()
87 def get_documented_tuples(sourcedir, omit_no_default=True):
88 path = os.path.join(sourcedir, "bin", "default", "docs-xml", "smbdotconf")
89 if not os.path.exists(os.path.join(path, "parameters.all.xml")):
90 raise Exception("Unable to find parameters.all.xml")
91 try:
92 p = open(os.path.join(path, "parameters.all.xml"), 'r')
93 except IOError, e:
94 raise Exception("Error opening parameters file")
95 out = p.read()
97 root = ET.fromstring(out)
98 for parameter in root:
99 name = parameter.attrib.get("name")
100 param_type = parameter.attrib.get("type")
101 if parameter.attrib.get('removed') == "1":
102 continue
103 values = parameter.findall("value")
104 defaults = []
105 for value in values:
106 if value.attrib.get("type") == "default":
107 defaults.append(value)
109 default_text = None
110 if len(defaults) == 0:
111 if omit_no_default:
112 continue
113 elif len(defaults) > 1:
114 raise Exception("More than one default found for parameter %s" % name)
115 else:
116 default_text = defaults[0].text
118 if default_text is None:
119 default_text = ""
120 context = parameter.attrib.get("context")
121 yield name, default_text, context, param_type
122 p.close()
124 class SmbDotConfTests(TestCase):
126 # defines the cases where the defaults may differ from the documentation
127 special_cases = set(['log level', 'path', 'ldapsam:trusted', 'spoolss: architecture',
128 'share:fake_fscaps', 'ldapsam:editposix', 'rpc_daemon:DAEMON',
129 'rpc_server:SERVER', 'panic action', 'homedir map', 'NIS homedir',
130 'server string', 'netbios name', 'socket options', 'use mmap',
131 'ctdbd socket', 'printing', 'printcap name', 'queueresume command',
132 'queuepause command','lpresume command', 'lppause command',
133 'lprm command', 'lpq command', 'print command', 'template homedir',
134 'spoolss: os_major', 'spoolss: os_minor', 'spoolss: os_build'])
136 def setUp(self):
137 super(SmbDotConfTests, self).setUp()
138 # create a minimal smb.conf file for testparm
139 self.smbconf = os.path.join(self.tempdir, "paramtestsmb.conf")
140 f = open(self.smbconf, 'w')
141 try:
142 f.write("""
143 [test]
144 path = /
145 """)
146 finally:
147 f.close()
149 self.blankconf = os.path.join(self.tempdir, "emptytestsmb.conf")
150 f = open(self.blankconf, 'w')
151 try:
152 f.write("")
153 finally:
154 f.close()
156 def tearDown(self):
157 super(SmbDotConfTests, self).tearDown()
158 os.unlink(self.smbconf)
159 os.unlink(self.blankconf)
161 def test_unknown(self):
162 topdir = os.path.abspath(samba.source_tree_topdir())
163 try:
164 documented = set(get_documented_parameters(topdir))
165 except e:
166 self.fail("Unable to load parameters")
167 parameters = set(get_implementation_parameters(topdir))
168 # Filter out parametric options, since we can't find them in the parm
169 # table
170 documented = set([p for p in documented if not ":" in p])
171 unknown = documented.difference(parameters)
172 if len(unknown) > 0:
173 self.fail(self._format_message(unknown,
174 "Parameters that are documented but not in the implementation:"))
176 def test_undocumented(self):
177 topdir = os.path.abspath(samba.source_tree_topdir())
178 try:
179 documented = set(get_documented_parameters(topdir))
180 except:
181 self.fail("Unable to load parameters")
182 parameters = set(get_implementation_parameters(topdir))
183 undocumented = parameters.difference(documented)
184 if len(undocumented) > 0:
185 self.fail(self._format_message(undocumented,
186 "Parameters that are in the implementation but undocumented:"))
188 def test_default_s3(self):
189 self._test_default(['bin/testparm'])
190 self._set_defaults(['bin/testparm'])
192 # registry shares appears to need sudo
193 self._set_arbitrary(['bin/testparm'],
194 exceptions = ['client lanman auth',
195 'client plaintext auth',
196 'registry shares',
197 'smb ports'])
198 self._test_empty(['bin/testparm'])
200 def test_default_s4(self):
201 self._test_default(['bin/samba-tool', 'testparm'])
202 self._set_defaults(['bin/samba-tool', 'testparm'])
203 self._set_arbitrary(['bin/samba-tool', 'testparm'],
204 exceptions = ['smb ports'])
205 self._test_empty(['bin/samba-tool', 'testparm'])
207 def _test_default(self, program):
208 topdir = os.path.abspath(samba.source_tree_topdir())
209 try:
210 defaults = set(get_documented_tuples(topdir))
211 except:
212 self.fail("Unable to load parameters")
213 bindir = os.path.join(topdir, "bin")
214 failset = set()
215 count = 0
217 for tuples in defaults:
218 param, default, context, param_type = tuples
219 if param in self.special_cases:
220 continue
221 section = None
222 if context == "G":
223 section = "global"
224 elif context == "S":
225 section = "test"
226 else:
227 self.fail("%s has no valid context" % param)
228 p = subprocess.Popen(program + ["-s", self.smbconf,
229 "--section-name", section, "--parameter-name", param],
230 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
231 if p[0].upper().strip() != default.upper():
232 if not (p[0].upper().strip() == "" and default == '""'):
233 doc_triple = "%s\n Expected: %s" % (param, default)
234 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
236 if len(failset) > 0:
237 self.fail(self._format_message(failset,
238 "Parameters that do not have matching defaults:"))
240 def _set_defaults(self, program):
241 topdir = os.path.abspath(samba.source_tree_topdir())
242 try:
243 defaults = set(get_documented_tuples(topdir))
244 except:
245 self.fail("Unable to load parameters")
246 bindir = os.path.join(topdir, "bin")
247 failset = set()
248 count = 0
250 for tuples in defaults:
251 param, default, context, param_type = tuples
253 if param in ['printing']:
254 continue
256 section = None
257 if context == "G":
258 section = "global"
259 elif context == "S":
260 section = "test"
261 else:
262 self.fail("%s has no valid context" % param)
263 p = subprocess.Popen(program + ["-s", self.smbconf,
264 "--section-name", section, "--parameter-name", param,
265 "--option", "%s = %s" % (param, default)],
266 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
267 if p[0].upper().strip() != default.upper():
268 if not (p[0].upper().strip() == "" and default == '""'):
269 doc_triple = "%s\n Expected: %s" % (param, default)
270 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
272 if len(failset) > 0:
273 self.fail(self._format_message(failset,
274 "Parameters that do not have matching defaults:"))
276 def _set_arbitrary(self, program, exceptions=None):
277 arbitrary = {'string': 'string', 'boolean': 'yes', 'integer': '5',
278 'enum':'', 'boolean-auto': '', 'char': 'a', 'list': 'a, b, c'}
279 opposite_arbitrary = {'string': 'string2', 'boolean': 'no', 'integer': '6',
280 'enum':'', 'boolean-auto': '', 'char': 'b', 'list': 'd, e, f'}
281 topdir = os.path.abspath(samba.source_tree_topdir())
282 try:
283 defaults = set(get_documented_tuples(topdir, False))
284 except Exception,e:
285 self.fail("Unable to load parameters" + e)
286 bindir = os.path.join(topdir, "bin")
287 failset = set()
288 count = 0
290 for tuples in defaults:
291 param, default, context, param_type = tuples
293 if param in ['printing', 'copy', 'include', 'log level']:
294 continue
296 # currently no easy way to set an arbitrary value for these
297 if param_type in ['enum', 'boolean-auto']:
298 continue
300 if exceptions is not None:
301 if param in exceptions:
302 continue
304 section = None
305 if context == "G":
306 section = "global"
307 elif context == "S":
308 section = "test"
309 else:
310 self.fail("%s has no valid context" % param)
312 value_to_use = arbitrary.get(param_type)
313 if value_to_use is None:
314 self.fail("%s has an invalid type" % param)
316 p = subprocess.Popen(program + ["-s", self.smbconf,
317 "--section-name", section, "--parameter-name", param,
318 "--option", "%s = %s" % (param, value_to_use)],
319 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
320 if p[0].upper().strip() != value_to_use.upper():
321 # currently no way to distinguish command lists
322 if param_type == 'list':
323 if ", ".join(p[0].upper().strip().split()) == value_to_use.upper():
324 continue
326 # currently no way to identify octal
327 if param_type == 'integer':
328 try:
329 if int(value_to_use, 8) == int(p[0].strip(), 8):
330 continue
331 except:
332 pass
334 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
335 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
337 opposite_value = opposite_arbitrary.get(param_type)
338 tempconf = os.path.join(self.tempdir, "tempsmb.conf")
339 g = open(tempconf, 'w')
340 try:
341 towrite = section + "\n"
342 towrite += param + " = " + opposite_value
343 g.write(towrite)
344 finally:
345 g.close()
347 p = subprocess.Popen(program + ["-s", tempconf, "--suppress-prompt",
348 "--option", "%s = %s" % (param, value_to_use)],
349 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
351 os.unlink(tempconf)
353 # testparm doesn't display a value if they are equivalent
354 if (value_to_use.lower() != opposite_value.lower()):
355 for line in p[0].splitlines():
356 if not line.strip().startswith(param):
357 continue
359 value_found = line.split("=")[1].upper().strip()
360 if value_found != value_to_use.upper():
361 # currently no way to distinguish command lists
362 if param_type == 'list':
363 if ", ".join(value_found.split()) == value_to_use.upper():
364 continue
366 # currently no way to identify octal
367 if param_type == 'integer':
368 try:
369 if int(value_to_use, 8) == int(value_found, 8):
370 continue
371 except:
372 pass
374 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
375 failset.add("%s\n Got: %s" % (doc_triple, value_found))
378 if len(failset) > 0:
379 self.fail(self._format_message(failset,
380 "Parameters that were unexpectedly not set:"))
382 def _test_empty(self, program):
383 topdir = os.path.abspath(samba.source_tree_topdir())
385 p = subprocess.Popen(program + ["-s", self.blankconf, "--suppress-prompt"],
386 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
387 output = ""
389 for line in p[0].splitlines():
390 if line.strip().startswith('#'):
391 continue
392 if line.strip().startswith("idmap config *"):
393 continue
394 output += line.strip().lower() + '\n'
396 if output.strip() != '[global]' and output.strip() != '[globals]':
397 self.fail("Testparm returned unexpected output on an empty smb.conf.")