2 # run tests on all Samba subprojects and push to a git tree on success
3 # Copyright Andrew Tridgell 2010
4 # released under GNU GPL v3 or later
6 from __future__ import print_function
7 from subprocess import call, check_call, check_output, Popen, PIPE
13 from optparse import OptionParser
16 from email.mime.text import MIMEText
17 from email.mime.base import MIMEBase
18 from email.mime.application import MIMEApplication
19 from email.mime.multipart import MIMEMultipart
20 from distutils.sysconfig import get_python_lib
24 from waflib.Build import CACHE_SUFFIX
26 sys.path.insert(0, "./third_party/waf")
27 from waflib.Build import CACHE_SUFFIX
30 os.environ["PYTHONUNBUFFERED"] = "1"
32 # This speeds up testing remarkably.
33 os.environ['TDB_NO_FSYNC'] = '1'
37 '''get to the top of the git repo'''
40 if os.path.isdir(os.path.join(p, ".git")):
42 p = os.path.abspath(os.path.join(p, '..'))
46 gitroot = find_git_root()
48 raise Exception("Failed to find git root")
51 def_testbase = os.getenv("AUTOBUILD_TESTBASE", "/memdisk/%s" % os.getenv('USER'))
53 parser = OptionParser()
54 parser.add_option("--tail", help="show output while running", default=False, action="store_true")
55 parser.add_option("--keeplogs", help="keep logs", default=False, action="store_true")
56 parser.add_option("--nocleanup", help="don't remove test tree", default=False, action="store_true")
57 parser.add_option("--testbase", help="base directory to run tests in (default %s)" % def_testbase,
59 parser.add_option("--passcmd", help="command to run on success", default=None)
60 parser.add_option("--verbose", help="show all commands as they are run",
61 default=False, action="store_true")
62 parser.add_option("--rebase", help="rebase on the given tree before testing",
63 default=None, type='str')
64 parser.add_option("--pushto", help="push to a git url on success",
65 default=None, type='str')
66 parser.add_option("--mark", help="add a Tested-By signoff before pushing",
67 default=False, action="store_true")
68 parser.add_option("--fix-whitespace", help="fix whitespace on rebase",
69 default=False, action="store_true")
70 parser.add_option("--retry", help="automatically retry if master changes",
71 default=False, action="store_true")
72 parser.add_option("--email", help="send email to the given address on failure",
73 type='str', default=None)
74 parser.add_option("--email-from", help="send email from the given address",
75 type='str', default="autobuild@samba.org")
76 parser.add_option("--email-server", help="send email via the given server",
77 type='str', default='localhost')
78 parser.add_option("--always-email", help="always send email, even on success",
80 parser.add_option("--daemon", help="daemonize after initial setup",
82 parser.add_option("--branch", help="the branch to work on (default=master)",
83 default="master", type='str')
84 parser.add_option("--log-base", help="location where the logs can be found (default=cwd)",
85 default=gitroot, type='str')
86 parser.add_option("--attach-logs", help="Attach logs to mails sent on success/failure?",
87 default=False, action="store_true")
88 parser.add_option("--restrict-tests", help="run as make test with this TESTS= regex",
90 parser.add_option("--enable-coverage", dest='enable_coverage',
91 action="store_const", const='--enable-coverage', default='',
92 help="Add --enable-coverage option while configure")
94 (options, args) = parser.parse_args()
97 if options.rebase is None:
98 raise Exception('You can only use --retry if you also rebase')
100 testbase = "%s/b%u" % (options.testbase, os.getpid())
101 test_master = "%s/master" % testbase
102 test_prefix = "%s/prefix" % testbase
103 test_tmpdir = "%s/tmp" % testbase
104 os.environ['TMPDIR'] = test_tmpdir
106 if options.enable_coverage:
107 LCOV_CMD = "cd ${TEST_SOURCE_DIR} && lcov --capture --directory . --output-file ${LOG_BASE}/${NAME}.info --rc 'geninfo_adjust_src_path=${TEST_SOURCE_DIR}/'"
109 LCOV_CMD = 'echo "lcov skipped since no --enable-coverage specified"'
112 # If we are only running specific test,
113 # do not sleep randomly to wait for it to start
114 def random_sleep(low, high):
117 def random_sleep(low, high):
118 return 'sleep {}'.format(random.randint(low, high))
126 "samba-fileserver": ".",
127 "samba-ad-member": ".",
133 "samba-none-env": ".",
134 "samba-ad-dc-1": ".",
135 "samba-ad-dc-2": ".",
136 "samba-ad-dc-3": ".",
137 "samba-ad-dc-4": ".",
138 "samba-ad-dc-5": ".",
139 "samba-ad-dc-6": ".",
140 "samba-ad-dc-ntvfs": ".",
141 "samba-ad-dc-backup": ".",
142 "samba-systemkrb5": ".",
143 "samba-nopython": ".",
144 "samba-nopython-py2": ".",
145 "samba-schemaupgrade": ".",
148 "talloc": "lib/talloc",
149 "replace": "lib/replace",
150 "tevent": "lib/tevent",
154 defaulttasks = list(builddirs.keys())
156 if os.environ.get("AUTOBUILD_SKIP_SAMBA_O3", "0") == "1":
157 defaulttasks.remove("samba-o3")
159 ctdb_configure_params = " --enable-developer --picky-developer ${PREFIX}"
160 samba_configure_params = " ${ENABLE_COVERAGE} --picky-developer ${PREFIX} --with-profiling-data"
162 samba_libs_envvars = "PYTHONPATH=${PYTHON_PREFIX}:$PYTHONPATH"
163 samba_libs_envvars += " PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${PREFIX_DIR}/lib/pkgconfig"
164 samba_libs_envvars += " ADDITIONAL_CFLAGS='-Wmissing-prototypes'"
165 samba_libs_configure_base = samba_libs_envvars + " ./configure --abi-check ${ENABLE_COVERAGE} --enable-debug --picky-developer -C ${PREFIX}"
166 samba_libs_configure_libs = samba_libs_configure_base + " --bundled-libraries=cmocka,popt,NONE"
167 samba_libs_configure_bundled_libs = " --bundled-libraries=!talloc,!pytalloc-util,!tdb,!pytdb,!ldb,!pyldb,!pyldb-util,!tevent,!pytevent,!popt"
168 samba_libs_configure_samba = samba_libs_configure_base + samba_libs_configure_bundled_libs
171 def format_option(name, value=None):
172 """Format option as str list."""
173 if value is None: # boolean option
175 if not isinstance(value, list): # single value option
178 return ['{}={}'.format(name, item) for item in value]
190 test_options = format_option('--include-env', include_envs)
192 test_options = format_option('--exclude-env', exclude_envs)
194 # join envs options to original test options
195 TESTS = (TESTS + ' ' + ' '.join(test_options)).strip()
199 _options.append('FAIL_IMMEDIATELY=1')
201 _options.append("TESTS='{}'".format(TESTS))
203 return ' '.join([cmd] + _options)
208 ("random-sleep", random_sleep(300, 900)),
209 ("configure", "./configure " + ctdb_configure_params),
210 ("make", "make all"),
211 ("install", "make install"),
212 ("test", "make autotest"),
213 ("check-clean-tree", "../script/clean-source-tree.sh"),
214 ("clean", "make clean"),
217 # We have 'test' before 'install' because, 'test' should work without 'install (runs all the other envs)'
219 ("random-sleep", random_sleep(300, 900)),
220 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
222 ("test", make_test(exclude_envs=[
238 "ad_member_idmap_rid",
239 "ad_member_idmap_ad",
263 ("install", "make install"),
264 ("check-clean-tree", "script/clean-source-tree.sh"),
265 ("clean", "make clean"),
269 ("random-sleep", random_sleep(300, 900)),
270 ("configure", "./configure.developer --without-ads --with-selftest-prefix=./bin/ab" + samba_configure_params),
272 ("test", make_test(include_envs=[
278 ("install", "make install"),
279 ("check-clean-tree", "script/clean-source-tree.sh"),
280 ("clean", "make clean"),
283 "samba-fileserver": [
284 ("random-sleep", random_sleep(300, 900)),
285 ("configure", "./configure.developer --without-ad-dc --without-ldap --without-ads --without-json --with-selftest-prefix=./bin/ab" + samba_configure_params),
287 ("test", make_test(include_envs=[
293 ("check-clean-tree", "script/clean-source-tree.sh"),
297 ("random-sleep", random_sleep(300, 900)),
298 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
300 ("test", make_test(include_envs=[
302 "ad_member_idmap_rid",
303 "ad_member_idmap_ad",
307 ("check-clean-tree", "script/clean-source-tree.sh"),
311 ("random-sleep", random_sleep(1, 1)),
312 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
314 ("test", make_test(include_envs=[
320 ("check-clean-tree", "script/clean-source-tree.sh"),
324 ("random-sleep", random_sleep(1, 1)),
325 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
327 ("test", make_test(include_envs=[
333 ("check-clean-tree", "script/clean-source-tree.sh"),
337 ("random-sleep", random_sleep(1, 1)),
338 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
340 ("test", make_test(include_envs=[
347 ("check-clean-tree", "script/clean-source-tree.sh"),
351 ("random-sleep", random_sleep(1, 1)),
352 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
354 ("test", make_test(include_envs=[
361 ("check-clean-tree", "script/clean-source-tree.sh"),
365 ("random-sleep", random_sleep(1, 1)),
366 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
368 ("test", make_test(include_envs=["ad_dc_default"])),
370 ("check-clean-tree", "script/clean-source-tree.sh"),
374 ("random-sleep", random_sleep(1, 1)),
375 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
377 ("test", make_test(include_envs=["ad_dc_slowtests"])),
379 ("check-clean-tree", "script/clean-source-tree.sh"),
382 "samba-schemaupgrade": [
383 ("random-sleep", random_sleep(1, 1)),
384 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
386 ("test", make_test(include_envs=["schema_dc", "schema_pair_dc"])),
388 ("check-clean-tree", "script/clean-source-tree.sh"),
391 # We split out the ad_dc_ntvfs tests (which are long) so other test do not wait
392 # This is currently the longest task, so we don't randomly delay it.
393 "samba-ad-dc-ntvfs": [
394 ("random-sleep", random_sleep(1, 1)),
395 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
397 ("test", make_test(include_envs=["ad_dc_ntvfs"])),
399 ("check-clean-tree", "script/clean-source-tree.sh"),
402 # run the backup/restore testenvs separately as they're fairly standalone
403 # (and CI seems to max out at ~8 different DCs running at once)
404 "samba-ad-dc-backup": [
405 ("random-sleep", random_sleep(300, 900)),
406 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
408 ("test", make_test(include_envs=[
417 ("check-clean-tree", "script/clean-source-tree.sh"),
421 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab --abi-check-disable" + samba_configure_params),
423 ("test", make_test(TESTS="${TESTS}")),
427 # Test cross-compile infrastructure
429 ("random-sleep", random_sleep(900, 1500)),
430 ("configure-native", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
431 ("configure-cross-execute", "./configure.developer --out ./bin-xe --cross-compile --cross-execute=script/identity_cc.sh" \
432 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xe/ab" + samba_configure_params),
433 ("configure-cross-answers", "./configure.developer --out ./bin-xa --cross-compile" \
434 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xa/ab" + samba_configure_params),
435 ("compare-results", "script/compare_cc_results.py "
436 "./bin/c4che/default{} "
437 "./bin-xe/c4che/default{} "
438 "./bin-xa/c4che/default{}".format(*([CACHE_SUFFIX]*3))),
441 # test build with -O3 -- catches extra warnings and bugs, tests the ad_dc environments
443 ("random-sleep", random_sleep(300, 900)),
444 ("configure", "ADDITIONAL_CFLAGS='-O3 -Wp,-D_FORTIFY_SOURCE=2' ./configure.developer --with-selftest-prefix=./bin/ab --abi-check-disable" + samba_configure_params),
446 ("test", make_test(cmd='make quicktest', include_envs=["ad_dc"])),
448 ("install", "make install"),
449 ("check-clean-tree", "script/clean-source-tree.sh"),
450 ("clean", "make clean"),
454 ("random-sleep", random_sleep(900, 1500)),
456 # make sure we have tdb around:
457 ("tdb-configure", "cd lib/tdb && PYTHONPATH=${PYTHON_PREFIX}:$PYTHONPATH PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${PREFIX_DIR}/lib/pkgconfig ./configure --bundled-libraries=NONE --abi-check --enable-debug -C ${PREFIX}"),
458 ("tdb-make", "cd lib/tdb && make"),
459 ("tdb-install", "cd lib/tdb && make install"),
461 # build samba with cluster support (also building ctdb):
462 ("samba-configure", "PYTHONPATH=${PYTHON_PREFIX}:$PYTHONPATH PKG_CONFIG_PATH=${PREFIX_DIR}/lib/pkgconfig:${PKG_CONFIG_PATH} ./configure.developer --picky-developer ${PREFIX} --with-selftest-prefix=./bin/ab --with-cluster-support --bundled-libraries=!tdb"),
463 ("samba-make", "make"),
464 ("samba-check", "./bin/smbd -b | grep CLUSTER_SUPPORT"),
465 ("samba-install", "make install"),
466 ("ctdb-check", "test -e ${PREFIX_DIR}/sbin/ctdbd"),
469 ("check-clean-tree", "script/clean-source-tree.sh"),
470 ("clean", "make clean"),
471 ("ctdb-clean", "cd ./ctdb && make clean"),
475 ("random-sleep", random_sleep(300, 900)),
476 ("talloc-configure", "cd lib/talloc && " + samba_libs_configure_libs),
477 ("talloc-make", "cd lib/talloc && make"),
478 ("talloc-install", "cd lib/talloc && make install"),
480 ("tdb-configure", "cd lib/tdb && " + samba_libs_configure_libs),
481 ("tdb-make", "cd lib/tdb && make"),
482 ("tdb-install", "cd lib/tdb && make install"),
484 ("tevent-configure", "cd lib/tevent && " + samba_libs_configure_libs),
485 ("tevent-make", "cd lib/tevent && make"),
486 ("tevent-install", "cd lib/tevent && make install"),
488 ("ldb-configure", "cd lib/ldb && " + samba_libs_configure_libs),
489 ("ldb-make", "cd lib/ldb && make"),
490 ("ldb-install", "cd lib/ldb && make install"),
492 ("nondevel-configure", "./configure ${PREFIX}"),
493 ("nondevel-make", "make -j"),
494 ("nondevel-check", "./bin/smbd -b | grep WITH_NTVFS_FILESERVER && exit 1; exit 0"),
495 ("nondevel-install", "make install"),
496 ("nondevel-dist", "make dist"),
498 # retry with all modules shared
499 ("allshared-distclean", "make distclean"),
500 ("allshared-configure", samba_libs_configure_samba + " --with-shared-modules=ALL"),
501 ("allshared-make", "make -j"),
505 ("random-sleep", random_sleep(1, 1)),
506 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
508 ("test", make_test(include_envs=["none"])),
513 ("random-sleep", random_sleep(1, 1)),
514 # build with all modules static
515 ("allstatic-configure", "./configure.developer " + samba_configure_params + " --with-static-modules=ALL"),
516 ("allstatic-make", "make -j"),
517 ("allstatic-test", make_test(TESTS="samba3.smb2.create.*nt4_dc")),
520 # retry without any required modules
521 ("none-distclean", "make distclean"),
522 ("none-configure", "./configure.developer " + samba_configure_params + " --with-static-modules=!FORCED,!DEFAULT --with-shared-modules=!FORCED,!DEFAULT"),
523 ("none-make", "make -j"),
525 # retry with nonshared smbd and smbtorture
526 ("nonshared-distclean", "make distclean"),
527 ("nonshared-configure", "./configure.developer " + samba_configure_params + " --bundled-libraries=talloc,tdb,pytdb,ldb,pyldb,tevent,pytevent --with-static-modules=ALL --nonshared-binary=smbtorture,smbd/smbd"),
528 ("nonshared-make", "make -j"),
531 "samba-systemkrb5": [
532 ("random-sleep", random_sleep(900, 1500)),
533 ("configure", "./configure.developer " + samba_configure_params + " --with-system-mitkrb5 --with-experimental-mit-ad-dc"),
535 # we currently cannot run a full make test, a limited list of tests could be run
536 # via "make test TESTS=sometests"
537 ("test", make_test(include_envs=["ktest"])),
539 ("install", "make install"),
540 ("check-clean-tree", "script/clean-source-tree.sh"),
541 ("clean", "make clean"),
544 # Test Samba without python still builds. When this test fails
545 # due to more use of Python, the expectations is that the newly
546 # failing part of the code should be disabled when
547 # --disable-python is set (rather than major work being done to
548 # support this environment). The target here is for vendors
549 # shipping a minimal smbd.
551 ("random-sleep", random_sleep(300, 900)),
552 ("configure", "./configure.developer ${ENABLE_COVERAGE} --picky-developer ${PREFIX} --with-profiling-data --disable-python --without-ad-dc"),
554 ("install", "make install"),
555 ("find-python", "script/find_python.sh ${PREFIX}"),
556 ("test", "make test-nopython"),
558 ("check-clean-tree", "script/clean-source-tree.sh"),
559 ("clean", "make clean"),
561 ("talloc-configure", "cd lib/talloc && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
562 ("talloc-make", "cd lib/talloc && make"),
563 ("talloc-install", "cd lib/talloc && make install"),
565 ("tdb-configure", "cd lib/tdb && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
566 ("tdb-make", "cd lib/tdb && make"),
567 ("tdb-install", "cd lib/tdb && make install"),
569 ("tevent-configure", "cd lib/tevent && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
570 ("tevent-make", "cd lib/tevent && make"),
571 ("tevent-install", "cd lib/tevent && make install"),
573 ("ldb-configure", "cd lib/ldb && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
574 ("ldb-make", "cd lib/ldb && make"),
575 ("ldb-install", "cd lib/ldb && make install"),
577 # retry against installed library packages
578 ("libs-configure", samba_libs_configure_base + samba_libs_configure_bundled_libs + " --disable-python --without-ad-dc"),
579 ("libs-make", "make -j"),
580 ("libs-install", "make install"),
581 ("libs-check-clean-tree", "script/clean-source-tree.sh"),
582 ("libs-clean", "make clean"),
585 # check we can do the same thing using python2
586 "samba-nopython-py2": [
587 ("random-sleep", random_sleep(300, 900)),
588 ("configure", "PYTHON=python2 ./configure.developer ${ENABLE_COVERAGE} --picky-developer ${PREFIX} --with-profiling-data --disable-python --without-ad-dc"),
589 ("make", "PYTHON=python2 make -j"),
590 ("install", "PYTHON=python2 make install"),
591 ("find-python", "script/find_python.sh ${PREFIX}"),
592 ("test", "make test-nopython"),
594 ("check-clean-tree", "script/clean-source-tree.sh"),
595 ("clean", "PYTHON=python2 make clean"),
597 ("talloc-configure", "cd lib/talloc && PYTHON=python2 " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
598 ("talloc-make", "cd lib/talloc && PYTHON=python2 make"),
599 ("talloc-install", "cd lib/talloc && PYTHON=python2 make install"),
601 ("tdb-configure", "cd lib/tdb && PYTHON=python2 " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
602 ("tdb-make", "cd lib/tdb && PYTHON=python2 make"),
603 ("tdb-install", "cd lib/tdb && PYTHON=python2 make install"),
605 ("tevent-configure", "cd lib/tevent && PYTHON=python2 " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
606 ("tevent-make", "cd lib/tevent && PYTHON=python2 make"),
607 ("tevent-install", "cd lib/tevent && PYTHON=python2 make install"),
609 ("ldb-configure", "cd lib/ldb && PYTHON=python2 " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
610 ("ldb-make", "cd lib/ldb && PYTHON=python2 make"),
611 ("ldb-install", "cd lib/ldb && PYTHON=python2 make install"),
613 # retry against installed library packages
614 ("libs-configure", "PYTHON=python2 " + samba_libs_configure_base + samba_libs_configure_bundled_libs + " --disable-python --without-ad-dc"),
615 ("libs-make", "PYTHON=python2 make -j"),
616 ("libs-install", "PYTHON=python2 make install"),
617 ("libs-check-clean-tree", "script/clean-source-tree.sh"),
618 ("libs-clean", "PYTHON=python2 make clean"),
622 ("random-sleep", random_sleep(60, 600)),
623 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
625 ("install", "make install"),
626 ("test", "make test"),
628 ("clean", "make clean"),
629 ("configure-no-lmdb", "./configure ${ENABLE_COVERAGE} --enable-developer --without-ldb-lmdb -C ${PREFIX}"),
630 ("make-no-lmdb", "make"),
631 ("test-no-lmdb", "make test"),
632 ("lcov-no-lmdb", LCOV_CMD),
633 ("install-no-lmdb", "make install"),
634 ("check-clean-tree", "../../script/clean-source-tree.sh"),
635 ("distcheck", "make distcheck"),
636 ("clean", "make clean"),
640 ("random-sleep", random_sleep(60, 600)),
641 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
643 ("install", "make install"),
644 ("test", "make test"),
646 ("check-clean-tree", "../../script/clean-source-tree.sh"),
647 ("distcheck", "make distcheck"),
648 ("clean", "make clean"),
652 ("random-sleep", random_sleep(60, 600)),
653 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
655 ("install", "make install"),
656 ("test", "make test"),
658 ("check-clean-tree", "../../script/clean-source-tree.sh"),
659 ("distcheck", "make distcheck"),
660 ("clean", "make clean"),
664 ("random-sleep", random_sleep(60, 600)),
665 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
667 ("install", "make install"),
668 ("test", "make test"),
670 ("check-clean-tree", "../../script/clean-source-tree.sh"),
671 ("distcheck", "make distcheck"),
672 ("clean", "make clean"),
676 ("random-sleep", random_sleep(60, 600)),
677 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
679 ("install", "make install"),
680 ("test", "make test"),
682 ("check-clean-tree", "../../script/clean-source-tree.sh"),
683 ("distcheck", "make distcheck"),
684 ("clean", "make clean"),
688 ("random-sleep", random_sleep(60, 600)),
689 ("configure", "perl Makefile.PL PREFIX=${PREFIX_DIR}"),
690 ("touch", "touch *.yp"),
692 ("test", "make test"),
693 ("install", "make install"),
694 ("checkout-yapp-generated", "git checkout lib/Parse/Pidl/IDL.pm lib/Parse/Pidl/Expr.pm"),
695 ("check-clean-tree", "../script/clean-source-tree.sh"),
696 ("clean", "make clean"),
699 # these are useful for debugging autobuild
700 'pass': [("pass", 'echo passing && /bin/true')],
701 'fail': [("fail", 'echo failing && /bin/false')],
711 def run_cmd(cmd, dir=".", show=None, output=False, checkfail=True):
713 show = options.verbose
715 do_print("Running: '%s' in '%s'" % (cmd, dir))
717 out = check_output([cmd], shell=True, cwd=dir)
718 return out.decode(encoding='utf-8', errors='backslashreplace')
720 return check_call(cmd, shell=True, cwd=dir)
722 return call(cmd, shell=True, cwd=dir)
725 class builder(object):
726 '''handle build of one directory'''
728 def __init__(self, name, sequence, cp=True):
730 if name in builddirs:
731 self.dir = builddirs[name]
735 self.tag = self.name.replace('/', '_')
736 self.sequence = sequence
738 self.stdout_path = "%s/%s.stdout" % (gitroot, self.tag)
739 self.stderr_path = "%s/%s.stderr" % (gitroot, self.tag)
741 do_print("stdout for %s in %s" % (self.name, self.stdout_path))
742 do_print("stderr for %s in %s" % (self.name, self.stderr_path))
743 run_cmd("rm -f %s %s" % (self.stdout_path, self.stderr_path))
744 self.stdout = open(self.stdout_path, 'w')
745 self.stderr = open(self.stderr_path, 'w')
746 self.stdin = open("/dev/null", 'r')
747 self.test_source_dir = "%s/%s" % (testbase, self.tag)
748 self.cwd = "%s/%s" % (self.test_source_dir, self.dir)
749 self.prefix = "%s/%s" % (test_prefix, self.tag)
750 run_cmd("rm -rf %s" % self.test_source_dir)
751 run_cmd("rm -rf %s" % self.prefix)
753 run_cmd("cp --recursive --link --archive %s %s" % (test_master, self.test_source_dir), dir=test_master, show=True)
755 run_cmd("git clone --recursive --shared %s %s" % (test_master, self.test_source_dir), dir=test_master, show=True)
758 def start_next(self):
759 if self.next == len(self.sequence):
760 if not options.nocleanup:
761 run_cmd("rm -rf %s" % self.test_source_dir)
762 run_cmd("rm -rf %s" % self.prefix)
763 do_print('%s: Completed OK' % self.name)
766 (self.stage, self.cmd) = self.sequence[self.next]
767 self.cmd = self.cmd.replace("${PYTHON_PREFIX}", get_python_lib(plat_specific=1, standard_lib=0, prefix=self.prefix))
768 self.cmd = self.cmd.replace("${PREFIX}", "--prefix=%s" % self.prefix)
769 self.cmd = self.cmd.replace("${PREFIX_DIR}", "%s" % self.prefix)
770 self.cmd = self.cmd.replace("${TESTS}", options.restrict_tests)
771 self.cmd = self.cmd.replace("${TEST_SOURCE_DIR}", self.test_source_dir)
772 self.cmd = self.cmd.replace("${LOG_BASE}", options.log_base)
773 self.cmd = self.cmd.replace("${NAME}", self.name)
774 self.cmd = self.cmd.replace("${ENABLE_COVERAGE}", options.enable_coverage)
775 do_print('%s: [%s] Running %s in %r' % (self.name, self.stage, self.cmd, self.cwd))
776 self.proc = Popen(self.cmd, shell=True,
777 close_fds=True, cwd=self.cwd,
778 stdout=self.stdout, stderr=self.stderr, stdin=self.stdin)
782 class buildlist(object):
783 '''handle build of multiple directories'''
785 def __init__(self, tasknames, rebase_url, rebase_branch="master"):
788 self.tail_proc = None
791 if options.restrict_tests:
792 tasknames = ["samba-test-only"]
794 tasknames = defaulttasks
797 b = builder(n, tasks[n], cp=n is not "pidl")
800 rebase_remote = "rebaseon"
801 retry_task = [("retry",
803 git remote add -t %s %s %s
807 git describe %s/%s > old_remote_branch.desc
809 git describe %s/%s > remote_branch.desc
810 diff old_remote_branch.desc remote_branch.desc
813 rebase_branch, rebase_remote, rebase_url,
815 rebase_remote, rebase_branch,
817 rebase_remote, rebase_branch
820 self.retry = builder('retry', retry_task, cp=False)
821 self.need_retry = False
824 if self.tail_proc is not None:
825 self.tail_proc.terminate()
826 self.tail_proc.wait()
827 self.tail_proc = None
828 if self.retry is not None:
829 self.retry.proc.terminate()
830 self.retry.proc.wait()
833 if b.proc is not None:
834 run_cmd("killbysubdir %s > /dev/null 2>&1" % b.test_source_dir, checkfail=False)
846 b.status = b.proc.poll()
852 ret = self.retry.proc.poll()
854 self.need_retry = True
864 if options.retry and self.need_retry:
866 do_print("retry needed")
867 return (0, None, None, None, "retry")
870 if os.WIFSIGNALED(b.status) or os.WEXITSTATUS(b.status) != 0:
872 return (b.status, b.name, b.stage, b.tag, "%s: [%s] failed '%s' with status %d" % (b.name, b.stage, b.cmd, b.status))
875 return (0, None, None, None, "All OK")
877 def write_system_info(self):
878 filename = 'system-info.txt'
879 f = open(filename, 'w')
880 for cmd in ['uname -a',
887 'df -m %s' % testbase]:
888 out = run_cmd(cmd, output=True, checkfail=False)
889 print('### %s' % cmd, file=f)
895 def tarlogs(self, fname):
896 tar = tarfile.open(fname, "w:gz")
898 tar.add(b.stdout_path, arcname="%s.stdout" % b.tag)
899 tar.add(b.stderr_path, arcname="%s.stderr" % b.tag)
900 if os.path.exists("autobuild.log"):
901 tar.add("autobuild.log")
902 sys_info = self.write_system_info()
906 def remove_logs(self):
908 os.unlink(b.stdout_path)
909 os.unlink(b.stderr_path)
911 def start_tail(self):
914 cmd.append(b.stdout_path)
915 cmd.append(b.stderr_path)
916 self.tail_proc = Popen(cmd, close_fds=True)
920 if options.nocleanup:
922 run_cmd("stat %s || true" % test_tmpdir, show=True)
923 run_cmd("stat %s" % testbase, show=True)
924 do_print("Cleaning up %r" % cleanup_list)
925 for d in cleanup_list:
926 run_cmd("rm -rf %s" % d)
929 def daemonize(logfile):
931 if pid == 0: # Parent
934 if pid != 0: # Actual daemon
939 import resource # Resource usage information.
940 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
941 if maxfd == resource.RLIM_INFINITY:
942 maxfd = 1024 # Rough guess at maximum number of open file descriptors.
943 for fd in range(0, maxfd):
948 os.open(logfile, os.O_RDWR | os.O_CREAT)
953 def write_pidfile(fname):
954 '''write a pid file, cleanup on exit'''
955 f = open(fname, mode='w')
956 f.write("%u\n" % os.getpid())
960 def rebase_tree(rebase_url, rebase_branch="master"):
961 rebase_remote = "rebaseon"
962 do_print("Rebasing on %s" % rebase_url)
963 run_cmd("git describe HEAD", show=True, dir=test_master)
964 run_cmd("git remote add -t %s %s %s" %
965 (rebase_branch, rebase_remote, rebase_url),
966 show=True, dir=test_master)
967 run_cmd("git fetch %s" % rebase_remote, show=True, dir=test_master)
968 if options.fix_whitespace:
969 run_cmd("git rebase --force-rebase --whitespace=fix %s/%s" %
970 (rebase_remote, rebase_branch),
971 show=True, dir=test_master)
973 run_cmd("git rebase --force-rebase %s/%s" %
974 (rebase_remote, rebase_branch),
975 show=True, dir=test_master)
976 diff = run_cmd("git --no-pager diff HEAD %s/%s" %
977 (rebase_remote, rebase_branch),
978 dir=test_master, output=True)
980 do_print("No differences between HEAD and %s/%s - exiting" %
981 (rebase_remote, rebase_branch))
983 run_cmd("git describe %s/%s" %
984 (rebase_remote, rebase_branch),
985 show=True, dir=test_master)
986 run_cmd("git describe HEAD", show=True, dir=test_master)
987 run_cmd("git --no-pager diff --stat HEAD %s/%s" %
988 (rebase_remote, rebase_branch),
989 show=True, dir=test_master)
992 def push_to(push_url, push_branch="master"):
993 push_remote = "pushto"
994 do_print("Pushing to %s" % push_url)
996 run_cmd("git config --replace-all core.editor script/commit_mark.sh", dir=test_master)
997 run_cmd("git commit --amend -c HEAD", dir=test_master)
998 # the notes method doesn't work yet, as metze hasn't allowed refs/notes/* in master
999 # run_cmd("EDITOR=script/commit_mark.sh git notes edit HEAD", dir=test_master)
1000 run_cmd("git remote add -t %s %s %s" %
1001 (push_branch, push_remote, push_url),
1002 show=True, dir=test_master)
1003 run_cmd("git push %s +HEAD:%s" %
1004 (push_remote, push_branch),
1005 show=True, dir=test_master)
1008 def send_email(subject, text, log_tar):
1009 if options.email is None:
1010 do_print("not sending email because the recipient is not set")
1011 do_print("the text content would have been:\n\nSubject: %s\n\n%s" %
1014 outer = MIMEMultipart()
1015 outer['Subject'] = subject
1016 outer['To'] = options.email
1017 outer['From'] = options.email_from
1018 outer['Date'] = email.utils.formatdate(localtime=True)
1019 outer.preamble = 'Autobuild mails are now in MIME because we optionally attach the logs.\n'
1020 outer.attach(MIMEText(text, 'plain'))
1021 if options.attach_logs:
1022 fp = open(log_tar, 'rb')
1023 msg = MIMEApplication(fp.read(), 'gzip', email.encoders.encode_base64)
1025 # Set the filename parameter
1026 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(log_tar))
1028 content = outer.as_string()
1029 s = smtplib.SMTP(options.email_server)
1030 email_user = os.getenv('SMTP_USERNAME')
1031 email_password = os.getenv('SMTP_PASSWORD')
1032 if email_user is not None:
1034 s.login(email_user, email_password)
1036 s.sendmail(options.email_from, [options.email], content)
1041 def email_failure(status, failed_task, failed_stage, failed_tag, errstr,
1042 elapsed_time, log_base=None, add_log_tail=True):
1043 '''send an email to options.email about the failure'''
1044 elapsed_minutes = elapsed_time / 60.0
1045 if log_base is None:
1050 Your autobuild on %s failed after %.1f minutes
1051 when trying to test %s with the following error:
1055 the autobuild has been abandoned. Please fix the error and resubmit.
1057 A summary of the autobuild process is here:
1060 ''' % (platform.node(), elapsed_minutes, failed_task, errstr, log_base)
1062 if options.restrict_tests:
1064 The build was restricted to tests matching %s\n""" % options.restrict_tests
1066 if failed_task != 'rebase':
1068 You can see logs of the failed task here:
1073 or you can get full logs of all tasks in this job here:
1077 The top commit for the tree that was built was:
1081 ''' % (log_base, failed_tag, log_base, failed_tag, log_base, top_commit_msg)
1084 f = open("%s/%s.stdout" % (gitroot, failed_tag), 'r')
1085 lines = f.readlines()
1086 log_tail = "".join(lines[-50:])
1087 num_lines = len(lines)
1089 # Also include stderr (compile failures) if < 50 lines of stdout
1090 f = open("%s/%s.stderr" % (gitroot, failed_tag), 'r')
1091 log_tail += "".join(f.readlines()[-(50 - num_lines):])
1094 The last 50 lines of log messages:
1100 logs = os.path.join(gitroot, 'logs.tar.gz')
1101 send_email('autobuild[%s] failure on %s for task %s during %s'
1102 % (options.branch, platform.node(), failed_task, failed_stage),
1106 def email_success(elapsed_time, log_base=None):
1107 '''send an email to options.email about a successful build'''
1108 if log_base is None:
1113 Your autobuild on %s has succeeded after %.1f minutes.
1115 ''' % (platform.node(), elapsed_time / 60.)
1117 if options.restrict_tests:
1119 The build was restricted to tests matching %s\n""" % options.restrict_tests
1121 if options.keeplogs:
1124 you can get full logs of all tasks in this job here:
1131 The top commit for the tree that was built was:
1134 ''' % top_commit_msg
1136 logs = os.path.join(gitroot, 'logs.tar.gz')
1137 send_email('autobuild[%s] success on %s' % (options.branch, platform.node()),
1141 # get the top commit message, for emails
1142 top_commit_msg = run_cmd("git log -1", dir=gitroot, output=True)
1145 os.makedirs(testbase)
1146 except Exception as reason:
1147 raise Exception("Unable to create %s : %s" % (testbase, reason))
1148 cleanup_list.append(testbase)
1151 logfile = os.path.join(testbase, "log")
1152 do_print("Forking into the background, writing progress to %s" % logfile)
1155 write_pidfile(gitroot + "/autobuild.pid")
1157 start_time = time.time()
1161 run_cmd("rm -rf %s" % test_tmpdir, show=True)
1162 os.makedirs(test_tmpdir)
1163 # The waf uninstall code removes empty directories all the way
1164 # up the tree. Creating a file in test_tmpdir stops it from
1166 run_cmd("touch %s" % os.path.join(test_tmpdir,
1167 ".directory-is-not-empty"), show=True)
1168 run_cmd("stat %s" % test_tmpdir, show=True)
1169 run_cmd("stat %s" % testbase, show=True)
1170 run_cmd("git clone --recursive --shared %s %s" % (gitroot, test_master), show=True, dir=gitroot)
1177 if options.rebase is not None:
1178 rebase_tree(options.rebase, rebase_branch=options.branch)
1180 cleanup_list.append(gitroot + "/autobuild.pid")
1182 elapsed_time = time.time() - start_time
1183 email_failure(-1, 'rebase', 'rebase', 'rebase',
1184 'rebase on %s failed' % options.branch,
1185 elapsed_time, log_base=options.log_base)
1187 blist = buildlist(args, options.rebase, rebase_branch=options.branch)
1190 (status, failed_task, failed_stage, failed_tag, errstr) = blist.run()
1191 if status != 0 or errstr != "retry":
1198 cleanup_list.append(gitroot + "/autobuild.pid")
1204 do_print("waiting for tail to flush")
1207 elapsed_time = time.time() - start_time
1209 if options.passcmd is not None:
1210 do_print("Running passcmd: %s" % options.passcmd)
1211 run_cmd(options.passcmd, dir=test_master)
1212 if options.pushto is not None:
1213 push_to(options.pushto, push_branch=options.branch)
1214 if options.keeplogs or options.attach_logs:
1215 blist.tarlogs("logs.tar.gz")
1216 do_print("Logs in logs.tar.gz")
1217 if options.always_email:
1218 email_success(elapsed_time, log_base=options.log_base)
1224 # something failed, gather a tar of the logs
1225 blist.tarlogs("logs.tar.gz")
1227 if options.email is not None:
1228 email_failure(status, failed_task, failed_stage, failed_tag, errstr,
1229 elapsed_time, log_base=options.log_base)
1231 elapsed_minutes = elapsed_time / 60.0
1234 ####################################################################
1238 Your autobuild[%s] on %s failed after %.1f minutes
1239 when trying to test %s with the following error:
1243 the autobuild has been abandoned. Please fix the error and resubmit.
1245 ####################################################################
1247 ''' % (options.branch, platform.node(), elapsed_minutes, failed_task, errstr))
1251 do_print("Logs in logs.tar.gz")