summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--scripts/LocalDriver.py37
-rw-r--r--scripts/Util.py61
2 files changed, 88 insertions, 10 deletions
diff --git a/scripts/LocalDriver.py b/scripts/LocalDriver.py
index af6efbd2739..5a839622c3f 100644
--- a/scripts/LocalDriver.py
+++ b/scripts/LocalDriver.py
@@ -301,6 +301,23 @@ class RemoteTestCaseRunner(TestCaseRunner):
current.config.cprops,
current.config.sprops)
+class XmlExporter:
+
+ def __init__(self, results, duration, failures):
+ self.results = results
+ self.duration = duration
+ self.failures = failures
+
+ def save(self, filename, hostname):
+ with open(filename, "w") as out:
+ out.write('<?xml version="1.1" encoding="UTF-8"?>\n')
+ out.write('<testsuites tests="{0}" failures="{1}" time="{2}">\n'.format(len(self.results),
+ self.duration,
+ len(self.failures)))
+ for r in self.results:
+ r.writeAsXml(out, hostname)
+ out.write('</testsuites>\n')
+
class LocalDriver(Driver):
class Current(Driver.Current):
@@ -313,7 +330,7 @@ class LocalDriver(Driver):
@classmethod
def getSupportedArgs(self):
return ("", ["cross=", "workers=", "continue", "loop", "start=", "all", "all-cross", "host=",
- "client=", "server=", "show-durations"])
+ "client=", "server=", "show-durations", "export-xml="])
@classmethod
def usage(self):
@@ -329,6 +346,7 @@ class LocalDriver(Driver):
print("--client=<proxy> The endpoint of the controller to run the client side.")
print("--server=<proxy> The endpoint of the controller to run the server side.")
print("--show-durations Print out the duration of each tests.")
+ print("--export-xml=<file> Export JUnit XML test report.")
def __init__(self, options, *args, **kargs):
Driver.__init__(self, options, *args, **kargs)
@@ -341,6 +359,7 @@ class LocalDriver(Driver):
self.start = 0
self.all = False
self.showDurations = False
+ self.exportToXml = ""
self.clientCtlPrx = ""
self.serverCtlPrx = ""
@@ -350,7 +369,8 @@ class LocalDriver(Driver):
"all-cross" : "allCross",
"client" : "clientCtlPrx",
"server" : "serverCtlPrx",
- "show-durations" : "showDurations" })
+ "show-durations" : "showDurations",
+ "export-xml" : "exportToXml" })
if self.cross:
self.cross = Mapping.getByName(self.cross)
@@ -409,7 +429,12 @@ class LocalDriver(Driver):
Expect.cleanup() # Cleanup processes which might still be around
failures = [r for r in results if not r.isSuccess()]
- m, s = divmod(time.time() - now, 60)
+ duration = time.time() - now
+
+ if self.exportToXml:
+ XmlExporter(results, duration, failures).save(self.exportToXml, os.getenv("NODE_NAME", ""))
+
+ m, s = divmod(duration, 60)
print("")
if m > 0:
print("Ran {0} tests in {1} minutes {2:02.2f} seconds".format(len(results), m, s))
@@ -505,7 +530,7 @@ class LocalDriver(Driver):
if cross and server.getMapping() != cross:
if not self.allCross:
- current.writeln("skipped, no server available for `{0}' mapping".format(cross))
+ current.result.skipped(self.testcase, "no server available for `{0}' mapping".format(cross))
continue
current.writeln("[ running {0} test - {1} ]".format(current.testcase, time.strftime("%x %X")))
@@ -517,7 +542,7 @@ class LocalDriver(Driver):
if cross:
current.writeln("- Mappings: {0},{1}".format(client.getMapping(), server.getMapping()))
if not current.config.canRun(current) or not current.testcase.canRun(current):
- current.writeln("skipped, not supported with this configuration")
+ current.result.skipped(self.testcase, "not supported with this configuration")
return
success = False
@@ -538,7 +563,7 @@ class LocalDriver(Driver):
if confStr:
current.writeln("- Config: {0}".format(confStr))
if not current.config.canRun(current) or not current.testcase.canRun(current):
- current.writeln("skipped, not supported with this configuration")
+ current.result.skipped(self.testcase, "not supported with this configuration")
return
current.testcase._runClientSide(current)
diff --git a/scripts/Util.py b/scripts/Util.py
index 7d83362842d..c792a187257 100644
--- a/scripts/Util.py
+++ b/scripts/Util.py
@@ -9,6 +9,8 @@
import os, sys, runpy, getopt, traceback, types, threading, time, datetime, re, itertools, random, subprocess, shutil, copy, inspect
+import xml.sax.saxutils
+
isPython2 = sys.version_info[0] == 2
if isPython2:
import Queue as queue
@@ -48,6 +50,13 @@ def val(v, escapeQuotes=False, quoteValue=True):
else:
return str(v)
+def escape(s):
+ # Remove backspace characters from the output (they aren't accepted by Jenkins XML parser)
+ if isPython2:
+ return xml.sax.saxutils.escape("".join(ch for ch in unicode(s) if ch != u"\u0008").encode("utf-8"))
+ else:
+ return xml.sax.saxutils.escape("".join(ch for ch in s if ch != u"\u0008"))
+
def getIceSoVersion():
config = open(os.path.join(toplevel, "cpp", "include", "IceUtil", "Config.h"), "r")
intVersion = int(re.search("ICE_INT_VERSION ([0-9]*)", config.read()).group(1))
@@ -1547,13 +1556,14 @@ class Result:
def __init__(self, testsuite, writeToStdout):
self.testsuite = testsuite
- self._skipped = []
self._failed = {}
self._succeeded = []
+ self._skipped = []
self._stdout = StringIO()
self._writeToStdout = writeToStdout
self._testcases = {}
self._duration = 0
+ self._testCaseDuration = 0;
def start(self):
self._duration = time.time()
@@ -1562,11 +1572,13 @@ class Result:
self._duration = time.time() - self._duration
def started(self, testcase):
+ self._testCaseDuration = time.time();
self._start = self._stdout.tell()
def failed(self, testcase, exception):
+ self._testCaseDuration = time.time() - self._testCaseDuration;
self.writeln("\ntest in {0} failed:\n{1}".format(self.testsuite, exception))
- self._testcases[testcase] = (self._start, self._stdout.tell())
+ self._testcases[testcase] = (self._start, self._stdout.tell(), self._testCaseDuration)
self._failed[testcase] = exception
output = self.getOutput(testcase)
for s in ["EADDRINUSE", "Address already in use"]:
@@ -1578,9 +1590,16 @@ class Result:
self.writeln(run("lsof -n -P -i; ps ax"))
def succeeded(self, testcase):
- self._testcases[testcase] = (self._start, self._stdout.tell())
+ self._testCaseDuration = time.time() - self._testCaseDuration;
+ self._testcases[testcase] = (self._start, self._stdout.tell(), self._testCaseDuration)
self._succeeded.append(testcase)
+ def skipped(self, testcase, reason):
+ self._start = self._stdout.tell()
+ self.writeln("skipped, " + reason)
+ self._testcases[testcase] = (self._start, self._stdout.tell(), 0)
+ self._skipped[testcase] = reason
+
def isSuccess(self):
return len(self._failed) == 0
@@ -1593,7 +1612,7 @@ class Result:
def getOutput(self, testcase=None):
if testcase:
if testcase in self._testcases:
- (start, end) = self._testcases[testcase]
+ (start, end, duration) = self._testcases[testcase]
self._stdout.seek(start)
try:
return self._stdout.read(end - start)
@@ -1632,6 +1651,40 @@ class Result:
self._stdout.write(msg)
self._stdout.write("\n")
+ def writeAsXml(self, out, hostname=""):
+ out.write(' <testsuite tests="{0}" failures="{1}" skipped="{2}" time="{3}" name="{4}">\n'
+ .format(len(self._testcases) - 2,
+ len(self._failed),
+ len(self._skipped),
+ self._duration,
+ self.testsuite))
+
+ for (tc, v) in self._testcases.items():
+ if isinstance(tc, TestCase):
+ (s, e, d) = v
+ out.write(' <testcase name="{0}" time="{1}" classname="{2}.{3}">\n'
+ .format(tc,
+ d,
+ self.testsuite.getMapping(),
+ self.testsuite.getId().replace("/", ".")))
+ if tc in self._failed:
+ last = self._failed[tc].strip().split('\n')
+ if len(last) > 0:
+ last = last[len(last) - 1]
+ if hostname:
+ last = "Failed on {0}\n{1}".format(hostname, last)
+ out.write(' <failure message="{1}">{0}</failure>\n'.format(escape(self._failed[tc]), last))
+ elif tc in self._skipped:
+ out.write(' <skipped message="{0}"/>\n'.format(escape(self._skipped[tc])))
+ out.write(' <system-out>\n')
+ if hostname:
+ out.write('Running on {0}\n'.format(hostname))
+ out.write(escape(self.getOutput(tc)))
+ out.write(' </system-out>\n')
+ out.write(' </testcase>\n')
+
+ out.write( '</testsuite>\n')
+
class TestSuite:
def __init__(self, path, testcases=None, options=None, libDirs=None, runOnMainThread=False, chdir=False,