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" : ".",
34 "talloc" : "lib/talloc",
35 "replace" : "lib/replace",
36 "tevent" : "lib/tevent",
43 defaulttasks
= [ "ctdb", "samba", "samba-xc", "samba-o3", "samba-ctdb", "samba-libs", "samba-static", "ldb", "tdb", "talloc", "replace", "tevent", "pidl" ]
45 if os
.environ
.get("AUTOBUILD_SKIP_SAMBA_O3", "0") == "1":
46 defaulttasks
.remove("samba-o3")
48 samba_configure_params
= " --picky-developer ${PREFIX} ${EXTRA_PYTHON} --with-profiling-data"
50 samba_libs_envvars
= "PYTHONPATH=${PYTHON_PREFIX}/site-packages:$PYTHONPATH"
51 samba_libs_envvars
+= " PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${PREFIX_DIR}/lib/pkgconfig"
52 samba_libs_envvars
+= " ADDITIONAL_CFLAGS='-Wmissing-prototypes'"
53 samba_libs_configure_base
= samba_libs_envvars
+ " ./configure --abi-check --enable-debug --picky-developer -C ${PREFIX} ${EXTRA_PYTHON}"
54 samba_libs_configure_libs
= samba_libs_configure_base
+ " --bundled-libraries=NONE"
55 samba_libs_configure_samba
= samba_libs_configure_base
+ " --bundled-libraries=!talloc,!pytalloc-util,!tdb,!pytdb,!ldb,!pyldb,!pyldb-util,!tevent,!pytevent"
57 if os
.environ
.get("AUTOBUILD_NO_EXTRA_PYTHON", "0") == "1":
60 extra_python
= "--extra-python=/usr/bin/python3"
63 "ctdb" : [ ("random-sleep", "../script/random-sleep.sh 60 600", "text/plain"),
64 ("configure", "./configure ${PREFIX}", "text/plain"),
65 ("make", "make all", "text/plain"),
66 ("install", "make install", "text/plain"),
67 ("test", "make autotest", "text/plain"),
68 ("check-clean-tree", "../script/clean-source-tree.sh", "text/plain"),
69 ("clean", "make clean", "text/plain") ],
71 # We have 'test' before 'install' because, 'test' should work without 'install'
72 "samba" : [ ("configure", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params
, "text/plain"),
73 ("make", "make -j", "text/plain"),
74 ("test", "make test FAIL_IMMEDIATELY=1", "text/plain"),
75 ("install", "make install", "text/plain"),
76 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
77 ("clean", "make clean", "text/plain") ],
79 "samba-test-only" : [ ("configure", "./configure.developer --with-selftest-prefix=./bin/ab --abi-check-disable" + samba_configure_params
, "text/plain"),
80 ("make", "make -j", "text/plain"),
81 ("test", "make test FAIL_IMMEDIATELY=1 TESTS=${TESTS}", "text/plain") ],
83 # Test cross-compile infrastructure
84 "samba-xc" : [ ("configure-native", "./configure.developer --with-selftest-prefix=./bin/ab" + samba_configure_params
, "text/plain"),
85 ("configure-cross-execute", "./configure.developer -b ./bin-xe --cross-compile --cross-execute=script/identity_cc.sh" \
86 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xe/ab" + samba_configure_params
, "text/plain"),
87 ("configure-cross-answers", "./configure.developer -b ./bin-xa --cross-compile" \
88 " --cross-answers=./bin-xe/cross-answers.txt --with-selftest-prefix=./bin-xa/ab" + samba_configure_params
, "text/plain"),
89 ("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")],
91 # test build with -O3 -- catches extra warnings and bugs
92 "samba-o3" : [ ("random-sleep", "../script/random-sleep.sh 60 600", "text/plain"),
93 ("configure", "ADDITIONAL_CFLAGS='-O3' ./configure.developer --with-selftest-prefix=./bin/ab --abi-check-disable" + samba_configure_params
, "text/plain"),
94 ("make", "make -j", "text/plain"),
95 ("test", "make quicktest FAIL_IMMEDIATELY=1 TESTS='\(ad_dc\)'", "text/plain"),
96 ("install", "make install", "text/plain"),
97 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
98 ("clean", "make clean", "text/plain") ],
100 "samba-ctdb" : [ ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
102 # make sure we have tdb around:
103 ("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"),
104 ("tdb-make", "cd lib/tdb && make", "text/plain"),
105 ("tdb-install", "cd lib/tdb && make install", "text/plain"),
108 # build samba with cluster support (also building ctdb):
109 ("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"),
110 ("samba-make", "make", "text/plain"),
111 ("samba-check", "./bin/smbd -b | grep CLUSTER_SUPPORT", "text/plain"),
112 ("samba-install", "make install", "text/plain"),
113 ("ctdb-check", "test -e ${PREFIX_DIR}/sbin/ctdbd", "text/plain"),
116 ("check-clean-tree", "script/clean-source-tree.sh", "text/plain"),
117 ("clean", "make clean", "text/plain"),
118 ("ctdb-clean", "cd ./ctdb && make clean", "text/plain") ],
121 ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
122 ("talloc-configure", "cd lib/talloc && " + samba_libs_configure_libs
, "text/plain"),
123 ("talloc-make", "cd lib/talloc && make", "text/plain"),
124 ("talloc-install", "cd lib/talloc && make install", "text/plain"),
126 ("tdb-configure", "cd lib/tdb && " + samba_libs_configure_libs
, "text/plain"),
127 ("tdb-make", "cd lib/tdb && make", "text/plain"),
128 ("tdb-install", "cd lib/tdb && make install", "text/plain"),
130 ("tevent-configure", "cd lib/tevent && " + samba_libs_configure_libs
, "text/plain"),
131 ("tevent-make", "cd lib/tevent && make", "text/plain"),
132 ("tevent-install", "cd lib/tevent && make install", "text/plain"),
134 ("ldb-configure", "cd lib/ldb && " + samba_libs_configure_libs
, "text/plain"),
135 ("ldb-make", "cd lib/ldb && make", "text/plain"),
136 ("ldb-install", "cd lib/ldb && make install", "text/plain"),
138 ("nondevel-configure", "./configure ${PREFIX}", "text/plain"),
139 ("nondevel-make", "make -j", "text/plain"),
140 ("nondevel-check", "./bin/smbd -b | grep WITH_NTVFS_FILESERVER && exit 1; exit 0", "text/plain"),
141 ("nondevel-install", "make install", "text/plain"),
142 ("nondevel-dist", "make dist", "text/plain"),
144 # retry with all modules shared
145 ("allshared-distclean", "make distclean", "text/plain"),
146 ("allshared-configure", samba_libs_configure_samba
+ " --with-shared-modules=ALL", "text/plain"),
147 ("allshared-make", "make -j", "text/plain")],
150 ("random-sleep", "script/random-sleep.sh 60 600", "text/plain"),
151 # build with all modules static
152 ("allstatic-configure", "./configure.developer " + samba_configure_params
+ " --with-static-modules=ALL", "text/plain"),
153 ("allstatic-make", "make -j", "text/plain"),
155 # retry without any required modules
156 ("none-distclean", "make distclean", "text/plain"),
157 ("none-configure", "./configure.developer " + samba_configure_params
+ " --with-static-modules=!FORCED,!DEFAULT --with-shared-modules=!FORCED,!DEFAULT", "text/plain"),
158 ("none-make", "make -j", "text/plain"),
160 # retry with nonshared smbd and smbtorture
161 ("nonshared-distclean", "make distclean", "text/plain"),
162 ("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"),
163 ("nonshared-make", "make -j", "text/plain")],
166 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
167 ("configure", "./configure --enable-developer -C ${PREFIX} ${EXTRA_PYTHON}", "text/plain"),
168 ("make", "make", "text/plain"),
169 ("install", "make install", "text/plain"),
170 ("test", "make test", "text/plain"),
171 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
172 ("distcheck", "make distcheck", "text/plain"),
173 ("clean", "make clean", "text/plain") ],
176 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
177 ("configure", "./configure --enable-developer -C ${PREFIX} ${EXTRA_PYTHON}", "text/plain"),
178 ("make", "make", "text/plain"),
179 ("install", "make install", "text/plain"),
180 ("test", "make test", "text/plain"),
181 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
182 ("distcheck", "make distcheck", "text/plain"),
183 ("clean", "make clean", "text/plain") ],
186 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
187 ("configure", "./configure --enable-developer -C ${PREFIX} ${EXTRA_PYTHON}", "text/plain"),
188 ("make", "make", "text/plain"),
189 ("install", "make install", "text/plain"),
190 ("test", "make test", "text/plain"),
191 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
192 ("distcheck", "make distcheck", "text/plain"),
193 ("clean", "make clean", "text/plain") ],
196 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
197 ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"),
198 ("make", "make", "text/plain"),
199 ("install", "make install", "text/plain"),
200 ("test", "make test", "text/plain"),
201 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
202 ("distcheck", "make distcheck", "text/plain"),
203 ("clean", "make clean", "text/plain") ],
206 ("random-sleep", "../../script/random-sleep.sh 60 600", "text/plain"),
207 ("configure", "./configure --enable-developer -C ${PREFIX} ${EXTRA_PYTHON}", "text/plain"),
208 ("make", "make", "text/plain"),
209 ("install", "make install", "text/plain"),
210 ("test", "make test", "text/plain"),
211 ("check-clean-tree", "../../script/clean-source-tree.sh", "text/plain"),
212 ("distcheck", "make distcheck", "text/plain"),
213 ("clean", "make clean", "text/plain") ],
216 ("random-sleep", "../script/random-sleep.sh 60 600", "text/plain"),
217 ("configure", "perl Makefile.PL PREFIX=${PREFIX_DIR}", "text/plain"),
218 ("touch", "touch *.yp", "text/plain"),
219 ("make", "make", "text/plain"),
220 ("test", "make test", "text/plain"),
221 ("install", "make install", "text/plain"),
222 ("checkout-yapp-generated", "git checkout lib/Parse/Pidl/IDL.pm lib/Parse/Pidl/Expr.pm", "text/plain"),
223 ("check-clean-tree", "../script/clean-source-tree.sh", "text/plain"),
224 ("clean", "make clean", "text/plain") ],
226 # these are useful for debugging autobuild
227 'pass' : [ ("pass", 'echo passing && /bin/true', "text/plain") ],
228 'fail' : [ ("fail", 'echo failing && /bin/false', "text/plain") ]
231 def run_cmd(cmd
, dir=".", show
=None, output
=False, checkfail
=True):
233 show
= options
.verbose
235 print("Running: '%s' in '%s'" % (cmd
, dir))
237 return Popen([cmd
], shell
=True, stdout
=PIPE
, cwd
=dir).communicate()[0]
239 return check_call(cmd
, shell
=True, cwd
=dir)
241 return call(cmd
, shell
=True, cwd
=dir)
244 class builder(object):
245 '''handle build of one directory'''
247 def __init__(self
, name
, sequence
, cp
=True):
249 self
.dir = builddirs
[name
]
251 self
.tag
= self
.name
.replace('/', '_')
252 self
.sequence
= sequence
254 self
.stdout_path
= "%s/%s.stdout" % (gitroot
, self
.tag
)
255 self
.stderr_path
= "%s/%s.stderr" % (gitroot
, self
.tag
)
257 print("stdout for %s in %s" % (self
.name
, self
.stdout_path
))
258 print("stderr for %s in %s" % (self
.name
, self
.stderr_path
))
259 run_cmd("rm -f %s %s" % (self
.stdout_path
, self
.stderr_path
))
260 self
.stdout
= open(self
.stdout_path
, 'w')
261 self
.stderr
= open(self
.stderr_path
, 'w')
262 self
.stdin
= open("/dev/null", 'r')
263 self
.sdir
= "%s/%s" % (testbase
, self
.tag
)
264 self
.prefix
= "%s/prefix/%s" % (testbase
, self
.tag
)
265 run_cmd("rm -rf %s" % self
.sdir
)
266 cleanup_list
.append(self
.sdir
)
267 cleanup_list
.append(self
.prefix
)
268 os
.makedirs(self
.sdir
)
269 run_cmd("rm -rf %s" % self
.sdir
)
271 run_cmd("cp --recursive --link --archive %s %s" % (test_master
, self
.sdir
), dir=test_master
, show
=True)
273 run_cmd("git clone --recursive --shared %s %s" % (test_master
, self
.sdir
), dir=test_master
, show
=True)
276 def start_next(self
):
277 if self
.next
== len(self
.sequence
):
278 print '%s: Completed OK' % self
.name
281 (self
.stage
, self
.cmd
, self
.output_mime_type
) = self
.sequence
[self
.next
]
282 self
.cmd
= self
.cmd
.replace("${PYTHON_PREFIX}", get_python_lib(standard_lib
=1, prefix
=self
.prefix
))
283 self
.cmd
= self
.cmd
.replace("${PREFIX}", "--prefix=%s" % self
.prefix
)
284 self
.cmd
= self
.cmd
.replace("${EXTRA_PYTHON}", "%s" % extra_python
)
285 self
.cmd
= self
.cmd
.replace("${PREFIX_DIR}", "%s" % self
.prefix
)
286 self
.cmd
= self
.cmd
.replace("${TESTS}", options
.restrict_tests
)
287 # if self.output_mime_type == "text/x-subunit":
288 # self.cmd += " | %s --immediate" % (os.path.join(os.path.dirname(__file__), "selftest/format-subunit"))
289 print '%s: [%s] Running %s' % (self
.name
, self
.stage
, self
.cmd
)
291 os
.chdir("%s/%s" % (self
.sdir
, self
.dir))
292 self
.proc
= Popen(self
.cmd
, shell
=True,
293 stdout
=self
.stdout
, stderr
=self
.stderr
, stdin
=self
.stdin
)
298 class buildlist(object):
299 '''handle build of multiple directories'''
301 def __init__(self
, tasknames
, rebase_url
, rebase_branch
="master"):
304 self
.tail_proc
= None
307 if options
.restrict_tests
:
308 tasknames
= ["samba-test-only"]
310 tasknames
= defaulttasks
312 # If we are only running one test,
313 # do not sleep randomly to wait for it to start
314 os
.environ
['AUTOBUILD_RANDOM_SLEEP_OVERRIDE'] = '1'
317 b
= builder(n
, tasks
[n
], cp
=n
is not "pidl")
320 rebase_remote
= "rebaseon"
321 retry_task
= [ ("retry",
323 git remote add -t %s %s %s
327 git describe %s/%s > old_remote_branch.desc
329 git describe %s/%s > remote_branch.desc
330 diff old_remote_branch.desc remote_branch.desc
333 rebase_branch
, rebase_remote
, rebase_url
,
335 rebase_remote
, rebase_branch
,
337 rebase_remote
, rebase_branch
341 self
.retry
= builder('retry', retry_task
, cp
=False)
342 self
.need_retry
= False
345 if self
.tail_proc
is not None:
346 self
.tail_proc
.terminate()
347 self
.tail_proc
.wait()
348 self
.tail_proc
= None
349 if self
.retry
is not None:
350 self
.retry
.proc
.terminate()
351 self
.retry
.proc
.wait()
354 if b
.proc
is not None:
355 run_cmd("killbysubdir %s > /dev/null 2>&1" % b
.sdir
, checkfail
=False)
367 b
.status
= b
.proc
.poll()
373 ret
= self
.retry
.proc
.poll()
375 self
.need_retry
= True
385 if options
.retry
and self
.need_retry
:
387 print("retry needed")
388 return (0, None, None, None, "retry")
391 if os
.WIFSIGNALED(b
.status
) or os
.WEXITSTATUS(b
.status
) != 0:
393 return (b
.status
, b
.name
, b
.stage
, b
.tag
, "%s: [%s] failed '%s' with status %d" % (b
.name
, b
.stage
, b
.cmd
, b
.status
))
396 return (0, None, None, None, "All OK")
398 def write_system_info(self
):
399 filename
= 'system-info.txt'
400 f
= open(filename
, 'w')
401 for cmd
in ['uname -a', 'free', 'cat /proc/cpuinfo']:
402 print >>f
, '### %s' % cmd
403 print >>f
, run_cmd(cmd
, output
=True, checkfail
=False)
408 def tarlogs(self
, fname
):
409 tar
= tarfile
.open(fname
, "w:gz")
411 tar
.add(b
.stdout_path
, arcname
="%s.stdout" % b
.tag
)
412 tar
.add(b
.stderr_path
, arcname
="%s.stderr" % b
.tag
)
413 if os
.path
.exists("autobuild.log"):
414 tar
.add("autobuild.log")
415 sys_info
= self
.write_system_info()
419 def remove_logs(self
):
421 os
.unlink(b
.stdout_path
)
422 os
.unlink(b
.stderr_path
)
424 def start_tail(self
):
426 cmd
= "tail -f *.stdout *.stderr"
428 self
.tail_proc
= Popen(cmd
, shell
=True)
433 if options
.nocleanup
:
435 print("Cleaning up ....")
436 for d
in cleanup_list
:
437 run_cmd("rm -rf %s" % d
)
441 '''get to the top of the git repo'''
444 if os
.path
.isdir(os
.path
.join(p
, ".git")):
446 p
= os
.path
.abspath(os
.path
.join(p
, '..'))
450 def daemonize(logfile
):
452 if pid
== 0: # Parent
455 if pid
!= 0: # Actual daemon
460 import resource
# Resource usage information.
461 maxfd
= resource
.getrlimit(resource
.RLIMIT_NOFILE
)[1]
462 if maxfd
== resource
.RLIM_INFINITY
:
463 maxfd
= 1024 # Rough guess at maximum number of open file descriptors.
464 for fd
in range(0, maxfd
):
469 os
.open(logfile
, os
.O_RDWR | os
.O_CREAT
)
473 def write_pidfile(fname
):
474 '''write a pid file, cleanup on exit'''
475 f
= open(fname
, mode
='w')
476 f
.write("%u\n" % os
.getpid())
480 def rebase_tree(rebase_url
, rebase_branch
= "master"):
481 rebase_remote
= "rebaseon"
482 print("Rebasing on %s" % rebase_url
)
483 run_cmd("git describe HEAD", show
=True, dir=test_master
)
484 run_cmd("git remote add -t %s %s %s" %
485 (rebase_branch
, rebase_remote
, rebase_url
),
486 show
=True, dir=test_master
)
487 run_cmd("git fetch %s" % rebase_remote
, show
=True, dir=test_master
)
488 if options
.fix_whitespace
:
489 run_cmd("git rebase --force-rebase --whitespace=fix %s/%s" %
490 (rebase_remote
, rebase_branch
),
491 show
=True, dir=test_master
)
493 run_cmd("git rebase --force-rebase %s/%s" %
494 (rebase_remote
, rebase_branch
),
495 show
=True, dir=test_master
)
496 diff
= run_cmd("git --no-pager diff HEAD %s/%s" %
497 (rebase_remote
, rebase_branch
),
498 dir=test_master
, output
=True)
500 print("No differences between HEAD and %s/%s - exiting" %
501 (rebase_remote
, rebase_branch
))
503 run_cmd("git describe %s/%s" %
504 (rebase_remote
, rebase_branch
),
505 show
=True, dir=test_master
)
506 run_cmd("git describe HEAD", show
=True, dir=test_master
)
507 run_cmd("git --no-pager diff --stat HEAD %s/%s" %
508 (rebase_remote
, rebase_branch
),
509 show
=True, dir=test_master
)
511 def push_to(push_url
, push_branch
= "master"):
512 push_remote
= "pushto"
513 print("Pushing to %s" % push_url
)
515 run_cmd("git config --replace-all core.editor script/commit_mark.sh", dir=test_master
)
516 run_cmd("git commit --amend -c HEAD", dir=test_master
)
517 # the notes method doesn't work yet, as metze hasn't allowed refs/notes/* in master
518 # run_cmd("EDITOR=script/commit_mark.sh git notes edit HEAD", dir=test_master)
519 run_cmd("git remote add -t %s %s %s" %
520 (push_branch
, push_remote
, push_url
),
521 show
=True, dir=test_master
)
522 run_cmd("git push %s +HEAD:%s" %
523 (push_remote
, push_branch
),
524 show
=True, dir=test_master
)
526 def_testbase
= os
.getenv("AUTOBUILD_TESTBASE", "/memdisk/%s" % os
.getenv('USER'))
528 gitroot
= find_git_root()
530 raise Exception("Failed to find git root")
532 parser
= OptionParser()
533 parser
.add_option("", "--tail", help="show output while running", default
=False, action
="store_true")
534 parser
.add_option("", "--keeplogs", help="keep logs", default
=False, action
="store_true")
535 parser
.add_option("", "--nocleanup", help="don't remove test tree", default
=False, action
="store_true")
536 parser
.add_option("", "--testbase", help="base directory to run tests in (default %s)" % def_testbase
,
537 default
=def_testbase
)
538 parser
.add_option("", "--passcmd", help="command to run on success", default
=None)
539 parser
.add_option("", "--verbose", help="show all commands as they are run",
540 default
=False, action
="store_true")
541 parser
.add_option("", "--rebase", help="rebase on the given tree before testing",
542 default
=None, type='str')
543 parser
.add_option("", "--pushto", help="push to a git url on success",
544 default
=None, type='str')
545 parser
.add_option("", "--mark", help="add a Tested-By signoff before pushing",
546 default
=False, action
="store_true")
547 parser
.add_option("", "--fix-whitespace", help="fix whitespace on rebase",
548 default
=False, action
="store_true")
549 parser
.add_option("", "--retry", help="automatically retry if master changes",
550 default
=False, action
="store_true")
551 parser
.add_option("", "--email", help="send email to the given address on failure",
552 type='str', default
=None)
553 parser
.add_option("", "--email-from", help="send email from the given address",
554 type='str', default
="autobuild@samba.org")
555 parser
.add_option("", "--email-server", help="send email via the given server",
556 type='str', default
='localhost')
557 parser
.add_option("", "--always-email", help="always send email, even on success",
559 parser
.add_option("", "--daemon", help="daemonize after initial setup",
561 parser
.add_option("", "--branch", help="the branch to work on (default=master)",
562 default
="master", type='str')
563 parser
.add_option("", "--log-base", help="location where the logs can be found (default=cwd)",
564 default
=gitroot
, type='str')
565 parser
.add_option("", "--attach-logs", help="Attach logs to mails sent on success/failure?",
566 default
=False, action
="store_true")
567 parser
.add_option("", "--restrict-tests", help="run as make test with this TESTS= regex",
570 def send_email(subject
, text
, log_tar
):
571 outer
= MIMEMultipart()
572 outer
['Subject'] = subject
573 outer
['To'] = options
.email
574 outer
['From'] = options
.email_from
575 outer
['Date'] = email
.utils
.formatdate(localtime
= True)
576 outer
.preamble
= 'Autobuild mails are now in MIME because we optionally attach the logs.\n'
577 outer
.attach(MIMEText(text
, 'plain'))
578 if options
.attach_logs
:
579 fp
= open(log_tar
, 'rb')
580 msg
= MIMEApplication(fp
.read(), 'gzip', email
.encoders
.encode_base64
)
582 # Set the filename parameter
583 msg
.add_header('Content-Disposition', 'attachment', filename
=os
.path
.basename(log_tar
))
585 content
= outer
.as_string()
586 s
= smtplib
.SMTP(options
.email_server
)
587 s
.sendmail(options
.email_from
, [options
.email
], content
)
591 def email_failure(status
, failed_task
, failed_stage
, failed_tag
, errstr
,
592 elapsed_time
, log_base
=None, add_log_tail
=True):
593 '''send an email to options.email about the failure'''
594 elapsed_minutes
= elapsed_time
/ 60.0
595 user
= os
.getenv("USER")
601 Your autobuild on %s failed after %.1f minutes
602 when trying to test %s with the following error:
606 the autobuild has been abandoned. Please fix the error and resubmit.
608 A summary of the autobuild process is here:
611 ''' % (platform
.node(), elapsed_minutes
, failed_task
, errstr
, log_base
)
613 if options
.restrict_tests
:
615 The build was restricted to tests matching %s\n""" % options
.restrict_tests
617 if failed_task
!= 'rebase':
619 You can see logs of the failed task here:
624 or you can get full logs of all tasks in this job here:
628 The top commit for the tree that was built was:
632 ''' % (log_base
, failed_tag
, log_base
, failed_tag
, log_base
, top_commit_msg
)
635 f
= open("%s/%s.stdout" % (gitroot
, failed_tag
), 'r')
636 lines
= f
.readlines()
637 log_tail
= "".join(lines
[-50:])
638 num_lines
= len(lines
)
640 # Also include stderr (compile failures) if < 50 lines of stdout
641 f
= open("%s/%s.stderr" % (gitroot
, failed_tag
), 'r')
642 log_tail
+= "".join(f
.readlines()[-(50-num_lines
):])
645 The last 50 lines of log messages:
651 logs
= os
.path
.join(gitroot
, 'logs.tar.gz')
652 send_email('autobuild[%s] failure on %s for task %s during %s'
653 % (options
.branch
, platform
.node(), failed_task
, failed_stage
),
656 def email_success(elapsed_time
, log_base
=None):
657 '''send an email to options.email about a successful build'''
658 user
= os
.getenv("USER")
664 Your autobuild on %s has succeeded after %.1f minutes.
666 ''' % (platform
.node(), elapsed_time
/ 60.)
668 if options
.restrict_tests
:
670 The build was restricted to tests matching %s\n""" % options
.restrict_tests
675 you can get full logs of all tasks in this job here:
682 The top commit for the tree that was built was:
687 logs
= os
.path
.join(gitroot
, 'logs.tar.gz')
688 send_email('autobuild[%s] success on %s' % (options
.branch
, platform
.node()),
692 (options
, args
) = parser
.parse_args()
695 if options
.rebase
is None:
696 raise Exception('You can only use --retry if you also rebase')
698 testbase
= "%s/b%u" % (options
.testbase
, os
.getpid())
699 test_master
= "%s/master" % testbase
701 # get the top commit message, for emails
702 top_commit_msg
= run_cmd("git log -1", dir=gitroot
, output
=True)
705 os
.makedirs(testbase
)
706 except Exception, reason
:
707 raise Exception("Unable to create %s : %s" % (testbase
, reason
))
708 cleanup_list
.append(testbase
)
711 logfile
= os
.path
.join(testbase
, "log")
712 print "Forking into the background, writing progress to %s" % logfile
715 write_pidfile(gitroot
+ "/autobuild.pid")
717 start_time
= time
.time()
721 run_cmd("rm -rf %s" % test_master
)
722 cleanup_list
.append(test_master
)
723 run_cmd("git clone --recursive --shared %s %s" % (gitroot
, test_master
), show
=True, dir=gitroot
)
730 if options
.rebase
is not None:
731 rebase_tree(options
.rebase
, rebase_branch
=options
.branch
)
733 cleanup_list
.append(gitroot
+ "/autobuild.pid")
735 elapsed_time
= time
.time() - start_time
736 email_failure(-1, 'rebase', 'rebase', 'rebase',
737 'rebase on %s failed' % options
.branch
,
738 elapsed_time
, log_base
=options
.log_base
)
740 blist
= buildlist(args
, options
.rebase
, rebase_branch
=options
.branch
)
743 (status
, failed_task
, failed_stage
, failed_tag
, errstr
) = blist
.run()
744 if status
!= 0 or errstr
!= "retry":
751 cleanup_list
.append(gitroot
+ "/autobuild.pid")
755 print("waiting for tail to flush")
758 elapsed_time
= time
.time() - start_time
761 if options
.passcmd
is not None:
762 print("Running passcmd: %s" % options
.passcmd
)
763 run_cmd(options
.passcmd
, dir=test_master
)
764 if options
.pushto
is not None:
765 push_to(options
.pushto
, push_branch
=options
.branch
)
766 if options
.keeplogs
or options
.attach_logs
:
767 blist
.tarlogs("logs.tar.gz")
768 print("Logs in logs.tar.gz")
769 if options
.always_email
:
770 email_success(elapsed_time
, log_base
=options
.log_base
)
776 # something failed, gather a tar of the logs
777 blist
.tarlogs("logs.tar.gz")
779 if options
.email
is not None:
780 email_failure(status
, failed_task
, failed_stage
, failed_tag
, errstr
,
781 elapsed_time
, log_base
=options
.log_base
)
783 elapsed_minutes
= elapsed_time
/ 60.0
786 ####################################################################
790 Your autobuild[%s] on %s failed after %.1f minutes
791 when trying to test %s with the following error:
795 the autobuild has been abandoned. Please fix the error and resubmit.
797 ####################################################################
799 ''' % (options
.branch
, platform
.node(), elapsed_minutes
, failed_task
, errstr
)
803 print("Logs in logs.tar.gz")