blob: 742ab71294c6cfc86f03699e6c1e1d7449ede1f1 [file] [log] [blame]
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +00001#!/usr/bin/python3
Maxim Kuvyrkov59877482021-07-07 11:22:26 +00002
3# Script to compare testsuite failures against a list of known-to-fail
4# tests.
5#
Maxim Kuvyrkov59877482021-07-07 11:22:26 +00006# Contributed by Diego Novillo <dnovillo@google.com>
7#
8# Copyright (C) 2011-2013 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
28detailed 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
34The script must be executed from the toplevel build directory. When
35executed it will:
36
371- Determine the target built: TARGET
382- Determine the source directory: SRCDIR
393- Look for a failure manifest file in
40 <SRCDIR>/<MANIFEST_SUBDIR>/<MANIFEST_NAME>.xfail
414- Collect all the <tool>.sum files from the build tree.
425- 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.
456- 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
48Manifest files contain expected DejaGNU results that are otherwise
49treated as failures.
50They may also contain additional text:
51
52# This is a comment. - self explanatory
53@include file - the file is a path relative to the includer
54@remove result text - result text is removed from the expected set
55"""
56
57import datetime
58import optparse
59import os
60import re
61import sys
62
63# Handled test results.
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +000064_INTERESTING_TEST_RESULTS = [ 'FAIL', 'UNRESOLVED', 'XPASS', 'ERROR' ]
65_INTERESTING_TEST_RESULTS_REX = re.compile("%s" %
66 "|".join(_INTERESTING_TEST_RESULTS))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +000067
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +000068# Formats of .sum file sections
69_TOOL_LINE_FORMAT = '\t\t=== %s tests ===\n'
70_EXP_LINE_FORMAT = '\nRunning %s ...\n'
71_SUMMARY_LINE_FORMAT = '\n\t\t=== %s Summary ===\n'
72
73# ... and their compiled regexs.
74_TOOL_LINE_REX = re.compile('^\t\t=== (.*) tests ===\n')
75_EXP_LINE_REX = re.compile('^Running .*(?:/testsuite/)?(.*\.exp) \.\.\.\n')
76_SUMMARY_LINE_REX = re.compile('^\t\t=== (.*) Summary ===\n')
77
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +000078_STATE_ID_TO_PRETTY_STATE = {
79 'expected passes' : 'PASS',
80 'unexpected failures' : 'FAIL',
81 'unexpected successes' : 'XPASS',
82 'expected failures' : 'XFAIL',
83 'unresolved testcases' : 'UNRESOLVED',
84 'unsupported tests' : 'UNSUPPORTED'
85}
86
Maxim Kuvyrkov59877482021-07-07 11:22:26 +000087# Subdirectory of srcdir in which to find the manifest file.
88_MANIFEST_SUBDIR = 'contrib/testsuite-management'
89
90# Pattern for naming manifest files.
91# The first argument should be the toplevel GCC(/GNU tool) source directory.
92# The second argument is the manifest subdir.
93# The third argument is the manifest target, which defaults to the target
94# triplet used during the build.
95_MANIFEST_PATH_PATTERN = '%s/%s/%s.xfail'
96
97# The options passed to the program.
98_OPTIONS = None
99
100def Error(msg):
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000101 print('error: %s' % msg, file=sys.stderr)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000102 sys.exit(1)
103
104
105class TestResult(object):
106 """Describes a single DejaGNU test result as emitted in .sum files.
107
108 We are only interested in representing unsuccessful tests. So, only
109 a subset of all the tests are loaded.
110
111 The summary line used to build the test result should have this format:
112
113 attrlist | XPASS: gcc.dg/unroll_1.c (test for excess errors)
114 ^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
115 optional state name description
116 attributes
117
118 Attributes:
119 attrlist: A comma separated list of attributes.
120 Valid values:
121 flaky Indicates that this test may not always fail. These
122 tests are reported, but their presence does not affect
123 the results.
124
125 expire=YYYYMMDD After this date, this test will produce an error
126 whether it is in the manifest or not.
127
128 state: One of UNRESOLVED, XPASS or FAIL.
129 name: File name for the test.
130 description: String describing the test (flags used, dejagnu message, etc)
131 ordinal: Monotonically increasing integer.
132 It is used to keep results for one .exp file sorted
133 by the order the tests were run.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000134 tool: Top-level testsuite name (aka "tool" in DejaGnu parlance) of the test.
135 exp: Name of .exp testsuite file.
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000136 """
137
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000138 def __init__(self, summary_line, ordinal, tool, exp):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000139 try:
140 (self.attrs, summary_line) = SplitAttributesFromSummaryLine(summary_line)
141 try:
142 (self.state,
143 self.name,
144 self.description) = re.match(r'([A-Z]+):\s*(\S+)\s*(.*)',
145 summary_line).groups()
146 except:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000147 print('Failed to parse summary line: "%s"' % summary_line)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000148 raise
149 self.ordinal = ordinal
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000150 self.tool = tool
151 self.exp = exp
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000152 except ValueError:
153 Error('Cannot parse summary line "%s"' % summary_line)
154
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +0000155 if self.state not in _INTERESTING_TEST_RESULTS:
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000156 Error('Invalid test result %s in "%s" (parsed as "%s")' % (
157 self.state, summary_line, self))
158
159 def __lt__(self, other):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000160 if (self.tool != other.tool):
161 return self.tool < other.tool
162 if (self.exp != other.exp):
163 return self.exp < other.exp
164 if (self.name != other.name):
165 return self.name < other.name
166 return self.ordinal < other.ordinal
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000167
168 def __hash__(self):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000169 return (hash(self.state) ^ hash(self.tool) ^ hash(self.exp)
170 ^ hash(self.name) ^ hash(self.description))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000171
172 def __eq__(self, other):
173 return (self.state == other.state and
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000174 self.tool == other.tool and
175 self.exp == other.exp and
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000176 self.name == other.name and
177 self.description == other.description)
178
179 def __ne__(self, other):
180 return not (self == other)
181
182 def __str__(self):
183 attrs = ''
184 if self.attrs:
185 attrs = '%s | ' % self.attrs
186 return '%s%s: %s %s' % (attrs, self.state, self.name, self.description)
187
188 def ExpirationDate(self):
189 # Return a datetime.date object with the expiration date for this
190 # test result. Return None, if no expiration has been set.
191 if re.search(r'expire=', self.attrs):
192 expiration = re.search(r'expire=(\d\d\d\d)(\d\d)(\d\d)', self.attrs)
193 if not expiration:
194 Error('Invalid expire= format in "%s". Must be of the form '
195 '"expire=YYYYMMDD"' % self)
196 return datetime.date(int(expiration.group(1)),
197 int(expiration.group(2)),
198 int(expiration.group(3)))
199 return None
200
201 def HasExpired(self):
202 # Return True if the expiration date of this result has passed.
203 expiration_date = self.ExpirationDate()
204 if expiration_date:
205 now = datetime.date.today()
206 return now > expiration_date
207
208
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +0000209class ResultStats(dict):
210 def __init__(self, tool_line):
211 super().__init__()
212
213 def Count(self, line):
214 state = re.match(r'([A-Z]+):\s*(\S+)\s*(.*)', line).groups()[0]
215 if not state in self:
216 self[state] = 0
217 self[state] += 1
218
219 def IdOfPrettyState(pretty_state):
220 for i in _STATE_ID_TO
221 if pretty_state in _PRETTY_STATE_TO_ID:
222 return _PRETTY_STATE_TO_ID[pretty_state]
223 Error('Cannot parse pretty state %s' % pretty_state)
224
225 def PrettyState(state):
226 if state in _STATE_ID_TO_PRETTY_STATE:
227 return _STATE_ID_TO_PRETTY_STATE[state]
228 return state
229
230 def Parse(sum_line, infile):
231 stats = ResultStats(sum_line)
232 for orig_line in infile:
233 line = orig_line.strip()
234 if line == '':
235 pass
236 elif isStatLine(orig_line):
237 (state_desc, count) = re.match(r'', orig_line).groups()
238 state = desc2state(state_desc)
239 self[state] = count
240 else
241 return stats
242
243
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000244class ResultSet(set):
245 """Describes a set of DejaGNU test results.
246 This set can be read in from .sum files or emitted as a manifest.
247
248 Attributes:
249 current_tool: Name of the current top-level DejaGnu testsuite.
250 current_exp: Name of the current .exp testsuite file.
251 """
252
253 def __init__(self):
254 super().__init__()
255 self.ResetToolExp()
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +0000256 self.stats = ResultStats()
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000257
258 def ResetToolExp(self):
259 self.current_tool = None
260 self.current_exp = None
261
262 def MakeTestResult(self, summary_line, ordinal=-1):
263 return TestResult(summary_line, ordinal,
264 self.current_tool, self.current_exp)
265
266 def Print(self, outfile=sys.stdout):
267 current_tool = None
268 current_exp = None
269
270 for result in sorted(self):
271 if current_tool != result.tool:
272 current_tool = result.tool
273 outfile.write(_TOOL_LINE_FORMAT % current_tool)
274 if current_exp != result.exp:
275 current_exp = result.exp
276 outfile.write(_EXP_LINE_FORMAT % current_exp)
277 outfile.write('%s\n' % result)
278
279 outfile.write(_SUMMARY_LINE_FORMAT % 'Results')
280
281
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000282def GetMakefileValue(makefile_name, value_name):
283 if os.path.exists(makefile_name):
284 makefile = open(makefile_name)
285 for line in makefile:
286 if line.startswith(value_name):
287 (_, value) = line.split('=', 1)
288 value = value.strip()
289 makefile.close()
290 return value
291 makefile.close()
292 return None
293
294
295def ValidBuildDirectory(builddir):
296 if (not os.path.exists(builddir) or
297 not os.path.exists('%s/Makefile' % builddir)):
298 return False
299 return True
300
301
302def IsComment(line):
303 """Return True if line is a comment."""
304 return line.startswith('#')
305
306
307def SplitAttributesFromSummaryLine(line):
308 """Splits off attributes from a summary line, if present."""
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +0000309 if '|' in line and not _INTERESTING_TEST_RESULTS_REX.match(line):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000310 (attrs, line) = line.split('|', 1)
311 attrs = attrs.strip()
312 else:
313 attrs = ''
314 line = line.strip()
315 return (attrs, line)
316
317
318def IsInterestingResult(line):
319 """Return True if line is one of the summary lines we care about."""
320 (_, line) = SplitAttributesFromSummaryLine(line)
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +0000321 return bool(_INTERESTING_TEST_RESULTS_REX.match(line))
322
323
324def IsBoringResult(line):
325 """Return True if line is a result that should be included in stats."""
326 return bool(_BORING_TEST_RESULTS_REX.match(line))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000327
328
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000329def IsToolLine(line):
330 """Return True if line mentions the tool (in DejaGnu terms) for the following tests."""
331 return bool(_TOOL_LINE_REX.match(line))
332
333
334def IsExpLine(line):
335 """Return True if line mentions the .exp file for the following tests."""
336 return bool(_EXP_LINE_REX.match(line))
337
338
339def IsSummaryLine(line):
340 """Return True if line starts .sum footer."""
341 return bool(_SUMMARY_LINE_REX.match(line))
342
343
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000344def IsInclude(line):
345 """Return True if line is an include of another file."""
346 return line.startswith("@include ")
347
348
349def GetIncludeFile(line, includer):
350 """Extract the name of the include file from line."""
351 includer_dir = os.path.dirname(includer)
352 include_file = line[len("@include "):]
353 return os.path.join(includer_dir, include_file.strip())
354
355
356def IsNegativeResult(line):
357 """Return True if line should be removed from the expected results."""
358 return line.startswith("@remove ")
359
360
361def GetNegativeResult(line):
362 """Extract the name of the negative result from line."""
363 line = line[len("@remove "):]
364 return line.strip()
365
366
367def ParseManifestWorker(result_set, manifest_path):
368 """Read manifest_path, adding the contents to result_set."""
369 if _OPTIONS.verbosity >= 1:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000370 print('Parsing manifest file %s.' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000371 manifest_file = open(manifest_path)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000372 for orig_line in manifest_file:
373 line = orig_line.strip()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000374 if line == "":
375 pass
376 elif IsComment(line):
377 pass
378 elif IsNegativeResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000379 result_set.remove(result_set.MakeTestResult(GetNegativeResult(line)))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000380 elif IsInclude(line):
381 ParseManifestWorker(result_set, GetIncludeFile(line, manifest_path))
382 elif IsInterestingResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000383 result_set.add(result_set.MakeTestResult(line))
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +0000384 elif IsBoringResult(line):
385 pass
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000386 elif IsExpLine(orig_line):
387 result_set.current_exp = _EXP_LINE_REX.match(orig_line).groups()[0]
388 elif IsToolLine(orig_line):
389 result_set.current_tool = _TOOL_LINE_REX.match(orig_line).groups()[0]
390 elif IsSummaryLine(orig_line):
391 result_set.ResetToolExp()
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +0000392 manifest_stats = ParseResultStats(line, manifest_file)
393 result_set.stats[result_set.current_tool] += manifest_stats
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000394 else:
395 Error('Unrecognized line in manifest file: %s' % line)
396 manifest_file.close()
397
398
399def ParseManifest(manifest_path):
400 """Create a set of TestResult instances from the given manifest file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000401 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000402 ParseManifestWorker(result_set, manifest_path)
403 return result_set
404
405
406def ParseSummary(sum_fname):
407 """Create a set of TestResult instances from the given summary file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000408 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000409 # ordinal is used when sorting the results so that tests within each
410 # .exp file are kept sorted.
411 ordinal=0
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +0000412 current_tool_stats = None
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000413 sum_file = open(sum_fname)
414 for line in sum_file:
415 if IsInterestingResult(line):
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +0000416 current_tool_stats.Count(line)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000417 result = result_set.MakeTestResult(line, ordinal)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000418 ordinal += 1
419 if result.HasExpired():
420 # Tests that have expired are not added to the set of expected
421 # results. If they are still present in the set of actual results,
422 # they will cause an error to be reported.
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000423 print('WARNING: Expected failure "%s" has expired.' % line.strip())
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000424 continue
425 result_set.add(result)
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +0000426 elif IsBoringResult(line):
427 current_tool_stats.Count(line)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000428 elif IsExpLine(line):
429 result_set.current_exp = _EXP_LINE_REX.match(line).groups()[0]
430 elif IsToolLine(line):
431 result_set.current_tool = _TOOL_LINE_REX.match(line).groups()[0]
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +0000432 current_tool_stats = ResultStats(results_set.current_tool)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000433 elif IsSummaryLine(line):
434 result_set.ResetToolExp()
Maxim Kuvyrkov24e49342021-07-07 11:33:38 +0000435 sum_stats = ResultStats.Parse(line, sum_file)
436 if (current_tool_stats != sum_stats):
437 Error('Calculated and parsed statistics do not match')
438 results_set.stats[results_set.current_tool] += current_tool_stats
439
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000440 sum_file.close()
441 return result_set
442
443
444def GetManifest(manifest_path):
445 """Build a set of expected failures from the manifest file.
446
447 Each entry in the manifest file should have the format understood
448 by the TestResult constructor.
449
450 If no manifest file exists for this target, it returns an empty set.
451 """
452 if os.path.exists(manifest_path):
453 return ParseManifest(manifest_path)
454 else:
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000455 return ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000456
457
458def CollectSumFiles(builddir):
459 sum_files = []
460 for root, dirs, files in os.walk(builddir):
461 for ignored in ('.svn', '.git'):
462 if ignored in dirs:
463 dirs.remove(ignored)
464 for fname in files:
465 if fname.endswith('.sum'):
466 sum_files.append(os.path.join(root, fname))
467 return sum_files
468
469
470def GetResults(sum_files):
471 """Collect all the test results from the given .sum files."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000472 build_results = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000473 for sum_fname in sum_files:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000474 print('\t%s' % sum_fname)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000475 build_results |= ParseSummary(sum_fname)
476 return build_results
477
478
479def CompareResults(manifest, actual):
480 """Compare sets of results and return two lists:
481 - List of results present in ACTUAL but missing from MANIFEST.
482 - List of results present in MANIFEST but missing from ACTUAL.
483 """
484 # Collect all the actual results not present in the manifest.
485 # Results in this set will be reported as errors.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000486 actual_vs_manifest = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000487 for actual_result in actual:
488 if actual_result not in manifest:
489 actual_vs_manifest.add(actual_result)
490
491 # Collect all the tests in the manifest that were not found
492 # in the actual results.
493 # Results in this set will be reported as warnings (since
494 # they are expected failures that are not failing anymore).
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000495 manifest_vs_actual = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000496 for expected_result in manifest:
497 # Ignore tests marked flaky.
498 if 'flaky' in expected_result.attrs:
499 continue
500 if expected_result not in actual:
501 manifest_vs_actual.add(expected_result)
502
503 return actual_vs_manifest, manifest_vs_actual
504
505
506def GetManifestPath(srcdir, target, user_provided_must_exist):
507 """Return the full path to the manifest file."""
508 manifest_path = _OPTIONS.manifest
509 if manifest_path:
510 if user_provided_must_exist and not os.path.exists(manifest_path):
511 Error('Manifest does not exist: %s' % manifest_path)
512 return manifest_path
513 else:
514 if not srcdir:
515 Error('Could not determine the location of GCC\'s source tree. '
516 'The Makefile does not contain a definition for "srcdir".')
517 if not target:
518 Error('Could not determine the target triplet for this build. '
519 'The Makefile does not contain a definition for "target_alias".')
520 return _MANIFEST_PATH_PATTERN % (srcdir, _MANIFEST_SUBDIR, target)
521
522
523def GetBuildData():
524 if not ValidBuildDirectory(_OPTIONS.build_dir):
525 # If we have been given a set of results to use, we may
526 # not be inside a valid GCC build directory. In that case,
527 # the user must provide both a manifest file and a set
528 # of results to check against it.
529 if not _OPTIONS.results or not _OPTIONS.manifest:
530 Error('%s is not a valid GCC top level build directory. '
531 'You must use --manifest and --results to do the validation.' %
532 _OPTIONS.build_dir)
533 else:
534 return None, None
535 srcdir = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'srcdir =')
536 target = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'target_alias=')
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000537 print('Source directory: %s' % srcdir)
538 print('Build target: %s' % target)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000539 return srcdir, target
540
541
542def PrintSummary(msg, summary):
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000543 print('\n\n%s' % msg)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000544 summary.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000545
546def GetSumFiles(results, build_dir):
547 if not results:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000548 print('Getting actual results from build directory %s' % build_dir)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000549 sum_files = CollectSumFiles(build_dir)
550 else:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000551 print('Getting actual results from user-provided results')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000552 sum_files = results.split()
553 return sum_files
554
555
556def PerformComparison(expected, actual, ignore_missing_failures):
557 actual_vs_expected, expected_vs_actual = CompareResults(expected, actual)
558
559 tests_ok = True
560 if len(actual_vs_expected) > 0:
561 PrintSummary('Unexpected results in this build (new failures)',
562 actual_vs_expected)
563 tests_ok = False
564
565 if not ignore_missing_failures and len(expected_vs_actual) > 0:
566 PrintSummary('Expected results not present in this build (fixed tests)'
567 '\n\nNOTE: This is not a failure. It just means that these '
568 'tests were expected\nto fail, but either they worked in '
569 'this configuration or they were not\npresent at all.\n',
570 expected_vs_actual)
571
572 if tests_ok:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000573 print('\nSUCCESS: No unexpected failures.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000574
575 return tests_ok
576
577
578def CheckExpectedResults():
579 srcdir, target = GetBuildData()
580 manifest_path = GetManifestPath(srcdir, target, True)
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000581 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000582 manifest = GetManifest(manifest_path)
583 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
584 actual = GetResults(sum_files)
585
586 if _OPTIONS.verbosity >= 1:
587 PrintSummary('Tests expected to fail', manifest)
588 PrintSummary('\nActual test results', actual)
589
590 return PerformComparison(manifest, actual, _OPTIONS.ignore_missing_failures)
591
592
593def ProduceManifest():
594 (srcdir, target) = GetBuildData()
595 manifest_path = GetManifestPath(srcdir, target, False)
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000596 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000597 if os.path.exists(manifest_path) and not _OPTIONS.force:
598 Error('Manifest file %s already exists.\nUse --force to overwrite.' %
599 manifest_path)
600
601 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
602 actual = GetResults(sum_files)
603 manifest_file = open(manifest_path, 'w')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000604 actual.Print(manifest_file)
605 actual.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000606 manifest_file.close()
607
608 return True
609
610
611def CompareBuilds():
612 (srcdir, target) = GetBuildData()
613
614 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
615 actual = GetResults(sum_files)
616
617 clean_sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.clean_build)
618 clean = GetResults(clean_sum_files)
619
620 return PerformComparison(clean, actual, _OPTIONS.ignore_missing_failures)
621
622
623def Main(argv):
624 parser = optparse.OptionParser(usage=__doc__)
625
626 # Keep the following list sorted by option name.
627 parser.add_option('--build_dir', action='store', type='string',
628 dest='build_dir', default='.',
629 help='Build directory to check (default = .)')
630 parser.add_option('--clean_build', action='store', type='string',
631 dest='clean_build', default=None,
632 help='Compare test results from this build against '
633 'those of another (clean) build. Use this option '
634 'when comparing the test results of your patch versus '
635 'the test results of a clean build without your patch. '
636 'You must provide the path to the top directory of your '
637 'clean build.')
638 parser.add_option('--force', action='store_true', dest='force',
639 default=False, help='When used with --produce_manifest, '
640 'it will overwrite an existing manifest file '
641 '(default = False)')
642 parser.add_option('--ignore_missing_failures', action='store_true',
643 dest='ignore_missing_failures', default=False,
644 help='When a failure is expected in the manifest but '
645 'it is not found in the actual results, the script '
646 'produces a note alerting to this fact. This means '
647 'that the expected failure has been fixed, or '
648 'it did not run, or it may simply be flaky '
649 '(default = False)')
650 parser.add_option('--manifest', action='store', type='string',
651 dest='manifest', default=None,
652 help='Name of the manifest file to use (default = '
653 'taken from '
654 'contrib/testsuite-managment/<target_alias>.xfail)')
655 parser.add_option('--produce_manifest', action='store_true',
656 dest='produce_manifest', default=False,
657 help='Produce the manifest for the current '
658 'build (default = False)')
659 parser.add_option('--results', action='store', type='string',
660 dest='results', default=None, help='Space-separated list '
661 'of .sum files with the testing results to check. The '
662 'only content needed from these files are the lines '
663 'starting with FAIL, XPASS or UNRESOLVED (default = '
664 '.sum files collected from the build directory).')
665 parser.add_option('--verbosity', action='store', dest='verbosity',
666 type='int', default=0, help='Verbosity level (default = 0)')
667 global _OPTIONS
668 (_OPTIONS, _) = parser.parse_args(argv[1:])
669
670 if _OPTIONS.produce_manifest:
671 retval = ProduceManifest()
672 elif _OPTIONS.clean_build:
673 retval = CompareBuilds()
674 else:
675 retval = CheckExpectedResults()
676
677 if retval:
678 return 0
679 else:
680 return 1
681
682
683if __name__ == '__main__':
684 retval = Main(sys.argv)
685 sys.exit(retval)