results.py revision 11512:060fc1591151
1#!/usr/bin/env python
2#
3# Copyright (c) 2016 ARM Limited
4# All rights reserved
5#
6# The license below extends only to copyright in the software and shall
7# not be construed as granting a license to any other intellectual
8# property including but not limited to intellectual property relating
9# to a hardware implementation of the functionality of the software
10# licensed hereunder.  You may use the software subject to the license
11# terms below provided that you ensure that this notice is replicated
12# unmodified and in its entirety in all distributions of the software,
13# modified or unmodified, in source code or in binary form.
14#
15# Redistribution and use in source and binary forms, with or without
16# modification, are permitted provided that the following conditions are
17# met: redistributions of source code must retain the above copyright
18# notice, this list of conditions and the following disclaimer;
19# redistributions in binary form must reproduce the above copyright
20# notice, this list of conditions and the following disclaimer in the
21# documentation and/or other materials provided with the distribution;
22# neither the name of the copyright holders nor the names of its
23# contributors may be used to endorse or promote products derived from
24# this software without specific prior written permission.
25#
26# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37#
38# Authors: Andreas Sandberg
39
40from abc import ABCMeta, abstractmethod
41import inspect
42import pickle
43import string
44import sys
45
46import xml.etree.cElementTree as ET
47
48class UnitResult(object):
49    """Results of a single test unit.
50
51    A test result can be one of:
52        - STATE_OK: Test ran successfully.
53        - STATE_SKIPPED: The test was skipped.
54        - STATE_ERROR: The test failed to run.
55        - STATE_FAILED: Test ran, but failed.
56
57    The difference between STATE_ERROR and STATE_FAILED is very
58    subtle. In a gem5 context, STATE_ERROR would mean that gem5 failed
59    to start or crashed, while STATE_FAILED would mean that a test
60    failed (e.g., statistics mismatch).
61
62    """
63
64    STATE_OK = 0
65    STATE_SKIPPED = 1
66    STATE_ERROR = 2
67    STATE_FAILURE = 3
68
69    state_names = {
70        STATE_OK : "OK",
71        STATE_SKIPPED : "SKIPPED",
72        STATE_ERROR : "ERROR",
73        STATE_FAILURE : "FAILURE",
74    }
75
76    def __init__(self, name, state, message="", stderr="", stdout="",
77                 runtime=0.0):
78        self.name = name
79        self.state = state
80        self.message = message
81        self.stdout = stdout
82        self.stderr = stderr
83        self.runtime = runtime
84
85    def skipped(self):
86        return self.state == UnitResult.STATE_SKIPPED
87
88    def success(self):
89        return self.state == UnitResult.STATE_OK
90
91    def state_name(self):
92        return UnitResult.state_names[self.state]
93
94    def __nonzero__(self):
95        return self.success() or self.skipped()
96
97    def __str__(self):
98        state_name = self.state_name()
99
100        status = "%s: %s" % (state_name, self.message) if self.message else \
101                 state_name
102
103        return "%s: %s" % (self.name, status)
104
105class TestResult(object):
106    """Results for from a single test consisting of one or more units."""
107
108    def __init__(self, name, results=[]):
109        self.name = name
110        self.results = results
111
112    def success(self):
113        return all([ r.success() for r in self.results])
114
115    def skipped(self):
116        return all([ r.skipped() for r in self.results])
117
118    def changed(self):
119        return self.results[0].success() and self.failed()
120
121    def failed(self):
122        return any([ not r for r in self.results])
123
124    def runtime(self):
125        return sum([ r.runtime for r in self.results ])
126
127    def __nonzero__(self):
128        return all([r for r in self.results])
129
130class ResultFormatter(object):
131    __metaclass__ = ABCMeta
132
133    def __init__(self, fout=sys.stdout, verbose=False):
134        self.verbose = verbose
135        self.fout = fout
136
137    @abstractmethod
138    def dump_suites(self, suites):
139        pass
140
141class Pickle(ResultFormatter):
142    """Save test results as a binary using Python's pickle
143    functionality.
144
145    """
146
147    def __init__(self, **kwargs):
148        super(Pickle, self).__init__(**kwargs)
149
150    def dump_suites(self, suites):
151        pickle.dump(suites, self.fout, pickle.HIGHEST_PROTOCOL)
152
153class Text(ResultFormatter):
154    """Output test results as text."""
155
156    def __init__(self, **kwargs):
157        super(Text, self).__init__(**kwargs)
158
159    def dump_suites(self, suites):
160        fout = self.fout
161        for suite in suites:
162            print >> fout, "--- %s ---" % suite.name
163
164            for t in suite.results:
165                print >> fout, "*** %s" % t
166
167                if t and not self.verbose:
168                    continue
169
170                if t.message:
171                    print >> fout, t.message
172
173                if t.stderr:
174                    print >> fout, t.stderr
175                if t.stdout:
176                    print >> fout, t.stdout
177
178class TextSummary(ResultFormatter):
179    """Output test results as a text summary"""
180
181    def __init__(self, **kwargs):
182        super(TextSummary, self).__init__(**kwargs)
183
184    def test_status(self, suite):
185        if suite.skipped():
186            return "SKIPPED"
187        elif suite.changed():
188            return "CHANGED"
189        elif suite:
190            return "OK"
191        else:
192            return "FAILED"
193
194    def dump_suites(self, suites):
195        fout = self.fout
196        for suite in suites:
197            status = self.test_status(suite)
198            print >> fout, "%s: %s" % (suite.name, status)
199
200class JUnit(ResultFormatter):
201    """Output test results as JUnit XML"""
202
203    def __init__(self, translate_names=True, **kwargs):
204        super(JUnit, self).__init__(**kwargs)
205
206        if translate_names:
207            self.name_table = string.maketrans(
208                "/.",
209                ".-",
210            )
211        else:
212            self.name_table = string.maketrans("", "")
213
214    def convert_unit(self, x_suite, test):
215        x_test = ET.SubElement(x_suite, "testcase",
216                               name=test.name,
217                               time="%f" % test.runtime)
218
219        x_state = None
220        if test.state == UnitResult.STATE_OK:
221            pass
222        elif test.state == UnitResult.STATE_SKIPPED:
223            x_state = ET.SubElement(x_test, "skipped")
224        elif test.state == UnitResult.STATE_FAILURE:
225            x_state = ET.SubElement(x_test, "failure")
226        elif test.state == UnitResult.STATE_ERROR:
227            x_state = ET.SubElement(x_test, "error")
228        else:
229            assert False, "Unknown test state"
230
231        if x_state is not None:
232            if test.message:
233                x_state.set("message", test.message)
234
235            msg = []
236            if test.stderr:
237                msg.append("*** Standard Errror: ***")
238                msg.append(test.stderr)
239            if test.stdout:
240                msg.append("*** Standard Out: ***")
241                msg.append(test.stdout)
242
243            x_state.text = "\n".join(msg)
244
245        return x_test
246
247    def convert_suite(self, x_suites, suite):
248        x_suite = ET.SubElement(x_suites, "testsuite",
249                                name=suite.name.translate(self.name_table),
250                                time="%f" % suite.runtime())
251        errors = 0
252        failures = 0
253        skipped = 0
254
255        for test in suite.results:
256            if test.state != UnitResult.STATE_OK:
257                if test.state == UnitResult.STATE_SKIPPED:
258                    skipped += 1
259                elif test.state == UnitResult.STATE_ERROR:
260                    errors += 1
261                elif test.state == UnitResult.STATE_FAILURE:
262                    failures += 1
263
264            x_test = self.convert_unit(x_suite, test)
265
266        x_suite.set("errors", str(errors))
267        x_suite.set("failures", str(failures))
268        x_suite.set("skipped", str(skipped))
269        x_suite.set("tests", str(len(suite.results)))
270
271        return x_suite
272
273    def convert_suites(self, suites):
274        x_root = ET.Element("testsuites")
275
276        for suite in suites:
277            self.convert_suite(x_root, suite)
278
279        return x_root
280
281    def dump_suites(self, suites):
282        et = ET.ElementTree(self.convert_suites(suites))
283        et.write(self.fout, encoding="UTF-8")
284