docs: add test to docs.py to set parameters to some arbitrary value
[obnox/samba/samba-obnox.git] / python / samba / tests / docs.py
1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2012
3 #
4 # Tests for documentation.
5 #
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.
10 #
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.
15 #
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/>.
18 #
19
20 """Tests for presence of documentation."""
21
22 import samba
23 import samba.tests
24 from samba.tests import TestSkipped, TestCaseInTempDir
25
26 import errno
27 import os
28 import re
29 import subprocess
30 import xml.etree.ElementTree as ET
31
32 class TestCase(samba.tests.TestCaseInTempDir):
33
34     def _format_message(self, parameters, message):
35         parameters = list(parameters)
36         parameters.sort()
37         return message + '\n\n    %s' % ('\n    '.join(parameters))
38
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()
48
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()
60
61
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("static struct parm_struct parm_table"):
70                 break
71
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
81
82             name = m.group(1)
83             yield name
84     finally:
85         f.close()
86
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()
96
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)
108
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
117
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()
123
124 class SmbDotConfTests(TestCase):
125
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
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()
147
148     def tearDown(self):
149         super(SmbDotConfTests, self).tearDown()
150         os.unlink(self.smbconf)
151
152     def test_unknown(self):
153         topdir = os.path.abspath(samba.source_tree_topdir())
154         try:
155             documented = set(get_documented_parameters(topdir))
156         except e:
157             self.fail("Unable to load parameters")
158         parameters = set(get_implementation_parameters(topdir))
159         # Filter out parametric options, since we can't find them in the parm
160         # table
161         documented = set([p for p in documented if not ":" in p])
162         unknown = documented.difference(parameters)
163         if len(unknown) > 0:
164             self.fail(self._format_message(unknown,
165                 "Parameters that are documented but not in the implementation:"))
166
167     def test_undocumented(self):
168         topdir = os.path.abspath(samba.source_tree_topdir())
169         try:
170             documented = set(get_documented_parameters(topdir))
171         except:
172             self.fail("Unable to load parameters")
173         parameters = set(get_implementation_parameters(topdir))
174         undocumented = parameters.difference(documented)
175         if len(undocumented) > 0:
176             self.fail(self._format_message(undocumented,
177                 "Parameters that are in the implementation but undocumented:"))
178
179     def test_default_s3(self):
180         self._test_default(['bin/testparm'])
181         self._set_defaults(['bin/testparm'])
182
183         # registry shares appears to need sudo
184         self._set_arbitrary(['bin/testparm'],
185             exceptions = ['client lanman auth',
186                           'client plaintext auth',
187                           'registry shares',
188                           'idmap backend',
189                           'idmap gid',
190                           'idmap uid'])
191
192     def test_default_s4(self):
193         self._test_default(['bin/samba-tool', 'testparm'])
194         self._set_defaults(['bin/samba-tool', 'testparm'])
195         self._set_arbitrary(['bin/samba-tool', 'testparm'])
196
197     def _test_default(self, program):
198         topdir = os.path.abspath(samba.source_tree_topdir())
199         try:
200             defaults = set(get_documented_tuples(topdir))
201         except:
202             self.fail("Unable to load parameters")
203         bindir = os.path.join(topdir, "bin")
204         failset = set()
205         count = 0
206
207         for tuples in defaults:
208             param, default, context, param_type = tuples
209             if param in self.special_cases:
210                 continue
211             section = None
212             if context == "G":
213                 section = "global"
214             elif context == "S":
215                 section = "test"
216             else:
217                  self.fail("%s has no valid context" % param)
218             p = subprocess.Popen(program + ["-s", self.smbconf,
219                     "--section-name", section, "--parameter-name", param],
220                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
221             if p[0].upper().strip() != default.upper():
222                 if not (p[0].upper().strip() == "" and default == '""'):
223                     doc_triple = "%s\n      Expected: %s" % (param, default)
224                     failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
225
226         if len(failset) > 0:
227             self.fail(self._format_message(failset,
228                 "Parameters that do not have matching defaults:"))
229
230     def _set_defaults(self, program):
231         topdir = os.path.abspath(samba.source_tree_topdir())
232         try:
233             defaults = set(get_documented_tuples(topdir))
234         except:
235             self.fail("Unable to load parameters")
236         bindir = os.path.join(topdir, "bin")
237         failset = set()
238         count = 0
239
240         for tuples in defaults:
241             param, default, context, param_type = tuples
242
243             # temporarily remove parametric options - no dump available in s4
244             if param in ['printing'] or ':' in param:
245                 continue
246
247             section = None
248             if context == "G":
249                 section = "global"
250             elif context == "S":
251                 section = "test"
252             else:
253                  self.fail("%s has no valid context" % param)
254             p = subprocess.Popen(program + ["-s", self.smbconf,
255                     "--section-name", section, "--parameter-name", param,
256                     "--option", "%s = %s" % (param, default)],
257                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
258             if p[0].upper().strip() != default.upper():
259                 if not (p[0].upper().strip() == "" and default == '""'):
260                     doc_triple = "%s\n      Expected: %s" % (param, default)
261                     failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
262
263         if len(failset) > 0:
264             self.fail(self._format_message(failset,
265                 "Parameters that do not have matching defaults:"))
266
267     def _set_arbitrary(self, program, exceptions=None):
268         arbitrary = {'string': 'string', 'boolean': 'yes', 'integer': '5',
269                      'enum':'', 'boolean-auto': '', 'char': 'a', 'list': 'a, b, c'}
270
271         topdir = os.path.abspath(samba.source_tree_topdir())
272         try:
273             defaults = set(get_documented_tuples(topdir, False))
274         except Exception,e:
275             self.fail("Unable to load parameters" + e)
276         bindir = os.path.join(topdir, "bin")
277         failset = set()
278         count = 0
279
280         for tuples in defaults:
281             param, default, context, param_type = tuples
282
283             # temporarily remove parametric options - no dump available in s4
284             if param in ['printing', 'copy', 'include', 'log level'] or ':' in param:
285                 continue
286
287             # currently no easy way to set an arbitrary value for these
288             if param_type in ['enum', 'boolean-auto']:
289                 continue
290
291             if exceptions is not None:
292                 if param in exceptions:
293                     continue
294
295             section = None
296             if context == "G":
297                 section = "global"
298             elif context == "S":
299                 section = "test"
300             else:
301                  self.fail("%s has no valid context" % param)
302
303             value_to_use = arbitrary.get(param_type)
304             if value_to_use is None:
305                 self.fail("%s has an invalid type" % param)
306
307             p = subprocess.Popen(program + ["-s", self.smbconf,
308                     "--section-name", section, "--parameter-name", param,
309                     "--option", "%s = %s" % (param, value_to_use)],
310                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
311             if p[0].upper().strip() != value_to_use.upper():
312                 # currently no way to distinguish command lists
313                 if param_type == 'list':
314                     if ", ".join(p[0].upper().strip().split()) == value_to_use.upper():
315                         continue
316
317                 # currently no way to identify octal
318                 if param_type == 'integer':
319                     try:
320                         if int(value_to_use, 8) == int(p[0].strip(), 8):
321                             continue
322                     except:
323                         pass
324
325                 doc_triple = "%s\n      Expected: %s" % (param, value_to_use)
326                 failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
327
328         if len(failset) > 0:
329             self.fail(self._format_message(failset,
330                 "Parameters that were unexpectedly not set:"))