#!/usr/bin/python
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Report summarizer of internal test pass% from running many tests in LTP.
LTP is the Linux Test Project from http://ltp.sourceforge.net/.
This script serves to summarize the results of a test run by LTP test
infrastructure. LTP frequently runs >1000 tests so summarizing the results
by result-type and count is useful. This script is invoked by the ltp.py
wrapper in Autotest as a post-processing step to summarize the LTP run results
in the Autotest log file.
This script may be invoked by the command-line as follows:
$ ./parse_ltp_out.py -l /mypath/ltp.out
"""
import optparse
import os
import re
import sys
SUMMARY_BORDER = 80 * '-'
# Prefix char used in summaries:
# +: sums into 'passing'
# -: sums into 'notpassing'
TEST_FILTERS = {'TPASS': '+Pass', 'TFAIL': '-Fail', 'TBROK': '-Broken',
'TCONF': '-Config error', 'TRETR': 'Retired',
'TWARN': '+Warning'}
def parse_args(argv):
"""Setup command line parsing options.
@param argv: command-line arguments.
@return parsed option result from optparse.
"""
parser = optparse.OptionParser('Usage: %prog --ltp-out-file=/path/ltp.out')
parser.add_option(
'-l', '--ltp-out-file',
help='[required] Path and file name for ltp.out [default: %default]',
dest='ltp_out_file',
default=None)
parser.add_option(
'-t', '--timings',
help='Show test timings in buckets [default: %default]',
dest='test_timings', action='store_true',
default=False)
options, args = parser.parse_args()
if not options.ltp_out_file:
parser.error('You must supply a value for --ltp-out-file.')
return options
def _filter_and_count(ltp_out_file, test_filters):
"""Utility function to count lines that match certain filters.
@param ltp_out_file: human-readable output file from LTP -p (ltp.out).
@param test_filters: dict of the tags to match and corresponding print tags.
@return a dictionary with counts of the lines that matched each tag.
"""
marker_line = '^<<<%s>>>$'
status_line_re = re.compile('^\w+ +\d+ +(\w+) +: +\w+')
filter_accumulator = dict.fromkeys(test_filters.keys(), 0)
parse_states = (
{'filters': {},
'terminator': re.compile(marker_line % 'test_output')},
{'filters': filter_accumulator,
'terminator': re.compile(marker_line % 'execution_status')})
# Simple 2-state state machine.
state_test_active = False
with open(ltp_out_file) as f:
for line in f:
state_index = int(state_test_active)
if re.match(parse_states[state_index]['terminator'], line):
# This state is terminated - proceed to next.
state_test_active = not state_test_active
else:
# Determine if this line matches any of the sought tags.
m = re.match(status_line_re, line)
if m and m.group(1) in parse_states[state_index]['filters']:
parse_states[state_index]['filters'][m.group(1)] += 1
return filter_accumulator
def _print_summary(filters, accumulator):
"""Utility function to print the summary of the parsing of ltp.out.
Prints a count of each type of test result, then a %pass-rate score.
@param filters: map of tags sought and corresponding print headers.
@param accumulator: counts of test results with same keys as filters.
"""
print SUMMARY_BORDER
print 'Linux Test Project (LTP) Run Summary:'
print SUMMARY_BORDER
# Size the header to the largest printable tag.
fmt = '%%%ss: %%s' % max(map(lambda x: len(x), filters.values()))
for k in sorted(filters):
print fmt % (filters[k], accumulator[k])
print SUMMARY_BORDER
# These calculations from ltprun-summary.sh script.
pass_count = sum([accumulator[k] for k in filters if filters[k][0] == '+'])
notpass_count = sum([accumulator[k] for k in filters
if filters[k][0] == '-'])
total_count = pass_count + notpass_count
if total_count:
score = float(pass_count) / float(total_count) * 100.0
else:
score = 0.0
print 'SCORE.ltp: %.2f' % score
print SUMMARY_BORDER
def _filter_times(ltp_out_file):
"""Utility function to count lines that match certain filters.
@param ltp_out_file: human-readable output file from LTP -p (ltp.out).
@return a dictionary with test tags and corresponding times.
The dictionary is a set of buckets of tests based on the test
duration:
0: [tests that recoreded 0sec runtimes],
1: [tests that recorded runtimes from 0-60sec], ...
2: [tests that recorded runtimes from 61-120sec], ...
"""
test_tag_line_re = re.compile('^tag=(\w+)\s+stime=(\d+)$')
test_duration_line_re = re.compile('^duration=(\d+)\s+.*')
filter_accumulator = {}
with open(ltp_out_file) as f:
previous_tag = None
previous_time_s = 0
recorded_tags = set()
for line in f:
tag_matches = re.match(test_tag_line_re, line)
if tag_matches:
current_tag = tag_matches.group(1)
if previous_tag:
if previous_tag in recorded_tags:
print 'WARNING: duplicate tag found: %s.' % previous_tag
previous_tag = current_tag
continue
duration_matches = re.match(test_duration_line_re, line)
if duration_matches:
duration = int(duration_matches.group(1))
if not previous_tag:
print 'WARNING: duration without a tag: %s.' % duration
continue
if duration != 0:
duration = int(duration / 60) + 1
test_list = filter_accumulator.setdefault(duration, [])
test_list.append(previous_tag)
return filter_accumulator
def _print_timings(accumulator):
"""Utility function to print the summary of the parsing of ltp.out.
Prints a count of each type of test result, then a %pass-rate score.
Args:
@param accumulator: counts of test results
"""
print SUMMARY_BORDER
print 'Linux Test Project (LTP) Timing Summary:'
print SUMMARY_BORDER
for test_limit in sorted(accumulator.keys()):
print '<=%smin: %s tags: %s' % (
test_limit, len(accumulator[test_limit]),
', '.join(sorted(accumulator[test_limit])))
print ''
print SUMMARY_BORDER
return
def summarize(ltp_out_file, test_timings=None):
"""Scan detailed output from LTP run for summary test status reporting.
Looks for all possible test result types know to LTP: pass, fail, broken,
config error, retired and warning. Prints a summary.
@param ltp_out_file: human-readable output file from LTP -p (ltp.out).
@param test_timings: if True, emit an ordered summary of run timings of
tests.
"""
if not os.path.isfile(ltp_out_file):
print 'Unable to locate %s.' % ltp_out_file
return
_print_summary(TEST_FILTERS, _filter_and_count(ltp_out_file, TEST_FILTERS))
if test_timings:
_print_timings(_filter_times(ltp_out_file))
def main(argv):
""" Parse the human-readable logs from an LTP run and print a summary.
@param argv: command-line arguments.
"""
options = parse_args(argv)
summarize(options.ltp_out_file, options.test_timings)
if __name__ == '__main__':
main(sys.argv)