rpc_server: Allow to configure the port range for RPC services
[kai/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
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
113     def setUp(self):
114         super(SmbDotConfTests, self).setUp()
115         # create a minimal smb.conf file for testparm
116         self.smbconf = os.path.join(self.tempdir, "paramtestsmb.conf")
117         f = open(self.smbconf, 'w')
118         try:
119             f.write("""
120 [test]
121    path = /
122 """)
123         finally:
124             f.close()
125
126         self.blankconf = os.path.join(self.tempdir, "emptytestsmb.conf")
127         f = open(self.blankconf, 'w')
128         try:
129             f.write("")
130         finally:
131             f.close()
132
133         self.topdir = os.path.abspath(samba.source_tree_topdir())
134
135         try:
136             self.documented = set(get_documented_parameters(self.topdir))
137         except:
138             self.fail("Unable to load documented parameters")
139
140         try:
141             self.defaults = set(get_documented_tuples(self.topdir))
142         except:
143             self.fail("Unable to load parameters")
144
145         try:
146             self.defaults_all = set(get_documented_tuples(self.topdir, False))
147         except:
148             self.fail("Unable to load parameters")
149
150
151     def tearDown(self):
152         super(SmbDotConfTests, self).tearDown()
153         os.unlink(self.smbconf)
154         os.unlink(self.blankconf)
155
156     def test_default_s3(self):
157         self._test_default(['bin/testparm'])
158         self._set_defaults(['bin/testparm'])
159
160         # registry shares appears to need sudo
161         self._set_arbitrary(['bin/testparm'],
162             exceptions = ['client lanman auth',
163                           'client plaintext auth',
164                           'registry shares',
165                           'smb ports',
166                           'rpc server dynamic port range'])
167         self._test_empty(['bin/testparm'])
168
169     def test_default_s4(self):
170         self._test_default(['bin/samba-tool', 'testparm'])
171         self._set_defaults(['bin/samba-tool', 'testparm'])
172         self._set_arbitrary(['bin/samba-tool', 'testparm'],
173             exceptions = ['smb ports',
174                           'rpc server dynamic port range'])
175         self._test_empty(['bin/samba-tool', 'testparm'])
176
177     def _test_default(self, program):
178         failset = set()
179         count = 0
180
181         for tuples in self.defaults:
182             param, default, context, param_type = tuples
183
184             if param in self.special_cases:
185                 continue
186             section = None
187             if context == "G":
188                 section = "global"
189             elif context == "S":
190                 section = "test"
191             else:
192                  self.fail("%s has no valid context" % param)
193             p = subprocess.Popen(program + ["-s", self.smbconf,
194                     "--section-name", section, "--parameter-name", param],
195                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
196             if p[0].upper().strip() != default.upper():
197                 if not (p[0].upper().strip() == "" and default == '""'):
198                     doc_triple = "%s\n      Expected: %s" % (param, default)
199                     failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
200
201         if len(failset) > 0:
202             self.fail(self._format_message(failset,
203                 "Parameters that do not have matching defaults:"))
204
205     def _set_defaults(self, program):
206         failset = set()
207         count = 0
208
209         for tuples in self.defaults:
210             param, default, context, param_type = tuples
211
212             if param in ['printing', 'rpc server dynamic port range']:
213                 continue
214
215             section = None
216             if context == "G":
217                 section = "global"
218             elif context == "S":
219                 section = "test"
220             else:
221                  self.fail("%s has no valid context" % param)
222             p = subprocess.Popen(program + ["-s", self.smbconf,
223                     "--section-name", section, "--parameter-name", param,
224                     "--option", "%s = %s" % (param, default)],
225                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
226             if p[0].upper().strip() != default.upper():
227                 if not (p[0].upper().strip() == "" and default == '""'):
228                     doc_triple = "%s\n      Expected: %s" % (param, default)
229                     failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
230
231         if len(failset) > 0:
232             self.fail(self._format_message(failset,
233                 "Parameters that do not have matching defaults:"))
234
235     def _set_arbitrary(self, program, exceptions=None):
236         arbitrary = {'string': 'string', 'boolean': 'yes', 'integer': '5',
237                      'boolean-rev': 'yes',
238                      'cmdlist': 'a b c',
239                      'bytes': '10',
240                      'octal': '0123',
241                      'ustring': 'ustring',
242                      'enum':'', 'boolean-auto': '', 'char': 'a', 'list': 'a, b, c'}
243         opposite_arbitrary = {'string': 'string2', 'boolean': 'no', 'integer': '6',
244                               'boolean-rev': 'no',
245                               'cmdlist': 'd e f',
246                               'bytes': '11',
247                               'octal': '0567',
248                               'ustring': 'ustring2',
249                               'enum':'', 'boolean-auto': '', 'char': 'b', 'list': 'd, e, f'}
250
251         failset = set()
252         count = 0
253
254         for tuples in self.defaults_all:
255             param, default, context, param_type = tuples
256
257             if param in ['printing', 'copy', 'include', 'log level']:
258                 continue
259
260             # currently no easy way to set an arbitrary value for these
261             if param_type in ['enum', 'boolean-auto']:
262                 continue
263
264             if exceptions is not None:
265                 if param in exceptions:
266                     continue
267
268             section = None
269             if context == "G":
270                 section = "global"
271             elif context == "S":
272                 section = "test"
273             else:
274                  self.fail("%s has no valid context" % param)
275
276             value_to_use = arbitrary.get(param_type)
277             if value_to_use is None:
278                 self.fail("%s has an invalid type" % param)
279
280             p = subprocess.Popen(program + ["-s", self.smbconf,
281                     "--section-name", section, "--parameter-name", param,
282                     "--option", "%s = %s" % (param, value_to_use)],
283                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
284             if p[0].upper().strip() != value_to_use.upper():
285                 # currently no way to distinguish command lists
286                 if param_type == 'list':
287                     if ", ".join(p[0].upper().strip().split()) == value_to_use.upper():
288                         continue
289
290                 # currently no way to identify octal
291                 if param_type == 'integer':
292                     try:
293                         if int(value_to_use, 8) == int(p[0].strip(), 8):
294                             continue
295                     except:
296                         pass
297
298                 doc_triple = "%s\n      Expected: %s" % (param, value_to_use)
299                 failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
300
301             opposite_value = opposite_arbitrary.get(param_type)
302             tempconf = os.path.join(self.tempdir, "tempsmb.conf")
303             g = open(tempconf, 'w')
304             try:
305                 towrite = section + "\n"
306                 towrite += param + " = " + opposite_value
307                 g.write(towrite)
308             finally:
309                 g.close()
310
311             p = subprocess.Popen(program + ["-s", tempconf, "--suppress-prompt",
312                     "--option", "%s = %s" % (param, value_to_use)],
313                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
314
315             os.unlink(tempconf)
316
317             # testparm doesn't display a value if they are equivalent
318             if (value_to_use.lower() != opposite_value.lower()):
319                 for line in p[0].splitlines():
320                     if not line.strip().startswith(param):
321                         continue
322
323                     value_found = line.split("=")[1].upper().strip()
324                     if value_found != value_to_use.upper():
325                         # currently no way to distinguish command lists
326                         if param_type == 'list':
327                             if ", ".join(value_found.split()) == value_to_use.upper():
328                                 continue
329
330                         # currently no way to identify octal
331                         if param_type == 'integer':
332                             try:
333                                 if int(value_to_use, 8) == int(value_found, 8):
334                                     continue
335                             except:
336                                 pass
337
338                         doc_triple = "%s\n      Expected: %s" % (param, value_to_use)
339                         failset.add("%s\n      Got: %s" % (doc_triple, value_found))
340
341
342         if len(failset) > 0:
343             self.fail(self._format_message(failset,
344                 "Parameters that were unexpectedly not set:"))
345
346     def _test_empty(self, program):
347         p = subprocess.Popen(program + ["-s", self.blankconf, "--suppress-prompt"],
348                 stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.topdir).communicate()
349         output = ""
350
351         for line in p[0].splitlines():
352             if line.strip().startswith('#'):
353                 continue
354             if line.strip().startswith("idmap config *"):
355                 continue
356             output += line.strip().lower() + '\n'
357
358         if output.strip() != '[global]' and output.strip() != '[globals]':
359             self.fail("Testparm returned unexpected output on an empty smb.conf.")