Home | History | Annotate | Download | only in firmware_TouchMTB
      1 #!/usr/bin/python
      2 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """This module sets up the system for the touch device firmware test suite."""
      7 
      8 import getopt
      9 import glob
     10 import logging
     11 import os
     12 import sys
     13 
     14 import common
     15 import cros_gs
     16 import firmware_utils
     17 
     18 # TODO(josephsih): remove this hack when not relying on pygtk.
     19 # The pygtk related stuffs are needed by firmware_window below.
     20 if not firmware_utils.install_pygtk():
     21     sys.exit(1)
     22 
     23 import firmware_window
     24 import keyboard_device
     25 import mtb
     26 import test_conf as conf
     27 import test_flow
     28 import touch_device
     29 import validators
     30 
     31 from common_util import print_and_exit
     32 from firmware_constants import MODE, OPTIONS
     33 from report_html import ReportHtml
     34 
     35 
     36 def _display_test_result(report_html_name, flag_skip_html):
     37     """Display the test result html doc using telemetry."""
     38     if not flag_skip_html and os.path.isdir('/usr/local/telemetry'):
     39         import chrome
     40 
     41         base_url = os.path.basename(report_html_name)
     42         url = os.path.join('file://' + conf.docroot, base_url)
     43         logging.info('Navigate to the URL: %s', url)
     44 
     45         # Launch a browser to display the url.
     46         print 'Display the html test report on the browser.'
     47         print 'This may take a while...\n'
     48         chrome.Chrome().browser.tabs[0].Navigate(url)
     49     else:
     50         print 'You can look up the html test result in %s' % report_html_name
     51 
     52 
     53 class firmware_TouchMTB:
     54     """Set up the system for touch device firmware tests."""
     55 
     56     def __init__(self, options):
     57         self.options = options
     58 
     59         self.test_version = 'test_' + self._get_test_version()
     60 
     61         # Get the board name
     62         self._get_board()
     63 
     64         # We may need to use a device description file to create a fake device
     65         # for replay purpose.
     66         self._get_device_description_file()
     67 
     68         # Create the touch device
     69         # If you are going to be testing a touchscreen, set it here
     70         self.touch_device = touch_device.TouchDevice(
     71             is_touchscreen=options[OPTIONS.TOUCHSCREEN],
     72             device_description_file=self.device_description_file)
     73         self._check_device(self.touch_device)
     74         validators.init_base_validator(self.touch_device)
     75 
     76         # Create the keyboard device.
     77         self.keyboard = keyboard_device.KeyboardDevice()
     78         self._check_device(self.keyboard)
     79 
     80         # Get the MTB parser.
     81         self.parser = mtb.MtbParser()
     82 
     83         # Create a simple gtk window.
     84         self._get_screen_size()
     85         self._get_touch_device_window_geometry()
     86         self._get_prompt_frame_geometry()
     87         self._get_result_frame_geometry()
     88         self.win = firmware_window.FirmwareWindow(
     89                 size=self.screen_size,
     90                 prompt_size=self.prompt_frame_size,
     91                 image_size=self.touch_device_window_size,
     92                 result_size=self.result_frame_size)
     93 
     94         mode = options[OPTIONS.MODE]
     95         if options[OPTIONS.RESUME]:
     96             # Use the firmware version of the real touch device for recording.
     97             firmware_version = self.touch_device.get_firmware_version()
     98             self.log_dir = options[OPTIONS.RESUME]
     99         elif options[OPTIONS.REPLAY]:
    100             # Use the firmware version of the specified logs for replay.
    101             self.log_dir = options[OPTIONS.REPLAY]
    102             fw_str, date = firmware_utils.get_fw_and_date(self.log_dir)
    103             _, firmware_version = fw_str.split(conf.fw_prefix)
    104         else:
    105             # Use the firmware version of the real touch device for recording.
    106             firmware_version = self.touch_device.get_firmware_version()
    107             self.log_dir = firmware_utils.create_log_dir(firmware_version, mode)
    108 
    109         # Save the device description file for future replay purpose if needed.
    110         if not (self.options[OPTIONS.REPLAY] or self.options[OPTIONS.RESUME]):
    111             self._save_device_description_file()
    112 
    113         # Create the HTML report object and the output object to print messages
    114         # on the window and to print the results in the report.
    115         self._create_report_name(mode, firmware_version)
    116         self.report_html = ReportHtml(self.report_html_name,
    117                                       self.screen_size,
    118                                       self.touch_device_window_size,
    119                                       conf.score_colors,
    120                                       self.test_version)
    121         self.output = firmware_utils.Output(self.log_dir,
    122                                             self.report_name,
    123                                             self.win, self.report_html)
    124 
    125         # Get the test_flow object which will guide through the gesture list.
    126         self.test_flow = test_flow.TestFlow(self.touch_device_window_geometry,
    127                                             self.touch_device,
    128                                             self.keyboard,
    129                                             self.win,
    130                                             self.parser,
    131                                             self.output,
    132                                             self.test_version,
    133                                             self.board,
    134                                             firmware_version,
    135                                             options)
    136 
    137         # Register some callback functions for firmware window
    138         self.win.register_callback('expose_event',
    139                                    self.test_flow.init_gesture_setup_callback)
    140 
    141         # Register a callback function to watch keyboard input events.
    142         # This is required because the set_input_focus function of a window
    143         # is flaky maybe due to problems of the window manager.
    144         # Hence, we handle the keyboard input at a lower level.
    145         self.win.register_io_add_watch(self.test_flow.user_choice_callback,
    146                                        self.keyboard.system_device)
    147 
    148         # Stop power management so that the screen does not dim during tests
    149         firmware_utils.stop_power_management()
    150 
    151     def _check_device(self, device):
    152         """Check if a device has been created successfully."""
    153         if not device.exists():
    154             logging.error('Cannot find device_node.')
    155             exit(1)
    156 
    157     def _get_test_version(self):
    158         """Get the test suite version number."""
    159         if not os.path.isfile(conf.version_filename):
    160             err_msg = ('Error: cannot find the test version file: %s\n\n'
    161                        'Perform the following steps in chroot to install '
    162                        'the test suite correctly:\n'
    163                        'Step 1: (cr) $ cd ~/trunk/src/scripts\n'
    164                        'Step 2: (cr) $ test_that --autotest_dir '
    165                        '~/trunk/src/third_party/autotest/files '
    166                        '$MACHINE_IP firmware_TouchMTBSetup\n')
    167             print err_msg % conf.version_filename
    168             sys.exit(1)
    169 
    170         with open(conf.version_filename) as version_file:
    171             return version_file.read()
    172 
    173     def _get_board(self):
    174         """Get the board.
    175 
    176         If this is in replay mode, get the board from the replay directory.
    177         Otherwise, get the board name from current chromebook machine.
    178         """
    179         replay_dir = self.options[OPTIONS.REPLAY]
    180         if replay_dir:
    181             self.board = firmware_utils.get_board_from_directory(replay_dir)
    182             if self.board is None:
    183                 msg = 'Error: cannot get the board from the replay directory %s'
    184                 print_and_exit(msg % replay_dir)
    185         else:
    186             self.board = firmware_utils.get_board()
    187         print '      board: %s' % self.board
    188 
    189     def _get_device_ext(self):
    190         """Set the file extension of the device description filename to
    191         'touchscreen' if it is a touchscreen; otherwise, set it to 'touchpad'.
    192         """
    193         return ('touchscreen' if self.options[OPTIONS.TOUCHSCREEN] else
    194                 'touchpad')
    195 
    196     def _get_device_description_file(self):
    197         """Get the device description file for replay purpose.
    198 
    199         Get the device description file only when it is in replay mode and
    200         the system DEVICE option is not specified.
    201 
    202         The priority to locate the device description file:
    203         (1) in the directory specified by the REPLAY option,
    204         (2) in the tests/device/ directory
    205 
    206         A device description file name looks like "link.touchpad"
    207         """
    208         self.device_description_file = None
    209         # Replay without using the system device. So use a mocked device.
    210         if self.options[OPTIONS.REPLAY] and not self.options[OPTIONS.DEVICE]:
    211             device_ext = self._get_device_ext()
    212             board = self.board
    213             descriptions = [
    214                 # (1) Try to find the device description in REPLAY directory.
    215                 (self.options[OPTIONS.REPLAY], '*.%s' % device_ext),
    216                 # (2) Try to find the device description in tests/device/
    217                 (conf.device_description_dir, '%s.%s' % (board, device_ext),)
    218             ]
    219 
    220             for description_dir, description_pattern in descriptions:
    221                 files = glob.glob(os.path.join(description_dir,
    222                                                description_pattern))
    223                 if files:
    224                     self.device_description_file = files[0]
    225                     break
    226             else:
    227                 msg = 'Error: cannot find the device description file.'
    228                 print_and_exit(msg)
    229         print '      device description file: %s' % self.device_description_file
    230 
    231     def _save_device_description_file(self):
    232         """Save the device description file for future replay."""
    233         filename = '%s.%s' % (self.board, self._get_device_ext())
    234         filepath = os.path.join(self.log_dir, filename)
    235         if not self.touch_device.save_device_description_file(
    236                 filepath, self.board):
    237             msg = 'Error: fail to save the device description file: %s'
    238             print_and_exit(msg % filepath)
    239 
    240     def _create_report_name(self, mode, firmware_version):
    241         """Create the report names for both plain-text and html files.
    242 
    243         A typical html file name looks like:
    244             touch_firmware_report-lumpy-fw_11.25-20121016_080924.html
    245         """
    246         firmware_str = conf.fw_prefix + firmware_version
    247         curr_time = firmware_utils.get_current_time_str()
    248         fname = conf.filename.sep.join([conf.report_basename,
    249                                         self.board,
    250                                         firmware_str,
    251                                         mode,
    252                                         curr_time])
    253         self.report_name = os.path.join(self.log_dir, fname)
    254         self.report_html_name = self.report_name + conf.html_ext
    255 
    256     def _get_screen_size(self):
    257         """Get the screen size."""
    258         self.screen_size = firmware_utils.get_screen_size()
    259 
    260     def _get_touch_device_window_geometry(self):
    261         """Get the preferred window geometry to display mtplot."""
    262         display_ratio = 0.7
    263         self.touch_device_window_geometry = \
    264                 self.touch_device.get_display_geometry(
    265                 self.screen_size, display_ratio)
    266         self.touch_device_window_size = self.touch_device_window_geometry[0:2]
    267 
    268     def _get_prompt_frame_geometry(self):
    269         """Get the display geometry of the prompt frame."""
    270         (_, wint_height, _, _) = self.touch_device_window_geometry
    271         screen_width, screen_height = self.screen_size
    272         win_x = 0
    273         win_y = 0
    274         win_width = screen_width
    275         win_height = screen_height - wint_height
    276         self.winp_geometry = (win_x, win_y, win_width, win_height)
    277         self.prompt_frame_size = (win_width, win_height)
    278 
    279     def _get_result_frame_geometry(self):
    280         """Get the display geometry of the test result frame."""
    281         (wint_width, wint_height, _, _) = self.touch_device_window_geometry
    282         screen_width, _ = self.screen_size
    283         win_width = screen_width - wint_width
    284         win_height = wint_height
    285         self.result_frame_size = (win_width, win_height)
    286 
    287     def main(self):
    288         """A helper to enter gtk main loop."""
    289         # Enter the window event driven mode.
    290         fw.win.main()
    291 
    292         # Resume the power management.
    293         firmware_utils.start_power_management()
    294 
    295         flag_skip_html = self.options[OPTIONS.SKIP_HTML]
    296         try:
    297             _display_test_result(self.report_html_name, flag_skip_html)
    298         except Exception, e:
    299             print 'Warning: cannot display the html result file: %s\n' % e
    300             print ('You can access the html result file: "%s"\n' %
    301                    self.report_html_name)
    302         finally:
    303             print 'You can upload all data in the latest result directory:'
    304             print '  $ DISPLAY=:0 OPTIONS="-u latest" python main.py\n'
    305             print ('You can also upload any test result directory, e.g., '
    306                    '"20130702_063631-fw_1.23-manual", in "%s"' %
    307                    conf.log_root_dir)
    308             print ('  $ DISPLAY=:0 OPTIONS="-u 20130702_063631-fw_11.23-manual"'
    309                    ' python main.py\n')
    310 
    311             if self.options[OPTIONS.MODE] == MODE.NOISE:
    312                 print ('You can generate a summary of the extended noise test_flow '
    313                        'by copying the html report to your computer and running '
    314                        'noise_summary.py, located in '
    315                        '~/trunk/src/third_party/autotest/files/client/site_tests/firmware_TouchMTB/')
    316 
    317             if self.options[OPTIONS.MODE] == MODE.CALIBRATION:
    318                 print ('Please upload the raw data to the spreadsheet after '
    319                        'the calibration tests have been finished successfully:')
    320                 print '$ python spreadsheet.py -v'
    321 
    322 
    323 def upload_to_gs(log_dir):
    324     """Upload the gesture event files specified in log_dir to Google cloud
    325     storage server.
    326 
    327     @param log_dir: the log directory of which the gesture event files are
    328             to be uploaded to Google cloud storage server
    329     """
    330     # Set up gsutil package.
    331     # The board argument is used to locate the proper bucket directory
    332     gs = cros_gs.CrosGs(firmware_utils.get_board())
    333 
    334     log_path = os.path.join(conf.log_root_dir, log_dir)
    335     if not os.path.isdir(log_path):
    336         print_and_exit('Error: the log path "%s" does not exist.' % log_path)
    337 
    338     print 'Uploading "%s" to %s ...\n' % (log_path, gs.bucket)
    339     try:
    340         gs.upload(log_path)
    341     except Exception, e:
    342         msg = 'Error in uploading event files in %s: %s.'
    343         print_and_exit(msg % (log_path, e))
    344 
    345 
    346 def _usage_and_exit():
    347     """Print the usage of this program."""
    348     print 'Usage: $ DISPLAY=:0 [OPTIONS="options"] python %s\n' % sys.argv[0]
    349     print 'options:'
    350     print '  -d, --%s' % OPTIONS.DEVICE
    351     print '        use the system device for replay'
    352     print '  -h, --%s' % OPTIONS.HELP
    353     print '        show this help'
    354     print '  -i, --%s iterations' % OPTIONS.ITERATIONS
    355     print '        specify the number of iterations'
    356     print '  -f, --%s' % OPTIONS.FNGENERATOR
    357     print '        Indicate that (despite not having a touchbot) there is a'
    358     print '        function generator attached for the noise tests'
    359     print '  -m, --%s mode' % OPTIONS.MODE
    360     print '        specify the gesture playing mode'
    361     print '        mode could be one of the following options'
    362     print '            calibration: conducting pressure calibration'
    363     print '            complete: all gestures including those in ' \
    364                                 'both manual mode and robot mode'
    365     print '            manual: all gestures minus gestures in robot mode'
    366     print '            noise: an extensive, 4 hour noise test'
    367     print '            robot: using robot to perform gestures automatically'
    368     print '            robot_sim: robot simulation, for developer only'
    369     print '  --%s log_dir' % OPTIONS.REPLAY
    370     print '        Replay the gesture files and get the test results.'
    371     print '        log_dir is a log sub-directory in %s' % conf.log_root_dir
    372     print '  --%s log_dir' % OPTIONS.RESUME
    373     print '        Resume recording the gestures files in the log_dir.'
    374     print '        log_dir is a log sub-directory in %s' % conf.log_root_dir
    375     print '  -s, --%s' % OPTIONS.SIMPLIFIED
    376     print '        Use one variation per gesture'
    377     print '  --%s' % OPTIONS.SKIP_HTML
    378     print '        Do not show the html test result.'
    379     print '  -t, --%s' % OPTIONS.TOUCHSCREEN
    380     print '        Use the touchscreen instead of a touchpad'
    381     print '  -u, --%s log_dir' % OPTIONS.UPLOAD
    382     print '        Upload the gesture event files in the specified log_dir '
    383     print '        to Google cloud storage server.'
    384     print '        It uploads results that you already have from a previous run'
    385     print '        without re-running the test.'
    386     print '        log_dir could be either '
    387     print '        (1) a directory in %s' % conf.log_root_dir
    388     print '        (2) a full path, or'
    389     print '        (3) the default "latest" directory in %s if omitted' % \
    390                    conf.log_root_dir
    391     print
    392     print 'Example:'
    393     print '  # Use the robot to perform 3 iterations of the robot gestures.'
    394     print '  $ DISPLAY=:0 OPTIONS="-m robot_sim -i 3" python main.py\n'
    395     print '  # Perform 1 iteration of the manual gestures.'
    396     print '  $ DISPLAY=:0 OPTIONS="-m manual" python main.py\n'
    397     print '  # Perform 1 iteration of all manual and robot gestures.'
    398     print '  $ DISPLAY=:0 OPTIONS="-m complete" python main.py\n'
    399     print '  # Perform pressure calibration.'
    400     print '  $ DISPLAY=:0 OPTIONS="-m calibration" python main.py\n'
    401     print '  # Use the robot to perform a latency test with Quickstep'
    402     print '  $ DISPLAY=:0 OPTIONS="-m quickstep" python main.py\n'
    403     print '  # Use the robot to perform an extensive, 4 hour noise test'
    404     print '  $ DISPLAY=:0 OPTIONS="-m noise" python main.py\n'
    405     print '  # Replay the gesture files in the latest log directory.'
    406     print '  $ DISPLAY=:0 OPTIONS="--replay latest" python main.py\n'
    407     example_log_dir = '20130226_040802-fw_1.2-manual'
    408     print ('  # Replay the gesture files in %s/%s with a mocked device.' %
    409             (conf.log_root_dir, example_log_dir))
    410     print '  $ DISPLAY=:0 OPTIONS="--replay %s" python main.py\n' % \
    411             example_log_dir
    412     print ('  # Replay the gesture files in %s/%s with the system device.' %
    413             (conf.log_root_dir, example_log_dir))
    414     print ('  $ DISPLAY=:0 OPTIONS="--replay %s -d" python main.py\n' %
    415             example_log_dir)
    416     print '  # Resume recording the gestures in the latest log directory.'
    417     print '  $ DISPLAY=:0 OPTIONS="--resume latest" python main.py\n'
    418     print '  # Resume recording the gestures in %s/%s.' % (conf.log_root_dir,
    419                                                            example_log_dir)
    420     print '  $ DISPLAY=:0 OPTIONS="--resume %s" python main.py\n' % \
    421             example_log_dir
    422     print ('  # Upload the gesture event files specified in the log_dir '
    423              'to Google cloud storage server.')
    424     print ('  $ DISPLAY=:0 OPTIONS="-u 20130701_020120-fw_11.23-complete" '
    425            'python main.py\n')
    426     print ('  # Upload the gesture event files in the "latest" directory '
    427            'to Google cloud storage server.')
    428     print '  $ DISPLAY=:0 OPTIONS="-u latest" python main.py\n'
    429 
    430     sys.exit(1)
    431 
    432 
    433 def _parsing_error(msg):
    434     """Print the usage and exit when encountering parsing error."""
    435     print 'Error: %s' % msg
    436     _usage_and_exit()
    437 
    438 
    439 def _parse_options():
    440     """Parse the options.
    441 
    442     Note that the options are specified with environment variable OPTIONS,
    443     because pyauto seems not compatible with command line options.
    444     """
    445     # Set the default values of options.
    446     options = {OPTIONS.DEVICE: False,
    447                OPTIONS.FNGENERATOR: False,
    448                OPTIONS.ITERATIONS: 1,
    449                OPTIONS.MODE: MODE.MANUAL,
    450                OPTIONS.REPLAY: None,
    451                OPTIONS.RESUME: None,
    452                OPTIONS.SIMPLIFIED: False,
    453                OPTIONS.SKIP_HTML: False,
    454                OPTIONS.TOUCHSCREEN: False,
    455                OPTIONS.UPLOAD: None,
    456     }
    457 
    458     # Get the command line options or get the options from environment OPTIONS
    459     options_list = sys.argv[1:] or os.environ.get('OPTIONS', '').split()
    460     if not options_list:
    461         return options
    462 
    463     short_opt = 'dfhi:m:stu:'
    464     long_opt = [OPTIONS.DEVICE,
    465                 OPTIONS.FNGENERATOR,
    466                 OPTIONS.HELP,
    467                 OPTIONS.ITERATIONS + '=',
    468                 OPTIONS.MODE + '=',
    469                 OPTIONS.REPLAY + '=',
    470                 OPTIONS.RESUME + '=',
    471                 OPTIONS.SIMPLIFIED,
    472                 OPTIONS.SKIP_HTML,
    473                 OPTIONS.TOUCHSCREEN,
    474                 OPTIONS.UPLOAD + '=',
    475     ]
    476     try:
    477         opts, args = getopt.getopt(options_list, short_opt, long_opt)
    478     except getopt.GetoptError, err:
    479         _parsing_error(str(err))
    480 
    481     for opt, arg in opts:
    482         if opt in ('-d', '--%s' % OPTIONS.DEVICE):
    483             options[OPTIONS.DEVICE] = True
    484         if opt in ('-f', '--%s' % OPTIONS.FNGENERATOR):
    485             options[OPTIONS.FNGENERATOR] = True
    486         elif opt in ('-h', '--%s' % OPTIONS.HELP):
    487             _usage_and_exit()
    488         elif opt in ('-i', '--%s' % OPTIONS.ITERATIONS):
    489             if arg.isdigit():
    490                 options[OPTIONS.ITERATIONS] = int(arg)
    491             else:
    492                 _usage_and_exit()
    493         elif opt in ('-m', '--%s' % OPTIONS.MODE):
    494             arg = arg.lower()
    495             if arg in MODE.GESTURE_PLAY_MODE:
    496                 options[OPTIONS.MODE] = arg
    497             else:
    498                 print 'Warning: -m should be one of %s' % MODE.GESTURE_PLAY_MODE
    499         elif opt in ('--%s' % OPTIONS.REPLAY, '--%s' % OPTIONS.RESUME):
    500             log_dir = os.path.join(conf.log_root_dir, arg)
    501             if os.path.isdir(log_dir):
    502                 # opt could be either '--replay' or '--resume'.
    503                 # We would like to strip off the '-' on the left hand side.
    504                 options[opt.lstrip('-')] = log_dir
    505             else:
    506                 print 'Error: the log directory "%s" does not exist.' % log_dir
    507                 _usage_and_exit()
    508         elif opt in ('-s', '--%s' % OPTIONS.SIMPLIFIED):
    509             options[OPTIONS.SIMPLIFIED] = True
    510         elif opt in ('--%s' % OPTIONS.SKIP_HTML,):
    511             options[OPTIONS.SKIP_HTML] = True
    512         elif opt in ('-t', '--%s' % OPTIONS.TOUCHSCREEN):
    513             options[OPTIONS.TOUCHSCREEN] = True
    514         elif opt in ('-u', '--%s' % OPTIONS.UPLOAD):
    515             upload_to_gs(arg)
    516             sys.exit()
    517         else:
    518             msg = 'This option "%s" is not supported.' % opt
    519             _parsing_error(opt)
    520 
    521     print 'Note: the %s mode is used.' % options[OPTIONS.MODE]
    522     return options
    523 
    524 
    525 if __name__ == '__main__':
    526     options = _parse_options()
    527     fw = firmware_TouchMTB(options)
    528     fw.main()
    529