blob: c5cc7c35e5af199e0497b37f88a3df0bb8285094 [file] [log] [blame]
yaberauneya0d39b832009-11-20 05:45:41 +00001#!/usr/bin/env python
2"""
3 An LTP [execution and] parsing wrapper.
4
5 Used as a second layer for ease-of-use with users as many developers
6 complain about complexity involved with trying to use LTP in my
7 organization -_-.
8
9 Copyright (C) 2009, Garrett Cooper
10
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License along
22 with this program; if not, write to the Free Software Foundation, Inc.,
23 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24"""
25
26from optparse import OptionGroup, OptionParser
27import os, re, sys
28
29class ResultsParseException(Exception):
30 """ Extended class for parsing LTP results. """
31
32def parse_ltp_results(exec_log, output_log, verbose=0):
33 """ Function for parsing LTP results.
34
35 1. The exec log is the log with the results in summary form.
36
37 And now a note from our sponsors about exec logs...
38
39 startup='Thu Oct 1 06:42:07 2009'
40 tag=abort01 stime=1254379327 dur=2 exit=exited stat=0 core=no cu=0 cs=16
41 tag=accept01 stime=1254379329 dur=0 exit=exited stat=0 core=no cu=1 cs=0
42 tag=access01 stime=1254379329 dur=0 exit=exited stat=0 core=no cu=0 cs=0
43 tag=access02 stime=1254379329 dur=0 exit=exited stat=0 core=no cu=0 cs=0
44 tag=access03 stime=1254379329 dur=1 exit=exited stat=0 core=no cu=0 cs=1
45
46 [...]
47
48 a. tag is the test tag name.
49 b. stime is the system time at the start of the exec.
50 c. dur is the total duration of the test.
51 d. exit tells you what the result was. Valid values are:
52 - exited
53 - signaled
54 - stopped
55 - unknown
56 See run_child in pan.c.
57 e. stat is the exit status.
58 f. core answers the question: `did I dump core?'.
59 g. cu is the cutime (cumulative user time).
60 h. cs is the cstime (cumulative system time).
61
62 2. The output log is the log with all of the terse results.
63 3. verbose tells us whether or not we need to include the passed results.
64 """
65
66 if not os.access(exec_log, os.R_OK):
67 raise ResultsParseException("Exec log - %s - specified doesn't exist"
68 % exec_log)
69 elif 1 < verbose and not os.access(output_log, os.R_OK):
70 # Need the output log for context to the end user.
71 raise ResultsParseException("Output log - %s - specified doesn't exist"
72 % output_log )
73
74 context = None
75
76 failed = [ ]
77 passed = 0
78
79 if 2 <= verbose:
80 passed = [ ]
81
82 target_vals = ( 'exited', '0', 'no' )
83
84 fd = open(exec_log, 'r')
85
86 try:
87 content = fd.read()
88 matches = re.finditer('tag=(?P<tag>\w+).+exit=(?P<exit>\w+) '
89 'stat=(?P<stat>\d+) core=(?P<core>\w+)', content)
90 finally:
91 fd.close()
92
93 if not matches:
94 raise ResultsParseException("No parseable results were found in the "
95 "exec log - `%s'."% exec_log)
96
97 for match in matches:
98
99 if ((match.group('exit'), match.group('stat'), match.group('core')) !=
100 target_vals):
101 failed.append(match.group('tag'))
102 elif 2 <= verbose:
103 passed.append(match.group('tag'))
104 else:
105 passed += 1
106
107 # Save memory on large files because lists can eat up a fair amount of
108 # memory.
109 matches = None
110
111 if 1 <= verbose:
112
113 context = { }
114
115 search_tags = failed[:]
116
117 if 2 <= verbose:
118 search_tags += passed
119
120 search_tags.sort()
121
122 fd = open(output_log, 'r')
123
124 try:
125
126 try:
127 lines = fd.readlines()
128 finally:
129 fd.close()
130
131 fd.close()
132
133 end_output = '<<<execution_status>>>'
134 output_start = '<<<test_output>>>'
135
136 tag_re = re.compile('tag=(\w+)')
137
138 grab_output = False
139
yaberauneya0d39b832009-11-20 05:45:41 +0000140 local_context = ''
141
yaberauneya0d39b832009-11-20 05:45:41 +0000142 search_tag = None
143
yaberauneyabbfbd512009-12-12 07:19:50 +0000144 line_iterator = lines.__iter__()
yaberauneya0d39b832009-11-20 05:45:41 +0000145
yaberauneyabbfbd512009-12-12 07:19:50 +0000146 try:
yaberauneya0d39b832009-11-20 05:45:41 +0000147
yaberauneyabbfbd512009-12-12 07:19:50 +0000148 while True:
yaberauneya0d39b832009-11-20 05:45:41 +0000149
yaberauneyabbfbd512009-12-12 07:19:50 +0000150 line = line_iterator.next()
yaberauneya0d39b832009-11-20 05:45:41 +0000151
yaberauneyabbfbd512009-12-12 07:19:50 +0000152 if line.startswith(end_output):
yaberauneya0d39b832009-11-20 05:45:41 +0000153
yaberauneyabbfbd512009-12-12 07:19:50 +0000154 if search_tag:
155 context[search_tag] = local_context
yaberauneya0d39b832009-11-20 05:45:41 +0000156
yaberauneyabbfbd512009-12-12 07:19:50 +0000157 grab_output = False
158 local_context = ''
159 search_tag = None
yaberauneya0d39b832009-11-20 05:45:41 +0000160
yaberauneyabbfbd512009-12-12 07:19:50 +0000161 if not search_tag:
yaberauneya0d39b832009-11-20 05:45:41 +0000162
yaberauneyabbfbd512009-12-12 07:19:50 +0000163 while True:
yaberauneya0d39b832009-11-20 05:45:41 +0000164
yaberauneyabbfbd512009-12-12 07:19:50 +0000165 line = line_iterator.next()
yaberauneya0d39b832009-11-20 05:45:41 +0000166
yaberauneyabbfbd512009-12-12 07:19:50 +0000167 match = tag_re.match(line)
168
169 if match and match.group(1) in search_tags:
170 search_tag = match.group(1)
171 break
172
173 elif line.startswith(output_start):
174 grab_output = True
175 elif grab_output:
176 local_context += line
177
178 except StopIteration:
179 pass
yaberauneya0d39b832009-11-20 05:45:41 +0000180
181 for k in context.keys():
182 if k not in search_tags:
183 raise ResultsParseException('Leftover token in search '
184 'keys: %s' % k)
185
186 except Exception, exc:
187 # XXX (garrcoop): change from Exception to soft error and print
188 # out warning with logging module.
189 raise ResultsParseException('Encountered exception reading output '
190 'for context: %s' % str(exc))
191
192 return failed, passed, context
193
194def determine_context(output_log, testsuite, test_set, context):
195 """ Return a set of context values mapping test_set -> context. """
196
197 test_set_context = {}
198
199 for test in test_set:
200
201 if test in context:
202 test_context = context[test]
203 del context[test]
204 else:
205 test_context = ('Could not determine context for %s; please see '
206 'output log - %s' % (test, output_log))
207
208
209 test_set_context['%s : %s' % (testsuite, test)] = test_context
210
211 return test_set_context
212
213def print_context(output_dest, header, testsuite_context):
214 """ Print out testsuite_context to output_dest, heading it up with
215 header.
216 """
217 output_dest.write('\n'.join(['', '=' * 40, header, '-' * 40, '']))
218
219 for test, context in testsuite_context.items():
220 output_dest.write('<output test="%s">\n%s\n</output>\n' %
221 (test, context.strip()))
222
223def main():
224 """ main. """
225
226 parser = OptionParser(prog=os.path.basename(sys.argv[0]),
227 usage='usage: %prog [options] test ...',
Garrett Cooper7324c3a2010-11-16 21:48:45 -0800228 version='0.0.2')
yaberauneya0d39b832009-11-20 05:45:41 +0000229
230 ltpdir = os.getenv('LTPROOT', '@prefix@')
231
232 parser.add_option('-l', '--ltp-dir', dest='ltp_dir',
233 default=ltpdir, help='LTP directory [default: %default]')
234 parser.add_option('-L', '--log-dir', dest='log_dir',
235 default=None,
236 help=('directory for [storing and] retrieving logs '
237 '[default: %s/output]' % ltpdir),
238 metavar='DIR')
239 parser.add_option('-p', '--postprocess-only', dest='postprocess_only',
240 default=False, action='store_true',
241 help=("Don't execute runltp; just postprocess logs "
242 "[default: %default]."))
243 parser.add_option('-o', '--output-file', dest='output_file',
244 default=None,
245 help='File to output results')
246 parser.add_option('-r', '--runltp-opts', dest='runltp_opts',
247 default='',
248 help=('options to pass directly to runltp (will '
249 'suppress -q).'))
250
251 group = OptionGroup(parser, 'Logging',
252 'If --summary-mode is 0, then the summary output is '
253 'suppressed. '
254 'If --summary-mode is 1 [the default], then summary '
255 'output will be displayed for test execution'
256 'If --summary-mode is 2, then summary output will be '
257 'provided on a per-test suite basis. If only '
258 'one test suite is specified, this has the same net '
259 "effect as `--summary-mode 1'"
260 'If --verbose is specified once, prints out failed '
261 'test information with additional context. '
262 'If --verbose is specified twice, prints out the '
263 'failed and passed test context, as well as the '
264 'summary.')
265
266 parser.add_option('-s', '--summary-mode', dest='summary_mode', default=1,
267 type='int',
268 help='See Logging.')
269
270 parser.add_option('-v', '--verbose', dest='verbose', default=0,
271 action='count',
272 help=('Increases context verbosity from tests. See '
273 'Verbosity for more details.'))
274 parser.add_option_group(group)
275
276 group = OptionGroup(parser, 'Copyright',
277 '%(prog)s version %(version)s, Copyright (C) 2009, '
278 'Garrett Cooper %(prog)s comes with ABSOLUTELY NO '
279 'WARRANTY; '
280 'This is free software, and you are welcome to '
281 'redistribute it under certain conditions (See the '
282 'license tort in %(file)s for more details).'
283 % { 'file' : os.path.abspath(__file__),
284 'prog' : parser.prog,
285 'version' : parser.version })
286
287 parser.add_option_group(group)
288
289 opts, args = parser.parse_args()
290
291 # Remove -q from the opts string, as long as it's a standalone option.
292 runltp_opts = re.sub('^((?<!\S)+\-q\s+|\-q|\s+\-q(?!\S))$', '',
293 opts.runltp_opts)
294
295 if not opts.log_dir:
296 opts.log_dir = os.path.join(opts.ltp_dir, 'output')
297
298 if not opts.summary_mode and not opts.verbose:
299 parser.error('You cannot suppress summary output and disable '
300 'verbosity.')
301 elif opts.summary_mode not in range(3):
302 parser.error('--summary-mode must be a value between 0 and 2.')
303
304 if len(args) == 0:
Garrett Cooper66076082010-11-16 21:49:31 -0800305 # Default to scenarios also used by runltp.
Garrett Cooper7324c3a2010-11-16 21:48:45 -0800306 fd = open(os.path.join(ltpdir, 'scenario-groups/default'), 'r')
307 try:
308 args = fd.readlines()
309 finally:
310 fd.close()
yaberauneya0d39b832009-11-20 05:45:41 +0000311
312 if opts.output_file:
313
314 output_dir = os.path.dirname(opts.output_file)
315
316 if output_dir:
317 # Not cwd; let's check to make sure that the directory does or
318 # does not exist.
319
320 if not os.path.exists(output_dir):
321 # We need to make the directory.
322 os.makedirs(os.path.dirname(opts.output_file))
323 elif not os.path.isdir(os.path.abspath(output_dir)):
324 # Path exists, but isn't a file. Oops!
325 parser.error('Dirname for path specified - %s - is not valid'
326 % output_dir)
327
328 else:
329 # Current path (cwd)
330 opts.output_file = os.path.join(os.getcwd(), opts.output_file)
331
332 output_dest = open(opts.output_file, 'w')
333
334 else:
335
336 output_dest = sys.stdout
337
338 try:
339
340 failed_context = {}
341 passed_context = {}
342
343 failed_count = 0
344 passed_count = 0
345
346 if opts.summary_mode == 2 and len(args) == 1:
347 opts.summary_mode = 1
348
349 for testsuite in args:
350
351 # Iterate over the provided test list
352
353 context = {}
354 exec_log = os.path.join(opts.log_dir, '%s-exec.log' % testsuite)
355 output_log = os.path.join(opts.log_dir, ('%s-output.log'
356 % testsuite))
357
358 failed_subset = {}
359
360 runtest_file = os.path.join(opts.ltp_dir, 'runtest', testsuite)
361
362 if not opts.postprocess_only:
363
364 for log in [ exec_log, output_log ]:
365 if os.path.isfile(log):
366 os.remove(log)
367
368 if not os.access(runtest_file, os.R_OK):
369 output_dest.write("%s doesn't exist; skipping "
370 "test\n" % runtest_file)
371 continue
372
373 os.system(' '.join([ os.path.join(opts.ltp_dir, 'runltp'),
374 runltp_opts, '-f', testsuite,
375 '-l', exec_log, '-o', output_log ]))
376
377 try:
378
379 failed_subset, passed_css, context = \
380 parse_ltp_results(exec_log, output_log,
381 verbose=opts.verbose)
382
383 except ResultsParseException, rpe:
384 output_dest.write('Error encountered when parsing results for '
385 'test - %s: %s\n' % (testsuite, str(rpe)))
386 continue
387
388 failed_count += len(failed_subset)
389
390 failed_subset_context = {}
391 passed_subset_context = {}
392
393 if opts.verbose:
394 failed_subset_context = determine_context(output_log,
395 testsuite,
396 failed_subset,
397 context)
398 if type(passed_css) == list:
399
400 passed_count += len(passed_css)
401
402 if opts.verbose == 2:
403 passed_subset_context = determine_context(output_log,
404 testsuite,
405 passed_css,
406 context)
407
408 else:
409
410 passed_count += passed_css
411
412 if opts.summary_mode == 1:
413
414 failed_context.update(failed_subset_context)
415 passed_context.update(passed_subset_context)
416
417 else:
418
419 if 1 <= opts.verbose:
420 # Print out failed testcases.
421 print_context(output_dest,
422 'FAILED TESTCASES for %s' % testsuite,
423 failed_subset_context)
424
425 if opts.verbose == 2:
426 # Print out passed testcases with context.
427 print_context(output_dest,
428 'PASSED TESTCASES for %s' % testsuite,
429 passed_subset_context)
430
431 if opts.summary_mode == 2:
432 output_dest.write("""
433========================================
434SUMMARY for: %s
435----------------------------------------
436PASS - %d
437FAIL - %d
438----------------------------------------
439""" % (testsuite, passed_count, len(failed_subset)))
440
441 if opts.summary_mode == 1:
442
443 # Print out overall results.
444
445 if 1 <= opts.verbose:
446 # Print out failed testcases with context.
447 print_context(output_dest, "FAILED TESTCASES", failed_context)
448
449 if opts.verbose == 2:
450 # Print out passed testcases with context.
451 print_context(output_dest, "PASSED TESTCASES", passed_context)
452
453 output_dest.write("""
454========================================
455SUMMARY for tests:
456%s
457----------------------------------------
458PASS - %d
459FAIL - %d
460----------------------------------------
461""" % (' '.join(args), passed_count, failed_count))
462
463 finally:
464
465 if output_dest != sys.stdout:
466
467 output_dest.close()
468
469if __name__ == '__main__':
470 main()