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, CalledProcessError
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.exists(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("--skip-dependencies", help="skip to run task dependency tasks", default=False, action="store_true")
58 parser.add_option("--testbase", help="base directory to run tests in (default %s)" % def_testbase,
60 parser.add_option("--full-testbase", help="full base directory to run tests in (default %s/b$PID)" % def_testbase,
62 parser.add_option("--passcmd", help="command to run on success", default=None)
63 parser.add_option("--verbose", help="show all commands as they are run",
64 default=False, action="store_true")
65 parser.add_option("--rebase", help="rebase on the given tree before testing",
66 default=None, type='str')
67 parser.add_option("--pushto", help="push to a git url on success",
68 default=None, type='str')
69 parser.add_option("--mark", help="add a Tested-By signoff before pushing",
70 default=False, action="store_true")
71 parser.add_option("--fix-whitespace", help="fix whitespace on rebase",
72 default=False, action="store_true")
73 parser.add_option("--retry", help="automatically retry if master changes",
74 default=False, action="store_true")
75 parser.add_option("--email", help="send email to the given address on failure",
76 type='str', default=None)
77 parser.add_option("--email-from", help="send email from the given address",
78 type='str', default="autobuild@samba.org")
79 parser.add_option("--email-server", help="send email via the given server",
80 type='str', default='localhost')
81 parser.add_option("--always-email", help="always send email, even on success",
83 parser.add_option("--daemon", help="daemonize after initial setup",
85 parser.add_option("--branch", help="the branch to work on (default=master)",
86 default="master", type='str')
87 parser.add_option("--log-base", help="location where the logs can be found (default=cwd)",
88 default=gitroot, type='str')
89 parser.add_option("--attach-logs", help="Attach logs to mails sent on success/failure?",
90 default=False, action="store_true")
91 parser.add_option("--restrict-tests", help="run as make test with this TESTS= regex",
93 parser.add_option("--enable-coverage", dest='enable_coverage',
94 action="store_const", const='--enable-coverage', default='',
95 help="Add --enable-coverage option while configure")
97 (options, args) = parser.parse_args()
100 if options.rebase is None:
101 raise Exception('You can only use --retry if you also rebase')
103 if options.full_testbase is not None:
104 testbase = options.full_testbase
106 testbase = "%s/b%u" % (options.testbase, os.getpid())
107 test_master = "%s/master" % testbase
108 test_prefix = "%s/prefix" % testbase
109 test_tmpdir = "%s/tmp" % testbase
110 os.environ['TMPDIR'] = test_tmpdir
112 if options.enable_coverage:
113 LCOV_CMD = "cd ${TEST_SOURCE_DIR} && lcov --capture --directory . --output-file ${LOG_BASE}/${NAME}.info --rc 'geninfo_adjust_src_path=${TEST_SOURCE_DIR}/'"
115 LCOV_CMD = 'echo "lcov skipped since no --enable-coverage specified"'
117 CLEAN_SOURCE_TREE_CMD = "cd ${TEST_SOURCE_DIR} && script/clean-source-tree.sh"
120 # If we are only running specific test,
121 # do not sleep randomly to wait for it to start
122 def random_sleep(low, high):
125 def random_sleep(low, high):
126 return 'sleep {}'.format(random.randint(low, high))
134 "talloc": "lib/talloc",
135 "replace": "lib/replace",
136 "tevent": "lib/tevent",
138 "docs-xml": "docs-xml"
141 ctdb_configure_params = " --enable-developer ${PREFIX}"
142 samba_configure_params = " ${ENABLE_COVERAGE} ${PREFIX} --with-profiling-data"
144 samba_libs_envvars = "PYTHONPATH=${PYTHON_PREFIX}:$PYTHONPATH"
145 samba_libs_envvars += " PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${PREFIX_DIR}/lib/pkgconfig"
146 samba_libs_envvars += " ADDITIONAL_CFLAGS='-Wmissing-prototypes'"
147 samba_libs_configure_base = samba_libs_envvars + " ./configure --abi-check ${ENABLE_COVERAGE} --enable-debug -C ${PREFIX}"
148 samba_libs_configure_libs = samba_libs_configure_base + " --bundled-libraries=cmocka,popt,NONE"
149 samba_libs_configure_bundled_libs = " --bundled-libraries=!talloc,!pytalloc-util,!tdb,!pytdb,!ldb,!pyldb,!pyldb-util,!tevent,!pytevent,!popt"
150 samba_libs_configure_samba = samba_libs_configure_base + samba_libs_configure_bundled_libs
153 def format_option(name, value=None):
154 """Format option as str list."""
155 if value is None: # boolean option
157 if not isinstance(value, list): # single value option
160 return ['{}={}'.format(name, item) for item in value]
166 INJECT_SELFTEST_PREFIX=1,
173 test_options = format_option('--include-env', include_envs)
175 test_options = format_option('--exclude-env', exclude_envs)
177 # join envs options to original test options
178 TESTS = (TESTS + ' ' + ' '.join(test_options)).strip()
182 _options.append('FAIL_IMMEDIATELY=1')
184 _options.append("TESTS='{}'".format(TESTS))
186 if INJECT_SELFTEST_PREFIX:
187 _options.append("TEST_OPTIONS='--with-selftest-prefix={}'".format("${SELFTEST_PREFIX}"))
188 _options.append("--directory='{}'".format("${TEST_SOURCE_DIR}"))
190 return ' '.join([cmd] + _options)
193 # When updating this list, also update .gitlab-ci.yml to add the job
194 # and to make it a dependency of 'page' for the coverage report.
199 ("random-sleep", random_sleep(300, 900)),
200 ("configure", "./configure " + ctdb_configure_params),
201 ("make", "make all"),
202 ("install", "make install"),
203 ("test", "make autotest"),
204 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
205 ("clean", "make clean"),
210 ("random-sleep", random_sleep(300, 900)),
211 ("autoconf", "autoconf"),
212 ("configure", "./configure"),
213 ("make", "make html htmlman"),
214 ("clean", "make clean"),
219 "git-clone-required": True,
221 ("configure", "./configure.developer" + samba_configure_params),
223 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
224 ("chmod-R-a-w", "chmod -R a-w ."),
229 "git-clone-required": True,
231 ("configure", "./configure.developer --with-system-mitkrb5 --with-experimental-mit-ad-dc" + samba_configure_params),
233 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
234 ("chmod-R-a-w", "chmod -R a-w ."),
239 "git-clone-required": True,
241 ("configure", "./configure.developer --without-ad-dc --without-ldap --without-ads --without-json" + samba_configure_params),
243 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
244 ("chmod-R-a-w", "chmod -R a-w ."),
249 "git-clone-required": True,
251 ("configure", "./configure.developer --without-ad-dc --with-system-heimdalkrb5" + samba_configure_params),
253 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
254 ("chmod-R-a-w", "chmod -R a-w ."),
258 "samba-no-opath-build": {
259 "git-clone-required": True,
261 ("configure", "ADDITIONAL_CFLAGS='-DDISABLE_OPATH=1' ./configure.developer --without-ad-dc " + samba_configure_params),
263 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
264 ("chmod-R-a-w", "chmod -R a-w ."),
268 # We have 'test' before 'install' because, 'test' should work without 'install (runs all the other envs)'
271 ("random-sleep", random_sleep(300, 900)),
272 ("configure", "./configure.developer" + samba_configure_params),
274 ("test", make_test(exclude_envs=[
287 "ad_dc_default_smb1",
295 "ad_member_idmap_rid",
296 "ad_member_idmap_ad",
303 "fileserver_smb1_done",
317 "ad_dc_default_smb1",
318 "ad_dc_default_smb1_done",
324 ("test-slow-none", make_test(cmd='make test', TESTS="--include=selftest/slow-none", include_envs=["none"])),
326 ("install", "make install"),
327 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
328 ("clean", "make clean"),
332 # We have 'test' before 'install' because, 'test' should work without 'install (runs all the other envs)'
335 ("random-sleep", random_sleep(300, 900)),
336 ("configure", "./configure.developer --with-system-mitkrb5 --with-experimental-mit-ad-dc" + samba_configure_params),
338 ("test", make_test(exclude_envs=[
351 "ad_dc_default_smb1",
352 "ad_dc_default_smb1_done",
360 "ad_member_idmap_rid",
361 "ad_member_idmap_ad",
368 "fileserver_smb1_done",
382 "ad_dc_default_smb1",
383 "ad_dc_default_smb1_done",
390 ("install", "make install"),
391 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
392 ("clean", "make clean"),
397 "dependency": "samba-nt4-build",
399 ("random-sleep", random_sleep(300, 900)),
400 ("test", make_test(include_envs=[
409 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
413 "samba-fileserver": {
414 "dependency": "samba-h5l-build",
416 ("random-sleep", random_sleep(300, 900)),
417 ("test", make_test(include_envs=[
420 "fileserver_smb1_done",
422 "ktest", # ktest is also tested in samba and samba-mitkrb5
423 # but is tested here against a system Heimdal
426 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
431 "dependency": "samba-def-build",
433 ("random-sleep", random_sleep(300, 900)),
434 ("test", make_test(include_envs=[
436 "ad_member_idmap_rid",
437 "ad_member_idmap_ad",
441 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
446 "dependency": "samba-no-opath-build",
448 ("random-sleep", random_sleep(300, 900)),
450 cmd="make testonly DISABLE_OPATH=1",
460 ("check-clean-tree", "script/clean-source-tree.sh"),
465 "dependency": "samba-no-opath-build",
467 ("random-sleep", random_sleep(300, 900)),
469 cmd="make testonly DISABLE_OPATH=1",
473 "fileserver_smb1_done",
476 ("check-clean-tree", "script/clean-source-tree.sh"),
481 "dependency": "samba-def-build",
483 ("random-sleep", random_sleep(1, 1)),
484 ("test", make_test(include_envs=[
492 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
497 "dependency": "samba-def-build",
499 ("random-sleep", random_sleep(1, 1)),
500 ("test", make_test(include_envs=[
506 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
511 "dependency": "samba-def-build",
513 ("random-sleep", random_sleep(1, 1)),
514 ("test", make_test(include_envs=[
521 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
526 "dependency": "samba-def-build",
528 ("random-sleep", random_sleep(1, 1)),
529 ("test", make_test(include_envs=[
536 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
541 "dependency": "samba-def-build",
543 ("random-sleep", random_sleep(1, 1)),
544 ("test", make_test(include_envs=[
545 "ad_dc_default", "ad_dc_default_smb1", "ad_dc_default_smb1_done"])),
547 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
552 "dependency": "samba-def-build",
554 ("random-sleep", random_sleep(1, 1)),
555 ("test", make_test(include_envs=["ad_dc_slowtests", "ad_dc_backup"])),
557 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
561 "samba-schemaupgrade": {
562 "dependency": "samba-def-build",
564 ("random-sleep", random_sleep(1, 1)),
565 ("test", make_test(include_envs=["schema_dc", "schema_pair_dc"])),
567 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
571 # We split out the ad_dc_ntvfs tests (which are long) so other test do not wait
572 # This is currently the longest task, so we don't randomly delay it.
573 "samba-ad-dc-ntvfs": {
574 "dependency": "samba-def-build",
576 ("random-sleep", random_sleep(1, 1)),
577 ("test", make_test(include_envs=["ad_dc_ntvfs"])),
579 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
583 # Test fips compliance
585 "dependency": "samba-mit-build",
587 ("random-sleep", random_sleep(1, 1)),
588 ("test", make_test(include_envs=["ad_dc_fips", "ad_member_fips"])),
589 # TODO: This seems to generate only an empty samba-fips.info ("lcov", LCOV_CMD),
590 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
594 # run the backup/restore testenvs separately as they're fairly standalone
595 # (and CI seems to max out at ~3 different DCs running at once)
597 "dependency": "samba-def-build",
599 ("random-sleep", random_sleep(300, 900)),
600 ("test", make_test(include_envs=[
606 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
610 "dependency": "samba-def-build",
612 ("random-sleep", random_sleep(300, 900)),
613 ("test", make_test(include_envs=[
619 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
624 "dependency": "samba-mit-build",
626 ("random-sleep", random_sleep(1, 1)),
627 ("test", make_test(include_envs=[
629 "ad_member_idmap_rid",
630 "ad_member_idmap_ad",
634 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
638 "samba-ad-dc-1-mitkrb5": {
639 "dependency": "samba-mit-build",
641 ("random-sleep", random_sleep(1, 1)),
642 ("test", make_test(include_envs=[
650 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
654 "samba-ad-dc-4-mitkrb5": {
655 "dependency": "samba-mit-build",
657 ("random-sleep", random_sleep(1, 1)),
658 ("test", make_test(include_envs=[
665 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
671 ("configure", "./configure.developer --abi-check-disable" + samba_configure_params),
673 ("test", make_test(TESTS="${TESTS}")),
678 # Test cross-compile infrastructure
681 ("random-sleep", random_sleep(900, 1500)),
682 ("configure-native", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
683 ("configure-cross-execute", "./configure.developer --out ./bin-xe --cross-compile --cross-execute=script/identity_cc.sh" \
684 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xe/ab" + samba_configure_params),
685 ("verify-cross-execute-output", "grep '^Checking value of NSIG' ./bin-xe/cross-answers.txt"),
686 ("configure-cross-answers", "./configure.developer --out ./bin-xa --cross-compile" \
687 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xa/ab" + samba_configure_params),
688 ("compare-results", "script/compare_cc_results.py "
689 "./bin/c4che/default{} "
690 "./bin-xe/c4che/default{} "
691 "./bin-xa/c4che/default{}".format(*([CACHE_SUFFIX]*3))),
692 ("modify-cross-answers", "sed -i.bak -e 's/^\\(Checking value of NSIG:\\) .*/\\1 \"1234\"/' ./bin-xe/cross-answers.txt"),
693 ("configure-cross-answers-modified", "./configure.developer --out ./bin-xa2 --cross-compile" \
694 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xa2/ab" + samba_configure_params),
695 ("verify-cross-answers", "test $(sed -n -e 's/VALUEOF_NSIG = \\(.*\\)/\\1/p' ./bin-xa2/c4che/default{})" \
696 " = \"'1234'\"".format(CACHE_SUFFIX)),
697 ("invalidate-cross-answers", "sed -i.bak -e '/^Checking value of NSIG/d' ./bin-xe/cross-answers.txt"),
698 ("configure-cross-answers-fail", "./configure.developer --out ./bin-xa3 --cross-compile" \
699 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xa3/ab" + samba_configure_params + \
704 # test build with -O3 -- catches extra warnings and bugs, tests the ad_dc environments
707 ("random-sleep", random_sleep(300, 900)),
708 ("configure", "ADDITIONAL_CFLAGS='-O3 -Wp,-D_FORTIFY_SOURCE=2' ./configure.developer --abi-check-disable" + samba_configure_params),
710 ("test", make_test(cmd='make test', TESTS="--exclude=selftest/slow-none", include_envs=["none"])),
711 ("quicktest", make_test(cmd='make quicktest', include_envs=["ad_dc", "ad_dc_smb1", "ad_dc_smb1_done"])),
713 ("install", "make install"),
714 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
715 ("clean", "make clean"),
721 ("random-sleep", random_sleep(900, 1500)),
723 # make sure we have tdb around:
724 ("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}"),
725 ("tdb-make", "cd lib/tdb && make"),
726 ("tdb-install", "cd lib/tdb && make install"),
728 # build samba with cluster support (also building ctdb):
730 "PYTHONPATH=${PYTHON_PREFIX}:$PYTHONPATH "
731 "PKG_CONFIG_PATH=${PREFIX_DIR}/lib/pkgconfig:${PKG_CONFIG_PATH} "
732 "./configure.developer ${PREFIX} "
733 "--with-selftest-prefix=./bin/ab "
735 "--with-cluster-support "
737 "--bundled-libraries=!tdb"),
738 ("samba-make", "make"),
739 ("samba-check", "./bin/smbd -b | grep CLUSTER_SUPPORT"),
740 ("samba-install", "make install"),
741 ("ctdb-check", "test -e ${PREFIX_DIR}/sbin/ctdbd"),
745 INJECT_SELFTEST_PREFIX=0,
746 include_envs=["clusteredmember"])
750 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
751 ("clean", "make clean"),
752 ("ctdb-clean", "cd ./ctdb && make clean"),
758 ("random-sleep", random_sleep(300, 900)),
759 ("talloc-configure", "cd lib/talloc && " + samba_libs_configure_libs),
760 ("talloc-make", "cd lib/talloc && make"),
761 ("talloc-install", "cd lib/talloc && make install"),
763 ("tdb-configure", "cd lib/tdb && " + samba_libs_configure_libs),
764 ("tdb-make", "cd lib/tdb && make"),
765 ("tdb-install", "cd lib/tdb && make install"),
767 ("tevent-configure", "cd lib/tevent && " + samba_libs_configure_libs),
768 ("tevent-make", "cd lib/tevent && make"),
769 ("tevent-install", "cd lib/tevent && make install"),
771 ("ldb-configure", "cd lib/ldb && " + samba_libs_configure_libs),
772 ("ldb-make", "cd lib/ldb && make"),
773 ("ldb-install", "cd lib/ldb && make install"),
775 ("nondevel-configure", "./configure ${PREFIX}"),
776 ("nondevel-make", "make -j"),
777 ("nondevel-check", "./bin/smbd -b | grep WITH_NTVFS_FILESERVER && exit 1; exit 0"),
778 ("nondevel-install", "make install"),
779 ("nondevel-dist", "make dist"),
781 # retry with all modules shared
782 ("allshared-distclean", "make distclean"),
783 ("allshared-configure", samba_libs_configure_samba + " --with-shared-modules=ALL"),
784 ("allshared-make", "make -j"),
790 # build the fuzzers (static) via the oss-fuzz script
791 ("fuzzers-mkdir-prefix", "mkdir -p ${PREFIX_DIR}"),
792 ("fuzzers-build", "OUT=${PREFIX_DIR} LIB_FUZZING_ENGINE= SANITIZER=address CXX= CFLAGS= ADDITIONAL_LDFLAGS='-fuse-ld=bfd' ./lib/fuzzing/oss-fuzz/build_samba.sh --enable-afl-fuzzer"),
796 # * Test smbd and smbtorture can build semi-static
798 # * Test Samba without python still builds.
800 # When this test fails due to more use of Python, the expectations
801 # is that the newly failing part of the code should be disabled
802 # when --disable-python is set (rather than major work being done
803 # to support this environment).
805 # The target here is for vendors shipping a minimal smbd.
806 "samba-minimal-smbd": {
808 ("random-sleep", random_sleep(300, 900)),
810 # build with all modules static
811 ("allstatic-configure", "./configure.developer " + samba_configure_params + " --with-static-modules=ALL"),
812 ("allstatic-make", "make -j"),
813 ("allstatic-test", make_test(TESTS="samba3.smb2.create.*nt4_dc")),
816 # retry with nonshared smbd and smbtorture
817 ("nonshared-distclean", "make distclean"),
818 ("nonshared-configure", "./configure.developer " + samba_configure_params + " --bundled-libraries=ALL --with-static-modules=ALL --nonshared-binary=smbtorture,smbd/smbd"),
819 ("nonshared-make", "make -j"),
821 ("configure", "./configure.developer ${ENABLE_COVERAGE} ${PREFIX} --with-profiling-data --disable-python --without-ad-dc"),
823 ("find-python", "script/find_python.sh ${PREFIX}"),
824 ("test", "make test-nopython"),
826 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
827 ("clean", "make clean"),
829 ("talloc-configure", "cd lib/talloc && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
830 ("talloc-make", "cd lib/talloc && make"),
831 ("talloc-install", "cd lib/talloc && make install"),
833 ("tdb-configure", "cd lib/tdb && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
834 ("tdb-make", "cd lib/tdb && make"),
835 ("tdb-install", "cd lib/tdb && make install"),
837 ("tevent-configure", "cd lib/tevent && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
838 ("tevent-make", "cd lib/tevent && make"),
839 ("tevent-install", "cd lib/tevent && make install"),
841 ("ldb-configure", "cd lib/ldb && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
842 ("ldb-make", "cd lib/ldb && make"),
843 ("ldb-install", "cd lib/ldb && make install"),
845 # retry against installed library packages, but no required modules
846 ("libs-configure", samba_libs_configure_base + samba_libs_configure_bundled_libs + " --disable-python --without-ad-dc --with-static-modules=!FORCED,!DEFAULT --with-shared-modules=!FORCED,!DEFAULT"),
847 ("libs-make", "make -j"),
848 ("libs-install", "make install"),
849 ("libs-check-clean-tree", CLEAN_SOURCE_TREE_CMD),
850 ("libs-clean", "make clean"),
857 ("random-sleep", random_sleep(60, 600)),
858 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
860 ("install", "make install"),
861 ("test", "make test"),
863 ("clean", "make clean"),
864 ("configure-no-lmdb", "./configure ${ENABLE_COVERAGE} --enable-developer --without-ldb-lmdb -C ${PREFIX}"),
865 ("make-no-lmdb", "make"),
866 ("test-no-lmdb", "make test"),
867 ("lcov-no-lmdb", LCOV_CMD),
868 ("install-no-lmdb", "make install"),
869 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
870 ("distcheck", "make distcheck"),
871 ("clean", "make clean"),
877 ("random-sleep", random_sleep(60, 600)),
878 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
880 ("install", "make install"),
881 ("test", "make test"),
883 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
884 ("distcheck", "make distcheck"),
885 ("clean", "make clean"),
891 ("random-sleep", random_sleep(60, 600)),
892 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
894 ("install", "make install"),
895 ("test", "make test"),
897 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
898 ("distcheck", "make distcheck"),
899 ("clean", "make clean"),
905 ("random-sleep", random_sleep(60, 600)),
906 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
908 ("install", "make install"),
909 ("test", "make test"),
911 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
912 ("distcheck", "make distcheck"),
913 ("clean", "make clean"),
919 ("random-sleep", random_sleep(60, 600)),
920 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
922 ("install", "make install"),
923 ("test", "make test"),
925 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
926 ("distcheck", "make distcheck"),
927 ("clean", "make clean"),
932 "git-clone-required": True,
934 ("random-sleep", random_sleep(60, 600)),
935 ("configure", "perl Makefile.PL PREFIX=${PREFIX_DIR}"),
936 ("touch", "touch *.yp"),
938 ("test", "make test"),
939 ("install", "make install"),
940 ("checkout-yapp-generated", "git checkout lib/Parse/Pidl/IDL.pm lib/Parse/Pidl/Expr.pm"),
941 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
942 ("clean", "make clean"),
946 # these are useful for debugging autobuild
949 ("pass", 'echo passing && /bin/true'),
954 ("fail", 'echo failing && /bin/false'),
959 defaulttasks = list(tasks.keys())
961 defaulttasks.remove("pass")
962 defaulttasks.remove("fail")
963 defaulttasks.remove("samba-def-build")
964 defaulttasks.remove("samba-nt4-build")
965 defaulttasks.remove("samba-mit-build")
966 defaulttasks.remove("samba-h5l-build")
967 defaulttasks.remove("samba-no-opath-build")
968 defaulttasks.remove("samba-test-only")
969 defaulttasks.remove("samba-fuzz")
970 defaulttasks.remove("samba-fips")
971 if os.environ.get("AUTOBUILD_SKIP_SAMBA_O3", "0") == "1":
972 defaulttasks.remove("samba-o3")
981 def run_cmd(cmd, dir=".", show=None, output=False, checkfail=True):
983 show = options.verbose
985 do_print("Running: '%s' in '%s'" % (cmd, dir))
987 out = check_output([cmd], shell=True, cwd=dir)
988 return out.decode(encoding='utf-8', errors='backslashreplace')
990 return check_call(cmd, shell=True, cwd=dir)
992 return call(cmd, shell=True, cwd=dir)
994 def rmdir_force(dirname, re_raise=True):
996 run_cmd("test -d %s && chmod -R +w %s; rm -rf %s" % (
997 dirname, dirname, dirname), output=True, show=True)
998 except CalledProcessError as e:
999 do_print("Failed: '%s'" % (str(e)))
1000 run_cmd("tree %s" % dirname, output=True, show=True)
1006 class builder(object):
1007 '''handle build of one directory'''
1009 def __init__(self, name, definition):
1011 self.dir = builddirs.get(name, '.')
1012 self.tag = self.name.replace('/', '_')
1013 self.definition = definition
1014 self.sequence = definition["sequence"]
1015 self.git_clone_required = False
1016 if "git-clone-required" in definition:
1017 self.git_clone_required = bool(definition["git-clone-required"])
1021 self.stdout_path = "%s/%s.stdout" % (gitroot, self.tag)
1022 self.stderr_path = "%s/%s.stderr" % (gitroot, self.tag)
1024 do_print("stdout for %s in %s" % (self.name, self.stdout_path))
1025 do_print("stderr for %s in %s" % (self.name, self.stderr_path))
1026 run_cmd("rm -f %s %s" % (self.stdout_path, self.stderr_path))
1027 self.stdout = open(self.stdout_path, 'w')
1028 self.stderr = open(self.stderr_path, 'w')
1029 self.stdin = open("/dev/null", 'r')
1030 self.builder_dir = "%s/%s" % (testbase, self.tag)
1031 self.test_source_dir = self.builder_dir
1032 self.cwd = "%s/%s" % (self.builder_dir, self.dir)
1033 self.selftest_prefix = "%s/bin/ab" % (self.cwd)
1034 self.prefix = "%s/%s" % (test_prefix, self.tag)
1036 self.producer = None
1038 if self.git_clone_required:
1039 assert "dependency" not in definition
1041 def mark_existing(self):
1042 do_print('%s: Mark as existing dependency' % self.name)
1043 self.next = len(self.sequence)
1046 def add_consumer(self, consumer):
1047 do_print("%s: add consumer: %s" % (self.name, consumer.name))
1048 consumer.producer = self
1049 consumer.test_source_dir = self.test_source_dir
1050 self.consumers.append(consumer)
1052 def start_next(self):
1053 if self.producer is not None:
1054 if not self.producer.done:
1055 do_print("%s: Waiting for producer: %s" % (self.name, self.producer.name))
1059 rmdir_force(self.builder_dir)
1060 rmdir_force(self.prefix)
1061 if self.producer is not None:
1062 run_cmd("mkdir %s" % (self.builder_dir), dir=test_master, show=True)
1063 elif not self.git_clone_required:
1064 run_cmd("cp -R -a -l %s %s" % (test_master, self.builder_dir), dir=test_master, show=True)
1066 run_cmd("git clone --recursive --shared %s %s" % (test_master, self.builder_dir), dir=test_master, show=True)
1068 if self.next == len(self.sequence):
1070 do_print('%s: Completed OK' % self.name)
1072 if not options.nocleanup and len(self.consumers) == 0:
1073 do_print('%s: Cleaning up' % self.name)
1074 rmdir_force(self.builder_dir)
1075 rmdir_force(self.prefix)
1076 for consumer in self.consumers:
1077 if consumer.next != 0:
1079 do_print('%s: Starting consumer %s' % (self.name, consumer.name))
1080 consumer.start_next()
1081 if self.producer is not None:
1082 self.producer.consumers.remove(self)
1083 assert self.producer.done
1084 self.producer.start_next()
1085 do_print('%s: Remaining consumers %u' % (self.name, len(self.consumers)))
1087 (self.stage, self.cmd) = self.sequence[self.next]
1088 self.cmd = self.cmd.replace("${PYTHON_PREFIX}", get_python_lib(plat_specific=1, standard_lib=0, prefix=self.prefix))
1089 self.cmd = self.cmd.replace("${PREFIX}", "--prefix=%s" % self.prefix)
1090 self.cmd = self.cmd.replace("${PREFIX_DIR}", "%s" % self.prefix)
1091 self.cmd = self.cmd.replace("${TESTS}", options.restrict_tests)
1092 self.cmd = self.cmd.replace("${TEST_SOURCE_DIR}", self.test_source_dir)
1093 self.cmd = self.cmd.replace("${SELFTEST_PREFIX}", self.selftest_prefix)
1094 self.cmd = self.cmd.replace("${LOG_BASE}", options.log_base)
1095 self.cmd = self.cmd.replace("${NAME}", self.name)
1096 self.cmd = self.cmd.replace("${ENABLE_COVERAGE}", options.enable_coverage)
1097 do_print('%s: [%s] Running %s in %r' % (self.name, self.stage, self.cmd, self.cwd))
1098 self.proc = Popen(self.cmd, shell=True,
1099 close_fds=True, cwd=self.cwd,
1100 stdout=self.stdout, stderr=self.stderr, stdin=self.stdin)
1103 def expand_dependencies(n):
1105 if "dependency" in tasks[n]:
1106 depname = tasks[n]["dependency"]
1107 assert depname in tasks
1108 sdeps = expand_dependencies(depname)
1109 assert n not in sdeps
1112 deps.append(depname)
1116 class buildlist(object):
1117 '''handle build of multiple directories'''
1119 def __init__(self, tasknames, rebase_url, rebase_branch="master"):
1120 self.tail_proc = None
1123 if options.restrict_tests:
1124 tasknames = ["samba-test-only"]
1126 tasknames = defaulttasks
1128 given_tasknames = tasknames.copy()
1129 implicit_tasknames = []
1130 for n in given_tasknames:
1131 deps = expand_dependencies(n)
1133 if dep in given_tasknames:
1135 if dep in implicit_tasknames:
1137 implicit_tasknames.append(dep)
1139 tasknames = implicit_tasknames.copy()
1140 tasknames.extend(given_tasknames)
1141 do_print("given_tasknames: %s" % given_tasknames)
1142 do_print("implicit_tasknames: %s" % implicit_tasknames)
1143 do_print("tasknames: %s" % tasknames)
1144 self.tlist = [builder(n, tasks[n]) for n in tasknames]
1147 rebase_remote = "rebaseon"
1149 "git-clone-required": True,
1153 git remote add -t %s %s %s
1157 git describe %s/%s > old_remote_branch.desc
1159 git describe %s/%s > remote_branch.desc
1160 diff old_remote_branch.desc remote_branch.desc
1163 rebase_branch, rebase_remote, rebase_url,
1165 rebase_remote, rebase_branch,
1167 rebase_remote, rebase_branch
1170 self.retry = builder('retry', retry_task)
1171 self.need_retry = False
1173 if options.skip_dependencies:
1174 for b in self.tlist:
1175 if b.name in implicit_tasknames:
1178 for b in self.tlist:
1179 do_print("b.name=%s" % b.name)
1180 if "dependency" not in b.definition:
1182 depname = b.definition["dependency"]
1183 do_print("b.name=%s: dependency:%s" % (b.name, depname))
1184 for p in self.tlist:
1185 if p.name == depname:
1188 def kill_kids(self):
1189 if self.tail_proc is not None:
1190 self.tail_proc.terminate()
1191 self.tail_proc.wait()
1192 self.tail_proc = None
1193 if self.retry is not None:
1194 self.retry.proc.terminate()
1195 self.retry.proc.wait()
1197 for b in self.tlist:
1198 if b.proc is not None:
1199 run_cmd("killbysubdir %s > /dev/null 2>&1" % b.test_source_dir, checkfail=False)
1207 for b in self.tlist:
1210 none_running = False
1211 b.status = b.proc.poll()
1212 if b.status is None:
1217 ret = self.retry.proc.poll()
1219 self.need_retry = True
1227 for b in self.tlist:
1230 self.retry.start_next()
1233 if options.retry and self.need_retry:
1235 do_print("retry needed")
1236 return (0, None, None, None, "retry")
1239 if os.WIFSIGNALED(b.status) or os.WEXITSTATUS(b.status) != 0:
1241 return (b.status, b.name, b.stage, b.tag, "%s: [%s] failed '%s' with status %d" % (b.name, b.stage, b.cmd, b.status))
1244 return (0, None, None, None, "All OK")
1246 def write_system_info(self, filename):
1247 with open(filename, 'w') as f:
1248 for cmd in ['uname -a',
1252 'cat /proc/cpuinfo',
1255 'df -m %s' % testbase]:
1257 out = run_cmd(cmd, output=True, checkfail=False)
1258 except CalledProcessError as e:
1259 out = "<failed: %s>" % str(e)
1260 print('### %s' % cmd, file=f)
1264 def tarlogs(self, fname):
1265 with tarfile.open(fname, "w:gz") as tar:
1266 for b in self.tlist:
1267 tar.add(b.stdout_path, arcname="%s.stdout" % b.tag)
1268 tar.add(b.stderr_path, arcname="%s.stderr" % b.tag)
1269 if os.path.exists("autobuild.log"):
1270 tar.add("autobuild.log")
1271 filename = 'system-info.txt'
1272 self.write_system_info(filename)
1275 def remove_logs(self):
1276 for b in self.tlist:
1277 os.unlink(b.stdout_path)
1278 os.unlink(b.stderr_path)
1280 def start_tail(self):
1281 cmd = ["tail", "-f"]
1282 for b in self.tlist:
1283 cmd.append(b.stdout_path)
1284 cmd.append(b.stderr_path)
1285 self.tail_proc = Popen(cmd, close_fds=True)
1288 def cleanup(do_raise=False):
1289 if options.nocleanup:
1291 run_cmd("stat %s || true" % test_tmpdir, show=True)
1292 run_cmd("stat %s" % testbase, show=True)
1293 do_print("Cleaning up %r" % cleanup_list)
1294 for d in cleanup_list:
1295 ok = rmdir_force(d, re_raise=False)
1298 if os.path.isdir(d):
1299 do_print("Killing, waiting and retry")
1300 run_cmd("killbysubdir %s > /dev/null 2>&1" % d, checkfail=False)
1302 do_print("Waiting and retry")
1304 rmdir_force(d, re_raise=do_raise)
1307 def daemonize(logfile):
1309 if pid == 0: # Parent
1312 if pid != 0: # Actual daemon
1317 import resource # Resource usage information.
1318 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1319 if maxfd == resource.RLIM_INFINITY:
1320 maxfd = 1024 # Rough guess at maximum number of open file descriptors.
1321 for fd in range(0, maxfd):
1326 os.open(logfile, os.O_RDWR | os.O_CREAT)
1331 def write_pidfile(fname):
1332 '''write a pid file, cleanup on exit'''
1333 with open(fname, mode='w') as f:
1334 f.write("%u\n" % os.getpid())
1337 def rebase_tree(rebase_url, rebase_branch="master"):
1338 rebase_remote = "rebaseon"
1339 do_print("Rebasing on %s" % rebase_url)
1340 run_cmd("git describe HEAD", show=True, dir=test_master)
1341 run_cmd("git remote add -t %s %s %s" %
1342 (rebase_branch, rebase_remote, rebase_url),
1343 show=True, dir=test_master)
1344 run_cmd("git fetch %s" % rebase_remote, show=True, dir=test_master)
1345 if options.fix_whitespace:
1346 run_cmd("git rebase --force-rebase --whitespace=fix %s/%s" %
1347 (rebase_remote, rebase_branch),
1348 show=True, dir=test_master)
1350 run_cmd("git rebase --force-rebase %s/%s" %
1351 (rebase_remote, rebase_branch),
1352 show=True, dir=test_master)
1353 diff = run_cmd("git --no-pager diff HEAD %s/%s" %
1354 (rebase_remote, rebase_branch),
1355 dir=test_master, output=True)
1357 do_print("No differences between HEAD and %s/%s - exiting" %
1358 (rebase_remote, rebase_branch))
1360 run_cmd("git describe %s/%s" %
1361 (rebase_remote, rebase_branch),
1362 show=True, dir=test_master)
1363 run_cmd("git describe HEAD", show=True, dir=test_master)
1364 run_cmd("git --no-pager diff --stat HEAD %s/%s" %
1365 (rebase_remote, rebase_branch),
1366 show=True, dir=test_master)
1369 def push_to(push_url, push_branch="master"):
1370 push_remote = "pushto"
1371 do_print("Pushing to %s" % push_url)
1373 run_cmd("git config --replace-all core.editor script/commit_mark.sh", dir=test_master)
1374 run_cmd("git commit --amend -c HEAD", dir=test_master)
1375 # the notes method doesn't work yet, as metze hasn't allowed refs/notes/* in master
1376 # run_cmd("EDITOR=script/commit_mark.sh git notes edit HEAD", dir=test_master)
1377 run_cmd("git remote add -t %s %s %s" %
1378 (push_branch, push_remote, push_url),
1379 show=True, dir=test_master)
1380 run_cmd("git push %s +HEAD:%s" %
1381 (push_remote, push_branch),
1382 show=True, dir=test_master)
1385 def send_email(subject, text, log_tar):
1386 if options.email is None:
1387 do_print("not sending email because the recipient is not set")
1388 do_print("the text content would have been:\n\nSubject: %s\n\n%s" %
1391 outer = MIMEMultipart()
1392 outer['Subject'] = subject
1393 outer['To'] = options.email
1394 outer['From'] = options.email_from
1395 outer['Date'] = email.utils.formatdate(localtime=True)
1396 outer.preamble = 'Autobuild mails are now in MIME because we optionally attach the logs.\n'
1397 outer.attach(MIMEText(text, 'plain', 'utf-8'))
1398 if options.attach_logs:
1399 with open(log_tar, 'rb') as fp:
1400 msg = MIMEApplication(fp.read(), 'gzip', email.encoders.encode_base64)
1401 # Set the filename parameter
1402 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(log_tar))
1404 content = outer.as_string()
1405 s = smtplib.SMTP(options.email_server)
1406 email_user = os.getenv('SMTP_USERNAME')
1407 email_password = os.getenv('SMTP_PASSWORD')
1408 if email_user is not None:
1410 s.login(email_user, email_password)
1412 s.sendmail(options.email_from, [options.email], content)
1417 def email_failure(status, failed_task, failed_stage, failed_tag, errstr,
1418 elapsed_time, log_base=None, add_log_tail=True):
1419 '''send an email to options.email about the failure'''
1420 elapsed_minutes = elapsed_time / 60.0
1421 if log_base is None:
1426 Your autobuild on %s failed after %.1f minutes
1427 when trying to test %s with the following error:
1431 the autobuild has been abandoned. Please fix the error and resubmit.
1433 A summary of the autobuild process is here:
1436 ''' % (platform.node(), elapsed_minutes, failed_task, errstr, log_base)
1438 if options.restrict_tests:
1440 The build was restricted to tests matching %s\n""" % options.restrict_tests
1442 if failed_task != 'rebase':
1444 You can see logs of the failed task here:
1449 or you can get full logs of all tasks in this job here:
1453 The top commit for the tree that was built was:
1457 ''' % (log_base, failed_tag, log_base, failed_tag, log_base, top_commit_msg)
1460 f = open("%s/%s.stdout" % (gitroot, failed_tag), 'r')
1461 lines = f.readlines()
1462 log_tail = "".join(lines[-50:])
1463 num_lines = len(lines)
1465 # Also include stderr (compile failures) if < 50 lines of stdout
1466 f = open("%s/%s.stderr" % (gitroot, failed_tag), 'r')
1467 log_tail += "".join(f.readlines()[-(50 - num_lines):])
1470 The last 50 lines of log messages:
1476 logs = os.path.join(gitroot, 'logs.tar.gz')
1477 send_email('autobuild[%s] failure on %s for task %s during %s'
1478 % (options.branch, platform.node(), failed_task, failed_stage),
1482 def email_success(elapsed_time, log_base=None):
1483 '''send an email to options.email about a successful build'''
1484 if log_base is None:
1489 Your autobuild on %s has succeeded after %.1f minutes.
1491 ''' % (platform.node(), elapsed_time / 60.)
1493 if options.restrict_tests:
1495 The build was restricted to tests matching %s\n""" % options.restrict_tests
1497 if options.keeplogs:
1500 you can get full logs of all tasks in this job here:
1507 The top commit for the tree that was built was:
1510 ''' % top_commit_msg
1512 logs = os.path.join(gitroot, 'logs.tar.gz')
1513 send_email('autobuild[%s] success on %s' % (options.branch, platform.node()),
1517 # get the top commit message, for emails
1518 top_commit_msg = run_cmd("git log -1", dir=gitroot, output=True)
1521 if options.skip_dependencies:
1522 run_cmd("stat %s" % testbase, dir=testbase, output=True)
1524 os.makedirs(testbase)
1525 except Exception as reason:
1526 raise Exception("Unable to create %s : %s" % (testbase, reason))
1527 cleanup_list.append(testbase)
1530 logfile = os.path.join(testbase, "log")
1531 do_print("Forking into the background, writing progress to %s" % logfile)
1534 write_pidfile(gitroot + "/autobuild.pid")
1536 start_time = time.time()
1540 run_cmd("rm -rf %s" % test_tmpdir, show=True)
1541 os.makedirs(test_tmpdir)
1542 # The waf uninstall code removes empty directories all the way
1543 # up the tree. Creating a file in test_tmpdir stops it from
1545 run_cmd("touch %s" % os.path.join(test_tmpdir,
1546 ".directory-is-not-empty"), show=True)
1547 run_cmd("stat %s" % test_tmpdir, show=True)
1548 run_cmd("stat %s" % testbase, show=True)
1549 if options.skip_dependencies:
1550 run_cmd("stat %s" % test_master, dir=testbase, output=True)
1552 run_cmd("git clone --recursive --shared %s %s" % (gitroot, test_master), show=True, dir=gitroot)
1558 if options.rebase is not None:
1559 rebase_tree(options.rebase, rebase_branch=options.branch)
1561 cleanup_list.append(gitroot + "/autobuild.pid")
1563 elapsed_time = time.time() - start_time
1564 email_failure(-1, 'rebase', 'rebase', 'rebase',
1565 'rebase on %s failed' % options.branch,
1566 elapsed_time, log_base=options.log_base)
1570 blist = buildlist(args, options.rebase, rebase_branch=options.branch)
1573 (status, failed_task, failed_stage, failed_tag, errstr) = blist.run()
1574 if status != 0 or errstr != "retry":
1576 cleanup(do_raise=True)
1581 cleanup_list.append(gitroot + "/autobuild.pid")
1587 do_print("waiting for tail to flush")
1590 elapsed_time = time.time() - start_time
1592 if options.passcmd is not None:
1593 do_print("Running passcmd: %s" % options.passcmd)
1594 run_cmd(options.passcmd, dir=test_master)
1595 if options.pushto is not None:
1596 push_to(options.pushto, push_branch=options.branch)
1597 if options.keeplogs or options.attach_logs:
1598 blist.tarlogs("logs.tar.gz")
1599 do_print("Logs in logs.tar.gz")
1600 if options.always_email:
1601 email_success(elapsed_time, log_base=options.log_base)
1607 # something failed, gather a tar of the logs
1608 blist.tarlogs("logs.tar.gz")
1610 if options.email is not None:
1611 email_failure(status, failed_task, failed_stage, failed_tag, errstr,
1612 elapsed_time, log_base=options.log_base)
1614 elapsed_minutes = elapsed_time / 60.0
1617 ####################################################################
1621 Your autobuild[%s] on %s failed after %.1f minutes
1622 when trying to test %s with the following error:
1626 the autobuild has been abandoned. Please fix the error and resubmit.
1628 ####################################################################
1630 ''' % (options.branch, platform.node(), elapsed_minutes, failed_task, errstr))
1634 do_print("Logs in logs.tar.gz")