tests:docs: don't try to test parametric option defaults
[metze/samba/wip.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
25 import os
26 import re
27 import subprocess
28 import xml.etree.ElementTree as ET
29
30 class TestCase(samba.tests.TestCaseInTempDir):
31
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))
37
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_documented_tuples(sourcedir, omit_no_default=True):
63     path = os.path.join(sourcedir, "bin", "default", "docs-xml", "smbdotconf")
64     if not os.path.exists(os.path.join(path, "parameters.all.xml")):
65         raise Exception("Unable to find parameters.all.xml")
66     try:
67         p = open(os.path.join(path, "parameters.all.xml"), 'r')
68     except IOError, e:
69         raise Exception("Error opening parameters file")
70     out = p.read()
71
72     root = ET.fromstring(out)
73     for parameter in root:
74         name = parameter.attrib.get("name")
75         param_type = parameter.attrib.get("type")
76         if parameter.attrib.get('removed') == "1":
77            continue
78         values = parameter.findall("value")
79         defaults = []
80         for value in values:
81             if value.attrib.get("type") == "default":
82                 defaults.append(value)
83
84         default_text = None
85         if len(defaults) == 0:
86             if omit_no_default:
87                 continue
88         elif len(defaults) > 1:
89             raise Exception("More than one default found for parameter %s" % name)
90         else:
91             default_text = defaults[0].text
92
93         if default_text is None:
94             default_text = ""
95         context = parameter.attrib.get("context")
96         yield name, default_text, context, param_type
97     p.close()
98
99 class SmbDotConfTests(TestCase):
100
101     # defines the cases where the defaults may differ from the documentation
102     special_cases = set(['log level', 'path', 'ldapsam:trusted', 'spoolss: architecture',
103                          'share:fake_fscaps', 'ldapsam:editposix', 'rpc_daemon:DAEMON',
104                          'rpc_server:SERVER', 'panic action', 'homedir map', 'NIS homedir',
105                          'server string', 'netbios name', 'socket options', 'use mmap',
106                          'ctdbd socket', 'printing', 'printcap name', 'queueresume command',
107                          'queuepause command','lpresume command', 'lppause command',
108                          'lprm command', 'lpq command', 'print command', 'template homedir',
109                          'spoolss: os_major', 'spoolss: os_minor', 'spoolss: os_build',
110                          'max open files', 'fss: prune stale', 'fss: sequence timeout',
111                          'include system krb5 conf', 'rpc server dynamic port range',
112                          'mit kdc command'])
113
114     def setUp(self):
115         super(SmbDotConfTests, self).setUp()
116         # create a minimal smb.conf file for testparm
117         self.smbconf = os.path.join(self.tempdir, "paramtestsmb.conf")
118         f = open(self.smbconf, 'w')
119         try:
120             f.write("""
121 [test]
122    path = /
123 """)
124         finally:
125             f.close()
126
127         self.blankconf = os.path.join(self.tempdir, "emptytestsmb.conf")
128         f = open(self.blankconf, 'w')
129         try:
130             f.write("")
131         finally:
132             f.close()
133
134         self.topdir = os.path.abspath(samba.source_tree_topdir())
135
136         try:
137             self.documented = set(get_documented_parameters(self.topdir))
138         except:
139             self.fail("Unable to load documented parameters")
140
141         try:
142             self.defaults = set(get_documented_tuples(self.topdir))
143         except:
144             self.fail("Unable to load parameters")
145
146         try:
147             self.defaults_all = set(get_documented_tuples(self.topdir, False))
148         except:
149             self.fail("Unable to load parameters")
150
151
152     def tearDown(self):
153         super(SmbDotConfTests, self).tearDown()
154         os.unlink(self.smbconf)
155         os.unlink(self.blankconf)
156
157     def test_default_s3(self):
158         self._test_default(['bin/testparm'])
159         self._set_defaults(['bin/testparm'])
160
161         # registry shares appears to need sudo
162         self._set_arbitrary(['bin/testparm'],
163             exceptions = ['client lanman auth',
164                           'client plaintext auth',
165                           'registry shares',
166                           'smb ports',
167                           'rpc server dynamic port range',
168                           'name resolve order'])
169         self._test_empty(['bin/testparm'])
170
171     def test_default_s4(self):
172         self._test_default(['bin/samba-tool', 'testparm'])
173         self._set_defaults(['bin/samba-tool', 'testparm'])
174         self._set_arbitrary(['bin/samba-tool', 'testparm'],
175             exceptions = ['smb ports',
176                           'rpc server dynamic port range',
177                           'name resolve order'])
178         self._test_empty(['bin/samba-tool', 'testparm'])
179
180     def _test_default(self, program):
181         failset = set()
182         count = 0
183
184         for tuples in self.defaults:
185             param, default, context, param_type = tuples
186
187             if param in self.special_cases:
188                 continue
189             # bad, bad parametric options - we don't have their default values
190             if ':' in param:
191                 continue
192             section = None
193             if context == "G":
194                 section = "global"
195             elif context == "S":
196                 section = "test"
197             else:
198                  self.fail("%s has no valid context" % param)
199             p = subprocess.Popen(program + ["-s", self.smbconf,
200                     "--section-name", section, "--parameter-name", param],
201                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
202             if p[0].upper().strip() != default.upper():
203                 if not (p[0].upper().strip() == "" and default == '""'):
204                     doc_triple = "%s\n      Expected: %s" % (param, default)
205                     failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
206
207         if len(failset) > 0:
208             self.fail(self._format_message(failset,
209                 "Parameters that do not have matching defaults:"))
210
211     def _set_defaults(self, program):
212         failset = set()
213         count = 0
214
215         for tuples in self.defaults:
216             param, default, context, param_type = tuples
217
218             if param in ['printing', 'rpc server dynamic port range']:
219                 continue
220
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                     "--option", "%s = %s" % (param, default)],
231                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.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()))
236
237         if len(failset) > 0:
238             self.fail(self._format_message(failset,
239                 "Parameters that do not have matching defaults:"))
240
241     def _set_arbitrary(self, program, exceptions=None):
242         arbitrary = {'string': 'string', 'boolean': 'yes', 'integer': '5',
243                      'boolean-rev': 'yes',
244                      'cmdlist': 'a b c',
245                      'bytes': '10',
246                      'octal': '0123',
247                      'ustring': 'ustring',
248                      'enum':'', 'boolean-auto': '', 'char': 'a', 'list': 'a, b, c'}
249         opposite_arbitrary = {'string': 'string2', 'boolean': 'no', 'integer': '6',
250                               'boolean-rev': 'no',
251                               'cmdlist': 'd e f',
252                               'bytes': '11',
253                               'octal': '0567',
254                               'ustring': 'ustring2',
255                               'enum':'', 'boolean-auto': '', 'char': 'b', 'list': 'd, e, f'}
256
257         failset = set()
258         count = 0
259
260         for tuples in self.defaults_all:
261             param, default, context, param_type = tuples
262
263             if param in ['printing', 'copy', 'include', 'log level']:
264                 continue
265
266             # currently no easy way to set an arbitrary value for these
267             if param_type in ['enum', 'boolean-auto']:
268                 continue
269
270             if exceptions is not None:
271                 if param in exceptions:
272                     continue
273
274             section = None
275             if context == "G":
276                 section = "global"
277             elif context == "S":
278                 section = "test"
279             else:
280                  self.fail("%s has no valid context" % param)
281
282             value_to_use = arbitrary.get(param_type)
283             if value_to_use is None:
284                 self.fail("%s has an invalid type" % param)
285
286             p = subprocess.Popen(program + ["-s", self.smbconf,
287                     "--section-name", section, "--parameter-name", param,
288                     "--option", "%s = %s" % (param, value_to_use)],
289                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
290             if p[0].upper().strip() != value_to_use.upper():
291                 # currently no way to distinguish command lists
292                 if param_type == 'list':
293                     if ", ".join(p[0].upper().strip().split()) == value_to_use.upper():
294                         continue
295
296                 # currently no way to identify octal
297                 if param_type == 'integer':
298                     try:
299                         if int(value_to_use, 8) == int(p[0].strip(), 8):
300                             continue
301                     except:
302                         pass
303
304                 doc_triple = "%s\n      Expected: %s" % (param, value_to_use)
305                 failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
306
307             opposite_value = opposite_arbitrary.get(param_type)
308             tempconf = os.path.join(self.tempdir, "tempsmb.conf")
309             g = open(tempconf, 'w')
310             try:
311                 towrite = section + "\n"
312                 towrite += param + " = " + opposite_value
313                 g.write(towrite)
314             finally:
315                 g.close()
316
317             p = subprocess.Popen(program + ["-s", tempconf, "--suppress-prompt",
318                     "--option", "%s = %s" % (param, value_to_use)],
319                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
320
321             os.unlink(tempconf)
322
323             # testparm doesn't display a value if they are equivalent
324             if (value_to_use.lower() != opposite_value.lower()):
325                 for line in p[0].splitlines():
326                     if not line.strip().startswith(param):
327                         continue
328
329                     value_found = line.split("=")[1].upper().strip()
330                     if value_found != value_to_use.upper():
331                         # currently no way to distinguish command lists
332                         if param_type == 'list':
333                             if ", ".join(value_found.split()) == value_to_use.upper():
334                                 continue
335
336                         # currently no way to identify octal
337                         if param_type == 'integer':
338                             try:
339                                 if int(value_to_use, 8) == int(value_found, 8):
340                                     continue
341                             except:
342                                 pass
343
344                         doc_triple = "%s\n      Expected: %s" % (param, value_to_use)
345                         failset.add("%s\n      Got: %s" % (doc_triple, value_found))
346
347
348         if len(failset) > 0:
349             self.fail(self._format_message(failset,
350                 "Parameters that were unexpectedly not set:"))
351
352     def _test_empty(self, program):
353         p = subprocess.Popen(program + ["-s", self.blankconf, "--suppress-prompt"],
354                 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
355         output = ""
356
357         for line in p[0].splitlines():
358             if line.strip().startswith('#'):
359                 continue
360             if line.strip().startswith("idmap config *"):
361                 continue
362             output += line.strip().lower() + '\n'
363
364         if output.strip() != '[global]' and output.strip() != '[globals]':
365             self.fail("Testparm returned unexpected output on an empty smb.conf.")