librpc/ndr: Fix fuzz CI on latest tumbleweed
[Samba.git] / python / samba / tests / usage.py
blob3312bfe37465d1b89cde2aba3bd87d76c11a3f5a
1 # Unix SMB/CIFS implementation.
2 # Copyright © Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 import os
18 import subprocess
19 from samba.tests import TestCase, check_help_consistency
20 import re
21 import stat
23 if 'SRCDIR_ABS' in os.environ:
24 BASEDIR = os.environ['SRCDIR_ABS']
25 else:
26 BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
27 '../../..'))
29 TEST_DIRS = [
30 "bootstrap",
31 "testdata",
32 "ctdb",
33 "dfs_server",
34 "pidl",
35 "auth",
36 "packaging",
37 "python",
38 "include",
39 "nsswitch",
40 "libcli",
41 "coverity",
42 "release-scripts",
43 "testprogs",
44 "bin",
45 "source3",
46 "docs-xml",
47 "buildtools",
48 "file_server",
49 "dynconfig",
50 "source4",
51 "tests",
52 "libds",
53 "selftest",
54 "lib",
55 "script",
56 "traffic",
57 "testsuite",
58 "libgpo",
59 "wintest",
60 "librpc",
64 EXCLUDE_USAGE = {
65 'script/autobuild.py', # defaults to mount /memdisk/
66 'script/bisect-test.py',
67 'ctdb/utils/etcd/ctdb_etcd_lock',
68 'selftest/filter-subunit',
69 'selftest/format-subunit',
70 'bin/gen_output.py', # too much output!
71 'source4/scripting/bin/gen_output.py',
72 'lib/ldb/tests/python/index.py',
73 'lib/ldb/tests/python/api.py',
74 'source4/selftest/tests.py',
75 'buildtools/bin/waf',
76 'selftest/tap2subunit',
77 'script/show_test_time',
78 'source4/scripting/bin/subunitrun',
79 'bin/samba_downgrade_db',
80 'source4/scripting/bin/samba_downgrade_db',
81 'source3/selftest/tests.py',
82 'selftest/tests.py',
83 'python/samba/subunit/run.py',
84 'bin/python/samba/subunit/run.py',
85 'lib/compression/tests/scripts/three-byte-hash',
88 EXCLUDE_HELP = {
89 'selftest/tap2subunit',
90 'wintest/test-s3.py',
91 'wintest/test-s4-howto.py',
95 EXCLUDE_DIRS = {
96 'source3/script/tests',
97 'python/examples',
98 'source4/dsdb/tests/python',
99 'bin/ab',
100 'bin/python/samba/tests',
101 'bin/python/samba/tests/blackbox',
102 'bin/python/samba/tests/dcerpc',
103 'bin/python/samba/tests/krb5',
104 'bin/python/samba/tests/ndr',
105 'python/samba/tests',
106 'python/samba/tests/bin',
107 'python/samba/tests/blackbox',
108 'python/samba/tests/dcerpc',
109 'python/samba/tests/krb5',
110 'python/samba/tests/ndr',
114 def _init_git_file_finder():
115 """Generate a function that quickly answers the question:
116 'is this a git file?'
118 git_file_cache = set()
119 p = subprocess.run(['git',
120 '-C', BASEDIR,
121 'ls-files',
122 '-z'],
123 stdout=subprocess.PIPE)
124 if p.returncode == 0:
125 for fn in p.stdout.split(b'\0'):
126 git_file_cache.add(os.path.join(BASEDIR, fn.decode('utf-8')))
127 return git_file_cache.__contains__
130 is_git_file = _init_git_file_finder()
133 def script_iterator(d=BASEDIR, cache=None,
134 shebang_filter=None,
135 filename_filter=None,
136 subdirs=None):
137 if subdirs is None:
138 subdirs = TEST_DIRS
139 if not cache:
140 safename = re.compile(r'\W+').sub
141 for subdir in subdirs:
142 sd = os.path.join(d, subdir)
143 for root, dirs, files in os.walk(sd, followlinks=False):
144 for fn in files:
145 if fn.endswith('~'):
146 continue
147 if fn.endswith('.inst'):
148 continue
149 ffn = os.path.join(root, fn)
150 try:
151 s = os.stat(ffn)
152 except FileNotFoundError:
153 continue
154 if not s.st_mode & stat.S_IXUSR:
155 continue
156 if not (subdir == 'bin' or is_git_file(ffn)):
157 continue
159 if filename_filter is not None:
160 if not filename_filter(ffn):
161 continue
163 if shebang_filter is not None:
164 try:
165 f = open(ffn, 'rb')
166 except OSError as e:
167 print("could not open %s: %s" % (ffn, e))
168 continue
169 line = f.read(40)
170 f.close()
171 if not shebang_filter(line):
172 continue
174 name = safename('_', fn)
175 while name in cache:
176 name += '_'
177 cache[name] = ffn
179 return cache.items()
181 # For ELF we only look at /bin/* top level.
182 def elf_file_name(fn):
183 fn = fn.partition('bin/')[2]
184 return fn and '/' not in fn and 'test' not in fn and 'ldb' in fn
186 def elf_shebang(x):
187 return x[:4] == b'\x7fELF'
189 elf_cache = {}
190 def elf_iterator():
191 return script_iterator(BASEDIR, elf_cache,
192 shebang_filter=elf_shebang,
193 filename_filter=elf_file_name,
194 subdirs=['bin'])
197 perl_shebang = re.compile(br'#!.+perl').match
199 perl_script_cache = {}
200 def perl_script_iterator():
201 return script_iterator(BASEDIR, perl_script_cache, perl_shebang)
204 python_shebang = re.compile(br'#!.+python').match
206 python_script_cache = {}
207 def python_script_iterator():
208 return script_iterator(BASEDIR, python_script_cache, python_shebang)
211 class PerlScriptUsageTests(TestCase):
212 """Perl scripts run without arguments should print a usage string,
213 not fail with a traceback.
216 @classmethod
217 def initialise(cls):
218 for name, filename in perl_script_iterator():
219 print(name, filename)
222 class PythonScriptUsageTests(TestCase):
223 """Python scripts run without arguments should print a usage string,
224 not fail with a traceback.
227 @classmethod
228 def initialise(cls):
229 for name, filename in python_script_iterator():
230 # We add the actual tests after the class definition so we
231 # can give individual names to them, so we can have a
232 # knownfail list.
233 fn = filename.replace(BASEDIR, '').lstrip('/')
235 if fn in EXCLUDE_USAGE:
236 print("skipping %s (EXCLUDE_USAGE)" % filename)
237 continue
239 if os.path.dirname(fn) in EXCLUDE_DIRS:
240 print("skipping %s (EXCLUDE_DIRS)" % filename)
241 continue
243 def _f(self, filename=filename):
244 print(filename)
245 try:
246 p = subprocess.Popen(['python3', filename],
247 stderr=subprocess.PIPE,
248 stdout=subprocess.PIPE)
249 out, err = p.communicate(timeout=5)
250 except OSError as e:
251 self.fail("Error: %s" % e)
252 except subprocess.SubprocessError as e:
253 self.fail("Subprocess error: %s" % e)
255 err = err.decode('utf-8')
256 out = out.decode('utf-8')
257 self.assertNotIn('Traceback', err)
259 self.assertIn('usage', out.lower() + err.lower(),
260 'stdout:\n%s\nstderr:\n%s' % (out, err))
262 attr = 'test_%s' % name
263 if hasattr(cls, attr):
264 raise RuntimeError(f'Usage test ‘{attr}’ already exists!')
265 setattr(cls, attr, _f)
268 class HelpTestSuper(TestCase):
269 """Python scripts run with -h or --help should print a help string,
270 and exit with success.
272 check_return_code = True
273 check_consistency = True
274 check_contains_usage = True
275 check_multiline = True
276 check_merged_out_and_err = False
278 interpreter = None
280 options_start = None
281 options_end = None
282 def iterator(self):
283 raise NotImplementedError("Subclass this "
284 "and add an iterator function!")
286 @classmethod
287 def initialise(cls):
288 for name, filename in cls.iterator():
289 # We add the actual tests after the class definition so we
290 # can give individual names to them, so we can have a
291 # knownfail list.
292 fn = filename.replace(BASEDIR, '').lstrip('/')
294 if fn in EXCLUDE_HELP:
295 print("skipping %s (EXCLUDE_HELP)" % filename)
296 continue
298 if os.path.dirname(fn) in EXCLUDE_DIRS:
299 print("skipping %s (EXCLUDE_DIRS)" % filename)
300 continue
302 def _f(self, filename=filename):
303 print(filename)
304 for h in ('--help', '-h'):
305 cmd = [filename, h]
306 if self.interpreter:
307 cmd.insert(0, self.interpreter)
308 try:
309 p = subprocess.Popen(cmd,
310 stderr=subprocess.PIPE,
311 stdout=subprocess.PIPE)
312 out, err = p.communicate(timeout=5)
313 except OSError as e:
314 self.fail("Error: %s" % e)
315 except subprocess.SubprocessError as e:
316 self.fail("Subprocess error: %s" % e)
318 err = err.decode('utf-8')
319 out = out.decode('utf-8')
320 if self.check_merged_out_and_err:
321 out = "%s\n%s" % (out, err)
323 outl = out[:500].lower()
324 # NOTE:
325 # These assertions are heuristics, not policy.
326 # If your script fails this test when it shouldn't
327 # just add it to EXCLUDE_HELP above or change the
328 # heuristic.
330 # --help should produce:
331 # * multiple lines of help on stdout (not stderr),
332 # * including a "Usage:" string,
333 # * not contradict itself or repeat options,
334 # * and return success.
335 #print(out.encode('utf8'))
336 #print(err.encode('utf8'))
337 if self.check_consistency:
338 errors = check_help_consistency(out,
339 self.options_start,
340 self.options_end)
341 if errors is not None:
342 self.fail(errors)
344 if self.check_return_code:
345 self.assertEqual(p.returncode, 0,
346 "%s %s\nreturncode should not be %d\n"
347 "err:\n%s\nout:\n%s" %
348 (filename, h, p.returncode, err, out))
349 if self.check_contains_usage:
350 self.assertIn('usage', outl, 'lacks "Usage:"\n')
351 if self.check_multiline:
352 self.assertIn('\n', out, 'expected multi-line output')
354 attr = 'test_%s' % name
355 if hasattr(cls, attr):
356 raise RuntimeError(f'Usage test ‘{attr}’ already exists!')
357 setattr(cls, attr, _f)
360 class PythonScriptHelpTests(HelpTestSuper):
361 """Python scripts run with -h or --help should print a help string,
362 and exit with success.
364 iterator = python_script_iterator
365 interpreter = 'python3'
368 class ElfHelpTests(HelpTestSuper):
369 """ELF binaries run with -h or --help should print a help string,
370 and exit with success.
372 iterator = elf_iterator
373 check_return_code = False
374 check_merged_out_and_err = True
377 PerlScriptUsageTests.initialise()
378 PythonScriptUsageTests.initialise()
379 PythonScriptHelpTests.initialise()
380 ElfHelpTests.initialise()