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