blob: 5375c774e83d5766b78c3f96a9015607359ab363 [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/"
Maxim Kuvyrkova5358522023-05-19 12:46:49 +000077_EXP_LINE_REX = re.compile('^Running (?:.*:)?(?:.*/testsuite/)?(.*) \.\.\.\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
Maxim Kuvyrkov7df81782023-05-25 06:42:06 +0000171 # Note that we don't include "attrs" in this comparison. This means that
172 # result entries "FAIL: test" and "flaky | FAIL: test" are considered
173 # the same. Therefore the ResultSet will preserve only the first occurence.
174 # In practice this means that flaky entries should preceed expected fails
175 # entries.
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000176 def __eq__(self, other):
177 return (self.state == other.state and
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000178 self.tool == other.tool and
179 self.exp == other.exp and
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000180 self.name == other.name and
181 self.description == other.description)
182
183 def __ne__(self, other):
184 return not (self == other)
185
186 def __str__(self):
187 attrs = ''
188 if self.attrs:
189 attrs = '%s | ' % self.attrs
190 return '%s%s: %s %s' % (attrs, self.state, self.name, self.description)
191
192 def ExpirationDate(self):
193 # Return a datetime.date object with the expiration date for this
194 # test result. Return None, if no expiration has been set.
195 if re.search(r'expire=', self.attrs):
196 expiration = re.search(r'expire=(\d\d\d\d)(\d\d)(\d\d)', self.attrs)
197 if not expiration:
198 Error('Invalid expire= format in "%s". Must be of the form '
199 '"expire=YYYYMMDD"' % self)
200 return datetime.date(int(expiration.group(1)),
201 int(expiration.group(2)),
202 int(expiration.group(3)))
203 return None
204
205 def HasExpired(self):
206 # Return True if the expiration date of this result has passed.
207 expiration_date = self.ExpirationDate()
208 if expiration_date:
Maxim Kuvyrkov158e61d2023-05-25 12:18:30 +0000209 return _OPTIONS.expiry_today_date > expiration_date
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000210
211
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000212class ResultSet(set):
213 """Describes a set of DejaGNU test results.
214 This set can be read in from .sum files or emitted as a manifest.
215
216 Attributes:
217 current_tool: Name of the current top-level DejaGnu testsuite.
218 current_exp: Name of the current .exp testsuite file.
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000219 testsuites: A set of (tool, exp) tuples representing encountered testsuites.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000220 """
221
222 def __init__(self):
223 super().__init__()
224 self.ResetToolExp()
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000225 self.testsuites=set()
226
227 def update(self, other):
228 super().update(other)
229 self.testsuites.update(other.testsuites)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000230
231 def ResetToolExp(self):
232 self.current_tool = None
233 self.current_exp = None
234
235 def MakeTestResult(self, summary_line, ordinal=-1):
236 return TestResult(summary_line, ordinal,
237 self.current_tool, self.current_exp)
238
239 def Print(self, outfile=sys.stdout):
240 current_tool = None
241 current_exp = None
242
243 for result in sorted(self):
244 if current_tool != result.tool:
245 current_tool = result.tool
246 outfile.write(_TOOL_LINE_FORMAT % current_tool)
247 if current_exp != result.exp:
248 current_exp = result.exp
Christophe Lyona7d8c4c2023-04-14 12:01:23 +0000249 outfile.write(_EXP_LINE_FORMAT % (current_tool, current_exp))
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000250 outfile.write('%s\n' % result)
251
252 outfile.write(_SUMMARY_LINE_FORMAT % 'Results')
253
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000254 # Check if testsuite of expected_result is present in current results.
255 # This is used to compare partial test results against a full manifest.
256 def HasTestsuite(self, expected_result):
257 return (expected_result.tool, expected_result.exp) in self.testsuites
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000258
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000259def GetMakefileValue(makefile_name, value_name):
260 if os.path.exists(makefile_name):
261 makefile = open(makefile_name)
262 for line in makefile:
263 if line.startswith(value_name):
264 (_, value) = line.split('=', 1)
265 value = value.strip()
266 makefile.close()
267 return value
268 makefile.close()
269 return None
270
271
272def ValidBuildDirectory(builddir):
273 if (not os.path.exists(builddir) or
274 not os.path.exists('%s/Makefile' % builddir)):
275 return False
276 return True
277
278
279def IsComment(line):
280 """Return True if line is a comment."""
281 return line.startswith('#')
282
283
284def SplitAttributesFromSummaryLine(line):
285 """Splits off attributes from a summary line, if present."""
286 if '|' in line and not _VALID_TEST_RESULTS_REX.match(line):
287 (attrs, line) = line.split('|', 1)
288 attrs = attrs.strip()
289 else:
290 attrs = ''
291 line = line.strip()
292 return (attrs, line)
293
294
295def IsInterestingResult(line):
296 """Return True if line is one of the summary lines we care about."""
297 (_, line) = SplitAttributesFromSummaryLine(line)
298 return bool(_VALID_TEST_RESULTS_REX.match(line))
299
300
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000301def IsToolLine(line):
302 """Return True if line mentions the tool (in DejaGnu terms) for the following tests."""
303 return bool(_TOOL_LINE_REX.match(line))
304
305
306def IsExpLine(line):
307 """Return True if line mentions the .exp file for the following tests."""
308 return bool(_EXP_LINE_REX.match(line))
309
310
311def IsSummaryLine(line):
312 """Return True if line starts .sum footer."""
313 return bool(_SUMMARY_LINE_REX.match(line))
314
315
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000316def IsInclude(line):
317 """Return True if line is an include of another file."""
318 return line.startswith("@include ")
319
320
321def GetIncludeFile(line, includer):
322 """Extract the name of the include file from line."""
323 includer_dir = os.path.dirname(includer)
324 include_file = line[len("@include "):]
325 return os.path.join(includer_dir, include_file.strip())
326
327
328def IsNegativeResult(line):
329 """Return True if line should be removed from the expected results."""
330 return line.startswith("@remove ")
331
332
333def GetNegativeResult(line):
334 """Extract the name of the negative result from line."""
335 line = line[len("@remove "):]
336 return line.strip()
337
338
339def ParseManifestWorker(result_set, manifest_path):
340 """Read manifest_path, adding the contents to result_set."""
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000341 if _OPTIONS.verbosity >= 5:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000342 print('Parsing manifest file %s.' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000343 manifest_file = open(manifest_path)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000344 for orig_line in manifest_file:
345 line = orig_line.strip()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000346 if line == "":
347 pass
348 elif IsComment(line):
349 pass
350 elif IsNegativeResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000351 result_set.remove(result_set.MakeTestResult(GetNegativeResult(line)))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000352 elif IsInclude(line):
353 ParseManifestWorker(result_set, GetIncludeFile(line, manifest_path))
354 elif IsInterestingResult(line):
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000355 result = result_set.MakeTestResult(line)
356 if result.HasExpired():
357 # Ignore expired manifest entries.
Maxim Kuvyrkov7df81782023-05-25 06:42:06 +0000358 if _OPTIONS.verbosity >= 4:
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000359 print('WARNING: Expected failure "%s" has expired.' % line.strip())
360 continue
361 result_set.add(result)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000362 elif IsExpLine(orig_line):
363 result_set.current_exp = _EXP_LINE_REX.match(orig_line).groups()[0]
364 elif IsToolLine(orig_line):
365 result_set.current_tool = _TOOL_LINE_REX.match(orig_line).groups()[0]
366 elif IsSummaryLine(orig_line):
367 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000368 else:
369 Error('Unrecognized line in manifest file: %s' % line)
370 manifest_file.close()
371
372
373def ParseManifest(manifest_path):
374 """Create a set of TestResult instances from the given manifest file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000375 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000376 ParseManifestWorker(result_set, manifest_path)
377 return result_set
378
379
380def ParseSummary(sum_fname):
381 """Create a set of TestResult instances from the given summary file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000382 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000383 # ordinal is used when sorting the results so that tests within each
384 # .exp file are kept sorted.
385 ordinal=0
386 sum_file = open(sum_fname)
387 for line in sum_file:
388 if IsInterestingResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000389 result = result_set.MakeTestResult(line, ordinal)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000390 ordinal += 1
391 if result.HasExpired():
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000392 # ??? What is the use-case for this? How "expiry" annotations are
393 # ??? supposed to be added to .sum results?
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000394 # Tests that have expired are not added to the set of expected
395 # results. If they are still present in the set of actual results,
396 # they will cause an error to be reported.
Maxim Kuvyrkov7df81782023-05-25 06:42:06 +0000397 if _OPTIONS.verbosity >= 4:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000398 print('WARNING: Expected failure "%s" has expired.' % line.strip())
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000399 continue
400 result_set.add(result)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000401 elif IsExpLine(line):
402 result_set.current_exp = _EXP_LINE_REX.match(line).groups()[0]
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000403 result_set.testsuites.add((result_set.current_tool,
404 result_set.current_exp))
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000405 elif IsToolLine(line):
406 result_set.current_tool = _TOOL_LINE_REX.match(line).groups()[0]
Maxim Kuvyrkovd8951a22021-07-08 08:20:28 +0000407 result_set.current_exp = None
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000408 elif IsSummaryLine(line):
409 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000410 sum_file.close()
411 return result_set
412
413
414def GetManifest(manifest_path):
415 """Build a set of expected failures from the manifest file.
416
417 Each entry in the manifest file should have the format understood
418 by the TestResult constructor.
419
420 If no manifest file exists for this target, it returns an empty set.
421 """
422 if os.path.exists(manifest_path):
423 return ParseManifest(manifest_path)
424 else:
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000425 return ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000426
427
428def CollectSumFiles(builddir):
429 sum_files = []
430 for root, dirs, files in os.walk(builddir):
431 for ignored in ('.svn', '.git'):
432 if ignored in dirs:
433 dirs.remove(ignored)
434 for fname in files:
435 if fname.endswith('.sum'):
436 sum_files.append(os.path.join(root, fname))
437 return sum_files
438
439
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000440def GetResults(sum_files, build_results = None):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000441 """Collect all the test results from the given .sum files."""
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000442 if build_results == None:
443 build_results = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000444 for sum_fname in sum_files:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000445 if _OPTIONS.verbosity >= 3:
446 print('\t%s' % sum_fname)
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000447 build_results.update(ParseSummary(sum_fname))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000448 return build_results
449
450
451def CompareResults(manifest, actual):
452 """Compare sets of results and return two lists:
453 - List of results present in ACTUAL but missing from MANIFEST.
454 - List of results present in MANIFEST but missing from ACTUAL.
455 """
456 # Collect all the actual results not present in the manifest.
457 # Results in this set will be reported as errors.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000458 actual_vs_manifest = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000459 for actual_result in actual:
460 if actual_result not in manifest:
461 actual_vs_manifest.add(actual_result)
462
463 # Collect all the tests in the manifest that were not found
464 # in the actual results.
465 # Results in this set will be reported as warnings (since
466 # they are expected failures that are not failing anymore).
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000467 manifest_vs_actual = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000468 for expected_result in manifest:
469 # Ignore tests marked flaky.
470 if 'flaky' in expected_result.attrs:
471 continue
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000472 # We try to support comparing partial results vs full manifest
473 # (e.g., manifest has failures for gcc, g++, gfortran, but we ran only
474 # g++ testsuite). To achieve this we record encountered testsuites in
475 # actual.testsuites set, and then we check it here using HasTestsuite().
476 if expected_result not in actual and actual.HasTestsuite(expected_result):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000477 manifest_vs_actual.add(expected_result)
478
479 return actual_vs_manifest, manifest_vs_actual
480
481
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000482def GetManifestPath(user_provided_must_exist):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000483 """Return the full path to the manifest file."""
484 manifest_path = _OPTIONS.manifest
485 if manifest_path:
486 if user_provided_must_exist and not os.path.exists(manifest_path):
487 Error('Manifest does not exist: %s' % manifest_path)
488 return manifest_path
489 else:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000490 (srcdir, target) = GetBuildData()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000491 if not srcdir:
492 Error('Could not determine the location of GCC\'s source tree. '
493 'The Makefile does not contain a definition for "srcdir".')
494 if not target:
495 Error('Could not determine the target triplet for this build. '
496 'The Makefile does not contain a definition for "target_alias".')
497 return _MANIFEST_PATH_PATTERN % (srcdir, _MANIFEST_SUBDIR, target)
498
499
500def GetBuildData():
501 if not ValidBuildDirectory(_OPTIONS.build_dir):
502 # If we have been given a set of results to use, we may
503 # not be inside a valid GCC build directory. In that case,
504 # the user must provide both a manifest file and a set
505 # of results to check against it.
506 if not _OPTIONS.results or not _OPTIONS.manifest:
507 Error('%s is not a valid GCC top level build directory. '
508 'You must use --manifest and --results to do the validation.' %
509 _OPTIONS.build_dir)
510 else:
511 return None, None
512 srcdir = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'srcdir =')
513 target = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'target_alias=')
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000514 if _OPTIONS.verbosity >= 3:
515 print('Source directory: %s' % srcdir)
516 print('Build target: %s' % target)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000517 return srcdir, target
518
519
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000520def PrintSummary(summary):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000521 summary.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000522
523def GetSumFiles(results, build_dir):
524 if not results:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000525 if _OPTIONS.verbosity >= 3:
526 print('Getting actual results from build directory %s' % build_dir)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000527 sum_files = CollectSumFiles(build_dir)
528 else:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000529 if _OPTIONS.verbosity >= 3:
530 print('Getting actual results from user-provided results')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000531 sum_files = results.split()
532 return sum_files
533
534
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000535def PerformComparison(expected, actual):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000536 actual_vs_expected, expected_vs_actual = CompareResults(expected, actual)
537
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000538 if _OPTIONS.inverse_match:
539 # Switch results if inverse comparison is requested.
540 # This is useful in detecting flaky tests that FAILed in expected set,
541 # but PASSed in actual set.
542 actual_vs_expected, expected_vs_actual \
Maxim Kuvyrkov7df81782023-05-25 06:42:06 +0000543 = expected_vs_actual, actual_vs_expected
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000544
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000545 tests_ok = True
546 if len(actual_vs_expected) > 0:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000547 if _OPTIONS.verbosity >= 3:
548 print('\n\nUnexpected results in this build (new failures)')
549 if _OPTIONS.verbosity >= 1:
550 PrintSummary(actual_vs_expected)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000551 tests_ok = False
552
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000553 if _OPTIONS.verbosity >= 2 and len(expected_vs_actual) > 0:
554 print('\n\nExpected results not present in this build (fixed tests)'
555 '\n\nNOTE: This is not a failure. It just means that these '
556 'tests were expected\nto fail, but either they worked in '
557 'this configuration or they were not\npresent at all.\n')
558 PrintSummary(expected_vs_actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000559
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000560 if tests_ok and _OPTIONS.verbosity >= 3:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000561 print('\nSUCCESS: No unexpected failures.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000562
563 return tests_ok
564
565
566def CheckExpectedResults():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000567 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000568 if _OPTIONS.verbosity >= 3:
569 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000570 manifest = GetManifest(manifest_path)
571 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
572 actual = GetResults(sum_files)
573
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000574 if _OPTIONS.verbosity >= 5:
575 print('\n\nTests expected to fail')
576 PrintSummary(manifest)
577 print('\n\nActual test results')
578 PrintSummary(actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000579
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000580 return PerformComparison(manifest, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000581
582
583def ProduceManifest():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000584 manifest_path = GetManifestPath(False)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000585 if _OPTIONS.verbosity >= 3:
586 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000587 if os.path.exists(manifest_path) and not _OPTIONS.force:
588 Error('Manifest file %s already exists.\nUse --force to overwrite.' %
589 manifest_path)
590
591 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
592 actual = GetResults(sum_files)
593 manifest_file = open(manifest_path, 'w')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000594 actual.Print(manifest_file)
595 actual.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000596 manifest_file.close()
597
598 return True
599
600
601def CompareBuilds():
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000602 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
603 actual = GetResults(sum_files)
604
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000605 clean = ResultSet()
606
607 if _OPTIONS.manifest:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000608 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000609 if _OPTIONS.verbosity >= 3:
610 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000611 clean = GetManifest(manifest_path)
612
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000613 clean_sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.clean_build)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000614 clean = GetResults(clean_sum_files, clean)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000615
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000616 return PerformComparison(clean, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000617
618
619def Main(argv):
620 parser = optparse.OptionParser(usage=__doc__)
621
622 # Keep the following list sorted by option name.
623 parser.add_option('--build_dir', action='store', type='string',
624 dest='build_dir', default='.',
625 help='Build directory to check (default = .)')
626 parser.add_option('--clean_build', action='store', type='string',
627 dest='clean_build', default=None,
628 help='Compare test results from this build against '
629 'those of another (clean) build. Use this option '
630 'when comparing the test results of your patch versus '
631 'the test results of a clean build without your patch. '
632 'You must provide the path to the top directory of your '
633 'clean build.')
634 parser.add_option('--force', action='store_true', dest='force',
635 default=False, help='When used with --produce_manifest, '
636 'it will overwrite an existing manifest file '
637 '(default = False)')
Maxim Kuvyrkov158e61d2023-05-25 12:18:30 +0000638 parser.add_option('--expiry_date', action='store',
639 dest='expiry_today_date', default=None,
640 help='Use provided date YYYYMMDD to decide whether '
641 'manifest entries with expiry settings have expired '
642 'or not. (default = Use today date)')
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000643 parser.add_option('--inverse_match', action='store_true',
644 dest='inverse_match', default=False,
645 help='Inverse result sets in comparison. '
646 'Output unexpected passes as unexpected failures and '
647 'unexpected failures as unexpected passes. '
648 'This is used to catch FAIL->PASS flaky tests. '
649 '(default = False)')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000650 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',
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000666 type='int', default=3, help='Verbosity level '
667 '(default = 3). Level 0: only error output, this is '
668 'useful in scripting when only the exit code is used. '
669 'Level 1: output unexpected failures. '
670 'Level 2: output unexpected passes. '
671 'Level 3: output helpful information. '
672 'Level 5: output debug information.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000673 global _OPTIONS
674 (_OPTIONS, _) = parser.parse_args(argv[1:])
675
Maxim Kuvyrkov158e61d2023-05-25 12:18:30 +0000676 # Set "today" date to compare expiration entries against.
677 # Setting expiration date into the future allows re-detection of flaky
678 # tests and creating fresh entries for them before the current flaky entries
679 # expire.
680 if _OPTIONS.expiry_today_date:
681 today_date = re.search(r'(\d\d\d\d)(\d\d)(\d\d)',
682 _OPTIONS.expiry_today_date)
683 if not today_date:
684 Error('Invalid --expiry_today_date format "%s". Must be of the form '
685 '"expire=YYYYMMDD"' % _OPTIONS.expiry_today_date)
686 _OPTIONS.expiry_today_date=datetime.date(int(today_date.group(1)),
687 int(today_date.group(2)),
688 int(today_date.group(3)))
689 else:
690 _OPTIONS.expiry_today_date = datetime.date.today()
691
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000692 if _OPTIONS.produce_manifest:
693 retval = ProduceManifest()
694 elif _OPTIONS.clean_build:
695 retval = CompareBuilds()
696 else:
697 retval = CheckExpectedResults()
698
699 if retval:
700 return 0
701 else:
Maxim Kuvyrkov972bb812021-08-30 14:18:09 +0000702 return 2
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000703
704
705if __name__ == '__main__':
706 retval = Main(sys.argv)
707 sys.exit(retval)