blob: 4ca8c64c89119d16b8cceb5b91421d8dd98f0b06 [file] [log] [blame]
#!/usr/bin/python2
# 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 sets up the system for the touch device firmware test suite."""
import getopt
import glob
import logging
import os
import sys
import common
import cros_gs
import firmware_utils
# TODO(josephsih): remove this hack when not relying on pygtk.
# The pygtk related stuffs are needed by firmware_window below.
if not firmware_utils.install_pygtk():
sys.exit(1)
import firmware_window
import keyboard_device
import mtb
import test_conf as conf
import test_flow
import touch_device
import validators
from common_util import print_and_exit
from firmware_constants import MODE, OPTIONS
from report_html import ReportHtml
def _display_test_result(report_html_name, flag_skip_html):
"""Display the test result html doc using telemetry."""
if not flag_skip_html and os.path.isdir('/usr/local/telemetry'):
import chrome
base_url = os.path.basename(report_html_name)
url = os.path.join('file://' + conf.docroot, base_url)
logging.info('Navigate to the URL: %s', url)
# Launch a browser to display the url.
print 'Display the html test report on the browser.'
print 'This may take a while...\n'
chrome.Chrome().browser.tabs[0].Navigate(url)
else:
print 'You can look up the html test result in %s' % report_html_name
class firmware_TouchMTB:
"""Set up the system for touch device firmware tests."""
def __init__(self, options):
self.options = options
self.test_version = 'test_' + self._get_test_version()
# Get the board name
self._get_board()
# We may need to use a device description file to create a fake device
# for replay purpose.
self._get_device_description_file()
# Create the touch device
# If you are going to be testing a touchscreen, set it here
self.touch_device = touch_device.TouchDevice(
is_touchscreen=options[OPTIONS.TOUCHSCREEN],
device_description_file=self.device_description_file)
self._check_device(self.touch_device)
validators.init_base_validator(self.touch_device)
# Create the keyboard device.
self.keyboard = keyboard_device.KeyboardDevice()
self._check_device(self.keyboard)
# Get the MTB parser.
self.parser = mtb.MtbParser()
# Create a simple gtk window.
self._get_screen_size()
self._get_touch_device_window_geometry()
self._get_prompt_frame_geometry()
self._get_result_frame_geometry()
self.win = firmware_window.FirmwareWindow(
size=self.screen_size,
prompt_size=self.prompt_frame_size,
image_size=self.touch_device_window_size,
result_size=self.result_frame_size)
mode = options[OPTIONS.MODE]
if options[OPTIONS.RESUME]:
# Use the firmware version of the real touch device for recording.
firmware_version = self.touch_device.get_firmware_version()
self.log_dir = options[OPTIONS.RESUME]
elif options[OPTIONS.REPLAY]:
# Use the firmware version of the specified logs for replay.
self.log_dir = options[OPTIONS.REPLAY]
fw_str, date = firmware_utils.get_fw_and_date(self.log_dir)
_, firmware_version = fw_str.split(conf.fw_prefix)
else:
# Use the firmware version of the real touch device for recording.
firmware_version = self.touch_device.get_firmware_version()
self.log_dir = firmware_utils.create_log_dir(firmware_version, mode)
# Save the device description file for future replay purpose if needed.
if not (self.options[OPTIONS.REPLAY] or self.options[OPTIONS.RESUME]):
self._save_device_description_file()
# Create the HTML report object and the output object to print messages
# on the window and to print the results in the report.
self._create_report_name(mode, firmware_version)
self.report_html = ReportHtml(self.report_html_name,
self.screen_size,
self.touch_device_window_size,
conf.score_colors,
self.test_version)
self.output = firmware_utils.Output(self.log_dir,
self.report_name,
self.win, self.report_html)
# Get the test_flow object which will guide through the gesture list.
self.test_flow = test_flow.TestFlow(self.touch_device_window_geometry,
self.touch_device,
self.keyboard,
self.win,
self.parser,
self.output,
self.test_version,
self.board,
firmware_version,
options)
# Register some callback functions for firmware window
self.win.register_callback('expose_event',
self.test_flow.init_gesture_setup_callback)
# Register a callback function to watch keyboard input events.
# This is required because the set_input_focus function of a window
# is flaky maybe due to problems of the window manager.
# Hence, we handle the keyboard input at a lower level.
self.win.register_io_add_watch(self.test_flow.user_choice_callback,
self.keyboard.system_device)
# Stop power management so that the screen does not dim during tests
firmware_utils.stop_power_management()
def _check_device(self, device):
"""Check if a device has been created successfully."""
if not device.exists():
logging.error('Cannot find device_node.')
exit(1)
def _get_test_version(self):
"""Get the test suite version number."""
if not os.path.isfile(conf.version_filename):
err_msg = ('Error: cannot find the test version file: %s\n\n'
'Perform the following steps in chroot to install '
'the test suite correctly:\n'
'Step 1: (cr) $ cd ~/trunk/src/scripts\n'
'Step 2: (cr) $ test_that --autotest_dir '
'~/trunk/src/third_party/autotest/files '
'$MACHINE_IP firmware_TouchMTBSetup\n')
print err_msg % conf.version_filename
sys.exit(1)
with open(conf.version_filename) as version_file:
return version_file.read()
def _get_board(self):
"""Get the board.
If this is in replay mode, get the board from the replay directory.
Otherwise, get the board name from current chromebook machine.
"""
replay_dir = self.options[OPTIONS.REPLAY]
if replay_dir:
self.board = firmware_utils.get_board_from_directory(replay_dir)
if self.board is None:
msg = 'Error: cannot get the board from the replay directory %s'
print_and_exit(msg % replay_dir)
else:
self.board = firmware_utils.get_board()
print ' board: %s' % self.board
def _get_device_ext(self):
"""Set the file extension of the device description filename to
'touchscreen' if it is a touchscreen; otherwise, set it to 'touchpad'.
"""
return ('touchscreen' if self.options[OPTIONS.TOUCHSCREEN] else
'touchpad')
def _get_device_description_file(self):
"""Get the device description file for replay purpose.
Get the device description file only when it is in replay mode and
the system DEVICE option is not specified.
The priority to locate the device description file:
(1) in the directory specified by the REPLAY option,
(2) in the tests/device/ directory
A device description file name looks like "link.touchpad"
"""
self.device_description_file = None
# Replay without using the system device. So use a mocked device.
if self.options[OPTIONS.REPLAY] and not self.options[OPTIONS.DEVICE]:
device_ext = self._get_device_ext()
board = self.board
descriptions = [
# (1) Try to find the device description in REPLAY directory.
(self.options[OPTIONS.REPLAY], '*.%s' % device_ext),
# (2) Try to find the device description in tests/device/
(conf.device_description_dir, '%s.%s' % (board, device_ext),)
]
for description_dir, description_pattern in descriptions:
files = glob.glob(os.path.join(description_dir,
description_pattern))
if files:
self.device_description_file = files[0]
break
else:
msg = 'Error: cannot find the device description file.'
print_and_exit(msg)
print ' device description file: %s' % self.device_description_file
def _save_device_description_file(self):
"""Save the device description file for future replay."""
filename = '%s.%s' % (self.board, self._get_device_ext())
filepath = os.path.join(self.log_dir, filename)
if not self.touch_device.save_device_description_file(
filepath, self.board):
msg = 'Error: fail to save the device description file: %s'
print_and_exit(msg % filepath)
def _create_report_name(self, mode, firmware_version):
"""Create the report names for both plain-text and html files.
A typical html file name looks like:
touch_firmware_report-lumpy-fw_11.25-20121016_080924.html
"""
firmware_str = conf.fw_prefix + firmware_version
curr_time = firmware_utils.get_current_time_str()
fname = conf.filename.sep.join([conf.report_basename,
self.board,
firmware_str,
mode,
curr_time])
self.report_name = os.path.join(self.log_dir, fname)
self.report_html_name = self.report_name + conf.html_ext
def _get_screen_size(self):
"""Get the screen size."""
self.screen_size = firmware_utils.get_screen_size()
def _get_touch_device_window_geometry(self):
"""Get the preferred window geometry to display mtplot."""
display_ratio = 0.7
self.touch_device_window_geometry = \
self.touch_device.get_display_geometry(
self.screen_size, display_ratio)
self.touch_device_window_size = self.touch_device_window_geometry[0:2]
def _get_prompt_frame_geometry(self):
"""Get the display geometry of the prompt frame."""
(_, wint_height, _, _) = self.touch_device_window_geometry
screen_width, screen_height = self.screen_size
win_x = 0
win_y = 0
win_width = screen_width
win_height = screen_height - wint_height
self.winp_geometry = (win_x, win_y, win_width, win_height)
self.prompt_frame_size = (win_width, win_height)
def _get_result_frame_geometry(self):
"""Get the display geometry of the test result frame."""
(wint_width, wint_height, _, _) = self.touch_device_window_geometry
screen_width, _ = self.screen_size
win_width = screen_width - wint_width
win_height = wint_height
self.result_frame_size = (win_width, win_height)
def main(self):
"""A helper to enter gtk main loop."""
# Enter the window event driven mode.
fw.win.main()
# Resume the power management.
firmware_utils.start_power_management()
flag_skip_html = self.options[OPTIONS.SKIP_HTML]
try:
_display_test_result(self.report_html_name, flag_skip_html)
except Exception, e:
print 'Warning: cannot display the html result file: %s\n' % e
print ('You can access the html result file: "%s"\n' %
self.report_html_name)
finally:
print 'You can upload all data in the latest result directory:'
print ' $ DISPLAY=:0 OPTIONS="-u latest" python main.py\n'
print ('You can also upload any test result directory, e.g., '
'"20130702_063631-fw_1.23-manual", in "%s"' %
conf.log_root_dir)
print (' $ DISPLAY=:0 OPTIONS="-u 20130702_063631-fw_11.23-manual"'
' python main.py\n')
if self.options[OPTIONS.MODE] == MODE.NOISE:
print ('You can generate a summary of the extended noise test_flow '
'by copying the html report to your computer and running '
'noise_summary.py, located in '
'~/trunk/src/third_party/autotest/files/client/site_tests/firmware_TouchMTB/')
def upload_to_gs(log_dir):
"""Upload the gesture event files specified in log_dir to Google cloud
storage server.
@param log_dir: the log directory of which the gesture event files are
to be uploaded to Google cloud storage server
"""
# Set up gsutil package.
# The board argument is used to locate the proper bucket directory
gs = cros_gs.CrosGs(firmware_utils.get_board())
log_path = os.path.join(conf.log_root_dir, log_dir)
if not os.path.isdir(log_path):
print_and_exit('Error: the log path "%s" does not exist.' % log_path)
print 'Uploading "%s" to %s ...\n' % (log_path, gs.bucket)
try:
gs.upload(log_path)
except Exception, e:
msg = 'Error in uploading event files in %s: %s.'
print_and_exit(msg % (log_path, e))
def _usage_and_exit():
"""Print the usage of this program."""
print 'Usage: $ DISPLAY=:0 [OPTIONS="options"] python %s\n' % sys.argv[0]
print 'options:'
print ' -d, --%s' % OPTIONS.DEVICE
print ' use the system device for replay'
print ' -h, --%s' % OPTIONS.HELP
print ' show this help'
print ' -i, --%s iterations' % OPTIONS.ITERATIONS
print ' specify the number of iterations'
print ' -f, --%s' % OPTIONS.FNGENERATOR
print ' Indicate that (despite not having a touchbot) there is a'
print ' function generator attached for the noise tests'
print ' -m, --%s mode' % OPTIONS.MODE
print ' specify the gesture playing mode'
print ' mode could be one of the following options'
print ' calibration: conducting pressure calibration'
print ' complete: all gestures including those in ' \
'both manual mode and robot mode'
print ' manual: all gestures minus gestures in robot mode'
print ' noise: an extensive, 4 hour noise test'
print ' robot: using robot to perform gestures automatically'
print ' robot_sim: robot simulation, for developer only'
print ' --%s log_dir' % OPTIONS.REPLAY
print ' Replay the gesture files and get the test results.'
print ' log_dir is a log sub-directory in %s' % conf.log_root_dir
print ' --%s log_dir' % OPTIONS.RESUME
print ' Resume recording the gestures files in the log_dir.'
print ' log_dir is a log sub-directory in %s' % conf.log_root_dir
print ' -s, --%s' % OPTIONS.SIMPLIFIED
print ' Use one variation per gesture'
print ' --%s' % OPTIONS.SKIP_HTML
print ' Do not show the html test result.'
print ' -t, --%s' % OPTIONS.TOUCHSCREEN
print ' Use the touchscreen instead of a touchpad'
print ' -u, --%s log_dir' % OPTIONS.UPLOAD
print ' Upload the gesture event files in the specified log_dir '
print ' to Google cloud storage server.'
print ' It uploads results that you already have from a previous run'
print ' without re-running the test.'
print ' log_dir could be either '
print ' (1) a directory in %s' % conf.log_root_dir
print ' (2) a full path, or'
print ' (3) the default "latest" directory in %s if omitted' % \
conf.log_root_dir
print
print 'Example:'
print ' # Use the robot to perform 3 iterations of the robot gestures.'
print ' $ DISPLAY=:0 OPTIONS="-m robot_sim -i 3" python main.py\n'
print ' # Perform 1 iteration of the manual gestures.'
print ' $ DISPLAY=:0 OPTIONS="-m manual" python main.py\n'
print ' # Perform 1 iteration of all manual and robot gestures.'
print ' $ DISPLAY=:0 OPTIONS="-m complete" python main.py\n'
print ' # Perform pressure calibration.'
print ' $ DISPLAY=:0 OPTIONS="-m calibration" python main.py\n'
print ' # Use the robot to perform a latency test with Quickstep'
print ' $ DISPLAY=:0 OPTIONS="-m quickstep" python main.py\n'
print ' # Use the robot to perform an extensive, 4 hour noise test'
print ' $ DISPLAY=:0 OPTIONS="-m noise" python main.py\n'
print ' # Replay the gesture files in the latest log directory.'
print ' $ DISPLAY=:0 OPTIONS="--replay latest" python main.py\n'
example_log_dir = '20130226_040802-fw_1.2-manual'
print (' # Replay the gesture files in %s/%s with a mocked device.' %
(conf.log_root_dir, example_log_dir))
print ' $ DISPLAY=:0 OPTIONS="--replay %s" python main.py\n' % \
example_log_dir
print (' # Replay the gesture files in %s/%s with the system device.' %
(conf.log_root_dir, example_log_dir))
print (' $ DISPLAY=:0 OPTIONS="--replay %s -d" python main.py\n' %
example_log_dir)
print ' # Resume recording the gestures in the latest log directory.'
print ' $ DISPLAY=:0 OPTIONS="--resume latest" python main.py\n'
print ' # Resume recording the gestures in %s/%s.' % (conf.log_root_dir,
example_log_dir)
print ' $ DISPLAY=:0 OPTIONS="--resume %s" python main.py\n' % \
example_log_dir
print (' # Upload the gesture event files specified in the log_dir '
'to Google cloud storage server.')
print (' $ DISPLAY=:0 OPTIONS="-u 20130701_020120-fw_11.23-complete" '
'python main.py\n')
print (' # Upload the gesture event files in the "latest" directory '
'to Google cloud storage server.')
print ' $ DISPLAY=:0 OPTIONS="-u latest" python main.py\n'
sys.exit(1)
def _parsing_error(msg):
"""Print the usage and exit when encountering parsing error."""
print 'Error: %s' % msg
_usage_and_exit()
def _parse_options():
"""Parse the options.
Note that the options are specified with environment variable OPTIONS,
because pyauto seems not compatible with command line options.
"""
# Set the default values of options.
options = {OPTIONS.DEVICE: False,
OPTIONS.FNGENERATOR: False,
OPTIONS.ITERATIONS: 1,
OPTIONS.MODE: MODE.MANUAL,
OPTIONS.REPLAY: None,
OPTIONS.RESUME: None,
OPTIONS.SIMPLIFIED: False,
OPTIONS.SKIP_HTML: False,
OPTIONS.TOUCHSCREEN: False,
OPTIONS.UPLOAD: None,
}
# Get the command line options or get the options from environment OPTIONS
options_list = sys.argv[1:] or os.environ.get('OPTIONS', '').split()
if not options_list:
return options
short_opt = 'dfhi:m:stu:'
long_opt = [OPTIONS.DEVICE,
OPTIONS.FNGENERATOR,
OPTIONS.HELP,
OPTIONS.ITERATIONS + '=',
OPTIONS.MODE + '=',
OPTIONS.REPLAY + '=',
OPTIONS.RESUME + '=',
OPTIONS.SIMPLIFIED,
OPTIONS.SKIP_HTML,
OPTIONS.TOUCHSCREEN,
OPTIONS.UPLOAD + '=',
]
try:
opts, args = getopt.getopt(options_list, short_opt, long_opt)
except getopt.GetoptError, err:
_parsing_error(str(err))
for opt, arg in opts:
if opt in ('-d', '--%s' % OPTIONS.DEVICE):
options[OPTIONS.DEVICE] = True
if opt in ('-f', '--%s' % OPTIONS.FNGENERATOR):
options[OPTIONS.FNGENERATOR] = True
elif opt in ('-h', '--%s' % OPTIONS.HELP):
_usage_and_exit()
elif opt in ('-i', '--%s' % OPTIONS.ITERATIONS):
if arg.isdigit():
options[OPTIONS.ITERATIONS] = int(arg)
else:
_usage_and_exit()
elif opt in ('-m', '--%s' % OPTIONS.MODE):
arg = arg.lower()
if arg in MODE.GESTURE_PLAY_MODE:
options[OPTIONS.MODE] = arg
else:
print 'Warning: -m should be one of %s' % MODE.GESTURE_PLAY_MODE
elif opt in ('--%s' % OPTIONS.REPLAY, '--%s' % OPTIONS.RESUME):
log_dir = os.path.join(conf.log_root_dir, arg)
if os.path.isdir(log_dir):
# opt could be either '--replay' or '--resume'.
# We would like to strip off the '-' on the left hand side.
options[opt.lstrip('-')] = log_dir
else:
print 'Error: the log directory "%s" does not exist.' % log_dir
_usage_and_exit()
elif opt in ('-s', '--%s' % OPTIONS.SIMPLIFIED):
options[OPTIONS.SIMPLIFIED] = True
elif opt in ('--%s' % OPTIONS.SKIP_HTML,):
options[OPTIONS.SKIP_HTML] = True
elif opt in ('-t', '--%s' % OPTIONS.TOUCHSCREEN):
options[OPTIONS.TOUCHSCREEN] = True
elif opt in ('-u', '--%s' % OPTIONS.UPLOAD):
upload_to_gs(arg)
sys.exit()
else:
msg = 'This option "%s" is not supported.' % opt
_parsing_error(opt)
print 'Note: the %s mode is used.' % options[OPTIONS.MODE]
return options
if __name__ == '__main__':
options = _parse_options()
fw = firmware_TouchMTB(options)
fw.main()