927b3749020116ac029a8bb75db8516f4ca9f9bf
[sfrench/samba-autobuild/.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("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                          'spoolss: os_major', 'spoolss: os_minor', 'spoolss: os_build'])
135
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()
148
149     def tearDown(self):
150         super(SmbDotConfTests, self).tearDown()
151         os.unlink(self.smbconf)
152
153     def test_unknown(self):
154         topdir = os.path.abspath(samba.source_tree_topdir())
155         try:
156             documented = set(get_documented_parameters(topdir))
157         except e:
158             self.fail("Unable to load parameters")
159         parameters = set(get_implementation_parameters(topdir))
160         # Filter out parametric options, since we can't find them in the parm
161         # table
162         documented = set([p for p in documented if not ":" in p])
163         unknown = documented.difference(parameters)
164         if len(unknown) > 0:
165             self.fail(self._format_message(unknown,
166                 "Parameters that are documented but not in the implementation:"))
167
168     def test_undocumented(self):
169         topdir = os.path.abspath(samba.source_tree_topdir())
170         try:
171             documented = set(get_documented_parameters(topdir))
172         except:
173             self.fail("Unable to load parameters")
174         parameters = set(get_implementation_parameters(topdir))
175         undocumented = parameters.difference(documented)
176         if len(undocumented) > 0:
177             self.fail(self._format_message(undocumented,
178                 "Parameters that are in the implementation but undocumented:"))
179
180     def test_default_s3(self):
181         self._test_default(['bin/testparm'])
182         self._set_defaults(['bin/testparm'])
183
184         # registry shares appears to need sudo
185         self._set_arbitrary(['bin/testparm'],
186             exceptions = ['client lanman auth',
187                           'client plaintext auth',
188                           'registry shares',
189                           'idmap backend',
190                           'idmap gid',
191                           'idmap uid'])
192
193     def test_default_s4(self):
194         self._test_default(['bin/samba-tool', 'testparm'])
195         self._set_defaults(['bin/samba-tool', 'testparm'])
196         self._set_arbitrary(['bin/samba-tool', 'testparm'])
197
198     def _test_default(self, program):
199         topdir = os.path.abspath(samba.source_tree_topdir())
200         try:
201             defaults = set(get_documented_tuples(topdir))
202         except:
203             self.fail("Unable to load parameters")
204         bindir = os.path.join(topdir, "bin")
205         failset = set()
206         count = 0
207
208         for tuples in defaults:
209             param, default, context, param_type = tuples
210             if param in self.special_cases:
211                 continue
212             section = None
213             if context == "G":
214                 section = "global"
215             elif context == "S":
216                 section = "test"
217             else:
218                  self.fail("%s has no valid context" % param)
219             p = subprocess.Popen(program + ["-s", self.smbconf,
220                     "--section-name", section, "--parameter-name", param],
221                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
222             if p[0].upper().strip() != default.upper():
223                 if not (p[0].upper().strip() == "" and default == '""'):
224                     doc_triple = "%s\n      Expected: %s" % (param, default)
225                     failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
226
227         if len(failset) > 0:
228             self.fail(self._format_message(failset,
229                 "Parameters that do not have matching defaults:"))
230
231     def _set_defaults(self, program):
232         topdir = os.path.abspath(samba.source_tree_topdir())
233         try:
234             defaults = set(get_documented_tuples(topdir))
235         except:
236             self.fail("Unable to load parameters")
237         bindir = os.path.join(topdir, "bin")
238         failset = set()
239         count = 0
240
241         for tuples in defaults:
242             param, default, context, param_type = tuples
243
244             if param in ['printing']:
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             if param in ['printing', 'copy', 'include', 'log level']:
284                 continue
285
286             # currently no easy way to set an arbitrary value for these
287             if param_type in ['enum', 'boolean-auto']:
288                 continue
289
290             if exceptions is not None:
291                 if param in exceptions:
292                     continue
293
294             section = None
295             if context == "G":
296                 section = "global"
297             elif context == "S":
298                 section = "test"
299             else:
300                  self.fail("%s has no valid context" % param)
301
302             value_to_use = arbitrary.get(param_type)
303             if value_to_use is None:
304                 self.fail("%s has an invalid type" % param)
305
306             p = subprocess.Popen(program + ["-s", self.smbconf,
307                     "--section-name", section, "--parameter-name", param,
308                     "--option", "%s = %s" % (param, value_to_use)],
309                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=topdir).communicate()
310             if p[0].upper().strip() != value_to_use.upper():
311                 # currently no way to distinguish command lists
312                 if param_type == 'list':
313                     if ", ".join(p[0].upper().strip().split()) == value_to_use.upper():
314                         continue
315
316                 # currently no way to identify octal
317                 if param_type == 'integer':
318                     try:
319                         if int(value_to_use, 8) == int(p[0].strip(), 8):
320                             continue
321                     except:
322                         pass
323
324                 doc_triple = "%s\n      Expected: %s" % (param, value_to_use)
325                 failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
326
327         if len(failset) > 0:
328             self.fail(self._format_message(failset,
329                 "Parameters that were unexpectedly not set:"))