[lit] Implement support of per test timeout in lit.
[oota-llvm.git] / utils / lit / lit / Test.py
1 import os
2 from xml.sax.saxutils import escape
3 from json import JSONEncoder
4
5 # Test result codes.
6
7 class ResultCode(object):
8     """Test result codes."""
9
10     # We override __new__ and __getnewargs__ to ensure that pickling still
11     # provides unique ResultCode objects in any particular instance.
12     _instances = {}
13     def __new__(cls, name, isFailure):
14         res = cls._instances.get(name)
15         if res is None:
16             cls._instances[name] = res = super(ResultCode, cls).__new__(cls)
17         return res
18     def __getnewargs__(self):
19         return (self.name, self.isFailure)
20
21     def __init__(self, name, isFailure):
22         self.name = name
23         self.isFailure = isFailure
24
25     def __repr__(self):
26         return '%s%r' % (self.__class__.__name__,
27                          (self.name, self.isFailure))
28
29 PASS        = ResultCode('PASS', False)
30 FLAKYPASS   = ResultCode('FLAKYPASS', False)
31 XFAIL       = ResultCode('XFAIL', False)
32 FAIL        = ResultCode('FAIL', True)
33 XPASS       = ResultCode('XPASS', True)
34 UNRESOLVED  = ResultCode('UNRESOLVED', True)
35 UNSUPPORTED = ResultCode('UNSUPPORTED', False)
36 TIMEOUT     = ResultCode('TIMEOUT', True)
37
38 # Test metric values.
39
40 class MetricValue(object):
41     def format(self):
42         """
43         format() -> str
44
45         Convert this metric to a string suitable for displaying as part of the
46         console output.
47         """
48         raise RuntimeError("abstract method")
49
50     def todata(self):
51         """
52         todata() -> json-serializable data
53
54         Convert this metric to content suitable for serializing in the JSON test
55         output.
56         """
57         raise RuntimeError("abstract method")
58
59 class IntMetricValue(MetricValue):
60     def __init__(self, value):
61         self.value = value
62
63     def format(self):
64         return str(self.value)
65
66     def todata(self):
67         return self.value
68
69 class RealMetricValue(MetricValue):
70     def __init__(self, value):
71         self.value = value
72
73     def format(self):
74         return '%.4f' % self.value
75
76     def todata(self):
77         return self.value
78
79 class JSONMetricValue(MetricValue):
80     """
81         JSONMetricValue is used for types that are representable in the output
82         but that are otherwise uninterpreted.
83     """
84     def __init__(self, value):
85         # Ensure the value is a serializable by trying to encode it.
86         # WARNING: The value may change before it is encoded again, and may
87         #          not be encodable after the change.
88         try:
89             e = JSONEncoder()
90             e.encode(value)
91         except TypeError:
92             raise
93         self.value = value
94
95     def format(self):
96         e = JSONEncoder(indent=2, sort_keys=True)
97         return e.encode(self.value)
98
99     def todata(self):
100         return self.value
101
102 def toMetricValue(value):
103     if isinstance(value, MetricValue):
104         return value
105     elif isinstance(value, int) or isinstance(value, long):
106         return IntMetricValue(value)
107     elif isinstance(value, float):
108         return RealMetricValue(value)
109     else:
110         # Try to create a JSONMetricValue and let the constructor throw
111         # if value is not a valid type.
112         return JSONMetricValue(value)
113
114
115 # Test results.
116
117 class Result(object):
118     """Wrapper for the results of executing an individual test."""
119
120     def __init__(self, code, output='', elapsed=None):
121         # The result code.
122         self.code = code
123         # The test output.
124         self.output = output
125         # The wall timing to execute the test, if timing.
126         self.elapsed = elapsed
127         # The metrics reported by this test.
128         self.metrics = {}
129
130     def addMetric(self, name, value):
131         """
132         addMetric(name, value)
133
134         Attach a test metric to the test result, with the given name and list of
135         values. It is an error to attempt to attach the metrics with the same
136         name multiple times.
137
138         Each value must be an instance of a MetricValue subclass.
139         """
140         if name in self.metrics:
141             raise ValueError("result already includes metrics for %r" % (
142                     name,))
143         if not isinstance(value, MetricValue):
144             raise TypeError("unexpected metric value: %r" % (value,))
145         self.metrics[name] = value
146
147 # Test classes.
148
149 class TestSuite:
150     """TestSuite - Information on a group of tests.
151
152     A test suite groups together a set of logically related tests.
153     """
154
155     def __init__(self, name, source_root, exec_root, config):
156         self.name = name
157         self.source_root = source_root
158         self.exec_root = exec_root
159         # The test suite configuration.
160         self.config = config
161
162     def getSourcePath(self, components):
163         return os.path.join(self.source_root, *components)
164
165     def getExecPath(self, components):
166         return os.path.join(self.exec_root, *components)
167
168 class Test:
169     """Test - Information on a single test instance."""
170
171     def __init__(self, suite, path_in_suite, config, file_path = None):
172         self.suite = suite
173         self.path_in_suite = path_in_suite
174         self.config = config
175         self.file_path = file_path
176         # A list of conditions under which this test is expected to fail. These
177         # can optionally be provided by test format handlers, and will be
178         # honored when the test result is supplied.
179         self.xfails = []
180         # The test result, once complete.
181         self.result = None
182
183     def setResult(self, result):
184         if self.result is not None:
185             raise ArgumentError("test result already set")
186         if not isinstance(result, Result):
187             raise ArgumentError("unexpected result type")
188
189         self.result = result
190
191         # Apply the XFAIL handling to resolve the result exit code.
192         if self.isExpectedToFail():
193             if self.result.code == PASS:
194                 self.result.code = XPASS
195             elif self.result.code == FAIL:
196                 self.result.code = XFAIL
197         
198     def getFullName(self):
199         return self.suite.config.name + ' :: ' + '/'.join(self.path_in_suite)
200
201     def getFilePath(self):
202         if self.file_path:
203             return self.file_path
204         return self.getSourcePath()
205
206     def getSourcePath(self):
207         return self.suite.getSourcePath(self.path_in_suite)
208
209     def getExecPath(self):
210         return self.suite.getExecPath(self.path_in_suite)
211
212     def isExpectedToFail(self):
213         """
214         isExpectedToFail() -> bool
215
216         Check whether this test is expected to fail in the current
217         configuration. This check relies on the test xfails property which by
218         some test formats may not be computed until the test has first been
219         executed.
220         """
221
222         # Check if any of the xfails match an available feature or the target.
223         for item in self.xfails:
224             # If this is the wildcard, it always fails.
225             if item == '*':
226                 return True
227
228             # If this is an exact match for one of the features, it fails.
229             if item in self.config.available_features:
230                 return True
231
232             # If this is a part of the target triple, it fails.
233             if item in self.suite.config.target_triple:
234                 return True
235
236         return False
237
238
239     def getJUnitXML(self):
240         test_name = self.path_in_suite[-1]
241         test_path = self.path_in_suite[:-1]
242         safe_test_path = [x.replace(".","_") for x in test_path]
243         safe_name = self.suite.name.replace(".","-")
244
245         if safe_test_path:
246             class_name = safe_name + "." + "/".join(safe_test_path) 
247         else:
248             class_name = safe_name + "." + safe_name
249
250         xml = "<testcase classname='" + class_name + "' name='" + \
251             test_name + "'"
252         xml += " time='%.2f'" % (self.result.elapsed,)
253         if self.result.code.isFailure:
254             xml += ">\n\t<failure >\n" + escape(self.result.output)
255             xml += "\n\t</failure>\n</testcase>"
256         else:
257             xml += "/>"
258         return xml