blob: 65f836a8da8dc6cd5d64a9731102dce263657c91 [file] [log] [blame] [edit]
#!/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.
"""This module calculates the test summary of specified test result files."""
import getopt
import glob
import operator
import os
import re
import sys
import time
import common_util
import trackpad_util
from trackpad_util import read_trackpad_test_conf, KEY_LOG, KEY_SEQ
# Define some constants and formats for parsing the result file
RESULT_END = 'End of Test Summary'
RESULT_STOP = 'stop'
RESULT_NOT_FUNCTIONALITY = 'not functionality'
RESULT_FORMAT_PASS_RATE = ' {0:<50}: {1:4s} {2:9s} passed'
RESULT_PATTERN_PASS_RATE = \
u'\s*(\S+)\s*:\s*\d+%\s*\s\(\s*(\d+)\D+(\d+)\s*\)\s*passed'
SUMMARY_LABEL = '_tut1_'
LINE_SPLIT = '\n'
def format_result_header(file_name, tot_pass_count, tot_count):
"""Format the result header."""
header = []
header.append(LINE_SPLIT)
header.append('Result summary file: %s' % file_name)
ratio = 0 if tot_count == 0 else (1.0 * tot_pass_count / tot_count)
tot_pass_rate_str = '%3.0f%%' % (100.0 * ratio)
header.append('*** Total pass rate: %s' % tot_pass_rate_str)
msg = ('*** Total number of (passed / tested) files: (%d / %d)\n\n' %
(tot_pass_count, tot_count))
header.append(msg)
return LINE_SPLIT.join(header)
def format_result_area(area_name):
"""Format of the area name."""
return ' Area: %s' % area_name
def format_result_pass_rate(name, pass_count, test_count):
"""Format the line of the pass rate and pass count."""
pass_rate_str = '%3.0f%%' % (100.0 * pass_count / test_count)
count_str = '(%2d / %2d)' % (pass_count, test_count)
return RESULT_FORMAT_PASS_RATE.format(name, pass_rate_str, count_str)
def format_result_body(summary_list, pass_count, tot_count, gss_vlog_dict,
flag_vlog=False):
"""Format the body of the test result."""
body = []
area = None
for s in summary_list:
if s.lstrip().startswith('Area'):
body.append(s)
# Extract area name
# e.g., "Area: click"
area = s.split(':')[1].strip()
else:
if pass_count.has_key(s) and tot_count.has_key(s):
line = format_result_pass_rate(s, pass_count[s], tot_count[s])
else:
line = '%s: %s' % (s, 'Warning: missing counts')
if flag_vlog:
line = LINE_SPLIT + line
body.append(line)
if flag_vlog:
if area is not None:
# E.g.,
# area: click
# s: no_cursor_wobble.tap
# fullname: click-no_cursor_wobble.tap
fullname = '-'.join([area, s])
for filename, root_cause in \
gss_vlog_dict[KEY_LOG][fullname]:
line = ' %s:' % os.path.basename(filename)
body.append(line)
for c in root_cause:
rc = root_cause[c]
if isinstance(rc, list):
indent = ' %s'
indent_rc = list(indent % str(i) for i in rc)
rc = LINE_SPLIT.join(indent_rc)
line = ' %s:\n%s' % (c, rc)
else:
line = ' %s: %s' % (c, rc)
body.append(line)
return LINE_SPLIT.join(body)
def format_result_tail():
"""Format the tail of the result."""
return '\n\n*** %s\n\n' % RESULT_END
def get_count_from_result_line(line):
"""Get the pass count and total count from a given line."""
if RESULT_END in line:
return RESULT_STOP
# Try to extract information from a result line which looks as
# ' no_cursor_wobble.tap : 50% ( 1 / 2 ) passed'
m = re.search(RESULT_PATTERN_PASS_RATE, line)
if m is None:
return RESULT_NOT_FUNCTIONALITY
fullname = m.group(1)
single_pass_count = int(m.group(2))
single_tot_count = int(m.group(3))
return (fullname, single_pass_count, single_tot_count)
def insert_list(summary_list, fullname, area):
"""Insert a functionality fullname to the end of a given area."""
if area is not None:
summary_list_length = len(summary_list)
if summary_list_length == 0:
return False
found_the_area = False
index = None
for i, s in enumerate(summary_list):
if found_the_area:
flag_found_next_area = re.search('Area:', s, re.I) is not None
if flag_found_next_area:
index = i
break
elif re.search('Area\s*:\s*%s' % area, s, re.I) is not None:
found_the_area = True
flag_end_of_list = (i == summary_list_length - 1)
if index is None and found_the_area and flag_end_of_list:
index = summary_list_length
if index is not None:
summary_list.insert(index, fullname)
return True
return False
class TrackpadResult:
"""Calculate integrated results over a number of gesture sets."""
def __init__(self, results_dir=None):
if results_dir is None:
result_name = 'gesture_files_path_results'
results_dir = read_trackpad_test_conf(result_name, '.')
self.results_dir = results_dir
if not os.path.isdir(self.results_dir):
os.makedirs(self.results_dir)
print '"%s" is created.\n' % self.results_dir
self._open_summary_file()
print 'The summary file is saved in %s.\n' % self.summary_name
msg = ('A report of trackpad autotest analysis on trackpad '
'usability study\n')
self.summary_file.write(msg)
msg = ' Gesture Sets Results Directory: "%s"\n' % self.results_dir
self.summary_file.write(msg)
self._get_results_files()
def _get_results_files(self):
"""Collect results_files from results_dir."""
self.label = SUMMARY_LABEL
_results = glob.glob(os.path.join(self.results_dir, '*'))
self.results_files = filter(lambda f: self.label in f and
os.path.isfile(f), _results)
def _open_summary_file(self):
time_format = '%Y%m%d_%H%M%S'
summary_time = 'summary:' + time.strftime(time_format, time.gmtime())
self.summary_name = os.path.join(self.results_dir, summary_time)
self.summary_file = open(self.summary_name, 'w')
def calc_test_summary(self, gss_vlog_dict, flag_vlog=False):
"""Calculate the test summary of test result files in the result_dir."""
# Initialization
pass_count = {}
tot_count = {}
fullname_list = []
summary_list = []
if not self.results_files:
print 'Warning: no result files in "%s".' % self.results_dir
return False
if flag_vlog:
msg = '\n\n\nThe detailed root causes of failed cases:\n'
self.summary_file.write(msg)
self.summary_file.write('-' * len(msg.strip('\n')))
for rfile in self.results_files:
with open(rfile) as f:
area = None
for line in f:
rv = get_count_from_result_line(line)
if rv == RESULT_NOT_FUNCTIONALITY:
# An area looks as
# ' Area: click '
m = re.search('\s*Area:\s*(\S+)\s*', line, re.I)
if m is not None:
area = m.group(1)
area_line = format_result_area(area)
if flag_vlog:
area_line = '\n' + area_line
if area_line not in summary_list:
summary_list.append(area_line)
continue
elif rv == RESULT_STOP:
break
else:
fullname, single_pass_count, single_tot_count = rv
if fullname not in pass_count:
pass_count[fullname] = 0
tot_count[fullname] = 0
pass_count[fullname] += single_pass_count
tot_count[fullname] += single_tot_count
if fullname not in summary_list:
# Insert the functionality fullname in the area
if not insert_list(summary_list, fullname, area):
msg = ' Warning: cannot insert %s into area %s'
print (msg % (fullname, area))
# Calculate the final statistics
if pass_count.values():
final_pass_count = reduce(operator.add, pass_count.values())
else:
final_pass_count = 0
if tot_count.values():
final_tot_count = reduce(operator.add, tot_count.values())
else:
final_tot_count = 0
# Create a test result summary
header = format_result_header(self.summary_name, final_pass_count,
final_tot_count)
body = format_result_body(summary_list, pass_count, tot_count,
gss_vlog_dict, flag_vlog=flag_vlog)
tail = format_result_tail()
self.summary_file.write(header)
self.summary_file.write(body)
self.summary_file.write(tail)
return True
def get_vlog(self, vlog_file):
''' Extract verification log from the vlog_file
The files contains something like
vlog{attr}={'log': {...}, 'seq': {...}}
'''
vlog_dict = None
with open(vlog_file) as f:
for line in f:
if line.startswith('Verification Log'):
vlog_dict = eval(line.split('=', 1)[1])
break
return vlog_dict
def _result_exists(self, gesture_set):
''' Check if a result file of a gesture set (gs) already exists'''
gs = os.path.basename(gesture_set)
gs_in_results = filter(lambda result_file: gs in result_file,
self.results_files)
return len(gs_in_results) > 0
def hardware_trackpad_test_all(self, gss_path=None, allow_duplicate=False):
''' Run all trackpad autotest analysis on all gesture sets (gss)
When allow_duplicate is True, it means to run autotest analysis for
the gesture sets no matter whether the result files of the gesture
sets exist or not.
'''
if gss_path is None:
gss_path = read_trackpad_test_conf('gesture_files_path_root', '.')
if not os.path.isdir(gss_path):
print 'Error: "%s" does not exist.' % gss_path
sys.exit(1)
print ' Gesture Sets: "%s"' % gss_path
autotest_link = read_trackpad_test_conf('gesture_files_path_autotest',
'.')
autotest_program = read_trackpad_test_conf('autotest_program', '.')
for gs in glob.glob(os.path.join(gss_path, '*')):
if (self.label in gs and
(allow_duplicate or not self._result_exists(gs))):
if os.path.islink(gs):
continue
if os.path.isfile(gs):
continue
print ' Test the gesture set "%s"' % gs
if os.path.islink(autotest_link):
os.remove(autotest_link)
os.symlink(gs, autotest_link)
cmd = '%s %s' % (autotest_program, 'control')
common_util.simple_system(cmd)
# Update the results_files since more results files have been created.
self._get_results_files()
def hardware_trackpad_vlog_all(self, results_dir=None, flag_vlog=False):
''' Collect all verification logs from all gesture set results path. '''
if results_dir is None:
results_dir = self.results_dir
# Initialize a cross-iteration verification dictionary for all gss
gss_vlog_dict = {}
gss_vlog_dict[KEY_LOG] = {}
gss_vlog_dict[KEY_SEQ] = []
# Integrate all the verification log
msg = '\nThis summary report is derived from the following %d users:\n'
self.summary_file.write(msg % len(self.results_files))
for result_file in self.results_files:
vlog_dict = self.get_vlog(result_file)
user_date = os.path.basename(result_file).split('.')[0]
if SUMMARY_LABEL in user_date:
user, date = user_date.split(SUMMARY_LABEL)
msg = ' user: %s (%s)\n' % (user, date)
self.summary_file.write(msg)
if vlog_dict is not None:
for gname in vlog_dict[KEY_SEQ]:
if gname not in gss_vlog_dict[KEY_SEQ]:
gss_vlog_dict[KEY_SEQ].append(gname)
gss_vlog_dict[KEY_LOG][gname] = []
if gname in vlog_dict[KEY_LOG]:
filename = vlog_dict[KEY_LOG][gname]['file']
root_cause = vlog_dict[KEY_LOG][gname]['root_cause']
gss_vlog_dict[KEY_LOG][gname].append([filename,
root_cause])
# Print all the verification log
if flag_vlog:
print '\n\ngss_vlog_dict:'
for gname in gss_vlog_dict[KEY_SEQ]:
print ' %s:' % gname
for filename, root_cause in gss_vlog_dict[KEY_LOG][gname]:
print ' %s:' % os.path.basename(filename)
for c in root_cause:
print ' %s: %s' % (c, root_cause[c])
return gss_vlog_dict
def _usage():
"""Print the usage of this program."""
# Print the usage
print 'Usage: $ %s [options]\n' % sys.argv[0]
print 'options:'
print ' -d, --dir=<result_directory>'
print ' <result_directory>: the path containing result files'
print ' -e, --enforce: enforce to run autotest analysis. Default is False.'
print ' -h, --help: show this help'
print ' -t, --tar: To tar all tpcontrol log files.'
print
def _parsing_error(msg):
"""Print the usage and exit when encountering parsing error."""
print 'Error: %s' % msg
_usage()
sys.exit(1)
def _parse_options():
"""Parse the command line options."""
try:
short_opt = 'hd:et'
long_opt = ['help', 'dir=', 'enforce', 'tar']
opts, args = getopt.getopt(sys.argv[1:], short_opt, long_opt)
except getopt.GetoptError, err:
_parsing_error(str(err))
# Initialize the option dictionary
option_dict = {}
option_dict['results_dir'] = read_trackpad_test_conf(
'gesture_files_path_results', '.')
option_dict['enforce'] = False
option_dict['tar'] = False
for opt, arg in opts:
if opt in ('-h', '--help'):
_usage()
sys.exit(1)
elif opt in ('-d', '--dir'):
if os.path.isdir(arg):
option_dict['results_dir'] = arg
else:
print 'Error: the result directory "%s" does not exist.' % arg
sys.exit(1)
elif opt in ('-e', '--enforce'):
option_dict['enforce'] = True
elif opt in ('-t', '--tar'):
option_dict['tar'] = True
else:
msg = 'Error: This option %s is not handled in program' % opt
_parsing_error(msg)
return option_dict
def main():
"""Run trackpad autotest on all gesture sets and create a summary report."""
# Parse command options
option_dict = _parse_options()
tresult = TrackpadResult(results_dir=option_dict['results_dir'])
# Determine whether to execute autotest analysis on gesture sets
if option_dict['enforce']:
tresult.hardware_trackpad_test_all()
# Tar all tpcontrol logs into a tar ball
if option_dict['tar']:
trackpad_util.TpcontrolLog().tar_tpcontrol_logs()
# Collect verification logs from all analysis logs
gss_vlog_dict = tresult.hardware_trackpad_vlog_all()
# Calculate the test summary over the analysis log without detailed logs
tresult.calc_test_summary(gss_vlog_dict, flag_vlog=False)
# List the test summary over the analysis log with detailed root causes
# of failed cases
tresult.calc_test_summary(gss_vlog_dict, flag_vlog=True)
if __name__ == '__main__':
main()