• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python
2
3# Script to compare testsuite failures against a list of known-to-fail
4# tests.
5
6# Contributed by Diego Novillo <dnovillo@google.com>
7# Overhaul by Krystian Baclawski <kbaclawski@google.com>
8#
9# Copyright (C) 2011 Free Software Foundation, Inc.
10#
11# This file is part of GCC.
12#
13# GCC is free software; you can redistribute it and/or modify
14# it under the terms of the GNU General Public License as published by
15# the Free Software Foundation; either version 3, or (at your option)
16# any later version.
17#
18# GCC is distributed in the hope that it will be useful,
19# but WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21# GNU General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License
24# along with GCC; see the file COPYING.  If not, write to
25# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
26# Boston, MA 02110-1301, USA.
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>/contrib/testsuite-management/<TARGET>.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"""
48
49import optparse
50import logging
51import os
52import sys
53
54sys.path.append(os.path.dirname(os.path.abspath(__file__)))
55
56from dejagnu.manifest import Manifest
57from dejagnu.summary import DejaGnuTestResult
58from dejagnu.summary import DejaGnuTestRun
59
60# Pattern for naming manifest files.  The first argument should be
61# the toplevel GCC source directory.  The second argument is the
62# target triple used during the build.
63_MANIFEST_PATH_PATTERN = '%s/contrib/testsuite-management/%s.xfail'
64
65
66def GetMakefileVars(makefile_path):
67  assert os.path.exists(makefile_path)
68
69  with open(makefile_path) as lines:
70    kvs = [line.split('=', 1) for line in lines if '=' in line]
71
72    return dict((k.strip(), v.strip()) for k, v in kvs)
73
74
75def GetSumFiles(build_dir):
76  summaries = []
77
78  for root, _, filenames in os.walk(build_dir):
79    summaries.extend([os.path.join(root, filename)
80                      for filename in filenames if filename.endswith('.sum')])
81
82  return map(os.path.normpath, summaries)
83
84
85def ValidBuildDirectory(build_dir, target):
86  mandatory_paths = [build_dir, os.path.join(build_dir, 'Makefile')]
87
88  extra_paths = [os.path.join(build_dir, target),
89                 os.path.join(build_dir, 'build-%s' % target)]
90
91  return (all(map(os.path.exists, mandatory_paths)) and
92          any(map(os.path.exists, extra_paths)))
93
94
95def GetManifestPath(build_dir):
96  makefile = GetMakefileVars(os.path.join(build_dir, 'Makefile'))
97  srcdir = makefile['srcdir']
98  target = makefile['target']
99
100  if not ValidBuildDirectory(build_dir, target):
101    target = makefile['target_alias']
102
103  if not ValidBuildDirectory(build_dir, target):
104    logging.error('%s is not a valid GCC top level build directory.', build_dir)
105    sys.exit(1)
106
107  logging.info('Discovered source directory: "%s"', srcdir)
108  logging.info('Discovered build target: "%s"', target)
109
110  return _MANIFEST_PATH_PATTERN % (srcdir, target)
111
112
113def CompareResults(manifest, actual):
114  """Compare sets of results and return two lists:
115     - List of results present in MANIFEST but missing from ACTUAL.
116     - List of results present in ACTUAL but missing from MANIFEST.
117  """
118  # Report all the actual results not present in the manifest.
119  actual_vs_manifest = actual - manifest
120
121  # Filter out tests marked flaky.
122  manifest_without_flaky_tests = set(filter(lambda result: not result.flaky,
123                                            manifest))
124
125  # Simlarly for all the tests in the manifest.
126  manifest_vs_actual = manifest_without_flaky_tests - actual
127
128  return actual_vs_manifest, manifest_vs_actual
129
130
131def LogResults(level, results):
132  log_fun = getattr(logging, level)
133
134  for num, result in enumerate(sorted(results), start=1):
135    log_fun('  %d) %s', num, result)
136
137
138def CheckExpectedResults(manifest_path, build_dir):
139  logging.info('Reading manifest file: "%s"', manifest_path)
140
141  manifest = set(Manifest.FromFile(manifest_path))
142
143  logging.info('Getting actual results from build directory: "%s"',
144               os.path.realpath(build_dir))
145
146  summaries = GetSumFiles(build_dir)
147
148  actual = set()
149
150  for summary in summaries:
151    test_run = DejaGnuTestRun.FromFile(summary)
152    failures = set(Manifest.FromDejaGnuTestRun(test_run))
153    actual.update(failures)
154
155  if manifest:
156    logging.debug('Tests expected to fail:')
157    LogResults('debug', manifest)
158
159  if actual:
160    logging.debug('Actual test failures:')
161    LogResults('debug', actual)
162
163  actual_vs_manifest, manifest_vs_actual = CompareResults(manifest, actual)
164
165  if actual_vs_manifest:
166    logging.info('Build results not in the manifest:')
167    LogResults('info', actual_vs_manifest)
168
169  if manifest_vs_actual:
170    logging.info('Manifest results not present in the build:')
171    LogResults('info', manifest_vs_actual)
172    logging.info('NOTE: This is not a failure!  ',
173                 'It just means that the manifest expected these tests to '
174                 'fail, but they worked in this configuration.')
175
176  if actual_vs_manifest or manifest_vs_actual:
177    sys.exit(1)
178
179  logging.info('No unexpected failures.')
180
181
182def ProduceManifest(manifest_path, build_dir, overwrite):
183  if os.path.exists(manifest_path) and not overwrite:
184    logging.error('Manifest file "%s" already exists.', manifest_path)
185    logging.error('Use --force to overwrite.')
186    sys.exit(1)
187
188  testruns = map(DejaGnuTestRun.FromFile, GetSumFiles(build_dir))
189  manifests = map(Manifest.FromDejaGnuTestRun, testruns)
190
191  with open(manifest_path, 'w') as manifest_file:
192    manifest_strings = [manifest.Generate() for manifest in manifests]
193    logging.info('Writing manifest to "%s".', manifest_path)
194    manifest_file.write('\n'.join(manifest_strings))
195
196
197def Main(argv):
198  parser = optparse.OptionParser(usage=__doc__)
199  parser.add_option(
200      '-b',
201      '--build_dir',
202      dest='build_dir',
203      action='store',
204      metavar='PATH',
205      default=os.getcwd(),
206      help='Build directory to check. (default: current directory)')
207  parser.add_option('-m',
208                    '--manifest',
209                    dest='manifest',
210                    action='store_true',
211                    help='Produce the manifest for the current build.')
212  parser.add_option(
213      '-f',
214      '--force',
215      dest='force',
216      action='store_true',
217      help=('Overwrite an existing manifest file, if user requested creating '
218            'new one. (default: False)'))
219  parser.add_option('-v',
220                    '--verbose',
221                    dest='verbose',
222                    action='store_true',
223                    help='Increase verbosity.')
224  options, _ = parser.parse_args(argv[1:])
225
226  if options.verbose:
227    logging.root.setLevel(logging.DEBUG)
228
229  manifest_path = GetManifestPath(options.build_dir)
230
231  if options.manifest:
232    ProduceManifest(manifest_path, options.build_dir, options.force)
233  else:
234    CheckExpectedResults(manifest_path, options.build_dir)
235
236
237if __name__ == '__main__':
238  logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO)
239  Main(sys.argv)
240