dns.py: Always remove the test zone in tearDown()
[Samba.git] / python / samba / tests / docs.py
bloba6a1a155fa68579ddea50813728421751237da77
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',
135 'max open files'])
137 def setUp(self):
138 super(SmbDotConfTests, self).setUp()
139 # create a minimal smb.conf file for testparm
140 self.smbconf = os.path.join(self.tempdir, "paramtestsmb.conf")
141 f = open(self.smbconf, 'w')
142 try:
143 f.write("""
144 [test]
145 path = /
146 """)
147 finally:
148 f.close()
150 self.blankconf = os.path.join(self.tempdir, "emptytestsmb.conf")
151 f = open(self.blankconf, 'w')
152 try:
153 f.write("")
154 finally:
155 f.close()
157 def tearDown(self):
158 super(SmbDotConfTests, self).tearDown()
159 os.unlink(self.smbconf)
160 os.unlink(self.blankconf)
162 def test_unknown(self):
163 topdir = os.path.abspath(samba.source_tree_topdir())
164 try:
165 documented = set(get_documented_parameters(topdir))
166 except e:
167 self.fail("Unable to load parameters")
168 parameters = set(get_implementation_parameters(topdir))
169 # Filter out parametric options, since we can't find them in the parm
170 # table
171 documented = set([p for p in documented if not ":" in p])
172 unknown = documented.difference(parameters)
173 if len(unknown) > 0:
174 self.fail(self._format_message(unknown,
175 "Parameters that are documented but not in the implementation:"))
177 def test_undocumented(self):
178 topdir = os.path.abspath(samba.source_tree_topdir())
179 try:
180 documented = set(get_documented_parameters(topdir))
181 except:
182 self.fail("Unable to load parameters")
183 parameters = set(get_implementation_parameters(topdir))
184 undocumented = parameters.difference(documented)
185 if len(undocumented) > 0:
186 self.fail(self._format_message(undocumented,
187 "Parameters that are in the implementation but undocumented:"))
189 def test_default_s3(self):
190 self._test_default(['bin/testparm'])
191 self._set_defaults(['bin/testparm'])
193 # registry shares appears to need sudo
194 self._set_arbitrary(['bin/testparm'],
195 exceptions = ['client lanman auth',
196 'client plaintext auth',
197 'registry shares',
198 'smb ports'])
199 self._test_empty(['bin/testparm'])
201 def test_default_s4(self):
202 self._test_default(['bin/samba-tool', 'testparm'])
203 self._set_defaults(['bin/samba-tool', 'testparm'])
204 self._set_arbitrary(['bin/samba-tool', 'testparm'],
205 exceptions = ['smb ports'])
206 self._test_empty(['bin/samba-tool', 'testparm'])
208 def _test_default(self, program):
209 topdir = os.path.abspath(samba.source_tree_topdir())
210 try:
211 defaults = set(get_documented_tuples(topdir))
212 except:
213 self.fail("Unable to load parameters")
214 bindir = os.path.join(topdir, "bin")
215 failset = set()
216 count = 0
218 for tuples in defaults:
219 param, default, context, param_type = tuples
220 if param in self.special_cases:
221 continue
222 section = None
223 if context == "G":
224 section = "global"
225 elif context == "S":
226 section = "test"
227 else:
228 self.fail("%s has no valid context" % param)
229 p = subprocess.Popen(program + ["-s", self.smbconf,
230 "--section-name", section, "--parameter-name", param],
231 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
232 if p[0].upper().strip() != default.upper():
233 if not (p[0].upper().strip() == "" and default == '""'):
234 doc_triple = "%s\n Expected: %s" % (param, default)
235 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
237 if len(failset) > 0:
238 self.fail(self._format_message(failset,
239 "Parameters that do not have matching defaults:"))
241 def _set_defaults(self, program):
242 topdir = os.path.abspath(samba.source_tree_topdir())
243 try:
244 defaults = set(get_documented_tuples(topdir))
245 except:
246 self.fail("Unable to load parameters")
247 bindir = os.path.join(topdir, "bin")
248 failset = set()
249 count = 0
251 for tuples in defaults:
252 param, default, context, param_type = tuples
254 if param in ['printing']:
255 continue
257 section = None
258 if context == "G":
259 section = "global"
260 elif context == "S":
261 section = "test"
262 else:
263 self.fail("%s has no valid context" % param)
264 p = subprocess.Popen(program + ["-s", self.smbconf,
265 "--section-name", section, "--parameter-name", param,
266 "--option", "%s = %s" % (param, default)],
267 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
268 if p[0].upper().strip() != default.upper():
269 if not (p[0].upper().strip() == "" and default == '""'):
270 doc_triple = "%s\n Expected: %s" % (param, default)
271 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
273 if len(failset) > 0:
274 self.fail(self._format_message(failset,
275 "Parameters that do not have matching defaults:"))
277 def _set_arbitrary(self, program, exceptions=None):
278 arbitrary = {'string': 'string', 'boolean': 'yes', 'integer': '5',
279 'enum':'', 'boolean-auto': '', 'char': 'a', 'list': 'a, b, c'}
280 opposite_arbitrary = {'string': 'string2', 'boolean': 'no', 'integer': '6',
281 'enum':'', 'boolean-auto': '', 'char': 'b', 'list': 'd, e, f'}
282 topdir = os.path.abspath(samba.source_tree_topdir())
283 try:
284 defaults = set(get_documented_tuples(topdir, False))
285 except Exception,e:
286 self.fail("Unable to load parameters" + e)
287 bindir = os.path.join(topdir, "bin")
288 failset = set()
289 count = 0
291 for tuples in defaults:
292 param, default, context, param_type = tuples
294 if param in ['printing', 'copy', 'include', 'log level']:
295 continue
297 # currently no easy way to set an arbitrary value for these
298 if param_type in ['enum', 'boolean-auto']:
299 continue
301 if exceptions is not None:
302 if param in exceptions:
303 continue
305 section = None
306 if context == "G":
307 section = "global"
308 elif context == "S":
309 section = "test"
310 else:
311 self.fail("%s has no valid context" % param)
313 value_to_use = arbitrary.get(param_type)
314 if value_to_use is None:
315 self.fail("%s has an invalid type" % param)
317 p = subprocess.Popen(program + ["-s", self.smbconf,
318 "--section-name", section, "--parameter-name", param,
319 "--option", "%s = %s" % (param, value_to_use)],
320 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
321 if p[0].upper().strip() != value_to_use.upper():
322 # currently no way to distinguish command lists
323 if param_type == 'list':
324 if ", ".join(p[0].upper().strip().split()) == value_to_use.upper():
325 continue
327 # currently no way to identify octal
328 if param_type == 'integer':
329 try:
330 if int(value_to_use, 8) == int(p[0].strip(), 8):
331 continue
332 except:
333 pass
335 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
336 failset.add("%s\n Got: %s" % (doc_triple, p[0].upper().strip()))
338 opposite_value = opposite_arbitrary.get(param_type)
339 tempconf = os.path.join(self.tempdir, "tempsmb.conf")
340 g = open(tempconf, 'w')
341 try:
342 towrite = section + "\n"
343 towrite += param + " = " + opposite_value
344 g.write(towrite)
345 finally:
346 g.close()
348 p = subprocess.Popen(program + ["-s", tempconf, "--suppress-prompt",
349 "--option", "%s = %s" % (param, value_to_use)],
350 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
352 os.unlink(tempconf)
354 # testparm doesn't display a value if they are equivalent
355 if (value_to_use.lower() != opposite_value.lower()):
356 for line in p[0].splitlines():
357 if not line.strip().startswith(param):
358 continue
360 value_found = line.split("=")[1].upper().strip()
361 if value_found != value_to_use.upper():
362 # currently no way to distinguish command lists
363 if param_type == 'list':
364 if ", ".join(value_found.split()) == value_to_use.upper():
365 continue
367 # currently no way to identify octal
368 if param_type == 'integer':
369 try:
370 if int(value_to_use, 8) == int(value_found, 8):
371 continue
372 except:
373 pass
375 doc_triple = "%s\n Expected: %s" % (param, value_to_use)
376 failset.add("%s\n Got: %s" % (doc_triple, value_found))
379 if len(failset) > 0:
380 self.fail(self._format_message(failset,
381 "Parameters that were unexpectedly not set:"))
383 def _test_empty(self, program):
384 topdir = os.path.abspath(samba.source_tree_topdir())
386 p = subprocess.Popen(program + ["-s", self.blankconf, "--suppress-prompt"],
387 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=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.")