1 |
723 |
jeremybenn |
#!/usr/bin/python
|
2 |
|
|
|
3 |
|
|
# Script to compare testsuite failures against a list of known-to-fail
|
4 |
|
|
# tests.
|
5 |
|
|
|
6 |
|
|
# Contributed by Diego Novillo <dnovillo@google.com>
|
7 |
|
|
#
|
8 |
|
|
# Copyright (C) 2011 Free Software Foundation, Inc.
|
9 |
|
|
#
|
10 |
|
|
# This file is part of GCC.
|
11 |
|
|
#
|
12 |
|
|
# GCC is free software; you can redistribute it and/or modify
|
13 |
|
|
# it under the terms of the GNU General Public License as published by
|
14 |
|
|
# the Free Software Foundation; either version 3, or (at your option)
|
15 |
|
|
# any later version.
|
16 |
|
|
#
|
17 |
|
|
# GCC is distributed in the hope that it will be useful,
|
18 |
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
19 |
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
20 |
|
|
# GNU General Public License for more details.
|
21 |
|
|
#
|
22 |
|
|
# You should have received a copy of the GNU General Public License
|
23 |
|
|
# along with GCC; see the file COPYING. If not, write to
|
24 |
|
|
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
|
25 |
|
|
# Boston, MA 02110-1301, USA.
|
26 |
|
|
|
27 |
|
|
"""This script provides a coarser XFAILing mechanism that requires no
|
28 |
|
|
detailed DejaGNU markings. This is useful in a variety of scenarios:
|
29 |
|
|
|
30 |
|
|
- Development branches with many known failures waiting to be fixed.
|
31 |
|
|
- Release branches with known failures that are not considered
|
32 |
|
|
important for the particular release criteria used in that branch.
|
33 |
|
|
|
34 |
|
|
The script must be executed from the toplevel build directory. When
|
35 |
|
|
executed it will:
|
36 |
|
|
|
37 |
|
|
1- Determine the target built: TARGET
|
38 |
|
|
2- Determine the source directory: SRCDIR
|
39 |
|
|
3- Look for a failure manifest file in
|
40 |
|
|
<SRCDIR>/contrib/testsuite-management/<TARGET>.xfail
|
41 |
|
|
4- Collect all the <tool>.sum files from the build tree.
|
42 |
|
|
5- Produce a report stating:
|
43 |
|
|
a- Failures expected in the manifest but not present in the build.
|
44 |
|
|
b- Failures in the build not expected in the manifest.
|
45 |
|
|
6- If all the build failures are expected in the manifest, it exits
|
46 |
|
|
with exit code 0. Otherwise, it exits with error code 1.
|
47 |
|
|
"""
|
48 |
|
|
|
49 |
|
|
import optparse
|
50 |
|
|
import os
|
51 |
|
|
import re
|
52 |
|
|
import sys
|
53 |
|
|
|
54 |
|
|
# Handled test results.
|
55 |
|
|
_VALID_TEST_RESULTS = [ 'FAIL', 'UNRESOLVED', 'XPASS', 'ERROR' ]
|
56 |
|
|
|
57 |
|
|
# Pattern for naming manifest files. The first argument should be
|
58 |
|
|
# the toplevel GCC source directory. The second argument is the
|
59 |
|
|
# target triple used during the build.
|
60 |
|
|
_MANIFEST_PATH_PATTERN = '%s/contrib/testsuite-management/%s.xfail'
|
61 |
|
|
|
62 |
|
|
def Error(msg):
|
63 |
|
|
print >>sys.stderr, '\nerror: %s' % msg
|
64 |
|
|
sys.exit(1)
|
65 |
|
|
|
66 |
|
|
|
67 |
|
|
class TestResult(object):
|
68 |
|
|
"""Describes a single DejaGNU test result as emitted in .sum files.
|
69 |
|
|
|
70 |
|
|
We are only interested in representing unsuccessful tests. So, only
|
71 |
|
|
a subset of all the tests are loaded.
|
72 |
|
|
|
73 |
|
|
The summary line used to build the test result should have this format:
|
74 |
|
|
|
75 |
|
|
attrlist | XPASS: gcc.dg/unroll_1.c (test for excess errors)
|
76 |
|
|
^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
|
77 |
|
|
optional state name description
|
78 |
|
|
attributes
|
79 |
|
|
|
80 |
|
|
Attributes:
|
81 |
|
|
attrlist: A comma separated list of attributes.
|
82 |
|
|
Valid values:
|
83 |
|
|
flaky Indicates that this test may not always fail. These
|
84 |
|
|
tests are reported, but their presence does not affect
|
85 |
|
|
the results.
|
86 |
|
|
|
87 |
|
|
expire=YYYYMMDD After this date, this test will produce an error
|
88 |
|
|
whether it is in the manifest or not.
|
89 |
|
|
|
90 |
|
|
state: One of UNRESOLVED, XPASS or FAIL.
|
91 |
|
|
name: File name for the test.
|
92 |
|
|
description: String describing the test (flags used, dejagnu message, etc)
|
93 |
|
|
"""
|
94 |
|
|
|
95 |
|
|
def __init__(self, summary_line):
|
96 |
|
|
try:
|
97 |
|
|
self.attrs = ''
|
98 |
|
|
if '|' in summary_line:
|
99 |
|
|
(self.attrs, summary_line) = summary_line.split('|', 1)
|
100 |
|
|
(self.state,
|
101 |
|
|
self.name,
|
102 |
|
|
self.description) = re.match(r' *([A-Z]+): ([^ ]+) (.*)',
|
103 |
|
|
summary_line).groups()
|
104 |
|
|
self.attrs = self.attrs.strip()
|
105 |
|
|
self.state = self.state.strip()
|
106 |
|
|
self.description = self.description.strip()
|
107 |
|
|
except ValueError:
|
108 |
|
|
Error('Cannot parse summary line "%s"' % summary_line)
|
109 |
|
|
|
110 |
|
|
if self.state not in _VALID_TEST_RESULTS:
|
111 |
|
|
Error('Invalid test result %s in "%s" (parsed as "%s")' % (
|
112 |
|
|
self.state, summary_line, self))
|
113 |
|
|
|
114 |
|
|
def __lt__(self, other):
|
115 |
|
|
return self.name < other.name
|
116 |
|
|
|
117 |
|
|
def __hash__(self):
|
118 |
|
|
return hash(self.state) ^ hash(self.name) ^ hash(self.description)
|
119 |
|
|
|
120 |
|
|
def __eq__(self, other):
|
121 |
|
|
return (self.state == other.state and
|
122 |
|
|
self.name == other.name and
|
123 |
|
|
self.description == other.description)
|
124 |
|
|
|
125 |
|
|
def __ne__(self, other):
|
126 |
|
|
return not (self == other)
|
127 |
|
|
|
128 |
|
|
def __str__(self):
|
129 |
|
|
attrs = ''
|
130 |
|
|
if self.attrs:
|
131 |
|
|
attrs = '%s | ' % self.attrs
|
132 |
|
|
return '%s%s: %s %s' % (attrs, self.state, self.name, self.description)
|
133 |
|
|
|
134 |
|
|
|
135 |
|
|
def GetMakefileValue(makefile_name, value_name):
|
136 |
|
|
if os.path.exists(makefile_name):
|
137 |
|
|
with open(makefile_name) as makefile:
|
138 |
|
|
for line in makefile:
|
139 |
|
|
if line.startswith(value_name):
|
140 |
|
|
(_, value) = line.split('=', 1)
|
141 |
|
|
value = value.strip()
|
142 |
|
|
return value
|
143 |
|
|
return None
|
144 |
|
|
|
145 |
|
|
|
146 |
|
|
def ValidBuildDirectory(builddir, target):
|
147 |
|
|
if (not os.path.exists(builddir) or
|
148 |
|
|
not os.path.exists('%s/Makefile' % builddir) or
|
149 |
|
|
(not os.path.exists('%s/build-%s' % (builddir, target)) and
|
150 |
|
|
not os.path.exists('%s/%s' % (builddir, target)))):
|
151 |
|
|
return False
|
152 |
|
|
return True
|
153 |
|
|
|
154 |
|
|
|
155 |
|
|
def IsInterestingResult(line):
|
156 |
|
|
"""Return True if the given line is one of the summary lines we care about."""
|
157 |
|
|
line = line.strip()
|
158 |
|
|
if line.startswith('#'):
|
159 |
|
|
return False
|
160 |
|
|
if '|' in line:
|
161 |
|
|
(_, line) = line.split('|', 1)
|
162 |
|
|
line = line.strip()
|
163 |
|
|
for result in _VALID_TEST_RESULTS:
|
164 |
|
|
if line.startswith(result):
|
165 |
|
|
return True
|
166 |
|
|
return False
|
167 |
|
|
|
168 |
|
|
|
169 |
|
|
def ParseSummary(sum_fname):
|
170 |
|
|
"""Create a set of TestResult instances from the given summary file."""
|
171 |
|
|
result_set = set()
|
172 |
|
|
with open(sum_fname) as sum_file:
|
173 |
|
|
for line in sum_file:
|
174 |
|
|
if IsInterestingResult(line):
|
175 |
|
|
result_set.add(TestResult(line))
|
176 |
|
|
return result_set
|
177 |
|
|
|
178 |
|
|
|
179 |
|
|
def GetManifest(manifest_name):
|
180 |
|
|
"""Build a set of expected failures from the manifest file.
|
181 |
|
|
|
182 |
|
|
Each entry in the manifest file should have the format understood
|
183 |
|
|
by the TestResult constructor.
|
184 |
|
|
|
185 |
|
|
If no manifest file exists for this target, it returns an empty
|
186 |
|
|
set.
|
187 |
|
|
"""
|
188 |
|
|
if os.path.exists(manifest_name):
|
189 |
|
|
return ParseSummary(manifest_name)
|
190 |
|
|
else:
|
191 |
|
|
return set()
|
192 |
|
|
|
193 |
|
|
|
194 |
|
|
def GetSumFiles(builddir):
|
195 |
|
|
sum_files = []
|
196 |
|
|
for root, dirs, files in os.walk(builddir):
|
197 |
|
|
if '.svn' in dirs:
|
198 |
|
|
dirs.remove('.svn')
|
199 |
|
|
for fname in files:
|
200 |
|
|
if fname.endswith('.sum'):
|
201 |
|
|
sum_files.append(os.path.join(root, fname))
|
202 |
|
|
return sum_files
|
203 |
|
|
|
204 |
|
|
|
205 |
|
|
def GetResults(builddir):
|
206 |
|
|
"""Collect all the test results from .sum files under the given build
|
207 |
|
|
directory."""
|
208 |
|
|
sum_files = GetSumFiles(builddir)
|
209 |
|
|
build_results = set()
|
210 |
|
|
for sum_fname in sum_files:
|
211 |
|
|
print '\t%s' % sum_fname
|
212 |
|
|
build_results |= ParseSummary(sum_fname)
|
213 |
|
|
return build_results
|
214 |
|
|
|
215 |
|
|
|
216 |
|
|
def CompareResults(manifest, actual):
|
217 |
|
|
"""Compare sets of results and return two lists:
|
218 |
|
|
- List of results present in MANIFEST but missing from ACTUAL.
|
219 |
|
|
- List of results present in ACTUAL but missing from MANIFEST.
|
220 |
|
|
"""
|
221 |
|
|
# Report all the actual results not present in the manifest.
|
222 |
|
|
actual_vs_manifest = set()
|
223 |
|
|
for actual_result in actual:
|
224 |
|
|
if actual_result not in manifest:
|
225 |
|
|
actual_vs_manifest.add(actual_result)
|
226 |
|
|
|
227 |
|
|
# Simlarly for all the tests in the manifest.
|
228 |
|
|
manifest_vs_actual = set()
|
229 |
|
|
for expected_result in manifest:
|
230 |
|
|
# Ignore tests marked flaky.
|
231 |
|
|
if 'flaky' in expected_result.attrs:
|
232 |
|
|
continue
|
233 |
|
|
if expected_result not in actual:
|
234 |
|
|
manifest_vs_actual.add(expected_result)
|
235 |
|
|
|
236 |
|
|
return actual_vs_manifest, manifest_vs_actual
|
237 |
|
|
|
238 |
|
|
|
239 |
|
|
def GetBuildData(options):
|
240 |
|
|
target = GetMakefileValue('%s/Makefile' % options.build_dir, 'target=')
|
241 |
|
|
srcdir = GetMakefileValue('%s/Makefile' % options.build_dir, 'srcdir =')
|
242 |
|
|
if not ValidBuildDirectory(options.build_dir, target):
|
243 |
|
|
Error('%s is not a valid GCC top level build directory.' %
|
244 |
|
|
options.build_dir)
|
245 |
|
|
print 'Source directory: %s' % srcdir
|
246 |
|
|
print 'Build target: %s' % target
|
247 |
|
|
return srcdir, target, True
|
248 |
|
|
|
249 |
|
|
|
250 |
|
|
def PrintSummary(msg, summary):
|
251 |
|
|
print '\n\n%s' % msg
|
252 |
|
|
for result in sorted(summary):
|
253 |
|
|
print result
|
254 |
|
|
|
255 |
|
|
|
256 |
|
|
def CheckExpectedResults(options):
|
257 |
|
|
(srcdir, target, valid_build) = GetBuildData(options)
|
258 |
|
|
if not valid_build:
|
259 |
|
|
return False
|
260 |
|
|
|
261 |
|
|
manifest_name = _MANIFEST_PATH_PATTERN % (srcdir, target)
|
262 |
|
|
print 'Manifest: %s' % manifest_name
|
263 |
|
|
manifest = GetManifest(manifest_name)
|
264 |
|
|
|
265 |
|
|
print 'Getting actual results from build'
|
266 |
|
|
actual = GetResults(options.build_dir)
|
267 |
|
|
|
268 |
|
|
if options.verbosity >= 1:
|
269 |
|
|
PrintSummary('Tests expected to fail', manifest)
|
270 |
|
|
PrintSummary('\nActual test results', actual)
|
271 |
|
|
|
272 |
|
|
actual_vs_manifest, manifest_vs_actual = CompareResults(manifest, actual)
|
273 |
|
|
|
274 |
|
|
tests_ok = True
|
275 |
|
|
if len(actual_vs_manifest) > 0:
|
276 |
|
|
PrintSummary('Build results not in the manifest', actual_vs_manifest)
|
277 |
|
|
tests_ok = False
|
278 |
|
|
|
279 |
|
|
if len(manifest_vs_actual) > 0:
|
280 |
|
|
PrintSummary('Manifest results not present in the build'
|
281 |
|
|
'\n\nNOTE: This is not a failure. It just means that the '
|
282 |
|
|
'manifest expected\nthese tests to fail, '
|
283 |
|
|
'but they worked in this configuration.\n',
|
284 |
|
|
manifest_vs_actual)
|
285 |
|
|
|
286 |
|
|
if tests_ok:
|
287 |
|
|
print '\nSUCCESS: No unexpected failures.'
|
288 |
|
|
|
289 |
|
|
return tests_ok
|
290 |
|
|
|
291 |
|
|
|
292 |
|
|
def ProduceManifest(options):
|
293 |
|
|
(srcdir, target, valid_build) = GetBuildData(options)
|
294 |
|
|
if not valid_build:
|
295 |
|
|
return False
|
296 |
|
|
|
297 |
|
|
manifest_name = _MANIFEST_PATH_PATTERN % (srcdir, target)
|
298 |
|
|
if os.path.exists(manifest_name) and not options.force:
|
299 |
|
|
Error('Manifest file %s already exists.\nUse --force to overwrite.' %
|
300 |
|
|
manifest_name)
|
301 |
|
|
|
302 |
|
|
actual = GetResults(options.build_dir)
|
303 |
|
|
with open(manifest_name, 'w') as manifest_file:
|
304 |
|
|
for result in sorted(actual):
|
305 |
|
|
print result
|
306 |
|
|
manifest_file.write('%s\n' % result)
|
307 |
|
|
|
308 |
|
|
return True
|
309 |
|
|
|
310 |
|
|
|
311 |
|
|
def Main(argv):
|
312 |
|
|
parser = optparse.OptionParser(usage=__doc__)
|
313 |
|
|
parser.add_option('--build_dir', action='store', type='string',
|
314 |
|
|
dest='build_dir', default='.',
|
315 |
|
|
help='Build directory to check (default = .)')
|
316 |
|
|
parser.add_option('--manifest', action='store_true', dest='manifest',
|
317 |
|
|
default=False, help='Produce the manifest for the current '
|
318 |
|
|
'build (default = False)')
|
319 |
|
|
parser.add_option('--force', action='store_true', dest='force',
|
320 |
|
|
default=False, help='When used with --manifest, it will '
|
321 |
|
|
'overwrite an existing manifest file (default = False)')
|
322 |
|
|
parser.add_option('--verbosity', action='store', dest='verbosity',
|
323 |
|
|
type='int', default=0, help='Verbosity level (default = 0)')
|
324 |
|
|
(options, _) = parser.parse_args(argv[1:])
|
325 |
|
|
|
326 |
|
|
if options.manifest:
|
327 |
|
|
retval = ProduceManifest(options)
|
328 |
|
|
else:
|
329 |
|
|
retval = CheckExpectedResults(options)
|
330 |
|
|
|
331 |
|
|
if retval:
|
332 |
|
|
return 0
|
333 |
|
|
else:
|
334 |
|
|
return 1
|
335 |
|
|
|
336 |
|
|
if __name__ == '__main__':
|
337 |
|
|
retval = Main(sys.argv)
|
338 |
|
|
sys.exit(retval)
|