[Piglit] [PATCH 17/19] framework: Add a JUnit backend

Jose Fonseca jfonseca at vmware.com
Fri Aug 29 04:36:58 PDT 2014


On 28/08/14 23:35, Dylan Baker wrote:
> This adds a mostly compliant JUnit backend. It currently passes all of
> its tests and jenkins happily consumes the xml we produce.
>
> This needs to have some refactoring done in profile to make it 100%
> compliant with the junit-7.xsd from upstream jenkins, (although I would
> be comfortable for now pushing with the known non-optimal behavior if
> Jenkins will accept it), because JUnit expects to be given the number of
> tests for the initial metadata block, but we have no way to calculate
> that number until after the run has started.  This is because of the
> flattening pass in profile that flattens the nested directory structure
> into a flat dictionary.
>
> There are two options to solve this problem:
> 1) Flatten all.py and other modules. This is a lot of work and I have
>     many work-in-progress branches to do just hat
> 2) Push the pass out to a public method and call it ahead of time. This
>     seems really hacky to me, and I'd rather not do something that ugly.
>
> Currently this patch just passes 0 for the test count unconditionally,
> jenkins does not seem to have a problem with this.
>
> This includes JUnit.xsd from the jenkins svn repository for piglit
> framework unit testing. This is only used in the piglit python framework
> unit tests.
>
> Signed-off-by: Dylan Baker <dylanx.c.baker at intel.com>
> ---
>   framework/programs/run.py          |   4 ++
>   framework/results.py               |  65 ++++++++++++++++++++++-
>   framework/tests/results_tests.py   |  91 ++++++++++++++++++++++++++++++++
>   framework/tests/schema/junit-7.xsd | 104 +++++++++++++++++++++++++++++++++++++
>   framework/tests/utils.py           |  22 ++++++++
>   5 files changed, 285 insertions(+), 1 deletion(-)
>   create mode 100644 framework/tests/schema/junit-7.xsd
>
> diff --git a/framework/programs/run.py b/framework/programs/run.py
> index 8b7045d..82fc797 100644
> --- a/framework/programs/run.py
> +++ b/framework/programs/run.py
> @@ -221,6 +221,10 @@ def run(input_):
>           options['platform'] = args.platform
>       options['name'] = results.name
>       options['env'] = core.collect_system_info()
> +    # FIXME: this should be the actual count, but profile needs to be
> +    # refactored to make that possible because of the flattening pass that is
> +    # part of profile.run
> +    options['test_count'] = 0

I don't think this attribute is actually necessary in practice.  Jenkins 
will count tests when it processes the JUnit XML and stores it internally.


>
>       # Begin json.
>       backend = framework.results.get_backend(args.backend)(
> diff --git a/framework/results.py b/framework/results.py
> index 741adc9..eabeb4d 100644
> --- a/framework/results.py
> +++ b/framework/results.py
> @@ -26,11 +26,16 @@ import os
>   import sys
>   import abc
>   import threading
> +import posixpath
>   from cStringIO import StringIO
>   try:
>       import simplejson as json
>   except ImportError:
>       import json
> +try:
> +    from lxml import etree
> +except ImportError:
> +    import xml.etree.cElementTree as etree
>
>   import framework.status as status
>
> @@ -43,7 +48,7 @@ __all__ = [
>   ]
>
>   # A list of available backends
> -BACKENDS = ['json']
> +BACKENDS = ['json', 'junit']
>
>   # The current version of the JSON results
>   CURRENT_JSON_VERSION = 1
> @@ -349,6 +354,63 @@ class JSONBackend(Backend):
>               self._write_dict_item(name, data)
>
>
> +class JUnitBackend(Backend):
> +    """ Backend that produces ANT JUnit XML
> +
> +    Based on the following schema:
> +    https://urldefense.proofpoint.com/v1/url?u=https://svn.jenkins-ci.org/trunk/hudson/dtkit/dtkit-format/dtkit-junit-model/src/main/resources/com/thalesgroup/dtkit/junit/model/xsd/junit-7.xsd&k=oIvRg1%2BdGAgOoM1BIlLLqw%3D%3D%0A&r=NMr9uy2iTjWVixC0wOcYCWEIYhfo80qKwRgdodpoDzA%3D%0A&m=214XzeIU6H5%2BhNCoizqfg3xjfdXgSseG1Fn0SVHv9oY%3D%0A&s=3ac0b84d76100043ccbe5216c44bbb5b14e24e3115f989ee5303fb6c246d78ff
> +
> +    """
> +    # TODO: add fsync support
> +
> +    def __init__(self, dest, metadata, **options):
> +        self._file = open(os.path.join(dest, 'results.xml'), 'w')
> +
> +        # Write initial headers and other data that etree cannot write for us
> +        self._file.write('<?xml version="1.0" encoding="UTF-8" ?>\n')
> +        self._file.write('<testsuites>\n')
> +        self._file.write(
> +            '<testsuite name="piglit" tests="{}">\n'.format(
> +                metadata['test_count']))
> +
> +    def finalize(self, metadata=None):
> +        self._file.write('</testsuite>\n')
> +        self._file.write('</testsuites>\n')
> +        self._file.close()

I'm not sure if there isn't one already, but you might want to add a 
"try: ... finally: " construct at the top level to ensure that XML is 
always valid, even if something bad happens halfway through.

> +
> +    def write_test(self, name, data):
> +        # Split the name of the test and the group (what junit refers to as
> +        # classname), and replace piglits '/' seperated groups with '.', after
> +        # replacing any '.' with '_' (so we don't get false groups)
> +        classname, testname = posixpath.split(name)
> +        assert classname
> +        assert testname
> +        classname = classname.replace('.', '_').replace('/', '.')

We might need to replace('\\', '.') too, for Windows' sake.

> +        element = etree.Element('testcase', name=testname, classname=classname,
> +                                time=str(data['time']),
> +                                status=str(data['result']))
> +
> +        # Add stdout
> +        out = etree.SubElement(element, 'system-out')
> +        out.text = data['out']
> +
> +        # Add stderr
> +        err = etree.SubElement(element, 'system-err')
> +        err.text = data['err']
> +
> +        # Add relavent result value, if the result is pass then it doesn't need

relevant

> +        # one of these statuses
> +        if data['result'] == 'skip':
> +            etree.SubElement(element, 'skipped')
> +        elif data['result'] in ['warn', 'fail', 'dmesg-warn', 'dmesg-fail']:
> +            etree.SubElement(element, 'failure')
[...]

Otherwise looks good.

Jose


More information about the Piglit mailing list