blob: c6e7d355ab825aa3d2352059ccbfc44bd2052212 [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
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +000063# Formats of .sum file sections
64_TOOL_LINE_FORMAT = '\t\t=== %s tests ===\n'
65_EXP_LINE_FORMAT = '\nRunning %s ...\n'
66_SUMMARY_LINE_FORMAT = '\n\t\t=== %s Summary ===\n'
67
68# ... and their compiled regexs.
69_TOOL_LINE_REX = re.compile('^\t\t=== (.*) tests ===\n')
Maxim Kuvyrkovd8951a22021-07-08 08:20:28 +000070_EXP_LINE_REX = re.compile('^Running (?:.*/testsuite/)?(.*\.exp) \.\.\.\n')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +000071_SUMMARY_LINE_REX = re.compile('^\t\t=== (.*) Summary ===\n')
72
Maxim Kuvyrkov59877482021-07-07 11:22:26 +000073# Subdirectory of srcdir in which to find the manifest file.
74_MANIFEST_SUBDIR = 'contrib/testsuite-management'
75
76# Pattern for naming manifest files.
77# The first argument should be the toplevel GCC(/GNU tool) source directory.
78# The second argument is the manifest subdir.
79# The third argument is the manifest target, which defaults to the target
80# triplet used during the build.
81_MANIFEST_PATH_PATTERN = '%s/%s/%s.xfail'
82
83# The options passed to the program.
84_OPTIONS = None
85
86def Error(msg):
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +000087 print('error: %s' % msg, file=sys.stderr)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +000088 sys.exit(1)
89
90
91class TestResult(object):
92 """Describes a single DejaGNU test result as emitted in .sum files.
93
94 We are only interested in representing unsuccessful tests. So, only
95 a subset of all the tests are loaded.
96
97 The summary line used to build the test result should have this format:
98
99 attrlist | XPASS: gcc.dg/unroll_1.c (test for excess errors)
100 ^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
101 optional state name description
102 attributes
103
104 Attributes:
105 attrlist: A comma separated list of attributes.
106 Valid values:
107 flaky Indicates that this test may not always fail. These
108 tests are reported, but their presence does not affect
109 the results.
110
111 expire=YYYYMMDD After this date, this test will produce an error
112 whether it is in the manifest or not.
113
114 state: One of UNRESOLVED, XPASS or FAIL.
115 name: File name for the test.
116 description: String describing the test (flags used, dejagnu message, etc)
117 ordinal: Monotonically increasing integer.
118 It is used to keep results for one .exp file sorted
119 by the order the tests were run.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000120 tool: Top-level testsuite name (aka "tool" in DejaGnu parlance) of the test.
121 exp: Name of .exp testsuite file.
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000122 """
123
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000124 def __init__(self, summary_line, ordinal, tool, exp):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000125 try:
126 (self.attrs, summary_line) = SplitAttributesFromSummaryLine(summary_line)
127 try:
128 (self.state,
129 self.name,
130 self.description) = re.match(r'([A-Z]+):\s*(\S+)\s*(.*)',
131 summary_line).groups()
132 except:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000133 print('Failed to parse summary line: "%s"' % summary_line)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000134 raise
135 self.ordinal = ordinal
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000136 self.tool = tool
137 self.exp = exp
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000138 except ValueError:
139 Error('Cannot parse summary line "%s"' % summary_line)
140
141 if self.state not in _VALID_TEST_RESULTS:
142 Error('Invalid test result %s in "%s" (parsed as "%s")' % (
143 self.state, summary_line, self))
144
145 def __lt__(self, other):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000146 if (self.tool != other.tool):
147 return self.tool < other.tool
148 if (self.exp != other.exp):
149 return self.exp < other.exp
150 if (self.name != other.name):
151 return self.name < other.name
152 return self.ordinal < other.ordinal
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000153
154 def __hash__(self):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000155 return (hash(self.state) ^ hash(self.tool) ^ hash(self.exp)
156 ^ hash(self.name) ^ hash(self.description))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000157
158 def __eq__(self, other):
159 return (self.state == other.state and
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000160 self.tool == other.tool and
161 self.exp == other.exp and
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000162 self.name == other.name and
163 self.description == other.description)
164
165 def __ne__(self, other):
166 return not (self == other)
167
168 def __str__(self):
169 attrs = ''
170 if self.attrs:
171 attrs = '%s | ' % self.attrs
172 return '%s%s: %s %s' % (attrs, self.state, self.name, self.description)
173
174 def ExpirationDate(self):
175 # Return a datetime.date object with the expiration date for this
176 # test result. Return None, if no expiration has been set.
177 if re.search(r'expire=', self.attrs):
178 expiration = re.search(r'expire=(\d\d\d\d)(\d\d)(\d\d)', self.attrs)
179 if not expiration:
180 Error('Invalid expire= format in "%s". Must be of the form '
181 '"expire=YYYYMMDD"' % self)
182 return datetime.date(int(expiration.group(1)),
183 int(expiration.group(2)),
184 int(expiration.group(3)))
185 return None
186
187 def HasExpired(self):
188 # Return True if the expiration date of this result has passed.
189 expiration_date = self.ExpirationDate()
190 if expiration_date:
191 now = datetime.date.today()
192 return now > expiration_date
193
194
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000195class ResultSet(set):
196 """Describes a set of DejaGNU test results.
197 This set can be read in from .sum files or emitted as a manifest.
198
199 Attributes:
200 current_tool: Name of the current top-level DejaGnu testsuite.
201 current_exp: Name of the current .exp testsuite file.
202 """
203
204 def __init__(self):
205 super().__init__()
206 self.ResetToolExp()
207
208 def ResetToolExp(self):
209 self.current_tool = None
210 self.current_exp = None
211
212 def MakeTestResult(self, summary_line, ordinal=-1):
213 return TestResult(summary_line, ordinal,
214 self.current_tool, self.current_exp)
215
216 def Print(self, outfile=sys.stdout):
217 current_tool = None
218 current_exp = None
219
220 for result in sorted(self):
221 if current_tool != result.tool:
222 current_tool = result.tool
223 outfile.write(_TOOL_LINE_FORMAT % current_tool)
224 if current_exp != result.exp:
225 current_exp = result.exp
226 outfile.write(_EXP_LINE_FORMAT % current_exp)
227 outfile.write('%s\n' % result)
228
229 outfile.write(_SUMMARY_LINE_FORMAT % 'Results')
230
231
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000232def GetMakefileValue(makefile_name, value_name):
233 if os.path.exists(makefile_name):
234 makefile = open(makefile_name)
235 for line in makefile:
236 if line.startswith(value_name):
237 (_, value) = line.split('=', 1)
238 value = value.strip()
239 makefile.close()
240 return value
241 makefile.close()
242 return None
243
244
245def ValidBuildDirectory(builddir):
246 if (not os.path.exists(builddir) or
247 not os.path.exists('%s/Makefile' % builddir)):
248 return False
249 return True
250
251
252def IsComment(line):
253 """Return True if line is a comment."""
254 return line.startswith('#')
255
256
257def SplitAttributesFromSummaryLine(line):
258 """Splits off attributes from a summary line, if present."""
259 if '|' in line and not _VALID_TEST_RESULTS_REX.match(line):
260 (attrs, line) = line.split('|', 1)
261 attrs = attrs.strip()
262 else:
263 attrs = ''
264 line = line.strip()
265 return (attrs, line)
266
267
268def IsInterestingResult(line):
269 """Return True if line is one of the summary lines we care about."""
270 (_, line) = SplitAttributesFromSummaryLine(line)
271 return bool(_VALID_TEST_RESULTS_REX.match(line))
272
273
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000274def IsToolLine(line):
275 """Return True if line mentions the tool (in DejaGnu terms) for the following tests."""
276 return bool(_TOOL_LINE_REX.match(line))
277
278
279def IsExpLine(line):
280 """Return True if line mentions the .exp file for the following tests."""
281 return bool(_EXP_LINE_REX.match(line))
282
283
284def IsSummaryLine(line):
285 """Return True if line starts .sum footer."""
286 return bool(_SUMMARY_LINE_REX.match(line))
287
288
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000289def IsInclude(line):
290 """Return True if line is an include of another file."""
291 return line.startswith("@include ")
292
293
294def GetIncludeFile(line, includer):
295 """Extract the name of the include file from line."""
296 includer_dir = os.path.dirname(includer)
297 include_file = line[len("@include "):]
298 return os.path.join(includer_dir, include_file.strip())
299
300
301def IsNegativeResult(line):
302 """Return True if line should be removed from the expected results."""
303 return line.startswith("@remove ")
304
305
306def GetNegativeResult(line):
307 """Extract the name of the negative result from line."""
308 line = line[len("@remove "):]
309 return line.strip()
310
311
312def ParseManifestWorker(result_set, manifest_path):
313 """Read manifest_path, adding the contents to result_set."""
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000314 if _OPTIONS.verbosity >= 5:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000315 print('Parsing manifest file %s.' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000316 manifest_file = open(manifest_path)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000317 for orig_line in manifest_file:
318 line = orig_line.strip()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000319 if line == "":
320 pass
321 elif IsComment(line):
322 pass
323 elif IsNegativeResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000324 result_set.remove(result_set.MakeTestResult(GetNegativeResult(line)))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000325 elif IsInclude(line):
326 ParseManifestWorker(result_set, GetIncludeFile(line, manifest_path))
327 elif IsInterestingResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000328 result_set.add(result_set.MakeTestResult(line))
329 elif IsExpLine(orig_line):
330 result_set.current_exp = _EXP_LINE_REX.match(orig_line).groups()[0]
331 elif IsToolLine(orig_line):
332 result_set.current_tool = _TOOL_LINE_REX.match(orig_line).groups()[0]
333 elif IsSummaryLine(orig_line):
334 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000335 else:
336 Error('Unrecognized line in manifest file: %s' % line)
337 manifest_file.close()
338
339
340def ParseManifest(manifest_path):
341 """Create a set of TestResult instances from the given manifest file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000342 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000343 ParseManifestWorker(result_set, manifest_path)
344 return result_set
345
346
347def ParseSummary(sum_fname):
348 """Create a set of TestResult instances from the given summary file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000349 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000350 # ordinal is used when sorting the results so that tests within each
351 # .exp file are kept sorted.
352 ordinal=0
353 sum_file = open(sum_fname)
354 for line in sum_file:
355 if IsInterestingResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000356 result = result_set.MakeTestResult(line, ordinal)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000357 ordinal += 1
358 if result.HasExpired():
359 # Tests that have expired are not added to the set of expected
360 # results. If they are still present in the set of actual results,
361 # they will cause an error to be reported.
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000362 if _OPTIONS.verbosity >= 3:
363 print('WARNING: Expected failure "%s" has expired.' % line.strip())
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000364 continue
365 result_set.add(result)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000366 elif IsExpLine(line):
367 result_set.current_exp = _EXP_LINE_REX.match(line).groups()[0]
368 elif IsToolLine(line):
369 result_set.current_tool = _TOOL_LINE_REX.match(line).groups()[0]
Maxim Kuvyrkovd8951a22021-07-08 08:20:28 +0000370 result_set.current_exp = None
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000371 elif IsSummaryLine(line):
372 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000373 sum_file.close()
374 return result_set
375
376
377def GetManifest(manifest_path):
378 """Build a set of expected failures from the manifest file.
379
380 Each entry in the manifest file should have the format understood
381 by the TestResult constructor.
382
383 If no manifest file exists for this target, it returns an empty set.
384 """
385 if os.path.exists(manifest_path):
386 return ParseManifest(manifest_path)
387 else:
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000388 return ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000389
390
391def CollectSumFiles(builddir):
392 sum_files = []
393 for root, dirs, files in os.walk(builddir):
394 for ignored in ('.svn', '.git'):
395 if ignored in dirs:
396 dirs.remove(ignored)
397 for fname in files:
398 if fname.endswith('.sum'):
399 sum_files.append(os.path.join(root, fname))
400 return sum_files
401
402
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000403def GetResults(sum_files, build_results = None):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000404 """Collect all the test results from the given .sum files."""
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000405 if build_results == None:
406 build_results = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000407 for sum_fname in sum_files:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000408 if _OPTIONS.verbosity >= 3:
409 print('\t%s' % sum_fname)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000410 build_results |= ParseSummary(sum_fname)
411 return build_results
412
413
414def CompareResults(manifest, actual):
415 """Compare sets of results and return two lists:
416 - List of results present in ACTUAL but missing from MANIFEST.
417 - List of results present in MANIFEST but missing from ACTUAL.
418 """
419 # Collect all the actual results not present in the manifest.
420 # Results in this set will be reported as errors.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000421 actual_vs_manifest = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000422 for actual_result in actual:
423 if actual_result not in manifest:
424 actual_vs_manifest.add(actual_result)
425
426 # Collect all the tests in the manifest that were not found
427 # in the actual results.
428 # Results in this set will be reported as warnings (since
429 # they are expected failures that are not failing anymore).
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000430 manifest_vs_actual = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000431 for expected_result in manifest:
432 # Ignore tests marked flaky.
433 if 'flaky' in expected_result.attrs:
434 continue
435 if expected_result not in actual:
436 manifest_vs_actual.add(expected_result)
437
438 return actual_vs_manifest, manifest_vs_actual
439
440
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000441def GetManifestPath(user_provided_must_exist):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000442 """Return the full path to the manifest file."""
443 manifest_path = _OPTIONS.manifest
444 if manifest_path:
445 if user_provided_must_exist and not os.path.exists(manifest_path):
446 Error('Manifest does not exist: %s' % manifest_path)
447 return manifest_path
448 else:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000449 (srcdir, target) = GetBuildData()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000450 if not srcdir:
451 Error('Could not determine the location of GCC\'s source tree. '
452 'The Makefile does not contain a definition for "srcdir".')
453 if not target:
454 Error('Could not determine the target triplet for this build. '
455 'The Makefile does not contain a definition for "target_alias".')
456 return _MANIFEST_PATH_PATTERN % (srcdir, _MANIFEST_SUBDIR, target)
457
458
459def GetBuildData():
460 if not ValidBuildDirectory(_OPTIONS.build_dir):
461 # If we have been given a set of results to use, we may
462 # not be inside a valid GCC build directory. In that case,
463 # the user must provide both a manifest file and a set
464 # of results to check against it.
465 if not _OPTIONS.results or not _OPTIONS.manifest:
466 Error('%s is not a valid GCC top level build directory. '
467 'You must use --manifest and --results to do the validation.' %
468 _OPTIONS.build_dir)
469 else:
470 return None, None
471 srcdir = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'srcdir =')
472 target = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'target_alias=')
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000473 if _OPTIONS.verbosity >= 3:
474 print('Source directory: %s' % srcdir)
475 print('Build target: %s' % target)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000476 return srcdir, target
477
478
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000479def PrintSummary(summary):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000480 summary.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000481
482def GetSumFiles(results, build_dir):
483 if not results:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000484 if _OPTIONS.verbosity >= 3:
485 print('Getting actual results from build directory %s' % build_dir)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000486 sum_files = CollectSumFiles(build_dir)
487 else:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000488 if _OPTIONS.verbosity >= 3:
489 print('Getting actual results from user-provided results')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000490 sum_files = results.split()
491 return sum_files
492
493
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000494def PerformComparison(expected, actual):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000495 actual_vs_expected, expected_vs_actual = CompareResults(expected, actual)
496
497 tests_ok = True
498 if len(actual_vs_expected) > 0:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000499 if _OPTIONS.verbosity >= 3:
500 print('\n\nUnexpected results in this build (new failures)')
501 if _OPTIONS.verbosity >= 1:
502 PrintSummary(actual_vs_expected)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000503 tests_ok = False
504
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000505 if _OPTIONS.verbosity >= 2 and len(expected_vs_actual) > 0:
506 print('\n\nExpected results not present in this build (fixed tests)'
507 '\n\nNOTE: This is not a failure. It just means that these '
508 'tests were expected\nto fail, but either they worked in '
509 'this configuration or they were not\npresent at all.\n')
510 PrintSummary(expected_vs_actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000511
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000512 if tests_ok and _OPTIONS.verbosity >= 3:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000513 print('\nSUCCESS: No unexpected failures.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000514
515 return tests_ok
516
517
518def CheckExpectedResults():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000519 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000520 if _OPTIONS.verbosity >= 3:
521 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000522 manifest = GetManifest(manifest_path)
523 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
524 actual = GetResults(sum_files)
525
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000526 if _OPTIONS.verbosity >= 5:
527 print('\n\nTests expected to fail')
528 PrintSummary(manifest)
529 print('\n\nActual test results')
530 PrintSummary(actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000531
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000532 return PerformComparison(manifest, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000533
534
535def ProduceManifest():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000536 manifest_path = GetManifestPath(False)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000537 if _OPTIONS.verbosity >= 3:
538 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000539 if os.path.exists(manifest_path) and not _OPTIONS.force:
540 Error('Manifest file %s already exists.\nUse --force to overwrite.' %
541 manifest_path)
542
543 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
544 actual = GetResults(sum_files)
545 manifest_file = open(manifest_path, 'w')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000546 actual.Print(manifest_file)
547 actual.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000548 manifest_file.close()
549
550 return True
551
552
553def CompareBuilds():
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000554 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
555 actual = GetResults(sum_files)
556
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000557 clean = ResultSet()
558
559 if _OPTIONS.manifest:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000560 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000561 if _OPTIONS.verbosity >= 3:
562 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000563 clean = GetManifest(manifest_path)
564
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000565 clean_sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.clean_build)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000566 clean = GetResults(clean_sum_files, clean)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000567
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000568 return PerformComparison(clean, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000569
570
571def Main(argv):
572 parser = optparse.OptionParser(usage=__doc__)
573
574 # Keep the following list sorted by option name.
575 parser.add_option('--build_dir', action='store', type='string',
576 dest='build_dir', default='.',
577 help='Build directory to check (default = .)')
578 parser.add_option('--clean_build', action='store', type='string',
579 dest='clean_build', default=None,
580 help='Compare test results from this build against '
581 'those of another (clean) build. Use this option '
582 'when comparing the test results of your patch versus '
583 'the test results of a clean build without your patch. '
584 'You must provide the path to the top directory of your '
585 'clean build.')
586 parser.add_option('--force', action='store_true', dest='force',
587 default=False, help='When used with --produce_manifest, '
588 'it will overwrite an existing manifest file '
589 '(default = False)')
Maxim Kuvyrkova6df4d22021-07-13 09:27:35 +0000590 parser.add_option('--ignore_ERRORs', action='store_true',
591 dest='ignore_ERRORs', default=False,
592 help='Ignore DejaGnu "ERROR: foo" results '
593 '(default = False)')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000594 parser.add_option('--manifest', action='store', type='string',
595 dest='manifest', default=None,
596 help='Name of the manifest file to use (default = '
597 'taken from '
598 'contrib/testsuite-managment/<target_alias>.xfail)')
599 parser.add_option('--produce_manifest', action='store_true',
600 dest='produce_manifest', default=False,
601 help='Produce the manifest for the current '
602 'build (default = False)')
603 parser.add_option('--results', action='store', type='string',
604 dest='results', default=None, help='Space-separated list '
605 'of .sum files with the testing results to check. The '
606 'only content needed from these files are the lines '
607 'starting with FAIL, XPASS or UNRESOLVED (default = '
608 '.sum files collected from the build directory).')
609 parser.add_option('--verbosity', action='store', dest='verbosity',
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000610 type='int', default=3, help='Verbosity level '
611 '(default = 3). Level 0: only error output, this is '
612 'useful in scripting when only the exit code is used. '
613 'Level 1: output unexpected failures. '
614 'Level 2: output unexpected passes. '
615 'Level 3: output helpful information. '
616 'Level 5: output debug information.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000617 global _OPTIONS
618 (_OPTIONS, _) = parser.parse_args(argv[1:])
619
Maxim Kuvyrkova6df4d22021-07-13 09:27:35 +0000620 # Handled test results.
621 global _VALID_TEST_RESULTS
622 global _VALID_TEST_RESULTS_REX
623 _VALID_TEST_RESULTS = [ 'FAIL', 'UNRESOLVED', 'XPASS' ]
624 if not _OPTIONS.ignore_ERRORs:
625 _VALID_TEST_RESULTS.append('ERROR')
626 _VALID_TEST_RESULTS_REX = re.compile("%s" % "|".join(_VALID_TEST_RESULTS))
627
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000628 if _OPTIONS.produce_manifest:
629 retval = ProduceManifest()
630 elif _OPTIONS.clean_build:
631 retval = CompareBuilds()
632 else:
633 retval = CheckExpectedResults()
634
635 if retval:
636 return 0
637 else:
638 return 1
639
640
641if __name__ == '__main__':
642 retval = Main(sys.argv)
643 sys.exit(retval)