#!/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 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/') 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 ' -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()