You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
239 lines
7.7 KiB
239 lines
7.7 KiB
#!/usr/bin/python |
|
|
|
# Script to compare testsuite failures against a list of known-to-fail |
|
# tests. |
|
|
|
# Contributed by Diego Novillo <dnovillo@google.com> |
|
# Overhaul by Krystian Baclawski <kbaclawski@google.com> |
|
# |
|
# Copyright (C) 2011 Free Software Foundation, Inc. |
|
# |
|
# This file is part of GCC. |
|
# |
|
# GCC is free software; you can redistribute it and/or modify |
|
# it under the terms of the GNU General Public License as published by |
|
# the Free Software Foundation; either version 3, or (at your option) |
|
# any later version. |
|
# |
|
# GCC is distributed in the hope that it will be useful, |
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
# GNU General Public License for more details. |
|
# |
|
# You should have received a copy of the GNU General Public License |
|
# along with GCC; see the file COPYING. If not, write to |
|
# the Free Software Foundation, 51 Franklin Street, Fifth Floor, |
|
# Boston, MA 02110-1301, USA. |
|
"""This script provides a coarser XFAILing mechanism that requires no |
|
detailed DejaGNU markings. This is useful in a variety of scenarios: |
|
|
|
- Development branches with many known failures waiting to be fixed. |
|
- Release branches with known failures that are not considered |
|
important for the particular release criteria used in that branch. |
|
|
|
The script must be executed from the toplevel build directory. When |
|
executed it will: |
|
|
|
1) Determine the target built: TARGET |
|
2) Determine the source directory: SRCDIR |
|
3) Look for a failure manifest file in |
|
<SRCDIR>/contrib/testsuite-management/<TARGET>.xfail |
|
4) Collect all the <tool>.sum files from the build tree. |
|
5) Produce a report stating: |
|
a) Failures expected in the manifest but not present in the build. |
|
b) Failures in the build not expected in the manifest. |
|
6) If all the build failures are expected in the manifest, it exits |
|
with exit code 0. Otherwise, it exits with error code 1. |
|
""" |
|
|
|
import optparse |
|
import logging |
|
import os |
|
import sys |
|
|
|
sys.path.append(os.path.dirname(os.path.abspath(__file__))) |
|
|
|
from dejagnu.manifest import Manifest |
|
from dejagnu.summary import DejaGnuTestResult |
|
from dejagnu.summary import DejaGnuTestRun |
|
|
|
# Pattern for naming manifest files. The first argument should be |
|
# the toplevel GCC source directory. The second argument is the |
|
# target triple used during the build. |
|
_MANIFEST_PATH_PATTERN = '%s/contrib/testsuite-management/%s.xfail' |
|
|
|
|
|
def GetMakefileVars(makefile_path): |
|
assert os.path.exists(makefile_path) |
|
|
|
with open(makefile_path) as lines: |
|
kvs = [line.split('=', 1) for line in lines if '=' in line] |
|
|
|
return dict((k.strip(), v.strip()) for k, v in kvs) |
|
|
|
|
|
def GetSumFiles(build_dir): |
|
summaries = [] |
|
|
|
for root, _, filenames in os.walk(build_dir): |
|
summaries.extend([os.path.join(root, filename) |
|
for filename in filenames if filename.endswith('.sum')]) |
|
|
|
return map(os.path.normpath, summaries) |
|
|
|
|
|
def ValidBuildDirectory(build_dir, target): |
|
mandatory_paths = [build_dir, os.path.join(build_dir, 'Makefile')] |
|
|
|
extra_paths = [os.path.join(build_dir, target), |
|
os.path.join(build_dir, 'build-%s' % target)] |
|
|
|
return (all(map(os.path.exists, mandatory_paths)) and |
|
any(map(os.path.exists, extra_paths))) |
|
|
|
|
|
def GetManifestPath(build_dir): |
|
makefile = GetMakefileVars(os.path.join(build_dir, 'Makefile')) |
|
srcdir = makefile['srcdir'] |
|
target = makefile['target'] |
|
|
|
if not ValidBuildDirectory(build_dir, target): |
|
target = makefile['target_alias'] |
|
|
|
if not ValidBuildDirectory(build_dir, target): |
|
logging.error('%s is not a valid GCC top level build directory.', build_dir) |
|
sys.exit(1) |
|
|
|
logging.info('Discovered source directory: "%s"', srcdir) |
|
logging.info('Discovered build target: "%s"', target) |
|
|
|
return _MANIFEST_PATH_PATTERN % (srcdir, target) |
|
|
|
|
|
def CompareResults(manifest, actual): |
|
"""Compare sets of results and return two lists: |
|
- List of results present in MANIFEST but missing from ACTUAL. |
|
- List of results present in ACTUAL but missing from MANIFEST. |
|
""" |
|
# Report all the actual results not present in the manifest. |
|
actual_vs_manifest = actual - manifest |
|
|
|
# Filter out tests marked flaky. |
|
manifest_without_flaky_tests = set(filter(lambda result: not result.flaky, |
|
manifest)) |
|
|
|
# Simlarly for all the tests in the manifest. |
|
manifest_vs_actual = manifest_without_flaky_tests - actual |
|
|
|
return actual_vs_manifest, manifest_vs_actual |
|
|
|
|
|
def LogResults(level, results): |
|
log_fun = getattr(logging, level) |
|
|
|
for num, result in enumerate(sorted(results), start=1): |
|
log_fun(' %d) %s', num, result) |
|
|
|
|
|
def CheckExpectedResults(manifest_path, build_dir): |
|
logging.info('Reading manifest file: "%s"', manifest_path) |
|
|
|
manifest = set(Manifest.FromFile(manifest_path)) |
|
|
|
logging.info('Getting actual results from build directory: "%s"', |
|
os.path.realpath(build_dir)) |
|
|
|
summaries = GetSumFiles(build_dir) |
|
|
|
actual = set() |
|
|
|
for summary in summaries: |
|
test_run = DejaGnuTestRun.FromFile(summary) |
|
failures = set(Manifest.FromDejaGnuTestRun(test_run)) |
|
actual.update(failures) |
|
|
|
if manifest: |
|
logging.debug('Tests expected to fail:') |
|
LogResults('debug', manifest) |
|
|
|
if actual: |
|
logging.debug('Actual test failures:') |
|
LogResults('debug', actual) |
|
|
|
actual_vs_manifest, manifest_vs_actual = CompareResults(manifest, actual) |
|
|
|
if actual_vs_manifest: |
|
logging.info('Build results not in the manifest:') |
|
LogResults('info', actual_vs_manifest) |
|
|
|
if manifest_vs_actual: |
|
logging.info('Manifest results not present in the build:') |
|
LogResults('info', manifest_vs_actual) |
|
logging.info('NOTE: This is not a failure! ', |
|
'It just means that the manifest expected these tests to ' |
|
'fail, but they worked in this configuration.') |
|
|
|
if actual_vs_manifest or manifest_vs_actual: |
|
sys.exit(1) |
|
|
|
logging.info('No unexpected failures.') |
|
|
|
|
|
def ProduceManifest(manifest_path, build_dir, overwrite): |
|
if os.path.exists(manifest_path) and not overwrite: |
|
logging.error('Manifest file "%s" already exists.', manifest_path) |
|
logging.error('Use --force to overwrite.') |
|
sys.exit(1) |
|
|
|
testruns = map(DejaGnuTestRun.FromFile, GetSumFiles(build_dir)) |
|
manifests = map(Manifest.FromDejaGnuTestRun, testruns) |
|
|
|
with open(manifest_path, 'w') as manifest_file: |
|
manifest_strings = [manifest.Generate() for manifest in manifests] |
|
logging.info('Writing manifest to "%s".', manifest_path) |
|
manifest_file.write('\n'.join(manifest_strings)) |
|
|
|
|
|
def Main(argv): |
|
parser = optparse.OptionParser(usage=__doc__) |
|
parser.add_option( |
|
'-b', |
|
'--build_dir', |
|
dest='build_dir', |
|
action='store', |
|
metavar='PATH', |
|
default=os.getcwd(), |
|
help='Build directory to check. (default: current directory)') |
|
parser.add_option('-m', |
|
'--manifest', |
|
dest='manifest', |
|
action='store_true', |
|
help='Produce the manifest for the current build.') |
|
parser.add_option( |
|
'-f', |
|
'--force', |
|
dest='force', |
|
action='store_true', |
|
help=('Overwrite an existing manifest file, if user requested creating ' |
|
'new one. (default: False)')) |
|
parser.add_option('-v', |
|
'--verbose', |
|
dest='verbose', |
|
action='store_true', |
|
help='Increase verbosity.') |
|
options, _ = parser.parse_args(argv[1:]) |
|
|
|
if options.verbose: |
|
logging.root.setLevel(logging.DEBUG) |
|
|
|
manifest_path = GetManifestPath(options.build_dir) |
|
|
|
if options.manifest: |
|
ProduceManifest(manifest_path, options.build_dir, options.force) |
|
else: |
|
CheckExpectedResults(manifest_path, options.build_dir) |
|
|
|
|
|
if __name__ == '__main__': |
|
logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO) |
|
Main(sys.argv)
|
|
|