Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / native_client / pnacl / scripts / parse_llvm_test_report.py
1 #!/usr/bin/python
2 # Copyright (c) 2012 The Native Client Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Parse the report output of the llvm test suite or regression tests,
7    filter out known failures, and check for new failures
8
9 pnacl/scripts/parse_llvm_test_report.py [options]+ reportfile
10
11 """
12
13 import csv
14 import logging
15 import optparse
16 import os
17 import sys
18 import StringIO
19
20 # exclude these tests
21 EXCLUDES = {}
22
23 def ParseCommandLine(argv):
24   parser = optparse.OptionParser(prog=argv[0])
25   parser.add_option('-x', '--exclude', action='append', dest='excludes',
26                     default=[],
27                     help='Add list of excluded tests (expected fails)')
28   parser.add_option('-c', '--check-excludes', action='store_true',
29                     default=False, dest='check_excludes',
30                     help='Report tests which unexpectedly pass')
31   parser.add_option('-v', '--verbose', action='store_true',
32                     default=False, dest='verbose',
33                     help='Print compilation/run logs of failing tests')
34   parser.add_option('-p', '--build-path', dest='buildpath',
35                     help='Path to test-suite build directory')
36   parser.add_option('-a', '--attribute', dest='attributes', action='append',
37                     default=[],
38                     help='Add attribute of test configuration (e.g. arch)')
39   parser.add_option('-t', '--testsuite', action='store_true', dest='testsuite',
40                     default=False)
41   parser.add_option('-l', '--lit', action='store_true', dest='lit',
42                     default=False)
43
44   options, args = parser.parse_args(argv[1:])
45   return options, args
46
47 def Fatal(text):
48   print >> sys.stderr, text
49   sys.exit(1)
50
51 def IsFullname(name):
52   return name.find('/') != -1
53
54 def GetShortname(fullname):
55   return fullname.split('/')[-1]
56
57 def ParseTestsuiteCSV(filecontents):
58   ''' Parse a CSV file output by llvm testsuite with a record for each test.
59       returns 2 dictionaries:
60       1) a mapping from the short name of the test (without the path) to
61        a list of full pathnames that match it. It contains all the tests.
62       2) a mapping of all test failures, mapping full test path to the type
63        of failure (compile or exec)
64   '''
65   alltests = {}
66   failures = {}
67   reader = csv.DictReader(StringIO.StringIO(filecontents))
68
69   testcount = 0
70   for row in reader:
71     testcount += 1
72     fullname = row['Program']
73     shortname = GetShortname(fullname)
74     fullnames = alltests.get(shortname, [])
75     fullnames.append(fullname)
76     alltests[shortname] = fullnames
77
78     if row['CC'] == '*':
79       failures[fullname] = 'compile'
80     elif row['Exec'] == '*':
81       failures[fullname] = 'exec'
82
83   logging.info('%d tests, %d failures', testcount, len(failures))
84   return alltests, failures
85
86 def ParseLit(filecontents):
87   ''' Parse the output of the LLVM regression test runner (lit/make check).
88       returns a dictionary mapping test name to the type of failure
89       (Clang, LLVM, LLVMUnit, etc)
90   '''
91   alltests = {}
92   failures = {}
93   testcount = 0
94   for line in filecontents.splitlines():
95     l = line.split()
96     if len(l) < 4:
97       continue
98     if l[0] in ('PASS:', 'FAIL:', 'XFAIL:', 'XPASS:', 'UNSUPPORTED:'):
99       testcount += 1
100       fullname = ''.join(l[1:4])
101       shortname = GetShortname(fullname)
102       fullnames = alltests.get(shortname, [])
103       fullnames.append(fullname)
104       alltests[shortname] = fullnames
105     if l[0] in ('FAIL:', 'XPASS:'):
106       failures[fullname] = l[1]
107   logging.info('%d tests, %d failures', testcount, len(failures))
108   return alltests, failures
109
110 def ParseExcludeFile(filename, config_attributes,
111                      check_test_names=False, alltests=None):
112   ''' Parse a list of excludes (known test failures). Excludes can be specified
113       by shortname (e.g. fbench) or by full path
114       (e.g. SingleSource/Benchmarks/Misc/fbench) but if there is more than
115       one test with the same shortname, the full name must be given.
116       Errors are reported if an exclude does not match exactly one test
117       in alltests, or if there are duplicate excludes.
118
119       Returns:
120         Number of failures in the exclusion file.
121   '''
122   errors = 0
123   f = open(filename)
124   for line in f:
125     line = line.strip()
126     if not line: continue
127     if line.startswith('#'): continue
128     tokens = line.split()
129     if len(tokens) > 1:
130       testname = tokens[0]
131       attributes = set(tokens[1].split(','))
132       if not attributes.issubset(config_attributes):
133         continue
134     else:
135       testname = line
136     if testname in EXCLUDES:
137       logging.error('Duplicate exclude: %s', line)
138       errors += 1
139     if IsFullname(testname):
140       shortname = GetShortname(testname)
141       if shortname not in alltests or testname not in alltests[shortname]:
142         logging.error('Exclude %s not found in list of tests', line)
143         errors += 1
144       fullname = testname
145     else:
146       # short name is specified
147       shortname = testname
148       if shortname not in alltests:
149         logging.error('Exclude %s not found in list of tests', shortname)
150         errors += 1
151       if len(alltests[shortname]) > 1:
152         logging.error('Exclude %s matches more than one test: %s. ' +
153                       'Specify full name in exclude file.',
154                       shortname, str(alltests[shortname]))
155         errors += 1
156       fullname = alltests[shortname][0]
157
158     if fullname in EXCLUDES:
159       logging.error('Duplicate exclude %s', fullname)
160       errors += 1
161
162     EXCLUDES[fullname] = filename
163   f.close()
164   logging.info('Parsed %s: now %d total excludes', filename, len(EXCLUDES))
165   return errors
166
167 def DumpFileContents(name):
168   error = not os.path.exists(name)
169   logging.debug(name)
170   try:
171     logging.debug(open(name, 'rb').read())
172   except IOError:
173     error = True
174   if error:
175     logging.error("Couldn't open file: %s", name)
176     # Make the bots go red
177     logging.error('@@@STEP_FAILURE@@@')
178
179 def PrintTestsuiteCompilationResult(path, test):
180   ''' Print the compilation and run results for the specified test in the
181       LLVM testsuite.
182       These results are left in several different log files by the testsuite
183       driver, and are different for MultiSource/SingleSource tests
184   '''
185   logging.debug('RESULTS for %s', test)
186   testpath = os.path.join(path, test)
187   testdir, testname = os.path.split(testpath)
188   outputdir = os.path.join(testdir, 'Output')
189
190   logging.debug('COMPILE phase')
191   logging.debug('OBJECT file phase')
192   if test.startswith('MultiSource'):
193     for f in os.listdir(outputdir):
194       if f.endswith('llvm.o.compile'):
195         DumpFileContents(os.path.join(outputdir, f))
196   elif test.startswith('SingleSource'):
197     DumpFileContents(os.path.join(outputdir, testname + '.llvm.o.compile'))
198   else:
199     Fatal('ERROR: unrecognized test type ' + test)
200
201   logging.debug('PEXE generation phase')
202   DumpFileContents(os.path.join(outputdir,
203                                 testname + '.nonfinal.pexe.compile'))
204
205   logging.debug('PEXE finalization phase')
206   DumpFileContents(os.path.join(outputdir, testname + '.final.pexe.finalize'))
207
208   logging.debug('TRANSLATION phase')
209   DumpFileContents(os.path.join(outputdir, testname + '.nexe.translate'))
210
211   logging.debug('EXECUTION phase')
212   logging.debug('native output:')
213   DumpFileContents(os.path.join(outputdir, testname + '.out-nat'))
214   logging.debug('pnacl output:')
215   DumpFileContents(os.path.join(outputdir, testname + '.out-pnacl'))
216
217 def main(argv):
218   options, args = ParseCommandLine(argv)
219
220   if len(args) != 1:
221     Fatal('Must specify filename to parse')
222   filename = args[0]
223   return Report(vars(options), filename=filename)
224
225
226 def Report(options, filename=None, filecontents=None):
227   loglevel = logging.INFO
228   if options['verbose']:
229     loglevel = logging.DEBUG
230   logging.basicConfig(level=loglevel, format='%(message)s')
231
232   if not (filename or filecontents):
233     Fatal('ERROR: must specify filename or filecontents')
234
235   failures = {}
236   logging.debug('Full test results:')
237
238   if not filecontents:
239     with open(filename, 'rb') as f:
240       filecontents = f.read();
241   # get the set of tests and failures
242   if options['testsuite']:
243     if options['verbose'] and options['buildpath'] is None:
244       Fatal('ERROR: must specify build path if verbose output is desired')
245     alltests, failures = ParseTestsuiteCSV(filecontents)
246     check_test_names = True
247   elif options['lit']:
248     alltests, failures = ParseLit(filecontents)
249     check_test_names = True
250   else:
251     Fatal('Must specify either testsuite (-t) or lit (-l) output format')
252
253   # get the set of excludes
254   exclusion_failures = 0
255   for f in options['excludes']:
256     exclusion_failures += ParseExcludeFile(f, set(options['attributes']),
257                                            check_test_names=check_test_names,
258                                            alltests=alltests)
259
260   # Regardless of the verbose option, do a dry run of
261   # PrintTestsuiteCompilationResult so we can catch errors when intermediate
262   # filenames in the compilation pipeline change.
263   # E.g. https://code.google.com/p/nativeclient/issues/detail?id=3659
264   if len(alltests) and options['testsuite']:
265     logging.disable(logging.INFO)
266     PrintTestsuiteCompilationResult(options['buildpath'],
267                                     alltests.values()[0][0])
268     logging.disable(logging.NOTSET)
269
270   # intersect them and check for unexpected fails/passes
271   unexpected_failures = 0
272   unexpected_passes = 0
273   for tests in alltests.itervalues():
274     for test in tests:
275       if test in failures:
276         if test not in EXCLUDES:
277           unexpected_failures += 1
278           logging.info('[  FAILED  ] %s: %s failure', test, failures[test])
279           if options['testsuite']:
280             PrintTestsuiteCompilationResult(options['buildpath'], test)
281       elif test in EXCLUDES:
282         unexpected_passes += 1
283         logging.info('%s: unexpected success', test)
284
285   logging.info('%d unexpected failures %d unexpected passes',
286                unexpected_failures, unexpected_passes)
287   if exclusion_failures:
288     logging.info('%d problems in known_failures exclusion files',
289                  exclusion_failures)
290
291   if options['check_excludes']:
292     return unexpected_failures + unexpected_passes + exclusion_failures > 0
293   return unexpected_failures + exclusion_failures > 0
294
295 if __name__ == '__main__':
296   sys.exit(main(sys.argv))