| # 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 |
| 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() |
| |
| # Get a simple x object to manipulate X properties. |
| self.simple_x = firmware_utils.SimpleX('aura') |
| |
| # 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' |
| 'You need to perform the following steps in chroot ' |
| 'which will copy the test version to the test machine.\n' |
| 'Step 1: cd to the firmware_TouchMTB directory\n' |
| 'Step 2: (cr) $ ./version.sh -r $MACHINE_IP') |
| 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 = self.simple_x.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.simple_x.get_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.simple_x.get_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() |
| |
| # Release simple x before launching the Chrome browser to display the |
| # html test result. |
| del self.simple_x |
| 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.CALIBRATION: |
| print ('Please upload the raw data to the spreadsheet after ' |
| 'the calibration tests have been finished successfully:') |
| print '$ python spreadsheet.py -v' |
| |
| |
| 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 ' -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 ' 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 ' # 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.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 = 'dhi:m:stu:' |
| long_opt = [OPTIONS.DEVICE, |
| 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 |
| 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() |