blob: 052bbd120b2f105ee29de2dd6dd2882720e9a654 [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
Maxim Kuvyrkova0d90542024-07-23 11:37:04 +000062import json
Maxim Kuvyrkov59877482021-07-07 11:22:26 +000063
Maxim Kuvyrkova0d90542024-07-23 11:37:04 +000064# Results that we want keep an eye on.
65# Note, 'NOEXE' state is used by llvm-test-suite.
66_INTERESTING_RESULTS = [ 'FAIL', 'UNRESOLVED', 'XPASS', 'ERROR', 'NOEXE' ]
Maxim Kuvyrkov8704bc12023-05-03 15:03:34 +000067# <STATE>: <NAME> <DESCRIPTION"
Christophe Lyon82987872025-04-04 09:15:20 +000068_INTERESTING_RESULTS_REX = re.compile(r'(%s):\s*(\S+)\s*(.*)'
Maxim Kuvyrkov966fac42024-04-01 11:47:27 +000069 % "|".join(_INTERESTING_RESULTS))
Maxim Kuvyrkov8704bc12023-05-03 15:03:34 +000070
Christophe Lyon82987872025-04-04 09:15:20 +000071_VALID_RESULTS_REX = re.compile(r'([A-Z]+):\s*(\S+)\s*(.*)')
Maxim Kuvyrkov88799c62024-04-01 12:33:09 +000072
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +000073# Formats of .sum file sections
74_TOOL_LINE_FORMAT = '\t\t=== %s tests ===\n'
Christophe Lyona7d8c4c2023-04-14 12:01:23 +000075_EXP_LINE_FORMAT = '\nRunning %s:%s ...\n'
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +000076_SUMMARY_LINE_FORMAT = '\n\t\t=== %s Summary ===\n'
77
78# ... and their compiled regexs.
Christophe Lyon82987872025-04-04 09:15:20 +000079_TOOL_LINE_REX = re.compile(r'^\t\t=== (.*) tests ===\n')
Christophe Lyona7d8c4c2023-04-14 12:01:23 +000080# Match .exp file name, optionally prefixed by a "tool:" name and a
81# path ending with "testsuite/"
Christophe Lyon82987872025-04-04 09:15:20 +000082_EXP_LINE_REX = re.compile(r'^Running (?:.*:)?(.*) \.\.\.\n')
83_SUMMARY_LINE_REX = re.compile(r'^\t\t=== (.*) Summary ===\n')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +000084
Maxim Kuvyrkov59877482021-07-07 11:22:26 +000085# Subdirectory of srcdir in which to find the manifest file.
86_MANIFEST_SUBDIR = 'contrib/testsuite-management'
87
88# Pattern for naming manifest files.
89# The first argument should be the toplevel GCC(/GNU tool) source directory.
90# The second argument is the manifest subdir.
91# The third argument is the manifest target, which defaults to the target
92# triplet used during the build.
93_MANIFEST_PATH_PATTERN = '%s/%s/%s.xfail'
94
95# The options passed to the program.
96_OPTIONS = None
97
98def Error(msg):
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +000099 print('error: %s' % msg, file=sys.stderr)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000100 sys.exit(1)
101
102
103class TestResult(object):
104 """Describes a single DejaGNU test result as emitted in .sum files.
105
106 We are only interested in representing unsuccessful tests. So, only
107 a subset of all the tests are loaded.
108
109 The summary line used to build the test result should have this format:
110
111 attrlist | XPASS: gcc.dg/unroll_1.c (test for excess errors)
112 ^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
113 optional state name description
114 attributes
115
116 Attributes:
117 attrlist: A comma separated list of attributes.
118 Valid values:
119 flaky Indicates that this test may not always fail. These
120 tests are reported, but their presence does not affect
121 the results.
122
123 expire=YYYYMMDD After this date, this test will produce an error
124 whether it is in the manifest or not.
125
126 state: One of UNRESOLVED, XPASS or FAIL.
127 name: File name for the test.
128 description: String describing the test (flags used, dejagnu message, etc)
129 ordinal: Monotonically increasing integer.
130 It is used to keep results for one .exp file sorted
131 by the order the tests were run.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000132 tool: Top-level testsuite name (aka "tool" in DejaGnu parlance) of the test.
133 exp: Name of .exp testsuite file.
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000134 """
135
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000136 def __init__(self, summary_line, ordinal, tool, exp):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000137 try:
138 (self.attrs, summary_line) = SplitAttributesFromSummaryLine(summary_line)
139 try:
140 (self.state,
141 self.name,
Maxim Kuvyrkov966fac42024-04-01 11:47:27 +0000142 self.description) = _INTERESTING_RESULTS_REX.match(summary_line).groups()
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000143 if _OPTIONS.srcpath_regex and _OPTIONS.srcpath_regex != '':
144 self.description = re.sub(_OPTIONS.srcpath_regex, '',
145 self.description)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000146 except:
Thiago Jung Bauermann7b82a592023-04-22 14:12:06 +0000147 print('Failed to parse summary line: "%s"' % summary_line,
148 file=sys.stderr)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000149 raise
150 self.ordinal = ordinal
Maxim Kuvyrkovf09ab0e2021-08-30 14:19:04 +0000151 if tool == None or exp == None:
152 # .sum file seem to be broken. There was no "tool" and/or "exp"
153 # lines preceding this result.
Thiago Jung Bauermann7b82a592023-04-22 14:12:06 +0000154 print(f'.sum file seems to be broken: tool="{tool}", exp="{exp}", summary_line="{summary_line}"',
155 file=sys.stderr)
Maxim Kuvyrkovf09ab0e2021-08-30 14:19:04 +0000156 raise
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000157 self.tool = tool
158 self.exp = exp
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000159 except ValueError:
160 Error('Cannot parse summary line "%s"' % summary_line)
161
Maxim Kuvyrkov966fac42024-04-01 11:47:27 +0000162 if self.state not in _INTERESTING_RESULTS:
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000163 Error('Invalid test result %s in "%s" (parsed as "%s")' % (
164 self.state, summary_line, self))
165
166 def __lt__(self, other):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000167 if (self.tool != other.tool):
168 return self.tool < other.tool
169 if (self.exp != other.exp):
170 return self.exp < other.exp
171 if (self.name != other.name):
172 return self.name < other.name
173 return self.ordinal < other.ordinal
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000174
175 def __hash__(self):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000176 return (hash(self.state) ^ hash(self.tool) ^ hash(self.exp)
177 ^ hash(self.name) ^ hash(self.description))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000178
Maxim Kuvyrkov7df81782023-05-25 06:42:06 +0000179 # Note that we don't include "attrs" in this comparison. This means that
180 # result entries "FAIL: test" and "flaky | FAIL: test" are considered
181 # the same. Therefore the ResultSet will preserve only the first occurence.
182 # In practice this means that flaky entries should preceed expected fails
183 # entries.
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000184 def __eq__(self, other):
185 return (self.state == other.state and
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000186 self.tool == other.tool and
187 self.exp == other.exp and
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000188 self.name == other.name and
189 self.description == other.description)
190
191 def __ne__(self, other):
192 return not (self == other)
193
Leandro Luporif98e8ea2024-09-10 15:07:45 -0300194 def IsSameTest(self, other):
195 return (self.tool == other.tool and
196 self.exp == other.exp and
197 self.name == other.name and
198 self.description == other.description)
199
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000200 def __str__(self):
201 attrs = ''
202 if self.attrs:
203 attrs = '%s | ' % self.attrs
204 return '%s%s: %s %s' % (attrs, self.state, self.name, self.description)
205
206 def ExpirationDate(self):
207 # Return a datetime.date object with the expiration date for this
208 # test result. Return None, if no expiration has been set.
209 if re.search(r'expire=', self.attrs):
210 expiration = re.search(r'expire=(\d\d\d\d)(\d\d)(\d\d)', self.attrs)
211 if not expiration:
212 Error('Invalid expire= format in "%s". Must be of the form '
213 '"expire=YYYYMMDD"' % self)
214 return datetime.date(int(expiration.group(1)),
215 int(expiration.group(2)),
216 int(expiration.group(3)))
217 return None
218
219 def HasExpired(self):
220 # Return True if the expiration date of this result has passed.
221 expiration_date = self.ExpirationDate()
222 if expiration_date:
Maxim Kuvyrkov158e61d2023-05-25 12:18:30 +0000223 return _OPTIONS.expiry_today_date > expiration_date
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000224
225
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000226class ResultSet(set):
227 """Describes a set of DejaGNU test results.
228 This set can be read in from .sum files or emitted as a manifest.
229
230 Attributes:
231 current_tool: Name of the current top-level DejaGnu testsuite.
232 current_exp: Name of the current .exp testsuite file.
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000233 testsuites: A set of (tool, exp) tuples representing encountered testsuites.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000234 """
235
236 def __init__(self):
237 super().__init__()
238 self.ResetToolExp()
Maxim Kuvyrkov88799c62024-04-01 12:33:09 +0000239 self.testsuites = set()
240 self.total = 0
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000241
242 def update(self, other):
243 super().update(other)
244 self.testsuites.update(other.testsuites)
Maxim Kuvyrkov88799c62024-04-01 12:33:09 +0000245 self.total += other.total
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000246
247 def ResetToolExp(self):
248 self.current_tool = None
249 self.current_exp = None
250
251 def MakeTestResult(self, summary_line, ordinal=-1):
252 return TestResult(summary_line, ordinal,
253 self.current_tool, self.current_exp)
254
255 def Print(self, outfile=sys.stdout):
256 current_tool = None
257 current_exp = None
258
259 for result in sorted(self):
260 if current_tool != result.tool:
261 current_tool = result.tool
262 outfile.write(_TOOL_LINE_FORMAT % current_tool)
Maxim Kuvyrkov3a777532024-04-18 11:35:58 +0000263 current_exp = None
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000264 if current_exp != result.exp:
265 current_exp = result.exp
Christophe Lyona7d8c4c2023-04-14 12:01:23 +0000266 outfile.write(_EXP_LINE_FORMAT % (current_tool, current_exp))
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000267 outfile.write('%s\n' % result)
268
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000269 # Check if testsuite of expected_result is present in current results.
270 # This is used to compare partial test results against a full manifest.
271 def HasTestsuite(self, expected_result):
272 return (expected_result.tool, expected_result.exp) in self.testsuites
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000273
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000274def GetMakefileValue(makefile_name, value_name):
275 if os.path.exists(makefile_name):
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000276 makefile = open(makefile_name, encoding='latin-1', mode='r')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000277 for line in makefile:
278 if line.startswith(value_name):
279 (_, value) = line.split('=', 1)
280 value = value.strip()
281 makefile.close()
282 return value
283 makefile.close()
284 return None
285
286
287def ValidBuildDirectory(builddir):
288 if (not os.path.exists(builddir) or
289 not os.path.exists('%s/Makefile' % builddir)):
290 return False
291 return True
292
293
294def IsComment(line):
295 """Return True if line is a comment."""
296 return line.startswith('#')
297
298
299def SplitAttributesFromSummaryLine(line):
300 """Splits off attributes from a summary line, if present."""
Maxim Kuvyrkov966fac42024-04-01 11:47:27 +0000301 if '|' in line and not _INTERESTING_RESULTS_REX.match(line):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000302 (attrs, line) = line.split('|', 1)
303 attrs = attrs.strip()
304 else:
305 attrs = ''
306 line = line.strip()
307 return (attrs, line)
308
309
Thiago Jung Bauermann41505402023-06-14 14:37:00 +0200310def IsInterestingResult(result_set, line):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000311 """Return True if line is one of the summary lines we care about."""
312 (_, line) = SplitAttributesFromSummaryLine(line)
Maxim Kuvyrkov966fac42024-04-01 11:47:27 +0000313 interesting_result = bool(_INTERESTING_RESULTS_REX.match(line))
Thiago Jung Bauermann41505402023-06-14 14:37:00 +0200314
Maxim Kuvyrkovc0253642024-04-01 11:51:59 +0000315 # If there's no .exp defined it means that either the results section hasn't
Thiago Jung Bauermann41505402023-06-14 14:37:00 +0200316 # started yet, or it is already over.
Maxim Kuvyrkovc0253642024-04-01 11:51:59 +0000317 if interesting_result and result_set.current_exp is None:
Thiago Jung Bauermann41505402023-06-14 14:37:00 +0200318 if _OPTIONS.verbosity >= 3:
Maxim Kuvyrkovc0253642024-04-01 11:51:59 +0000319 print(f'WARNING: Result "{line}" found outside sum file boundaries.')
Thiago Jung Bauermann41505402023-06-14 14:37:00 +0200320 return False
321
Maxim Kuvyrkov966fac42024-04-01 11:47:27 +0000322 return interesting_result
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000323
324
Maxim Kuvyrkov88799c62024-04-01 12:33:09 +0000325def IsValidResult(result_set, line):
326 """Return True if line is a valid test result."""
327 valid_result = bool(_VALID_RESULTS_REX.match(line))
328
329 # If there's no .exp defined it means that either the results section hasn't
330 # started yet, or it is already over.
331 if valid_result and result_set.current_exp is None:
332 if _OPTIONS.verbosity >= 3:
333 print(f'WARNING: Result "{line}" found outside sum file boundaries.')
334 return False
335
336 return valid_result
337
338
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000339def IsToolLine(line):
340 """Return True if line mentions the tool (in DejaGnu terms) for the following tests."""
341 return bool(_TOOL_LINE_REX.match(line))
342
343
344def IsExpLine(line):
345 """Return True if line mentions the .exp file for the following tests."""
346 return bool(_EXP_LINE_REX.match(line))
347
348
349def IsSummaryLine(line):
350 """Return True if line starts .sum footer."""
351 return bool(_SUMMARY_LINE_REX.match(line))
352
353
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000354def IsInclude(line):
355 """Return True if line is an include of another file."""
356 return line.startswith("@include ")
357
358
359def GetIncludeFile(line, includer):
360 """Extract the name of the include file from line."""
361 includer_dir = os.path.dirname(includer)
362 include_file = line[len("@include "):]
363 return os.path.join(includer_dir, include_file.strip())
364
365
366def IsNegativeResult(line):
367 """Return True if line should be removed from the expected results."""
368 return line.startswith("@remove ")
369
370
371def GetNegativeResult(line):
372 """Extract the name of the negative result from line."""
373 line = line[len("@remove "):]
374 return line.strip()
375
376
377def ParseManifestWorker(result_set, manifest_path):
378 """Read manifest_path, adding the contents to result_set."""
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000379 if _OPTIONS.verbosity >= 5:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000380 print('Parsing manifest file %s.' % manifest_path)
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000381 manifest_file = open(manifest_path, encoding='latin-1', mode='r')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000382 for orig_line in manifest_file:
383 line = orig_line.strip()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000384 if line == "":
385 pass
386 elif IsComment(line):
387 pass
388 elif IsNegativeResult(line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000389 result_set.remove(result_set.MakeTestResult(GetNegativeResult(line)))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000390 elif IsInclude(line):
391 ParseManifestWorker(result_set, GetIncludeFile(line, manifest_path))
Thiago Jung Bauermann41505402023-06-14 14:37:00 +0200392 elif IsInterestingResult(result_set, line):
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000393 result = result_set.MakeTestResult(line)
394 if result.HasExpired():
395 # Ignore expired manifest entries.
Maxim Kuvyrkov7df81782023-05-25 06:42:06 +0000396 if _OPTIONS.verbosity >= 4:
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000397 print('WARNING: Expected failure "%s" has expired.' % line.strip())
398 continue
399 result_set.add(result)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000400 elif IsExpLine(orig_line):
401 result_set.current_exp = _EXP_LINE_REX.match(orig_line).groups()[0]
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000402 if _OPTIONS.srcpath_regex and _OPTIONS.srcpath_regex != '':
403 result_set.current_exp = re.sub(_OPTIONS.srcpath_regex, '',
404 result_set.current_exp)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000405 elif IsToolLine(orig_line):
406 result_set.current_tool = _TOOL_LINE_REX.match(orig_line).groups()[0]
Maxim Kuvyrkovc0253642024-04-01 11:51:59 +0000407 result_set.current_exp = None
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000408 elif IsSummaryLine(orig_line):
409 result_set.ResetToolExp()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000410 else:
411 Error('Unrecognized line in manifest file: %s' % line)
412 manifest_file.close()
413
414
415def ParseManifest(manifest_path):
416 """Create a set of TestResult instances from the given manifest file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000417 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000418 ParseManifestWorker(result_set, manifest_path)
419 return result_set
420
421
Maxim Kuvyrkova0d90542024-07-23 11:37:04 +0000422def convertJSONResult(json_result):
423 """Convert JSON result into a dejagnu-like line."""
424 # We are just starting to process JSON data (from LLVM's testsuite),
425 # and it seems prudent to convert JSON into dejagnu-like lines, so that we
426 # can re-use dejagnu parsing logic, rather than duplicating it for JSON.
427
428 name = json_result['name']
429 tool_exp = name.split(' :: ')
430 tool_exp[1] = os.path.dirname(tool_exp[1])
431 line = json_result['code'] + ': ' + json_result['name']
432 tool_exp.append(line)
433 return tool_exp
434
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000435def ParseSummary(sum_fname):
436 """Create a set of TestResult instances from the given summary file."""
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000437 result_set = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000438 # ordinal is used when sorting the results so that tests within each
439 # .exp file are kept sorted.
440 ordinal=0
Maxim Kuvyrkova0d90542024-07-23 11:37:04 +0000441
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000442 sum_file = open(sum_fname, encoding='latin-1', mode='r')
Maxim Kuvyrkova0d90542024-07-23 11:37:04 +0000443 file_results = sum_file
444 json_input = sum_fname.endswith('.json')
445
446 if json_input:
Christophe Lyon23708902024-08-08 16:03:26 +0000447 try:
448 file_results = json.load(sum_file)
449 except:
450 if _OPTIONS.verbosity >= 2:
451 # GCC's testing generates random .json files, which we can detect
452 # as results. Some of them cannot even be parsed.
453 print(f'WARNING: cannot parse JSON result file "{sum_fname}"')
454 return result_set
455
Maxim Kuvyrkova9243212024-08-03 09:37:01 +0000456 if not 'tests' in file_results:
457 if _OPTIONS.verbosity >= 2:
458 # GCC's testing generates random .json files, which we can detect
459 # as results.
460 print(f'WARNING: malformed JSON result file "{sum_fname}"')
461 return result_set
462
Maxim Kuvyrkova0d90542024-07-23 11:37:04 +0000463 file_results = file_results['tests']
464
465 for file_result in file_results:
466 if json_input:
467 (result_set.current_tool, result_set.current_exp, line)\
468 = convertJSONResult(file_result)
Maxim Kuvyrkov13a82492024-07-31 07:32:16 +0000469 # This is a copy of logic from IsExpLine below
470 if _OPTIONS.srcpath_regex and _OPTIONS.srcpath_regex != '':
471 result_set.current_exp = re.sub(_OPTIONS.srcpath_regex, '',
472 result_set.current_exp)
473 result_set.testsuites.add((result_set.current_tool,
474 result_set.current_exp))
Thiago Jung Bauermann672eb022024-07-24 14:43:47 -0300475 else:
476 line = file_result
Maxim Kuvyrkova0d90542024-07-23 11:37:04 +0000477
Thiago Jung Bauermann41505402023-06-14 14:37:00 +0200478 if IsInterestingResult(result_set, line):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000479 result = result_set.MakeTestResult(line, ordinal)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000480 ordinal += 1
481 if result.HasExpired():
Maxim Kuvyrkova6b29dd2023-04-12 14:35:39 +0000482 # ??? What is the use-case for this? How "expiry" annotations are
483 # ??? supposed to be added to .sum results?
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000484 # Tests that have expired are not added to the set of expected
485 # results. If they are still present in the set of actual results,
486 # they will cause an error to be reported.
Maxim Kuvyrkov7df81782023-05-25 06:42:06 +0000487 if _OPTIONS.verbosity >= 4:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000488 print('WARNING: Expected failure "%s" has expired.' % line.strip())
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000489 continue
490 result_set.add(result)
Maxim Kuvyrkov88799c62024-04-01 12:33:09 +0000491 result_set.total += 1
492 elif IsValidResult(result_set, line):
493 result_set.total += 1
Maxim Kuvyrkova0d90542024-07-23 11:37:04 +0000494 elif json_input:
495 Error('Unrecognized json result: %s' % line)
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000496 elif IsExpLine(line):
497 result_set.current_exp = _EXP_LINE_REX.match(line).groups()[0]
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000498 if _OPTIONS.srcpath_regex and _OPTIONS.srcpath_regex != '':
499 result_set.current_exp = re.sub(_OPTIONS.srcpath_regex, '',
500 result_set.current_exp)
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000501 result_set.testsuites.add((result_set.current_tool,
502 result_set.current_exp))
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000503 elif IsToolLine(line):
504 result_set.current_tool = _TOOL_LINE_REX.match(line).groups()[0]
Maxim Kuvyrkovd8951a22021-07-08 08:20:28 +0000505 result_set.current_exp = None
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000506 elif IsSummaryLine(line):
507 result_set.ResetToolExp()
Maxim Kuvyrkova0d90542024-07-23 11:37:04 +0000508
509 if json_input:
510 result_set.ResetToolExp()
511
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000512 sum_file.close()
513 return result_set
514
515
516def GetManifest(manifest_path):
517 """Build a set of expected failures from the manifest file.
518
519 Each entry in the manifest file should have the format understood
520 by the TestResult constructor.
521
522 If no manifest file exists for this target, it returns an empty set.
523 """
524 if os.path.exists(manifest_path):
525 return ParseManifest(manifest_path)
526 else:
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000527 return ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000528
529
530def CollectSumFiles(builddir):
531 sum_files = []
532 for root, dirs, files in os.walk(builddir):
533 for ignored in ('.svn', '.git'):
534 if ignored in dirs:
535 dirs.remove(ignored)
536 for fname in files:
Maxim Kuvyrkovdfc04502024-07-29 13:00:19 +0000537 if fname.endswith('.sum') or fname.endswith('.json'):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000538 sum_files.append(os.path.join(root, fname))
539 return sum_files
540
541
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000542def GetResults(sum_files, build_results = None):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000543 """Collect all the test results from the given .sum files."""
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000544 if build_results == None:
545 build_results = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000546 for sum_fname in sum_files:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000547 if _OPTIONS.verbosity >= 3:
548 print('\t%s' % sum_fname)
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000549 build_results.update(ParseSummary(sum_fname))
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000550 return build_results
551
Maxim Kuvyrkovc0014022024-04-01 12:26:44 +0000552class ResultsStats:
553 """Describes statistics of DejaGNU test results.
554
555 Attributes:
556 fails: Number of non-flaky failed tests in the results.
557 flaky: Number of flaky entries in the manifest.
558 total: Total number of tests in the results, including flaky passes and
559 fails.
560 """
561
562 def __init__(self):
563 self.fails = 0
564 self.flaky = 0
565 self.total = 0
566
567 def Print(self, outfile=sys.stdout):
568 outfile.write(_SUMMARY_LINE_FORMAT % 'Results')
569 outfile.write(f'\n')
570 outfile.write(f'# of stable fails\t\t{self.fails}\n')
571 outfile.write(f'# of flaky entries\t\t{self.flaky}\n')
572 outfile.write(f'# of all tests\t\t\t{self.total}\n')
573
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000574
Laurent Alfonsi1aabe572024-10-08 16:53:01 +0200575def CompareResults(manifest, actual, limit_to_subset):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000576 """Compare sets of results and return two lists:
577 - List of results present in ACTUAL but missing from MANIFEST.
578 - List of results present in MANIFEST but missing from ACTUAL.
579 """
580 # Collect all the actual results not present in the manifest.
581 # Results in this set will be reported as errors.
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000582 actual_vs_manifest = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000583 for actual_result in actual:
584 if actual_result not in manifest:
Laurent Alfonsi1aabe572024-10-08 16:53:01 +0200585 # When using limit_to_subset, report only the ones from limit_to_subset
586 if limit_to_subset==None or actual_result in limit_to_subset:
587 actual_vs_manifest.add(actual_result)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000588
589 # Collect all the tests in the manifest that were not found
590 # in the actual results.
591 # Results in this set will be reported as warnings (since
592 # they are expected failures that are not failing anymore).
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000593 manifest_vs_actual = ResultSet()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000594 for expected_result in manifest:
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000595 # We try to support comparing partial results vs full manifest
596 # (e.g., manifest has failures for gcc, g++, gfortran, but we ran only
597 # g++ testsuite). To achieve this we record encountered testsuites in
598 # actual.testsuites set, and then we check it here using HasTestsuite().
599 if expected_result not in actual and actual.HasTestsuite(expected_result):
Laurent Alfonsi1aabe572024-10-08 16:53:01 +0200600 # When using limit_to_subset, report only the ones from limit_to_subset
601 if limit_to_subset==None or expected_result in limit_to_subset:
602 manifest_vs_actual.add(expected_result)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000603
604 return actual_vs_manifest, manifest_vs_actual
605
606
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000607def GetManifestPath(user_provided_must_exist):
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000608 """Return the full path to the manifest file."""
609 manifest_path = _OPTIONS.manifest
610 if manifest_path:
611 if user_provided_must_exist and not os.path.exists(manifest_path):
612 Error('Manifest does not exist: %s' % manifest_path)
613 return manifest_path
614 else:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000615 (srcdir, target) = GetBuildData()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000616 if not srcdir:
617 Error('Could not determine the location of GCC\'s source tree. '
618 'The Makefile does not contain a definition for "srcdir".')
619 if not target:
620 Error('Could not determine the target triplet for this build. '
621 'The Makefile does not contain a definition for "target_alias".')
622 return _MANIFEST_PATH_PATTERN % (srcdir, _MANIFEST_SUBDIR, target)
623
624
625def GetBuildData():
626 if not ValidBuildDirectory(_OPTIONS.build_dir):
627 # If we have been given a set of results to use, we may
628 # not be inside a valid GCC build directory. In that case,
629 # the user must provide both a manifest file and a set
630 # of results to check against it.
631 if not _OPTIONS.results or not _OPTIONS.manifest:
632 Error('%s is not a valid GCC top level build directory. '
633 'You must use --manifest and --results to do the validation.' %
634 _OPTIONS.build_dir)
635 else:
636 return None, None
637 srcdir = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'srcdir =')
638 target = GetMakefileValue('%s/Makefile' % _OPTIONS.build_dir, 'target_alias=')
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000639 if _OPTIONS.verbosity >= 3:
640 print('Source directory: %s' % srcdir)
641 print('Build target: %s' % target)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000642 return srcdir, target
643
644
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000645def PrintSummary(summary):
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000646 summary.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000647
648def GetSumFiles(results, build_dir):
649 if not results:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000650 if _OPTIONS.verbosity >= 3:
651 print('Getting actual results from build directory %s' % build_dir)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000652 sum_files = CollectSumFiles(build_dir)
653 else:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000654 if _OPTIONS.verbosity >= 3:
655 print('Getting actual results from user-provided results')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000656 sum_files = results.split()
657 return sum_files
658
Maxim Kuvyrkoveb482c72024-04-01 12:30:19 +0000659def DiscardFlaky(expected, actual):
660 flaky_list = []
661 for expected_result in expected:
662 if 'flaky' in expected_result.attrs:
663 flaky_list.append(expected_result)
664
665 for expected_result in flaky_list:
666 expected.remove(expected_result)
667 actual.discard(expected_result)
668
669 return len(flaky_list)
670
Leandro Luporif98e8ea2024-09-10 15:07:45 -0300671def DiscardNoexeToFail(expected, actual):
672 """ Discard NOEXE to FAIL transitions, as these are not regressions. """
673 discard_expected_list = []
674
675 for expected_result in expected:
676 if expected_result.state == 'NOEXE':
677 discard_actual = None
678 for actual_result in actual:
679 if actual_result.state == 'FAIL' and \
680 expected_result.IsSameTest(actual_result):
681 discard_actual = actual_result
682 discard_expected_list.append(expected_result)
683 break
684 if discard_actual:
685 actual.discard(discard_actual)
686
687 for discard_expected in discard_expected_list:
688 expected.discard(discard_expected)
689
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000690
Laurent Alfonsi1aabe572024-10-08 16:53:01 +0200691def PerformComparison(expected, actual, limit_to_subset=None):
Maxim Kuvyrkovc0014022024-04-01 12:26:44 +0000692 stats = ResultsStats()
Maxim Kuvyrkoveb482c72024-04-01 12:30:19 +0000693 stats.total = actual.total
Leandro Luporif98e8ea2024-09-10 15:07:45 -0300694 # We need to ignore flaky tests and NOEXE to FAIL transitions in comparison,
695 # so remove them now from both expected and actual sets.
Maxim Kuvyrkoveb482c72024-04-01 12:30:19 +0000696 stats.flaky = DiscardFlaky(expected, actual)
Leandro Luporif98e8ea2024-09-10 15:07:45 -0300697 if _OPTIONS.inverse_match:
698 DiscardNoexeToFail(actual, expected)
699 else:
700 DiscardNoexeToFail(expected, actual)
Maxim Kuvyrkovc0014022024-04-01 12:26:44 +0000701 stats.fails = len(actual)
702
Laurent Alfonsi1aabe572024-10-08 16:53:01 +0200703 actual_vs_expected, expected_vs_actual = CompareResults(expected, actual, limit_to_subset)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000704
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000705 if _OPTIONS.inverse_match:
706 # Switch results if inverse comparison is requested.
707 # This is useful in detecting flaky tests that FAILed in expected set,
708 # but PASSed in actual set.
709 actual_vs_expected, expected_vs_actual \
Maxim Kuvyrkov7df81782023-05-25 06:42:06 +0000710 = expected_vs_actual, actual_vs_expected
Maxim Kuvyrkov55213032024-04-08 12:52:21 +0000711 stats.fails = len(expected)
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000712
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000713 tests_ok = True
714 if len(actual_vs_expected) > 0:
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000715 if _OPTIONS.verbosity >= 3:
716 print('\n\nUnexpected results in this build (new failures)')
717 if _OPTIONS.verbosity >= 1:
718 PrintSummary(actual_vs_expected)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000719 tests_ok = False
720
Maxim Kuvyrkov55213032024-04-08 12:52:21 +0000721 if _OPTIONS.verbosity >= 1:
Maxim Kuvyrkovc0014022024-04-01 12:26:44 +0000722 stats.Print()
723
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000724 if _OPTIONS.verbosity >= 2 and len(expected_vs_actual) > 0:
725 print('\n\nExpected results not present in this build (fixed tests)'
726 '\n\nNOTE: This is not a failure. It just means that these '
727 'tests were expected\nto fail, but either they worked in '
728 'this configuration or they were not\npresent at all.\n')
729 PrintSummary(expected_vs_actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000730
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000731 if tests_ok and _OPTIONS.verbosity >= 3:
Maxim Kuvyrkov63ad5352021-07-04 07:38:22 +0000732 print('\nSUCCESS: No unexpected failures.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000733
734 return tests_ok
735
736
737def CheckExpectedResults():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000738 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000739 if _OPTIONS.verbosity >= 3:
740 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000741 manifest = GetManifest(manifest_path)
742 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
743 actual = GetResults(sum_files)
Laurent Alfonsib44cfe42024-10-15 22:23:19 +0200744 subset = None
745 if _OPTIONS.only_for_subset:
746 subset = GetResults([ _OPTIONS.only_for_subset ])
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000747
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000748 if _OPTIONS.verbosity >= 5:
749 print('\n\nTests expected to fail')
750 PrintSummary(manifest)
751 print('\n\nActual test results')
752 PrintSummary(actual)
Laurent Alfonsi1aabe572024-10-08 16:53:01 +0200753 print('\n\nSubset results')
754 PrintSummary(subset)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000755
Laurent Alfonsi1aabe572024-10-08 16:53:01 +0200756 return PerformComparison(manifest, actual, subset)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000757
758
759def ProduceManifest():
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000760 manifest_path = GetManifestPath(False)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000761 if _OPTIONS.verbosity >= 3:
762 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000763 if os.path.exists(manifest_path) and not _OPTIONS.force:
764 Error('Manifest file %s already exists.\nUse --force to overwrite.' %
765 manifest_path)
766
767 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
768 actual = GetResults(sum_files)
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000769 manifest_file = open(manifest_path, encoding='latin-1', mode='w')
Maxim Kuvyrkov51e3fa12021-07-04 10:58:53 +0000770 actual.Print(manifest_file)
771 actual.Print()
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000772 manifest_file.close()
773
774 return True
775
776
777def CompareBuilds():
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000778 sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.build_dir)
779 actual = GetResults(sum_files)
780
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000781 clean = ResultSet()
782
783 if _OPTIONS.manifest:
Maxim Kuvyrkov918bc262021-07-08 08:27:39 +0000784 manifest_path = GetManifestPath(True)
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000785 if _OPTIONS.verbosity >= 3:
786 print('Manifest: %s' % manifest_path)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000787 clean = GetManifest(manifest_path)
788
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000789 clean_sum_files = GetSumFiles(_OPTIONS.results, _OPTIONS.clean_build)
Maxim Kuvyrkov8ef7c852021-07-08 08:21:18 +0000790 clean = GetResults(clean_sum_files, clean)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000791
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000792 return PerformComparison(clean, actual)
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000793
794
795def Main(argv):
796 parser = optparse.OptionParser(usage=__doc__)
797
798 # Keep the following list sorted by option name.
799 parser.add_option('--build_dir', action='store', type='string',
800 dest='build_dir', default='.',
801 help='Build directory to check (default = .)')
802 parser.add_option('--clean_build', action='store', type='string',
803 dest='clean_build', default=None,
804 help='Compare test results from this build against '
805 'those of another (clean) build. Use this option '
806 'when comparing the test results of your patch versus '
807 'the test results of a clean build without your patch. '
808 'You must provide the path to the top directory of your '
809 'clean build.')
810 parser.add_option('--force', action='store_true', dest='force',
811 default=False, help='When used with --produce_manifest, '
812 'it will overwrite an existing manifest file '
813 '(default = False)')
Maxim Kuvyrkov158e61d2023-05-25 12:18:30 +0000814 parser.add_option('--expiry_date', action='store',
815 dest='expiry_today_date', default=None,
816 help='Use provided date YYYYMMDD to decide whether '
817 'manifest entries with expiry settings have expired '
818 'or not. (default = Use today date)')
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000819 parser.add_option('--srcpath', action='store', type='string',
820 dest='srcpath_regex', default='[^ ]+/testsuite/',
821 help='Remove provided path (can be a regex) from '
822 'the result entries. This is useful to remove '
823 'occasional filesystem path from the results. '
824 '(default = "[^ ]+/testsuite/")')
Maxim Kuvyrkovd8d6c472023-05-03 15:53:17 +0000825 parser.add_option('--inverse_match', action='store_true',
826 dest='inverse_match', default=False,
827 help='Inverse result sets in comparison. '
828 'Output unexpected passes as unexpected failures and '
829 'unexpected failures as unexpected passes. '
830 'This is used to catch FAIL->PASS flaky tests. '
831 '(default = False)')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000832 parser.add_option('--manifest', action='store', type='string',
833 dest='manifest', default=None,
834 help='Name of the manifest file to use (default = '
835 'taken from '
836 'contrib/testsuite-managment/<target_alias>.xfail)')
Laurent Alfonsi1aabe572024-10-08 16:53:01 +0200837 parser.add_option('--only_for_subset', action='store', type='string',
838 dest='only_for_subset', default=None,
839 help='Name of the fail.sum file, that contains the '
840 'testcase we are interested in. This is useful to check '
841 'a particular regression')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000842 parser.add_option('--produce_manifest', action='store_true',
843 dest='produce_manifest', default=False,
844 help='Produce the manifest for the current '
845 'build (default = False)')
846 parser.add_option('--results', action='store', type='string',
847 dest='results', default=None, help='Space-separated list '
848 'of .sum files with the testing results to check. The '
849 'only content needed from these files are the lines '
850 'starting with FAIL, XPASS or UNRESOLVED (default = '
851 '.sum files collected from the build directory).')
852 parser.add_option('--verbosity', action='store', dest='verbosity',
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000853 type='int', default=3, help='Verbosity level '
854 '(default = 3). Level 0: only error output, this is '
855 'useful in scripting when only the exit code is used. '
856 'Level 1: output unexpected failures. '
857 'Level 2: output unexpected passes. '
858 'Level 3: output helpful information. '
Maxim Kuvyrkov8396bb32023-06-14 14:32:38 +0000859 'Level 4: output notification on expired entries. '
Maxim Kuvyrkov40205382021-07-12 15:41:47 +0000860 'Level 5: output debug information.')
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000861 global _OPTIONS
862 (_OPTIONS, _) = parser.parse_args(argv[1:])
863
Maxim Kuvyrkov158e61d2023-05-25 12:18:30 +0000864 # Set "today" date to compare expiration entries against.
865 # Setting expiration date into the future allows re-detection of flaky
866 # tests and creating fresh entries for them before the current flaky entries
867 # expire.
868 if _OPTIONS.expiry_today_date:
869 today_date = re.search(r'(\d\d\d\d)(\d\d)(\d\d)',
870 _OPTIONS.expiry_today_date)
871 if not today_date:
872 Error('Invalid --expiry_today_date format "%s". Must be of the form '
873 '"expire=YYYYMMDD"' % _OPTIONS.expiry_today_date)
874 _OPTIONS.expiry_today_date=datetime.date(int(today_date.group(1)),
875 int(today_date.group(2)),
876 int(today_date.group(3)))
877 else:
878 _OPTIONS.expiry_today_date = datetime.date.today()
879
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000880 if _OPTIONS.produce_manifest:
881 retval = ProduceManifest()
882 elif _OPTIONS.clean_build:
883 retval = CompareBuilds()
884 else:
885 retval = CheckExpectedResults()
886
887 if retval:
888 return 0
889 else:
Maxim Kuvyrkov972bb812021-08-30 14:18:09 +0000890 return 2
Maxim Kuvyrkov59877482021-07-07 11:22:26 +0000891
892
893if __name__ == '__main__':
894 retval = Main(sys.argv)
895 sys.exit(retval)