param: Use IDL-based constants for NBT and NBT dgram ports
[Samba.git] / python / samba / tests / docs.py
blobd57701d47d0bdf5af3161f25f0e578f61bc2ef2a
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.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 if re.match(".*P_SEPARATOR.*", l):
76 continue
77 m = re.match("\s*\.label\s*=\s*\"(.*)\".*", l)
78 if not m:
79 continue
81 name = m.group(1)
82 yield name
83 finally:
84 f.close()
86 def get_documented_tuples(sourcedir, omit_no_default=True):
87 path = os.path.join(sourcedir, "bin", "default", "docs-xml", "smbdotconf")
88 if not os.path.exists(os.path.join(path, "parameters.all.xml")):
89 raise Exception("Unable to find parameters.all.xml")
90 try:
91 p = open(os.path.join(path, "parameters.all.xml"), 'r')
92 except IOError, e:
93 raise Exception("Error opening parameters file")
94 out = p.read()
96 root = ET.fromstring(out)
97 for parameter in root:
98 name = parameter.attrib.get("name")
99 param_type = parameter.attrib.get("type")
100 if parameter.attrib.get('removed') == "1":
101 continue
102 values = parameter.findall("value")
103 defaults = []
104 for value in values:
105 if value.attrib.get("type") == "default":
106 defaults.append(value)
108 default_text = None
109 if len(defaults) == 0:
110 if omit_no_default:
111 continue
112 elif len(defaults) > 1:
113 raise Exception("More than one default found for parameter %s" % name)
114 else:
115 default_text = defaults[0].text
117 if default_text is None:
118 default_text = ""
119 context = parameter.attrib.get("context")
120 yield name, default_text, context, param_type
121 p.close()
123 class SmbDotConfTests(TestCase):
125 # defines the cases where the defaults may differ from the documentation
126 special_cases = set(['log level', 'path', 'ldapsam:trusted', 'spoolss: architecture',
127 'share:fake_fscaps', 'ldapsam:editposix', 'rpc_daemon:DAEMON',
128 'rpc_server:SERVER', 'panic action', 'homedir map', 'NIS homedir',
129 'server string', 'netbios name', 'socket options', 'use mmap',
130 'ctdbd socket', 'printing', 'printcap name', 'queueresume command',
131 'queuepause command','lpresume command', 'lppause command',
132 'lprm command', 'lpq command', 'print command', 'template homedir',
133 'spoolss: os_major', 'spoolss: os_minor', 'spoolss: os_build',
134 'max open files'])
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.")