diff options
Diffstat (limited to 'script/land.py')
-rwxr-xr-x | script/land.py | 741 |
1 files changed, 0 insertions, 741 deletions
diff --git a/script/land.py b/script/land.py deleted file mode 100755 index 72bdd4b840..0000000000 --- a/script/land.py +++ /dev/null @@ -1,741 +0,0 @@ -#!/usr/bin/env python -# run tests on all Samba subprojects and push to a git tree on success -# Copyright Andrew Tridgell 2010 -# Copyright Jelmer Vernooij 2010 -# released under GNU GPL v3 or later - -from cStringIO import StringIO -import fcntl -from subprocess import call, check_call, Popen, PIPE -import os, tarfile, sys, time -from optparse import OptionParser -import smtplib -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../selftest")) -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../lib/testtools")) -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../lib/subunit/python")) -import subunit -import testtools -import subunithelper -import tempfile -from email.mime.application import MIMEApplication -from email.mime.text import MIMEText -from email.mime.multipart import MIMEMultipart - -samba_master = os.getenv('SAMBA_MASTER', 'git://git.samba.org/samba.git') -samba_master_ssh = os.getenv('SAMBA_MASTER_SSH', 'git+ssh://git.samba.org/data/git/samba.git') - -cleanup_list = [] - -os.environ['CC'] = "ccache gcc" - -tasks = { - "source3" : [ ("autogen", "./autogen.sh", "text/plain"), - ("configure", "./configure.developer ${PREFIX}", "text/plain"), - ("make basics", "make basics", "text/plain"), - ("make", "make -j 4 everything", "text/plain"), # don't use too many processes - ("install", "make install", "text/plain"), - ("test", "TDB_NO_FSYNC=1 make subunit-test", "text/x-subunit") ], - - "source4" : [ ("configure", "./configure.developer ${PREFIX}", "text/plain"), - ("make", "make -j", "text/plain"), - ("install", "make install", "text/plain"), - ("test", "TDB_NO_FSYNC=1 make subunit-test", "text/x-subunit") ], - - "lib/ldb" : [ ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"), - ("make", "make -j", "text/plain"), - ("install", "make install", "text/plain"), - ("test", "make test", "text/plain") ], - - "lib/tdb" : [ ("autogen", "./autogen-waf.sh", "text/plain"), - ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"), - ("make", "make -j", "text/plain"), - ("install", "make install", "text/plain"), - ("test", "make test", "text/plain") ], - - "lib/talloc" : [ ("autogen", "./autogen-waf.sh", "text/plain"), - ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"), - ("make", "make -j", "text/plain"), - ("install", "make install", "text/plain"), - ("test", "make test", "text/x-subunit"), ], - - "lib/replace" : [ ("autogen", "./autogen-waf.sh", "text/plain"), - ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"), - ("make", "make -j", "text/plain"), - ("install", "make install", "text/plain"), - ("test", "make test", "text/plain"), ], - - "lib/tevent" : [ ("configure", "./configure --enable-developer -C ${PREFIX}", "text/plain"), - ("make", "make -j", "text/plain"), - ("install", "make install", "text/plain"), - ("test", "make test", "text/plain"), ], -} - - -def run_cmd(cmd, dir=None, show=None, output=False, checkfail=True, shell=False): - if show is None: - show = options.verbose - if show: - print("Running: '%s' in '%s'" % (cmd, dir)) - if output: - return Popen(cmd, stdout=PIPE, cwd=dir, shell=shell).communicate()[0] - elif checkfail: - return check_call(cmd, cwd=dir, shell=shell) - else: - return call(cmd, cwd=dir, shell=shell) - - -def clone_gitroot(test_master, revision="HEAD"): - run_cmd(["git", "clone", "--shared", gitroot, test_master]) - if revision != "HEAD": - run_cmd(["git", "checkout", revision]) - - -class RetryChecker(object): - """Check whether it is necessary to retry.""" - - def __init__(self, dir): - run_cmd(["git", "remote", "add", "-t", "master", "master", samba_master]) - run_cmd(["git", "fetch", "master"]) - cmd = '''set -e - while :; do - sleep 60 - git describe master/master > old_master.desc - git fetch master - git describe master/master > master.desc - diff old_master.desc master.desc - done - ''' - self.proc = Popen(cmd, shell=True, cwd=self.dir) - - def poll(self): - return self.proc.poll() - - def kill(self): - self.proc.terminate() - self.proc.wait() - self.retry.proc = None - - -class TreeStageBuilder(object): - """Handle building of a particular stage for a tree. - """ - - def __init__(self, tree, name, command, fail_quickly=False): - self.tree = tree - self.name = name - self.command = command - self.fail_quickly = fail_quickly - self.exitcode = None - self.stdin = open(os.devnull, 'r') - - def start(self): - raise NotImplementedError(self.start) - - def poll(self): - self.exitcode = self.proc.poll() - return self.exitcode - - def kill(self): - if self.proc is not None: - try: - run_cmd(["killbysubdir", self.tree.sdir], checkfail=False) - except OSError: - # killbysubdir doesn't exist ? - pass - self.proc.terminate() - self.proc.wait() - self.proc = None - - @property - def failure_reason(self): - raise NotImplementedError(self.failure_reason) - - @property - def failed(self): - return (self.exitcode != 0) - - -class PlainTreeStageBuilder(TreeStageBuilder): - - def start(self): - print '%s: [%s] Running %s' % (self.name, self.name, self.command) - self.proc = Popen(self.command, shell=True, cwd=self.tree.dir, - stdout=self.tree.stdout, stderr=self.tree.stderr, - stdin=self.stdin) - - @property - def failure_reason(self): - return "failed '%s' with exit code %d" % (self.command, self.exitcode) - - -class AbortingTestResult(subunithelper.TestsuiteEnabledTestResult): - - def __init__(self, stage): - super(AbortingTestResult, self).__init__() - self.stage = stage - - def addError(self, test, details=None): - self.stage.proc.terminate() - - def addFailure(self, test, details=None): - self.stage.proc.terminate() - - -class FailureTrackingTestResult(subunithelper.TestsuiteEnabledTestResult): - - def __init__(self, stage): - super(FailureTrackingTestResult, self).__init__() - self.stage = stage - - def addError(self, test, details=None): - if self.stage.failed_test is None: - self.stage.failed_test = ("error", test) - - def addFailure(self, test, details=None): - if self.stage.failed_test is None: - self.stage.failed_test = ("failure", test) - - -class SubunitTreeStageBuilder(TreeStageBuilder): - - def __init__(self, tree, name, command, fail_quickly=False): - super(SubunitTreeStageBuilder, self).__init__(tree, name, command, - fail_quickly) - self.failed_test = None - self.subunit_path = os.path.join(gitroot, - "%s.%s.subunit" % (self.tree.tag, self.name)) - self.tree.logfiles.append( - (self.subunit_path, os.path.basename(self.subunit_path), - "text/x-subunit")) - self.subunit = open(self.subunit_path, 'w') - - formatter = subunithelper.PlainFormatter(False, True, {}) - clients = [formatter, subunit.TestProtocolClient(self.subunit), - FailureTrackingTestResult(self)] - if fail_quickly: - clients.append(AbortingTestResult(self)) - self.subunit_server = subunit.TestProtocolServer( - testtools.MultiTestResult(*clients), - self.subunit) - self.buffered = "" - - def start(self): - print '%s: [%s] Running' % (self.tree.name, self.name) - self.proc = Popen(self.command, shell=True, cwd=self.tree.dir, - stdout=PIPE, stderr=self.tree.stderr, stdin=self.stdin) - fd = self.proc.stdout.fileno() - fl = fcntl.fcntl(fd, fcntl.F_GETFL) - fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) - - def poll(self): - try: - data = self.proc.stdout.read() - except IOError: - return None - else: - self.buffered += data - buffered = "" - for l in self.buffered.splitlines(True): - if l[-1] == "\n": - self.subunit_server.lineReceived(l) - else: - buffered += l - self.buffered = buffered - self.exitcode = self.proc.poll() - if self.exitcode is not None: - self.subunit.close() - return self.exitcode - - @property - def failure_reason(self): - if self.failed_test: - return "failed '%s' with %s in test %s" (self.command, self.failed_test[0], self.failed_test[1]) - else: - return "failed '%s' with exit code %d in unknown test" % (self.command, self.exitcode) - - -class TreeBuilder(object): - '''handle build of one directory''' - - def __init__(self, name, sequence, fail_quickly=False): - self.name = name - self.fail_quickly = fail_quickly - - self.tag = self.name.replace('/', '_') - self.sequence = sequence - self.next = 0 - self.stages = [] - self.stdout_path = os.path.join(gitroot, "%s.stdout" % (self.tag, )) - self.stderr_path = os.path.join(gitroot, "%s.stderr" % (self.tag, )) - self.logfiles = [ - (self.stdout_path, os.path.basename(self.stdout_path), "text/plain"), - (self.stderr_path, os.path.basename(self.stderr_path), "text/plain"), - ] - if options.verbose: - print("stdout for %s in %s" % (self.name, self.stdout_path)) - print("stderr for %s in %s" % (self.name, self.stderr_path)) - if os.path.exists(self.stdout_path): - os.unlink(self.stdout_path) - if os.path.exists(self.stderr_path): - os.unlink(self.stderr_path) - self.stdout = open(self.stdout_path, 'w') - self.stderr = open(self.stderr_path, 'w') - self.sdir = os.path.join(testbase, self.tag) - if name in ['pass', 'fail', 'retry']: - self.dir = self.sdir - else: - self.dir = os.path.join(self.sdir, self.name) - self.prefix = os.path.join(testbase, "prefix", self.tag) - run_cmd(["rm", "-rf", self.sdir]) - cleanup_list.append(self.sdir) - cleanup_list.append(self.prefix) - os.makedirs(self.sdir) - run_cmd(["rm", "-rf", self.sdir]) - clone_gitroot(self.sdir, revision) - self.start_next() - self.exitcode = None - - def start_next(self): - if self.next == len(self.sequence): - print '%s: Completed OK' % self.name - self.done = True - self.stdout.close() - self.stderr.close() - return - (stage_name, cmd, output_mime_type) = self.sequence[self.next] - cmd = cmd.replace("${PREFIX}", "--prefix=%s" % self.prefix) - if output_mime_type == "text/plain": - self.current_stage = PlainTreeStageBuilder(self, stage_name, cmd, - self.fail_quickly) - elif output_mime_type == "text/x-subunit": - self.current_stage = SubunitTreeStageBuilder(self, stage_name, cmd, - self.fail_quickly) - else: - raise Exception("Unknown output mime type %s" % output_mime_type) - self.stages.append(self.current_stage) - self.current_stage.start() - self.next += 1 - - def remove_logs(self): - for path, name, mime_type in self.logfiles: - os.unlink(path) - - def poll(self): - self.exitcode = self.current_stage.poll() - if self.exitcode is not None: - self.current_stage = None - return self.exitcode - - def kill(self): - if self.current_stage is not None: - self.current_stage.kill() - self.current_stage = None - - @property - def failed(self): - return any([s.failed for s in self.stages]) - - @property - def failed_stage(self): - for s in self.stages: - if s.failed: - return s - return s - - @property - def failure_reason(self): - return "%s: [%s] %s" % (self.name, self.failed_stage.name, - self.failed_stage.failure_reason) - - -class BuildList(object): - '''handle build of multiple directories''' - - def __init__(self, tasklist, tasknames): - global tasks - self.tlist = [] - self.tail_proc = None - self.retry = None - if tasknames == ['pass']: - tasks = { 'pass' : [ ("pass", '/bin/true', "text/plain") ]} - if tasknames == ['fail']: - tasks = { 'fail' : [ ("fail", '/bin/false', "text/plain") ]} - if tasknames == []: - tasknames = tasklist - for n in tasknames: - b = TreeBuilder(n, tasks[n], not options.fail_slowly) - self.tlist.append(b) - if options.retry: - self.retry = RetryChecker(self.sdir) - self.need_retry = False - - def kill_kids(self): - if self.tail_proc is not None: - self.tail_proc.terminate() - self.tail_proc.wait() - self.tail_proc = None - if self.retry is not None: - self.retry.kill() - for b in self.tlist: - b.kill() - - def wait_one(self): - while True: - none_running = True - for b in self.tlist: - if b.current_stage is None: - continue - none_running = False - if b.poll() is None: - continue - return b - if options.retry: - ret = self.retry.poll() - if ret: - self.need_retry = True - self.retry = None - return None - if none_running: - return None - time.sleep(0.1) - - def run(self): - while True: - b = self.wait_one() - if options.retry and self.need_retry: - self.kill_kids() - print("retry needed") - return (0, None, None, None, "retry") - if b is None: - break - if b.failed: - self.kill_kids() - return (b.exitcode, b.name, b.failed_stage, b.tag, b.failure_reason) - b.start_next() - self.kill_kids() - return (0, None, None, None, "All OK") - - def tarlogs(self, name=None, fileobj=None): - tar = tarfile.open(name=name, fileobj=fileobj, mode="w:gz") - for b in self.tlist: - for (path, name, mime_type) in b.logfiles: - tar.add(path, arcname=name) - if os.path.exists("autobuild.log"): - tar.add("autobuild.log") - tar.close() - - def attach_logs(self, outer): - f = StringIO() - self.tarlogs(fileobj=f) - msg = MIMEApplication(f.getvalue(), "x-gzip") - msg.add_header('Content-Disposition', 'attachment', - filename="logs.tar.gz") - outer.attach(msg) - - def remove_logs(self): - for b in self.tlist: - b.remove_logs() - - def start_tail(self): - cmd = "tail -f *.stdout *.stderr" - self.tail_proc = Popen(cmd, shell=True, cwd=gitroot) - - -def cleanup(): - if options.nocleanup: - return - print("Cleaning up ....") - for d in cleanup_list: - run_cmd(["rm", "-rf", d]) - - -def find_git_root(p): - '''get to the top of the git repo''' - while p != '/': - if os.path.isdir(os.path.join(p, ".git")): - return p - p = os.path.abspath(os.path.join(p, '..')) - return None - - -def daemonize(logfile): - pid = os.fork() - if pid == 0: # Parent - os.setsid() - pid = os.fork() - if pid != 0: # Actual daemon - os._exit(0) - else: # Grandparent - os._exit(0) - - import resource # Resource usage information. - maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] - if maxfd == resource.RLIM_INFINITY: - maxfd = 1024 # Rough guess at maximum number of open file descriptors. - for fd in range(0, maxfd): - try: - os.close(fd) - except OSError: - pass - os.open(logfile, os.O_RDWR | os.O_CREAT) - os.dup2(0, 1) - os.dup2(0, 2) - - -def rebase_tree(url): - print("Rebasing on %s" % url) - run_cmd(["git", "remote", "add", "-t", "master", "master", url], show=True, - dir=test_master) - run_cmd(["git", "fetch", "master"], show=True, dir=test_master) - if options.fix_whitespace: - run_cmd(["git", "rebase", "--whitespace=fix", "master/master"], - show=True, dir=test_master) - else: - run_cmd(["git", "rebase", "master/master"], show=True, dir=test_master) - diff = run_cmd(["git", "--no-pager", "diff", "HEAD", "master/master"], - dir=test_master, output=True) - if diff == '': - print("No differences between HEAD and master/master - exiting") - sys.exit(0) - -def push_to(url): - print("Pushing to %s" % url) - if options.mark: - run_cmd("EDITOR=script/commit_mark.sh git commit --amend -c HEAD", - dir=test_master, shell=True) - # the notes method doesn't work yet, as metze hasn't allowed - # refs/notes/* in master - # run_cmd("EDITOR=script/commit_mark.sh git notes edit HEAD", - # dir=test_master) - run_cmd(["git", "remote", "add", "-t", "master", "pushto", url], show=True, - dir=test_master) - run_cmd(["git", "push", "pushto", "+HEAD:master"], show=True, - dir=test_master) - -def_testbase = os.getenv("AUTOBUILD_TESTBASE") -if def_testbase is None: - if os.path.exists("/memdisk"): - def_testbase = "/memdisk/%s" % os.getenv('USER') - else: - def_testbase = os.path.join(tempfile.gettempdir(), "autobuild-%s" % os.getenv("USER")) - -parser = OptionParser() -parser.add_option("--repository", help="repository to run tests for", default=None, type=str) -parser.add_option("--revision", help="revision to compile if not HEAD", default=None, type=str) -parser.add_option("--tail", help="show output while running", default=False, action="store_true") -parser.add_option("--keeplogs", help="keep logs", default=False, action="store_true") -parser.add_option("--nocleanup", help="don't remove test tree", default=False, action="store_true") -parser.add_option("--testbase", help="base directory to run tests in (default %s)" % def_testbase, - default=def_testbase) -parser.add_option("--passcmd", help="command to run on success", default=None) -parser.add_option("--verbose", help="show all commands as they are run", - default=False, action="store_true") -parser.add_option("--rebase", help="rebase on the given tree before testing", - default=None, type='str') -parser.add_option("--rebase-master", help="rebase on %s before testing" % samba_master, - default=False, action='store_true') -parser.add_option("--pushto", help="push to a git url on success", - default=None, type='str') -parser.add_option("--push-master", help="push to %s on success" % samba_master_ssh, - default=False, action='store_true') -parser.add_option("--mark", help="add a Tested-By signoff before pushing", - default=False, action="store_true") -parser.add_option("--fix-whitespace", help="fix whitespace on rebase", - default=False, action="store_true") -parser.add_option("--retry", help="automatically retry if master changes", - default=False, action="store_true") -parser.add_option("--email", help="send email to the given address on failure", - type='str', default=None) -parser.add_option("--always-email", help="always send email, even on success", - action="store_true") -parser.add_option("--daemon", help="daemonize after initial setup", - action="store_true") -parser.add_option("--fail-slowly", help="continue running tests even after one has already failed", - action="store_true") - - -def email_failure(blist, exitcode, failed_task, failed_stage, failed_tag, errstr): - '''send an email to options.email about the failure''' - user = os.getenv("USER") - text = ''' -Dear Developer, - -Your autobuild failed when trying to test %s with the following error: - %s - -the autobuild has been abandoned. Please fix the error and resubmit. - -You can see logs of the failed task here: - - http://git.samba.org/%s/samba-autobuild/%s.stdout - http://git.samba.org/%s/samba-autobuild/%s.stderr - -A summary of the autobuild process is here: - - http://git.samba.org/%s/samba-autobuild/autobuild.log - -or you can get full logs of all tasks in this job here: - - http://git.samba.org/%s/samba-autobuild/logs.tar.gz - -The top commit for the tree that was built was: - -%s - -''' % (failed_task, errstr, user, failed_tag, user, failed_tag, user, user, - get_top_commit_msg(test_master)) - - msg = MIMEMultipart() - msg['Subject'] = 'autobuild failure for task %s during %s' % ( - failed_task, failed_stage.name) - msg['From'] = 'autobuild@samba.org' - msg['To'] = options.email - - main = MIMEText(text) - msg.attach(main) - - blist.attach_logs(msg) - - s = smtplib.SMTP() - s.connect() - s.sendmail(msg['From'], [msg['To']], msg.as_string()) - s.quit() - -def email_success(blist): - '''send an email to options.email about a successful build''' - user = os.getenv("USER") - text = ''' -Dear Developer, - -Your autobuild has succeeded. - -''' - - if options.keeplogs: - text += ''' - -you can get full logs of all tasks in this job here: - - http://git.samba.org/%s/samba-autobuild/logs.tar.gz - -''' % user - - text += ''' -The top commit for the tree that was built was: - -%s -''' % (get_top_commit_msg(test_master),) - - msg = MIMEMultipart() - msg['Subject'] = 'autobuild success' - msg['From'] = 'autobuild@samba.org' - msg['To'] = options.email - - main = MIMEText(text, 'plain') - msg.attach(main) - - blist.attach_logs(msg) - - s = smtplib.SMTP() - s.connect() - s.sendmail(msg['From'], [msg['To']], msg.as_string()) - s.quit() - - -(options, args) = parser.parse_args() - -if options.retry: - if not options.rebase_master and options.rebase is None: - raise Exception('You can only use --retry if you also rebase') - -testbase = os.path.join(options.testbase, "b%u" % (os.getpid(),)) -test_master = os.path.join(testbase, "master") - -if options.repository is not None: - repository = options.repository -else: - repository = os.getcwd() - -gitroot = find_git_root(repository) -if gitroot is None: - raise Exception("Failed to find git root under %s" % repository) - -# get the top commit message, for emails -if options.revision is not None: - revision = options.revision -else: - revision = "HEAD" - -def get_top_commit_msg(reporoot): - return run_cmd(["git", "log", "-1"], dir=reporoot, output=True) - -try: - os.makedirs(testbase) -except Exception, reason: - raise Exception("Unable to create %s : %s" % (testbase, reason)) -cleanup_list.append(testbase) - -if options.daemon: - logfile = os.path.join(testbase, "log") - print "Forking into the background, writing progress to %s" % logfile - daemonize(logfile) - -while True: - try: - run_cmd(["rm", "-rf", test_master]) - cleanup_list.append(test_master) - clone_gitroot(test_master, revision) - except: - cleanup() - raise - - try: - if options.rebase is not None: - rebase_tree(options.rebase) - elif options.rebase_master: - rebase_tree(samba_master) - blist = BuildList(tasks, args) - if options.tail: - blist.start_tail() - (exitcode, failed_task, failed_stage, failed_tag, errstr) = blist.run() - if exitcode != 0 or errstr != "retry": - break - cleanup() - except: - cleanup() - raise - -blist.kill_kids() -if options.tail: - print("waiting for tail to flush") - time.sleep(1) - -if exitcode == 0: - print errstr - if options.passcmd is not None: - print("Running passcmd: %s" % options.passcmd) - run_cmd(options.passcmd, dir=test_master, shell=True) - if options.pushto is not None: - push_to(options.pushto) - elif options.push_master: - push_to(samba_master_ssh) - if options.keeplogs: - blist.tarlogs("logs.tar.gz") - print("Logs in logs.tar.gz") - if options.always_email: - email_success(blist) - blist.remove_logs() - cleanup() - print(errstr) -else: - # something failed, gather a tar of the logs - blist.tarlogs("logs.tar.gz") - - if options.email is not None: - email_failure(blist, exitcode, failed_task, failed_stage, failed_tag, - errstr) - - cleanup() - print(errstr) - print("Logs in logs.tar.gz") -sys.exit(exitcode) |