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": ".",
128 "samba-ad-member-mitkrb5": ".",
134 "samba-none-env": ".",
135 "samba-ad-dc-1": ".",
136 "samba-ad-dc-1-mitkrb5": ".",
137 "samba-ad-dc-2": ".",
138 "samba-ad-dc-3": ".",
139 "samba-ad-dc-4": ".",
140 "samba-ad-dc-4-mitkrb5": ".",
141 "samba-ad-dc-5": ".",
142 "samba-ad-dc-6": ".",
143 "samba-ad-dc-ntvfs": ".",
144 "samba-ad-dc-backup": ".",
145 "samba-nopython": ".",
146 "samba-nopython-py2": ".",
147 "samba-schemaupgrade": ".",
150 "talloc": "lib/talloc",
151 "replace": "lib/replace",
152 "tevent": "lib/tevent",
156 defaulttasks = list(builddirs.keys())
158 if os.environ.get("AUTOBUILD_SKIP_SAMBA_O3", "0") == "1":
159 defaulttasks.remove("samba-o3")
161 ctdb_configure_params = " --enable-developer ${PREFIX}"
162 samba_configure_params = " ${ENABLE_COVERAGE} ${PREFIX} --with-profiling-data"
164 samba_libs_envvars = "PYTHONPATH=${PYTHON_PREFIX}:$PYTHONPATH"
165 samba_libs_envvars += " PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${PREFIX_DIR}/lib/pkgconfig"
166 samba_libs_envvars += " ADDITIONAL_CFLAGS='-Wmissing-prototypes'"
167 samba_libs_configure_base = samba_libs_envvars + " ./configure --abi-check ${ENABLE_COVERAGE} --enable-debug -C ${PREFIX}"
168 samba_libs_configure_libs = samba_libs_configure_base + " --bundled-libraries=cmocka,popt,NONE"
169 samba_libs_configure_bundled_libs = " --bundled-libraries=!talloc,!pytalloc-util,!tdb,!pytdb,!ldb,!pyldb,!pyldb-util,!tevent,!pytevent,!popt"
170 samba_libs_configure_samba = samba_libs_configure_base + samba_libs_configure_bundled_libs
173 def format_option(name, value=None):
174 """Format option as str list."""
175 if value is None: # boolean option
177 if not isinstance(value, list): # single value option
180 return ['{}={}'.format(name, item) for item in value]
192 test_options = format_option('--include-env', include_envs)
194 test_options = format_option('--exclude-env', exclude_envs)
196 # join envs options to original test options
197 TESTS = (TESTS + ' ' + ' '.join(test_options)).strip()
201 _options.append('FAIL_IMMEDIATELY=1')
203 _options.append("TESTS='{}'".format(TESTS))
205 return ' '.join([cmd] + _options)
210 ("random-sleep", random_sleep(300, 900)),
211 ("configure", "./configure " + ctdb_configure_params),
212 ("make", "make all"),
213 ("install", "make install"),
214 ("test", "make autotest"),
215 ("check-clean-tree", "../script/clean-source-tree.sh"),
216 ("clean", "make clean"),
219 # We have 'test' before 'install' because, 'test' should work without 'install (runs all the other envs)'
221 ("random-sleep", random_sleep(300, 900)),
222 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
224 ("test", make_test(exclude_envs=[
240 "ad_member_idmap_rid",
241 "ad_member_idmap_ad",
265 ("install", "make install"),
266 ("check-clean-tree", "script/clean-source-tree.sh"),
267 ("clean", "make clean"),
271 ("random-sleep", random_sleep(300, 900)),
272 ("configure", "./configure.developer --without-ads --with-selftest-prefix=./bin/ab" + samba_configure_params),
274 ("test", make_test(include_envs=[
280 ("install", "make install"),
281 ("check-clean-tree", "script/clean-source-tree.sh"),
282 ("clean", "make clean"),
285 "samba-simpleserver": [
286 ("random-sleep", random_sleep(300, 900)),
287 ("configure", "./configure.developer --without-ad-dc --without-ldap --without-ads --without-json --with-selftest-prefix=./bin/ab" + samba_configure_params),
289 ("test", make_test(include_envs=[
293 ("check-clean-tree", "script/clean-source-tree.sh"),
296 "samba-fileserver": [
297 ("random-sleep", random_sleep(300, 900)),
298 ("configure", "./configure.developer --without-ad-dc --with-selftest-prefix=./bin/ab" + samba_configure_params),
300 ("test", make_test(include_envs=[
305 ("check-clean-tree", "script/clean-source-tree.sh"),
309 ("random-sleep", random_sleep(300, 900)),
310 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
312 ("test", make_test(include_envs=[
314 "ad_member_idmap_rid",
315 "ad_member_idmap_ad",
319 ("check-clean-tree", "script/clean-source-tree.sh"),
323 ("random-sleep", random_sleep(1, 1)),
324 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
326 ("test", make_test(include_envs=[
332 ("check-clean-tree", "script/clean-source-tree.sh"),
336 ("random-sleep", random_sleep(1, 1)),
337 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
339 ("test", make_test(include_envs=[
345 ("check-clean-tree", "script/clean-source-tree.sh"),
349 ("random-sleep", random_sleep(1, 1)),
350 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
352 ("test", make_test(include_envs=[
359 ("check-clean-tree", "script/clean-source-tree.sh"),
363 ("random-sleep", random_sleep(1, 1)),
364 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
366 ("test", make_test(include_envs=[
373 ("check-clean-tree", "script/clean-source-tree.sh"),
377 ("random-sleep", random_sleep(1, 1)),
378 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
380 ("test", make_test(include_envs=["ad_dc_default"])),
382 ("check-clean-tree", "script/clean-source-tree.sh"),
386 ("random-sleep", random_sleep(1, 1)),
387 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
389 ("test", make_test(include_envs=["ad_dc_slowtests"])),
391 ("check-clean-tree", "script/clean-source-tree.sh"),
394 "samba-schemaupgrade": [
395 ("random-sleep", random_sleep(1, 1)),
396 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
398 ("test", make_test(include_envs=["schema_dc", "schema_pair_dc"])),
400 ("check-clean-tree", "script/clean-source-tree.sh"),
403 # We split out the ad_dc_ntvfs tests (which are long) so other test do not wait
404 # This is currently the longest task, so we don't randomly delay it.
405 "samba-ad-dc-ntvfs": [
406 ("random-sleep", random_sleep(1, 1)),
407 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
409 ("test", make_test(include_envs=["ad_dc_ntvfs"])),
411 ("check-clean-tree", "script/clean-source-tree.sh"),
414 # run the backup/restore testenvs separately as they're fairly standalone
415 # (and CI seems to max out at ~8 different DCs running at once)
416 "samba-ad-dc-backup": [
417 ("random-sleep", random_sleep(300, 900)),
418 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
420 ("test", make_test(include_envs=[
429 ("check-clean-tree", "script/clean-source-tree.sh"),
432 "samba-ad-member-mitkrb5": [
433 ("random-sleep", random_sleep(300, 900)),
434 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab --with-system-mitkrb5 --with-experimental-mit-ad-dc" + samba_configure_params),
436 ("test", make_test(include_envs=[
438 "ad_member_idmap_rid",
439 "ad_member_idmap_ad",
443 ("check-clean-tree", "script/clean-source-tree.sh"),
446 "samba-ad-dc-1-mitkrb5": [
447 ("random-sleep", random_sleep(1, 1)),
448 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab --with-system-mitkrb5 --with-experimental-mit-ad-dc" + samba_configure_params),
450 ("test", make_test(include_envs=[
456 ("check-clean-tree", "script/clean-source-tree.sh"),
459 "samba-ad-dc-4-mitkrb5": [
460 ("random-sleep", random_sleep(1, 1)),
461 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab --with-system-mitkrb5 --with-experimental-mit-ad-dc" + samba_configure_params),
463 ("test", make_test(include_envs=[
470 ("check-clean-tree", "script/clean-source-tree.sh"),
474 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab --abi-check-disable" + samba_configure_params),
476 ("test", make_test(TESTS="${TESTS}")),
480 # Test cross-compile infrastructure
482 ("random-sleep", random_sleep(900, 1500)),
483 ("configure-native", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
484 ("configure-cross-execute", "./configure.developer --out ./bin-xe --cross-compile --cross-execute=script/identity_cc.sh" \
485 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xe/ab" + samba_configure_params),
486 ("configure-cross-answers", "./configure.developer --out ./bin-xa --cross-compile" \
487 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xa/ab" + samba_configure_params),
488 ("compare-results", "script/compare_cc_results.py "
489 "./bin/c4che/default{} "
490 "./bin-xe/c4che/default{} "
491 "./bin-xa/c4che/default{}".format(*([CACHE_SUFFIX]*3))),
494 # test build with -O3 -- catches extra warnings and bugs, tests the ad_dc environments
496 ("random-sleep", random_sleep(300, 900)),
497 ("configure", "ADDITIONAL_CFLAGS='-O3 -Wp,-D_FORTIFY_SOURCE=2' ./configure.developer --with-selftest-prefix=./bin/ab --abi-check-disable" + samba_configure_params),
499 ("test", make_test(cmd='make quicktest', include_envs=["ad_dc"])),
501 ("install", "make install"),
502 ("check-clean-tree", "script/clean-source-tree.sh"),
503 ("clean", "make clean"),
507 ("random-sleep", random_sleep(900, 1500)),
509 # make sure we have tdb around:
510 ("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}"),
511 ("tdb-make", "cd lib/tdb && make"),
512 ("tdb-install", "cd lib/tdb && make install"),
514 # build samba with cluster support (also building ctdb):
515 ("samba-configure", "PYTHONPATH=${PYTHON_PREFIX}:$PYTHONPATH PKG_CONFIG_PATH=${PREFIX_DIR}/lib/pkgconfig:${PKG_CONFIG_PATH} ./configure.developer ${PREFIX} --with-selftest-prefix=./bin/ab --with-cluster-support --bundled-libraries=!tdb"),
516 ("samba-make", "make"),
517 ("samba-check", "./bin/smbd -b | grep CLUSTER_SUPPORT"),
518 ("samba-install", "make install"),
519 ("ctdb-check", "test -e ${PREFIX_DIR}/sbin/ctdbd"),
522 ("check-clean-tree", "script/clean-source-tree.sh"),
523 ("clean", "make clean"),
524 ("ctdb-clean", "cd ./ctdb && make clean"),
528 ("random-sleep", random_sleep(300, 900)),
529 ("talloc-configure", "cd lib/talloc && " + samba_libs_configure_libs),
530 ("talloc-make", "cd lib/talloc && make"),
531 ("talloc-install", "cd lib/talloc && make install"),
533 ("tdb-configure", "cd lib/tdb && " + samba_libs_configure_libs),
534 ("tdb-make", "cd lib/tdb && make"),
535 ("tdb-install", "cd lib/tdb && make install"),
537 ("tevent-configure", "cd lib/tevent && " + samba_libs_configure_libs),
538 ("tevent-make", "cd lib/tevent && make"),
539 ("tevent-install", "cd lib/tevent && make install"),
541 ("ldb-configure", "cd lib/ldb && " + samba_libs_configure_libs),
542 ("ldb-make", "cd lib/ldb && make"),
543 ("ldb-install", "cd lib/ldb && make install"),
545 ("nondevel-configure", "./configure ${PREFIX}"),
546 ("nondevel-make", "make -j"),
547 ("nondevel-check", "./bin/smbd -b | grep WITH_NTVFS_FILESERVER && exit 1; exit 0"),
548 ("nondevel-install", "make install"),
549 ("nondevel-dist", "make dist"),
551 # retry with all modules shared
552 ("allshared-distclean", "make distclean"),
553 ("allshared-configure", samba_libs_configure_samba + " --with-shared-modules=ALL"),
554 ("allshared-make", "make -j"),
558 ("random-sleep", random_sleep(1, 1)),
559 ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
561 ("test", make_test(include_envs=["none"])),
566 ("random-sleep", random_sleep(1, 1)),
567 # build with all modules static
568 ("allstatic-configure", "./configure.developer " + samba_configure_params + " --with-static-modules=ALL"),
569 ("allstatic-make", "make -j"),
570 ("allstatic-test", make_test(TESTS="samba3.smb2.create.*nt4_dc")),
573 # retry without any required modules
574 ("none-distclean", "make distclean"),
575 ("none-configure", "./configure.developer " + samba_configure_params + " --with-static-modules=!FORCED,!DEFAULT --with-shared-modules=!FORCED,!DEFAULT"),
576 ("none-make", "make -j"),
578 # retry with nonshared smbd and smbtorture
579 ("nonshared-distclean", "make distclean"),
580 ("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"),
581 ("nonshared-make", "make -j"),
584 # Test Samba without python still builds. When this test fails
585 # due to more use of Python, the expectations is that the newly
586 # failing part of the code should be disabled when
587 # --disable-python is set (rather than major work being done to
588 # support this environment). The target here is for vendors
589 # shipping a minimal smbd.
591 ("random-sleep", random_sleep(300, 900)),
592 ("configure", "./configure.developer ${ENABLE_COVERAGE} ${PREFIX} --with-profiling-data --disable-python --without-ad-dc"),
594 ("install", "make install"),
595 ("find-python", "script/find_python.sh ${PREFIX}"),
596 ("test", "make test-nopython"),
598 ("check-clean-tree", "script/clean-source-tree.sh"),
599 ("clean", "make clean"),
601 ("talloc-configure", "cd lib/talloc && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
602 ("talloc-make", "cd lib/talloc && make"),
603 ("talloc-install", "cd lib/talloc && make install"),
605 ("tdb-configure", "cd lib/tdb && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
606 ("tdb-make", "cd lib/tdb && make"),
607 ("tdb-install", "cd lib/tdb && make install"),
609 ("tevent-configure", "cd lib/tevent && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
610 ("tevent-make", "cd lib/tevent && make"),
611 ("tevent-install", "cd lib/tevent && make install"),
613 ("ldb-configure", "cd lib/ldb && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
614 ("ldb-make", "cd lib/ldb && make"),
615 ("ldb-install", "cd lib/ldb && make install"),
617 # retry against installed library packages
618 ("libs-configure", samba_libs_configure_base + samba_libs_configure_bundled_libs + " --disable-python --without-ad-dc"),
619 ("libs-make", "make -j"),
620 ("libs-install", "make install"),
621 ("libs-check-clean-tree", "script/clean-source-tree.sh"),
622 ("libs-clean", "make clean"),
625 # check we can do the same thing using python2
626 "samba-nopython-py2": [
627 ("random-sleep", random_sleep(300, 900)),
628 ("configure", "PYTHON=python2 ./configure.developer ${ENABLE_COVERAGE} ${PREFIX} --with-profiling-data --disable-python --without-ad-dc"),
629 ("make", "PYTHON=python2 make -j"),
630 ("install", "PYTHON=python2 make install"),
631 ("find-python", "script/find_python.sh ${PREFIX}"),
632 ("test", "make test-nopython"),
634 ("check-clean-tree", "script/clean-source-tree.sh"),
635 ("clean", "PYTHON=python2 make clean"),
637 ("talloc-configure", "cd lib/talloc && PYTHON=python2 " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
638 ("talloc-make", "cd lib/talloc && PYTHON=python2 make"),
639 ("talloc-install", "cd lib/talloc && PYTHON=python2 make install"),
641 ("tdb-configure", "cd lib/tdb && PYTHON=python2 " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
642 ("tdb-make", "cd lib/tdb && PYTHON=python2 make"),
643 ("tdb-install", "cd lib/tdb && PYTHON=python2 make install"),
645 ("tevent-configure", "cd lib/tevent && PYTHON=python2 " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
646 ("tevent-make", "cd lib/tevent && PYTHON=python2 make"),
647 ("tevent-install", "cd lib/tevent && PYTHON=python2 make install"),
649 ("ldb-configure", "cd lib/ldb && PYTHON=python2 " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
650 ("ldb-make", "cd lib/ldb && PYTHON=python2 make"),
651 ("ldb-install", "cd lib/ldb && PYTHON=python2 make install"),
653 # retry against installed library packages
654 ("libs-configure", "PYTHON=python2 " + samba_libs_configure_base + samba_libs_configure_bundled_libs + " --disable-python --without-ad-dc"),
655 ("libs-make", "PYTHON=python2 make -j"),
656 ("libs-install", "PYTHON=python2 make install"),
657 ("libs-check-clean-tree", "script/clean-source-tree.sh"),
658 ("libs-clean", "PYTHON=python2 make clean"),
662 ("random-sleep", random_sleep(60, 600)),
663 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
665 ("install", "make install"),
666 ("test", "make test"),
668 ("clean", "make clean"),
669 ("configure-no-lmdb", "./configure ${ENABLE_COVERAGE} --enable-developer --without-ldb-lmdb -C ${PREFIX}"),
670 ("make-no-lmdb", "make"),
671 ("test-no-lmdb", "make test"),
672 ("lcov-no-lmdb", LCOV_CMD),
673 ("install-no-lmdb", "make install"),
674 ("check-clean-tree", "../../script/clean-source-tree.sh"),
675 ("distcheck", "make distcheck"),
676 ("clean", "make clean"),
680 ("random-sleep", random_sleep(60, 600)),
681 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
683 ("install", "make install"),
684 ("test", "make test"),
686 ("check-clean-tree", "../../script/clean-source-tree.sh"),
687 ("distcheck", "make distcheck"),
688 ("clean", "make clean"),
692 ("random-sleep", random_sleep(60, 600)),
693 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
695 ("install", "make install"),
696 ("test", "make test"),
698 ("check-clean-tree", "../../script/clean-source-tree.sh"),
699 ("distcheck", "make distcheck"),
700 ("clean", "make clean"),
704 ("random-sleep", random_sleep(60, 600)),
705 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
707 ("install", "make install"),
708 ("test", "make test"),
710 ("check-clean-tree", "../../script/clean-source-tree.sh"),
711 ("distcheck", "make distcheck"),
712 ("clean", "make clean"),
716 ("random-sleep", random_sleep(60, 600)),
717 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
719 ("install", "make install"),
720 ("test", "make test"),
722 ("check-clean-tree", "../../script/clean-source-tree.sh"),
723 ("distcheck", "make distcheck"),
724 ("clean", "make clean"),
728 ("random-sleep", random_sleep(60, 600)),
729 ("configure", "perl Makefile.PL PREFIX=${PREFIX_DIR}"),
730 ("touch", "touch *.yp"),
732 ("test", "make test"),
733 ("install", "make install"),
734 ("checkout-yapp-generated", "git checkout lib/Parse/Pidl/IDL.pm lib/Parse/Pidl/Expr.pm"),
735 ("check-clean-tree", "../script/clean-source-tree.sh"),
736 ("clean", "make clean"),
739 # these are useful for debugging autobuild
740 'pass': [("pass", 'echo passing && /bin/true')],
741 'fail': [("fail", 'echo failing && /bin/false')],
751 def run_cmd(cmd, dir=".", show=None, output=False, checkfail=True):
753 show = options.verbose
755 do_print("Running: '%s' in '%s'" % (cmd, dir))
757 out = check_output([cmd], shell=True, cwd=dir)
758 return out.decode(encoding='utf-8', errors='backslashreplace')
760 return check_call(cmd, shell=True, cwd=dir)
762 return call(cmd, shell=True, cwd=dir)
765 class builder(object):
766 '''handle build of one directory'''
768 def __init__(self, name, sequence, cp=True):
770 self.dir = builddirs.get(name, '.')
771 self.tag = self.name.replace('/', '_')
772 self.sequence = sequence
774 self.stdout_path = "%s/%s.stdout" % (gitroot, self.tag)
775 self.stderr_path = "%s/%s.stderr" % (gitroot, self.tag)
777 do_print("stdout for %s in %s" % (self.name, self.stdout_path))
778 do_print("stderr for %s in %s" % (self.name, self.stderr_path))
779 run_cmd("rm -f %s %s" % (self.stdout_path, self.stderr_path))
780 self.stdout = open(self.stdout_path, 'w')
781 self.stderr = open(self.stderr_path, 'w')
782 self.stdin = open("/dev/null", 'r')
783 self.test_source_dir = "%s/%s" % (testbase, self.tag)
784 self.cwd = "%s/%s" % (self.test_source_dir, self.dir)
785 self.prefix = "%s/%s" % (test_prefix, self.tag)
786 run_cmd("rm -rf %s" % self.test_source_dir)
787 run_cmd("rm -rf %s" % self.prefix)
789 run_cmd("cp --recursive --link --archive %s %s" % (test_master, self.test_source_dir), dir=test_master, show=True)
791 run_cmd("git clone --recursive --shared %s %s" % (test_master, self.test_source_dir), dir=test_master, show=True)
794 def start_next(self):
795 if self.next == len(self.sequence):
796 if not options.nocleanup:
797 run_cmd("rm -rf %s" % self.test_source_dir)
798 run_cmd("rm -rf %s" % self.prefix)
799 do_print('%s: Completed OK' % self.name)
802 (self.stage, self.cmd) = self.sequence[self.next]
803 self.cmd = self.cmd.replace("${PYTHON_PREFIX}", get_python_lib(plat_specific=1, standard_lib=0, prefix=self.prefix))
804 self.cmd = self.cmd.replace("${PREFIX}", "--prefix=%s" % self.prefix)
805 self.cmd = self.cmd.replace("${PREFIX_DIR}", "%s" % self.prefix)
806 self.cmd = self.cmd.replace("${TESTS}", options.restrict_tests)
807 self.cmd = self.cmd.replace("${TEST_SOURCE_DIR}", self.test_source_dir)
808 self.cmd = self.cmd.replace("${LOG_BASE}", options.log_base)
809 self.cmd = self.cmd.replace("${NAME}", self.name)
810 self.cmd = self.cmd.replace("${ENABLE_COVERAGE}", options.enable_coverage)
811 do_print('%s: [%s] Running %s in %r' % (self.name, self.stage, self.cmd, self.cwd))
812 self.proc = Popen(self.cmd, shell=True,
813 close_fds=True, cwd=self.cwd,
814 stdout=self.stdout, stderr=self.stderr, stdin=self.stdin)
818 class buildlist(object):
819 '''handle build of multiple directories'''
821 def __init__(self, tasknames, rebase_url, rebase_branch="master"):
822 self.tail_proc = None
825 if options.restrict_tests:
826 tasknames = ["samba-test-only"]
828 tasknames = defaulttasks
830 self.tlist = [builder(n, tasks[n], cp=(n != "pidl")) for n in tasknames]
833 rebase_remote = "rebaseon"
834 retry_task = [("retry",
836 git remote add -t %s %s %s
840 git describe %s/%s > old_remote_branch.desc
842 git describe %s/%s > remote_branch.desc
843 diff old_remote_branch.desc remote_branch.desc
846 rebase_branch, rebase_remote, rebase_url,
848 rebase_remote, rebase_branch,
850 rebase_remote, rebase_branch
853 self.retry = builder('retry', retry_task, cp=False)
854 self.need_retry = False
857 if self.tail_proc is not None:
858 self.tail_proc.terminate()
859 self.tail_proc.wait()
860 self.tail_proc = None
861 if self.retry is not None:
862 self.retry.proc.terminate()
863 self.retry.proc.wait()
866 if b.proc is not None:
867 run_cmd("killbysubdir %s > /dev/null 2>&1" % b.test_source_dir, checkfail=False)
879 b.status = b.proc.poll()
885 ret = self.retry.proc.poll()
887 self.need_retry = True
897 if options.retry and self.need_retry:
899 do_print("retry needed")
900 return (0, None, None, None, "retry")
903 if os.WIFSIGNALED(b.status) or os.WEXITSTATUS(b.status) != 0:
905 return (b.status, b.name, b.stage, b.tag, "%s: [%s] failed '%s' with status %d" % (b.name, b.stage, b.cmd, b.status))
908 return (0, None, None, None, "All OK")
910 def write_system_info(self, filename):
911 with open(filename, 'w') as f:
912 for cmd in ['uname -a',
919 'df -m %s' % testbase]:
920 out = run_cmd(cmd, output=True, checkfail=False)
921 print('### %s' % cmd, file=f)
925 def tarlogs(self, fname):
926 with tarfile.open(fname, "w:gz") as tar:
928 tar.add(b.stdout_path, arcname="%s.stdout" % b.tag)
929 tar.add(b.stderr_path, arcname="%s.stderr" % b.tag)
930 if os.path.exists("autobuild.log"):
931 tar.add("autobuild.log")
932 filename = 'system-info.txt'
933 self.write_system_info(filename)
936 def remove_logs(self):
938 os.unlink(b.stdout_path)
939 os.unlink(b.stderr_path)
941 def start_tail(self):
944 cmd.append(b.stdout_path)
945 cmd.append(b.stderr_path)
946 self.tail_proc = Popen(cmd, close_fds=True)
950 if options.nocleanup:
952 run_cmd("stat %s || true" % test_tmpdir, show=True)
953 run_cmd("stat %s" % testbase, show=True)
954 do_print("Cleaning up %r" % cleanup_list)
955 for d in cleanup_list:
956 run_cmd("rm -rf %s" % d)
959 def daemonize(logfile):
961 if pid == 0: # Parent
964 if pid != 0: # Actual daemon
969 import resource # Resource usage information.
970 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
971 if maxfd == resource.RLIM_INFINITY:
972 maxfd = 1024 # Rough guess at maximum number of open file descriptors.
973 for fd in range(0, maxfd):
978 os.open(logfile, os.O_RDWR | os.O_CREAT)
983 def write_pidfile(fname):
984 '''write a pid file, cleanup on exit'''
985 with open(fname, mode='w') as f:
986 f.write("%u\n" % os.getpid())
989 def rebase_tree(rebase_url, rebase_branch="master"):
990 rebase_remote = "rebaseon"
991 do_print("Rebasing on %s" % rebase_url)
992 run_cmd("git describe HEAD", show=True, dir=test_master)
993 run_cmd("git remote add -t %s %s %s" %
994 (rebase_branch, rebase_remote, rebase_url),
995 show=True, dir=test_master)
996 run_cmd("git fetch %s" % rebase_remote, show=True, dir=test_master)
997 if options.fix_whitespace:
998 run_cmd("git rebase --force-rebase --whitespace=fix %s/%s" %
999 (rebase_remote, rebase_branch),
1000 show=True, dir=test_master)
1002 run_cmd("git rebase --force-rebase %s/%s" %
1003 (rebase_remote, rebase_branch),
1004 show=True, dir=test_master)
1005 diff = run_cmd("git --no-pager diff HEAD %s/%s" %
1006 (rebase_remote, rebase_branch),
1007 dir=test_master, output=True)
1009 do_print("No differences between HEAD and %s/%s - exiting" %
1010 (rebase_remote, rebase_branch))
1012 run_cmd("git describe %s/%s" %
1013 (rebase_remote, rebase_branch),
1014 show=True, dir=test_master)
1015 run_cmd("git describe HEAD", show=True, dir=test_master)
1016 run_cmd("git --no-pager diff --stat HEAD %s/%s" %
1017 (rebase_remote, rebase_branch),
1018 show=True, dir=test_master)
1021 def push_to(push_url, push_branch="master"):
1022 push_remote = "pushto"
1023 do_print("Pushing to %s" % push_url)
1025 run_cmd("git config --replace-all core.editor script/commit_mark.sh", dir=test_master)
1026 run_cmd("git commit --amend -c HEAD", dir=test_master)
1027 # the notes method doesn't work yet, as metze hasn't allowed refs/notes/* in master
1028 # run_cmd("EDITOR=script/commit_mark.sh git notes edit HEAD", dir=test_master)
1029 run_cmd("git remote add -t %s %s %s" %
1030 (push_branch, push_remote, push_url),
1031 show=True, dir=test_master)
1032 run_cmd("git push %s +HEAD:%s" %
1033 (push_remote, push_branch),
1034 show=True, dir=test_master)
1037 def send_email(subject, text, log_tar):
1038 if options.email is None:
1039 do_print("not sending email because the recipient is not set")
1040 do_print("the text content would have been:\n\nSubject: %s\n\n%s" %
1043 outer = MIMEMultipart()
1044 outer['Subject'] = subject
1045 outer['To'] = options.email
1046 outer['From'] = options.email_from
1047 outer['Date'] = email.utils.formatdate(localtime=True)
1048 outer.preamble = 'Autobuild mails are now in MIME because we optionally attach the logs.\n'
1049 outer.attach(MIMEText(text, 'plain'))
1050 if options.attach_logs:
1051 with open(log_tar, 'rb') as fp:
1052 msg = MIMEApplication(fp.read(), 'gzip', email.encoders.encode_base64)
1053 # Set the filename parameter
1054 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(log_tar))
1056 content = outer.as_string()
1057 s = smtplib.SMTP(options.email_server)
1058 email_user = os.getenv('SMTP_USERNAME')
1059 email_password = os.getenv('SMTP_PASSWORD')
1060 if email_user is not None:
1062 s.login(email_user, email_password)
1064 s.sendmail(options.email_from, [options.email], content)
1069 def email_failure(status, failed_task, failed_stage, failed_tag, errstr,
1070 elapsed_time, log_base=None, add_log_tail=True):
1071 '''send an email to options.email about the failure'''
1072 elapsed_minutes = elapsed_time / 60.0
1073 if log_base is None:
1078 Your autobuild on %s failed after %.1f minutes
1079 when trying to test %s with the following error:
1083 the autobuild has been abandoned. Please fix the error and resubmit.
1085 A summary of the autobuild process is here:
1088 ''' % (platform.node(), elapsed_minutes, failed_task, errstr, log_base)
1090 if options.restrict_tests:
1092 The build was restricted to tests matching %s\n""" % options.restrict_tests
1094 if failed_task != 'rebase':
1096 You can see logs of the failed task here:
1101 or you can get full logs of all tasks in this job here:
1105 The top commit for the tree that was built was:
1109 ''' % (log_base, failed_tag, log_base, failed_tag, log_base, top_commit_msg)
1112 f = open("%s/%s.stdout" % (gitroot, failed_tag), 'r')
1113 lines = f.readlines()
1114 log_tail = "".join(lines[-50:])
1115 num_lines = len(lines)
1117 # Also include stderr (compile failures) if < 50 lines of stdout
1118 f = open("%s/%s.stderr" % (gitroot, failed_tag), 'r')
1119 log_tail += "".join(f.readlines()[-(50 - num_lines):])
1122 The last 50 lines of log messages:
1128 logs = os.path.join(gitroot, 'logs.tar.gz')
1129 send_email('autobuild[%s] failure on %s for task %s during %s'
1130 % (options.branch, platform.node(), failed_task, failed_stage),
1134 def email_success(elapsed_time, log_base=None):
1135 '''send an email to options.email about a successful build'''
1136 if log_base is None:
1141 Your autobuild on %s has succeeded after %.1f minutes.
1143 ''' % (platform.node(), elapsed_time / 60.)
1145 if options.restrict_tests:
1147 The build was restricted to tests matching %s\n""" % options.restrict_tests
1149 if options.keeplogs:
1152 you can get full logs of all tasks in this job here:
1159 The top commit for the tree that was built was:
1162 ''' % top_commit_msg
1164 logs = os.path.join(gitroot, 'logs.tar.gz')
1165 send_email('autobuild[%s] success on %s' % (options.branch, platform.node()),
1169 # get the top commit message, for emails
1170 top_commit_msg = run_cmd("git log -1", dir=gitroot, output=True)
1173 os.makedirs(testbase)
1174 except Exception as reason:
1175 raise Exception("Unable to create %s : %s" % (testbase, reason))
1176 cleanup_list.append(testbase)
1179 logfile = os.path.join(testbase, "log")
1180 do_print("Forking into the background, writing progress to %s" % logfile)
1183 write_pidfile(gitroot + "/autobuild.pid")
1185 start_time = time.time()
1189 run_cmd("rm -rf %s" % test_tmpdir, show=True)
1190 os.makedirs(test_tmpdir)
1191 # The waf uninstall code removes empty directories all the way
1192 # up the tree. Creating a file in test_tmpdir stops it from
1194 run_cmd("touch %s" % os.path.join(test_tmpdir,
1195 ".directory-is-not-empty"), show=True)
1196 run_cmd("stat %s" % test_tmpdir, show=True)
1197 run_cmd("stat %s" % testbase, show=True)
1198 run_cmd("git clone --recursive --shared %s %s" % (gitroot, test_master), show=True, dir=gitroot)
1204 if options.rebase is not None:
1205 rebase_tree(options.rebase, rebase_branch=options.branch)
1207 cleanup_list.append(gitroot + "/autobuild.pid")
1209 elapsed_time = time.time() - start_time
1210 email_failure(-1, 'rebase', 'rebase', 'rebase',
1211 'rebase on %s failed' % options.branch,
1212 elapsed_time, log_base=options.log_base)
1216 blist = buildlist(args, options.rebase, rebase_branch=options.branch)
1219 (status, failed_task, failed_stage, failed_tag, errstr) = blist.run()
1220 if status != 0 or errstr != "retry":
1227 cleanup_list.append(gitroot + "/autobuild.pid")
1233 do_print("waiting for tail to flush")
1236 elapsed_time = time.time() - start_time
1238 if options.passcmd is not None:
1239 do_print("Running passcmd: %s" % options.passcmd)
1240 run_cmd(options.passcmd, dir=test_master)
1241 if options.pushto is not None:
1242 push_to(options.pushto, push_branch=options.branch)
1243 if options.keeplogs or options.attach_logs:
1244 blist.tarlogs("logs.tar.gz")
1245 do_print("Logs in logs.tar.gz")
1246 if options.always_email:
1247 email_success(elapsed_time, log_base=options.log_base)
1253 # something failed, gather a tar of the logs
1254 blist.tarlogs("logs.tar.gz")
1256 if options.email is not None:
1257 email_failure(status, failed_task, failed_stage, failed_tag, errstr,
1258 elapsed_time, log_base=options.log_base)
1260 elapsed_minutes = elapsed_time / 60.0
1263 ####################################################################
1267 Your autobuild[%s] on %s failed after %.1f minutes
1268 when trying to test %s with the following error:
1272 the autobuild has been abandoned. Please fix the error and resubmit.
1274 ####################################################################
1276 ''' % (options.branch, platform.node(), elapsed_minutes, failed_task, errstr))
1280 do_print("Logs in logs.tar.gz")