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