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 8import getopt 9import glob 10import logging 11import os 12import sys 13 14import common 15import cros_gs 16import 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. 20if not firmware_utils.install_pygtk(): 21 sys.exit(1) 22 23import firmware_window 24import keyboard_device 25import mtb 26import test_conf as conf 27import test_flow 28import touch_device 29import validators 30 31from common_util import print_and_exit 32from firmware_constants import MODE, OPTIONS 33from report_html import ReportHtml 34 35 36def _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 53class 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 323def 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 346def _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 433def _parsing_error(msg): 434 """Print the usage and exit when encountering parsing error.""" 435 print 'Error: %s' % msg 436 _usage_and_exit() 437 438 439def _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 525if __name__ == '__main__': 526 options = _parse_options() 527 fw = firmware_TouchMTB(options) 528 fw.main() 529