blob: 5a4df6bee295dc70421e57e65329591a32c66788 [file] [log] [blame]
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +00001#!/usr/bin/env python3
Maxim Kuvyrkov59877482021-07-07 11:22:26 +00002
3# Script to compare testsuite failures against a list of known-to-fail
4# tests.
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +00005
Maxim Kuvyrkov59877482021-07-07 11:22:26 +00006# Contributed by Diego Novillo <dnovillo@google.com>
7#
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +00008# Copyright (C) 2011-2023 Free Software Foundation, Inc.
Maxim Kuvyrkov59877482021-07-07 11:22:26 +00009#
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 Kuvyrkov966fac42024-04-01 11:47:27 +000063_INTERESTING_RESULTS = [ 'FAIL', 'UNRESOLVED', 'XPASS', 'ERROR' ]
Maxim Kuvyrkov8704bc12023-05-03 15:03:34 +000064# <STATE>: <NAME> <DESCRIPTION"
Maxim Kuvyrkov966fac42024-04-01 11:47:27 +000065_INTERESTING_RESULTS_REX = re.compile('(%s):\s*(\S+)\s*(.*)'
66 % "|".join(_INTERESTING_RESULTS))
Maxim Kuvyrkov8704bc12023-05-03 15:03:34 +000067
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 Kuvyrkov8396bb32023-06-14 14:32:38 +000077_EXP_LINE_REX = re.compile('^Running (?:.*:)?(.*) \.\.\.\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 Kuvyrkov966fac42024-04-01 11:47:27 +0000137 self.description) = _INTERESTING_RESULTS_REX.match(summary_line).groups()
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000138 if _OPTIONS.srcpath_regex and _OPTIONS.srcpath_regex != '':
139 self.description = re.sub(_OPTIONS.srcpath_regex, '',
140 self.description)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000141 except:
Thiago Jung Bauermann7b82a592023-04-22 14:12:06 +0000142 print('Failed to parse summary line: "%s"' % summary_line,
143 file=sys.stderr)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000144 raise
145 self.ordinal = ordinal
Maxim Kuvyrkovf09ab0e2021-08-30 14:19:04 +0000146 if tool == None or exp == None:
147 # .sum file seem to be broken. There was no "tool" and/or "exp"
148 # lines preceding this result.
Thiago Jung Bauermann7b82a592023-04-22 14:12:06 +0000149 print(f'.sum file seems to be broken: tool="{tool}", exp="{exp}", summary_line="{summary_line}"',
150 file=sys.stderr)
Maxim Kuvyrkovf09ab0e2021-08-30 14:19:04 +0000151 raise
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000152 self.tool = tool
153 self.exp = exp
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000154 except ValueError:
155 Error('Cannot parse summary line "%s"' % summary_line)
156
Maxim Kuvyrkov966fac42024-04-01 11:47:27 +0000157 if self.state not in _INTERESTING_RESULTS:
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000158 Error('Invalid test result %s in "%s" (parsed as "%s")' % (
159 self.state, summary_line, self))
160
161 def __lt__(self, other):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000162 if (self.tool != other.tool):
163 return self.tool < other.tool
164 if (self.exp != other.exp):
165 return self.exp < other.exp
166 if (self.name != other.name):
167 return self.name < other.name
168 return self.ordinal < other.ordinal
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000169
170 def __hash__(self):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000171 return (hash(self.state) ^ hash(self.tool) ^ hash(self.exp)
172 ^ hash(self.name) ^ hash(self.description))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000173
Maxim Kuvyrkov7df81782023-05-25 06:42:06 +0000174 # Note that we don't include "attrs" in this comparison. This means that
175 # result entries "FAIL: test" and "flaky | FAIL: test" are considered
176 # the same. Therefore the ResultSet will preserve only the first occurence.
177 # In practice this means that flaky entries should preceed expected fails
178 # entries.
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000179 def __eq__(self, other):
180 return (self.state == other.state and
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000181 self.tool == other.tool and
182 self.exp == other.exp and
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000183 self.name == other.name and
184 self.description == other.description)
185
186 def __ne__(self, other):
187 return not (self == other)
188
189 def __str__(self):
190 attrs = ''
191 if self.attrs:
192 attrs = '%s | ' % self.attrs
193 return '%s%s: %s %s' % (attrs, self.state, self.name, self.description)
194
195 def ExpirationDate(self):
196 # Return a datetime.date object with the expiration date for this
197 # test result. Return None, if no expiration has been set.
198 if re.search(r'expire=', self.attrs):
199 expiration = re.search(r'expire=(\d\d\d\d)(\d\d)(\d\d)', self.attrs)
200 if not expiration:
201 Error('Invalid expire= format in "%s". Must be of the form '
202 '"expire=YYYYMMDD"' % self)
203 return datetime.date(int(expiration.group(1)),
204 int(expiration.group(2)),
205 int(expiration.group(3)))
206 return None
207
208 def HasExpired(self):
209 # Return True if the expiration date of this result has passed.
210 expiration_date = self.ExpirationDate()
211 if expiration_date:
Maxim Kuvyrkov158e61d2023-05-25 12:18:30 +0000212 return _OPTIONS.expiry_today_date > expiration_date
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000213
214
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000215class ResultSet(set):
216 """Describes a set of DejaGNU test results.
217 This set can be read in from .sum files or emitted as a manifest.
218
219 Attributes:
220 current_tool: Name of the current top-level DejaGnu testsuite.
221 current_exp: Name of the current .exp testsuite file.
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000222 testsuites: A set of (tool, exp) tuples representing encountered testsuites.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000223 """
224
225 def __init__(self):
226 super().__init__()
227 self.ResetToolExp()
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000228 self.testsuites=set()
229
230 def update(self, other):
231 super().update(other)
232 self.testsuites.update(other.testsuites)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000233
234 def ResetToolExp(self):
235 self.current_tool = None
236 self.current_exp = None
237
238 def MakeTestResult(self, summary_line, ordinal=-1):
239 return TestResult(summary_line, ordinal,
240 self.current_tool, self.current_exp)
241
242 def Print(self, outfile=sys.stdout):
243 current_tool = None
244 current_exp = None
245
246 for result in sorted(self):
247 if current_tool != result.tool:
248 current_tool = result.tool
249 outfile.write(_TOOL_LINE_FORMAT % current_tool)
250 if current_exp != result.exp:
251 current_exp = result.exp
Christophe Lyona7d8c4c2023-04-14 12:01:23 +0000252 outfile.write(_EXP_LINE_FORMAT % (current_tool, current_exp))
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000253 outfile.write('%s\n' % result)
254
255 outfile.write(_SUMMARY_LINE_FORMAT % 'Results')
256
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000257 # Check if testsuite of expected_result is present in current results.
258 # This is used to compare partial test results against a full manifest.
259 def HasTestsuite(self, expected_result):
260 return (expected_result.tool, expected_result.exp) in self.testsuites
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000261
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000262def GetMakefileValue(makefile_name, value_name):
263 if os.path.exists(makefile_name):
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000264 makefile = open(makefile_name, encoding='latin-1', mode='r')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000265 for line in makefile:
266 if line.startswith(value_name):
267 (_, value) = line.split('=', 1)
268 value = value.strip()
269 makefile.close()
270 return value
271 makefile.close()
272 return None
273
274
275def ValidBuildDirectory(builddir):
276 if (not os.path.exists(builddir) or
277 not os.path.exists('%s/Makefile' % builddir)):
278 return False
279 return True
280
281
282def IsComment(line):
283 """Return True if line is a comment."""
284 return line.startswith('#')
285
286
287def SplitAttributesFromSummaryLine(line):
288 """Splits off attributes from a summary line, if present."""
Maxim Kuvyrkov966fac42024-04-01 11:47:27 +0000289 if '|' in line and not _INTERESTING_RESULTS_REX.match(line):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000290 (attrs, line) = line.split('|', 1)
291 attrs = attrs.strip()
292 else:
293 attrs = ''
294 line = line.strip()
295 return (attrs, line)
296
297
Thiago Jung Bauermann41505402023-06-14 14:37:00 +0200298def IsInterestingResult(result_set, line):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000299 """Return True if line is one of the summary lines we care about."""
300 (_, line) = SplitAttributesFromSummaryLine(line)
Maxim Kuvyrkov966fac42024-04-01 11:47:27 +0000301 interesting_result = bool(_INTERESTING_RESULTS_REX.match(line))
Thiago Jung Bauermann41505402023-06-14 14:37:00 +0200302
303 # If there's no tool defined it means that either the results section hasn't
304 # started yet, or it is already over.
Maxim Kuvyrkov966fac42024-04-01 11:47:27 +0000305 if interesting_result and result_set.current_tool is None:
Thiago Jung Bauermann41505402023-06-14 14:37:00 +0200306 if _OPTIONS.verbosity >= 3:
307 print(f'WARNING: Result "{line}" found outside sum file boundaries.',
308 file=sys.stderr)
309 return False
310
Maxim Kuvyrkov966fac42024-04-01 11:47:27 +0000311 return interesting_result
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000312
313
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000314def IsToolLine(line):
315 """Return True if line mentions the tool (in DejaGnu terms) for the following tests."""
316 return bool(_TOOL_LINE_REX.match(line))
317
318
319def IsExpLine(line):
320 """Return True if line mentions the .exp file for the following tests."""
321 return bool(_EXP_LINE_REX.match(line))
322
323
324def IsSummaryLine(line):
325 """Return True if line starts .sum footer."""
326 return bool(_SUMMARY_LINE_REX.match(line))
327
328
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000329def IsInclude(line):
330 """Return True if line is an include of another file."""
331 return line.startswith("@include ")
332
333
334def GetIncludeFile(line, includer):
335 """Extract the name of the include file from line."""
336 includer_dir = os.path.dirname(includer)
337 include_file = line[len("@include "):]
338 return os.path.join(includer_dir, include_file.strip())
339
340
341def IsNegativeResult(line):
342 """Return True if line should be removed from the expected results."""
343 return line.startswith("@remove ")
344
345
346def GetNegativeResult(line):
347 """Extract the name of the negative result from line."""
348 line = line[len("@remove "):]
349 return line.strip()
350
351
352def ParseManifestWorker(result_set, manifest_path):
353 """Read manifest_path, adding the contents to result_set."""
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000354 if _OPTIONS.verbosity >= 5:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000355 print('Parsing manifest file %s.' % manifest_path)
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000356 manifest_file = open(manifest_path, encoding='latin-1', mode='r')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000357 for orig_line in manifest_file:
358 line = orig_line.strip()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000359 if line == "":
360 pass
361 elif IsComment(line):
362 pass
363 elif IsNegativeResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000364 result_set.remove(result_set.MakeTestResult(GetNegativeResult(line)))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000365 elif IsInclude(line):
366 ParseManifestWorker(result_set, GetIncludeFile(line, manifest_path))
Thiago Jung Bauermann41505402023-06-14 14:37:00 +0200367 elif IsInterestingResult(result_set, line):
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000368 result = result_set.MakeTestResult(line)
369 if result.HasExpired():
370 # Ignore expired manifest entries.
Maxim Kuvyrkov7df81782023-05-25 06:42:06 +0000371 if _OPTIONS.verbosity >= 4:
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000372 print('WARNING: Expected failure "%s" has expired.' % line.strip())
373 continue
374 result_set.add(result)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000375 elif IsExpLine(orig_line):
376 result_set.current_exp = _EXP_LINE_REX.match(orig_line).groups()[0]
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000377 if _OPTIONS.srcpath_regex and _OPTIONS.srcpath_regex != '':
378 result_set.current_exp = re.sub(_OPTIONS.srcpath_regex, '',
379 result_set.current_exp)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000380 elif IsToolLine(orig_line):
381 result_set.current_tool = _TOOL_LINE_REX.match(orig_line).groups()[0]
382 elif IsSummaryLine(orig_line):
383 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000384 else:
385 Error('Unrecognized line in manifest file: %s' % line)
386 manifest_file.close()
387
388
389def ParseManifest(manifest_path):
390 """Create a set of TestResult instances from the given manifest file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000391 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000392 ParseManifestWorker(result_set, manifest_path)
393 return result_set
394
395
396def ParseSummary(sum_fname):
397 """Create a set of TestResult instances from the given summary file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000398 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000399 # ordinal is used when sorting the results so that tests within each
400 # .exp file are kept sorted.
401 ordinal=0
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000402 sum_file = open(sum_fname, encoding='latin-1', mode='r')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000403 for line in sum_file:
Thiago Jung Bauermann41505402023-06-14 14:37:00 +0200404 if IsInterestingResult(result_set, line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000405 result = result_set.MakeTestResult(line, ordinal)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000406 ordinal += 1
407 if result.HasExpired():
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000408 # ??? What is the use-case for this? How "expiry" annotations are
409 # ??? supposed to be added to .sum results?
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000410 # Tests that have expired are not added to the set of expected
411 # results. If they are still present in the set of actual results,
412 # they will cause an error to be reported.
Maxim Kuvyrkov7df81782023-05-25 06:42:06 +0000413 if _OPTIONS.verbosity >= 4:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000414 print('WARNING: Expected failure "%s" has expired.' % line.strip())
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000415 continue
416 result_set.add(result)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000417 elif IsExpLine(line):
418 result_set.current_exp = _EXP_LINE_REX.match(line).groups()[0]
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000419 if _OPTIONS.srcpath_regex and _OPTIONS.srcpath_regex != '':
420 result_set.current_exp = re.sub(_OPTIONS.srcpath_regex, '',
421 result_set.current_exp)
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000422 result_set.testsuites.add((result_set.current_tool,
423 result_set.current_exp))
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000424 elif IsToolLine(line):
425 result_set.current_tool = _TOOL_LINE_REX.match(line).groups()[0]
Maxim Kuvyrkovd8951a22021-07-08 08:20:28 +0000426 result_set.current_exp = None
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000427 elif IsSummaryLine(line):
428 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000429 sum_file.close()
430 return result_set
431
432
433def GetManifest(manifest_path):
434 """Build a set of expected failures from the manifest file.
435
436 Each entry in the manifest file should have the format understood
437 by the TestResult constructor.
438
439 If no manifest file exists for this target, it returns an empty set.
440 """
441 if os.path.exists(manifest_path):
442 return ParseManifest(manifest_path)
443 else:
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000444 return ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000445
446
447def CollectSumFiles(builddir):
448 sum_files = []
449 for root, dirs, files in os.walk(builddir):
450 for ignored in ('.svn', '.git'):
451 if ignored in dirs:
452 dirs.remove(ignored)
453 for fname in files:
454 if fname.endswith('.sum'):
455 sum_files.append(os.path.join(root, fname))
456 return sum_files
457
458
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000459def GetResults(sum_files, build_results = None):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000460 """Collect all the test results from the given .sum files."""
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000461 if build_results == None:
462 build_results = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000463 for sum_fname in sum_files:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000464 if _OPTIONS.verbosity >= 3:
465 print('\t%s' % sum_fname)
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000466 build_results.update(ParseSummary(sum_fname))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000467 return build_results
468
469
470def CompareResults(manifest, actual):
471 """Compare sets of results and return two lists:
472 - List of results present in ACTUAL but missing from MANIFEST.
473 - List of results present in MANIFEST but missing from ACTUAL.
474 """
475 # Collect all the actual results not present in the manifest.
476 # Results in this set will be reported as errors.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000477 actual_vs_manifest = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000478 for actual_result in actual:
479 if actual_result not in manifest:
480 actual_vs_manifest.add(actual_result)
481
482 # Collect all the tests in the manifest that were not found
483 # in the actual results.
484 # Results in this set will be reported as warnings (since
485 # they are expected failures that are not failing anymore).
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000486 manifest_vs_actual = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000487 for expected_result in manifest:
488 # Ignore tests marked flaky.
489 if 'flaky' in expected_result.attrs:
490 continue
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000491 # We try to support comparing partial results vs full manifest
492 # (e.g., manifest has failures for gcc, g++, gfortran, but we ran only
493 # g++ testsuite). To achieve this we record encountered testsuites in
494 # actual.testsuites set, and then we check it here using HasTestsuite().
495 if expected_result not in actual and actual.HasTestsuite(expected_result):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000496 manifest_vs_actual.add(expected_result)
497
498 return actual_vs_manifest, manifest_vs_actual
499
500
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000501def GetManifestPath(user_provided_must_exist):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000502 """Return the full path to the manifest file."""
503 manifest_path = _OPTIONS.manifest
504 if manifest_path:
505 if user_provided_must_exist and not os.path.exists(manifest_path):
506 Error('Manifest does not exist: %s' % manifest_path)
507 return manifest_path
508 else:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000509 (srcdir, target) = GetBuildData()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000510 if not srcdir:
511 Error('Could not determine the location of GCC\'s source tree. '
512 'The Makefile does not contain a definition for "srcdir".')
513 if not target:
514 Error('Could not determine the target triplet for this build. '
515 'The Makefile does not contain a definition for "target_alias".')
516 return _MANIFEST_PATH_PATTERN % (srcdir, _MANIFEST_SUBDIR, target)
517
518
519def GetBuildData():
520 if not ValidBuildDirectory(_OPTIONS.build_dir):
521 # If we have been given a set of results to use, we may
522 # not be inside a valid GCC build directory. In that case,
523 # the user must provide both a manifest file and a set
524 # of results to check against it.
525 if not _OPTIONS.results or not _OPTIONS.manifest:
526 Error('%s is not a valid GCC top level build directory. '
527 'You must use --manifest and --results to do the validation.' %
528 _OPTIONS.build_dir)
529 else:
530 return None, None
531 srcdir = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'srcdir =')
532 target = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'target_alias=')
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000533 if _OPTIONS.verbosity >= 3:
534 print('Source directory: %s' % srcdir)
535 print('Build target: %s' % target)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000536 return srcdir, target
537
538
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000539def PrintSummary(summary):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000540 summary.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000541
542def GetSumFiles(results, build_dir):
543 if not results:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000544 if _OPTIONS.verbosity >= 3:
545 print('Getting actual results from build directory %s' % build_dir)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000546 sum_files = CollectSumFiles(build_dir)
547 else:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000548 if _OPTIONS.verbosity >= 3:
549 print('Getting actual results from user-provided results')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000550 sum_files = results.split()
551 return sum_files
552
553
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000554def PerformComparison(expected, actual):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000555 actual_vs_expected, expected_vs_actual = CompareResults(expected, actual)
556
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000557 if _OPTIONS.inverse_match:
558 # Switch results if inverse comparison is requested.
559 # This is useful in detecting flaky tests that FAILed in expected set,
560 # but PASSed in actual set.
561 actual_vs_expected, expected_vs_actual \
Maxim Kuvyrkov7df81782023-05-25 06:42:06 +0000562 = expected_vs_actual, actual_vs_expected
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000563
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000564 tests_ok = True
565 if len(actual_vs_expected) > 0:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000566 if _OPTIONS.verbosity >= 3:
567 print('\n\nUnexpected results in this build (new failures)')
568 if _OPTIONS.verbosity >= 1:
569 PrintSummary(actual_vs_expected)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000570 tests_ok = False
571
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000572 if _OPTIONS.verbosity >= 2 and len(expected_vs_actual) > 0:
573 print('\n\nExpected results not present in this build (fixed tests)'
574 '\n\nNOTE: This is not a failure. It just means that these '
575 'tests were expected\nto fail, but either they worked in '
576 'this configuration or they were not\npresent at all.\n')
577 PrintSummary(expected_vs_actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000578
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000579 if tests_ok and _OPTIONS.verbosity >= 3:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000580 print('\nSUCCESS: No unexpected failures.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000581
582 return tests_ok
583
584
585def CheckExpectedResults():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000586 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000587 if _OPTIONS.verbosity >= 3:
588 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000589 manifest = GetManifest(manifest_path)
590 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
591 actual = GetResults(sum_files)
592
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000593 if _OPTIONS.verbosity >= 5:
594 print('\n\nTests expected to fail')
595 PrintSummary(manifest)
596 print('\n\nActual test results')
597 PrintSummary(actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000598
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000599 return PerformComparison(manifest, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000600
601
602def ProduceManifest():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000603 manifest_path = GetManifestPath(False)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000604 if _OPTIONS.verbosity >= 3:
605 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000606 if os.path.exists(manifest_path) and not _OPTIONS.force:
607 Error('Manifest file %s already exists.\nUse --force to overwrite.' %
608 manifest_path)
609
610 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
611 actual = GetResults(sum_files)
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000612 manifest_file = open(manifest_path, encoding='latin-1', mode='w')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000613 actual.Print(manifest_file)
614 actual.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000615 manifest_file.close()
616
617 return True
618
619
620def CompareBuilds():
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000621 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
622 actual = GetResults(sum_files)
623
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000624 clean = ResultSet()
625
626 if _OPTIONS.manifest:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000627 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000628 if _OPTIONS.verbosity >= 3:
629 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000630 clean = GetManifest(manifest_path)
631
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000632 clean_sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.clean_build)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000633 clean = GetResults(clean_sum_files, clean)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000634
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000635 return PerformComparison(clean, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000636
637
638def Main(argv):
639 parser = optparse.OptionParser(usage=__doc__)
640
641 # Keep the following list sorted by option name.
642 parser.add_option('--build_dir', action='store', type='string',
643 dest='build_dir', default='.',
644 help='Build directory to check (default = .)')
645 parser.add_option('--clean_build', action='store', type='string',
646 dest='clean_build', default=None,
647 help='Compare test results from this build against '
648 'those of another (clean) build. Use this option '
649 'when comparing the test results of your patch versus '
650 'the test results of a clean build without your patch. '
651 'You must provide the path to the top directory of your '
652 'clean build.')
653 parser.add_option('--force', action='store_true', dest='force',
654 default=False, help='When used with --produce_manifest, '
655 'it will overwrite an existing manifest file '
656 '(default = False)')
Maxim Kuvyrkov158e61d2023-05-25 12:18:30 +0000657 parser.add_option('--expiry_date', action='store',
658 dest='expiry_today_date', default=None,
659 help='Use provided date YYYYMMDD to decide whether '
660 'manifest entries with expiry settings have expired '
661 'or not. (default = Use today date)')
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000662 parser.add_option('--srcpath', action='store', type='string',
663 dest='srcpath_regex', default='[^ ]+/testsuite/',
664 help='Remove provided path (can be a regex) from '
665 'the result entries. This is useful to remove '
666 'occasional filesystem path from the results. '
667 '(default = "[^ ]+/testsuite/")')
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000668 parser.add_option('--inverse_match', action='store_true',
669 dest='inverse_match', default=False,
670 help='Inverse result sets in comparison. '
671 'Output unexpected passes as unexpected failures and '
672 'unexpected failures as unexpected passes. '
673 'This is used to catch FAIL->PASS flaky tests. '
674 '(default = False)')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000675 parser.add_option('--manifest', action='store', type='string',
676 dest='manifest', default=None,
677 help='Name of the manifest file to use (default = '
678 'taken from '
679 'contrib/testsuite-managment/<target_alias>.xfail)')
680 parser.add_option('--produce_manifest', action='store_true',
681 dest='produce_manifest', default=False,
682 help='Produce the manifest for the current '
683 'build (default = False)')
684 parser.add_option('--results', action='store', type='string',
685 dest='results', default=None, help='Space-separated list '
686 'of .sum files with the testing results to check. The '
687 'only content needed from these files are the lines '
688 'starting with FAIL, XPASS or UNRESOLVED (default = '
689 '.sum files collected from the build directory).')
690 parser.add_option('--verbosity', action='store', dest='verbosity',
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000691 type='int', default=3, help='Verbosity level '
692 '(default = 3). Level 0: only error output, this is '
693 'useful in scripting when only the exit code is used. '
694 'Level 1: output unexpected failures. '
695 'Level 2: output unexpected passes. '
696 'Level 3: output helpful information. '
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000697 'Level 4: output notification on expired entries. '
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000698 'Level 5: output debug information.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000699 global _OPTIONS
700 (_OPTIONS, _) = parser.parse_args(argv[1:])
701
Maxim Kuvyrkov158e61d2023-05-25 12:18:30 +0000702 # Set "today" date to compare expiration entries against.
703 # Setting expiration date into the future allows re-detection of flaky
704 # tests and creating fresh entries for them before the current flaky entries
705 # expire.
706 if _OPTIONS.expiry_today_date:
707 today_date = re.search(r'(\d\d\d\d)(\d\d)(\d\d)',
708 _OPTIONS.expiry_today_date)
709 if not today_date:
710 Error('Invalid --expiry_today_date format "%s". Must be of the form '
711 '"expire=YYYYMMDD"' % _OPTIONS.expiry_today_date)
712 _OPTIONS.expiry_today_date=datetime.date(int(today_date.group(1)),
713 int(today_date.group(2)),
714 int(today_date.group(3)))
715 else:
716 _OPTIONS.expiry_today_date = datetime.date.today()
717
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000718 if _OPTIONS.produce_manifest:
719 retval = ProduceManifest()
720 elif _OPTIONS.clean_build:
721 retval = CompareBuilds()
722 else:
723 retval = CheckExpectedResults()
724
725 if retval:
726 return 0
727 else:
Maxim Kuvyrkov972bb812021-08-30 14:18:09 +0000728 return 2
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000729
730
731if __name__ == '__main__':
732 retval = Main(sys.argv)
733 sys.exit(retval)