Exporting XML reports, JUnit-compatible.

pull/2225/head
Nicolas "Pixel" Noble 10 years ago
parent 252d251002
commit 5937b5bc5a
  1. 2
      tools/jenkins/docker_run_jenkins.sh
  2. 3
      tools/jenkins/run_jenkins.sh
  3. 31
      tools/run_tests/jobset.py
  4. 18
      tools/run_tests/run_tests.py

@ -42,4 +42,4 @@ cd /var/local/git/grpc
nvm use 0.12 nvm use 0.12
rvm use ruby-2.1 rvm use ruby-2.1
tools/run_tests/prepare_travis.sh tools/run_tests/prepare_travis.sh
tools/run_tests/run_tests.py -t -c $config -l $language tools/run_tests/run_tests.py -t -c $config -l $language -x report.xml

@ -72,6 +72,7 @@ then
DOCKER_CID=`cat docker.cid` DOCKER_CID=`cat docker.cid`
docker kill $DOCKER_CID docker kill $DOCKER_CID
docker cp $DOCKER_CID:/var/local/git/grpc/report.xml $git_root
if [ "$DOCKER_FAILED" == "" ] if [ "$DOCKER_FAILED" == "" ]
then then
echo "Docker finished successfully, deleting the container $DOCKER_CID" echo "Docker finished successfully, deleting the container $DOCKER_CID"
@ -93,7 +94,7 @@ then
/cygdrive/c/nuget/nuget.exe restore vsprojects/grpc.sln /cygdrive/c/nuget/nuget.exe restore vsprojects/grpc.sln
/cygdrive/c/nuget/nuget.exe restore src/csharp/Grpc.sln /cygdrive/c/nuget/nuget.exe restore src/csharp/Grpc.sln
python tools/run_tests/run_tests.py -t -l $language python tools/run_tests/run_tests.py -t -l $language -x report.xml
else else
echo "Unknown platform $platform" echo "Unknown platform $platform"
exit 1 exit 1

