page_runner.py revision 3551c9c881056c480085172ff9840cab31610854
1# Copyright (c) 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. 4import collections 5import glob 6import logging 7import os 8import sys 9import tempfile 10import time 11import traceback 12import random 13 14from telemetry.core import browser_finder 15from telemetry.core import exceptions 16from telemetry.core import util 17from telemetry.core import wpr_modes 18from telemetry.core.platform.profiler import profiler_finder 19from telemetry.page import page_filter as page_filter_module 20from telemetry.page import page_measurement_results 21from telemetry.page import page_runner_repeat 22from telemetry.page import page_test 23from telemetry.page import results_options 24from telemetry.page.actions import navigate 25 26 27class _RunState(object): 28 def __init__(self): 29 self.browser = None 30 self.tab = None 31 32 self._append_to_existing_wpr = False 33 self._last_archive_path = None 34 self._first_browser = True 35 self.first_page = collections.defaultdict(lambda: True) 36 self.profiler_dir = None 37 self.repeat_state = None 38 39 def StartBrowser(self, test, page_set, page, possible_browser, 40 credentials_path, archive_path): 41 # Create a browser. 42 if not self.browser: 43 assert not self.tab 44 self.browser = possible_browser.Create() 45 self.browser.credentials.credentials_path = credentials_path 46 47 test.WillStartBrowser(self.browser) 48 self.browser.Start() 49 test.DidStartBrowser(self.browser) 50 51 if self._first_browser: 52 self._first_browser = False 53 self.browser.credentials.WarnIfMissingCredentials(page_set) 54 55 # Set up WPR path on the new browser. 56 self.browser.SetReplayArchivePath(archive_path, 57 self._append_to_existing_wpr, 58 page_set.make_javascript_deterministic) 59 self._last_archive_path = page.archive_path 60 else: 61 # Set up WPR path if it changed. 62 if page.archive_path and self._last_archive_path != page.archive_path: 63 self.browser.SetReplayArchivePath( 64 page.archive_path, 65 self._append_to_existing_wpr, 66 page_set.make_javascript_deterministic) 67 self._last_archive_path = page.archive_path 68 69 if self.browser.supports_tab_control: 70 # Create a tab if there's none. 71 if len(self.browser.tabs) == 0: 72 self.browser.tabs.New() 73 74 # Ensure only one tab is open. 75 while len(self.browser.tabs) > 1: 76 self.browser.tabs[-1].Close() 77 78 if not self.tab: 79 self.tab = self.browser.tabs[0] 80 81 if self.first_page[page]: 82 self.first_page[page] = False 83 84 def StopBrowser(self): 85 if self.tab: 86 self.tab.Disconnect() 87 self.tab = None 88 89 if self.browser: 90 self.browser.Close() 91 self.browser = None 92 93 # Restarting the state will also restart the wpr server. If we're 94 # recording, we need to continue adding into the same wpr archive, 95 # not overwrite it. 96 self._append_to_existing_wpr = True 97 98 def StartProfiling(self, page, options): 99 if not self.profiler_dir: 100 self.profiler_dir = tempfile.mkdtemp() 101 output_file = os.path.join(self.profiler_dir, page.url_as_file_safe_name) 102 if options.repeat_options.IsRepeating(): 103 output_file = _GetSequentialFileName(output_file) 104 self.browser.StartProfiling(options.profiler, output_file) 105 106 def StopProfiling(self): 107 self.browser.StopProfiling() 108 109 110class PageState(object): 111 def __init__(self): 112 self._did_login = False 113 114 def PreparePage(self, page, tab, test=None): 115 if page.is_file: 116 serving_dirs = page.serving_dirs_and_file[0] 117 if tab.browser.SetHTTPServerDirectories(serving_dirs) and test: 118 test.DidStartHTTPServer(tab) 119 120 if page.credentials: 121 if not tab.browser.credentials.LoginNeeded(tab, page.credentials): 122 raise page_test.Failure('Login as ' + page.credentials + ' failed') 123 self._did_login = True 124 125 if test: 126 if test.clear_cache_before_each_run: 127 tab.ClearCache() 128 129 def ImplicitPageNavigation(self, page, tab, test=None): 130 """Executes the implicit navigation that occurs for every page iteration. 131 132 This function will be called once per page before any actions are executed. 133 """ 134 if test: 135 test.WillNavigateToPage(page, tab) 136 test.RunNavigateSteps(page, tab) 137 test.DidNavigateToPage(page, tab) 138 else: 139 i = navigate.NavigateAction() 140 i.RunAction(page, tab, None) 141 page.WaitToLoad(tab, 60) 142 143 def CleanUpPage(self, page, tab): 144 if page.credentials and self._did_login: 145 tab.browser.credentials.LoginNoLongerNeeded(tab, page.credentials) 146 147 148def AddCommandLineOptions(parser): 149 page_filter_module.PageFilter.AddCommandLineOptions(parser) 150 results_options.AddResultsOptions(parser) 151 152 153def _LogStackTrace(title, browser): 154 if browser: 155 stack_trace = browser.GetStackTrace() 156 else: 157 stack_trace = 'Browser object is empty, no stack trace.' 158 stack_trace = (('\nStack Trace:\n') + 159 ('*' * 80) + 160 '\n\t' + stack_trace.replace('\n', '\n\t') + '\n' + 161 ('*' * 80)) 162 logging.warning('%s%s', title, stack_trace) 163 164 165def _PrepareAndRunPage(test, page_set, expectations, options, page, 166 credentials_path, possible_browser, results, state): 167 if options.wpr_mode != wpr_modes.WPR_RECORD: 168 if page.archive_path and os.path.isfile(page.archive_path): 169 possible_browser.options.wpr_mode = wpr_modes.WPR_REPLAY 170 else: 171 possible_browser.options.wpr_mode = wpr_modes.WPR_OFF 172 results_for_current_run = results 173 if state.first_page[page] and test.discard_first_result: 174 # If discarding results, substitute a dummy object. 175 results_for_current_run = page_measurement_results.PageMeasurementResults() 176 results_for_current_run.StartTest(page) 177 tries = 3 178 while tries: 179 try: 180 state.StartBrowser(test, page_set, page, possible_browser, 181 credentials_path, page.archive_path) 182 183 expectation = expectations.GetExpectationForPage(state.browser, page) 184 185 _WaitForThermalThrottlingIfNeeded(state.browser.platform) 186 187 if options.profiler: 188 state.StartProfiling(page, options) 189 190 try: 191 _RunPage(test, page, state, expectation, 192 results_for_current_run, options) 193 _CheckThermalThrottling(state.browser.platform) 194 except exceptions.TabCrashException: 195 _LogStackTrace('Tab crashed: %s' % page.url, state.browser) 196 state.StopBrowser() 197 198 if options.profiler: 199 state.StopProfiling() 200 201 if test.NeedsBrowserRestartAfterEachRun(state.tab): 202 state.StopBrowser() 203 204 break 205 except exceptions.BrowserGoneException: 206 _LogStackTrace('Browser crashed', state.browser) 207 logging.warning('Lost connection to browser. Retrying.') 208 state.StopBrowser() 209 tries -= 1 210 if not tries: 211 logging.error('Lost connection to browser 3 times. Failing.') 212 raise 213 results_for_current_run.StopTest(page) 214 215 216def Run(test, page_set, expectations, options): 217 """Runs a given test against a given page_set with the given options.""" 218 results = results_options.PrepareResults(test, options) 219 220 # Create a possible_browser with the given options. 221 test.CustomizeBrowserOptions(options) 222 if options.profiler: 223 profiler_class = profiler_finder.FindProfiler(options.profiler) 224 profiler_class.CustomizeBrowserOptions(options) 225 try: 226 possible_browser = browser_finder.FindBrowser(options) 227 except browser_finder.BrowserTypeRequiredException, e: 228 sys.stderr.write(str(e) + '\n') 229 sys.exit(1) 230 if not possible_browser: 231 sys.stderr.write( 232 'No browser found. Available browsers:\n' + 233 '\n'.join(browser_finder.GetAllAvailableBrowserTypes(options)) + '\n') 234 sys.exit(1) 235 236 # Reorder page set based on options. 237 pages = _ShuffleAndFilterPageSet(page_set, options) 238 239 if (not options.allow_live_sites and 240 options.wpr_mode != wpr_modes.WPR_RECORD): 241 pages = _CheckArchives(page_set, pages, results) 242 243 # Verify credentials path. 244 credentials_path = None 245 if page_set.credentials_path: 246 credentials_path = os.path.join(os.path.dirname(page_set.file_path), 247 page_set.credentials_path) 248 if not os.path.exists(credentials_path): 249 credentials_path = None 250 251 # Set up user agent. 252 if page_set.user_agent_type: 253 options.browser_user_agent_type = page_set.user_agent_type 254 255 for page in pages: 256 test.CustomizeBrowserOptionsForPage(page, possible_browser.options) 257 258 for page in list(pages): 259 if not test.CanRunForPage(page): 260 logging.warning('Skipping test: it cannot run for %s', page.url) 261 results.AddSkip(page, 'Test cannot run') 262 pages.remove(page) 263 264 if not pages: 265 return results 266 267 state = _RunState() 268 # TODO(dtu): Move results creation and results_for_current_run into RunState. 269 270 try: 271 test.WillRunTest(state.tab) 272 state.repeat_state = page_runner_repeat.PageRunnerRepeatState( 273 options.repeat_options) 274 275 state.repeat_state.WillRunPageSet() 276 while state.repeat_state.ShouldRepeatPageSet(): 277 for page in pages: 278 state.repeat_state.WillRunPage() 279 test.WillRunPageRepeats(page, state.tab) 280 while state.repeat_state.ShouldRepeatPage(): 281 # execute test on page 282 _PrepareAndRunPage(test, page_set, expectations, options, page, 283 credentials_path, possible_browser, results, state) 284 state.repeat_state.DidRunPage() 285 test.DidRunPageRepeats(page, state.tab) 286 state.repeat_state.DidRunPageSet() 287 288 test.DidRunTest(state.tab, results) 289 finally: 290 state.StopBrowser() 291 292 return results 293 294 295def _ShuffleAndFilterPageSet(page_set, options): 296 if options.pageset_shuffle_order_file and not options.pageset_shuffle: 297 raise Exception('--pageset-shuffle-order-file requires --pageset-shuffle.') 298 299 if options.pageset_shuffle_order_file: 300 return page_set.ReorderPageSet(options.pageset_shuffle_order_file) 301 302 page_filter = page_filter_module.PageFilter(options) 303 pages = [page for page in page_set.pages[:] 304 if not page.disabled and page_filter.IsSelected(page)] 305 306 if options.pageset_shuffle: 307 random.Random().shuffle(pages) 308 309 return pages 310 311 312def _CheckArchives(page_set, pages, results): 313 """Returns a subset of pages that are local or have WPR archives. 314 315 Logs warnings if any are missing.""" 316 page_set_has_live_sites = False 317 for page in pages: 318 if not page.is_local: 319 page_set_has_live_sites = True 320 break 321 322 # Potential problems with the entire page set. 323 if page_set_has_live_sites: 324 if not page_set.archive_data_file: 325 logging.warning('The page set is missing an "archive_data_file" ' 326 'property. Skipping any live sites. To include them, ' 327 'pass the flag --allow-live-sites.') 328 if not page_set.wpr_archive_info: 329 logging.warning('The archive info file is missing. ' 330 'To fix this, either add svn-internal to your ' 331 '.gclient using http://goto/read-src-internal, ' 332 'or create a new archive using record_wpr.') 333 334 # Potential problems with individual pages. 335 pages_missing_archive_path = [] 336 pages_missing_archive_data = [] 337 338 for page in pages: 339 if page.is_local: 340 continue 341 342 if not page.archive_path: 343 pages_missing_archive_path.append(page) 344 elif not os.path.isfile(page.archive_path): 345 pages_missing_archive_data.append(page) 346 347 if pages_missing_archive_path: 348 logging.warning('The page set archives for some pages do not exist. ' 349 'Skipping those pages. To fix this, record those pages ' 350 'using record_wpr. To ignore this warning and run ' 351 'against live sites, pass the flag --allow-live-sites.') 352 if pages_missing_archive_data: 353 logging.warning('The page set archives for some pages are missing. ' 354 'Someone forgot to check them in, or they were deleted. ' 355 'Skipping those pages. To fix this, record those pages ' 356 'using record_wpr. To ignore this warning and run ' 357 'against live sites, pass the flag --allow-live-sites.') 358 359 for page in pages_missing_archive_path + pages_missing_archive_data: 360 results.StartTest(page) 361 results.AddErrorMessage(page, 'Page set archive doesn\'t exist.') 362 results.StopTest(page) 363 364 return [page for page in pages if page not in 365 pages_missing_archive_path + pages_missing_archive_data] 366 367 368def _RunPage(test, page, state, expectation, results, options): 369 logging.info('Running %s' % page.url) 370 371 page_state = PageState() 372 tab = state.tab 373 374 def ProcessError(): 375 logging.error('%s:\n%s', page.url, traceback.format_exc()) 376 if expectation == 'fail': 377 logging.info('Error was expected\n') 378 results.AddSuccess(page) 379 else: 380 results.AddError(page, sys.exc_info()) 381 382 try: 383 page_state.PreparePage(page, tab, test) 384 if state.repeat_state.ShouldNavigate(options.skip_navigate_on_repeat): 385 page_state.ImplicitPageNavigation(page, tab, test) 386 test.Run(options, page, tab, results) 387 util.CloseConnections(tab) 388 except page_test.Failure: 389 logging.warning('%s:\n%s', page.url, traceback.format_exc()) 390 if expectation == 'fail': 391 logging.info('Failure was expected\n') 392 results.AddSuccess(page) 393 else: 394 results.AddFailure(page, sys.exc_info()) 395 except (util.TimeoutException, exceptions.LoginException, 396 exceptions.ProfilingException): 397 ProcessError() 398 except (exceptions.TabCrashException, exceptions.BrowserGoneException): 399 ProcessError() 400 # Run() catches these exceptions to relaunch the tab/browser, so re-raise. 401 raise 402 except Exception: 403 raise 404 else: 405 if expectation == 'fail': 406 logging.warning('%s was expected to fail, but passed.\n', page.url) 407 results.AddSuccess(page) 408 finally: 409 page_state.CleanUpPage(page, tab) 410 411 412def _GetSequentialFileName(base_name): 413 """Returns the next sequential file name based on |base_name| and the 414 existing files.""" 415 index = 0 416 while True: 417 output_name = '%s_%03d' % (base_name, index) 418 if not glob.glob(output_name + '.*'): 419 break 420 index = index + 1 421 return output_name 422 423 424def _WaitForThermalThrottlingIfNeeded(platform): 425 if not platform.CanMonitorThermalThrottling(): 426 return 427 thermal_throttling_retry = 0 428 while (platform.IsThermallyThrottled() and 429 thermal_throttling_retry < 3): 430 logging.warning('Thermally throttled, waiting (%d)...', 431 thermal_throttling_retry) 432 thermal_throttling_retry += 1 433 time.sleep(thermal_throttling_retry * 2) 434 435 if thermal_throttling_retry and platform.IsThermallyThrottled(): 436 logging.error('Device is thermally throttled before running ' 437 'performance tests, results will vary.') 438 439 440def _CheckThermalThrottling(platform): 441 if not platform.CanMonitorThermalThrottling(): 442 return 443 if platform.HasBeenThermallyThrottled(): 444 logging.error('Device has been thermally throttled during ' 445 'performance tests, results will vary.') 446