blob: 4cf4ad1625cc1487742f5570b834426e08c12504 [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'
65_EXP_LINE_FORMAT = '\nRunning %s ...\n'
66_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')
Maxim Kuvyrkovd8951a22021-07-08 08:20:28 +000070_EXP_LINE_REX = re.compile('^Running (?:.*/testsuite/)?(.*\.exp) \.\.\.\n')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +000071_SUMMARY_LINE_REX = re.compile('^\t\t=== (.*) Summary ===\n')
72
Maxim Kuvyrkov59877482021-07-07 11:22:26 +000073# Subdirectory of srcdir in which to find the manifest file.
74_MANIFEST_SUBDIR = 'contrib/testsuite-management'
75
76# Pattern for naming manifest files.
77# The first argument should be the toplevel GCC(/GNU tool) source directory.
78# The second argument is the manifest subdir.
79# The third argument is the manifest target, which defaults to the target
80# triplet used during the build.
81_MANIFEST_PATH_PATTERN = '%s/%s/%s.xfail'
82
83# The options passed to the program.
84_OPTIONS = None
85
86def Error(msg):
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +000087 print('error: %s' % msg, file=sys.stderr)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +000088 sys.exit(1)
89
90
91class TestResult(object):
92 """Describes a single DejaGNU test result as emitted in .sum files.
93
94 We are only interested in representing unsuccessful tests. So, only
95 a subset of all the tests are loaded.
96
97 The summary line used to build the test result should have this format:
98
99 attrlist | XPASS: gcc.dg/unroll_1.c (test for excess errors)
100 ^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
101 optional state name description
102 attributes
103
104 Attributes:
105 attrlist: A comma separated list of attributes.
106 Valid values:
107 flaky Indicates that this test may not always fail. These
108 tests are reported, but their presence does not affect
109 the results.
110
111 expire=YYYYMMDD After this date, this test will produce an error
112 whether it is in the manifest or not.
113
114 state: One of UNRESOLVED, XPASS or FAIL.
115 name: File name for the test.
116 description: String describing the test (flags used, dejagnu message, etc)
117 ordinal: Monotonically increasing integer.
118 It is used to keep results for one .exp file sorted
119 by the order the tests were run.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000120 tool: Top-level testsuite name (aka "tool" in DejaGnu parlance) of the test.
121 exp: Name of .exp testsuite file.
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000122 """
123
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000124 def __init__(self, summary_line, ordinal, tool, exp):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000125 try:
126 (self.attrs, summary_line) = SplitAttributesFromSummaryLine(summary_line)
127 try:
128 (self.state,
129 self.name,
130 self.description) = re.match(r'([A-Z]+):\s*(\S+)\s*(.*)',
131 summary_line).groups()
132 except:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000133 print('Failed to parse summary line: "%s"' % summary_line)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000134 raise
135 self.ordinal = ordinal
Maxim Kuvyrkovf09ab0e2021-08-30 14:19:04 +0000136 if tool == None or exp == None:
137 # .sum file seem to be broken. There was no "tool" and/or "exp"
138 # lines preceding this result.
139 raise
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000140 self.tool = tool
141 self.exp = exp
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000142 except ValueError:
143 Error('Cannot parse summary line "%s"' % summary_line)
144
145 if self.state not in _VALID_TEST_RESULTS:
146 Error('Invalid test result %s in "%s" (parsed as "%s")' % (
147 self.state, summary_line, self))
148
149 def __lt__(self, other):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000150 if (self.tool != other.tool):
151 return self.tool < other.tool
152 if (self.exp != other.exp):
153 return self.exp < other.exp
154 if (self.name != other.name):
155 return self.name < other.name
156 return self.ordinal < other.ordinal
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000157
158 def __hash__(self):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000159 return (hash(self.state) ^ hash(self.tool) ^ hash(self.exp)
160 ^ hash(self.name) ^ hash(self.description))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000161
162 def __eq__(self, other):
163 return (self.state == other.state and
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000164 self.tool == other.tool and
165 self.exp == other.exp and
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000166 self.name == other.name and
167 self.description == other.description)
168
169 def __ne__(self, other):
170 return not (self == other)
171
172 def __str__(self):
173 attrs = ''
174 if self.attrs:
175 attrs = '%s | ' % self.attrs
176 return '%s%s: %s %s' % (attrs, self.state, self.name, self.description)
177
178 def ExpirationDate(self):
179 # Return a datetime.date object with the expiration date for this
180 # test result. Return None, if no expiration has been set.
181 if re.search(r'expire=', self.attrs):
182 expiration = re.search(r'expire=(\d\d\d\d)(\d\d)(\d\d)', self.attrs)
183 if not expiration:
184 Error('Invalid expire= format in "%s". Must be of the form '
185 '"expire=YYYYMMDD"' % self)
186 return datetime.date(int(expiration.group(1)),
187 int(expiration.group(2)),
188 int(expiration.group(3)))
189 return None
190
191 def HasExpired(self):
192 # Return True if the expiration date of this result has passed.
193 expiration_date = self.ExpirationDate()
194 if expiration_date:
195 now = datetime.date.today()
196 return now > expiration_date
197
198
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000199class ResultSet(set):
200 """Describes a set of DejaGNU test results.
201 This set can be read in from .sum files or emitted as a manifest.
202
203 Attributes:
204 current_tool: Name of the current top-level DejaGnu testsuite.
205 current_exp: Name of the current .exp testsuite file.
206 """
207
208 def __init__(self):
209 super().__init__()
210 self.ResetToolExp()
211
212 def ResetToolExp(self):
213 self.current_tool = None
214 self.current_exp = None
215
216 def MakeTestResult(self, summary_line, ordinal=-1):
217 return TestResult(summary_line, ordinal,
218 self.current_tool, self.current_exp)
219
220 def Print(self, outfile=sys.stdout):
221 current_tool = None
222 current_exp = None
223
224 for result in sorted(self):
225 if current_tool != result.tool:
226 current_tool = result.tool
227 outfile.write(_TOOL_LINE_FORMAT % current_tool)
228 if current_exp != result.exp:
229 current_exp = result.exp
230 outfile.write(_EXP_LINE_FORMAT % current_exp)
231 outfile.write('%s\n' % result)
232
233 outfile.write(_SUMMARY_LINE_FORMAT % 'Results')
234
235
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000236def GetMakefileValue(makefile_name, value_name):
237 if os.path.exists(makefile_name):
238 makefile = open(makefile_name)
239 for line in makefile:
240 if line.startswith(value_name):
241 (_, value) = line.split('=', 1)
242 value = value.strip()
243 makefile.close()
244 return value
245 makefile.close()
246 return None
247
248
249def ValidBuildDirectory(builddir):
250 if (not os.path.exists(builddir) or
251 not os.path.exists('%s/Makefile' % builddir)):
252 return False
253 return True
254
255
256def IsComment(line):
257 """Return True if line is a comment."""
258 return line.startswith('#')
259
260
261def SplitAttributesFromSummaryLine(line):
262 """Splits off attributes from a summary line, if present."""
263 if '|' in line and not _VALID_TEST_RESULTS_REX.match(line):
264 (attrs, line) = line.split('|', 1)
265 attrs = attrs.strip()
266 else:
267 attrs = ''
268 line = line.strip()
269 return (attrs, line)
270
271
272def IsInterestingResult(line):
273 """Return True if line is one of the summary lines we care about."""
274 (_, line) = SplitAttributesFromSummaryLine(line)
275 return bool(_VALID_TEST_RESULTS_REX.match(line))
276
277
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000278def IsToolLine(line):
279 """Return True if line mentions the tool (in DejaGnu terms) for the following tests."""
280 return bool(_TOOL_LINE_REX.match(line))
281
282
283def IsExpLine(line):
284 """Return True if line mentions the .exp file for the following tests."""
285 return bool(_EXP_LINE_REX.match(line))
286
287
288def IsSummaryLine(line):
289 """Return True if line starts .sum footer."""
290 return bool(_SUMMARY_LINE_REX.match(line))
291
292
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000293def IsInclude(line):
294 """Return True if line is an include of another file."""
295 return line.startswith("@include ")
296
297
298def GetIncludeFile(line, includer):
299 """Extract the name of the include file from line."""
300 includer_dir = os.path.dirname(includer)
301 include_file = line[len("@include "):]
302 return os.path.join(includer_dir, include_file.strip())
303
304
305def IsNegativeResult(line):
306 """Return True if line should be removed from the expected results."""
307 return line.startswith("@remove ")
308
309
310def GetNegativeResult(line):
311 """Extract the name of the negative result from line."""
312 line = line[len("@remove "):]
313 return line.strip()
314
315
316def ParseManifestWorker(result_set, manifest_path):
317 """Read manifest_path, adding the contents to result_set."""
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000318 if _OPTIONS.verbosity >= 5:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000319 print('Parsing manifest file %s.' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000320 manifest_file = open(manifest_path)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000321 for orig_line in manifest_file:
322 line = orig_line.strip()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000323 if line == "":
324 pass
325 elif IsComment(line):
326 pass
327 elif IsNegativeResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000328 result_set.remove(result_set.MakeTestResult(GetNegativeResult(line)))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000329 elif IsInclude(line):
330 ParseManifestWorker(result_set, GetIncludeFile(line, manifest_path))
331 elif IsInterestingResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000332 result_set.add(result_set.MakeTestResult(line))
333 elif IsExpLine(orig_line):
334 result_set.current_exp = _EXP_LINE_REX.match(orig_line).groups()[0]
335 elif IsToolLine(orig_line):
336 result_set.current_tool = _TOOL_LINE_REX.match(orig_line).groups()[0]
337 elif IsSummaryLine(orig_line):
338 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000339 else:
340 Error('Unrecognized line in manifest file: %s' % line)
341 manifest_file.close()
342
343
344def ParseManifest(manifest_path):
345 """Create a set of TestResult instances from the given manifest file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000346 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000347 ParseManifestWorker(result_set, manifest_path)
348 return result_set
349
350
351def ParseSummary(sum_fname):
352 """Create a set of TestResult instances from the given summary file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000353 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000354 # ordinal is used when sorting the results so that tests within each
355 # .exp file are kept sorted.
356 ordinal=0
357 sum_file = open(sum_fname)
358 for line in sum_file:
359 if IsInterestingResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000360 result = result_set.MakeTestResult(line, ordinal)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000361 ordinal += 1
362 if result.HasExpired():
363 # Tests that have expired are not added to the set of expected
364 # results. If they are still present in the set of actual results,
365 # they will cause an error to be reported.
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000366 if _OPTIONS.verbosity >= 3:
367 print('WARNING: Expected failure "%s" has expired.' % line.strip())
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000368 continue
369 result_set.add(result)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000370 elif IsExpLine(line):
371 result_set.current_exp = _EXP_LINE_REX.match(line).groups()[0]
372 elif IsToolLine(line):
373 result_set.current_tool = _TOOL_LINE_REX.match(line).groups()[0]
Maxim Kuvyrkovd8951a22021-07-08 08:20:28 +0000374 result_set.current_exp = None
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000375 elif IsSummaryLine(line):
376 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000377 sum_file.close()
378 return result_set
379
380
381def GetManifest(manifest_path):
382 """Build a set of expected failures from the manifest file.
383
384 Each entry in the manifest file should have the format understood
385 by the TestResult constructor.
386
387 If no manifest file exists for this target, it returns an empty set.
388 """
389 if os.path.exists(manifest_path):
390 return ParseManifest(manifest_path)
391 else:
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000392 return ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000393
394
395def CollectSumFiles(builddir):
396 sum_files = []
397 for root, dirs, files in os.walk(builddir):
398 for ignored in ('.svn', '.git'):
399 if ignored in dirs:
400 dirs.remove(ignored)
401 for fname in files:
402 if fname.endswith('.sum'):
403 sum_files.append(os.path.join(root, fname))
404 return sum_files
405
406
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000407def GetResults(sum_files, build_results = None):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000408 """Collect all the test results from the given .sum files."""
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000409 if build_results == None:
410 build_results = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000411 for sum_fname in sum_files:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000412 if _OPTIONS.verbosity >= 3:
413 print('\t%s' % sum_fname)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000414 build_results |= ParseSummary(sum_fname)
415 return build_results
416
417
418def CompareResults(manifest, actual):
419 """Compare sets of results and return two lists:
420 - List of results present in ACTUAL but missing from MANIFEST.
421 - List of results present in MANIFEST but missing from ACTUAL.
422 """
423 # Collect all the actual results not present in the manifest.
424 # Results in this set will be reported as errors.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000425 actual_vs_manifest = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000426 for actual_result in actual:
427 if actual_result not in manifest:
428 actual_vs_manifest.add(actual_result)
429
430 # Collect all the tests in the manifest that were not found
431 # in the actual results.
432 # Results in this set will be reported as warnings (since
433 # they are expected failures that are not failing anymore).
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000434 manifest_vs_actual = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000435 for expected_result in manifest:
436 # Ignore tests marked flaky.
437 if 'flaky' in expected_result.attrs:
438 continue
439 if expected_result not in actual:
440 manifest_vs_actual.add(expected_result)
441
442 return actual_vs_manifest, manifest_vs_actual
443
444
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000445def GetManifestPath(user_provided_must_exist):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000446 """Return the full path to the manifest file."""
447 manifest_path = _OPTIONS.manifest
448 if manifest_path:
449 if user_provided_must_exist and not os.path.exists(manifest_path):
450 Error('Manifest does not exist: %s' % manifest_path)
451 return manifest_path
452 else:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000453 (srcdir, target) = GetBuildData()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000454 if not srcdir:
455 Error('Could not determine the location of GCC\'s source tree. '
456 'The Makefile does not contain a definition for "srcdir".')
457 if not target:
458 Error('Could not determine the target triplet for this build. '
459 'The Makefile does not contain a definition for "target_alias".')
460 return _MANIFEST_PATH_PATTERN % (srcdir, _MANIFEST_SUBDIR, target)
461
462
463def GetBuildData():
464 if not ValidBuildDirectory(_OPTIONS.build_dir):
465 # If we have been given a set of results to use, we may
466 # not be inside a valid GCC build directory. In that case,
467 # the user must provide both a manifest file and a set
468 # of results to check against it.
469 if not _OPTIONS.results or not _OPTIONS.manifest:
470 Error('%s is not a valid GCC top level build directory. '
471 'You must use --manifest and --results to do the validation.' %
472 _OPTIONS.build_dir)
473 else:
474 return None, None
475 srcdir = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'srcdir =')
476 target = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'target_alias=')
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000477 if _OPTIONS.verbosity >= 3:
478 print('Source directory: %s' % srcdir)
479 print('Build target: %s' % target)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000480 return srcdir, target
481
482
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000483def PrintSummary(summary):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000484 summary.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000485
486def GetSumFiles(results, build_dir):
487 if not results:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000488 if _OPTIONS.verbosity >= 3:
489 print('Getting actual results from build directory %s' % build_dir)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000490 sum_files = CollectSumFiles(build_dir)
491 else:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000492 if _OPTIONS.verbosity >= 3:
493 print('Getting actual results from user-provided results')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000494 sum_files = results.split()
495 return sum_files
496
497
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000498def PerformComparison(expected, actual):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000499 actual_vs_expected, expected_vs_actual = CompareResults(expected, actual)
500
501 tests_ok = True
502 if len(actual_vs_expected) > 0:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000503 if _OPTIONS.verbosity >= 3:
504 print('\n\nUnexpected results in this build (new failures)')
505 if _OPTIONS.verbosity >= 1:
506 PrintSummary(actual_vs_expected)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000507 tests_ok = False
508
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000509 if _OPTIONS.verbosity >= 2 and len(expected_vs_actual) > 0:
510 print('\n\nExpected results not present in this build (fixed tests)'
511 '\n\nNOTE: This is not a failure. It just means that these '
512 'tests were expected\nto fail, but either they worked in '
513 'this configuration or they were not\npresent at all.\n')
514 PrintSummary(expected_vs_actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000515
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000516 if tests_ok and _OPTIONS.verbosity >= 3:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000517 print('\nSUCCESS: No unexpected failures.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000518
519 return tests_ok
520
521
522def CheckExpectedResults():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000523 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000524 if _OPTIONS.verbosity >= 3:
525 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000526 manifest = GetManifest(manifest_path)
527 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
528 actual = GetResults(sum_files)
529
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000530 if _OPTIONS.verbosity >= 5:
531 print('\n\nTests expected to fail')
532 PrintSummary(manifest)
533 print('\n\nActual test results')
534 PrintSummary(actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000535
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000536 return PerformComparison(manifest, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000537
538
539def ProduceManifest():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000540 manifest_path = GetManifestPath(False)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000541 if _OPTIONS.verbosity >= 3:
542 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000543 if os.path.exists(manifest_path) and not _OPTIONS.force:
544 Error('Manifest file %s already exists.\nUse --force to overwrite.' %
545 manifest_path)
546
547 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
548 actual = GetResults(sum_files)
549 manifest_file = open(manifest_path, 'w')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000550 actual.Print(manifest_file)
551 actual.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000552 manifest_file.close()
553
554 return True
555
556
557def CompareBuilds():
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000558 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
559 actual = GetResults(sum_files)
560
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000561 clean = ResultSet()
562
563 if _OPTIONS.manifest:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000564 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000565 if _OPTIONS.verbosity >= 3:
566 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000567 clean = GetManifest(manifest_path)
568
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000569 clean_sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.clean_build)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000570 clean = GetResults(clean_sum_files, clean)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000571
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000572 return PerformComparison(clean, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000573
574
575def Main(argv):
576 parser = optparse.OptionParser(usage=__doc__)
577
578 # Keep the following list sorted by option name.
579 parser.add_option('--build_dir', action='store', type='string',
580 dest='build_dir', default='.',
581 help='Build directory to check (default = .)')
582 parser.add_option('--clean_build', action='store', type='string',
583 dest='clean_build', default=None,
584 help='Compare test results from this build against '
585 'those of another (clean) build. Use this option '
586 'when comparing the test results of your patch versus '
587 'the test results of a clean build without your patch. '
588 'You must provide the path to the top directory of your '
589 'clean build.')
590 parser.add_option('--force', action='store_true', dest='force',
591 default=False, help='When used with --produce_manifest, '
592 'it will overwrite an existing manifest file '
593 '(default = False)')
Maxim Kuvyrkova6df4d22021-07-13 09:27:35 +0000594 parser.add_option('--ignore_ERRORs', action='store_true',
595 dest='ignore_ERRORs', default=False,
596 help='Ignore DejaGnu "ERROR: foo" results '
597 '(default = False)')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000598 parser.add_option('--manifest', action='store', type='string',
599 dest='manifest', default=None,
600 help='Name of the manifest file to use (default = '
601 'taken from '
602 'contrib/testsuite-managment/<target_alias>.xfail)')
603 parser.add_option('--produce_manifest', action='store_true',
604 dest='produce_manifest', default=False,
605 help='Produce the manifest for the current '
606 'build (default = False)')
607 parser.add_option('--results', action='store', type='string',
608 dest='results', default=None, help='Space-separated list '
609 'of .sum files with the testing results to check. The '
610 'only content needed from these files are the lines '
611 'starting with FAIL, XPASS or UNRESOLVED (default = '
612 '.sum files collected from the build directory).')
613 parser.add_option('--verbosity', action='store', dest='verbosity',
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000614 type='int', default=3, help='Verbosity level '
615 '(default = 3). Level 0: only error output, this is '
616 'useful in scripting when only the exit code is used. '
617 'Level 1: output unexpected failures. '
618 'Level 2: output unexpected passes. '
619 'Level 3: output helpful information. '
620 'Level 5: output debug information.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000621 global _OPTIONS
622 (_OPTIONS, _) = parser.parse_args(argv[1:])
623
Maxim Kuvyrkova6df4d22021-07-13 09:27:35 +0000624 # Handled test results.
625 global _VALID_TEST_RESULTS
626 global _VALID_TEST_RESULTS_REX
627 _VALID_TEST_RESULTS = [ 'FAIL', 'UNRESOLVED', 'XPASS' ]
628 if not _OPTIONS.ignore_ERRORs:
629 _VALID_TEST_RESULTS.append('ERROR')
630 _VALID_TEST_RESULTS_REX = re.compile("%s" % "|".join(_VALID_TEST_RESULTS))
631
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000632 if _OPTIONS.produce_manifest:
633 retval = ProduceManifest()
634 elif _OPTIONS.clean_build:
635 retval = CompareBuilds()
636 else:
637 retval = CheckExpectedResults()
638
639 if retval:
640 return 0
641 else:
Maxim Kuvyrkov972bb812021-08-30 14:18:09 +0000642 return 2
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000643
644
645if __name__ == '__main__':
646 retval = Main(sys.argv)
647 sys.exit(retval)