blob: 0424ccda9c35df281acd50bfcf827d7a76fd7e8d [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.
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000215 testsuites: A set of (tool, exp) tuples representing encountered testsuites.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000216 """
217
218 def __init__(self):
219 super().__init__()
220 self.ResetToolExp()
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000221 self.testsuites=set()
222
223 def update(self, other):
224 super().update(other)
225 self.testsuites.update(other.testsuites)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000226
227 def ResetToolExp(self):
228 self.current_tool = None
229 self.current_exp = None
230
231 def MakeTestResult(self, summary_line, ordinal=-1):
232 return TestResult(summary_line, ordinal,
233 self.current_tool, self.current_exp)
234
235 def Print(self, outfile=sys.stdout):
236 current_tool = None
237 current_exp = None
238
239 for result in sorted(self):
240 if current_tool != result.tool:
241 current_tool = result.tool
242 outfile.write(_TOOL_LINE_FORMAT % current_tool)
243 if current_exp != result.exp:
244 current_exp = result.exp
Christophe Lyona7d8c4c2023-04-14 12:01:23 +0000245 outfile.write(_EXP_LINE_FORMAT % (current_tool, current_exp))
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000246 outfile.write('%s\n' % result)
247
248 outfile.write(_SUMMARY_LINE_FORMAT % 'Results')
249
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000250 # Check if testsuite of expected_result is present in current results.
251 # This is used to compare partial test results against a full manifest.
252 def HasTestsuite(self, expected_result):
253 return (expected_result.tool, expected_result.exp) in self.testsuites
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000254
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000255def GetMakefileValue(makefile_name, value_name):
256 if os.path.exists(makefile_name):
257 makefile = open(makefile_name)
258 for line in makefile:
259 if line.startswith(value_name):
260 (_, value) = line.split('=', 1)
261 value = value.strip()
262 makefile.close()
263 return value
264 makefile.close()
265 return None
266
267
268def ValidBuildDirectory(builddir):
269 if (not os.path.exists(builddir) or
270 not os.path.exists('%s/Makefile' % builddir)):
271 return False
272 return True
273
274
275def IsComment(line):
276 """Return True if line is a comment."""
277 return line.startswith('#')
278
279
280def SplitAttributesFromSummaryLine(line):
281 """Splits off attributes from a summary line, if present."""
282 if '|' in line and not _VALID_TEST_RESULTS_REX.match(line):
283 (attrs, line) = line.split('|', 1)
284 attrs = attrs.strip()
285 else:
286 attrs = ''
287 line = line.strip()
288 return (attrs, line)
289
290
291def IsInterestingResult(line):
292 """Return True if line is one of the summary lines we care about."""
293 (_, line) = SplitAttributesFromSummaryLine(line)
294 return bool(_VALID_TEST_RESULTS_REX.match(line))
295
296
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000297def IsToolLine(line):
298 """Return True if line mentions the tool (in DejaGnu terms) for the following tests."""
299 return bool(_TOOL_LINE_REX.match(line))
300
301
302def IsExpLine(line):
303 """Return True if line mentions the .exp file for the following tests."""
304 return bool(_EXP_LINE_REX.match(line))
305
306
307def IsSummaryLine(line):
308 """Return True if line starts .sum footer."""
309 return bool(_SUMMARY_LINE_REX.match(line))
310
311
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000312def IsInclude(line):
313 """Return True if line is an include of another file."""
314 return line.startswith("@include ")
315
316
317def GetIncludeFile(line, includer):
318 """Extract the name of the include file from line."""
319 includer_dir = os.path.dirname(includer)
320 include_file = line[len("@include "):]
321 return os.path.join(includer_dir, include_file.strip())
322
323
324def IsNegativeResult(line):
325 """Return True if line should be removed from the expected results."""
326 return line.startswith("@remove ")
327
328
329def GetNegativeResult(line):
330 """Extract the name of the negative result from line."""
331 line = line[len("@remove "):]
332 return line.strip()
333
334
335def ParseManifestWorker(result_set, manifest_path):
336 """Read manifest_path, adding the contents to result_set."""
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000337 if _OPTIONS.verbosity >= 5:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000338 print('Parsing manifest file %s.' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000339 manifest_file = open(manifest_path)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000340 for orig_line in manifest_file:
341 line = orig_line.strip()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000342 if line == "":
343 pass
344 elif IsComment(line):
345 pass
346 elif IsNegativeResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000347 result_set.remove(result_set.MakeTestResult(GetNegativeResult(line)))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000348 elif IsInclude(line):
349 ParseManifestWorker(result_set, GetIncludeFile(line, manifest_path))
350 elif IsInterestingResult(line):
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000351 result = result_set.MakeTestResult(line)
352 if result.HasExpired():
353 # Ignore expired manifest entries.
354 if _OPTIONS.verbosity >= 3:
355 print('WARNING: Expected failure "%s" has expired.' % line.strip())
356 continue
357 result_set.add(result)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000358 elif IsExpLine(orig_line):
359 result_set.current_exp = _EXP_LINE_REX.match(orig_line).groups()[0]
360 elif IsToolLine(orig_line):
361 result_set.current_tool = _TOOL_LINE_REX.match(orig_line).groups()[0]
362 elif IsSummaryLine(orig_line):
363 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000364 else:
365 Error('Unrecognized line in manifest file: %s' % line)
366 manifest_file.close()
367
368
369def ParseManifest(manifest_path):
370 """Create a set of TestResult instances from the given manifest file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000371 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000372 ParseManifestWorker(result_set, manifest_path)
373 return result_set
374
375
376def ParseSummary(sum_fname):
377 """Create a set of TestResult instances from the given summary file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000378 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000379 # ordinal is used when sorting the results so that tests within each
380 # .exp file are kept sorted.
381 ordinal=0
382 sum_file = open(sum_fname)
383 for line in sum_file:
384 if IsInterestingResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000385 result = result_set.MakeTestResult(line, ordinal)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000386 ordinal += 1
387 if result.HasExpired():
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000388 # ??? What is the use-case for this? How "expiry" annotations are
389 # ??? supposed to be added to .sum results?
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000390 # Tests that have expired are not added to the set of expected
391 # results. If they are still present in the set of actual results,
392 # they will cause an error to be reported.
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000393 if _OPTIONS.verbosity >= 3:
394 print('WARNING: Expected failure "%s" has expired.' % line.strip())
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000395 continue
396 result_set.add(result)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000397 elif IsExpLine(line):
398 result_set.current_exp = _EXP_LINE_REX.match(line).groups()[0]
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000399 result_set.testsuites.add((result_set.current_tool,
400 result_set.current_exp))
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000401 elif IsToolLine(line):
402 result_set.current_tool = _TOOL_LINE_REX.match(line).groups()[0]
Maxim Kuvyrkovd8951a22021-07-08 08:20:28 +0000403 result_set.current_exp = None
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000404 elif IsSummaryLine(line):
405 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000406 sum_file.close()
407 return result_set
408
409
410def GetManifest(manifest_path):
411 """Build a set of expected failures from the manifest file.
412
413 Each entry in the manifest file should have the format understood
414 by the TestResult constructor.
415
416 If no manifest file exists for this target, it returns an empty set.
417 """
418 if os.path.exists(manifest_path):
419 return ParseManifest(manifest_path)
420 else:
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000421 return ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000422
423
424def CollectSumFiles(builddir):
425 sum_files = []
426 for root, dirs, files in os.walk(builddir):
427 for ignored in ('.svn', '.git'):
428 if ignored in dirs:
429 dirs.remove(ignored)
430 for fname in files:
431 if fname.endswith('.sum'):
432 sum_files.append(os.path.join(root, fname))
433 return sum_files
434
435
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000436def GetResults(sum_files, build_results = None):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000437 """Collect all the test results from the given .sum files."""
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000438 if build_results == None:
439 build_results = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000440 for sum_fname in sum_files:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000441 if _OPTIONS.verbosity >= 3:
442 print('\t%s' % sum_fname)
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000443 build_results.update(ParseSummary(sum_fname))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000444 return build_results
445
446
447def CompareResults(manifest, actual):
448 """Compare sets of results and return two lists:
449 - List of results present in ACTUAL but missing from MANIFEST.
450 - List of results present in MANIFEST but missing from ACTUAL.
451 """
452 # Collect all the actual results not present in the manifest.
453 # Results in this set will be reported as errors.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000454 actual_vs_manifest = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000455 for actual_result in actual:
456 if actual_result not in manifest:
457 actual_vs_manifest.add(actual_result)
458
459 # Collect all the tests in the manifest that were not found
460 # in the actual results.
461 # Results in this set will be reported as warnings (since
462 # they are expected failures that are not failing anymore).
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000463 manifest_vs_actual = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000464 for expected_result in manifest:
465 # Ignore tests marked flaky.
466 if 'flaky' in expected_result.attrs:
467 continue
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000468 # We try to support comparing partial results vs full manifest
469 # (e.g., manifest has failures for gcc, g++, gfortran, but we ran only
470 # g++ testsuite). To achieve this we record encountered testsuites in
471 # actual.testsuites set, and then we check it here using HasTestsuite().
472 if expected_result not in actual and actual.HasTestsuite(expected_result):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000473 manifest_vs_actual.add(expected_result)
474
475 return actual_vs_manifest, manifest_vs_actual
476
477
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000478def GetManifestPath(user_provided_must_exist):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000479 """Return the full path to the manifest file."""
480 manifest_path = _OPTIONS.manifest
481 if manifest_path:
482 if user_provided_must_exist and not os.path.exists(manifest_path):
483 Error('Manifest does not exist: %s' % manifest_path)
484 return manifest_path
485 else:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000486 (srcdir, target) = GetBuildData()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000487 if not srcdir:
488 Error('Could not determine the location of GCC\'s source tree. '
489 'The Makefile does not contain a definition for "srcdir".')
490 if not target:
491 Error('Could not determine the target triplet for this build. '
492 'The Makefile does not contain a definition for "target_alias".')
493 return _MANIFEST_PATH_PATTERN % (srcdir, _MANIFEST_SUBDIR, target)
494
495
496def GetBuildData():
497 if not ValidBuildDirectory(_OPTIONS.build_dir):
498 # If we have been given a set of results to use, we may
499 # not be inside a valid GCC build directory. In that case,
500 # the user must provide both a manifest file and a set
501 # of results to check against it.
502 if not _OPTIONS.results or not _OPTIONS.manifest:
503 Error('%s is not a valid GCC top level build directory. '
504 'You must use --manifest and --results to do the validation.' %
505 _OPTIONS.build_dir)
506 else:
507 return None, None
508 srcdir = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'srcdir =')
509 target = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'target_alias=')
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000510 if _OPTIONS.verbosity >= 3:
511 print('Source directory: %s' % srcdir)
512 print('Build target: %s' % target)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000513 return srcdir, target
514
515
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000516def PrintSummary(summary):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000517 summary.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000518
519def GetSumFiles(results, build_dir):
520 if not results:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000521 if _OPTIONS.verbosity >= 3:
522 print('Getting actual results from build directory %s' % build_dir)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000523 sum_files = CollectSumFiles(build_dir)
524 else:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000525 if _OPTIONS.verbosity >= 3:
526 print('Getting actual results from user-provided results')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000527 sum_files = results.split()
528 return sum_files
529
530
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000531def PerformComparison(expected, actual):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000532 actual_vs_expected, expected_vs_actual = CompareResults(expected, actual)
533
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000534 if _OPTIONS.inverse_match:
535 # Switch results if inverse comparison is requested.
536 # This is useful in detecting flaky tests that FAILed in expected set,
537 # but PASSed in actual set.
538 actual_vs_expected, expected_vs_actual \
539 = expected_vs_actual, expected_vs_actual
540
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000541 tests_ok = True
542 if len(actual_vs_expected) > 0:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000543 if _OPTIONS.verbosity >= 3:
544 print('\n\nUnexpected results in this build (new failures)')
545 if _OPTIONS.verbosity >= 1:
546 PrintSummary(actual_vs_expected)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000547 tests_ok = False
548
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000549 if _OPTIONS.verbosity >= 2 and len(expected_vs_actual) > 0:
550 print('\n\nExpected results not present in this build (fixed tests)'
551 '\n\nNOTE: This is not a failure. It just means that these '
552 'tests were expected\nto fail, but either they worked in '
553 'this configuration or they were not\npresent at all.\n')
554 PrintSummary(expected_vs_actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000555
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000556 if tests_ok and _OPTIONS.verbosity >= 3:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000557 print('\nSUCCESS: No unexpected failures.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000558
559 return tests_ok
560
561
562def CheckExpectedResults():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000563 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000564 if _OPTIONS.verbosity >= 3:
565 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000566 manifest = GetManifest(manifest_path)
567 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
568 actual = GetResults(sum_files)
569
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000570 if _OPTIONS.verbosity >= 5:
571 print('\n\nTests expected to fail')
572 PrintSummary(manifest)
573 print('\n\nActual test results')
574 PrintSummary(actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000575
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000576 return PerformComparison(manifest, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000577
578
579def ProduceManifest():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000580 manifest_path = GetManifestPath(False)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000581 if _OPTIONS.verbosity >= 3:
582 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000583 if os.path.exists(manifest_path) and not _OPTIONS.force:
584 Error('Manifest file %s already exists.\nUse --force to overwrite.' %
585 manifest_path)
586
587 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
588 actual = GetResults(sum_files)
589 manifest_file = open(manifest_path, 'w')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000590 actual.Print(manifest_file)
591 actual.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000592 manifest_file.close()
593
594 return True
595
596
597def CompareBuilds():
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000598 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
599 actual = GetResults(sum_files)
600
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000601 clean = ResultSet()
602
603 if _OPTIONS.manifest:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000604 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000605 if _OPTIONS.verbosity >= 3:
606 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000607 clean = GetManifest(manifest_path)
608
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000609 clean_sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.clean_build)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000610 clean = GetResults(clean_sum_files, clean)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000611
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000612 return PerformComparison(clean, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000613
614
615def Main(argv):
616 parser = optparse.OptionParser(usage=__doc__)
617
618 # Keep the following list sorted by option name.
619 parser.add_option('--build_dir', action='store', type='string',
620 dest='build_dir', default='.',
621 help='Build directory to check (default = .)')
622 parser.add_option('--clean_build', action='store', type='string',
623 dest='clean_build', default=None,
624 help='Compare test results from this build against '
625 'those of another (clean) build. Use this option '
626 'when comparing the test results of your patch versus '
627 'the test results of a clean build without your patch. '
628 'You must provide the path to the top directory of your '
629 'clean build.')
630 parser.add_option('--force', action='store_true', dest='force',
631 default=False, help='When used with --produce_manifest, '
632 'it will overwrite an existing manifest file '
633 '(default = False)')
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000634 parser.add_option('--inverse_match', action='store_true',
635 dest='inverse_match', default=False,
636 help='Inverse result sets in comparison. '
637 'Output unexpected passes as unexpected failures and '
638 'unexpected failures as unexpected passes. '
639 'This is used to catch FAIL->PASS flaky tests. '
640 '(default = False)')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000641 parser.add_option('--manifest', action='store', type='string',
642 dest='manifest', default=None,
643 help='Name of the manifest file to use (default = '
644 'taken from '
645 'contrib/testsuite-managment/<target_alias>.xfail)')
646 parser.add_option('--produce_manifest', action='store_true',
647 dest='produce_manifest', default=False,
648 help='Produce the manifest for the current '
649 'build (default = False)')
650 parser.add_option('--results', action='store', type='string',
651 dest='results', default=None, help='Space-separated list '
652 'of .sum files with the testing results to check. The '
653 'only content needed from these files are the lines '
654 'starting with FAIL, XPASS or UNRESOLVED (default = '
655 '.sum files collected from the build directory).')
656 parser.add_option('--verbosity', action='store', dest='verbosity',
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000657 type='int', default=3, help='Verbosity level '
658 '(default = 3). Level 0: only error output, this is '
659 'useful in scripting when only the exit code is used. '
660 'Level 1: output unexpected failures. '
661 'Level 2: output unexpected passes. '
662 'Level 3: output helpful information. '
663 'Level 5: output debug information.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000664 global _OPTIONS
665 (_OPTIONS, _) = parser.parse_args(argv[1:])
666
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000667 if _OPTIONS.produce_manifest:
668 retval = ProduceManifest()
669 elif _OPTIONS.clean_build:
670 retval = CompareBuilds()
671 else:
672 retval = CheckExpectedResults()
673
674 if retval:
675 return 0
676 else:
Maxim Kuvyrkov972bb812021-08-30 14:18:09 +0000677 return 2
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000678
679
680if __name__ == '__main__':
681 retval = Main(sys.argv)
682 sys.exit(retval)