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=[
534 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
538 "dependency": "samba-def-build",
540 ("random-sleep", random_sleep(1, 1)),
541 ("test", make_test(include_envs=[
546 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
551 "dependency": "samba-def-build",
553 ("random-sleep", random_sleep(1, 1)),
554 ("test", make_test(include_envs=[
555 "ad_dc_default", "ad_dc_default_smb1", "ad_dc_default_smb1_done"])),
557 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
562 "dependency": "samba-def-build",
564 ("random-sleep", random_sleep(1, 1)),
565 ("test", make_test(include_envs=["ad_dc_slowtests", "ad_dc_backup"])),
567 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
571 "samba-schemaupgrade": {
572 "dependency": "samba-def-build",
574 ("random-sleep", random_sleep(1, 1)),
575 ("test", make_test(include_envs=["schema_dc", "schema_pair_dc"])),
577 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
581 # We split out the ad_dc_ntvfs tests (which are long) so other test do not wait
582 # This is currently the longest task, so we don't randomly delay it.
583 "samba-ad-dc-ntvfs": {
584 "dependency": "samba-def-build",
586 ("random-sleep", random_sleep(1, 1)),
587 ("test", make_test(include_envs=["ad_dc_ntvfs"])),
589 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
593 # Test fips compliance
595 "dependency": "samba-mit-build",
597 ("random-sleep", random_sleep(1, 1)),
598 ("test", make_test(include_envs=["ad_dc_fips", "ad_member_fips"])),
599 # TODO: This seems to generate only an empty samba-fips.info ("lcov", LCOV_CMD),
600 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
604 # run the backup/restore testenvs separately as they're fairly standalone
605 # (and CI seems to max out at ~3 different DCs running at once)
607 "dependency": "samba-def-build",
609 ("random-sleep", random_sleep(300, 900)),
610 ("test", make_test(include_envs=[
616 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
620 "dependency": "samba-def-build",
622 ("random-sleep", random_sleep(300, 900)),
623 ("test", make_test(include_envs=[
629 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
634 "dependency": "samba-mit-build",
636 ("random-sleep", random_sleep(1, 1)),
637 ("test", make_test(include_envs=[
639 "ad_member_idmap_rid",
640 "ad_member_idmap_ad",
644 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
648 "samba-ad-dc-1-mitkrb5": {
649 "dependency": "samba-mit-build",
651 ("random-sleep", random_sleep(1, 1)),
652 ("test", make_test(include_envs=[
660 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
664 "samba-ad-dc-4a-mitkrb5": {
665 "dependency": "samba-mit-build",
667 ("random-sleep", random_sleep(1, 1)),
668 ("test", make_test(include_envs=[
673 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
676 "samba-ad-dc-4b-mitkrb5": {
677 "dependency": "samba-mit-build",
679 ("random-sleep", random_sleep(1, 1)),
680 ("test", make_test(include_envs=[
685 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
691 ("configure", "./configure.developer --abi-check-disable" + samba_configure_params),
693 ("test", make_test(TESTS="${TESTS}")),
698 # Test cross-compile infrastructure
701 ("random-sleep", random_sleep(900, 1500)),
702 ("configure-native", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params),
703 ("configure-cross-execute", "./configure.developer --out ./bin-xe --cross-compile --cross-execute=script/identity_cc.sh" \
704 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xe/ab" + samba_configure_params),
705 ("verify-cross-execute-output", "grep '^Checking value of NSIG' ./bin-xe/cross-answers.txt"),
706 ("configure-cross-answers", "./configure.developer --out ./bin-xa --cross-compile" \
707 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xa/ab" + samba_configure_params),
708 ("compare-results", "script/compare_cc_results.py "
709 "./bin/c4che/default{} "
710 "./bin-xe/c4che/default{} "
711 "./bin-xa/c4che/default{}".format(*([CACHE_SUFFIX]*3))),
712 ("modify-cross-answers", "sed -i.bak -e 's/^\\(Checking value of NSIG:\\) .*/\\1 \"1234\"/' ./bin-xe/cross-answers.txt"),
713 ("configure-cross-answers-modified", "./configure.developer --out ./bin-xa2 --cross-compile" \
714 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xa2/ab" + samba_configure_params),
715 ("verify-cross-answers", "test $(sed -n -e 's/VALUEOF_NSIG = \\(.*\\)/\\1/p' ./bin-xa2/c4che/default{})" \
716 " = \"'1234'\"".format(CACHE_SUFFIX)),
717 ("invalidate-cross-answers", "sed -i.bak -e '/^Checking value of NSIG/d' ./bin-xe/cross-answers.txt"),
718 ("configure-cross-answers-fail", "./configure.developer --out ./bin-xa3 --cross-compile" \
719 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xa3/ab" + samba_configure_params + \
724 # test build with -O3 -- catches extra warnings and bugs, tests the ad_dc environments
727 ("random-sleep", random_sleep(300, 900)),
728 ("configure", "ADDITIONAL_CFLAGS='-O3 -Wp,-D_FORTIFY_SOURCE=2' ./configure.developer --abi-check-disable" + samba_configure_params),
730 ("test", make_test(cmd='make test', TESTS="--exclude=selftest/slow-none", include_envs=["none"])),
731 ("quicktest", make_test(cmd='make quicktest', include_envs=["ad_dc", "ad_dc_smb1", "ad_dc_smb1_done"])),
733 ("install", "make install"),
734 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
735 ("clean", "make clean"),
741 ("random-sleep", random_sleep(900, 1500)),
743 # make sure we have tdb around:
744 ("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}"),
745 ("tdb-make", "cd lib/tdb && make"),
746 ("tdb-install", "cd lib/tdb && make install"),
748 # build samba with cluster support (also building ctdb):
750 "PYTHONPATH=${PYTHON_PREFIX}:$PYTHONPATH "
751 "PKG_CONFIG_PATH=${PREFIX_DIR}/lib/pkgconfig:${PKG_CONFIG_PATH} "
752 "./configure.developer ${PREFIX} "
753 "--with-selftest-prefix=./bin/ab "
755 "--with-cluster-support "
757 "--bundled-libraries=!tdb"),
758 ("samba-make", "make"),
759 ("samba-check", "./bin/smbd -b | grep CLUSTER_SUPPORT"),
760 ("samba-install", "make install"),
761 ("ctdb-check", "test -e ${PREFIX_DIR}/sbin/ctdbd"),
765 INJECT_SELFTEST_PREFIX=0,
766 include_envs=["clusteredmember"])
770 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
771 ("clean", "make clean"),
772 ("ctdb-clean", "cd ./ctdb && make clean"),
778 ("random-sleep", random_sleep(300, 900)),
779 ("talloc-configure", "cd lib/talloc && " + samba_libs_configure_libs),
780 ("talloc-make", "cd lib/talloc && make"),
781 ("talloc-install", "cd lib/talloc && make install"),
783 ("tdb-configure", "cd lib/tdb && " + samba_libs_configure_libs),
784 ("tdb-make", "cd lib/tdb && make"),
785 ("tdb-install", "cd lib/tdb && make install"),
787 ("tevent-configure", "cd lib/tevent && " + samba_libs_configure_libs),
788 ("tevent-make", "cd lib/tevent && make"),
789 ("tevent-install", "cd lib/tevent && make install"),
791 ("ldb-configure", "cd lib/ldb && " + samba_libs_configure_libs),
792 ("ldb-make", "cd lib/ldb && make"),
793 ("ldb-install", "cd lib/ldb && make install"),
795 ("nondevel-configure", "./configure ${PREFIX}"),
796 ("nondevel-make", "make -j"),
797 ("nondevel-check", "./bin/smbd -b | grep WITH_NTVFS_FILESERVER && exit 1; exit 0"),
798 ("nondevel-install", "make install"),
799 ("nondevel-dist", "make dist"),
801 # retry with all modules shared
802 ("allshared-distclean", "make distclean"),
803 ("allshared-configure", samba_libs_configure_samba + " --with-shared-modules=ALL"),
804 ("allshared-make", "make -j"),
810 # build the fuzzers (static) via the oss-fuzz script
811 ("fuzzers-mkdir-prefix", "mkdir -p ${PREFIX_DIR}"),
812 ("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"),
816 # * Test smbd and smbtorture can build semi-static
818 # * Test Samba without python still builds.
820 # When this test fails due to more use of Python, the expectations
821 # is that the newly failing part of the code should be disabled
822 # when --disable-python is set (rather than major work being done
823 # to support this environment).
825 # The target here is for vendors shipping a minimal smbd.
826 "samba-minimal-smbd": {
828 ("random-sleep", random_sleep(300, 900)),
830 # build with all modules static
831 ("allstatic-configure", "./configure.developer " + samba_configure_params + " --with-static-modules=ALL"),
832 ("allstatic-make", "make -j"),
833 ("allstatic-test", make_test(TESTS="samba3.smb2.create.*nt4_dc")),
834 ("allstatic-lcov", LCOV_CMD),
836 # retry with nonshared smbd and smbtorture
837 ("nonshared-distclean", "make distclean"),
838 ("nonshared-configure", "./configure.developer " + samba_configure_params + " --bundled-libraries=ALL --with-static-modules=ALL --nonshared-binary=smbtorture,smbd/smbd"),
839 ("nonshared-make", "make -j"),
840 # TODO ("nonshared-test", make_test(TESTS="samba3.smb2.create.*nt4_dc")),
841 # TODO ("nonshared-lcov", LCOV_CMD),
843 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
844 ("clean", "make clean"),
850 ("random-sleep", random_sleep(300, 900)),
852 ("configure", "./configure.developer ${ENABLE_COVERAGE} ${PREFIX} --with-profiling-data --disable-python --without-ad-dc"),
854 ("find-python", "script/find_python.sh ${PREFIX}"),
855 ("test", "make test-nopython"),
857 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
858 ("clean", "make clean"),
860 ("talloc-configure", "cd lib/talloc && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
861 ("talloc-make", "cd lib/talloc && make"),
862 ("talloc-install", "cd lib/talloc && make install"),
864 ("tdb-configure", "cd lib/tdb && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
865 ("tdb-make", "cd lib/tdb && make"),
866 ("tdb-install", "cd lib/tdb && make install"),
868 ("tevent-configure", "cd lib/tevent && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
869 ("tevent-make", "cd lib/tevent && make"),
870 ("tevent-install", "cd lib/tevent && make install"),
872 ("ldb-configure", "cd lib/ldb && " + samba_libs_configure_base + " --bundled-libraries=cmocka,NONE --disable-python"),
873 ("ldb-make", "cd lib/ldb && make"),
874 ("ldb-install", "cd lib/ldb && make install"),
876 # retry against installed library packages, but no required modules
877 ("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"),
878 ("libs-make", "make -j"),
879 ("libs-install", "make install"),
880 ("libs-check-clean-tree", CLEAN_SOURCE_TREE_CMD),
881 ("libs-clean", "make clean"),
888 ("random-sleep", random_sleep(60, 600)),
889 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
891 ("install", "make install"),
892 ("test", "make test"),
894 ("clean", "make clean"),
895 ("configure-no-lmdb", "./configure ${ENABLE_COVERAGE} --enable-developer --without-ldb-lmdb -C ${PREFIX}"),
896 ("make-no-lmdb", "make"),
897 ("test-no-lmdb", "make test"),
898 ("lcov-no-lmdb", LCOV_CMD),
899 ("install-no-lmdb", "make install"),
900 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
901 ("distcheck", "make distcheck"),
902 ("clean", "make clean"),
908 ("random-sleep", random_sleep(60, 600)),
909 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
911 ("install", "make install"),
912 ("test", "make test"),
914 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
915 ("distcheck", "make distcheck"),
916 ("clean", "make clean"),
922 ("random-sleep", random_sleep(60, 600)),
923 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
925 ("install", "make install"),
926 ("test", "make test"),
928 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
929 ("distcheck", "make distcheck"),
930 ("clean", "make clean"),
936 ("random-sleep", random_sleep(60, 600)),
937 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
939 ("install", "make install"),
940 ("test", "make test"),
942 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
943 ("distcheck", "make distcheck"),
944 ("clean", "make clean"),
950 ("random-sleep", random_sleep(60, 600)),
951 ("configure", "./configure ${ENABLE_COVERAGE} --enable-developer -C ${PREFIX}"),
953 ("install", "make install"),
954 ("test", "make test"),
956 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
957 ("distcheck", "make distcheck"),
958 ("clean", "make clean"),
963 "git-clone-required": True,
965 ("random-sleep", random_sleep(60, 600)),
966 ("configure", "perl Makefile.PL PREFIX=${PREFIX_DIR}"),
967 ("touch", "touch *.yp"),
969 ("test", "make test"),
970 ("install", "make install"),
971 ("checkout-yapp-generated", "git checkout lib/Parse/Pidl/IDL.pm lib/Parse/Pidl/Expr.pm"),
972 ("check-clean-tree", CLEAN_SOURCE_TREE_CMD),
973 ("clean", "make clean"),
977 # these are useful for debugging autobuild
980 ("pass", 'echo passing && /bin/true'),
985 ("fail", 'echo failing && /bin/false'),
990 defaulttasks = list(tasks.keys())
992 defaulttasks.remove("pass")
993 defaulttasks.remove("fail")
994 defaulttasks.remove("samba-def-build")
995 defaulttasks.remove("samba-nt4-build")
996 defaulttasks.remove("samba-mit-build")
997 defaulttasks.remove("samba-h5l-build")
998 defaulttasks.remove("samba-no-opath-build")
999 defaulttasks.remove("samba-test-only")
1000 defaulttasks.remove("samba-fuzz")
1001 defaulttasks.remove("samba-fips")
1002 if os.environ.get("AUTOBUILD_SKIP_SAMBA_O3", "0") == "1":
1003 defaulttasks.remove("samba-o3")
1012 def run_cmd(cmd, dir=".", show=None, output=False, checkfail=True):
1014 show = options.verbose
1016 do_print("Running: '%s' in '%s'" % (cmd, dir))
1018 out = check_output([cmd], shell=True, cwd=dir)
1019 return out.decode(encoding='utf-8', errors='backslashreplace')
1021 return check_call(cmd, shell=True, cwd=dir)
1023 return call(cmd, shell=True, cwd=dir)
1025 def rmdir_force(dirname, re_raise=True):
1027 run_cmd("test -d %s && chmod -R +w %s; rm -rf %s" % (
1028 dirname, dirname, dirname), output=True, show=True)
1029 except CalledProcessError as e:
1030 do_print("Failed: '%s'" % (str(e)))
1031 run_cmd("tree %s" % dirname, output=True, show=True)
1037 class builder(object):
1038 '''handle build of one directory'''
1040 def __init__(self, name, definition):
1042 self.dir = builddirs.get(name, '.')
1043 self.tag = self.name.replace('/', '_')
1044 self.definition = definition
1045 self.sequence = definition["sequence"]
1046 self.git_clone_required = False
1047 if "git-clone-required" in definition:
1048 self.git_clone_required = bool(definition["git-clone-required"])
1052 self.stdout_path = "%s/%s.stdout" % (gitroot, self.tag)
1053 self.stderr_path = "%s/%s.stderr" % (gitroot, self.tag)
1055 do_print("stdout for %s in %s" % (self.name, self.stdout_path))
1056 do_print("stderr for %s in %s" % (self.name, self.stderr_path))
1057 run_cmd("rm -f %s %s" % (self.stdout_path, self.stderr_path))
1058 self.stdout = open(self.stdout_path, 'w')
1059 self.stderr = open(self.stderr_path, 'w')
1060 self.stdin = open("/dev/null", 'r')
1061 self.builder_dir = "%s/%s" % (testbase, self.tag)
1062 self.test_source_dir = self.builder_dir
1063 self.cwd = "%s/%s" % (self.builder_dir, self.dir)
1064 self.selftest_prefix = "%s/bin/ab" % (self.cwd)
1065 self.prefix = "%s/%s" % (test_prefix, self.tag)
1067 self.producer = None
1069 if self.git_clone_required:
1070 assert "dependency" not in definition
1072 def mark_existing(self):
1073 do_print('%s: Mark as existing dependency' % self.name)
1074 self.next = len(self.sequence)
1077 def add_consumer(self, consumer):
1078 do_print("%s: add consumer: %s" % (self.name, consumer.name))
1079 consumer.producer = self
1080 consumer.test_source_dir = self.test_source_dir
1081 self.consumers.append(consumer)
1083 def start_next(self):
1084 if self.producer is not None:
1085 if not self.producer.done:
1086 do_print("%s: Waiting for producer: %s" % (self.name, self.producer.name))
1090 rmdir_force(self.builder_dir)
1091 rmdir_force(self.prefix)
1092 if self.producer is not None:
1093 run_cmd("mkdir %s" % (self.builder_dir), dir=test_master, show=True)
1094 elif not self.git_clone_required:
1095 run_cmd("cp -R -a -l %s %s" % (test_master, self.builder_dir), dir=test_master, show=True)
1097 run_cmd("git clone --recursive --shared %s %s" % (test_master, self.builder_dir), dir=test_master, show=True)
1099 if self.next == len(self.sequence):
1101 do_print('%s: Completed OK' % self.name)
1103 if not options.nocleanup and len(self.consumers) == 0:
1104 do_print('%s: Cleaning up' % self.name)
1105 rmdir_force(self.builder_dir)
1106 rmdir_force(self.prefix)
1107 for consumer in self.consumers:
1108 if consumer.next != 0:
1110 do_print('%s: Starting consumer %s' % (self.name, consumer.name))
1111 consumer.start_next()
1112 if self.producer is not None:
1113 self.producer.consumers.remove(self)
1114 assert self.producer.done
1115 self.producer.start_next()
1116 do_print('%s: Remaining consumers %u' % (self.name, len(self.consumers)))
1118 (self.stage, self.cmd) = self.sequence[self.next]
1119 self.cmd = self.cmd.replace("${PYTHON_PREFIX}", get_python_lib(plat_specific=1, standard_lib=0, prefix=self.prefix))
1120 self.cmd = self.cmd.replace("${PREFIX}", "--prefix=%s" % self.prefix)
1121 self.cmd = self.cmd.replace("${PREFIX_DIR}", "%s" % self.prefix)
1122 self.cmd = self.cmd.replace("${TESTS}", options.restrict_tests)
1123 self.cmd = self.cmd.replace("${TEST_SOURCE_DIR}", self.test_source_dir)
1124 self.cmd = self.cmd.replace("${SELFTEST_PREFIX}", self.selftest_prefix)
1125 self.cmd = self.cmd.replace("${LOG_BASE}", options.log_base)
1126 self.cmd = self.cmd.replace("${NAME}", self.name)
1127 self.cmd = self.cmd.replace("${ENABLE_COVERAGE}", options.enable_coverage)
1128 do_print('%s: [%s] Running %s in %r' % (self.name, self.stage, self.cmd, self.cwd))
1129 self.proc = Popen(self.cmd, shell=True,
1130 close_fds=True, cwd=self.cwd,
1131 stdout=self.stdout, stderr=self.stderr, stdin=self.stdin)
1134 def expand_dependencies(n):
1136 if "dependency" in tasks[n]:
1137 depname = tasks[n]["dependency"]
1138 assert depname in tasks
1139 sdeps = expand_dependencies(depname)
1140 assert n not in sdeps
1143 deps.append(depname)
1147 class buildlist(object):
1148 '''handle build of multiple directories'''
1150 def __init__(self, tasknames, rebase_url, rebase_branch="master"):
1151 self.tail_proc = None
1154 if options.restrict_tests:
1155 tasknames = ["samba-test-only"]
1157 tasknames = defaulttasks
1159 given_tasknames = tasknames.copy()
1160 implicit_tasknames = []
1161 for n in given_tasknames:
1162 deps = expand_dependencies(n)
1164 if dep in given_tasknames:
1166 if dep in implicit_tasknames:
1168 implicit_tasknames.append(dep)
1170 tasknames = implicit_tasknames.copy()
1171 tasknames.extend(given_tasknames)
1172 do_print("given_tasknames: %s" % given_tasknames)
1173 do_print("implicit_tasknames: %s" % implicit_tasknames)
1174 do_print("tasknames: %s" % tasknames)
1175 self.tlist = [builder(n, tasks[n]) for n in tasknames]
1178 rebase_remote = "rebaseon"
1180 "git-clone-required": True,
1184 git remote add -t %s %s %s
1188 git describe %s/%s > old_remote_branch.desc
1190 git describe %s/%s > remote_branch.desc
1191 diff old_remote_branch.desc remote_branch.desc
1194 rebase_branch, rebase_remote, rebase_url,
1196 rebase_remote, rebase_branch,
1198 rebase_remote, rebase_branch
1201 self.retry = builder('retry', retry_task)
1202 self.need_retry = False
1204 if options.skip_dependencies:
1205 for b in self.tlist:
1206 if b.name in implicit_tasknames:
1209 for b in self.tlist:
1210 do_print("b.name=%s" % b.name)
1211 if "dependency" not in b.definition:
1213 depname = b.definition["dependency"]
1214 do_print("b.name=%s: dependency:%s" % (b.name, depname))
1215 for p in self.tlist:
1216 if p.name == depname:
1219 def kill_kids(self):
1220 if self.tail_proc is not None:
1221 self.tail_proc.terminate()
1222 self.tail_proc.wait()
1223 self.tail_proc = None
1224 if self.retry is not None:
1225 self.retry.proc.terminate()
1226 self.retry.proc.wait()
1228 for b in self.tlist:
1229 if b.proc is not None:
1230 run_cmd("killbysubdir %s > /dev/null 2>&1" % b.test_source_dir, checkfail=False)
1238 for b in self.tlist:
1241 none_running = False
1242 b.status = b.proc.poll()
1243 if b.status is None:
1248 ret = self.retry.proc.poll()
1250 self.need_retry = True
1258 for b in self.tlist:
1261 self.retry.start_next()
1264 if options.retry and self.need_retry:
1266 do_print("retry needed")
1267 return (0, None, None, None, "retry")
1270 if os.WIFSIGNALED(b.status) or os.WEXITSTATUS(b.status) != 0:
1272 return (b.status, b.name, b.stage, b.tag, "%s: [%s] failed '%s' with status %d" % (b.name, b.stage, b.cmd, b.status))
1275 return (0, None, None, None, "All OK")
1277 def write_system_info(self, filename):
1278 with open(filename, 'w') as f:
1279 for cmd in ['uname -a',
1283 'cat /proc/cpuinfo',
1286 'df -m %s' % testbase]:
1288 out = run_cmd(cmd, output=True, checkfail=False)
1289 except CalledProcessError as e:
1290 out = "<failed: %s>" % str(e)
1291 print('### %s' % cmd, file=f)
1295 def tarlogs(self, fname):
1296 with tarfile.open(fname, "w:gz") as tar:
1297 for b in self.tlist:
1298 tar.add(b.stdout_path, arcname="%s.stdout" % b.tag)
1299 tar.add(b.stderr_path, arcname="%s.stderr" % b.tag)
1300 if os.path.exists("autobuild.log"):
1301 tar.add("autobuild.log")
1302 filename = 'system-info.txt'
1303 self.write_system_info(filename)
1306 def remove_logs(self):
1307 for b in self.tlist:
1308 os.unlink(b.stdout_path)
1309 os.unlink(b.stderr_path)
1311 def start_tail(self):
1312 cmd = ["tail", "-f"]
1313 for b in self.tlist:
1314 cmd.append(b.stdout_path)
1315 cmd.append(b.stderr_path)
1316 self.tail_proc = Popen(cmd, close_fds=True)
1319 def cleanup(do_raise=False):
1320 if options.nocleanup:
1322 run_cmd("stat %s || true" % test_tmpdir, show=True)
1323 run_cmd("stat %s" % testbase, show=True)
1324 do_print("Cleaning up %r" % cleanup_list)
1325 for d in cleanup_list:
1326 ok = rmdir_force(d, re_raise=False)
1329 if os.path.isdir(d):
1330 do_print("Killing, waiting and retry")
1331 run_cmd("killbysubdir %s > /dev/null 2>&1" % d, checkfail=False)
1333 do_print("Waiting and retry")
1335 rmdir_force(d, re_raise=do_raise)
1338 def daemonize(logfile):
1340 if pid == 0: # Parent
1343 if pid != 0: # Actual daemon
1348 import resource # Resource usage information.
1349 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1350 if maxfd == resource.RLIM_INFINITY:
1351 maxfd = 1024 # Rough guess at maximum number of open file descriptors.
1352 for fd in range(0, maxfd):
1357 os.open(logfile, os.O_RDWR | os.O_CREAT)
1362 def write_pidfile(fname):
1363 '''write a pid file, cleanup on exit'''
1364 with open(fname, mode='w') as f:
1365 f.write("%u\n" % os.getpid())
1368 def rebase_tree(rebase_url, rebase_branch="master"):
1369 rebase_remote = "rebaseon"
1370 do_print("Rebasing on %s" % rebase_url)
1371 run_cmd("git describe HEAD", show=True, dir=test_master)
1372 run_cmd("git remote add -t %s %s %s" %
1373 (rebase_branch, rebase_remote, rebase_url),
1374 show=True, dir=test_master)
1375 run_cmd("git fetch %s" % rebase_remote, show=True, dir=test_master)
1376 if options.fix_whitespace:
1377 run_cmd("git rebase --force-rebase --whitespace=fix %s/%s" %
1378 (rebase_remote, rebase_branch),
1379 show=True, dir=test_master)
1381 run_cmd("git rebase --force-rebase %s/%s" %
1382 (rebase_remote, rebase_branch),
1383 show=True, dir=test_master)
1384 diff = run_cmd("git --no-pager diff HEAD %s/%s" %
1385 (rebase_remote, rebase_branch),
1386 dir=test_master, output=True)
1388 do_print("No differences between HEAD and %s/%s - exiting" %
1389 (rebase_remote, rebase_branch))
1391 run_cmd("git describe %s/%s" %
1392 (rebase_remote, rebase_branch),
1393 show=True, dir=test_master)
1394 run_cmd("git describe HEAD", show=True, dir=test_master)
1395 run_cmd("git --no-pager diff --stat HEAD %s/%s" %
1396 (rebase_remote, rebase_branch),
1397 show=True, dir=test_master)
1400 def push_to(push_url, push_branch="master"):
1401 push_remote = "pushto"
1402 do_print("Pushing to %s" % push_url)
1404 run_cmd("git config --replace-all core.editor script/commit_mark.sh", dir=test_master)
1405 run_cmd("git commit --amend -c HEAD", dir=test_master)
1406 # the notes method doesn't work yet, as metze hasn't allowed refs/notes/* in master
1407 # run_cmd("EDITOR=script/commit_mark.sh git notes edit HEAD", dir=test_master)
1408 run_cmd("git remote add -t %s %s %s" %
1409 (push_branch, push_remote, push_url),
1410 show=True, dir=test_master)
1411 run_cmd("git push %s +HEAD:%s" %
1412 (push_remote, push_branch),
1413 show=True, dir=test_master)
1416 def send_email(subject, text, log_tar):
1417 if options.email is None:
1418 do_print("not sending email because the recipient is not set")
1419 do_print("the text content would have been:\n\nSubject: %s\n\n%s" %
1422 outer = MIMEMultipart()
1423 outer['Subject'] = subject
1424 outer['To'] = options.email
1425 outer['From'] = options.email_from
1426 outer['Date'] = email.utils.formatdate(localtime=True)
1427 outer.preamble = 'Autobuild mails are now in MIME because we optionally attach the logs.\n'
1428 outer.attach(MIMEText(text, 'plain', 'utf-8'))
1429 if options.attach_logs:
1430 with open(log_tar, 'rb') as fp:
1431 msg = MIMEApplication(fp.read(), 'gzip', email.encoders.encode_base64)
1432 # Set the filename parameter
1433 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(log_tar))
1435 content = outer.as_string()
1436 s = smtplib.SMTP(options.email_server)
1437 email_user = os.getenv('SMTP_USERNAME')
1438 email_password = os.getenv('SMTP_PASSWORD')
1439 if email_user is not None:
1441 s.login(email_user, email_password)
1443 s.sendmail(options.email_from, [options.email], content)
1448 def email_failure(status, failed_task, failed_stage, failed_tag, errstr,
1449 elapsed_time, log_base=None, add_log_tail=True):
1450 '''send an email to options.email about the failure'''
1451 elapsed_minutes = elapsed_time / 60.0
1452 if log_base is None:
1457 Your autobuild on %s failed after %.1f minutes
1458 when trying to test %s with the following error:
1462 the autobuild has been abandoned. Please fix the error and resubmit.
1464 A summary of the autobuild process is here:
1467 ''' % (platform.node(), elapsed_minutes, failed_task, errstr, log_base)
1469 if options.restrict_tests:
1471 The build was restricted to tests matching %s\n""" % options.restrict_tests
1473 if failed_task != 'rebase':
1475 You can see logs of the failed task here:
1480 or you can get full logs of all tasks in this job here:
1484 The top commit for the tree that was built was:
1488 ''' % (log_base, failed_tag, log_base, failed_tag, log_base, top_commit_msg)
1491 f = open("%s/%s.stdout" % (gitroot, failed_tag), 'r')
1492 lines = f.readlines()
1493 log_tail = "".join(lines[-50:])
1494 num_lines = len(lines)
1496 # Also include stderr (compile failures) if < 50 lines of stdout
1497 f = open("%s/%s.stderr" % (gitroot, failed_tag), 'r')
1498 log_tail += "".join(f.readlines()[-(50 - num_lines):])
1501 The last 50 lines of log messages:
1507 logs = os.path.join(gitroot, 'logs.tar.gz')
1508 send_email('autobuild[%s] failure on %s for task %s during %s'
1509 % (options.branch, platform.node(), failed_task, failed_stage),
1513 def email_success(elapsed_time, log_base=None):
1514 '''send an email to options.email about a successful build'''
1515 if log_base is None:
1520 Your autobuild on %s has succeeded after %.1f minutes.
1522 ''' % (platform.node(), elapsed_time / 60.)
1524 if options.restrict_tests:
1526 The build was restricted to tests matching %s\n""" % options.restrict_tests
1528 if options.keeplogs:
1531 you can get full logs of all tasks in this job here:
1538 The top commit for the tree that was built was:
1541 ''' % top_commit_msg
1543 logs = os.path.join(gitroot, 'logs.tar.gz')
1544 send_email('autobuild[%s] success on %s' % (options.branch, platform.node()),
1548 # get the top commit message, for emails
1549 top_commit_msg = run_cmd("git log -1", dir=gitroot, output=True)
1552 if options.skip_dependencies:
1553 run_cmd("stat %s" % testbase, dir=testbase, output=True)
1555 os.makedirs(testbase)
1556 except Exception as reason:
1557 raise Exception("Unable to create %s : %s" % (testbase, reason))
1558 cleanup_list.append(testbase)
1561 logfile = os.path.join(testbase, "log")
1562 do_print("Forking into the background, writing progress to %s" % logfile)
1565 write_pidfile(gitroot + "/autobuild.pid")
1567 start_time = time.time()
1571 run_cmd("rm -rf %s" % test_tmpdir, show=True)
1572 os.makedirs(test_tmpdir)
1573 # The waf uninstall code removes empty directories all the way
1574 # up the tree. Creating a file in test_tmpdir stops it from
1576 run_cmd("touch %s" % os.path.join(test_tmpdir,
1577 ".directory-is-not-empty"), show=True)
1578 run_cmd("stat %s" % test_tmpdir, show=True)
1579 run_cmd("stat %s" % testbase, show=True)
1580 if options.skip_dependencies:
1581 run_cmd("stat %s" % test_master, dir=testbase, output=True)
1583 run_cmd("git clone --recursive --shared %s %s" % (gitroot, test_master), show=True, dir=gitroot)
1589 if options.rebase is not None:
1590 rebase_tree(options.rebase, rebase_branch=options.branch)
1592 cleanup_list.append(gitroot + "/autobuild.pid")
1594 elapsed_time = time.time() - start_time
1595 email_failure(-1, 'rebase', 'rebase', 'rebase',
1596 'rebase on %s failed' % options.branch,
1597 elapsed_time, log_base=options.log_base)
1601 blist = buildlist(args, options.rebase, rebase_branch=options.branch)
1604 (status, failed_task, failed_stage, failed_tag, errstr) = blist.run()
1605 if status != 0 or errstr != "retry":
1607 cleanup(do_raise=True)
1612 cleanup_list.append(gitroot + "/autobuild.pid")
1618 do_print("waiting for tail to flush")
1621 elapsed_time = time.time() - start_time
1623 if options.passcmd is not None:
1624 do_print("Running passcmd: %s" % options.passcmd)
1625 run_cmd(options.passcmd, dir=test_master)
1626 if options.pushto is not None:
1627 push_to(options.pushto, push_branch=options.branch)
1628 if options.keeplogs or options.attach_logs:
1629 blist.tarlogs("logs.tar.gz")
1630 do_print("Logs in logs.tar.gz")
1631 if options.always_email:
1632 email_success(elapsed_time, log_base=options.log_base)
1638 # something failed, gather a tar of the logs
1639 blist.tarlogs("logs.tar.gz")
1641 if options.email is not None:
1642 email_failure(status, failed_task, failed_stage, failed_tag, errstr,
1643 elapsed_time, log_base=options.log_base)
1645 elapsed_minutes = elapsed_time / 60.0
1648 ####################################################################
1652 Your autobuild[%s] on %s failed after %.1f minutes
1653 when trying to test %s with the following error:
1657 the autobuild has been abandoned. Please fix the error and resubmit.
1659 ####################################################################
1661 ''' % (options.branch, platform.node(), elapsed_minutes, failed_task, errstr))
1665 do_print("Logs in logs.tar.gz")