1# Copyright 2012 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import collections 6import copy 7import logging 8import optparse 9import os 10import random 11import sys 12import tempfile 13import time 14 15from telemetry import decorators 16from telemetry.core import browser_finder 17from telemetry.core import browser_info 18from telemetry.core import exceptions 19from telemetry.core import util 20from telemetry.core import wpr_modes 21from telemetry.core.platform.profiler import profiler_finder 22from telemetry.page import cloud_storage 23from telemetry.page import page_filter 24from telemetry.page import page_runner_repeat 25from telemetry.page import page_test 26from telemetry.page.actions import navigate 27from telemetry.page.actions import page_action 28from telemetry.results import results_options 29from telemetry.util import exception_formatter 30 31 32class _RunState(object): 33 def __init__(self): 34 self.browser = None 35 36 self._append_to_existing_wpr = False 37 self._last_archive_path = None 38 self._first_browser = True 39 self.first_page = collections.defaultdict(lambda: True) 40 self.profiler_dir = None 41 self.repeat_state = None 42 43 def StartBrowserIfNeeded(self, test, page_set, page, possible_browser, 44 credentials_path, archive_path, finder_options): 45 started_browser = not self.browser 46 # Create a browser. 47 if not self.browser: 48 test.CustomizeBrowserOptionsForSinglePage(page, finder_options) 49 self.browser = possible_browser.Create() 50 self.browser.credentials.credentials_path = credentials_path 51 52 # Set up WPR path on the new browser. 53 self.browser.SetReplayArchivePath(archive_path, 54 self._append_to_existing_wpr, 55 page_set.make_javascript_deterministic) 56 self._last_archive_path = page.archive_path 57 58 test.WillStartBrowser(self.browser) 59 self.browser.Start() 60 test.DidStartBrowser(self.browser) 61 62 if self._first_browser: 63 self._first_browser = False 64 self.browser.credentials.WarnIfMissingCredentials(page_set) 65 logging.info('OS: %s %s', 66 self.browser.platform.GetOSName(), 67 self.browser.platform.GetOSVersionName()) 68 if self.browser.supports_system_info: 69 system_info = self.browser.GetSystemInfo() 70 if system_info.model_name: 71 logging.info('Model: %s', system_info.model_name) 72 if system_info.gpu: 73 for i, device in enumerate(system_info.gpu.devices): 74 logging.info('GPU device %d: %s', i, device) 75 if system_info.gpu.aux_attributes: 76 logging.info('GPU Attributes:') 77 for k, v in sorted(system_info.gpu.aux_attributes.iteritems()): 78 logging.info(' %-20s: %s', k, v) 79 if system_info.gpu.feature_status: 80 logging.info('Feature Status:') 81 for k, v in sorted(system_info.gpu.feature_status.iteritems()): 82 logging.info(' %-20s: %s', k, v) 83 if system_info.gpu.driver_bug_workarounds: 84 logging.info('Driver Bug Workarounds:') 85 for workaround in system_info.gpu.driver_bug_workarounds: 86 logging.info(' %s', workaround) 87 else: 88 logging.info('No GPU devices') 89 else: 90 # Set up WPR path if it changed. 91 if page.archive_path and self._last_archive_path != page.archive_path: 92 self.browser.SetReplayArchivePath( 93 page.archive_path, 94 self._append_to_existing_wpr, 95 page_set.make_javascript_deterministic) 96 self._last_archive_path = page.archive_path 97 98 if self.browser.supports_tab_control and test.close_tabs_before_run: 99 # Create a tab if there's none. 100 if len(self.browser.tabs) == 0: 101 self.browser.tabs.New() 102 103 # Ensure only one tab is open, unless the test is a multi-tab test. 104 if not test.is_multi_tab_test: 105 while len(self.browser.tabs) > 1: 106 self.browser.tabs[-1].Close() 107 108 # Must wait for tab to commit otherwise it can commit after the next 109 # navigation has begun and RenderFrameHostManager::DidNavigateMainFrame() 110 # will cancel the next navigation because it's pending. This manifests as 111 # the first navigation in a PageSet freezing indefinitly because the 112 # navigation was silently cancelled when |self.browser.tabs[0]| was 113 # committed. Only do this when we just started the browser, otherwise 114 # there are cases where previous pages in a PageSet never complete 115 # loading so we'll wait forever. 116 if started_browser: 117 self.browser.tabs[0].WaitForDocumentReadyStateToBeComplete() 118 119 def StopBrowser(self): 120 if self.browser: 121 self.browser.Close() 122 self.browser = None 123 124 # Restarting the state will also restart the wpr server. If we're 125 # recording, we need to continue adding into the same wpr archive, 126 # not overwrite it. 127 self._append_to_existing_wpr = True 128 129 def StartProfiling(self, page, finder_options): 130 if not self.profiler_dir: 131 self.profiler_dir = tempfile.mkdtemp() 132 output_file = os.path.join(self.profiler_dir, page.file_safe_name) 133 is_repeating = (finder_options.page_repeat != 1 or 134 finder_options.pageset_repeat != 1) 135 if is_repeating: 136 output_file = util.GetSequentialFileName(output_file) 137 self.browser.StartProfiling(finder_options.profiler, output_file) 138 139 def StopProfiling(self): 140 if self.browser: 141 self.browser.StopProfiling() 142 143 144class PageState(object): 145 def __init__(self, page, tab): 146 self.page = page 147 self.tab = tab 148 149 self._did_login = False 150 151 def PreparePage(self, test=None): 152 if self.page.is_file: 153 server_started = self.tab.browser.SetHTTPServerDirectories( 154 self.page.page_set.serving_dirs | set([self.page.serving_dir])) 155 if server_started and test: 156 test.DidStartHTTPServer(self.tab) 157 158 if self.page.credentials: 159 if not self.tab.browser.credentials.LoginNeeded( 160 self.tab, self.page.credentials): 161 raise page_test.Failure('Login as ' + self.page.credentials + ' failed') 162 self._did_login = True 163 164 if test: 165 if test.clear_cache_before_each_run: 166 self.tab.ClearCache(force=True) 167 168 def ImplicitPageNavigation(self, test=None): 169 """Executes the implicit navigation that occurs for every page iteration. 170 171 This function will be called once per page before any actions are executed. 172 """ 173 if test: 174 test.WillNavigateToPage(self.page, self.tab) 175 test.RunNavigateSteps(self.page, self.tab) 176 test.DidNavigateToPage(self.page, self.tab) 177 else: 178 i = navigate.NavigateAction() 179 i.RunAction(self.page, self.tab, None) 180 181 def CleanUpPage(self, test): 182 test.CleanUpAfterPage(self.page, self.tab) 183 if self.page.credentials and self._did_login: 184 self.tab.browser.credentials.LoginNoLongerNeeded( 185 self.tab, self.page.credentials) 186 187 188def AddCommandLineArgs(parser): 189 page_filter.PageFilter.AddCommandLineArgs(parser) 190 results_options.AddResultsOptions(parser) 191 192 # Page set options 193 group = optparse.OptionGroup(parser, 'Page set ordering and repeat options') 194 group.add_option('--pageset-shuffle', action='store_true', 195 dest='pageset_shuffle', 196 help='Shuffle the order of pages within a pageset.') 197 group.add_option('--pageset-shuffle-order-file', 198 dest='pageset_shuffle_order_file', default=None, 199 help='Filename of an output of a previously run test on the current ' 200 'pageset. The tests will run in the same order again, overriding ' 201 'what is specified by --page-repeat and --pageset-repeat.') 202 group.add_option('--page-repeat', default=1, type='int', 203 help='Number of times to repeat each individual page ' 204 'before proceeding with the next page in the pageset.') 205 group.add_option('--pageset-repeat', default=1, type='int', 206 help='Number of times to repeat the entire pageset.') 207 parser.add_option_group(group) 208 209 # WPR options 210 group = optparse.OptionGroup(parser, 'Web Page Replay options') 211 group.add_option('--use-live-sites', 212 dest='use_live_sites', action='store_true', 213 help='Run against live sites and ignore the Web Page Replay archives.') 214 parser.add_option_group(group) 215 216 217def ProcessCommandLineArgs(parser, args): 218 page_filter.PageFilter.ProcessCommandLineArgs(parser, args) 219 220 # Page set options 221 if args.pageset_shuffle_order_file and not args.pageset_shuffle: 222 parser.error('--pageset-shuffle-order-file requires --pageset-shuffle.') 223 224 if args.page_repeat < 1: 225 parser.error('--page-repeat must be a positive integer.') 226 if args.pageset_repeat < 1: 227 parser.error('--pageset-repeat must be a positive integer.') 228 229 230def _PrepareAndRunPage(test, page_set, expectations, finder_options, 231 browser_options, page, credentials_path, 232 possible_browser, results, state): 233 if finder_options.use_live_sites: 234 browser_options.wpr_mode = wpr_modes.WPR_OFF 235 elif browser_options.wpr_mode != wpr_modes.WPR_RECORD: 236 browser_options.wpr_mode = ( 237 wpr_modes.WPR_REPLAY 238 if page.archive_path and os.path.isfile(page.archive_path) 239 else wpr_modes.WPR_OFF) 240 241 tries = test.attempts 242 while tries: 243 tries -= 1 244 try: 245 results_for_current_run = copy.copy(results) 246 results_for_current_run.StartTest(page) 247 if test.RestartBrowserBeforeEachPage() or page.startup_url: 248 state.StopBrowser() 249 # If we are restarting the browser for each page customize the per page 250 # options for just the current page before starting the browser. 251 state.StartBrowserIfNeeded(test, page_set, page, possible_browser, 252 credentials_path, page.archive_path, 253 finder_options) 254 if not page.CanRunOnBrowser(browser_info.BrowserInfo(state.browser)): 255 logging.info('Skip test for page %s because browser is not supported.' 256 % page.url) 257 results_for_current_run.StopTest(page) 258 return results 259 260 expectation = expectations.GetExpectationForPage(state.browser, page) 261 262 _WaitForThermalThrottlingIfNeeded(state.browser.platform) 263 264 if finder_options.profiler: 265 state.StartProfiling(page, finder_options) 266 267 try: 268 _RunPage(test, page, state, expectation, 269 results_for_current_run, finder_options) 270 _CheckThermalThrottling(state.browser.platform) 271 except exceptions.TabCrashException as e: 272 if test.is_multi_tab_test: 273 logging.error('Aborting multi-tab test after tab %s crashed', 274 page.url) 275 raise 276 logging.warning(e) 277 state.StopBrowser() 278 279 if finder_options.profiler: 280 state.StopProfiling() 281 282 if (test.StopBrowserAfterPage(state.browser, page)): 283 state.StopBrowser() 284 285 results_for_current_run.StopTest(page) 286 287 if state.first_page[page]: 288 state.first_page[page] = False 289 if test.discard_first_result: 290 return results 291 return results_for_current_run 292 except exceptions.BrowserGoneException as e: 293 state.StopBrowser() 294 if not tries: 295 logging.error('Aborting after too many retries') 296 raise 297 if test.is_multi_tab_test: 298 logging.error('Aborting multi-tab test after browser crashed') 299 raise 300 logging.warning(e) 301 302 303def _UpdatePageSetArchivesIfChanged(page_set): 304 # Attempt to download the credentials file. 305 if page_set.credentials_path: 306 try: 307 cloud_storage.GetIfChanged( 308 os.path.join(page_set.base_dir, page_set.credentials_path)) 309 except (cloud_storage.CredentialsError, cloud_storage.PermissionError, 310 cloud_storage.CloudStorageError) as e: 311 logging.warning('Cannot retrieve credential file %s due to cloud storage ' 312 'error %s', page_set.credentials_path, str(e)) 313 314 # Scan every serving directory for .sha1 files 315 # and download them from Cloud Storage. Assume all data is public. 316 all_serving_dirs = page_set.serving_dirs.copy() 317 # Add individual page dirs to all serving dirs. 318 for page in page_set: 319 if page.is_file: 320 all_serving_dirs.add(page.serving_dir) 321 # Scan all serving dirs. 322 for serving_dir in all_serving_dirs: 323 if os.path.splitdrive(serving_dir)[1] == '/': 324 raise ValueError('Trying to serve root directory from HTTP server.') 325 for dirpath, _, filenames in os.walk(serving_dir): 326 for filename in filenames: 327 path, extension = os.path.splitext( 328 os.path.join(dirpath, filename)) 329 if extension != '.sha1': 330 continue 331 cloud_storage.GetIfChanged(path) 332 333 334def Run(test, page_set, expectations, finder_options): 335 """Runs a given test against a given page_set with the given options.""" 336 results = results_options.PrepareResults(test, finder_options) 337 338 test.ValidatePageSet(page_set) 339 340 # Create a possible_browser with the given options. 341 try: 342 possible_browser = browser_finder.FindBrowser(finder_options) 343 except browser_finder.BrowserTypeRequiredException, e: 344 sys.stderr.write(str(e) + '\n') 345 sys.exit(-1) 346 if not possible_browser: 347 sys.stderr.write( 348 'No browser found. Available browsers:\n' + 349 '\n'.join(browser_finder.GetAllAvailableBrowserTypes(finder_options)) + 350 '\n') 351 sys.exit(-1) 352 353 browser_options = possible_browser.finder_options.browser_options 354 browser_options.browser_type = possible_browser.browser_type 355 browser_options.platform = possible_browser.platform 356 test.CustomizeBrowserOptions(browser_options) 357 358 if not decorators.IsEnabled( 359 test, browser_options.browser_type, browser_options.platform): 360 return results 361 362 # Reorder page set based on options. 363 pages = _ShuffleAndFilterPageSet(page_set, finder_options) 364 365 if (not finder_options.use_live_sites and 366 browser_options.wpr_mode != wpr_modes.WPR_RECORD): 367 _UpdatePageSetArchivesIfChanged(page_set) 368 pages = _CheckArchives(page_set, pages, results) 369 370 # Verify credentials path. 371 credentials_path = None 372 if page_set.credentials_path: 373 credentials_path = os.path.join(os.path.dirname(page_set.file_path), 374 page_set.credentials_path) 375 if not os.path.exists(credentials_path): 376 credentials_path = None 377 378 # Set up user agent. 379 browser_options.browser_user_agent_type = page_set.user_agent_type or None 380 381 if finder_options.profiler: 382 profiler_class = profiler_finder.FindProfiler(finder_options.profiler) 383 profiler_class.CustomizeBrowserOptions(browser_options.browser_type, 384 finder_options) 385 386 for page in list(pages): 387 if not test.CanRunForPage(page): 388 logging.debug('Skipping test: it cannot run for %s', page.url) 389 results.AddSkip(page, 'Test cannot run') 390 pages.remove(page) 391 392 if not pages: 393 return results 394 395 state = _RunState() 396 # TODO(dtu): Move results creation and results_for_current_run into RunState. 397 398 try: 399 test.WillRunTest(finder_options) 400 state.repeat_state = page_runner_repeat.PageRunnerRepeatState( 401 finder_options) 402 403 state.repeat_state.WillRunPageSet() 404 while state.repeat_state.ShouldRepeatPageSet() and not test.IsExiting(): 405 for page in pages: 406 state.repeat_state.WillRunPage() 407 test.WillRunPageRepeats(page) 408 while state.repeat_state.ShouldRepeatPage(): 409 results = _PrepareAndRunPage( 410 test, page_set, expectations, finder_options, browser_options, 411 page, credentials_path, possible_browser, results, state) 412 state.repeat_state.DidRunPage() 413 test.DidRunPageRepeats(page) 414 if (not test.max_failures is None and 415 len(results.failures) > test.max_failures): 416 logging.error('Too many failures. Aborting.') 417 test.RequestExit() 418 if (not test.max_errors is None and 419 len(results.errors) > test.max_errors): 420 logging.error('Too many errors. Aborting.') 421 test.RequestExit() 422 if test.IsExiting(): 423 break 424 state.repeat_state.DidRunPageSet() 425 426 test.DidRunTest(state.browser, results) 427 finally: 428 state.StopBrowser() 429 430 return results 431 432 433def _ShuffleAndFilterPageSet(page_set, finder_options): 434 if finder_options.pageset_shuffle_order_file: 435 return page_set.ReorderPageSet(finder_options.pageset_shuffle_order_file) 436 437 pages = [page for page in page_set.pages[:] 438 if not page.disabled and page_filter.PageFilter.IsSelected(page)] 439 440 if finder_options.pageset_shuffle: 441 random.Random().shuffle(pages) 442 443 return pages 444 445 446def _CheckArchives(page_set, pages, results): 447 """Returns a subset of pages that are local or have WPR archives. 448 449 Logs warnings if any are missing.""" 450 page_set_has_live_sites = False 451 for page in pages: 452 if not page.is_local: 453 page_set_has_live_sites = True 454 break 455 456 # Potential problems with the entire page set. 457 if page_set_has_live_sites: 458 if not page_set.archive_data_file: 459 logging.warning('The page set is missing an "archive_data_file" ' 460 'property. Skipping any live sites. To include them, ' 461 'pass the flag --use-live-sites.') 462 if not page_set.wpr_archive_info: 463 logging.warning('The archive info file is missing. ' 464 'To fix this, either add svn-internal to your ' 465 '.gclient using http://goto/read-src-internal, ' 466 'or create a new archive using record_wpr.') 467 468 # Potential problems with individual pages. 469 pages_missing_archive_path = [] 470 pages_missing_archive_data = [] 471 472 for page in pages: 473 if page.is_local: 474 continue 475 476 if not page.archive_path: 477 pages_missing_archive_path.append(page) 478 elif not os.path.isfile(page.archive_path): 479 pages_missing_archive_data.append(page) 480 481 if pages_missing_archive_path: 482 logging.warning('The page set archives for some pages do not exist. ' 483 'Skipping those pages. To fix this, record those pages ' 484 'using record_wpr. To ignore this warning and run ' 485 'against live sites, pass the flag --use-live-sites.') 486 if pages_missing_archive_data: 487 logging.warning('The page set archives for some pages are missing. ' 488 'Someone forgot to check them in, or they were deleted. ' 489 'Skipping those pages. To fix this, record those pages ' 490 'using record_wpr. To ignore this warning and run ' 491 'against live sites, pass the flag --use-live-sites.') 492 493 for page in pages_missing_archive_path + pages_missing_archive_data: 494 results.StartTest(page) 495 results.AddErrorMessage(page, 'Page set archive doesn\'t exist.') 496 results.StopTest(page) 497 498 return [page for page in pages if page not in 499 pages_missing_archive_path + pages_missing_archive_data] 500 501 502def _RunPage(test, page, state, expectation, results, finder_options): 503 if expectation == 'skip': 504 logging.debug('Skipping test: Skip expectation for %s', page.url) 505 results.AddSkip(page, 'Skipped by test expectations') 506 return 507 508 logging.info('Running %s', page.url) 509 510 page_state = PageState(page, test.TabForPage(page, state.browser)) 511 512 def ProcessError(): 513 if expectation == 'fail': 514 msg = 'Expected exception while running %s' % page.url 515 results.AddSuccess(page) 516 else: 517 msg = 'Exception while running %s' % page.url 518 results.AddError(page, sys.exc_info()) 519 exception_formatter.PrintFormattedException(msg=msg) 520 521 try: 522 page_state.PreparePage(test) 523 if state.repeat_state.ShouldNavigate( 524 finder_options.skip_navigate_on_repeat): 525 page_state.ImplicitPageNavigation(test) 526 test.RunPage(page, page_state.tab, results) 527 util.CloseConnections(page_state.tab) 528 except page_test.TestNotSupportedOnPlatformFailure: 529 raise 530 except page_test.Failure: 531 if expectation == 'fail': 532 exception_formatter.PrintFormattedException( 533 msg='Expected failure while running %s' % page.url) 534 results.AddSuccess(page) 535 else: 536 exception_formatter.PrintFormattedException( 537 msg='Failure while running %s' % page.url) 538 results.AddFailure(page, sys.exc_info()) 539 except (util.TimeoutException, exceptions.LoginException, 540 exceptions.ProfilingException): 541 ProcessError() 542 except (exceptions.TabCrashException, exceptions.BrowserGoneException): 543 ProcessError() 544 # Run() catches these exceptions to relaunch the tab/browser, so re-raise. 545 raise 546 except page_action.PageActionNotSupported as e: 547 results.AddSkip(page, 'Unsupported page action: %s' % e) 548 except Exception: 549 exception_formatter.PrintFormattedException( 550 msg='Unhandled exception while running %s' % page.url) 551 results.AddFailure(page, sys.exc_info()) 552 else: 553 if expectation == 'fail': 554 logging.warning('%s was expected to fail, but passed.\n', page.url) 555 results.AddSuccess(page) 556 finally: 557 page_state.CleanUpPage(test) 558 559 560def _WaitForThermalThrottlingIfNeeded(platform): 561 if not platform.CanMonitorThermalThrottling(): 562 return 563 thermal_throttling_retry = 0 564 while (platform.IsThermallyThrottled() and 565 thermal_throttling_retry < 3): 566 logging.warning('Thermally throttled, waiting (%d)...', 567 thermal_throttling_retry) 568 thermal_throttling_retry += 1 569 time.sleep(thermal_throttling_retry * 2) 570 571 if thermal_throttling_retry and platform.IsThermallyThrottled(): 572 logging.warning('Device is thermally throttled before running ' 573 'performance tests, results will vary.') 574 575 576def _CheckThermalThrottling(platform): 577 if not platform.CanMonitorThermalThrottling(): 578 return 579 if platform.HasBeenThermallyThrottled(): 580 logging.warning('Device has been thermally throttled during ' 581 'performance tests, results will vary.') 582