]> git.samba.org - garming/samba-autobuild/.git/blob - python/samba/tests/docs.py
ldb: Free memory when repacking database
[garming/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 subprocess
27 import xml.etree.ElementTree as ET
28
29
30 class TestCase(samba.tests.TestCaseInTempDir):
31
32     def _format_message(self, parameters, message):
33         parameters = list(parameters)
34         parameters = list(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 as 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 as 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
100 class SmbDotConfTests(TestCase):
101
102     # defines the cases where the defaults may differ from the documentation
103     special_cases = set([
104         'log level',
105         'path',
106         'panic action',
107         'homedir map',
108         'NIS homedir',
109         'server string',
110         'netbios name',
111         'socket options',
112         'use mmap',
113         'ctdbd socket',
114         'printing',
115         'printcap name',
116         'queueresume command',
117         'queuepause command',
118         'lpresume command',
119         'lppause command',
120         'lprm command',
121         'lpq command',
122         'print command',
123         'template homedir',
124         'max open files',
125         'include system krb5 conf',
126         'rpc server dynamic port range',
127         'mit kdc command',
128         'smbd max async dosmode',
129     ])
130
131     def setUp(self):
132         super(SmbDotConfTests, self).setUp()
133         # create a minimal smb.conf file for testparm
134         self.smbconf = os.path.join(self.tempdir, "paramtestsmb.conf")
135         f = open(self.smbconf, 'w')
136         try:
137             f.write("""
138 [test]
139    path = /
140 """)
141         finally:
142             f.close()
143
144         self.blankconf = os.path.join(self.tempdir, "emptytestsmb.conf")
145         f = open(self.blankconf, 'w')
146         try:
147             f.write("")
148         finally:
149             f.close()
150
151         self.topdir = os.path.abspath(samba.source_tree_topdir())
152
153         try:
154             self.documented = set(get_documented_parameters(self.topdir))
155         except:
156             self.fail("Unable to load documented parameters")
157
158         try:
159             self.defaults = set(get_documented_tuples(self.topdir))
160         except:
161             self.fail("Unable to load parameters")
162
163         try:
164             self.defaults_all = set(get_documented_tuples(self.topdir, False))
165         except:
166             self.fail("Unable to load parameters")
167
168     def tearDown(self):
169         super(SmbDotConfTests, self).tearDown()
170         os.unlink(self.smbconf)
171         os.unlink(self.blankconf)
172
173     def test_default_s3(self):
174         self._test_default(['bin/testparm'])
175         self._set_defaults(['bin/testparm'])
176
177         # registry shares appears to need sudo
178         self._set_arbitrary(['bin/testparm'],
179             exceptions = ['client lanman auth',
180                           'client plaintext auth',
181                           'registry shares',
182                           'smb ports',
183                           'rpc server dynamic port range',
184                           'name resolve order',
185                           'clustering'])
186         self._test_empty(['bin/testparm'])
187
188     def test_default_s4(self):
189         self._test_default(['bin/samba-tool', 'testparm'])
190         self._set_defaults(['bin/samba-tool', 'testparm'])
191         self._set_arbitrary(['bin/samba-tool', 'testparm'],
192                             exceptions=['smb ports',
193                                         'rpc server dynamic port range',
194                                         'name resolve order'])
195         self._test_empty(['bin/samba-tool', 'testparm'])
196
197     def _test_default(self, program):
198
199         if program[0] == 'bin/samba-tool' and os.getenv("PYTHON", None):
200             program = [os.environ["PYTHON"]] + program
201
202         failset = set()
203
204         for tuples in self.defaults:
205             param, default, context, param_type = tuples
206
207             if param in self.special_cases:
208                 continue
209             # bad, bad parametric options - we don't have their default values
210             if ':' in param:
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",
220                                             self.smbconf,
221                                             "--section-name",
222                                             section,
223                                             "--parameter-name",
224                                             param],
225                                  stdout=subprocess.PIPE,
226                                  stderr=subprocess.PIPE,
227                                  cwd=self.topdir).communicate()
228             result = p[0].decode().upper().strip()
229             if result != default.upper():
230                 if not (result == "" and default == '""'):
231                     doc_triple = "%s\n      Expected: %s" % (param, default)
232                     failset.add("%s\n      Got: %s" % (doc_triple, result))
233
234         if len(failset) > 0:
235             self.fail(self._format_message(failset,
236                                            "Parameters that do not have matching defaults:"))
237
238     def _set_defaults(self, program):
239
240         if program[0] == 'bin/samba-tool' and os.getenv("PYTHON", None):
241             program = [os.environ["PYTHON"]] + program
242
243         failset = set()
244
245         for tuples in self.defaults:
246             param, default, context, param_type = tuples
247
248             exceptions = set([
249                 'printing',
250                 'rpc server dynamic port range',
251                 'smbd max async dosmode',
252             ])
253
254             if param in exceptions:
255                 continue
256
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",
265                                             self.smbconf,
266                                             "--section-name",
267                                             section,
268                                             "--parameter-name",
269                                             param,
270                                             "--option",
271                                             "%s = %s" % (param, default)],
272                                  stdout=subprocess.PIPE,
273                                  stderr=subprocess.PIPE,
274                                  cwd=self.topdir).communicate()
275             result = p[0].decode().upper().strip()
276             if result != default.upper():
277                 if not (result == "" and default == '""'):
278                     doc_triple = "%s\n      Expected: %s" % (param, default)
279                     failset.add("%s\n      Got: %s" % (doc_triple, result))
280
281         if len(failset) > 0:
282             self.fail(self._format_message(failset,
283                                            "Parameters that do not have matching defaults:"))
284
285     def _set_arbitrary(self, program, exceptions=None):
286
287         if program[0] == 'bin/samba-tool' and os.getenv("PYTHON", None):
288             program = [os.environ["PYTHON"]] + program
289
290         arbitrary = {'string': 'string', 'boolean': 'yes', 'integer': '5',
291                      'boolean-rev': 'yes',
292                      'cmdlist': 'a b c',
293                      'bytes': '10',
294                      'octal': '0123',
295                      'ustring': 'ustring',
296                      'enum': '', 'boolean-auto': '', 'char': 'a', 'list': 'a, b, c'}
297         opposite_arbitrary = {'string': 'string2', 'boolean': 'no', 'integer': '6',
298                               'boolean-rev': 'no',
299                               'cmdlist': 'd e f',
300                               'bytes': '11',
301                               'octal': '0567',
302                               'ustring': 'ustring2',
303                               'enum': '', 'boolean-auto': '', 'char': 'b', 'list': 'd, e, f'}
304
305         failset = set()
306
307         for tuples in self.defaults_all:
308             param, default, context, param_type = tuples
309
310             if param in ['printing', 'copy', 'include', 'log level']:
311                 continue
312
313             # currently no easy way to set an arbitrary value for these
314             if param_type in ['enum', 'boolean-auto']:
315                 continue
316
317             if exceptions is not None:
318                 if param in exceptions:
319                     continue
320
321             section = None
322             if context == "G":
323                 section = "global"
324             elif context == "S":
325                 section = "test"
326             else:
327                 self.fail("%s has no valid context" % param)
328
329             value_to_use = arbitrary.get(param_type)
330             if value_to_use is None:
331                 self.fail("%s has an invalid type" % param)
332
333             p = subprocess.Popen(program + ["-s",
334                                             self.smbconf,
335                                             "--section-name",
336                                             section,
337                                             "--parameter-name",
338                                             param,
339                                             "--option",
340                                             "%s = %s" % (param, value_to_use)],
341                                  stdout=subprocess.PIPE,
342                                  stderr=subprocess.PIPE,
343                                  cwd=self.topdir).communicate()
344             result = p[0].decode().upper().strip()
345             if result != value_to_use.upper():
346                 # currently no way to distinguish command lists
347                 if param_type == 'list':
348                     if ", ".join(result.split()) == value_to_use.upper():
349                         continue
350
351                 # currently no way to identify octal
352                 if param_type == 'integer':
353                     try:
354                         if int(value_to_use, 8) == int(p[0].strip(), 8):
355                             continue
356                     except:
357                         pass
358
359                 doc_triple = "%s\n      Expected: %s" % (param, value_to_use)
360                 failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
361
362             opposite_value = opposite_arbitrary.get(param_type)
363             tempconf = os.path.join(self.tempdir, "tempsmb.conf")
364             g = open(tempconf, 'w')
365             try:
366                 towrite = section + "\n"
367                 towrite += param + " = " + opposite_value
368                 g.write(towrite)
369             finally:
370                 g.close()
371
372             p = subprocess.Popen(program + ["-s",
373                                             tempconf,
374                                             "--suppress-prompt",
375                                             "--option",
376                                             "%s = %s" % (param, value_to_use)],
377                                  stdout=subprocess.PIPE,
378                                  stderr=subprocess.PIPE,
379                                  cwd=self.topdir).communicate()
380
381             os.unlink(tempconf)
382
383             # testparm doesn't display a value if they are equivalent
384             if (value_to_use.lower() != opposite_value.lower()):
385                 for line in p[0].decode().splitlines():
386                     if not line.strip().startswith(param):
387                         continue
388
389                     value_found = line.split("=")[1].upper().strip()
390                     if value_found != value_to_use.upper():
391                         # currently no way to distinguish command lists
392                         if param_type == 'list':
393                             if ", ".join(value_found.split()) == value_to_use.upper():
394                                 continue
395
396                         # currently no way to identify octal
397                         if param_type == 'integer':
398                             try:
399                                 if int(value_to_use, 8) == int(value_found, 8):
400                                     continue
401                             except:
402                                 pass
403
404                         doc_triple = "%s\n      Expected: %s" % (param, value_to_use)
405                         failset.add("%s\n      Got: %s" % (doc_triple, value_found))
406
407         if len(failset) > 0:
408             self.fail(self._format_message(failset,
409                                            "Parameters that were unexpectedly not set:"))
410
411     def _test_empty(self, program):
412
413         if program[0] == 'bin/samba-tool' and os.getenv("PYTHON", None):
414             program = [os.environ["PYTHON"]] + program
415
416         p = subprocess.Popen(program + ["-s",
417                                         self.blankconf,
418                                         "--suppress-prompt"],
419                              stdout=subprocess.PIPE,
420                              stderr=subprocess.PIPE,
421                              cwd=self.topdir).communicate()
422         output = ""
423
424         for line in p[0].decode().splitlines():
425             if line.strip().startswith('#'):
426                 continue
427             if line.strip().startswith("idmap config *"):
428                 continue
429             output += line.strip().lower() + '\n'
430
431         if output.strip() != '[global]' and output.strip() != '[globals]':
432             self.fail("Testparm returned unexpected output on an empty smb.conf.")