@ -34,10 +34,12 @@ import multiprocessing
import os import os
import platform import platform
import signal import signal
import string
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
import time import time
import xml.etree.cElementTree as ET
_DEFAULT_MAX_JOBS = 16 * multiprocessing.cpu_count() _DEFAULT_MAX_JOBS = 16 * multiprocessing.cpu_count()
@ -159,7 +161,7 @@ class JobSpec(object):
class Job(object): class Job(object):
"""Manages one job.""" """Manages one job."""
def __init__(self, spec, bin_hash, newline_on_success, travis): def __init__(self, spec, bin_hash, newline_on_success, travis, xml_report):
self._spec = spec self._spec = spec
self._bin_hash = bin_hash self._bin_hash = bin_hash
self._tempfile = tempfile.TemporaryFile() self._tempfile = tempfile.TemporaryFile()
@ -176,19 +178,27 @@ class Job(object):
self._state = _RUNNING self._state = _RUNNING
self._newline_on_success = newline_on_success self._newline_on_success = newline_on_success
self._travis = travis self._travis = travis
self._xml_test = ET.SubElement(xml_report, 'testcase',
name=self._spec.shortname) if xml_report is not None else None
message('START', spec.shortname, do_newline=self._travis) message('START', spec.shortname, do_newline=self._travis)
def state(self, update_cache): def state(self, update_cache):
"""Poll current state of the job. Prints messages at completion.""" """Poll current state of the job. Prints messages at completion."""
if self._state == _RUNNING and self._process.poll() is not None: if self._state == _RUNNING and self._process.poll() is not None:
elapsed = time.time() - self._start elapsed = time.time() - self._start
self._tempfile.seek(0)
stdout = self._tempfile.read()
filtered_stdout = filter(lambda x: x in string.printable, stdout.decode(errors='ignore'))
if self._xml_test is not None:
self._xml_test.set('time', str(elapsed))
ET.SubElement(self._xml_test, 'system-out').text = filtered_stdout
if self._process.returncode != 0: if self._process.returncode != 0:
self._state = _FAILURE self._state = _FAILURE
self._tempfile.seek(0)
stdout = self._tempfile.read()
message('FAILED', '%s [ret=%d, pid=%d]' % ( message('FAILED', '%s [ret=%d, pid=%d]' % (
self._spec.shortname, self._process.returncode, self._process.pid), self._spec.shortname, self._process.returncode, self._process.pid),
stdout, do_newline=True) stdout, do_newline=True)
if self._xml_test is not None:
ET.SubElement(self._xml_test, 'failure', message='Failure').text
else: else:
self._state = _SUCCESS self._state = _SUCCESS
message('PASSED', '%s [time=%.1fsec]' % (self._spec.shortname, elapsed), message('PASSED', '%s [time=%.1fsec]' % (self._spec.shortname, elapsed),
@ -200,6 +210,9 @@ class Job(object):
stdout = self._tempfile.read() stdout = self._tempfile.read()
message('TIMEOUT', self._spec.shortname, stdout, do_newline=True) message('TIMEOUT', self._spec.shortname, stdout, do_newline=True)
self.kill() self.kill()
if self._xml_test is not None:
ET.SubElement(self._xml_test, 'system-out').text = stdout
ET.SubElement(self._xml_test, 'error', message='Timeout')
return self._state return self._state
def kill(self): def kill(self):
@ -212,7 +225,7 @@ class Jobset(object):
"""Manages one run of jobs.""" """Manages one run of jobs."""
def __init__(self, check_cancelled, maxjobs, newline_on_success, travis, def __init__(self, check_cancelled, maxjobs, newline_on_success, travis,
stop_on_failure, cache): stop_on_failure, cache, xml_report):
self._running = set() self._running = set()
self._check_cancelled = check_cancelled self._check_cancelled = check_cancelled
self._cancelled = False self._cancelled = False
@ -224,6 +237,7 @@ class Jobset(object):
self._cache = cache self._cache = cache
self._stop_on_failure = stop_on_failure self._stop_on_failure = stop_on_failure
self._hashes = {} self._hashes = {}
self._xml_report = xml_report
def start(self, spec): def start(self, spec):
"""Start a job. Return True on success, False on failure.""" """Start a job. Return True on success, False on failure."""
@ -250,7 +264,8 @@ class Jobset(object):
self._running.add(Job(spec, self._running.add(Job(spec,
bin_hash, bin_hash,
self._newline_on_success, self._newline_on_success,
self._travis)) self._travis,
self._xml_report))
except: except:
message('FAILED', spec.shortname) message('FAILED', spec.shortname)
self._cancelled = True self._cancelled = True
@ -324,11 +339,13 @@ def run(cmdlines,
travis=False, travis=False,
infinite_runs=False, infinite_runs=False,
stop_on_failure=False, stop_on_failure=False,
cache=None): cache=None,
xml_report=None):
js = Jobset(check_cancelled, js = Jobset(check_cancelled,
maxjobs if maxjobs is not None else _DEFAULT_MAX_JOBS, maxjobs if maxjobs is not None else _DEFAULT_MAX_JOBS,
newline_on_success, travis, stop_on_failure, newline_on_success, travis, stop_on_failure,
cache if cache is not None else NoCache()) cache if cache is not None else NoCache(),
xml_report)
for cmdline in cmdlines: for cmdline in cmdlines:
if not js.start(cmdline): if not js.start(cmdline):
break break

@ -42,6 +42,7 @@ import re
import subprocess import subprocess
import sys import sys
import time import time
import xml.etree.cElementTree as ET
import jobset import jobset
import watch_dirs import watch_dirs
@ -396,6 +397,8 @@ argp.add_argument('-S', '--stop_on_failure',
action='store_const', action='store_const',
const=True) const=True)
argp.add_argument('-a', '--antagonists', default=0, type=int) argp.add_argument('-a', '--antagonists', default=0, type=int)
argp.add_argument('-x', '--xml_report', default=None, type=str,
help='Generates a JUnit-compatible XML report')
args = argp.parse_args() args = argp.parse_args()
# grab config # grab config
@ -492,7 +495,7 @@ class TestCache(object):
self.parse(json.loads(f.read())) self.parse(json.loads(f.read()))
def _build_and_run(check_cancelled, newline_on_success, travis, cache): def _build_and_run(check_cancelled, newline_on_success, travis, cache, xml_report):
"""Do one pass of building & running tests.""" """Do one pass of building & running tests."""
# build latest sequentially # build latest sequentially
if not jobset.run(build_steps, maxjobs=1, if not jobset.run(build_steps, maxjobs=1,
@ -518,16 +521,24 @@ def _build_and_run(check_cancelled, newline_on_success, travis, cache):
runs_sequence = (itertools.repeat(massaged_one_run) if infinite_runs runs_sequence = (itertools.repeat(massaged_one_run) if infinite_runs
else itertools.repeat(massaged_one_run, runs_per_test)) else itertools.repeat(massaged_one_run, runs_per_test))
all_runs = itertools.chain.from_iterable(runs_sequence) all_runs = itertools.chain.from_iterable(runs_sequence)
root = ET.Element('testsuites') if xml_report else None
testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests') if xml_report else None
if not jobset.run(all_runs, check_cancelled, if not jobset.run(all_runs, check_cancelled,
newline_on_success=newline_on_success, travis=travis, newline_on_success=newline_on_success, travis=travis,
infinite_runs=infinite_runs, infinite_runs=infinite_runs,
maxjobs=args.jobs, maxjobs=args.jobs,
stop_on_failure=args.stop_on_failure, stop_on_failure=args.stop_on_failure,
cache=cache): cache=cache if not xml_report else None,
xml_report=testsuite):
return 2 return 2
finally: finally:
for antagonist in antagonists: for antagonist in antagonists:
antagonist.kill() antagonist.kill()
if xml_report:
tree = ET.ElementTree(root)
tree.write(xml_report, encoding='UTF-8')
if cache: cache.save() if cache: cache.save()
@ -559,7 +570,8 @@ else:
result = _build_and_run(check_cancelled=lambda: False, result = _build_and_run(check_cancelled=lambda: False,
newline_on_success=args.newline_on_success, newline_on_success=args.newline_on_success,
travis=args.travis, travis=args.travis,
cache=test_cache) cache=test_cache,
xml_report=args.xml_report)
if result == 0: if result == 0:
jobset.message('SUCCESS', 'All tests passed', do_newline=True) jobset.message('SUCCESS', 'All tests passed', do_newline=True)
else: else:

Loading…
Cancel
Save