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