blob: 78c43f99270a332e5c9eba08cdf7dd573118b54c [file] [log] [blame]
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +00001#!/usr/bin/python3
Maxim Kuvyrkov59877482021-07-07 11:22:26 +00002
3# Script to compare testsuite failures against a list of known-to-fail
4# tests.
5#
Maxim Kuvyrkov59877482021-07-07 11:22:26 +00006# Contributed by Diego Novillo <dnovillo@google.com>
7#
8# Copyright (C) 2011-2013 Free Software Foundation, Inc.
9#
10# This file is part of GCC.
11#
12# GCC is free software; you can redistribute it and/or modify
13# it under the terms of the GNU General Public License as published by
14# the Free Software Foundation; either version 3, or (at your option)
15# any later version.
16#
17# GCC is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License
23# along with GCC; see the file COPYING. If not, write to
24# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
25# Boston, MA 02110-1301, USA.
26
27"""This script provides a coarser XFAILing mechanism that requires no
28detailed DejaGNU markings. This is useful in a variety of scenarios:
29
30- Development branches with many known failures waiting to be fixed.
31- Release branches with known failures that are not considered
32 important for the particular release criteria used in that branch.
33
34The script must be executed from the toplevel build directory. When
35executed it will:
36
371- Determine the target built: TARGET
382- Determine the source directory: SRCDIR
393- Look for a failure manifest file in
40 <SRCDIR>/<MANIFEST_SUBDIR>/<MANIFEST_NAME>.xfail
414- Collect all the <tool>.sum files from the build tree.
425- Produce a report stating:
43 a- Failures expected in the manifest but not present in the build.
44 b- Failures in the build not expected in the manifest.
456- If all the build failures are expected in the manifest, it exits
46 with exit code 0. Otherwise, it exits with error code 1.
47
48Manifest files contain expected DejaGNU results that are otherwise
49treated as failures.
50They may also contain additional text:
51
52# This is a comment. - self explanatory
53@include file - the file is a path relative to the includer
54@remove result text - result text is removed from the expected set
55"""
56
57import datetime
58import optparse
59import os
60import re
61import sys
62
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +000063# Formats of .sum file sections
64_TOOL_LINE_FORMAT = '\t\t=== %s tests ===\n'
Christophe Lyona7d8c4c2023-04-14 12:01:23 +000065_EXP_LINE_FORMAT = '\nRunning %s:%s ...\n'
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +000066_SUMMARY_LINE_FORMAT = '\n\t\t=== %s Summary ===\n'
67
68# ... and their compiled regexs.
69_TOOL_LINE_REX = re.compile('^\t\t=== (.*) tests ===\n')
Christophe Lyona7d8c4c2023-04-14 12:01:23 +000070# Match .exp file name, optionally prefixed by a "tool:" name and a
71# path ending with "testsuite/"
72_EXP_LINE_REX = re.compile('^Running (?:.*:)?(?:.*/testsuite/)?(.*\.exp) \.\.\.\n')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +000073_SUMMARY_LINE_REX = re.compile('^\t\t=== (.*) Summary ===\n')
74
Maxim Kuvyrkov59877482021-07-07 11:22:26 +000075# Subdirectory of srcdir in which to find the manifest file.
76_MANIFEST_SUBDIR = 'contrib/testsuite-management'
77
78# Pattern for naming manifest files.
79# The first argument should be the toplevel GCC(/GNU tool) source directory.
80# The second argument is the manifest subdir.
81# The third argument is the manifest target, which defaults to the target
82# triplet used during the build.
83_MANIFEST_PATH_PATTERN = '%s/%s/%s.xfail'
84
85# The options passed to the program.
86_OPTIONS = None
87
88def Error(msg):
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +000089 print('error: %s' % msg, file=sys.stderr)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +000090 sys.exit(1)
91
92
93class TestResult(object):
94 """Describes a single DejaGNU test result as emitted in .sum files.
95
96 We are only interested in representing unsuccessful tests. So, only
97 a subset of all the tests are loaded.
98
99 The summary line used to build the test result should have this format:
100
101 attrlist | XPASS: gcc.dg/unroll_1.c (test for excess errors)
102 ^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
103 optional state name description
104 attributes
105
106 Attributes:
107 attrlist: A comma separated list of attributes.
108 Valid values:
109 flaky Indicates that this test may not always fail. These
110 tests are reported, but their presence does not affect
111 the results.
112
113 expire=YYYYMMDD After this date, this test will produce an error
114 whether it is in the manifest or not.
115
116 state: One of UNRESOLVED, XPASS or FAIL.
117 name: File name for the test.
118 description: String describing the test (flags used, dejagnu message, etc)
119 ordinal: Monotonically increasing integer.
120 It is used to keep results for one .exp file sorted
121 by the order the tests were run.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000122 tool: Top-level testsuite name (aka "tool" in DejaGnu parlance) of the test.
123 exp: Name of .exp testsuite file.
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000124 """
125
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000126 def __init__(self, summary_line, ordinal, tool, exp):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000127 try:
128 (self.attrs, summary_line) = SplitAttributesFromSummaryLine(summary_line)
129 try:
130 (self.state,
131 self.name,
Maxim Kuvyrkov56734ff2021-08-30 14:24:25 +0000132 self.description) = _VALID_TEST_RESULTS_REX.match(summary_line).groups()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000133 except:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000134 print('Failed to parse summary line: "%s"' % summary_line)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000135 raise
136 self.ordinal = ordinal
Maxim Kuvyrkovf09ab0e2021-08-30 14:19:04 +0000137 if tool == None or exp == None:
138 # .sum file seem to be broken. There was no "tool" and/or "exp"
139 # lines preceding this result.
140 raise
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000141 self.tool = tool
142 self.exp = exp
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000143 except ValueError:
144 Error('Cannot parse summary line "%s"' % summary_line)
145
146 if self.state not in _VALID_TEST_RESULTS:
147 Error('Invalid test result %s in "%s" (parsed as "%s")' % (
148 self.state, summary_line, self))
149
150 def __lt__(self, other):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000151 if (self.tool != other.tool):
152 return self.tool < other.tool
153 if (self.exp != other.exp):
154 return self.exp < other.exp
155 if (self.name != other.name):
156 return self.name < other.name
157 return self.ordinal < other.ordinal
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000158
159 def __hash__(self):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000160 return (hash(self.state) ^ hash(self.tool) ^ hash(self.exp)
161 ^ hash(self.name) ^ hash(self.description))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000162
163 def __eq__(self, other):
164 return (self.state == other.state and
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000165 self.tool == other.tool and
166 self.exp == other.exp and
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000167 self.name == other.name and
168 self.description == other.description)
169
170 def __ne__(self, other):
171 return not (self == other)
172
173 def __str__(self):
174 attrs = ''
175 if self.attrs:
176 attrs = '%s | ' % self.attrs
177 return '%s%s: %s %s' % (attrs, self.state, self.name, self.description)
178
179 def ExpirationDate(self):
180 # Return a datetime.date object with the expiration date for this
181 # test result. Return None, if no expiration has been set.
182 if re.search(r'expire=', self.attrs):
183 expiration = re.search(r'expire=(\d\d\d\d)(\d\d)(\d\d)', self.attrs)
184 if not expiration:
185 Error('Invalid expire= format in "%s". Must be of the form '
186 '"expire=YYYYMMDD"' % self)
187 return datetime.date(int(expiration.group(1)),
188 int(expiration.group(2)),
189 int(expiration.group(3)))
190 return None
191
192 def HasExpired(self):
193 # Return True if the expiration date of this result has passed.
194 expiration_date = self.ExpirationDate()
195 if expiration_date:
196 now = datetime.date.today()
197 return now > expiration_date
198
199
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000200class ResultSet(set):
201 """Describes a set of DejaGNU test results.
202 This set can be read in from .sum files or emitted as a manifest.
203
204 Attributes:
205 current_tool: Name of the current top-level DejaGnu testsuite.
206 current_exp: Name of the current .exp testsuite file.
207 """
208
209 def __init__(self):
210 super().__init__()
211 self.ResetToolExp()
212
213 def ResetToolExp(self):
214 self.current_tool = None
215 self.current_exp = None
216
217 def MakeTestResult(self, summary_line, ordinal=-1):
218 return TestResult(summary_line, ordinal,
219 self.current_tool, self.current_exp)
220
221 def Print(self, outfile=sys.stdout):
222 current_tool = None
223 current_exp = None
224
225 for result in sorted(self):
226 if current_tool != result.tool:
227 current_tool = result.tool
228 outfile.write(_TOOL_LINE_FORMAT % current_tool)
229 if current_exp != result.exp:
230 current_exp = result.exp
Christophe Lyona7d8c4c2023-04-14 12:01:23 +0000231 outfile.write(_EXP_LINE_FORMAT % (current_tool, current_exp))
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000232 outfile.write('%s\n' % result)
233
234 outfile.write(_SUMMARY_LINE_FORMAT % 'Results')
235
236
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000237def GetMakefileValue(makefile_name, value_name):
238 if os.path.exists(makefile_name):
239 makefile = open(makefile_name)
240 for line in makefile:
241 if line.startswith(value_name):
242 (_, value) = line.split('=', 1)
243 value = value.strip()
244 makefile.close()
245 return value
246 makefile.close()
247 return None
248
249
250def ValidBuildDirectory(builddir):
251 if (not os.path.exists(builddir) or
252 not os.path.exists('%s/Makefile' % builddir)):
253 return False
254 return True
255
256
257def IsComment(line):
258 """Return True if line is a comment."""
259 return line.startswith('#')
260
261
262def SplitAttributesFromSummaryLine(line):
263 """Splits off attributes from a summary line, if present."""
264 if '|' in line and not _VALID_TEST_RESULTS_REX.match(line):
265 (attrs, line) = line.split('|', 1)
266 attrs = attrs.strip()
267 else:
268 attrs = ''
269 line = line.strip()
270 return (attrs, line)
271
272
273def IsInterestingResult(line):
274 """Return True if line is one of the summary lines we care about."""
275 (_, line) = SplitAttributesFromSummaryLine(line)
276 return bool(_VALID_TEST_RESULTS_REX.match(line))
277
278
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000279def IsToolLine(line):
280 """Return True if line mentions the tool (in DejaGnu terms) for the following tests."""
281 return bool(_TOOL_LINE_REX.match(line))
282
283
284def IsExpLine(line):
285 """Return True if line mentions the .exp file for the following tests."""
286 return bool(_EXP_LINE_REX.match(line))
287
288
289def IsSummaryLine(line):
290 """Return True if line starts .sum footer."""
291 return bool(_SUMMARY_LINE_REX.match(line))
292
293
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000294def IsInclude(line):
295 """Return True if line is an include of another file."""
296 return line.startswith("@include ")
297
298
299def GetIncludeFile(line, includer):
300 """Extract the name of the include file from line."""
301 includer_dir = os.path.dirname(includer)
302 include_file = line[len("@include "):]
303 return os.path.join(includer_dir, include_file.strip())
304
305
306def IsNegativeResult(line):
307 """Return True if line should be removed from the expected results."""
308 return line.startswith("@remove ")
309
310
311def GetNegativeResult(line):
312 """Extract the name of the negative result from line."""
313 line = line[len("@remove "):]
314 return line.strip()
315
316
317def ParseManifestWorker(result_set, manifest_path):
318 """Read manifest_path, adding the contents to result_set."""
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000319 if _OPTIONS.verbosity >= 5:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000320 print('Parsing manifest file %s.' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000321 manifest_file = open(manifest_path)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000322 for orig_line in manifest_file:
323 line = orig_line.strip()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000324 if line == "":
325 pass
326 elif IsComment(line):
327 pass
328 elif IsNegativeResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000329 result_set.remove(result_set.MakeTestResult(GetNegativeResult(line)))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000330 elif IsInclude(line):
331 ParseManifestWorker(result_set, GetIncludeFile(line, manifest_path))
332 elif IsInterestingResult(line):
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000333 result = result_set.MakeTestResult(line)
334 if result.HasExpired():
335 # Ignore expired manifest entries.
336 if _OPTIONS.verbosity >= 3:
337 print('WARNING: Expected failure "%s" has expired.' % line.strip())
338 continue
339 result_set.add(result)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000340 elif IsExpLine(orig_line):
341 result_set.current_exp = _EXP_LINE_REX.match(orig_line).groups()[0]
342 elif IsToolLine(orig_line):
343 result_set.current_tool = _TOOL_LINE_REX.match(orig_line).groups()[0]
344 elif IsSummaryLine(orig_line):
345 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000346 else:
347 Error('Unrecognized line in manifest file: %s' % line)
348 manifest_file.close()
349
350
351def ParseManifest(manifest_path):
352 """Create a set of TestResult instances from the given manifest file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000353 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000354 ParseManifestWorker(result_set, manifest_path)
355 return result_set
356
357
358def ParseSummary(sum_fname):
359 """Create a set of TestResult instances from the given summary file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000360 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000361 # ordinal is used when sorting the results so that tests within each
362 # .exp file are kept sorted.
363 ordinal=0
364 sum_file = open(sum_fname)
365 for line in sum_file:
366 if IsInterestingResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000367 result = result_set.MakeTestResult(line, ordinal)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000368 ordinal += 1
369 if result.HasExpired():
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000370 # ??? What is the use-case for this? How "expiry" annotations are
371 # ??? supposed to be added to .sum results?
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000372 # Tests that have expired are not added to the set of expected
373 # results. If they are still present in the set of actual results,
374 # they will cause an error to be reported.
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000375 if _OPTIONS.verbosity >= 3:
376 print('WARNING: Expected failure "%s" has expired.' % line.strip())
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000377 continue
378 result_set.add(result)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000379 elif IsExpLine(line):
380 result_set.current_exp = _EXP_LINE_REX.match(line).groups()[0]
381 elif IsToolLine(line):
382 result_set.current_tool = _TOOL_LINE_REX.match(line).groups()[0]
Maxim Kuvyrkovd8951a22021-07-08 08:20:28 +0000383 result_set.current_exp = None
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000384 elif IsSummaryLine(line):
385 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000386 sum_file.close()
387 return result_set
388
389
390def GetManifest(manifest_path):
391 """Build a set of expected failures from the manifest file.
392
393 Each entry in the manifest file should have the format understood
394 by the TestResult constructor.
395
396 If no manifest file exists for this target, it returns an empty set.
397 """
398 if os.path.exists(manifest_path):
399 return ParseManifest(manifest_path)
400 else:
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000401 return ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000402
403
404def CollectSumFiles(builddir):
405 sum_files = []
406 for root, dirs, files in os.walk(builddir):
407 for ignored in ('.svn', '.git'):
408 if ignored in dirs:
409 dirs.remove(ignored)
410 for fname in files:
411 if fname.endswith('.sum'):
412 sum_files.append(os.path.join(root, fname))
413 return sum_files
414
415
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000416def GetResults(sum_files, build_results = None):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000417 """Collect all the test results from the given .sum files."""
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000418 if build_results == None:
419 build_results = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000420 for sum_fname in sum_files:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000421 if _OPTIONS.verbosity >= 3:
422 print('\t%s' % sum_fname)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000423 build_results |= ParseSummary(sum_fname)
424 return build_results
425
426
427def CompareResults(manifest, actual):
428 """Compare sets of results and return two lists:
429 - List of results present in ACTUAL but missing from MANIFEST.
430 - List of results present in MANIFEST but missing from ACTUAL.
431 """
432 # Collect all the actual results not present in the manifest.
433 # Results in this set will be reported as errors.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000434 actual_vs_manifest = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000435 for actual_result in actual:
436 if actual_result not in manifest:
437 actual_vs_manifest.add(actual_result)
438
439 # Collect all the tests in the manifest that were not found
440 # in the actual results.
441 # Results in this set will be reported as warnings (since
442 # they are expected failures that are not failing anymore).
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000443 manifest_vs_actual = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000444 for expected_result in manifest:
445 # Ignore tests marked flaky.
446 if 'flaky' in expected_result.attrs:
447 continue
448 if expected_result not in actual:
449 manifest_vs_actual.add(expected_result)
450
451 return actual_vs_manifest, manifest_vs_actual
452
453
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000454def GetManifestPath(user_provided_must_exist):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000455 """Return the full path to the manifest file."""
456 manifest_path = _OPTIONS.manifest
457 if manifest_path:
458 if user_provided_must_exist and not os.path.exists(manifest_path):
459 Error('Manifest does not exist: %s' % manifest_path)
460 return manifest_path
461 else:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000462 (srcdir, target) = GetBuildData()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000463 if not srcdir:
464 Error('Could not determine the location of GCC\'s source tree. '
465 'The Makefile does not contain a definition for "srcdir".')
466 if not target:
467 Error('Could not determine the target triplet for this build. '
468 'The Makefile does not contain a definition for "target_alias".')
469 return _MANIFEST_PATH_PATTERN % (srcdir, _MANIFEST_SUBDIR, target)
470
471
472def GetBuildData():
473 if not ValidBuildDirectory(_OPTIONS.build_dir):
474 # If we have been given a set of results to use, we may
475 # not be inside a valid GCC build directory. In that case,
476 # the user must provide both a manifest file and a set
477 # of results to check against it.
478 if not _OPTIONS.results or not _OPTIONS.manifest:
479 Error('%s is not a valid GCC top level build directory. '
480 'You must use --manifest and --results to do the validation.' %
481 _OPTIONS.build_dir)
482 else:
483 return None, None
484 srcdir = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'srcdir =')
485 target = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'target_alias=')
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000486 if _OPTIONS.verbosity >= 3:
487 print('Source directory: %s' % srcdir)
488 print('Build target: %s' % target)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000489 return srcdir, target
490
491
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000492def PrintSummary(summary):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000493 summary.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000494
495def GetSumFiles(results, build_dir):
496 if not results:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000497 if _OPTIONS.verbosity >= 3:
498 print('Getting actual results from build directory %s' % build_dir)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000499 sum_files = CollectSumFiles(build_dir)
500 else:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000501 if _OPTIONS.verbosity >= 3:
502 print('Getting actual results from user-provided results')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000503 sum_files = results.split()
504 return sum_files
505
506
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000507def PerformComparison(expected, actual):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000508 actual_vs_expected, expected_vs_actual = CompareResults(expected, actual)
509
510 tests_ok = True
511 if len(actual_vs_expected) > 0:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000512 if _OPTIONS.verbosity >= 3:
513 print('\n\nUnexpected results in this build (new failures)')
514 if _OPTIONS.verbosity >= 1:
515 PrintSummary(actual_vs_expected)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000516 tests_ok = False
517
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000518 if _OPTIONS.verbosity >= 2 and len(expected_vs_actual) > 0:
519 print('\n\nExpected results not present in this build (fixed tests)'
520 '\n\nNOTE: This is not a failure. It just means that these '
521 'tests were expected\nto fail, but either they worked in '
522 'this configuration or they were not\npresent at all.\n')
523 PrintSummary(expected_vs_actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000524
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000525 if tests_ok and _OPTIONS.verbosity >= 3:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000526 print('\nSUCCESS: No unexpected failures.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000527
528 return tests_ok
529
530
531def CheckExpectedResults():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000532 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000533 if _OPTIONS.verbosity >= 3:
534 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000535 manifest = GetManifest(manifest_path)
536 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
537 actual = GetResults(sum_files)
538
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000539 if _OPTIONS.verbosity >= 5:
540 print('\n\nTests expected to fail')
541 PrintSummary(manifest)
542 print('\n\nActual test results')
543 PrintSummary(actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000544
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000545 return PerformComparison(manifest, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000546
547
548def ProduceManifest():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000549 manifest_path = GetManifestPath(False)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000550 if _OPTIONS.verbosity >= 3:
551 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000552 if os.path.exists(manifest_path) and not _OPTIONS.force:
553 Error('Manifest file %s already exists.\nUse --force to overwrite.' %
554 manifest_path)
555
556 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
557 actual = GetResults(sum_files)
558 manifest_file = open(manifest_path, 'w')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000559 actual.Print(manifest_file)
560 actual.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000561 manifest_file.close()
562
563 return True
564
565
566def CompareBuilds():
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000567 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
568 actual = GetResults(sum_files)
569
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000570 clean = ResultSet()
571
572 if _OPTIONS.manifest:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000573 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000574 if _OPTIONS.verbosity >= 3:
575 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000576 clean = GetManifest(manifest_path)
577
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000578 clean_sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.clean_build)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000579 clean = GetResults(clean_sum_files, clean)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000580
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000581 return PerformComparison(clean, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000582
583
584def Main(argv):
585 parser = optparse.OptionParser(usage=__doc__)
586
587 # Keep the following list sorted by option name.
588 parser.add_option('--build_dir', action='store', type='string',
589 dest='build_dir', default='.',
590 help='Build directory to check (default = .)')
591 parser.add_option('--clean_build', action='store', type='string',
592 dest='clean_build', default=None,
593 help='Compare test results from this build against '
594 'those of another (clean) build. Use this option '
595 'when comparing the test results of your patch versus '
596 'the test results of a clean build without your patch. '
597 'You must provide the path to the top directory of your '
598 'clean build.')
599 parser.add_option('--force', action='store_true', dest='force',
600 default=False, help='When used with --produce_manifest, '
601 'it will overwrite an existing manifest file '
602 '(default = False)')
Maxim Kuvyrkova6df4d22021-07-13 09:27:35 +0000603 parser.add_option('--ignore_ERRORs', action='store_true',
604 dest='ignore_ERRORs', default=False,
605 help='Ignore DejaGnu "ERROR: foo" results '
606 '(default = False)')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000607 parser.add_option('--manifest', action='store', type='string',
608 dest='manifest', default=None,
609 help='Name of the manifest file to use (default = '
610 'taken from '
611 'contrib/testsuite-managment/<target_alias>.xfail)')
612 parser.add_option('--produce_manifest', action='store_true',
613 dest='produce_manifest', default=False,
614 help='Produce the manifest for the current '
615 'build (default = False)')
616 parser.add_option('--results', action='store', type='string',
617 dest='results', default=None, help='Space-separated list '
618 'of .sum files with the testing results to check. The '
619 'only content needed from these files are the lines '
620 'starting with FAIL, XPASS or UNRESOLVED (default = '
621 '.sum files collected from the build directory).')
622 parser.add_option('--verbosity', action='store', dest='verbosity',
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000623 type='int', default=3, help='Verbosity level '
624 '(default = 3). Level 0: only error output, this is '
625 'useful in scripting when only the exit code is used. '
626 'Level 1: output unexpected failures. '
627 'Level 2: output unexpected passes. '
628 'Level 3: output helpful information. '
629 'Level 5: output debug information.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000630 global _OPTIONS
631 (_OPTIONS, _) = parser.parse_args(argv[1:])
632
Maxim Kuvyrkova6df4d22021-07-13 09:27:35 +0000633 # Handled test results.
634 global _VALID_TEST_RESULTS
635 global _VALID_TEST_RESULTS_REX
636 _VALID_TEST_RESULTS = [ 'FAIL', 'UNRESOLVED', 'XPASS' ]
637 if not _OPTIONS.ignore_ERRORs:
638 _VALID_TEST_RESULTS.append('ERROR')
Maxim Kuvyrkov56734ff2021-08-30 14:24:25 +0000639
640 valid_results_regex = "|".join(_VALID_TEST_RESULTS)
641 valid_results_regex = '(%s):\s*(\S+)\s*(.*)' % valid_results_regex
642 _VALID_TEST_RESULTS_REX = re.compile(valid_results_regex)
Maxim Kuvyrkova6df4d22021-07-13 09:27:35 +0000643
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000644 if _OPTIONS.produce_manifest:
645 retval = ProduceManifest()
646 elif _OPTIONS.clean_build:
647 retval = CompareBuilds()
648 else:
649 retval = CheckExpectedResults()
650
651 if retval:
652 return 0
653 else:
Maxim Kuvyrkov972bb812021-08-30 14:18:09 +0000654 return 2
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000655
656
657if __name__ == '__main__':
658 retval = Main(sys.argv)
659 sys.exit(retval)