summaryrefslogtreecommitdiff
path: root/tests/testing/results.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/testing/results.py')
-rw-r--r--tests/testing/results.py271
1 files changed, 271 insertions, 0 deletions
diff --git a/tests/testing/results.py b/tests/testing/results.py
new file mode 100644
index 000000000..0c46c9665
--- /dev/null
+++ b/tests/testing/results.py
@@ -0,0 +1,271 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2016 ARM Limited
+# All rights reserved
+#
+# The license below extends only to copyright in the software and shall
+# not be construed as granting a license to any other intellectual
+# property including but not limited to intellectual property relating
+# to a hardware implementation of the functionality of the software
+# licensed hereunder. You may use the software subject to the license
+# terms below provided that you ensure that this notice is replicated
+# unmodified and in its entirety in all distributions of the software,
+# modified or unmodified, in source code or in binary form.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met: redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer;
+# redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution;
+# neither the name of the copyright holders nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Authors: Andreas Sandberg
+
+from abc import ABCMeta, abstractmethod
+import inspect
+import pickle
+import string
+import sys
+
+import xml.etree.cElementTree as ET
+
+class UnitResult(object):
+ """Results of a single test unit.
+
+ A test result can be one of:
+ - STATE_OK: Test ran successfully.
+ - STATE_SKIPPED: The test was skipped.
+ - STATE_ERROR: The test failed to run.
+ - STATE_FAILED: Test ran, but failed.
+
+ The difference between STATE_ERROR and STATE_FAILED is very
+ subtle. In a gem5 context, STATE_ERROR would mean that gem5 failed
+ to start or crashed, while STATE_FAILED would mean that a test
+ failed (e.g., statistics mismatch).
+
+ """
+
+ STATE_OK = 0
+ STATE_SKIPPED = 1
+ STATE_ERROR = 2
+ STATE_FAILURE = 3
+
+ state_names = {
+ STATE_OK : "OK",
+ STATE_SKIPPED : "SKIPPED",
+ STATE_ERROR : "ERROR",
+ STATE_FAILURE : "FAILURE",
+ }
+
+ def __init__(self, name, state, message="", stderr="", stdout="",
+ runtime=0.0):
+ self.name = name
+ self.state = state
+ self.message = message
+ self.stdout = stdout
+ self.stderr = stderr
+ self.runtime = runtime
+
+ def skipped(self):
+ return self.state == UnitResult.STATE_SKIPPED
+
+ def success(self):
+ return self.state == UnitResult.STATE_OK
+
+ def state_name(self):
+ return UnitResult.state_names[self.state]
+
+ def __nonzero__(self):
+ return self.success() or self.skipped()
+
+ def __str__(self):
+ state_name = self.state_name()
+
+ status = "%s: %s" % (state_name, self.message) if self.message else \
+ state_name
+
+ return "%s: %s" % (self.name, status)
+
+class TestResult(object):
+ """Results for from a single test consisting of one or more units."""
+
+ def __init__(self, name, results=[]):
+ self.name = name
+ self.results = results
+
+ def success(self):
+ return all([ r.success() for r in self.results])
+
+ def skipped(self):
+ return all([ r.skipped() for r in self.results])
+
+ def failed(self):
+ return any([ not r for r in self.results])
+
+ def runtime(self):
+ return sum([ r.runtime for r in self.results ])
+
+ def __nonzero__(self):
+ return all([r for r in self.results])
+
+class ResultFormatter(object):
+ __metaclass__ = ABCMeta
+
+ def __init__(self, fout=sys.stdout, verbose=False):
+ self.verbose = verbose
+ self.fout = fout
+
+ @abstractmethod
+ def dump_suites(self, suites):
+ pass
+
+class Pickle(ResultFormatter):
+ """Save test results as a binary using Python's pickle
+ functionality.
+
+ """
+
+ def __init__(self, **kwargs):
+ super(Pickle, self).__init__(**kwargs)
+
+ def dump_suites(self, suites):
+ pickle.dump(suites, self.fout, pickle.HIGHEST_PROTOCOL)
+
+class Text(ResultFormatter):
+ """Output test results as text."""
+
+ def __init__(self, **kwargs):
+ super(Text, self).__init__(**kwargs)
+
+ def dump_suites(self, suites):
+ fout = self.fout
+ for suite in suites:
+ print >> fout, "--- %s ---" % suite.name
+
+ for t in suite.results:
+ print >> fout, "*** %s" % t
+
+ if t and not self.verbose:
+ continue
+
+ if t.message:
+ print >> fout, t.message
+
+ if t.stderr:
+ print >> fout, t.stderr
+ if t.stdout:
+ print >> fout, t.stdout
+
+class TextSummary(ResultFormatter):
+ """Output test results as a text summary"""
+
+ def __init__(self, **kwargs):
+ super(TextSummary, self).__init__(**kwargs)
+
+ def dump_suites(self, suites):
+ fout = self.fout
+ for suite in suites:
+ status = "SKIPPED" if suite.skipped() else \
+ ("OK" if suite else "FAILED")
+ print >> fout, "%s: %s" % (suite.name, status)
+
+class JUnit(ResultFormatter):
+ """Output test results as JUnit XML"""
+
+ def __init__(self, translate_names=True, **kwargs):
+ super(JUnit, self).__init__(**kwargs)
+
+ if translate_names:
+ self.name_table = string.maketrans(
+ "/.",
+ ".-",
+ )
+ else:
+ self.name_table = string.maketrans("", "")
+
+ def convert_unit(self, x_suite, test):
+ x_test = ET.SubElement(x_suite, "testcase",
+ name=test.name,
+ time="%f" % test.runtime)
+
+ x_state = None
+ if test.state == UnitResult.STATE_OK:
+ pass
+ elif test.state == UnitResult.STATE_SKIPPED:
+ x_state = ET.SubElement(x_test, "skipped")
+ elif test.state == UnitResult.STATE_FAILURE:
+ x_state = ET.SubElement(x_test, "failure")
+ elif test.state == UnitResult.STATE_ERROR:
+ x_state = ET.SubElement(x_test, "error")
+ else:
+ assert False, "Unknown test state"
+
+ if x_state is not None:
+ if test.message:
+ x_state.set("message", test.message)
+
+ msg = []
+ if test.stderr:
+ msg.append("*** Standard Errror: ***")
+ msg.append(test.stderr)
+ if test.stdout:
+ msg.append("*** Standard Out: ***")
+ msg.append(test.stdout)
+
+ x_state.text = "\n".join(msg)
+
+ return x_test
+
+ def convert_suite(self, x_suites, suite):
+ x_suite = ET.SubElement(x_suites, "testsuite",
+ name=suite.name.translate(self.name_table),
+ time="%f" % suite.runtime())
+ errors = 0
+ failures = 0
+ skipped = 0
+
+ for test in suite.results:
+ if test.state != UnitResult.STATE_OK:
+ if test.state == UnitResult.STATE_SKIPPED:
+ skipped += 1
+ elif test.state == UnitResult.STATE_ERROR:
+ errors += 1
+ elif test.state == UnitResult.STATE_FAILURE:
+ failures += 1
+
+ x_test = self.convert_unit(x_suite, test)
+
+ x_suite.set("errors", str(errors))
+ x_suite.set("failures", str(failures))
+ x_suite.set("skipped", str(skipped))
+ x_suite.set("tests", str(len(suite.results)))
+
+ return x_suite
+
+ def convert_suites(self, suites):
+ x_root = ET.Element("testsuites")
+
+ for suite in suites:
+ self.convert_suite(x_root, suite)
+
+ return x_root
+
+ def dump_suites(self, suites):
+ et = ET.ElementTree(self.convert_suites(suites))
+ et.write(self.fout, encoding="UTF-8")