OLD | NEW |
(Empty) | |
| 1 # Copyright 2014 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 |
| 5 import logging |
| 6 import optparse |
| 7 import os |
| 8 import random |
| 9 import sys |
| 10 import time |
| 11 |
| 12 from telemetry import decorators |
| 13 from telemetry.core import exceptions |
| 14 from telemetry.core import util |
| 15 from telemetry.core import wpr_modes |
| 16 from telemetry.page import shared_page_state |
| 17 from telemetry.page import page_test |
| 18 from telemetry.page.actions import page_action |
| 19 from telemetry.results import results_options |
| 20 from telemetry.user_story import user_story_filter |
| 21 from telemetry.util import cloud_storage |
| 22 from telemetry.util import exception_formatter |
| 23 from telemetry.value import failure |
| 24 from telemetry.value import skip |
| 25 |
| 26 |
| 27 def AddCommandLineArgs(parser): |
| 28 user_story_filter.UserStoryFilter.AddCommandLineArgs(parser) |
| 29 results_options.AddResultsOptions(parser) |
| 30 |
| 31 # Page set options |
| 32 group = optparse.OptionGroup(parser, 'Page set ordering and repeat options') |
| 33 group.add_option('--pageset-shuffle', action='store_true', |
| 34 dest='pageset_shuffle', |
| 35 help='Shuffle the order of pages within a pageset.') |
| 36 group.add_option('--pageset-shuffle-order-file', |
| 37 dest='pageset_shuffle_order_file', default=None, |
| 38 help='Filename of an output of a previously run test on the current ' |
| 39 'pageset. The tests will run in the same order again, overriding ' |
| 40 'what is specified by --page-repeat and --pageset-repeat.') |
| 41 group.add_option('--page-repeat', default=1, type='int', |
| 42 help='Number of times to repeat each individual page ' |
| 43 'before proceeding with the next page in the pageset.') |
| 44 group.add_option('--pageset-repeat', default=1, type='int', |
| 45 help='Number of times to repeat the entire pageset.') |
| 46 group.add_option('--max-failures', default=None, type='int', |
| 47 help='Maximum number of test failures before aborting ' |
| 48 'the run. Defaults to the number specified by the ' |
| 49 'PageTest.') |
| 50 parser.add_option_group(group) |
| 51 |
| 52 # WPR options |
| 53 group = optparse.OptionGroup(parser, 'Web Page Replay options') |
| 54 group.add_option('--use-live-sites', |
| 55 dest='use_live_sites', action='store_true', |
| 56 help='Run against live sites and ignore the Web Page Replay archives.') |
| 57 parser.add_option_group(group) |
| 58 |
| 59 parser.add_option('-d', '--also-run-disabled-tests', |
| 60 dest='run_disabled_tests', |
| 61 action='store_true', default=False, |
| 62 help='Ignore @Disabled and @Enabled restrictions.') |
| 63 |
| 64 def ProcessCommandLineArgs(parser, args): |
| 65 user_story_filter.UserStoryFilter.ProcessCommandLineArgs(parser, args) |
| 66 results_options.ProcessCommandLineArgs(parser, args) |
| 67 |
| 68 # Page set options |
| 69 if args.pageset_shuffle_order_file and not args.pageset_shuffle: |
| 70 parser.error('--pageset-shuffle-order-file requires --pageset-shuffle.') |
| 71 |
| 72 if args.page_repeat < 1: |
| 73 parser.error('--page-repeat must be a positive integer.') |
| 74 if args.pageset_repeat < 1: |
| 75 parser.error('--pageset-repeat must be a positive integer.') |
| 76 |
| 77 |
| 78 def _RunPageAndHandleExceptionIfNeeded(test, page_set, expectations, |
| 79 page, results, state): |
| 80 expectation = None |
| 81 def ProcessError(): |
| 82 if expectation == 'fail': |
| 83 msg = 'Expected exception while running %s' % page.url |
| 84 exception_formatter.PrintFormattedException(msg=msg) |
| 85 else: |
| 86 msg = 'Exception while running %s' % page.url |
| 87 results.AddValue(failure.FailureValue(page, sys.exc_info())) |
| 88 |
| 89 try: |
| 90 state.WillRunPage(page, page_set) |
| 91 expectation, skip_value = state.GetPageExpectationAndSkipValue(expectations) |
| 92 if expectation == 'skip': |
| 93 assert skip_value |
| 94 results.AddValue(skip_value) |
| 95 return |
| 96 state.RunPage(results) |
| 97 except page_test.TestNotSupportedOnPlatformFailure: |
| 98 raise |
| 99 except (page_test.Failure, util.TimeoutException, exceptions.LoginException, |
| 100 exceptions.ProfilingException): |
| 101 ProcessError() |
| 102 except exceptions.AppCrashException: |
| 103 ProcessError() |
| 104 state.TearDown(results) |
| 105 if test.is_multi_tab_test: |
| 106 logging.error('Aborting multi-tab test after browser or tab crashed at ' |
| 107 'page %s' % page.url) |
| 108 test.RequestExit() |
| 109 return |
| 110 except page_action.PageActionNotSupported as e: |
| 111 results.AddValue(skip.SkipValue(page, 'Unsupported page action: %s' % e)) |
| 112 except Exception: |
| 113 exception_formatter.PrintFormattedException( |
| 114 msg='Unhandled exception while running %s' % page.url) |
| 115 results.AddValue(failure.FailureValue(page, sys.exc_info())) |
| 116 else: |
| 117 if expectation == 'fail': |
| 118 logging.warning('%s was expected to fail, but passed.\n', page.url) |
| 119 finally: |
| 120 state.DidRunPage(results) |
| 121 |
| 122 |
| 123 @decorators.Cache |
| 124 def _UpdatePageSetArchivesIfChanged(page_set): |
| 125 # Scan every serving directory for .sha1 files |
| 126 # and download them from Cloud Storage. Assume all data is public. |
| 127 all_serving_dirs = page_set.serving_dirs.copy() |
| 128 # Add individual page dirs to all serving dirs. |
| 129 for page in page_set: |
| 130 if page.is_file: |
| 131 all_serving_dirs.add(page.serving_dir) |
| 132 # Scan all serving dirs. |
| 133 for serving_dir in all_serving_dirs: |
| 134 if os.path.splitdrive(serving_dir)[1] == '/': |
| 135 raise ValueError('Trying to serve root directory from HTTP server.') |
| 136 for dirpath, _, filenames in os.walk(serving_dir): |
| 137 for filename in filenames: |
| 138 path, extension = os.path.splitext( |
| 139 os.path.join(dirpath, filename)) |
| 140 if extension != '.sha1': |
| 141 continue |
| 142 cloud_storage.GetIfChanged(path, page_set.bucket) |
| 143 |
| 144 |
| 145 def Run(test, page_set, expectations, finder_options, results): |
| 146 """Runs a given test against a given page_set with the given options.""" |
| 147 test.ValidatePageSet(page_set) |
| 148 |
| 149 # Reorder page set based on options. |
| 150 pages = _ShuffleAndFilterPageSet(page_set, finder_options) |
| 151 |
| 152 if not finder_options.use_live_sites: |
| 153 if finder_options.browser_options.wpr_mode != wpr_modes.WPR_RECORD: |
| 154 _UpdatePageSetArchivesIfChanged(page_set) |
| 155 pages = _CheckArchives(page_set, pages, results) |
| 156 |
| 157 for page in list(pages): |
| 158 if not test.CanRunForPage(page): |
| 159 results.WillRunPage(page) |
| 160 logging.debug('Skipping test: it cannot run for %s', page.url) |
| 161 results.AddValue(skip.SkipValue(page, 'Test cannot run')) |
| 162 results.DidRunPage(page) |
| 163 pages.remove(page) |
| 164 |
| 165 if not pages: |
| 166 return |
| 167 |
| 168 state = shared_page_state.SharedPageState(test, finder_options, page_set) |
| 169 pages_with_discarded_first_result = set() |
| 170 max_failures = finder_options.max_failures # command-line gets priority |
| 171 if max_failures is None: |
| 172 max_failures = test.max_failures # may be None |
| 173 |
| 174 try: |
| 175 test.WillRunTest(finder_options) |
| 176 for _ in xrange(finder_options.pageset_repeat): |
| 177 for page in pages: |
| 178 if test.IsExiting(): |
| 179 break |
| 180 for _ in xrange(finder_options.page_repeat): |
| 181 results.WillRunPage(page) |
| 182 try: |
| 183 _WaitForThermalThrottlingIfNeeded(state.platform) |
| 184 _RunPageAndHandleExceptionIfNeeded( |
| 185 test, page_set, expectations, page, results, state) |
| 186 except Exception: |
| 187 # Tear down & restart the state for unhandled exceptions thrown by |
| 188 # _RunPageAndHandleExceptionIfNeeded. |
| 189 results.AddValue(failure.FailureValue(page, sys.exc_info())) |
| 190 state.TearDown(results) |
| 191 state = shared_page_state.SharedPageState( |
| 192 test, finder_options, page_set) |
| 193 finally: |
| 194 _CheckThermalThrottling(state.platform) |
| 195 discard_run = (test.discard_first_result and |
| 196 page not in pages_with_discarded_first_result) |
| 197 if discard_run: |
| 198 pages_with_discarded_first_result.add(page) |
| 199 results.DidRunPage(page, discard_run=discard_run) |
| 200 if max_failures is not None and len(results.failures) > max_failures: |
| 201 logging.error('Too many failures. Aborting.') |
| 202 test.RequestExit() |
| 203 finally: |
| 204 state.TearDown(results) |
| 205 |
| 206 |
| 207 def _ShuffleAndFilterPageSet(page_set, finder_options): |
| 208 if finder_options.pageset_shuffle_order_file: |
| 209 return page_set.ReorderPageSet(finder_options.pageset_shuffle_order_file) |
| 210 pages = [page for page in page_set.pages[:] |
| 211 if user_story_filter.UserStoryFilter.IsSelected(page)] |
| 212 if finder_options.pageset_shuffle: |
| 213 random.shuffle(pages) |
| 214 return pages |
| 215 |
| 216 |
| 217 def _CheckArchives(page_set, pages, results): |
| 218 """Returns a subset of pages that are local or have WPR archives. |
| 219 |
| 220 Logs warnings if any are missing. |
| 221 """ |
| 222 # Warn of any problems with the entire page set. |
| 223 if any(not p.is_local for p in pages): |
| 224 if not page_set.archive_data_file: |
| 225 logging.warning('The page set is missing an "archive_data_file" ' |
| 226 'property. Skipping any live sites. To include them, ' |
| 227 'pass the flag --use-live-sites.') |
| 228 if not page_set.wpr_archive_info: |
| 229 logging.warning('The archive info file is missing. ' |
| 230 'To fix this, either add svn-internal to your ' |
| 231 '.gclient using http://goto/read-src-internal, ' |
| 232 'or create a new archive using record_wpr.') |
| 233 |
| 234 # Warn of any problems with individual pages and return valid pages. |
| 235 pages_missing_archive_path = [] |
| 236 pages_missing_archive_data = [] |
| 237 valid_pages = [] |
| 238 for page in pages: |
| 239 if not page.is_local and not page.archive_path: |
| 240 pages_missing_archive_path.append(page) |
| 241 elif not page.is_local and not os.path.isfile(page.archive_path): |
| 242 pages_missing_archive_data.append(page) |
| 243 else: |
| 244 valid_pages.append(page) |
| 245 if pages_missing_archive_path: |
| 246 logging.warning('The page set archives for some pages do not exist. ' |
| 247 'Skipping those pages. To fix this, record those pages ' |
| 248 'using record_wpr. To ignore this warning and run ' |
| 249 'against live sites, pass the flag --use-live-sites.') |
| 250 if pages_missing_archive_data: |
| 251 logging.warning('The page set archives for some pages are missing. ' |
| 252 'Someone forgot to check them in, or they were deleted. ' |
| 253 'Skipping those pages. To fix this, record those pages ' |
| 254 'using record_wpr. To ignore this warning and run ' |
| 255 'against live sites, pass the flag --use-live-sites.') |
| 256 for page in pages_missing_archive_path + pages_missing_archive_data: |
| 257 results.WillRunPage(page) |
| 258 results.AddValue(failure.FailureValue.FromMessage( |
| 259 page, 'Page set archive doesn\'t exist.')) |
| 260 results.DidRunPage(page) |
| 261 return valid_pages |
| 262 |
| 263 |
| 264 def _WaitForThermalThrottlingIfNeeded(platform): |
| 265 if not platform.CanMonitorThermalThrottling(): |
| 266 return |
| 267 thermal_throttling_retry = 0 |
| 268 while (platform.IsThermallyThrottled() and |
| 269 thermal_throttling_retry < 3): |
| 270 logging.warning('Thermally throttled, waiting (%d)...', |
| 271 thermal_throttling_retry) |
| 272 thermal_throttling_retry += 1 |
| 273 time.sleep(thermal_throttling_retry * 2) |
| 274 |
| 275 if thermal_throttling_retry and platform.IsThermallyThrottled(): |
| 276 logging.warning('Device is thermally throttled before running ' |
| 277 'performance tests, results will vary.') |
| 278 |
| 279 |
| 280 def _CheckThermalThrottling(platform): |
| 281 if not platform.CanMonitorThermalThrottling(): |
| 282 return |
| 283 if platform.HasBeenThermallyThrottled(): |
| 284 logging.warning('Device has been thermally throttled during ' |
| 285 'performance tests, results will vary.') |
OLD | NEW |