blob: 5fa1451516ac00d8dd850fc11824c613b1235e75 [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 Kuvyrkov8704bc12023-05-03 15:03:34 +000063_VALID_TEST_RESULTS = [ 'FAIL', 'UNRESOLVED', 'XPASS', 'ERROR' ]
64# <STATE>: <NAME> <DESCRIPTION"
65_VALID_TEST_RESULTS_REX = re.compile('(%s):\s*(\S+)\s*(.*)'
66 % "|".join(_VALID_TEST_RESULTS))
67
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +000068# Formats of .sum file sections
69_TOOL_LINE_FORMAT = '\t\t=== %s tests ===\n'
Christophe Lyona7d8c4c2023-04-14 12:01:23 +000070_EXP_LINE_FORMAT = '\nRunning %s:%s ...\n'
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +000071_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')
Christophe Lyona7d8c4c2023-04-14 12:01:23 +000075# Match .exp file name, optionally prefixed by a "tool:" name and a
76# path ending with "testsuite/"
77_EXP_LINE_REX = re.compile('^Running (?:.*:)?(?:.*/testsuite/)?(.*\.exp) \.\.\.\n')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +000078_SUMMARY_LINE_REX = re.compile('^\t\t=== (.*) Summary ===\n')
79
Maxim Kuvyrkov59877482021-07-07 11:22:26 +000080# Subdirectory of srcdir in which to find the manifest file.
81_MANIFEST_SUBDIR = 'contrib/testsuite-management'
82
83# Pattern for naming manifest files.
84# The first argument should be the toplevel GCC(/GNU tool) source directory.
85# The second argument is the manifest subdir.
86# The third argument is the manifest target, which defaults to the target
87# triplet used during the build.
88_MANIFEST_PATH_PATTERN = '%s/%s/%s.xfail'
89
90# The options passed to the program.
91_OPTIONS = None
92
93def Error(msg):
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +000094 print('error: %s' % msg, file=sys.stderr)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +000095 sys.exit(1)
96
97
98class TestResult(object):
99 """Describes a single DejaGNU test result as emitted in .sum files.
100
101 We are only interested in representing unsuccessful tests. So, only
102 a subset of all the tests are loaded.
103
104 The summary line used to build the test result should have this format:
105
106 attrlist | XPASS: gcc.dg/unroll_1.c (test for excess errors)
107 ^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
108 optional state name description
109 attributes
110
111 Attributes:
112 attrlist: A comma separated list of attributes.
113 Valid values:
114 flaky Indicates that this test may not always fail. These
115 tests are reported, but their presence does not affect
116 the results.
117
118 expire=YYYYMMDD After this date, this test will produce an error
119 whether it is in the manifest or not.
120
121 state: One of UNRESOLVED, XPASS or FAIL.
122 name: File name for the test.
123 description: String describing the test (flags used, dejagnu message, etc)
124 ordinal: Monotonically increasing integer.
125 It is used to keep results for one .exp file sorted
126 by the order the tests were run.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000127 tool: Top-level testsuite name (aka "tool" in DejaGnu parlance) of the test.
128 exp: Name of .exp testsuite file.
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000129 """
130
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000131 def __init__(self, summary_line, ordinal, tool, exp):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000132 try:
133 (self.attrs, summary_line) = SplitAttributesFromSummaryLine(summary_line)
134 try:
135 (self.state,
136 self.name,
Maxim Kuvyrkov56734ff2021-08-30 14:24:25 +0000137 self.description) = _VALID_TEST_RESULTS_REX.match(summary_line).groups()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000138 except:
Thiago Jung Bauermann7b82a592023-04-22 14:12:06 +0000139 print('Failed to parse summary line: "%s"' % summary_line,
140 file=sys.stderr)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000141 raise
142 self.ordinal = ordinal
Maxim Kuvyrkovf09ab0e2021-08-30 14:19:04 +0000143 if tool == None or exp == None:
144 # .sum file seem to be broken. There was no "tool" and/or "exp"
145 # lines preceding this result.
Thiago Jung Bauermann7b82a592023-04-22 14:12:06 +0000146 print(f'.sum file seems to be broken: tool="{tool}", exp="{exp}", summary_line="{summary_line}"',
147 file=sys.stderr)
Maxim Kuvyrkovf09ab0e2021-08-30 14:19:04 +0000148 raise
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000149 self.tool = tool
150 self.exp = exp
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000151 except ValueError:
152 Error('Cannot parse summary line "%s"' % summary_line)
153
154 if self.state not in _VALID_TEST_RESULTS:
155 Error('Invalid test result %s in "%s" (parsed as "%s")' % (
156 self.state, summary_line, self))
157
158 def __lt__(self, other):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000159 if (self.tool != other.tool):
160 return self.tool < other.tool
161 if (self.exp != other.exp):
162 return self.exp < other.exp
163 if (self.name != other.name):
164 return self.name < other.name
165 return self.ordinal < other.ordinal
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000166
167 def __hash__(self):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000168 return (hash(self.state) ^ hash(self.tool) ^ hash(self.exp)
169 ^ hash(self.name) ^ hash(self.description))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000170
171 def __eq__(self, other):
172 return (self.state == other.state and
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000173 self.tool == other.tool and
174 self.exp == other.exp and
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000175 self.name == other.name and
176 self.description == other.description)
177
178 def __ne__(self, other):
179 return not (self == other)
180
181 def __str__(self):
182 attrs = ''
183 if self.attrs:
184 attrs = '%s | ' % self.attrs
185 return '%s%s: %s %s' % (attrs, self.state, self.name, self.description)
186
187 def ExpirationDate(self):
188 # Return a datetime.date object with the expiration date for this
189 # test result. Return None, if no expiration has been set.
190 if re.search(r'expire=', self.attrs):
191 expiration = re.search(r'expire=(\d\d\d\d)(\d\d)(\d\d)', self.attrs)
192 if not expiration:
193 Error('Invalid expire= format in "%s". Must be of the form '
194 '"expire=YYYYMMDD"' % self)
195 return datetime.date(int(expiration.group(1)),
196 int(expiration.group(2)),
197 int(expiration.group(3)))
198 return None
199
200 def HasExpired(self):
201 # Return True if the expiration date of this result has passed.
202 expiration_date = self.ExpirationDate()
203 if expiration_date:
204 now = datetime.date.today()
205 return now > expiration_date
206
207
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000208class ResultSet(set):
209 """Describes a set of DejaGNU test results.
210 This set can be read in from .sum files or emitted as a manifest.
211
212 Attributes:
213 current_tool: Name of the current top-level DejaGnu testsuite.
214 current_exp: Name of the current .exp testsuite file.
215 """
216
217 def __init__(self):
218 super().__init__()
219 self.ResetToolExp()
220
221 def ResetToolExp(self):
222 self.current_tool = None
223 self.current_exp = None
224
225 def MakeTestResult(self, summary_line, ordinal=-1):
226 return TestResult(summary_line, ordinal,
227 self.current_tool, self.current_exp)
228
229 def Print(self, outfile=sys.stdout):
230 current_tool = None
231 current_exp = None
232
233 for result in sorted(self):
234 if current_tool != result.tool:
235 current_tool = result.tool
236 outfile.write(_TOOL_LINE_FORMAT % current_tool)
237 if current_exp != result.exp:
238 current_exp = result.exp
Christophe Lyona7d8c4c2023-04-14 12:01:23 +0000239 outfile.write(_EXP_LINE_FORMAT % (current_tool, current_exp))
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000240 outfile.write('%s\n' % result)
241
242 outfile.write(_SUMMARY_LINE_FORMAT % 'Results')
243
244
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000245def GetMakefileValue(makefile_name, value_name):
246 if os.path.exists(makefile_name):
247 makefile = open(makefile_name)
248 for line in makefile:
249 if line.startswith(value_name):
250 (_, value) = line.split('=', 1)
251 value = value.strip()
252 makefile.close()
253 return value
254 makefile.close()
255 return None
256
257
258def ValidBuildDirectory(builddir):
259 if (not os.path.exists(builddir) or
260 not os.path.exists('%s/Makefile' % builddir)):
261 return False
262 return True
263
264
265def IsComment(line):
266 """Return True if line is a comment."""
267 return line.startswith('#')
268
269
270def SplitAttributesFromSummaryLine(line):
271 """Splits off attributes from a summary line, if present."""
272 if '|' in line and not _VALID_TEST_RESULTS_REX.match(line):
273 (attrs, line) = line.split('|', 1)
274 attrs = attrs.strip()
275 else:
276 attrs = ''
277 line = line.strip()
278 return (attrs, line)
279
280
281def IsInterestingResult(line):
282 """Return True if line is one of the summary lines we care about."""
283 (_, line) = SplitAttributesFromSummaryLine(line)
284 return bool(_VALID_TEST_RESULTS_REX.match(line))
285
286
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000287def IsToolLine(line):
288 """Return True if line mentions the tool (in DejaGnu terms) for the following tests."""
289 return bool(_TOOL_LINE_REX.match(line))
290
291
292def IsExpLine(line):
293 """Return True if line mentions the .exp file for the following tests."""
294 return bool(_EXP_LINE_REX.match(line))
295
296
297def IsSummaryLine(line):
298 """Return True if line starts .sum footer."""
299 return bool(_SUMMARY_LINE_REX.match(line))
300
301
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000302def IsInclude(line):
303 """Return True if line is an include of another file."""
304 return line.startswith("@include ")
305
306
307def GetIncludeFile(line, includer):
308 """Extract the name of the include file from line."""
309 includer_dir = os.path.dirname(includer)
310 include_file = line[len("@include "):]
311 return os.path.join(includer_dir, include_file.strip())
312
313
314def IsNegativeResult(line):
315 """Return True if line should be removed from the expected results."""
316 return line.startswith("@remove ")
317
318
319def GetNegativeResult(line):
320 """Extract the name of the negative result from line."""
321 line = line[len("@remove "):]
322 return line.strip()
323
324
325def ParseManifestWorker(result_set, manifest_path):
326 """Read manifest_path, adding the contents to result_set."""
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000327 if _OPTIONS.verbosity >= 5:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000328 print('Parsing manifest file %s.' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000329 manifest_file = open(manifest_path)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000330 for orig_line in manifest_file:
331 line = orig_line.strip()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000332 if line == "":
333 pass
334 elif IsComment(line):
335 pass
336 elif IsNegativeResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000337 result_set.remove(result_set.MakeTestResult(GetNegativeResult(line)))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000338 elif IsInclude(line):
339 ParseManifestWorker(result_set, GetIncludeFile(line, manifest_path))
340 elif IsInterestingResult(line):
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000341 result = result_set.MakeTestResult(line)
342 if result.HasExpired():
343 # Ignore expired manifest entries.
344 if _OPTIONS.verbosity >= 3:
345 print('WARNING: Expected failure "%s" has expired.' % line.strip())
346 continue
347 result_set.add(result)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000348 elif IsExpLine(orig_line):
349 result_set.current_exp = _EXP_LINE_REX.match(orig_line).groups()[0]
350 elif IsToolLine(orig_line):
351 result_set.current_tool = _TOOL_LINE_REX.match(orig_line).groups()[0]
352 elif IsSummaryLine(orig_line):
353 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000354 else:
355 Error('Unrecognized line in manifest file: %s' % line)
356 manifest_file.close()
357
358
359def ParseManifest(manifest_path):
360 """Create a set of TestResult instances from the given manifest file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000361 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000362 ParseManifestWorker(result_set, manifest_path)
363 return result_set
364
365
366def ParseSummary(sum_fname):
367 """Create a set of TestResult instances from the given summary file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000368 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000369 # ordinal is used when sorting the results so that tests within each
370 # .exp file are kept sorted.
371 ordinal=0
372 sum_file = open(sum_fname)
373 for line in sum_file:
374 if IsInterestingResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000375 result = result_set.MakeTestResult(line, ordinal)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000376 ordinal += 1
377 if result.HasExpired():
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000378 # ??? What is the use-case for this? How "expiry" annotations are
379 # ??? supposed to be added to .sum results?
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000380 # Tests that have expired are not added to the set of expected
381 # results. If they are still present in the set of actual results,
382 # they will cause an error to be reported.
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000383 if _OPTIONS.verbosity >= 3:
384 print('WARNING: Expected failure "%s" has expired.' % line.strip())
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000385 continue
386 result_set.add(result)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000387 elif IsExpLine(line):
388 result_set.current_exp = _EXP_LINE_REX.match(line).groups()[0]
389 elif IsToolLine(line):
390 result_set.current_tool = _TOOL_LINE_REX.match(line).groups()[0]
Maxim Kuvyrkovd8951a22021-07-08 08:20:28 +0000391 result_set.current_exp = None
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000392 elif IsSummaryLine(line):
393 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000394 sum_file.close()
395 return result_set
396
397
398def GetManifest(manifest_path):
399 """Build a set of expected failures from the manifest file.
400
401 Each entry in the manifest file should have the format understood
402 by the TestResult constructor.
403
404 If no manifest file exists for this target, it returns an empty set.
405 """
406 if os.path.exists(manifest_path):
407 return ParseManifest(manifest_path)
408 else:
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000409 return ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000410
411
412def CollectSumFiles(builddir):
413 sum_files = []
414 for root, dirs, files in os.walk(builddir):
415 for ignored in ('.svn', '.git'):
416 if ignored in dirs:
417 dirs.remove(ignored)
418 for fname in files:
419 if fname.endswith('.sum'):
420 sum_files.append(os.path.join(root, fname))
421 return sum_files
422
423
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000424def GetResults(sum_files, build_results = None):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000425 """Collect all the test results from the given .sum files."""
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000426 if build_results == None:
427 build_results = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000428 for sum_fname in sum_files:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000429 if _OPTIONS.verbosity >= 3:
430 print('\t%s' % sum_fname)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000431 build_results |= ParseSummary(sum_fname)
432 return build_results
433
434
435def CompareResults(manifest, actual):
436 """Compare sets of results and return two lists:
437 - List of results present in ACTUAL but missing from MANIFEST.
438 - List of results present in MANIFEST but missing from ACTUAL.
439 """
440 # Collect all the actual results not present in the manifest.
441 # Results in this set will be reported as errors.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000442 actual_vs_manifest = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000443 for actual_result in actual:
444 if actual_result not in manifest:
445 actual_vs_manifest.add(actual_result)
446
447 # Collect all the tests in the manifest that were not found
448 # in the actual results.
449 # Results in this set will be reported as warnings (since
450 # they are expected failures that are not failing anymore).
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000451 manifest_vs_actual = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000452 for expected_result in manifest:
453 # Ignore tests marked flaky.
454 if 'flaky' in expected_result.attrs:
455 continue
456 if expected_result not in actual:
457 manifest_vs_actual.add(expected_result)
458
459 return actual_vs_manifest, manifest_vs_actual
460
461
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000462def GetManifestPath(user_provided_must_exist):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000463 """Return the full path to the manifest file."""
464 manifest_path = _OPTIONS.manifest
465 if manifest_path:
466 if user_provided_must_exist and not os.path.exists(manifest_path):
467 Error('Manifest does not exist: %s' % manifest_path)
468 return manifest_path
469 else:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000470 (srcdir, target) = GetBuildData()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000471 if not srcdir:
472 Error('Could not determine the location of GCC\'s source tree. '
473 'The Makefile does not contain a definition for "srcdir".')
474 if not target:
475 Error('Could not determine the target triplet for this build. '
476 'The Makefile does not contain a definition for "target_alias".')
477 return _MANIFEST_PATH_PATTERN % (srcdir, _MANIFEST_SUBDIR, target)
478
479
480def GetBuildData():
481 if not ValidBuildDirectory(_OPTIONS.build_dir):
482 # If we have been given a set of results to use, we may
483 # not be inside a valid GCC build directory. In that case,
484 # the user must provide both a manifest file and a set
485 # of results to check against it.
486 if not _OPTIONS.results or not _OPTIONS.manifest:
487 Error('%s is not a valid GCC top level build directory. '
488 'You must use --manifest and --results to do the validation.' %
489 _OPTIONS.build_dir)
490 else:
491 return None, None
492 srcdir = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'srcdir =')
493 target = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'target_alias=')
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000494 if _OPTIONS.verbosity >= 3:
495 print('Source directory: %s' % srcdir)
496 print('Build target: %s' % target)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000497 return srcdir, target
498
499
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000500def PrintSummary(summary):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000501 summary.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000502
503def GetSumFiles(results, build_dir):
504 if not results:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000505 if _OPTIONS.verbosity >= 3:
506 print('Getting actual results from build directory %s' % build_dir)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000507 sum_files = CollectSumFiles(build_dir)
508 else:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000509 if _OPTIONS.verbosity >= 3:
510 print('Getting actual results from user-provided results')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000511 sum_files = results.split()
512 return sum_files
513
514
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000515def PerformComparison(expected, actual):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000516 actual_vs_expected, expected_vs_actual = CompareResults(expected, actual)
517
518 tests_ok = True
519 if len(actual_vs_expected) > 0:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000520 if _OPTIONS.verbosity >= 3:
521 print('\n\nUnexpected results in this build (new failures)')
522 if _OPTIONS.verbosity >= 1:
523 PrintSummary(actual_vs_expected)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000524 tests_ok = False
525
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000526 if _OPTIONS.verbosity >= 2 and len(expected_vs_actual) > 0:
527 print('\n\nExpected results not present in this build (fixed tests)'
528 '\n\nNOTE: This is not a failure. It just means that these '
529 'tests were expected\nto fail, but either they worked in '
530 'this configuration or they were not\npresent at all.\n')
531 PrintSummary(expected_vs_actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000532
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000533 if tests_ok and _OPTIONS.verbosity >= 3:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000534 print('\nSUCCESS: No unexpected failures.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000535
536 return tests_ok
537
538
539def CheckExpectedResults():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000540 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000541 if _OPTIONS.verbosity >= 3:
542 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000543 manifest = GetManifest(manifest_path)
544 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
545 actual = GetResults(sum_files)
546
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000547 if _OPTIONS.verbosity >= 5:
548 print('\n\nTests expected to fail')
549 PrintSummary(manifest)
550 print('\n\nActual test results')
551 PrintSummary(actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000552
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000553 return PerformComparison(manifest, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000554
555
556def ProduceManifest():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000557 manifest_path = GetManifestPath(False)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000558 if _OPTIONS.verbosity >= 3:
559 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000560 if os.path.exists(manifest_path) and not _OPTIONS.force:
561 Error('Manifest file %s already exists.\nUse --force to overwrite.' %
562 manifest_path)
563
564 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
565 actual = GetResults(sum_files)
566 manifest_file = open(manifest_path, 'w')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000567 actual.Print(manifest_file)
568 actual.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000569 manifest_file.close()
570
571 return True
572
573
574def CompareBuilds():
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000575 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
576 actual = GetResults(sum_files)
577
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000578 clean = ResultSet()
579
580 if _OPTIONS.manifest:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000581 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000582 if _OPTIONS.verbosity >= 3:
583 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000584 clean = GetManifest(manifest_path)
585
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000586 clean_sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.clean_build)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000587 clean = GetResults(clean_sum_files, clean)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000588
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000589 return PerformComparison(clean, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000590
591
592def Main(argv):
593 parser = optparse.OptionParser(usage=__doc__)
594
595 # Keep the following list sorted by option name.
596 parser.add_option('--build_dir', action='store', type='string',
597 dest='build_dir', default='.',
598 help='Build directory to check (default = .)')
599 parser.add_option('--clean_build', action='store', type='string',
600 dest='clean_build', default=None,
601 help='Compare test results from this build against '
602 'those of another (clean) build. Use this option '
603 'when comparing the test results of your patch versus '
604 'the test results of a clean build without your patch. '
605 'You must provide the path to the top directory of your '
606 'clean build.')
607 parser.add_option('--force', action='store_true', dest='force',
608 default=False, help='When used with --produce_manifest, '
609 'it will overwrite an existing manifest file '
610 '(default = False)')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000611 parser.add_option('--manifest', action='store', type='string',
612 dest='manifest', default=None,
613 help='Name of the manifest file to use (default = '
614 'taken from '
615 'contrib/testsuite-managment/<target_alias>.xfail)')
616 parser.add_option('--produce_manifest', action='store_true',
617 dest='produce_manifest', default=False,
618 help='Produce the manifest for the current '
619 'build (default = False)')
620 parser.add_option('--results', action='store', type='string',
621 dest='results', default=None, help='Space-separated list '
622 'of .sum files with the testing results to check. The '
623 'only content needed from these files are the lines '
624 'starting with FAIL, XPASS or UNRESOLVED (default = '
625 '.sum files collected from the build directory).')
626 parser.add_option('--verbosity', action='store', dest='verbosity',
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000627 type='int', default=3, help='Verbosity level '
628 '(default = 3). Level 0: only error output, this is '
629 'useful in scripting when only the exit code is used. '
630 'Level 1: output unexpected failures. '
631 'Level 2: output unexpected passes. '
632 'Level 3: output helpful information. '
633 'Level 5: output debug information.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000634 global _OPTIONS
635 (_OPTIONS, _) = parser.parse_args(argv[1:])
636
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000637 if _OPTIONS.produce_manifest:
638 retval = ProduceManifest()
639 elif _OPTIONS.clean_build:
640 retval = CompareBuilds()
641 else:
642 retval = CheckExpectedResults()
643
644 if retval:
645 return 0
646 else:
Maxim Kuvyrkov972bb812021-08-30 14:18:09 +0000647 return 2
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000648
649
650if __name__ == '__main__':
651 retval = Main(sys.argv)
652 sys.exit(retval)