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 
    318 def upload_to_gs(log_dir):
    319     """Upload the gesture event files specified in log_dir to Google cloud
    320     storage server.
    321 
    322     @param log_dir: the log directory of which the gesture event files are
    323             to be uploaded to Google cloud storage server
    324     """
    325     # Set up gsutil package.
    326     # The board argument is used to locate the proper bucket directory
    327     gs = cros_gs.CrosGs(firmware_utils.get_board())
    328 
    329     log_path = os.path.join(conf.log_root_dir, log_dir)
    330     if not os.path.isdir(log_path):
    331         print_and_exit('Error: the log path "%s" does not exist.' % log_path)
    332 
    333     print 'Uploading "%s" to %s ...\n' % (log_path, gs.bucket)
    334     try:
    335         gs.upload(log_path)
    336     except Exception, e:
    337         msg = 'Error in uploading event files in %s: %s.'
    338         print_and_exit(msg % (log_path, e))
    339 
    340 
    341 def _usage_and_exit():
    342     """Print the usage of this program."""
    343     print 'Usage: $ DISPLAY=:0 [OPTIONS="options"] python %s\n' % sys.argv[0]
    344     print 'options:'
    345     print '  -d, --%s' % OPTIONS.DEVICE
    346     print '        use the system device for replay'
    347     print '  -h, --%s' % OPTIONS.HELP
    348     print '        show this help'
    349     print '  -i, --%s iterations' % OPTIONS.ITERATIONS
    350     print '        specify the number of iterations'
    351     print '  -f, --%s' % OPTIONS.FNGENERATOR
    352     print '        Indicate that (despite not having a touchbot) there is a'
    353     print '        function generator attached for the noise tests'
    354     print '  -m, --%s mode' % OPTIONS.MODE
    355     print '        specify the gesture playing mode'
    356     print '        mode could be one of the following options'
    357     print '            calibration: conducting pressure calibration'
    358     print '            complete: all gestures including those in ' \
    359                                 'both manual mode and robot mode'
    360     print '            manual: all gestures minus gestures in robot mode'
    361     print '            noise: an extensive, 4 hour noise test'
    362     print '            robot: using robot to perform gestures automatically'
    363     print '            robot_sim: robot simulation, for developer only'
    364     print '  --%s log_dir' % OPTIONS.REPLAY
    365     print '        Replay the gesture files and get the test results.'
    366     print '        log_dir is a log sub-directory in %s' % conf.log_root_dir
    367     print '  --%s log_dir' % OPTIONS.RESUME
    368     print '        Resume recording the gestures files in the log_dir.'
    369     print '        log_dir is a log sub-directory in %s' % conf.log_root_dir
    370     print '  -s, --%s' % OPTIONS.SIMPLIFIED
    371     print '        Use one variation per gesture'
    372     print '  --%s' % OPTIONS.SKIP_HTML
    373     print '        Do not show the html test result.'
    374     print '  -t, --%s' % OPTIONS.TOUCHSCREEN
    375     print '        Use the touchscreen instead of a touchpad'
    376     print '  -u, --%s log_dir' % OPTIONS.UPLOAD
    377     print '        Upload the gesture event files in the specified log_dir '
    378     print '        to Google cloud storage server.'
    379     print '        It uploads results that you already have from a previous run'
    380     print '        without re-running the test.'
    381     print '        log_dir could be either '
    382     print '        (1) a directory in %s' % conf.log_root_dir
    383     print '        (2) a full path, or'
    384     print '        (3) the default "latest" directory in %s if omitted' % \
    385                    conf.log_root_dir
    386     print
    387     print 'Example:'
    388     print '  # Use the robot to perform 3 iterations of the robot gestures.'
    389     print '  $ DISPLAY=:0 OPTIONS="-m robot_sim -i 3" python main.py\n'
    390     print '  # Perform 1 iteration of the manual gestures.'
    391     print '  $ DISPLAY=:0 OPTIONS="-m manual" python main.py\n'
    392     print '  # Perform 1 iteration of all manual and robot gestures.'
    393     print '  $ DISPLAY=:0 OPTIONS="-m complete" python main.py\n'
    394     print '  # Perform pressure calibration.'
    395     print '  $ DISPLAY=:0 OPTIONS="-m calibration" python main.py\n'
    396     print '  # Use the robot to perform a latency test with Quickstep'
    397     print '  $ DISPLAY=:0 OPTIONS="-m quickstep" python main.py\n'
    398     print '  # Use the robot to perform an extensive, 4 hour noise test'
    399     print '  $ DISPLAY=:0 OPTIONS="-m noise" python main.py\n'
    400     print '  # Replay the gesture files in the latest log directory.'
    401     print '  $ DISPLAY=:0 OPTIONS="--replay latest" python main.py\n'
    402     example_log_dir = '20130226_040802-fw_1.2-manual'
    403     print ('  # Replay the gesture files in %s/%s with a mocked device.' %
    404             (conf.log_root_dir, example_log_dir))
    405     print '  $ DISPLAY=:0 OPTIONS="--replay %s" python main.py\n' % \
    406             example_log_dir
    407     print ('  # Replay the gesture files in %s/%s with the system device.' %
    408             (conf.log_root_dir, example_log_dir))
    409     print ('  $ DISPLAY=:0 OPTIONS="--replay %s -d" python main.py\n' %
    410             example_log_dir)
    411     print '  # Resume recording the gestures in the latest log directory.'
    412     print '  $ DISPLAY=:0 OPTIONS="--resume latest" python main.py\n'
    413     print '  # Resume recording the gestures in %s/%s.' % (conf.log_root_dir,
    414                                                            example_log_dir)
    415     print '  $ DISPLAY=:0 OPTIONS="--resume %s" python main.py\n' % \
    416             example_log_dir
    417     print ('  # Upload the gesture event files specified in the log_dir '
    418              'to Google cloud storage server.')
    419     print ('  $ DISPLAY=:0 OPTIONS="-u 20130701_020120-fw_11.23-complete" '
    420            'python main.py\n')
    421     print ('  # Upload the gesture event files in the "latest" directory '
    422            'to Google cloud storage server.')
    423     print '  $ DISPLAY=:0 OPTIONS="-u latest" python main.py\n'
    424 
    425     sys.exit(1)
    426 
    427 
    428 def _parsing_error(msg):
    429     """Print the usage and exit when encountering parsing error."""
    430     print 'Error: %s' % msg
    431     _usage_and_exit()
    432 
    433 
    434 def _parse_options():
    435     """Parse the options.
    436 
    437     Note that the options are specified with environment variable OPTIONS,
    438     because pyauto seems not compatible with command line options.
    439     """
    440     # Set the default values of options.
    441     options = {OPTIONS.DEVICE: False,
    442                OPTIONS.FNGENERATOR: False,
    443                OPTIONS.ITERATIONS: 1,
    444                OPTIONS.MODE: MODE.MANUAL,
    445                OPTIONS.REPLAY: None,
    446                OPTIONS.RESUME: None,
    447                OPTIONS.SIMPLIFIED: False,
    448                OPTIONS.SKIP_HTML: False,
    449                OPTIONS.TOUCHSCREEN: False,
    450                OPTIONS.UPLOAD: None,
    451     }
    452 
    453     # Get the command line options or get the options from environment OPTIONS
    454     options_list = sys.argv[1:] or os.environ.get('OPTIONS', '').split()
    455     if not options_list:
    456         return options
    457 
    458     short_opt = 'dfhi:m:stu:'
    459     long_opt = [OPTIONS.DEVICE,
    460                 OPTIONS.FNGENERATOR,
    461                 OPTIONS.HELP,
    462                 OPTIONS.ITERATIONS + '=',
    463                 OPTIONS.MODE + '=',
    464                 OPTIONS.REPLAY + '=',
    465                 OPTIONS.RESUME + '=',
    466                 OPTIONS.SIMPLIFIED,
    467                 OPTIONS.SKIP_HTML,
    468                 OPTIONS.TOUCHSCREEN,
    469                 OPTIONS.UPLOAD + '=',
    470     ]
    471     try:
    472         opts, args = getopt.getopt(options_list, short_opt, long_opt)
    473     except getopt.GetoptError, err:
    474         _parsing_error(str(err))
    475 
    476     for opt, arg in opts:
    477         if opt in ('-d', '--%s' % OPTIONS.DEVICE):
    478             options[OPTIONS.DEVICE] = True
    479         if opt in ('-f', '--%s' % OPTIONS.FNGENERATOR):
    480             options[OPTIONS.FNGENERATOR] = True
    481         elif opt in ('-h', '--%s' % OPTIONS.HELP):
    482             _usage_and_exit()
    483         elif opt in ('-i', '--%s' % OPTIONS.ITERATIONS):
    484             if arg.isdigit():
    485                 options[OPTIONS.ITERATIONS] = int(arg)
    486             else:
    487                 _usage_and_exit()
    488         elif opt in ('-m', '--%s' % OPTIONS.MODE):
    489             arg = arg.lower()
    490             if arg in MODE.GESTURE_PLAY_MODE:
    491                 options[OPTIONS.MODE] = arg
    492             else:
    493                 print 'Warning: -m should be one of %s' % MODE.GESTURE_PLAY_MODE
    494         elif opt in ('--%s' % OPTIONS.REPLAY, '--%s' % OPTIONS.RESUME):
    495             log_dir = os.path.join(conf.log_root_dir, arg)
    496             if os.path.isdir(log_dir):
    497                 # opt could be either '--replay' or '--resume'.
    498                 # We would like to strip off the '-' on the left hand side.
    499                 options[opt.lstrip('-')] = log_dir
    500             else:
    501                 print 'Error: the log directory "%s" does not exist.' % log_dir
    502                 _usage_and_exit()
    503         elif opt in ('-s', '--%s' % OPTIONS.SIMPLIFIED):
    504             options[OPTIONS.SIMPLIFIED] = True
    505         elif opt in ('--%s' % OPTIONS.SKIP_HTML,):
    506             options[OPTIONS.SKIP_HTML] = True
    507         elif opt in ('-t', '--%s' % OPTIONS.TOUCHSCREEN):
    508             options[OPTIONS.TOUCHSCREEN] = True
    509         elif opt in ('-u', '--%s' % OPTIONS.UPLOAD):
    510             upload_to_gs(arg)
    511             sys.exit()
    512         else:
    513             msg = 'This option "%s" is not supported.' % opt
    514             _parsing_error(opt)
    515 
    516     print 'Note: the %s mode is used.' % options[OPTIONS.MODE]
    517     return options
    518 
    519 
    520 if __name__ == '__main__':
    521     options = _parse_options()
    522     fw = firmware_TouchMTB(options)
    523     fw.main()
    524