testing: improved automatic integration + reporting

Merge branches are removed locally before fetching from the remote repo.
Ensures that obsolete branched which where removed from the remote repo
also no longer exist locally. By itself, "git fetch" doesn't do that.

Automatic integration ignores branches which were already merged into
the upstream remote branch. Avoids redundant listing of branches
in the integration report which didn't really need to be merged for the
test run.

Unstaged and staged changes ("stash") are included in the patch report.
The patches are sorted from oldest to most recent. Fixed the fallback
code when a patch has no Subject.

The scripts for reporting tests results are taken from the merged
source code. That ensures that the reporting matches the tests
that were run. The versions from the boot-strapping SyncEvolution
code are the fallback, just in case that automatic integration
fails.

(cherry picked from commit 33bbf5df57)

Conflicts:

	test/generate-html.xsl

In contrast to the master branch, URLs are still absolute because
backporting those changes would have been more difficult.
This commit is contained in:
Patrick Ohly 2011-11-09 10:21:19 +00:00
parent 86cc5f5785
commit 436af6cbd8
2 changed files with 34 additions and 13 deletions

View File

@ -68,7 +68,7 @@ def extractPatchSummary(patchfile):
m = patchsummary.match(line)
if m:
return author + m.group(1)
return os.path.filename(patchfile)
return os.path.basename(patchfile)
def step1(resultdir, result, indents, dir, resulturi, shellprefix, srcdir):
'''Step1 of the result checking, collect system information and
@ -81,6 +81,7 @@ def step1(resultdir, result, indents, dir, resulturi, shellprefix, srcdir):
# include information prepared by GitCopy in runtests.py
result.write(indent+'<source-info>\n')
files = os.listdir(resultdir)
files.sort()
for source in files:
m = re.match('(.*)-source.log', source)
if m:

View File

@ -35,6 +35,17 @@ def abspath(path):
"""Absolute path after expanding vars and user."""
return os.path.abspath(os.path.expanduser(os.path.expandvars(path)))
def findInPaths(name, dirs):
"""find existing item in one of the directories, return None if
no directories give, absolute path to existing item or (as fallbac)
last dir + name"""
fullname = None
for dir in dirs:
fullname = os.path.join(abspath(dir), name)
if os.access(fullname, os.F_OK):
break
return fullname
def del_dir(path):
if not os.access(path, os.F_OK):
return
@ -180,6 +191,11 @@ class Context:
self.lastresultdir = lastresultdir
self.datadir = datadir
def findTestFile(self, name):
"""find item in SyncEvolution test directory, first using the
generated source of the current test, then the bootstrapping code"""
return findInPaths(name, (os.path.join(sync.basedir, "test"), self.datadir))
def runCommand(self, cmdstr, dumpCommands=False):
"""Log and run the given command, throwing an exception if it fails."""
cmd = shlex.split(cmdstr)
@ -303,21 +319,20 @@ class Context:
shell = re.sub(r'\S*valgrind\S*', '', options.shell)
prefix = re.sub(r'\S*valgrind\S*', '', options.testprefix)
uri = self.uri or ("file:///" + self.resultdir)
self.runCommand("resultchecker.py " +self.resultdir+" "+"'"+",".join(run_servers)+"'"+" "+uri +" "+srcdir + " '" + shell + " " + testprefix +" '"+" '" +backenddir +"'");
resultchecker = self.findTestFile("resultchecker.py")
compare = self.findTestFile("compare.xsl")
generateHTML = self.findTestFile("generate-html.xsl")
self.runCommand(resultchecker + " " +self.resultdir+" "+"'"+",".join(run_servers)+"'"+" "+uri +" "+srcdir + " '" + shell + " " + testprefix +" '"+" '" +backenddir +"'");
# transform to html
self.runCommand("xsltproc -o " + self.resultdir + "/cmp_result.xml --stringparam cmp_file " + self.lastresultdir +"/nightly.xml "+self.datadir +"/compare.xsl "+ self.resultdir+"/nightly.xml")
self.runCommand("xsltproc -o " + self.resultdir + "/nightly.html --stringparam cmp_result_file " + self.resultdir + "/cmp_result.xml " + self.datadir +"/generate-html.xsl "+ self.resultdir+"/nightly.xml")
self.runCommand("xsltproc -o " + self.resultdir + "/cmp_result.xml --stringparam cmp_file " + self.lastresultdir +"/nightly.xml "+compare+" "+ self.resultdir+"/nightly.xml")
# produce HTML
self.runCommand("xsltproc -o " + self.resultdir + "/nightly.html --stringparam cmp_result_file " + self.resultdir + "/cmp_result.xml " + generateHTML + " "+ self.resultdir+"/nightly.xml")
# report result by email
if self.recipients:
server = smtplib.SMTP(self.mailhost)
msg=''
try:
resulthtml = open (self.resultdir + "/nightly.html")
line=resulthtml.readline()
while(line!=''):
msg=msg+line
line=resulthtml.readline()
resulthtml.close()
msg = open(self.resultdir + "/nightly.html").read()
except IOError:
msg = '''<html><body><h1>Error: No HTML report generated!</h1></body></html>\n'''
body = StringIO.StringIO()
@ -458,7 +473,6 @@ class GitCopy(GitCheckoutBase, Action):
context.runCommand("(mkdir -p %s && cp -a -l %s/%s %s) || ( rm -rf %s && false )" %
(self.workdir, self.sourcedir, self.name, self.workdir, self.basedir))
os.chdir(self.basedir)
context.runCommand("git fetch && git fetch --tags")
cmd = " && ".join([
'rm -f %(patchlog)s',
'echo "save local changes with stash under a fixed name <rev>-nightly"',
@ -469,12 +483,18 @@ class GitCopy(GitCheckoutBase, Action):
'git checkout -q $( git show-ref --head --hash | head -1 )',
'if git branch | grep -q -w "^..%(revision)s$"; then git branch -D %(revision)s; fi',
'if git branch | grep -q -w "^..nightly$"; then git branch -D nightly; fi',
# fetch
'echo "remove stale merge branches and fetch anew"',
'git branch -r -D $( git branch -r | grep -e "/for-%(revision)s/" ) ',
'git branch -D $( git branch | grep -e "^ for-%(revision)s/" ) ',
'git fetch',
'git fetch --tags',
# pick tag or remote branch
'if git tag | grep -q -w %(revision)s; then base=%(revision)s; git checkout -f -b nightly %(revision)s; ' \
'else base=origin/%(revision)s; git checkout -f -b nightly origin/%(revision)s; fi',
# integrate remote branches first, followed by local ones;
# the hope is that local branches apply cleanly on top of the remote ones
'for patch in $( (git branch -r; git branch) | sed -e "s/^..//" | grep -e "^for-%(revision)s/" -e "/for-%(revision)s/" ); do ' \
'for patch in $( (git branch -r --no-merged origin/%(revision)s; git branch --no-merged origin/%(revision)s) | sed -e "s/^..//" | grep -e "^for-%(revision)s/" -e "/for-%(revision)s/" ); do ' \
'if git merge $patch; then echo >>%(patchlog)s $patch: okay; ' \
'else echo >>%(patchlog)s $patch: failed to apply; git reset --hard; fi; done',
'echo "restore <rev>-nightly and create permanent branch <rev>-nightly-before-<date>-<time> if that fails or new tree is different"',
@ -487,7 +507,7 @@ class GitCopy(GitCheckoutBase, Action):
'git format-patch -o .. $base..nightly',
'(cd ..; for i in [0-9]*.patch; do [ ! -f "$i" ] || mv $i %(name)s-$i; done)',
'git describe --tags --always nightly | sed -e "s/\(.*\)-\([0-9][0-9]*\)-g\(.*\)/\\1 + \\2 commit(s) = \\3/" >>%(patchlog)s',
'( git status | grep -q "working directory clean" && echo "working directory clean" || echo "working directory dirty" ) >>%(patchlog)s'
'( git status | grep -q "working directory clean" && echo "working directory clean" || ( echo "working directory dirty" && ( echo From: nightly testing ; echo Subject: [PATCH 1/1] uncommitted changes ; echo ; git status; echo; git diff HEAD ) >../%(name)s-1000-unstaged.patch ) ) >>%(patchlog)s'
]) % self
context.runCommand(cmd, dumpCommands=True)