tests:docs: don't load or test the static param_table.
[Samba.git] / python / samba / tests / docs.py
blobe7123b6d9ad00fdf008df76c25bfa5f23c7d0fb4
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_param_table_full(sourcedir, filename="lib/param/param_table_static.c"):
63 # Reading entries from source code
64 f = open(os.path.join(sourcedir, filename), "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():
74 if re.match("^\s*\}\;\s*$", l):
75 # end of the table reached
76 break
78 if re.match("^\s*\{\s*$", l):
79 # start a new entry
80 _label = ""
81 _type = ""
82 _class = ""
83 _offset = ""
84 _special = ""
85 _enum_list = ""
86 _flags = ""
87 continue
89 if re.match("^\s*\},\s*$", l):
90 # finish the entry
91 yield _label, _type, _class, _offset, _special, _enum_list, _flags
92 continue
94 m = re.match("^\s*\.([^\s]+)\s*=\s*(.*),.*", l)
95 if not m:
96 continue
98 attrib = m.group(1)
99 value = m.group(2)
101 if attrib == "label":
102 _label = value
103 elif attrib == "type":
104 _type = value
105 elif attrib == "p_class":
106 _class = value
107 elif attrib == "offset":
108 _offset = value
109 elif attrib == "special":
110 _special = value
111 elif attrib == "enum_list":
112 _special = value
113 elif attrib == "flags":
114 _flags = value
116 finally:
117 f.close()
120 def get_documented_tuples(sourcedir, omit_no_default=True):
121 path = os.path.join(sourcedir, "bin", "default", "docs-xml", "smbdotconf")
122 if not os.path.exists(os.path.join(path, "parameters.all.xml")):
123 raise Exception("Unable to find parameters.all.xml")
124 try:
125 p = open(os.path.join(path, "parameters.all.xml"), 'r')
126 except IOError, e:
127 raise Exception("Error opening parameters file")
128 out = p.read()
130 root = ET.fromstring(out)
131 for parameter in root:
132 name = parameter.attrib.get("name")
133 param_type = parameter.attrib.get("type")
134 if parameter.attrib.get('removed') == "1":
135 continue
136 values = parameter.findall("value")
137 defaults = []
138 for value in values:
139 if value.attrib.get("type") == "default":
140 defaults.append(value)
142 default_text = None
143 if len(defaults) == 0:
144 if omit_no_default:
145 continue
146 elif len(defaults) > 1:
147 raise Exception("More than one default found for parameter %s" % name)
148 else:
149 default_text = defaults[0].text
151 if default_text is None:
152 default_text = ""
153 context = parameter.attrib.get("context")
154 yield name, default_text, context, param_type
155 p.close()
157 class SmbDotConfTests(TestCase):
159 # defines the cases where the defaults may differ from the documentation
160 special_cases = set(['log level', 'path', 'ldapsam:trusted', 'spoolss: architecture',
161 'share:fake_fscaps', 'ldapsam:editposix', 'rpc_daemon:DAEMON',
162 'rpc_server:SERVER', 'panic action', 'homedir map', 'NIS homedir',
163 'server string', 'netbios name', 'socket options', 'use mmap',
164 'ctdbd socket', 'printing', 'printcap name', 'queueresume command',
165 'queuepause command','lpresume command', 'lppause command',
166 'lprm command', 'lpq command', 'print command', 'template homedir',
167 'spoolss: os_major', 'spoolss: os_minor', 'spoolss: os_build',
168 'max open files', 'fss: prune stale', 'fss: sequence timeout'])
170 def setUp(self):
171 super(SmbDotConfTests, self).setUp()
172 # create a minimal smb.conf file for testparm
173 self.smbconf = os.path.join(self.tempdir, "paramtestsmb.conf")
174 f = open(self.smbconf, 'w')
175 try:
176 f.write("""
177 [test]
178 path = /
179 """)
180 finally:
181 f.close()
183 self.blankconf = os.path.join(self.tempdir, "emptytestsmb.conf")
184 f = open(self.blankconf, 'w')
185 try:
186 f.write("")
187 finally:
188 f.close()
190 self.topdir = os.path.abspath(samba.source_tree_topdir())
192 try:
193 self.documented = set(get_documented_parameters(self.topdir))
194 except:
195 self.fail("Unable to load documented parameters")
197 try:
198 self.table_gen = set(get_param_table_full(self.topdir,
199 "bin/default/lib/param/param_table_gen.c"))
200 except:
201 self.fail("Unable to load generated parameter table")
203 try:
204 self.defaults = set(get_documented_tuples(self.topdir))
205 except:
206 self.fail("Unable to load parameters")
208 try:
209 self.defaults_all = set(get_documented_tuples(self.topdir, False))
210 except:
211 self.fail("Unable to load parameters")
214 def tearDown(self):
215 super(SmbDotConfTests, self).tearDown()
216 os.unlink(self.smbconf)
217 os.unlink(self.blankconf)
219 def test_default_s3(self):
220 self._test_default(['bin/testparm'])
221 self._set_defaults(['bin/testparm'])
223 # registry shares appears to need sudo
224 self._set_arbitrary(['bin/testparm'],
225 exceptions = ['client lanman auth',
226 'client plaintext auth',
227 'registry shares',
228 'smb ports'])
229 self._test_empty(['bin/testparm'])
231 def test_default_s4(self):
232 self._test_default(['bin/samba-tool', 'testparm'])
233 self._set_defaults(['bin/samba-tool', 'testparm'])
234 self._set_arbitrary(['bin/samba-tool', 'testparm'],
235 exceptions = ['smb ports'])
236 self._test_empty(['bin/samba-tool', 'testparm'])
238 def _test_default(self, program):
239 failset = set()
240 count = 0
242 for tuples in self.defaults:
243 param, default, context, param_type = tuples
244 if param in self.special_cases:
245 continue
246 section = None
247 if context == "G":
248 section = "global"
249 elif context == "S":
250 section = "test"
251 else:
252 self.fail("%s has no valid context" % param)
253 p = subprocess.Popen(program + ["-s", self.smbconf,
254 "--section-name", section, "--parameter-name", param],
255 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
256 if p[0].upper().strip() != default.upper():
257 if not (p[0].upper().strip() == "" and default == '""'):
258 doc_triple = "%s\n Expected: %s" % (param, default)
259 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
261 if len(failset) > 0:
262 self.fail(self._format_message(failset,
263 "Parameters that do not have matching defaults:"))
265 def _set_defaults(self, program):
266 failset = set()
267 count = 0
269 for tuples in self.defaults:
270 param, default, context, param_type = tuples
272 if param in ['printing']:
273 continue
275 section = None
276 if context == "G":
277 section = "global"
278 elif context == "S":
279 section = "test"
280 else:
281 self.fail("%s has no valid context" % param)
282 p = subprocess.Popen(program + ["-s", self.smbconf,
283 "--section-name", section, "--parameter-name", param,
284 "--option", "%s = %s" % (param, default)],
285 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
286 if p[0].upper().strip() != default.upper():
287 if not (p[0].upper().strip() == "" and default == '""'):
288 doc_triple = "%s\n Expected: %s" % (param, default)
289 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
291 if len(failset) > 0:
292 self.fail(self._format_message(failset,
293 "Parameters that do not have matching defaults:"))
295 def _set_arbitrary(self, program, exceptions=None):
296 arbitrary = {'string': 'string', 'boolean': 'yes', 'integer': '5',
297 'boolean-rev': 'yes',
298 'cmdlist': 'a b c',
299 'bytes': '10',
300 'octal': '0123',
301 'ustring': 'ustring',
302 'enum':'', 'boolean-auto': '', 'char': 'a', 'list': 'a, b, c'}
303 opposite_arbitrary = {'string': 'string2', 'boolean': 'no', 'integer': '6',
304 'boolean-rev': 'no',
305 'cmdlist': 'd e f',
306 'bytes': '11',
307 'octal': '0567',
308 'ustring': 'ustring2',
309 'enum':'', 'boolean-auto': '', 'char': 'b', 'list': 'd, e, f'}
311 failset = set()
312 count = 0
314 for tuples in self.defaults_all:
315 param, default, context, param_type = tuples
317 if param in ['printing', 'copy', 'include', 'log level']:
318 continue
320 # currently no easy way to set an arbitrary value for these
321 if param_type in ['enum', 'boolean-auto']:
322 continue
324 if exceptions is not None:
325 if param in exceptions:
326 continue
328 section = None
329 if context == "G":
330 section = "global"
331 elif context == "S":
332 section = "test"
333 else:
334 self.fail("%s has no valid context" % param)
336 value_to_use = arbitrary.get(param_type)
337 if value_to_use is None:
338 self.fail("%s has an invalid type" % param)
340 p = subprocess.Popen(program + ["-s", self.smbconf,
341 "--section-name", section, "--parameter-name", param,
342 "--option", "%s = %s" % (param, value_to_use)],
343 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
344 if p[0].upper().strip() != value_to_use.upper():
345 # currently no way to distinguish command lists
346 if param_type == 'list':
347 if ", ".join(p[0].upper().strip().split()) == value_to_use.upper():
348 continue
350 # currently no way to identify octal
351 if param_type == 'integer':
352 try:
353 if int(value_to_use, 8) == int(p[0].strip(), 8):
354 continue
355 except:
356 pass
358 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
359 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
361 opposite_value = opposite_arbitrary.get(param_type)
362 tempconf = os.path.join(self.tempdir, "tempsmb.conf")
363 g = open(tempconf, 'w')
364 try:
365 towrite = section + "\n"
366 towrite += param + " = " + opposite_value
367 g.write(towrite)
368 finally:
369 g.close()
371 p = subprocess.Popen(program + ["-s", tempconf, "--suppress-prompt",
372 "--option", "%s = %s" % (param, value_to_use)],
373 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
375 os.unlink(tempconf)
377 # testparm doesn't display a value if they are equivalent
378 if (value_to_use.lower() != opposite_value.lower()):
379 for line in p[0].splitlines():
380 if not line.strip().startswith(param):
381 continue
383 value_found = line.split("=")[1].upper().strip()
384 if value_found != value_to_use.upper():
385 # currently no way to distinguish command lists
386 if param_type == 'list':
387 if ", ".join(value_found.split()) == value_to_use.upper():
388 continue
390 # currently no way to identify octal
391 if param_type == 'integer':
392 try:
393 if int(value_to_use, 8) == int(value_found, 8):
394 continue
395 except:
396 pass
398 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
399 failset.add("%s\n Got: %s" % (doc_triple, value_found))
402 if len(failset) > 0:
403 self.fail(self._format_message(failset,
404 "Parameters that were unexpectedly not set:"))
406 def _test_empty(self, program):
407 p = subprocess.Popen(program + ["-s", self.blankconf, "--suppress-prompt"],
408 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
409 output = ""
411 for line in p[0].splitlines():
412 if line.strip().startswith('#'):
413 continue
414 if line.strip().startswith("idmap config *"):
415 continue
416 output += line.strip().lower() + '\n'
418 if output.strip() != '[global]' and output.strip() != '[globals]':
419 self.fail("Testparm returned unexpected output on an empty smb.conf.")