selftest.run: Factor out expand_command_list.
[samba.git] / selftest / selftest.py
1 #!/usr/bin/python -u
2 # Bootstrap Samba and run a number of tests against it.
3 # Copyright (C) 2005-2012 Jelmer Vernooij <jelmer@samba.org>
4 # Copyright (C) 2007-2009 Stefan Metzmacher <metze@samba.org>
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 import atexit
20 from cStringIO import StringIO
21 import datetime
22 import iso8601
23 import os
24 import sys
25 import signal
26 import shutil
27 import subprocess
28 import subunit
29 import tempfile
30 import traceback
31 import warnings
32
33 import optparse
34
35 sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
36
37 from selftest import (
38     socket_wrapper,
39     subunithelper,
40     testlist,
41     )
42 from selftest.run import (
43     expand_environment_strings,
44     expand_command_list,
45     )
46 from selftest.target import (
47     EnvironmentManager,
48     NoneTarget,
49     UnsupportedEnvironment,
50     )
51
52 includes = ()
53 excludes = ()
54
55 def now():
56     return datetime.datetime.utcnow().replace(tzinfo=iso8601.iso8601.Utc())
57
58 def read_excludes(fn):
59     excludes.extend(testlist.read_test_regexes(fn))
60
61 def read_includes(fn):
62     includes.extend(testlist.read_test_regexes(fn))
63
64 parser = optparse.OptionParser("TEST-REGEXES")
65 parser.add_option("--target", type="choice", choices=["samba", "samba3", "none"], default="samba", help="Samba version to target")
66 parser.add_option("--quick", help="run quick overall test", action="store_true", default=False)
67 parser.add_option("--list", help="list available tests", action="store_true", default=False)
68 parser.add_option("--socket-wrapper", help="enable socket wrapper", action="store_true", default=False)
69 parser.add_option("--socket-wrapper-pcap", help="save traffic to pcap directories", type="str")
70 parser.add_option("--socket-wrapper-keep-pcap", help="keep all pcap files, not just those for tests that failed", action="store_true", default=False)
71 parser.add_option("--one", help="abort when the first test fails", action="store_true", default=False)
72 parser.add_option("--exclude", action="callback", help="Add file to exclude files", callback=read_excludes)
73 parser.add_option("--include", action="callback", help="Add file to include files", callback=read_includes)
74 parser.add_option("--testenv", help="run a shell in the requested test environment", action="store_true", default=False)
75 parser.add_option("--resetup-environment", help="Re-setup environment", action="store_true", default=False)
76 parser.add_option("--binary-mapping", help="Map binaries to use", type=str)
77 parser.add_option("--load-list", help="Load list of tests to load from a file", type=str)
78 parser.add_option("--prefix", help="prefix to run tests in", type=str, default="./st")
79 parser.add_option("--srcdir", type=str, default=".", help="source directory")
80 parser.add_option("--bindir", type=str, default="./bin", help="binaries directory")
81 parser.add_option("--testlist", type=str, action="append", help="file to read available tests from")
82 parser.add_option("--ldap", help="back samba onto specified ldap server", choices=["openldap", "fedora-ds"], type="choice")
83
84 opts, args = parser.parse_args()
85
86 subunit_ops = subunithelper.SubunitOps(sys.stdout)
87
88 def handle_signal(sig, frame):
89     sys.stderr.write("Exiting early because of signal %s.\n" % sig)
90     sys.exit(1)
91
92 for sig in (signal.SIGINT, signal.SIGQUIT, signal.SIGTERm, signal.SIGPIPE):
93     signal.signal(sig, handle_signal)
94
95 def skip(name):
96     return testlist.find_in_list(excludes, name)
97
98 def setup_pcap(name):
99     if (not opts.socket_wrapper_pcap or
100         not os.environ.get("SOCKET_WRAPPER_PCAP_DIR")):
101         return
102
103     fname = "".join([x for x in name if x.isalnum() or x == '-'])
104
105     pcap_file = os.path.join(
106         os.environ["SOCKET_WRAPPER_PCAP_DIR"], "%s.pcap" % fname)
107
108     socket_wrapper.setup_pcap(pcap_file)
109     return pcap_file
110
111
112 def cleanup_pcap(pcap_file, exit_code):
113     if not opts.socket_wrapper_pcap:
114         return
115     if opts.socket_wrapper_keep_pcap:
116         return
117     if exitcode == 0:
118         return
119     if pcap_file is None:
120         return
121
122     os.unlink(pcap_file)
123
124
125 def run_testsuite(envname, name, cmd):
126     """Run a single testsuite.
127
128     :param envname: Name of the environment to ru nin
129     :param name: Name of the testsuite
130     :param cmd: Name of the (fully expanded) command to run
131     :return: exitcode of the command
132     """
133     pcap_file = setup_pcap(name)
134
135     subunit_ops.start_testsuite(name)
136     subunit_ops.progress(None, subunit.PROGRESS_PUSH)
137     subunit_ops.time(now())
138     try:
139         exitcode = subprocess.call(cmd, shell=True)
140     except Exception, e:
141         subunit_ops.time(now())
142         subunit_ops.progress(None, subunit.PROGRESS_POP)
143         subunit_ops.end_testsuite(name, "error", "Unable to run %r: %s" % (cmd, e))
144         sys.exit(1)
145
146     subunit_ops.time(now())
147     subunit_ops.progress(None, subunit.PROGRESS_POP)
148
149     envlog = env_manager.getlog_env(envname)
150     if envlog != "":
151         sys.stdout.write("envlog: %s\n" % envlog)
152
153     sys.stdout.write("command: %s\n" % cmd)
154     sys.stdout.write("expanded command: %s\n" % expand_environment_strings(cmd, os.environ))
155
156     if exitcode == 0:
157         subunit_ops.end_testsuite(name, "success")
158     else:
159         subunit_ops.end_testsuite(name, "failure", "Exit code was %d" % exitcode)
160
161     cleanup_pcap(pcap_file, exitcode)
162
163     if not opts.socket_wrapper_keep_pcap and pcap_file is not None:
164         sys.stdout.write("PCAP FILE: %s\n" % pcap_file)
165
166     if exitcode != 0 and opts.one:
167         sys.exit(1)
168
169     return exitcode
170
171 if opts.list and opts.testenv:
172     sys.stderr.write("--list and --testenv are mutually exclusive\n")
173     sys.exit(1)
174
175 tests = args
176
177 # quick hack to disable rpc validation when using valgrind - it is way too slow
178 if not os.environ.get("VALGRIND"):
179     os.environ["VALIDATE"] = "validate"
180     os.environ["MALLOC_CHECK_"] = "2"
181
182 # make all our python scripts unbuffered
183 os.environ["PYTHONUNBUFFERED"] = "1"
184
185 bindir_abs = os.path.abspath(opts.bindir)
186
187 # Backwards compatibility:
188 if os.environ.get("TEST_LDAP") == "yes":
189     if os.environ.get("FEDORA_DS_ROOT"):
190         ldap = "fedora-ds"
191     else:
192         ldap = "openldap"
193
194 torture_maxtime = int(os.getenv("TORTURE_MAXTIME", "1200"))
195 if opts.ldap:
196     # LDAP is slow
197     torture_maxtime *= 2
198
199 prefix = os.path.normpath(opts.prefix)
200
201 if prefix == "":
202     raise Exception("using an empty prefix isn't allowed")
203
204 # Ensure we have the test prefix around.
205 #
206 # We need restrictive permissions on this as some subdirectories in this tree
207 # will have wider permissions (ie 0777) and this would allow other users on the
208 # host to subvert the test process.
209 if not os.path.isdir(prefix):
210     os.mkdir(prefix, 0700)
211 else:
212     os.chmod(prefix, 0700)
213
214 prefix_abs = os.path.abspath(prefix)
215 tmpdir_abs = os.path.abspath(os.path.join(prefix_abs, "tmp"))
216 if not os.path.isdir(tmpdir_abs):
217     os.mkdir(tmpdir_abs, 0777)
218
219 srcdir_abs = os.path.abspath(opts.srcdir)
220
221 if prefix_abs == "":
222     raise Exception("using an empty absolute prefix isn't allowed")
223 if prefix_abs == "/":
224     raise Exception("using '/' as absolute prefix isn't allowed")
225
226 os.environ["PREFIX"] = prefix
227 os.environ["KRB5CCNAME"] = os.path.join(prefix, "krb5ticket")
228 os.environ["PREFIX_ABS"] = prefix_abs
229 os.environ["SRCDIR"] = opts.srcdir
230 os.environ["SRCDIR_ABS"] = srcdir_abs
231 os.environ["BINDIR"] = bindir_abs
232
233 tls_enabled = not opts.quick
234 if tls_enabled:
235     os.environ["TLS_ENABLED"] = "yes"
236 else:
237     os.environ["TLS_ENABLED"] = "no"
238
239 def prefix_pathvar(name, newpath):
240     if name in os.environ:
241         os.environ[name] = "%s:%s" % (newpath, os.environ[name])
242     else:
243         os.environ[name] = newpath
244 prefix_pathvar("PKG_CONFIG_PATH", os.path.join(bindir_abs, "pkgconfig"))
245 prefix_pathvar("PYTHONPATH", os.path.join(bindir_abs, "python"))
246
247 if opts.socket_wrapper_keep_pcap:
248     # Socket wrapper keep pcap implies socket wrapper pcap
249     opts.socket_wrapper_pcap = True
250
251 if opts.socket_wrapper_pcap:
252     # Socket wrapper pcap implies socket wrapper
253     opts.socket_wrapper = True
254
255 if opts.socket_wrapper:
256     socket_wrapper_dir = socket_wrapper.setup_dir(os.path.join(prefix_abs, "w"), opts.socket_wrapper_pcap)
257     sys.stdout.write("SOCKET_WRAPPER_DIR=%s\n" % socket_wrapper_dir)
258 elif not opts.list:
259     if os.getuid() != 0:
260         warnings.warn("not using socket wrapper, but also not running as root. Will not be able to listen on proper ports")
261
262 testenv_default = "none"
263
264 if opts.binary_mapping:
265     binary_mapping = dict([l.split(":") for l in opts.binary_mapping.split(",")])
266     os.environ["BINARY_MAPPING"] = opts.binary_mapping
267 else:
268     binary_mapping = {}
269     os.environ["BINARY_MAPPING"] = ""
270
271 # After this many seconds, the server will self-terminate.  All tests
272 # must terminate in this time, and testenv will only stay alive this
273 # long
274
275 server_maxtime = 7500
276 if os.environ.get("SMBD_MAXTIME", ""):
277     server_maxtime = int(os.environ["SMBD_MAXTIME"])
278
279
280 def has_socket_wrapper(bindir):
281     """Check if Samba has been built with socket wrapper support.
282     """
283     f = StringIO()
284     subprocess.check_call([os.path.join(bindir, "smbd"), "-b"], stdout=f)
285     for l in f.readlines():
286         if "SOCKET_WRAPPER" in l:
287             return True
288     return False
289
290
291 if not opts.list:
292     if opts.target == "samba":
293         if opts.socket_wrapper and not has_socket_wrapper(opts.bindir):
294             sys.stderr.write("You must include --enable-socket-wrapper when compiling Samba in order to execute 'make test'.  Exiting....\n")
295             sys.exit(1)
296         testenv_default = "dc"
297         from selftest.target.samba import Samba
298         target = Samba(opts.bindir, binary_mapping, ldap, opts.srcdir, server_maxtime)
299     elif opts.target == "samba3":
300         if opts.socket_wrapper and not has_socket_wrapper(opts.bindir):
301             sys.stderr.write("You must include --enable-socket-wrapper when compiling Samba in order to execute 'make test'.  Exiting....\n")
302             sys.exit(1)
303         testenv_default = "member"
304         from selftest.target.samba3 import Samba3
305         target = Samba3(opts.bindir, binary_mapping, srcdir_abs, server_maxtime)
306     elif opts.target == "none":
307         testenv_default = "none"
308         target = NoneTarget()
309
310     env_manager = EnvironmentManager(target)
311     atexit.register(env_manager.teardown_all)
312
313 interfaces = ",".join([
314     "127.0.0.11/8",
315     "127.0.0.12/8",
316     "127.0.0.13/8",
317     "127.0.0.14/8",
318     "127.0.0.15/8",
319     "127.0.0.16/8"])
320
321 clientdir = os.path.join(prefix_abs, "client")
322
323 conffile = os.path.join(clientdir, "client.conf")
324 os.environ["SMB_CONF_PATH"] = conffile
325
326 def write_clientconf(conffile, clientdir, vars):
327     if not os.path.isdir(clientdir):
328         os.mkdir(clientdir, 0777)
329
330     for n in ["private", "lockdir", "statedir", "cachedir"]:
331         p = os.path.join(clientdir, n)
332         if os.path.isdir(p):
333             shutil.rmtree(p)
334         os.mkdir(p, 0777)
335
336     # this is ugly, but the ncalrpcdir needs exactly 0755
337     # otherwise tests fail.
338     mask = os.umask(0022)
339
340     for n in ["ncalrpcdir", "ncalrpcdir/np"]:
341         p = os.path.join(clientdir, n)
342         if os.path.isdir(p):
343             shutil.rmtree(p)
344         os.mkdir(p, 0777)
345     os.umask(mask)
346
347     settings = {
348         "netbios name": "client",
349         "private dir": os.path.join(clientdir, "private"),
350         "lock dir": os.path.join(clientdir, "lockdir"),
351         "state directory": os.path.join(clientdir, "statedir"),
352         "cache directory": os.path.join(clientdir, "cachedir"),
353         "ncalrpc dir": os.path.join(clientdir, "ncalrpcdir"),
354         "name resolve order": "file bcast",
355         "panic action": os.path.join(os.path.dirname(__file__), "gdb_backtrace \%d"),
356         "max xmit": "32K",
357         "notify:inotify": "false",
358         "ldb:nosync": "true",
359         "system:anonymous": "true",
360         "client lanman auth": "Yes",
361         "log level": "1",
362         "torture:basedir": clientdir,
363         # We don't want to pass our self-tests if the PAC code is wrong
364         "gensec:require_pac": "true",
365         "resolv:host file": os.path.join(prefix_abs, "dns_host_file"),
366         # We don't want to run 'speed' tests for very long
367         "torture:timelimit": "1",
368         }
369
370     if "DOMAIN" in vars:
371         settings["workgroup"] = vars["DOMAIN"]
372     if "REALM" in vars:
373         settings["realm"] = vars["REALM"]
374     if opts.socket_wrapper:
375         settings["interfaces"] = interfaces
376
377     f = open(conffile, 'w')
378     try:
379         f.write("[global]\n")
380         for item in settings.iteritems():
381             f.write("\t%s = %s\n" % item)
382     finally:
383         f.close()
384
385 todo = []
386
387 if not opts.testlist:
388     sys.stderr.write("No testlists specified\n")
389     sys.exit(1)
390
391 os.environ["SELFTEST_PREFIX"] = prefix_abs
392 os.environ["SELFTEST_TMPDIR"] = tmpdir_abs
393 os.environ["TEST_DATA_PREFIX"] = tmpdir_abs
394 if opts.socket_wrapper:
395     os.environ["SELFTEST_INTERFACES"] = interfaces
396 else:
397     os.environ["SELFTEST_INTERFACES"] = ""
398 if opts.quick:
399     os.environ["SELFTEST_QUICK"] = "1"
400 else:
401     os.environ["SELFTEST_QUICK"] = ""
402 os.environ["SELFTEST_MAXTIME"] = str(torture_maxtime)
403
404
405 def open_file_or_pipe(path, mode):
406     if path.endswith("|"):
407         return os.popen(path[:-1], mode)
408     return open(path, mode)
409
410 available = []
411 for fn in opts.testlist:
412     inf = open_file_or_pipe(fn, 'r')
413     try:
414         for testsuite in testlist.read_testlist(inf, sys.stdout):
415             if not testlist.should_run_test(tests, testsuite):
416                 continue
417             name = testsuite[0]
418             if includes is not None and testlist.find_in_list(includes, name) is not None:
419                 continue
420             available.append(testsuite)
421     finally:
422         inf.close()
423
424 if opts.load_list:
425     restricted_mgr = testlist.RestrictedTestManager.from_path(opts.load_list)
426 else:
427     restricted_mgr = None
428
429
430 for testsuite in available:
431     name = testsuite[0]
432     skipreason = skip(name)
433     if restricted_mgr is not None:
434         match = restricted_mgr.should_run_testsuite(name)
435         if match == []:
436             continue
437     else:
438         match = None
439     if skipreason is not None:
440         if not opts.list:
441             subunit_ops.skip_testsuite(name, skipreason)
442     else:
443         todo.append(testsuite + (match,))
444
445 if restricted_mgr is not None:
446     for name in restricted_mgr.iter_unused():
447         sys.stdout.write("No test or testsuite found matching %s\n" % name)
448 if todo == []:
449     sys.stderr.write("No tests to run\n")
450     sys.exit(1)
451
452 suitestotal = len(todo)
453
454 if not opts.list:
455     subunit_ops.progress(suitestotal, subunit.PROGRESS_SET)
456     subunit_ops.time(now())
457
458 exported_envvars = [
459     # domain stuff
460     "DOMAIN",
461     "REALM",
462
463     # domain controller stuff
464     "DC_SERVER",
465     "DC_SERVER_IP",
466     "DC_NETBIOSNAME",
467     "DC_NETBIOSALIAS",
468
469     # domain member
470     "MEMBER_SERVER",
471     "MEMBER_SERVER_IP",
472     "MEMBER_NETBIOSNAME",
473     "MEMBER_NETBIOSALIAS",
474
475     # rpc proxy controller stuff
476     "RPC_PROXY_SERVER",
477     "RPC_PROXY_SERVER_IP",
478     "RPC_PROXY_NETBIOSNAME",
479     "RPC_PROXY_NETBIOSALIAS",
480
481     # domain controller stuff for Vampired DC
482     "VAMPIRE_DC_SERVER",
483     "VAMPIRE_DC_SERVER_IP",
484     "VAMPIRE_DC_NETBIOSNAME",
485     "VAMPIRE_DC_NETBIOSALIAS",
486
487     # server stuff
488     "SERVER",
489     "SERVER_IP",
490     "NETBIOSNAME",
491     "NETBIOSALIAS",
492
493     # user stuff
494     "USERNAME",
495     "USERID",
496     "PASSWORD",
497     "DC_USERNAME",
498     "DC_PASSWORD",
499
500     # misc stuff
501     "KRB5_CONFIG",
502     "WINBINDD_SOCKET_DIR",
503     "WINBINDD_PRIV_PIPE_DIR",
504     "NMBD_SOCKET_DIR",
505     "LOCAL_PATH"
506 ]
507
508 def exported_envvars_str(testenv_vars):
509     out = ""
510
511     for n in exported_envvars:
512         if not n in testenv_vars:
513             continue
514         out += "%s=%s\n" % (n, testenv_vars[n])
515
516     return out
517
518
519 def switch_env(name, prefix):
520     if ":" in name:
521         (envname, option) = name.split(":", 1)
522     else:
523         envname = name
524         option = "client"
525
526     env = env_manager.setup_env(envname, prefix)
527
528     testenv_vars = env.get_vars()
529
530     if option == "local":
531         socket_wrapper.set_default_iface(testenv_vars["SOCKET_WRAPPER_DEFAULT_IFACE"])
532         os.environ["SMB_CONF_PATH"] = testenv_vars["SERVERCONFFILE"]
533     elif option == "client":
534         socket_wrapper.set_default_iface(11)
535         write_clientconf(conffile, clientdir, testenv_vars)
536         os.environ["SMB_CONF_PATH"] = conffile
537     else:
538         raise Exception("Unknown option[%s] for envname[%s]" % (option,
539             envname))
540
541     for name in exported_envvars:
542         if name in testenv_vars:
543             os.environ[name] = testenv_vars[name]
544         elif name in os.environ:
545             del os.environ[name]
546
547     return testenv_vars
548
549 # This 'global' file needs to be empty when we start
550 dns_host_file_path = os.path.join(prefix_abs, "dns_host_file")
551 if os.path.exists(dns_host_file_path):
552     os.unlink(dns_host_file_path)
553
554 if opts.testenv:
555     testenv_name = os.environ.get("SELFTEST_TESTENV", testenv_default)
556
557     testenv_vars = switch_env(testenv_name, prefix)
558
559     os.environ["PIDDIR"] = testenv_vars["PIDDIR"]
560     os.environ["ENVNAME"] = testenv_name
561
562     envvarstr = exported_envvars_str(testenv_vars)
563
564     term = os.environ.get("TERMINAL", "xterm -e")
565     cmd = """'echo -e "
566 Welcome to the Samba4 Test environment '%(testenv_name)'
567
568 This matches the client environment used in make test
569 server is pid `cat \$PIDDIR/samba.pid`
570
571 Some useful environment variables:
572 TORTURE_OPTIONS=\$TORTURE_OPTIONS
573 SMB_CONF_PATH=\$SMB_CONF_PATH
574
575 $envvarstr
576 \" && LD_LIBRARY_PATH=%(LD_LIBRARY_PATH)s $(SHELL)'""" % {
577         "testenv_name": testenv_name,
578         "LD_LIBRARY_PATH": os.environ["LD_LIBRARY_PATH"]}
579     subprocess.call(term + ' ' + cmd, shell=True)
580     env_manager.teardown_env(testenv_name)
581 elif opts.list:
582     for (name, envname, cmd, supports_loadfile, supports_idlist, subtests) in todo:
583         cmd = expand_command_list(cmd)
584         if cmd is None:
585             warnings.warn("Unable to list tests in %s" % name)
586             continue
587
588         exitcode = subprocess.call(cmd, shell=True)
589
590         if exitcode != 0:
591             sys.stderr.write("%s exited with exit code %s\n" % (cmd, exitcode))
592             sys.exit(1)
593 else:
594     for (name, envname, cmd, supports_loadfile, supports_idlist, subtests) in todo:
595         try:
596             envvars = switch_env(envname, prefix)
597         except UnsupportedEnvironment:
598             subunit_ops.start_testsuite(name)
599             subunit_ops.end_testsuite(name, "skip",
600                 "environment %s is unknown in this test backend - skipping" % envname)
601             continue
602         except Exception, e:
603             subunit_ops.start_testsuite(name)
604             traceback.print_exc()
605             subunit_ops.end_testsuite(name, "error",
606                 "unable to set up environment %s: %s" % (envname, e))
607             continue
608
609         # Generate a file with the individual tests to run, if the
610         # test runner for this test suite supports it.
611         if subtests is not None:
612             if supports_loadfile:
613                 (fd, listid_file) = tempfile.mkstemp()
614                 # FIXME: Remove tempfile afterwards
615                 f = os.fdopen(fd)
616                 try:
617                     for test in subtests:
618                         f.write(test+"\n")
619                 finally:
620                     f.close()
621                 cmd = cmd.replace("$LOADLIST", "--load-list=%s" % listid_file)
622             elif supports_idlist:
623                 cmd += " ".join(subtests)
624
625         run_testsuite(envname, name, cmd)
626
627         if opts.resetup_environment:
628             env_manager.teardown_env(envname)
629
630 sys.stdout.write("\n")
631
632 if not opts.list:
633     env_manager.teardown_all()
634
635 # if there were any valgrind failures, show them
636 for fn in os.listdir(prefix):
637     if fn.startswith("valgrind.log"):
638         sys.stdout.write("VALGRIND FAILURE\n")
639         f = open(os.path.join(prefix, fn), 'r')
640         try:
641             sys.stdout.write(f.read())
642         finally:
643             f.close()
644
645 sys.exit(0)