From 43349c112b76aef43be61583455be99d564fb150 Mon Sep 17 00:00:00 2001 From: Cleber Rosa Date: Wed, 3 Aug 2016 14:40:18 -0300 Subject: [PATCH] Result: port xUnit result This moves all xUnit code to the plugin file. With that, the core avocado has no knowledge about this plugin, as it should be. Still, the `results.xml` is going to be generated by default, unless the command line option `--xunit-job-result` is set to `off`. Signed-off-by: Cleber Rosa --- avocado/core/job.py | 8 -- avocado/core/xunit.py | 221 ----------------------------------- avocado/plugins/xunit.py | 111 ++++++++++++++++-- selftests/unit/test_xunit.py | 13 ++- setup.py | 5 +- 5 files changed, 116 insertions(+), 242 deletions(-) delete mode 100644 avocado/core/xunit.py diff --git a/avocado/core/job.py b/avocado/core/job.py index ed9e4c55..d9755fc0 100644 --- a/avocado/core/job.py +++ b/avocado/core/job.py @@ -42,7 +42,6 @@ from . import output from . import multiplexer from . import tree from . import test -from . import xunit from . import jsonresult from . import replay from .output import STD_OUTPUT @@ -274,8 +273,6 @@ class Job(object): The basic idea behind the output plugins is: * If there are any active output plugins, use them - * Always add Xunit and JSON plugins outputting to files inside the - results dir * If at the end we only have 2 output plugins (Xunit and JSON), we can add the human output plugin. """ @@ -283,11 +280,6 @@ class Job(object): # If there are any active output plugins, let's use them self._set_output_plugins() - # Setup the xunit plugin to output to the debug directory - xunit_file = os.path.join(self.logdir, 'results.xml') - xunit_plugin = xunit.xUnitResult(self, xunit_file) - self.result_proxy.add_output_plugin(xunit_plugin) - # Setup the json plugin to output to the debug directory json_file = os.path.join(self.logdir, 'results.json') json_plugin = jsonresult.JSONResult(self, json_file) diff --git a/avocado/core/xunit.py b/avocado/core/xunit.py deleted file mode 100644 index 8fc4500b..00000000 --- a/avocado/core/xunit.py +++ /dev/null @@ -1,221 +0,0 @@ -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# See LICENSE for more details. -# -# Copyright: Red Hat Inc. 2014 -# Author: Ruda Moura - -"""xUnit module.""" - -import datetime -import logging -import string -from xml.sax.saxutils import quoteattr - -from .result import Result - - -# We use a subset of the XML format defined in this URL: -# https://svn.jenkins-ci.org/trunk/hudson/dtkit/dtkit-format/dtkit-junit-model/src/main/resources/com/thalesgroup/dtkit/junit/model/xsd/junit-4.xsd - -PRINTABLE = string.ascii_letters + string.digits + string.punctuation + '\n\r ' - - -class XmlResult(object): - - """ - Handles the XML details for xUnit output. - """ - - def __init__(self): - self.xml = [''] - - def _escape_attr(self, attrib): - attrib = ''.join(_ if _ in PRINTABLE else "\\x%02x" % ord(_) - for _ in str(attrib)) - return quoteattr(attrib) - - def _escape_cdata(self, cdata): - cdata = ''.join(_ if _ in PRINTABLE else "\\x%02x" % ord(_) - for _ in str(cdata)) - return cdata.replace(']]>', ']]>]]>' % timestamp - self.testcases = [] - - def end_testsuite(self, tests, errors, failures, skip, tests_total_time): - """ - End of testsuite node. - - :param tests: Number of tests. - :param errors: Number of test errors. - :param failures: Number of test failures. - :param skip: Number of test skipped. - :param total_time: The total time of test execution. - """ - values = {'tests': tests, - 'errors': errors, - 'failures': failures, - 'skip': skip, - 'tests_total_time': tests_total_time} - self.xml.append(self.testsuite.format(**values)) - for tc in self.testcases: - self.xml.append(tc) - self.xml.append('') - - def add_success(self, state): - """ - Add a testcase node of kind succeed. - - :param state: result of :class:`avocado.core.test.Test.get_state`. - :type state: dict - """ - tc = '\t' - values = {'class': self._escape_attr(state.get('class_name', "")), - 'name': self._escape_attr(state.get('name', "")), - 'time': state.get('time_elapsed', -1)} - self.testcases.append(tc.format(**values)) - - def add_skip(self, state): - """ - Add a testcase node of kind skipped. - - :param state: result of :class:`avocado.core.test.Test.get_state`. - :type state: dict - """ - tc = '''\t -\t\t -\t''' - values = {'class': self._escape_attr(state.get('class_name', "")), - 'name': self._escape_attr(state.get('name', "")), - 'time': state.get('time_elapsed', -1)} - self.testcases.append(tc.format(**values)) - - def add_failure(self, state): - """ - Add a testcase node of kind failed. - - :param state: result of :class:`avocado.core.test.Test.get_state`. - :type state: dict - """ - tc = '''\t -\t\t -\t\t -\t''' - values = {'class': self._escape_attr(state.get('class_name', "")), - 'name': self._escape_attr(state.get('name', "")), - 'time': state.get('time_elapsed', -1), - 'type': self._escape_attr(state.get('fail_class', "")), - 'traceback': self._escape_cdata(state.get('traceback', "")), - 'systemout': self._escape_cdata(state.get('text_output', "")), - 'reason': self._escape_attr(str(state.get('fail_reason', "")))} - self.testcases.append(tc.format(**values)) - - def add_error(self, state): - """ - Add a testcase node of kind error. - - :param state: result of :class:`avocado.core.test.Test.get_state`. - :type state: dict - """ - tc = '''\t -\t\t -\t\t -\t''' - values = {'class': self._escape_attr(state.get('class_name', "")), - 'name': self._escape_attr(state.get('name', "")), - 'time': state.get('time_elapsed', -1), - 'type': self._escape_attr(state.get('fail_class', "")), - 'traceback': self._escape_cdata(state.get('traceback', "")), - 'systemout': self._escape_cdata(state.get('text_output', "")), - 'reason': self._escape_attr(str(state.get('fail_reason', "")))} - self.testcases.append(tc.format(**values)) - - -class xUnitResult(Result): - - """ - xUnit Test Result class. - """ - - command_line_arg_name = '--xunit' - - def __init__(self, job, force_xunit_file=None): - """ - Creates an instance of xUnitResult. - - :param job: an instance of :class:`avocado.core.job.Job`. - :param force_xunit_file: Override the output file defined in job.args - """ - Result.__init__(self, job) - if force_xunit_file: - self.output = force_xunit_file - else: - self.output = getattr(self.args, 'xunit_output', '-') - self.log = logging.getLogger("avocado.app") - self.xml = XmlResult() - - def start_tests(self): - """ - Record a start tests event. - """ - Result.start_tests(self) - self.xml.start_testsuite(datetime.datetime.now()) - - def start_test(self, test): - """ - Record a start test event. - """ - Result.start_test(self, test) - - def end_test(self, state): - """ - Record an end test event, accord to the given test status. - - :param state: result of :class:`avocado.core.test.Test.get_state`. - :type state: dict - """ - Result.end_test(self, state) - status = state.get('status', "ERROR") - if status in ('PASS', 'WARN'): - self.xml.add_success(state) - elif status == 'SKIP': - self.xml.add_skip(state) - elif status == 'FAIL': - self.xml.add_failure(state) - else: # ERROR, INTERRUPTED, ... - self.xml.add_error(state) - - def end_tests(self): - """ - Record an end tests event. - """ - Result.end_tests(self) - values = {'tests': self.tests_total, - 'errors': self.errors + self.interrupted, - 'failures': self.failed, - 'skip': self.skipped, - 'tests_total_time': self.tests_total_time} - self.xml.end_testsuite(**values) - contents = self.xml.get_contents() - if self.output == '-': - self.log.debug(contents) - else: - with open(self.output, 'w') as xunit_output: - xunit_output.write(contents) diff --git a/avocado/plugins/xunit.py b/avocado/plugins/xunit.py index 871d6a83..712a1ed5 100644 --- a/avocado/plugins/xunit.py +++ b/avocado/plugins/xunit.py @@ -10,17 +10,109 @@ # See LICENSE for more details. # # Copyright: Red Hat Inc. 2014 -# Author: Ruda Moura +# Authors: Ruda Moura +# Cleber Rosa """xUnit module.""" +import datetime +import logging +import os +import string +from xml.dom.minidom import Document, Element + from avocado.core.parser import FileOrStdoutAction -from avocado.core.plugin_interfaces import CLI -from avocado.core.result import register_test_result_class -from avocado.core.xunit import xUnitResult +from avocado.core.plugin_interfaces import CLI, Result + + +class XUnitResult(Result): + + UNKNOWN = '' + PRINTABLE = string.ascii_letters + string.digits + string.punctuation + '\n\r ' + + def _escape_attr(self, attrib): + attrib = ''.join(_ if _ in self.PRINTABLE else "\\x%02x" % ord(_) + for _ in str(attrib)) + return attrib + + def _escape_cdata(self, cdata): + cdata = ''.join(_ if _ in self.PRINTABLE else "\\x%02x" % ord(_) + for _ in str(cdata)) + return cdata.replace(']]>', ']]>]]>