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