git-svn-id: https://zeitsenke.de/svn/SyncEvolution/trunk@230 15ad00c4-1369-45f4-8270-35d70d36bdcd
472 lines
18 KiB
Python
472 lines
18 KiB
Python
#!/usr/bin/python
|
|
|
|
|
|
"""
|
|
The general idea is that tests to run are defined as a list of
|
|
actions. Each action has a unique name and can depend on other
|
|
actions to have run successfully before.
|
|
|
|
Most work is executed in directories defined and owned by these
|
|
actions. The framework only manages one directory which represents
|
|
the result of each action:
|
|
- an overview file which lists the result of each action
|
|
- for each action a directory with stderr/out and additional files
|
|
that the action can put there
|
|
"""
|
|
|
|
import os, sys, popen2, traceback, re, time, smtplib, optparse
|
|
|
|
def cd(path):
|
|
"""Enter directories, creating them if necessary."""
|
|
if not os.access(path, os.F_OK):
|
|
os.makedirs(path)
|
|
os.chdir(path)
|
|
|
|
def abspath(path):
|
|
"""Absolute path after expanding vars and user."""
|
|
return os.path.abspath(os.path.expanduser(os.path.expandvars(path)))
|
|
|
|
def del_dir(path):
|
|
if not os.access(path, os.F_OK):
|
|
return
|
|
for file in os.listdir(path):
|
|
file_or_dir = os.path.join(path,file)
|
|
if os.path.isdir(file_or_dir) and not os.path.islink(file_or_dir):
|
|
del_dir(file_or_dir) #it's a directory reucursive call to function again
|
|
else:
|
|
os.remove(file_or_dir) #it's a file, delete it
|
|
os.rmdir(path)
|
|
|
|
def copy(f, t):
|
|
file(t, "w").writelines(file(f, "r").readlines())
|
|
|
|
class Action:
|
|
"""Base class for all actions to be performed."""
|
|
|
|
DONE = "0 DONE"
|
|
WARNINGS = "1 WARNINGS"
|
|
FAILED = "2 FAILED"
|
|
TODO = "3 TODO"
|
|
SKIPPED = "4 SKIPPED"
|
|
COMPLETED = (DONE, WARNINGS)
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.status = self.TODO
|
|
self.summary = ""
|
|
self.dependencies = []
|
|
|
|
def execute(self):
|
|
"""Runs action. Throws an exeception if anything fails.
|
|
Will be called by tryexecution() with stderr/stdout redirected into a file
|
|
and the current directory set to an empty temporary directory.
|
|
"""
|
|
raise Exception("not implemented")
|
|
|
|
def tryexecution(self, step, logs):
|
|
"""wrapper around execute which handles exceptions, directories and stdout"""
|
|
if logs:
|
|
fd = -1
|
|
oldstdout = os.dup(1)
|
|
oldstderr = os.dup(2)
|
|
oldout = sys.stdout
|
|
olderr = sys.stderr
|
|
cwd = os.getcwd()
|
|
try:
|
|
subdirname = "%d-%s" % (step, self.name)
|
|
del_dir(subdirname)
|
|
sys.stderr.flush()
|
|
sys.stdout.flush()
|
|
cd(subdirname)
|
|
if logs:
|
|
fd = os.open("output.txt", os.O_WRONLY|os.O_CREAT|os.O_TRUNC)
|
|
os.dup2(fd, 1)
|
|
os.dup2(fd, 2)
|
|
sys.stdout = os.fdopen(fd, "w")
|
|
sys.stderr = sys.stdout
|
|
print "=== starting %s ===" % (self.name)
|
|
self.execute()
|
|
self.status = Action.DONE
|
|
self.summary = "okay"
|
|
except Exception, inst:
|
|
traceback.print_exc()
|
|
self.status = Action.FAILED
|
|
self.summary = str(inst)
|
|
|
|
print "\n=== %s: %s ===" % (self.name, self.status)
|
|
sys.stdout.flush()
|
|
os.chdir(cwd)
|
|
if logs:
|
|
if fd >= 0:
|
|
os.close(fd)
|
|
os.dup2(oldstdout, 1)
|
|
os.dup2(oldstderr, 2)
|
|
sys.stderr = olderr
|
|
sys.stdout = oldout
|
|
os.close(oldstdout)
|
|
os.close(oldstderr)
|
|
return self.status
|
|
|
|
class Context:
|
|
"""Provides services required by actions and handles running them."""
|
|
|
|
def __init__(self, tmpdir, resultdir, workdir, mailtitle, sender, recipients, enabled, skip, nologs):
|
|
# preserve normal stdout because stdout/stderr will be redirected
|
|
self.out = os.fdopen(os.dup(1))
|
|
self.todo = []
|
|
self.actions = {}
|
|
self.tmpdir = abspath(tmpdir)
|
|
self.resultdir = abspath(resultdir)
|
|
self.workdir = abspath(workdir)
|
|
self.summary = []
|
|
self.mailtitle = mailtitle
|
|
self.sender = sender
|
|
self.recipients = recipients
|
|
self.enabled = enabled
|
|
self.skip = skip
|
|
self.nologs = nologs
|
|
|
|
def runCommand(self, cmd):
|
|
"""Log and run the given command, throwing an exception if it fails."""
|
|
print "%s: %s" % (os.getcwd(), cmd)
|
|
sys.stdout.flush()
|
|
result = os.system(cmd)
|
|
if result != 0:
|
|
raise Exception("%s: failed (return code %d)" % (cmd, result))
|
|
|
|
def add(self, action):
|
|
"""Add an action for later execution. Order is important, fifo..."""
|
|
self.todo.append(action)
|
|
self.actions[action.name] = action
|
|
|
|
def required(self, actionname):
|
|
"""Returns true if the action is required by one which is enabled."""
|
|
if actionname in self.enabled:
|
|
return True
|
|
for action in self.todo:
|
|
if actionname in action.dependencies and self.required(action.name):
|
|
return True
|
|
return False
|
|
|
|
def execute(self):
|
|
cd(self.resultdir)
|
|
s = file("summary.txt", "w+")
|
|
status = Action.DONE
|
|
|
|
step = 0
|
|
while len(self.todo) > 0:
|
|
try:
|
|
step = step + 1
|
|
|
|
# get action
|
|
action = self.todo.pop(0)
|
|
|
|
# check whether it actually needs to be executed
|
|
if self.enabled and \
|
|
not action.name in self.enabled and \
|
|
not self.required(action.name):
|
|
# disabled
|
|
action.status = Action.SKIPPED
|
|
self.summary.append("%s skipped: disabled in configuration" % (action.name))
|
|
elif action.name in self.skip:
|
|
# assume that it was done earlier
|
|
action.status = Action.SKIPPED
|
|
self.summary.append("%s assumed to be done: requested by configuration" % (action.name))
|
|
else:
|
|
# check dependencies
|
|
for depend in action.dependencies:
|
|
if not self.actions[depend].status in Action.COMPLETED:
|
|
action.status = Action.SKIPPED
|
|
self.summary.append("%s skipped: %s has not been executed" % (action.name, depend))
|
|
break
|
|
|
|
if action.status == Action.SKIPPED:
|
|
continue
|
|
|
|
# execute it
|
|
action.tryexecution(step, not self.nologs)
|
|
if action.status > status:
|
|
status = action.status
|
|
if action.status == Action.FAILED:
|
|
self.summary.append("%s: %s" % (action.name, action.summary))
|
|
elif action.status == Action.WARNINGS:
|
|
self.summary.append("%s done, but check the warnings" % action.name)
|
|
else:
|
|
self.summary.append("%s successful" % action.name)
|
|
except Exception, inst:
|
|
traceback.print_exc()
|
|
self.summary.append("%s failed: %s" % (action.name, inst))
|
|
|
|
# update summary
|
|
s.write("%s\n" % ("\n".join(self.summary)))
|
|
s.close()
|
|
|
|
# report result by email
|
|
if self.recipients:
|
|
server = smtplib.SMTP("localhost")
|
|
msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s" % \
|
|
(self.sender,
|
|
", ".join(self.recipients),
|
|
self.mailtitle,
|
|
"\n".join(self.summary))
|
|
failed = server.sendmail(self.sender, self.recipients, msg)
|
|
if failed:
|
|
print "could not send to: %s" % (failed)
|
|
sys.exit(1)
|
|
else:
|
|
print "\n".join(self.summary), "\n"
|
|
|
|
if status in Action.COMPLETED:
|
|
sys.exit(0)
|
|
else:
|
|
sys.exit(1)
|
|
|
|
# must be set before instantiating some of the following classes
|
|
context = None
|
|
|
|
class CVSCheckout(Action):
|
|
"""Does a CVS checkout (if directory does not exist yet) or an update (if it does)."""
|
|
|
|
def __init__(self, name, workdir, runner, cvsroot, module, revision):
|
|
"""workdir defines the directory to do the checkout in,
|
|
cvsroot the server, module the path to the files,
|
|
revision the tag to checkout"""
|
|
Action.__init__(self,name)
|
|
self.workdir = workdir
|
|
self.runner = runner
|
|
self.cvsroot = cvsroot
|
|
self.module = module
|
|
self.revision = revision
|
|
self.basedir = os.path.join(abspath(workdir), module)
|
|
|
|
def execute(self):
|
|
cd(self.workdir)
|
|
if os.access(self.module, os.F_OK):
|
|
os.chdir(self.module)
|
|
context.runCommand("cvs update -d -r %s" % (self.revision))
|
|
elif self.revision == "HEAD":
|
|
context.runCommand("cvs -d %s checkout %s" % (self.cvsroot, self.module))
|
|
os.chdir(self.module)
|
|
else:
|
|
context.runCommand("cvs -d %s checkout -r %s %s" % (self.cvsroot, self.revision, self.module))
|
|
os.chdir(self.module)
|
|
if os.access("autogen.sh", os.F_OK):
|
|
context.runCommand("%s ./autogen.sh" % (self.runner))
|
|
|
|
class ClientCheckout(CVSCheckout):
|
|
def __init__(self, name, revision):
|
|
"""checkout C++ client source code and apply all patches"""
|
|
CVSCheckout.__init__(self,
|
|
name, context.workdir, options.shell,
|
|
":ext:pohly@cvs.forge.objectweb.org:/cvsroot/sync4j",
|
|
"3x/client-api/native",
|
|
revision)
|
|
|
|
def execute(self):
|
|
# undo patches before upgrading
|
|
try:
|
|
os.chdir(self.basedir)
|
|
context.runCommand("patcher -B")
|
|
except:
|
|
pass
|
|
# checkout/upgrade
|
|
CVSCheckout.execute(self)
|
|
#patch again
|
|
context.runCommand("patcher -A")
|
|
|
|
|
|
class AutotoolsBuild(Action):
|
|
def __init__(self, name, src, configargs, runner, dependencies):
|
|
"""Runs configure from the src directory with the given arguments.
|
|
runner is a prefix for the configure command and can be used to setup the
|
|
environment."""
|
|
Action.__init__(self, name)
|
|
self.src = src
|
|
self.configargs = configargs
|
|
self.runner = runner
|
|
self.dependencies = dependencies
|
|
self.installdir = os.path.join(context.tmpdir, name + "-install")
|
|
self.builddir = os.path.join(context.tmpdir, name + "-build")
|
|
|
|
def execute(self):
|
|
del_dir(self.builddir)
|
|
cd(self.builddir)
|
|
context.runCommand("%s %s/configure --prefix=%s %s" % (self.runner, self.src, self.installdir, self.configargs))
|
|
context.runCommand("%s make install" % (self.runner))
|
|
|
|
|
|
class SyncEvolutionTest(Action):
|
|
def __init__(self, name, build, serverlogs, runner, tests, testenv):
|
|
"""Execute TestEvolution in the for all (empty tests) or the
|
|
selected tests."""
|
|
Action.__init__(self, name)
|
|
self.srcdir = os.path.join(build.builddir, "src")
|
|
self.serverlogs = serverlogs
|
|
self.runner = runner
|
|
self.tests = tests
|
|
self.testenv = testenv
|
|
self.dependencies.append(build.name)
|
|
|
|
def execute(self):
|
|
resdir = os.getcwd()
|
|
os.chdir(self.srcdir)
|
|
try:
|
|
basecmd = "%s TEST_EVOLUTION_LOG=%s %s ./TestEvolution" % (self.testenv, self.serverlogs, self.runner);
|
|
if self.tests:
|
|
ex = None
|
|
for test in self.tests:
|
|
try:
|
|
context.runCommand("%s %s" % (basecmd, test))
|
|
except Exception, inst:
|
|
if not ex:
|
|
ex = inst
|
|
if ex:
|
|
raise ex
|
|
else:
|
|
context.runCommand(basecmd)
|
|
finally:
|
|
tocopy = re.compile(r'.*\.log')
|
|
for f in os.listdir(self.srcdir):
|
|
if tocopy.match(f):
|
|
copy(f, os.path.join(resdir, f))
|
|
|
|
|
|
|
|
###################################################################
|
|
# Configuration part
|
|
###################################################################
|
|
|
|
parser = optparse.OptionParser()
|
|
parser.add_option("-e", "--enable",
|
|
action="append", type="string", dest="enabled",
|
|
help="use this to enable specific actions instead of executing all of them (can be used multiple times)")
|
|
parser.add_option("-n", "--no-logs",
|
|
action="store_true", dest="nologs",
|
|
help="print to stdout/stderr directly instead of redirecting into log files")
|
|
parser.add_option("-l", "--list",
|
|
action="store_true", dest="list",
|
|
help="list all available actions")
|
|
parser.add_option("-s", "--skip",
|
|
action="append", type="string", dest="skip", default=[],
|
|
help="instead of executing this action assume that it completed earlier (can be used multiple times)")
|
|
parser.add_option("", "--tmp",
|
|
type="string", dest="tmpdir", default="/tmp/runtests",
|
|
help="temporary directory for intermediate files")
|
|
parser.add_option("", "--workdir",
|
|
type="string", dest="workdir", default="/scratch/work/runtests",
|
|
help="directory for files which might be reused between runs")
|
|
parser.add_option("", "--resultdir",
|
|
type="string", dest="resultdir", default="/scratch/work/runtests-" + time.strftime("%Y-%m-%d-%M"),
|
|
help="directory for log files and results")
|
|
parser.add_option("", "--shell",
|
|
type="string", dest="shell", default="",
|
|
help="a prefix which is put in front of a command to execute it (can be used for e.g. run_garnome)")
|
|
parser.add_option("", "--synthesis",
|
|
type="string", dest="synthesisdir", default="/scratch/SyServ_Pro_linux_2.1.1.28",
|
|
help="directory with Synthesis installation")
|
|
parser.add_option("", "--funambol",
|
|
type="string", dest="funamboldir", default="/scratch/Funambol",
|
|
help="directory with Funambol installation")
|
|
parser.add_option("", "--from",
|
|
type="string", dest="sender",
|
|
help="sender of email if recipients are also specified")
|
|
parser.add_option("", "--to",
|
|
action="append", type="string", dest="recipients",
|
|
help="recipient of result email (option can be given multiple times)")
|
|
parser.add_option("", "--subject",
|
|
type="string", dest="subject", default="SyncML Tests " + time.strftime("%Y-%m-%d"),
|
|
help="subject of result email (default is \"SyncML Tests <date>\"")
|
|
|
|
(options, args) = parser.parse_args()
|
|
if options.recipients and not options.sender:
|
|
print "sending email also requires sender argument"
|
|
sys.exit(1)
|
|
|
|
context = Context(options.tmpdir, options.resultdir, options.workdir,
|
|
options.subject, options.sender, options.recipients,
|
|
options.enabled, options.skip, options.nologs)
|
|
|
|
class SyncEvolutionCheckout(CVSCheckout):
|
|
def __init__(self, name, revision):
|
|
"""checkout SyncEvolution"""
|
|
CVSCheckout.__init__(self,
|
|
name, context.workdir, options.shell,
|
|
":ext:pohly@sync4jevolution.cvs.sourceforge.net:/cvsroot/sync4jevolution",
|
|
"sync4jevolution",
|
|
revision)
|
|
|
|
class SyncEvolutionBuild(AutotoolsBuild):
|
|
def execute(self):
|
|
AutotoolsBuild.execute(self)
|
|
os.chdir("src")
|
|
context.runCommand("%s make test" % (self.runner))
|
|
|
|
clienthead = ClientCheckout("client-api-head", "HEAD")
|
|
context.add(clienthead)
|
|
synchead = SyncEvolutionCheckout("syncevolution-head", "HEAD")
|
|
context.add(synchead)
|
|
compilehead = SyncEvolutionBuild("compile-head",
|
|
synchead.basedir,
|
|
"--disable-shared CXXFLAGS=-g --with-sync4j-src=%s" % (clienthead.basedir),
|
|
options.shell,
|
|
[ clienthead.name, synchead.name ])
|
|
context.add(compilehead)
|
|
|
|
evolutiontest = SyncEvolutionTest("evolution", compilehead,
|
|
"", options.shell,
|
|
[ "ContactSource", "CalendarSource", "TaskSource" ],
|
|
"")
|
|
context.add(evolutiontest)
|
|
|
|
scheduleworldtest = SyncEvolutionTest("scheduleworld", compilehead,
|
|
"", options.shell,
|
|
[],
|
|
"TEST_EVOLUTION_SERVER=scheduleworld TEST_EVOLUTION_FAILURES=ContactSync::testItems,ContactSync::testTwinning,CalendarSync::testDeleteAllRefresh,CalendarSync::testItems,TaskSync::testDeleteAllRefresh,TaskSync::testItems,TaskSync::testTwinning")
|
|
context.add(scheduleworldtest)
|
|
|
|
class SynthesisTest(SyncEvolutionTest):
|
|
def __init__(self, name, build, synthesisdir, runner):
|
|
SyncEvolutionTest.__init__(self, name, build, os.path.join(synthesisdir, "logs"),
|
|
runner, [ "ContactSync", "ContactStress" ], "TEST_EVOLUTION_SERVER=synthesis")
|
|
self.synthesisdir = synthesisdir
|
|
self.dependencies.append(evolutiontest.name)
|
|
|
|
def execute(self):
|
|
context.runCommand("synthesis start \"%s\"" % (self.synthesisdir))
|
|
time.sleep(5)
|
|
try:
|
|
SyncEvolutionTest.execute(self)
|
|
finally:
|
|
context.runCommand("synthesis stop \"%s\"" % (self.synthesisdir))
|
|
|
|
synthesis = SynthesisTest("synthesis", compilehead,
|
|
options.synthesisdir,
|
|
options.shell)
|
|
context.add(synthesis)
|
|
|
|
class FunambolTest(SyncEvolutionTest):
|
|
def __init__(self, name, build, funamboldir, runner):
|
|
SyncEvolutionTest.__init__(self, name, build, os.path.join(funamboldir, "ds-server", "logs", "funambol_ds.log"),
|
|
runner, [ "ContactSync", "ContactStress" ], "TEST_EVOLUTION_DELAY=10 TEST_EVOLUTION_SERVER=funambol")
|
|
self.funamboldir = funamboldir
|
|
self.dependencies.append(evolutiontest.name)
|
|
|
|
def execute(self):
|
|
context.runCommand("%s/tools/bin/funambol.sh start" % (self.funamboldir))
|
|
time.sleep(5)
|
|
try:
|
|
SyncEvolutionTest.execute(self)
|
|
finally:
|
|
context.runCommand("%s/tools/bin/funambol.sh stop" % (self.funamboldir))
|
|
|
|
funambol = FunambolTest("funambol", compilehead,
|
|
options.funamboldir,
|
|
options.shell)
|
|
context.add(funambol)
|
|
|
|
if options.list:
|
|
for action in context.todo:
|
|
print action.name
|
|
else:
|
|
context.execute()
|