PEP8: fix E302: expected 2 blank lines, found 1
[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
31 class TestCase(samba.tests.TestCaseInTempDir):
32
33     def _format_message(self, parameters, message):
34         parameters = list(parameters)
35         parameters = map(str, parameters)
36         parameters.sort()
37         return message + '\n\n    %s' % ('\n    '.join(parameters))
38
39
40 def get_documented_parameters(sourcedir):
41     path = os.path.join(sourcedir, "bin", "default", "docs-xml", "smbdotconf")
42     if not os.path.exists(os.path.join(path, "parameters.all.xml")):
43         raise Exception("Unable to find parameters.all.xml")
44     try:
45         p = open(os.path.join(path, "parameters.all.xml"), 'r')
46     except IOError as e:
47         raise Exception("Error opening parameters file")
48     out = p.read()
49
50     root = ET.fromstring(out)
51     for parameter in root:
52         name = parameter.attrib.get('name')
53         if parameter.attrib.get('removed') == "1":
54             continue
55         yield name
56         syn = parameter.findall('synonym')
57         if syn is not None:
58             for sy in syn:
59                 yield sy.text
60     p.close()
61
62
63 def get_documented_tuples(sourcedir, omit_no_default=True):
64     path = os.path.join(sourcedir, "bin", "default", "docs-xml", "smbdotconf")
65     if not os.path.exists(os.path.join(path, "parameters.all.xml")):
66         raise Exception("Unable to find parameters.all.xml")
67     try:
68         p = open(os.path.join(path, "parameters.all.xml"), 'r')
69     except IOError as e:
70         raise Exception("Error opening parameters file")
71     out = p.read()
72
73     root = ET.fromstring(out)
74     for parameter in root:
75         name = parameter.attrib.get("name")
76         param_type = parameter.attrib.get("type")
77         if parameter.attrib.get('removed') == "1":
78             continue
79         values = parameter.findall("value")
80         defaults = []
81         for value in values:
82             if value.attrib.get("type") == "default":
83                 defaults.append(value)
84
85         default_text = None
86         if len(defaults) == 0:
87             if omit_no_default:
88                 continue
89         elif len(defaults) > 1:
90             raise Exception("More than one default found for parameter %s" % name)
91         else:
92             default_text = defaults[0].text
93
94         if default_text is None:
95             default_text = ""
96         context = parameter.attrib.get("context")
97         yield name, default_text, context, param_type
98     p.close()
99
100
101 class SmbDotConfTests(TestCase):
102
103     # defines the cases where the defaults may differ from the documentation
104     special_cases = set(['log level', 'path',
105                          'panic action', 'homedir map', 'NIS homedir',
106                          'server string', 'netbios name', 'socket options', 'use mmap',
107                          'ctdbd socket', 'printing', 'printcap name', 'queueresume command',
108                          'queuepause command', 'lpresume command', 'lppause command',
109                          'lprm command', 'lpq command', 'print command', 'template homedir',
110                          'max open files',
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",
200                                             self.smbconf,
201                                             "--section-name",
202                                             section,
203                                             "--parameter-name",
204                                             param],
205                                  stdout=subprocess.PIPE,
206                                  stderr=subprocess.PIPE,
207                                  cwd=self.topdir).communicate()
208             result = p[0].decode().upper().strip()
209             if result != default.upper():
210                 if not (result == "" and default == '""'):
211                     doc_triple = "%s\n      Expected: %s" % (param, default)
212                     failset.add("%s\n      Got: %s" % (doc_triple, result))
213
214         if len(failset) > 0:
215             self.fail(self._format_message(failset,
216                                            "Parameters that do not have matching defaults:"))
217
218     def _set_defaults(self, program):
219         failset = set()
220         count = 0
221
222         for tuples in self.defaults:
223             param, default, context, param_type = tuples
224
225             if param in ['printing', 'rpc server dynamic port range']:
226                 continue
227
228             section = None
229             if context == "G":
230                 section = "global"
231             elif context == "S":
232                 section = "test"
233             else:
234                 self.fail("%s has no valid context" % param)
235             p = subprocess.Popen(program + ["-s",
236                                             self.smbconf,
237                                             "--section-name",
238                                             section,
239                                             "--parameter-name",
240                                             param,
241                                             "--option",
242                                             "%s = %s" % (param, default)],
243                                  stdout=subprocess.PIPE,
244                                  stderr=subprocess.PIPE,
245                                  cwd=self.topdir).communicate()
246             result = p[0].decode().upper().strip()
247             if result != default.upper():
248                 if not (result == "" and default == '""'):
249                     doc_triple = "%s\n      Expected: %s" % (param, default)
250                     failset.add("%s\n      Got: %s" % (doc_triple, result))
251
252         if len(failset) > 0:
253             self.fail(self._format_message(failset,
254                                            "Parameters that do not have matching defaults:"))
255
256     def _set_arbitrary(self, program, exceptions=None):
257         arbitrary = {'string': 'string', 'boolean': 'yes', 'integer': '5',
258                      'boolean-rev': 'yes',
259                      'cmdlist': 'a b c',
260                      'bytes': '10',
261                      'octal': '0123',
262                      'ustring': 'ustring',
263                      'enum': '', 'boolean-auto': '', 'char': 'a', 'list': 'a, b, c'}
264         opposite_arbitrary = {'string': 'string2', 'boolean': 'no', 'integer': '6',
265                               'boolean-rev': 'no',
266                               'cmdlist': 'd e f',
267                               'bytes': '11',
268                               'octal': '0567',
269                               'ustring': 'ustring2',
270                               'enum': '', 'boolean-auto': '', 'char': 'b', 'list': 'd, e, f'}
271
272         failset = set()
273         count = 0
274
275         for tuples in self.defaults_all:
276             param, default, context, param_type = tuples
277
278             if param in ['printing', 'copy', 'include', 'log level']:
279                 continue
280
281             # currently no easy way to set an arbitrary value for these
282             if param_type in ['enum', 'boolean-auto']:
283                 continue
284
285             if exceptions is not None:
286                 if param in exceptions:
287                     continue
288
289             section = None
290             if context == "G":
291                 section = "global"
292             elif context == "S":
293                 section = "test"
294             else:
295                 self.fail("%s has no valid context" % param)
296
297             value_to_use = arbitrary.get(param_type)
298             if value_to_use is None:
299                 self.fail("%s has an invalid type" % param)
300
301             p = subprocess.Popen(program + ["-s",
302                                             self.smbconf,
303                                             "--section-name",
304                                             section,
305                                             "--parameter-name",
306                                             param,
307                                             "--option",
308                                             "%s = %s" % (param, value_to_use)],
309                                  stdout=subprocess.PIPE,
310                                  stderr=subprocess.PIPE,
311                                  cwd=self.topdir).communicate()
312             result = p[0].decode().upper().strip()
313             if result != value_to_use.upper():
314                 # currently no way to distinguish command lists
315                 if param_type == 'list':
316                     if ", ".join(result.split()) == value_to_use.upper():
317                         continue
318
319                 # currently no way to identify octal
320                 if param_type == 'integer':
321                     try:
322                         if int(value_to_use, 8) == int(p[0].strip(), 8):
323                             continue
324                     except:
325                         pass
326
327                 doc_triple = "%s\n      Expected: %s" % (param, value_to_use)
328                 failset.add("%s\n      Got: %s" % (doc_triple, p[0].upper().strip()))
329
330             opposite_value = opposite_arbitrary.get(param_type)
331             tempconf = os.path.join(self.tempdir, "tempsmb.conf")
332             g = open(tempconf, 'w')
333             try:
334                 towrite = section + "\n"
335                 towrite += param + " = " + opposite_value
336                 g.write(towrite)
337             finally:
338                 g.close()
339
340             p = subprocess.Popen(program + ["-s",
341                                             tempconf,
342                                             "--suppress-prompt",
343                                             "--option",
344                                             "%s = %s" % (param, value_to_use)],
345                                  stdout=subprocess.PIPE,
346                                  stderr=subprocess.PIPE,
347                                  cwd=self.topdir).communicate()
348
349             os.unlink(tempconf)
350
351             # testparm doesn't display a value if they are equivalent
352             if (value_to_use.lower() != opposite_value.lower()):
353                 for line in p[0].decode().splitlines():
354                     if not line.strip().startswith(param):
355                         continue
356
357                     value_found = line.split("=")[1].upper().strip()
358                     if value_found != value_to_use.upper():
359                         # currently no way to distinguish command lists
360                         if param_type == 'list':
361                             if ", ".join(value_found.split()) == value_to_use.upper():
362                                 continue
363
364                         # currently no way to identify octal
365                         if param_type == 'integer':
366                             try:
367                                 if int(value_to_use, 8) == int(value_found, 8):
368                                     continue
369                             except:
370                                 pass
371
372                         doc_triple = "%s\n      Expected: %s" % (param, value_to_use)
373                         failset.add("%s\n      Got: %s" % (doc_triple, value_found))
374
375
376         if len(failset) > 0:
377             self.fail(self._format_message(failset,
378                                            "Parameters that were unexpectedly not set:"))
379
380     def _test_empty(self, program):
381         p = subprocess.Popen(program + ["-s",
382                                         self.blankconf,
383                                         "--suppress-prompt"],
384                              stdout=subprocess.PIPE,
385                              stderr=subprocess.PIPE,
386                              cwd=self.topdir).communicate()
387         output = ""
388
389         for line in p[0].decode().splitlines():
390             if line.strip().startswith('#'):
391                 continue
392             if line.strip().startswith("idmap config *"):
393                 continue
394             output += line.strip().lower() + '\n'
395
396         if output.strip() != '[global]' and output.strip() != '[globals]':
397             self.fail("Testparm returned unexpected output on an empty smb.conf.")