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 subprocess
import call
, check_call
,Popen
, PIPE
7 import os
, tarfile
, sys
, time
8 from optparse
import OptionParser
11 from email
.mime
.text
import MIMEText
12 from email
.mime
.base
import MIMEBase
13 from email
.mime
.application
import MIMEApplication
14 from email
.mime
.multipart
import MIMEMultipart
15 from distutils
.sysconfig
import get_python_lib
18 # This speeds up testing remarkably.
19 os
.environ
['TDB_NO_FSYNC'] = '1'
31 "samba-test-only" : ".",
32 "samba-systemkrb5" : ".",
35 "talloc" : "lib/talloc",
36 "replace" : "lib/replace",
37 "tevent" : "lib/tevent",
44 defaulttasks
= [ "ctdb", "samba", "samba-xc", "samba-o3", "samba-ctdb", "samba-libs", "samba-static", "samba-systemkrb5", "ldb", "tdb", "talloc", "replace", "tevent", "pidl" ]
46 if os
.environ
.get("AUTOBUILD_SKIP_SAMBA_O3", "0") == "1":
47 defaulttasks
.remove("samba-o3")
49 samba_configure_params
= " --picky-developer ${PREFIX} ${EXTRA_PYTHON} --with-profiling-data"
51 samba_libs_envvars
= "PYTHONPATH=${PYTHON_PREFIX}/site-packages:$PYTHONPATH"
52 samba_libs_envvars
+= " PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${PREFIX_DIR}/lib/pkgconfig"
53 samba_libs_envvars
+= " ADDITIONAL_CFLAGS='-Wmissing-prototypes'"
54 samba_libs_configure_base
= samba_libs_envvars
+ " ./configure --abi-check --enable-debug --picky-developer -C ${PREFIX} ${EXTRA_PYTHON}"
55 samba_libs_configure_libs
= samba_libs_configure_base
+ " --bundled-libraries=NONE"
56 samba_libs_configure_samba
= samba_libs_configure_base
+ " --bundled-libraries=!talloc,!pytalloc-util,!tdb,!pytdb,!ldb,!pyldb,!pyldb-util,!tevent,!pytevent"
58 if os
.environ
.get("AUTOBUILD_NO_EXTRA_PYTHON", "0") == "1":
61 extra_python
= "--extra-python=/usr/bin/python3"
64 "ctdb" : [ ("random-sleep", "../script/random-sleep.sh 60 600", "text/plain"),
65 ("configure", "./configure ${PREFIX}", "text/plain"),
66 ("make", "make all", "text/plain"),
67 ("install", "make install", "text/plain"),
68 ("test", "make autotest", "text/plain"),
69 ("check-clean-tree", "../script/clean-source-tree.sh", "text/plain"),
70 ("clean", "make clean", "text/plain") ],
72 # We have 'test' before 'install' because, 'test' should work without 'install'
73 "samba" : [ ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params
, "text/plain"),
74 ("make", "make -j", "text/plain"),
75 ("test", "make test FAIL_IMMEDIATELY=1", "text/plain"),
76 ("install", "make install", "text/plain"),
77 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
78 ("clean", "make clean", "text/plain") ],
80 "samba-test-only" : [ ("configure", "./configure.developer --with-selftest-prefix=./bin/ab --abi-check-disable" + samba_configure_params
, "text/plain"),
81 ("make", "make -j", "text/plain"),
82 ("test", "make test FAIL_IMMEDIATELY=1 TESTS=${TESTS}", "text/plain") ],
84 # Test cross-compile infrastructure
85 "samba-xc" : [ ("configure-native", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params
, "text/plain"),
86 ("configure-cross-execute", "./configure.developer -b ./bin-xe --cross-compile --cross-execute=script/identity_cc.sh" \
87 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xe/ab" + samba_configure_params
, "text/plain"),
88 ("configure-cross-answers", "./configure.developer -b ./bin-xa --cross-compile" \
89 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xa/ab" + samba_configure_params
, "text/plain"),
90 ("compare-results", "script/compare_cc_results.py ./bin/c4che/default.cache.py ./bin-xe/c4che/default.cache.py ./bin-xa/c4che/default.cache.py", "text/plain")],
92 # test build with -O3 -- catches extra warnings and bugs
93 "samba-o3" : [ ("random-sleep", "../script/random-sleep.sh 60 600", "text/plain"),
94 ("configure", "ADDITIONAL_CFLAGS='-O3' ./configure.developer --with-selftest-prefix=./bin/ab --abi-check-disable" + samba_configure_params
, "text/plain"),
95 ("make", "make -j", "text/plain"),
96 ("test", "make quicktest FAIL_IMMEDIATELY=1 TESTS='\(ad_dc\)'", "text/plain"),
97 ("install", "make install", "text/plain"),
98 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
99 ("clean", "make clean", "text/plain") ],
101 "samba-ctdb" : [ ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
103 # make sure we have tdb around:
104 ("tdb-configure", "cd lib/tdb && PYTHONPATH=${PYTHON_PREFIX}/site-packages:$PYTHONPATH PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${PREFIX_DIR}/lib/pkgconfig ./configure --bundled-libraries=NONE --abi-check --enable-debug -C ${PREFIX}", "text/plain"),
105 ("tdb-make", "cd lib/tdb && make", "text/plain"),
106 ("tdb-install", "cd lib/tdb && make install", "text/plain"),
109 # build samba with cluster support (also building ctdb):
110 ("samba-configure", "PYTHONPATH=${PYTHON_PREFIX}/site-packages:$PYTHONPATH PKG_CONFIG_PATH=${PREFIX_DIR}/lib/pkgconfig:${PKG_CONFIG_PATH} ./configure.developer --picky-developer ${PREFIX} --with-selftest-prefix=./bin/ab --with-cluster-support --bundled-libraries=!tdb", "text/plain"),
111 ("samba-make", "make", "text/plain"),
112 ("samba-check", "./bin/smbd -b | grep CLUSTER_SUPPORT", "text/plain"),
113 ("samba-install", "make install", "text/plain"),
114 ("ctdb-check", "test -e ${PREFIX_DIR}/sbin/ctdbd", "text/plain"),
117 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
118 ("clean", "make clean", "text/plain"),
119 ("ctdb-clean", "cd ./ctdb && make clean", "text/plain") ],
122 ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
123 ("talloc-configure", "cd lib/talloc && " + samba_libs_configure_libs
, "text/plain"),
124 ("talloc-make", "cd lib/talloc && make", "text/plain"),
125 ("talloc-install", "cd lib/talloc && make install", "text/plain"),
127 ("tdb-configure", "cd lib/tdb && " + samba_libs_configure_libs
, "text/plain"),
128 ("tdb-make", "cd lib/tdb && make", "text/plain"),
129 ("tdb-install", "cd lib/tdb && make install", "text/plain"),
131 ("tevent-configure", "cd lib/tevent && " + samba_libs_configure_libs
, "text/plain"),
132 ("tevent-make", "cd lib/tevent && make", "text/plain"),
133 ("tevent-install", "cd lib/tevent && make install", "text/plain"),
135 ("ldb-configure", "cd lib/ldb && " + samba_libs_configure_libs
, "text/plain"),
136 ("ldb-make", "cd lib/ldb && make", "text/plain"),
137 ("ldb-install", "cd lib/ldb && make install", "text/plain"),
139 ("nondevel-configure", "./configure ${PREFIX}", "text/plain"),
140 ("nondevel-make", "make -j", "text/plain"),
141 ("nondevel-check", "./bin/smbd -b | grep WITH_NTVFS_FILESERVER && exit 1; exit 0", "text/plain"),
142 ("nondevel-install", "make install", "text/plain"),
143 ("nondevel-dist", "make dist", "text/plain"),
145 # retry with all modules shared
146 ("allshared-distclean", "make distclean", "text/plain"),
147 ("allshared-configure", samba_libs_configure_samba
+ " --with-shared-modules=ALL", "text/plain"),
148 ("allshared-make", "make -j", "text/plain")],
151 ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
152 # build with all modules static
153 ("allstatic-configure", "./configure.developer " + samba_configure_params
+ " --with-static-modules=ALL", "text/plain"),
154 ("allstatic-make", "make -j", "text/plain"),
156 # retry without any required modules
157 ("none-distclean", "make distclean", "text/plain"),
158 ("none-configure", "./configure.developer " + samba_configure_params
+ " --with-static-modules=!FORCED,!DEFAULT --with-shared-modules=!FORCED,!DEFAULT", "text/plain"),
159 ("none-make", "make -j", "text/plain"),
161 # retry with nonshared smbd and smbtorture
162 ("nonshared-distclean", "make distclean", "text/plain"),
163 ("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", "text/plain"),
164 ("nonshared-make", "make -j", "text/plain")],
166 "samba-systemkrb5" : [
167 ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
168 ("configure", "./configure.developer " + samba_configure_params
+ " --with-system-mitkrb5 --without-ad-dc", "text/plain"),
169 ("make", "make -j", "text/plain"),
170 # we currently cannot run a full make test, a limited list of tests could be run
171 # via "make test TESTS=sometests"
172 # ("test", "make test FAIL_IMMEDIATELY=1", "text/plain"),
173 ("install", "make install", "text/plain"),
174 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
175 ("clean", "make clean", "text/plain")
181 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
182 ("configure", "./configure --enable-developer -C ${PREFIX} ${EXTRA_PYTHON}", "text/plain"),
183 ("make", "make", "text/plain"),
184 ("install", "make install", "text/plain"),
185 ("test", "make test", "text/plain"),
186 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
187 ("distcheck", "make distcheck", "text/plain"),
188 ("clean", "make clean", "text/plain") ],
191 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
192 ("configure", "./configure --enable-developer -C ${PREFIX} ${EXTRA_PYTHON}", "text/plain"),
193 ("make", "make", "text/plain"),
194 ("install", "make install", "text/plain"),
195 ("test", "make test", "text/plain"),
196 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
197 ("distcheck", "make distcheck", "text/plain"),
198 ("clean", "make clean", "text/plain") ],
201 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
202 ("configure", "./configure --enable-developer -C ${PREFIX} ${EXTRA_PYTHON}", "text/plain"),
203 ("make", "make", "text/plain"),
204 ("install", "make install", "text/plain"),
205 ("test", "make test", "text/plain"),
206 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
207 ("distcheck", "make distcheck", "text/plain"),
208 ("clean", "make clean", "text/plain") ],
211 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
212 ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
213 ("make", "make", "text/plain"),
214 ("install", "make install", "text/plain"),
215 ("test", "make test", "text/plain"),
216 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
217 ("distcheck", "make distcheck", "text/plain"),
218 ("clean", "make clean", "text/plain") ],
221 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
222 ("configure", "./configure --enable-developer -C ${PREFIX} ${EXTRA_PYTHON}", "text/plain"),
223 ("make", "make", "text/plain"),
224 ("install", "make install", "text/plain"),
225 ("test", "make test", "text/plain"),
226 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
227 ("distcheck", "make distcheck", "text/plain"),
228 ("clean", "make clean", "text/plain") ],
231 ("random-sleep", "../script/random-sleep.sh 60 600", "text/plain"),
232 ("configure", "perl Makefile.PL PREFIX=${PREFIX_DIR}", "text/plain"),
233 ("touch", "touch *.yp", "text/plain"),
234 ("make", "make", "text/plain"),
235 ("test", "make test", "text/plain"),
236 ("install", "make install", "text/plain"),
237 ("checkout-yapp-generated", "git checkout lib/Parse/Pidl/IDL.pm lib/Parse/Pidl/Expr.pm", "text/plain"),
238 ("check-clean-tree", "../script/clean-source-tree.sh", "text/plain"),
239 ("clean", "make clean", "text/plain") ],
241 # these are useful for debugging autobuild
242 'pass' : [ ("pass", 'echo passing && /bin/true', "text/plain") ],
243 'fail' : [ ("fail", 'echo failing && /bin/false', "text/plain") ]
246 def run_cmd(cmd
, dir=".", show
=None, output
=False, checkfail
=True):
248 show
= options
.verbose
250 print("Running: '%s' in '%s'" % (cmd
, dir))
252 return Popen([cmd
], shell
=True, stdout
=PIPE
, cwd
=dir).communicate()[0]
254 return check_call(cmd
, shell
=True, cwd
=dir)
256 return call(cmd
, shell
=True, cwd
=dir)
259 class builder(object):
260 '''handle build of one directory'''
262 def __init__(self
, name
, sequence
, cp
=True):
264 self
.dir = builddirs
[name
]
266 self
.tag
= self
.name
.replace('/', '_')
267 self
.sequence
= sequence
269 self
.stdout_path
= "%s/%s.stdout" % (gitroot
, self
.tag
)
270 self
.stderr_path
= "%s/%s.stderr" % (gitroot
, self
.tag
)
272 print("stdout for %s in %s" % (self
.name
, self
.stdout_path
))
273 print("stderr for %s in %s" % (self
.name
, self
.stderr_path
))
274 run_cmd("rm -f %s %s" % (self
.stdout_path
, self
.stderr_path
))
275 self
.stdout
= open(self
.stdout_path
, 'w')
276 self
.stderr
= open(self
.stderr_path
, 'w')
277 self
.stdin
= open("/dev/null", 'r')
278 self
.sdir
= "%s/%s" % (testbase
, self
.tag
)
279 self
.prefix
= "%s/prefix/%s" % (testbase
, self
.tag
)
280 run_cmd("rm -rf %s" % self
.sdir
)
281 cleanup_list
.append(self
.sdir
)
282 cleanup_list
.append(self
.prefix
)
283 os
.makedirs(self
.sdir
)
284 run_cmd("rm -rf %s" % self
.sdir
)
286 run_cmd("cp --recursive --link --archive %s %s" % (test_master
, self
.sdir
), dir=test_master
, show
=True)
288 run_cmd("git clone --recursive --shared %s %s" % (test_master
, self
.sdir
), dir=test_master
, show
=True)
291 def start_next(self
):
292 if self
.next
== len(self
.sequence
):
293 print '%s: Completed OK' % self
.name
296 (self
.stage
, self
.cmd
, self
.output_mime_type
) = self
.sequence
[self
.next
]
297 self
.cmd
= self
.cmd
.replace("${PYTHON_PREFIX}", get_python_lib(standard_lib
=1, prefix
=self
.prefix
))
298 self
.cmd
= self
.cmd
.replace("${PREFIX}", "--prefix=%s" % self
.prefix
)
299 self
.cmd
= self
.cmd
.replace("${EXTRA_PYTHON}", "%s" % extra_python
)
300 self
.cmd
= self
.cmd
.replace("${PREFIX_DIR}", "%s" % self
.prefix
)
301 self
.cmd
= self
.cmd
.replace("${TESTS}", options
.restrict_tests
)
302 # if self.output_mime_type == "text/x-subunit":
303 # self.cmd += " | %s --immediate" % (os.path.join(os.path.dirname(__file__), "selftest/format-subunit"))
304 print '%s: [%s] Running %s' % (self
.name
, self
.stage
, self
.cmd
)
306 os
.chdir("%s/%s" % (self
.sdir
, self
.dir))
307 self
.proc
= Popen(self
.cmd
, shell
=True,
308 stdout
=self
.stdout
, stderr
=self
.stderr
, stdin
=self
.stdin
)
313 class buildlist(object):
314 '''handle build of multiple directories'''
316 def __init__(self
, tasknames
, rebase_url
, rebase_branch
="master"):
319 self
.tail_proc
= None
322 if options
.restrict_tests
:
323 tasknames
= ["samba-test-only"]
325 tasknames
= defaulttasks
327 # If we are only running one test,
328 # do not sleep randomly to wait for it to start
329 os
.environ
['AUTOBUILD_RANDOM_SLEEP_OVERRIDE'] = '1'
332 b
= builder(n
, tasks
[n
], cp
=n
is not "pidl")
335 rebase_remote
= "rebaseon"
336 retry_task
= [ ("retry",
338 git remote add -t %s %s %s
342 git describe %s/%s > old_remote_branch.desc
344 git describe %s/%s > remote_branch.desc
345 diff old_remote_branch.desc remote_branch.desc
348 rebase_branch
, rebase_remote
, rebase_url
,
350 rebase_remote
, rebase_branch
,
352 rebase_remote
, rebase_branch
356 self
.retry
= builder('retry', retry_task
, cp
=False)
357 self
.need_retry
= False
360 if self
.tail_proc
is not None:
361 self
.tail_proc
.terminate()
362 self
.tail_proc
.wait()
363 self
.tail_proc
= None
364 if self
.retry
is not None:
365 self
.retry
.proc
.terminate()
366 self
.retry
.proc
.wait()
369 if b
.proc
is not None:
370 run_cmd("killbysubdir %s > /dev/null 2>&1" % b
.sdir
, checkfail
=False)
382 b
.status
= b
.proc
.poll()
388 ret
= self
.retry
.proc
.poll()
390 self
.need_retry
= True
400 if options
.retry
and self
.need_retry
:
402 print("retry needed")
403 return (0, None, None, None, "retry")
406 if os
.WIFSIGNALED(b
.status
) or os
.WEXITSTATUS(b
.status
) != 0:
408 return (b
.status
, b
.name
, b
.stage
, b
.tag
, "%s: [%s] failed '%s' with status %d" % (b
.name
, b
.stage
, b
.cmd
, b
.status
))
411 return (0, None, None, None, "All OK")
413 def write_system_info(self
):
414 filename
= 'system-info.txt'
415 f
= open(filename
, 'w')
416 for cmd
in ['uname -a', 'free', 'cat /proc/cpuinfo']:
417 print >>f
, '### %s' % cmd
418 print >>f
, run_cmd(cmd
, output
=True, checkfail
=False)
423 def tarlogs(self
, fname
):
424 tar
= tarfile
.open(fname
, "w:gz")
426 tar
.add(b
.stdout_path
, arcname
="%s.stdout" % b
.tag
)
427 tar
.add(b
.stderr_path
, arcname
="%s.stderr" % b
.tag
)
428 if os
.path
.exists("autobuild.log"):
429 tar
.add("autobuild.log")
430 sys_info
= self
.write_system_info()
434 def remove_logs(self
):
436 os
.unlink(b
.stdout_path
)
437 os
.unlink(b
.stderr_path
)
439 def start_tail(self
):
441 cmd
= "tail -f *.stdout *.stderr"
443 self
.tail_proc
= Popen(cmd
, shell
=True)
448 if options
.nocleanup
:
450 print("Cleaning up ....")
451 for d
in cleanup_list
:
452 run_cmd("rm -rf %s" % d
)
456 '''get to the top of the git repo'''
459 if os
.path
.isdir(os
.path
.join(p
, ".git")):
461 p
= os
.path
.abspath(os
.path
.join(p
, '..'))
465 def daemonize(logfile
):
467 if pid
== 0: # Parent
470 if pid
!= 0: # Actual daemon
475 import resource
# Resource usage information.
476 maxfd
= resource
.getrlimit(resource
.RLIMIT_NOFILE
)[1]
477 if maxfd
== resource
.RLIM_INFINITY
:
478 maxfd
= 1024 # Rough guess at maximum number of open file descriptors.
479 for fd
in range(0, maxfd
):
484 os
.open(logfile
, os
.O_RDWR | os
.O_CREAT
)
488 def write_pidfile(fname
):
489 '''write a pid file, cleanup on exit'''
490 f
= open(fname
, mode
='w')
491 f
.write("%u\n" % os
.getpid())
495 def rebase_tree(rebase_url
, rebase_branch
= "master"):
496 rebase_remote
= "rebaseon"
497 print("Rebasing on %s" % rebase_url
)
498 run_cmd("git describe HEAD", show
=True, dir=test_master
)
499 run_cmd("git remote add -t %s %s %s" %
500 (rebase_branch
, rebase_remote
, rebase_url
),
501 show
=True, dir=test_master
)
502 run_cmd("git fetch %s" % rebase_remote
, show
=True, dir=test_master
)
503 if options
.fix_whitespace
:
504 run_cmd("git rebase --force-rebase --whitespace=fix %s/%s" %
505 (rebase_remote
, rebase_branch
),
506 show
=True, dir=test_master
)
508 run_cmd("git rebase --force-rebase %s/%s" %
509 (rebase_remote
, rebase_branch
),
510 show
=True, dir=test_master
)
511 diff
= run_cmd("git --no-pager diff HEAD %s/%s" %
512 (rebase_remote
, rebase_branch
),
513 dir=test_master
, output
=True)
515 print("No differences between HEAD and %s/%s - exiting" %
516 (rebase_remote
, rebase_branch
))
518 run_cmd("git describe %s/%s" %
519 (rebase_remote
, rebase_branch
),
520 show
=True, dir=test_master
)
521 run_cmd("git describe HEAD", show
=True, dir=test_master
)
522 run_cmd("git --no-pager diff --stat HEAD %s/%s" %
523 (rebase_remote
, rebase_branch
),
524 show
=True, dir=test_master
)
526 def push_to(push_url
, push_branch
= "master"):
527 push_remote
= "pushto"
528 print("Pushing to %s" % push_url
)
530 run_cmd("git config --replace-all core.editor script/commit_mark.sh", dir=test_master
)
531 run_cmd("git commit --amend -c HEAD", dir=test_master
)
532 # the notes method doesn't work yet, as metze hasn't allowed refs/notes/* in master
533 # run_cmd("EDITOR=script/commit_mark.sh git notes edit HEAD", dir=test_master)
534 run_cmd("git remote add -t %s %s %s" %
535 (push_branch
, push_remote
, push_url
),
536 show
=True, dir=test_master
)
537 run_cmd("git push %s +HEAD:%s" %
538 (push_remote
, push_branch
),
539 show
=True, dir=test_master
)
541 def_testbase
= os
.getenv("AUTOBUILD_TESTBASE", "/memdisk/%s" % os
.getenv('USER'))
543 gitroot
= find_git_root()
545 raise Exception("Failed to find git root")
547 parser
= OptionParser()
548 parser
.add_option("", "--tail", help="show output while running", default
=False, action
="store_true")
549 parser
.add_option("", "--keeplogs", help="keep logs", default
=False, action
="store_true")
550 parser
.add_option("", "--nocleanup", help="don't remove test tree", default
=False, action
="store_true")
551 parser
.add_option("", "--testbase", help="base directory to run tests in (default %s)" % def_testbase
,
552 default
=def_testbase
)
553 parser
.add_option("", "--passcmd", help="command to run on success", default
=None)
554 parser
.add_option("", "--verbose", help="show all commands as they are run",
555 default
=False, action
="store_true")
556 parser
.add_option("", "--rebase", help="rebase on the given tree before testing",
557 default
=None, type='str')
558 parser
.add_option("", "--pushto", help="push to a git url on success",
559 default
=None, type='str')
560 parser
.add_option("", "--mark", help="add a Tested-By signoff before pushing",
561 default
=False, action
="store_true")
562 parser
.add_option("", "--fix-whitespace", help="fix whitespace on rebase",
563 default
=False, action
="store_true")
564 parser
.add_option("", "--retry", help="automatically retry if master changes",
565 default
=False, action
="store_true")
566 parser
.add_option("", "--email", help="send email to the given address on failure",
567 type='str', default
=None)
568 parser
.add_option("", "--email-from", help="send email from the given address",
569 type='str', default
="autobuild@samba.org")
570 parser
.add_option("", "--email-server", help="send email via the given server",
571 type='str', default
='localhost')
572 parser
.add_option("", "--always-email", help="always send email, even on success",
574 parser
.add_option("", "--daemon", help="daemonize after initial setup",
576 parser
.add_option("", "--branch", help="the branch to work on (default=master)",
577 default
="master", type='str')
578 parser
.add_option("", "--log-base", help="location where the logs can be found (default=cwd)",
579 default
=gitroot
, type='str')
580 parser
.add_option("", "--attach-logs", help="Attach logs to mails sent on success/failure?",
581 default
=False, action
="store_true")
582 parser
.add_option("", "--restrict-tests", help="run as make test with this TESTS= regex",
585 def send_email(subject
, text
, log_tar
):
586 outer
= MIMEMultipart()
587 outer
['Subject'] = subject
588 outer
['To'] = options
.email
589 outer
['From'] = options
.email_from
590 outer
['Date'] = email
.utils
.formatdate(localtime
= True)
591 outer
.preamble
= 'Autobuild mails are now in MIME because we optionally attach the logs.\n'
592 outer
.attach(MIMEText(text
, 'plain'))
593 if options
.attach_logs
:
594 fp
= open(log_tar
, 'rb')
595 msg
= MIMEApplication(fp
.read(), 'gzip', email
.encoders
.encode_base64
)
597 # Set the filename parameter
598 msg
.add_header('Content-Disposition', 'attachment', filename
=os
.path
.basename(log_tar
))
600 content
= outer
.as_string()
601 s
= smtplib
.SMTP(options
.email_server
)
602 s
.sendmail(options
.email_from
, [options
.email
], content
)
606 def email_failure(status
, failed_task
, failed_stage
, failed_tag
, errstr
,
607 elapsed_time
, log_base
=None, add_log_tail
=True):
608 '''send an email to options.email about the failure'''
609 elapsed_minutes
= elapsed_time
/ 60.0
610 user
= os
.getenv("USER")
616 Your autobuild on %s failed after %.1f minutes
617 when trying to test %s with the following error:
621 the autobuild has been abandoned. Please fix the error and resubmit.
623 A summary of the autobuild process is here:
626 ''' % (platform
.node(), elapsed_minutes
, failed_task
, errstr
, log_base
)
628 if options
.restrict_tests
:
630 The build was restricted to tests matching %s\n""" % options
.restrict_tests
632 if failed_task
!= 'rebase':
634 You can see logs of the failed task here:
639 or you can get full logs of all tasks in this job here:
643 The top commit for the tree that was built was:
647 ''' % (log_base
, failed_tag
, log_base
, failed_tag
, log_base
, top_commit_msg
)
650 f
= open("%s/%s.stdout" % (gitroot
, failed_tag
), 'r')
651 lines
= f
.readlines()
652 log_tail
= "".join(lines
[-50:])
653 num_lines
= len(lines
)
655 # Also include stderr (compile failures) if < 50 lines of stdout
656 f
= open("%s/%s.stderr" % (gitroot
, failed_tag
), 'r')
657 log_tail
+= "".join(f
.readlines()[-(50-num_lines
):])
660 The last 50 lines of log messages:
666 logs
= os
.path
.join(gitroot
, 'logs.tar.gz')
667 send_email('autobuild[%s] failure on %s for task %s during %s'
668 % (options
.branch
, platform
.node(), failed_task
, failed_stage
),
671 def email_success(elapsed_time
, log_base
=None):
672 '''send an email to options.email about a successful build'''
673 user
= os
.getenv("USER")
679 Your autobuild on %s has succeeded after %.1f minutes.
681 ''' % (platform
.node(), elapsed_time
/ 60.)
683 if options
.restrict_tests
:
685 The build was restricted to tests matching %s\n""" % options
.restrict_tests
690 you can get full logs of all tasks in this job here:
697 The top commit for the tree that was built was:
702 logs
= os
.path
.join(gitroot
, 'logs.tar.gz')
703 send_email('autobuild[%s] success on %s' % (options
.branch
, platform
.node()),
707 (options
, args
) = parser
.parse_args()
710 if options
.rebase
is None:
711 raise Exception('You can only use --retry if you also rebase')
713 testbase
= "%s/b%u" % (options
.testbase
, os
.getpid())
714 test_master
= "%s/master" % testbase
716 # get the top commit message, for emails
717 top_commit_msg
= run_cmd("git log -1", dir=gitroot
, output
=True)
720 os
.makedirs(testbase
)
721 except Exception, reason
:
722 raise Exception("Unable to create %s : %s" % (testbase
, reason
))
723 cleanup_list
.append(testbase
)
726 logfile
= os
.path
.join(testbase
, "log")
727 print "Forking into the background, writing progress to %s" % logfile
730 write_pidfile(gitroot
+ "/autobuild.pid")
732 start_time
= time
.time()
736 run_cmd("rm -rf %s" % test_master
)
737 cleanup_list
.append(test_master
)
738 run_cmd("git clone --recursive --shared %s %s" % (gitroot
, test_master
), show
=True, dir=gitroot
)
745 if options
.rebase
is not None:
746 rebase_tree(options
.rebase
, rebase_branch
=options
.branch
)
748 cleanup_list
.append(gitroot
+ "/autobuild.pid")
750 elapsed_time
= time
.time() - start_time
751 email_failure(-1, 'rebase', 'rebase', 'rebase',
752 'rebase on %s failed' % options
.branch
,
753 elapsed_time
, log_base
=options
.log_base
)
755 blist
= buildlist(args
, options
.rebase
, rebase_branch
=options
.branch
)
758 (status
, failed_task
, failed_stage
, failed_tag
, errstr
) = blist
.run()
759 if status
!= 0 or errstr
!= "retry":
766 cleanup_list
.append(gitroot
+ "/autobuild.pid")
770 print("waiting for tail to flush")
773 elapsed_time
= time
.time() - start_time
776 if options
.passcmd
is not None:
777 print("Running passcmd: %s" % options
.passcmd
)
778 run_cmd(options
.passcmd
, dir=test_master
)
779 if options
.pushto
is not None:
780 push_to(options
.pushto
, push_branch
=options
.branch
)
781 if options
.keeplogs
or options
.attach_logs
:
782 blist
.tarlogs("logs.tar.gz")
783 print("Logs in logs.tar.gz")
784 if options
.always_email
:
785 email_success(elapsed_time
, log_base
=options
.log_base
)
791 # something failed, gather a tar of the logs
792 blist
.tarlogs("logs.tar.gz")
794 if options
.email
is not None:
795 email_failure(status
, failed_task
, failed_stage
, failed_tag
, errstr
,
796 elapsed_time
, log_base
=options
.log_base
)
798 elapsed_minutes
= elapsed_time
/ 60.0
801 ####################################################################
805 Your autobuild[%s] on %s failed after %.1f minutes
806 when trying to test %s with the following error:
810 the autobuild has been abandoned. Please fix the error and resubmit.
812 ####################################################################
814 ''' % (options
.branch
, platform
.node(), elapsed_minutes
, failed_task
, errstr
)
818 print("Logs in logs.tar.gz")