| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import logging | 5 import logging |
| 6 import optparse | 6 import optparse |
| 7 import os | 7 import os |
| 8 import random | 8 import random |
| 9 import sys | 9 import sys |
| 10 import time | 10 import time |
| 11 | 11 |
| 12 from telemetry import decorators | 12 from telemetry import decorators |
| 13 from telemetry.core import exceptions | 13 from telemetry.core import exceptions |
| 14 from telemetry.core import util | 14 from telemetry.core import util |
| 15 from telemetry.core import wpr_modes | 15 from telemetry.core import wpr_modes |
| 16 from telemetry.page import shared_page_state | 16 from telemetry.page import shared_page_state |
| 17 from telemetry.page import page_set as page_set_module |
| 17 from telemetry.page import page_test | 18 from telemetry.page import page_test |
| 18 from telemetry.page.actions import page_action | 19 from telemetry.page.actions import page_action |
| 19 from telemetry.results import results_options | 20 from telemetry.results import results_options |
| 20 from telemetry.user_story import user_story_filter | 21 from telemetry.user_story import user_story_filter |
| 21 from telemetry.util import cloud_storage | 22 from telemetry.util import cloud_storage |
| 22 from telemetry.util import exception_formatter | 23 from telemetry.util import exception_formatter |
| 23 from telemetry.value import failure | 24 from telemetry.value import failure |
| 24 from telemetry.value import skip | 25 from telemetry.value import skip |
| 25 | 26 |
| 26 | 27 |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 68 # Page set options | 69 # Page set options |
| 69 if args.pageset_shuffle_order_file and not args.pageset_shuffle: | 70 if args.pageset_shuffle_order_file and not args.pageset_shuffle: |
| 70 parser.error('--pageset-shuffle-order-file requires --pageset-shuffle.') | 71 parser.error('--pageset-shuffle-order-file requires --pageset-shuffle.') |
| 71 | 72 |
| 72 if args.page_repeat < 1: | 73 if args.page_repeat < 1: |
| 73 parser.error('--page-repeat must be a positive integer.') | 74 parser.error('--page-repeat must be a positive integer.') |
| 74 if args.pageset_repeat < 1: | 75 if args.pageset_repeat < 1: |
| 75 parser.error('--pageset-repeat must be a positive integer.') | 76 parser.error('--pageset-repeat must be a positive integer.') |
| 76 | 77 |
| 77 | 78 |
| 78 def _RunPageAndHandleExceptionIfNeeded(test, page_set, expectations, | 79 def _RunUserStoryAndProcessErrorIfNeeded( |
| 79 page, results, state): | 80 test, expectations, user_story, results, state): |
| 80 expectation = None | 81 expectation = None |
| 81 def ProcessError(): | 82 def ProcessError(): |
| 82 if expectation == 'fail': | 83 if expectation == 'fail': |
| 83 msg = 'Expected exception while running %s' % page.url | 84 msg = 'Expected exception while running %s' % user_story.display_name |
| 84 exception_formatter.PrintFormattedException(msg=msg) | 85 exception_formatter.PrintFormattedException(msg=msg) |
| 85 else: | 86 else: |
| 86 msg = 'Exception while running %s' % page.url | 87 msg = 'Exception while running %s' % user_story.display_name |
| 87 results.AddValue(failure.FailureValue(page, sys.exc_info())) | 88 results.AddValue(failure.FailureValue(user_story, sys.exc_info())) |
| 88 | 89 |
| 89 try: | 90 try: |
| 90 state.WillRunPage(page, page_set) | 91 state.WillRunUserStory(user_story) |
| 91 expectation, skip_value = state.GetPageExpectationAndSkipValue(expectations) | 92 expectation, skip_value = state.GetTestExpectationAndSkipValue(expectations) |
| 92 if expectation == 'skip': | 93 if expectation == 'skip': |
| 93 assert skip_value | 94 assert skip_value |
| 94 results.AddValue(skip_value) | 95 results.AddValue(skip_value) |
| 95 return | 96 return |
| 96 state.RunPage(results) | 97 state.RunUserStory(results) |
| 97 except page_test.TestNotSupportedOnPlatformFailure: | 98 except page_test.TestNotSupportedOnPlatformFailure: |
| 98 raise | 99 raise |
| 99 except (page_test.Failure, util.TimeoutException, exceptions.LoginException, | 100 except (page_test.Failure, util.TimeoutException, exceptions.LoginException, |
| 100 exceptions.ProfilingException): | 101 exceptions.ProfilingException): |
| 101 ProcessError() | 102 ProcessError() |
| 102 except exceptions.AppCrashException: | 103 except exceptions.AppCrashException: |
| 103 ProcessError() | 104 ProcessError() |
| 104 state.TearDown(results) | 105 state.TearDownState(results) |
| 105 if test.is_multi_tab_test: | 106 if test.is_multi_tab_test: |
| 106 logging.error('Aborting multi-tab test after browser or tab crashed at ' | 107 logging.error('Aborting multi-tab test after browser or tab crashed at ' |
| 107 'page %s' % page.url) | 108 'user story %s' % user_story.display_name) |
| 108 test.RequestExit() | 109 test.RequestExit() |
| 109 return | 110 return |
| 110 except page_action.PageActionNotSupported as e: | 111 except page_action.PageActionNotSupported as e: |
| 111 results.AddValue(skip.SkipValue(page, 'Unsupported page action: %s' % e)) | 112 results.AddValue( |
| 113 skip.SkipValue(user_story, 'Unsupported page action: %s' % e)) |
| 112 except Exception: | 114 except Exception: |
| 113 exception_formatter.PrintFormattedException( | 115 exception_formatter.PrintFormattedException( |
| 114 msg='Unhandled exception while running %s' % page.url) | 116 msg='Unhandled exception while running %s' % user_story.display_name) |
| 115 results.AddValue(failure.FailureValue(page, sys.exc_info())) | 117 results.AddValue(failure.FailureValue(user_story, sys.exc_info())) |
| 116 else: | 118 else: |
| 117 if expectation == 'fail': | 119 if expectation == 'fail': |
| 118 logging.warning('%s was expected to fail, but passed.\n', page.url) | 120 logging.warning( |
| 121 '%s was expected to fail, but passed.\n', user_story.display_name) |
| 119 finally: | 122 finally: |
| 120 state.DidRunPage(results) | 123 state.DidRunUserStory(results) |
| 121 | 124 |
| 122 | 125 |
| 123 @decorators.Cache | 126 @decorators.Cache |
| 124 def _UpdatePageSetArchivesIfChanged(page_set): | 127 def _UpdateUserStoryArchivesIfChanged(page_set): |
| 125 # Scan every serving directory for .sha1 files | 128 # Scan every serving directory for .sha1 files |
| 126 # and download them from Cloud Storage. Assume all data is public. | 129 # and download them from Cloud Storage. Assume all data is public. |
| 127 all_serving_dirs = page_set.serving_dirs.copy() | 130 all_serving_dirs = page_set.serving_dirs.copy() |
| 128 # Add individual page dirs to all serving dirs. | 131 # Add individual page dirs to all serving dirs. |
| 129 for page in page_set: | 132 for page in page_set: |
| 130 if page.is_file: | 133 if page.is_file: |
| 131 all_serving_dirs.add(page.serving_dir) | 134 all_serving_dirs.add(page.serving_dir) |
| 132 # Scan all serving dirs. | 135 # Scan all serving dirs. |
| 133 for serving_dir in all_serving_dirs: | 136 for serving_dir in all_serving_dirs: |
| 134 if os.path.splitdrive(serving_dir)[1] == '/': | 137 if os.path.splitdrive(serving_dir)[1] == '/': |
| 135 raise ValueError('Trying to serve root directory from HTTP server.') | 138 raise ValueError('Trying to serve root directory from HTTP server.') |
| 136 for dirpath, _, filenames in os.walk(serving_dir): | 139 for dirpath, _, filenames in os.walk(serving_dir): |
| 137 for filename in filenames: | 140 for filename in filenames: |
| 138 path, extension = os.path.splitext( | 141 path, extension = os.path.splitext( |
| 139 os.path.join(dirpath, filename)) | 142 os.path.join(dirpath, filename)) |
| 140 if extension != '.sha1': | 143 if extension != '.sha1': |
| 141 continue | 144 continue |
| 142 cloud_storage.GetIfChanged(path, page_set.bucket) | 145 cloud_storage.GetIfChanged(path, page_set.bucket) |
| 143 | 146 |
| 144 | 147 |
| 145 def Run(test, page_set, expectations, finder_options, results): | 148 class UserStoryGroup(object): |
| 149 def __init__(self, shared_user_story_state_class): |
| 150 self._shared_user_story_state_class = shared_user_story_state_class |
| 151 self._user_stories = [] |
| 152 |
| 153 @property |
| 154 def shared_user_story_state_class(self): |
| 155 return self._shared_user_story_state_class |
| 156 |
| 157 @property |
| 158 def user_stories(self): |
| 159 return self._user_stories |
| 160 |
| 161 def AddUserStory(self, user_story): |
| 162 assert (user_story.shared_user_story_state_class is |
| 163 self._shared_user_story_state_class) |
| 164 self._user_stories.append(user_story) |
| 165 |
| 166 |
| 167 def GetUserStoryGroupsWithSameSharedUserStoryClass(user_story_set): |
| 168 """ Returns a list of user story groups which each contains user stories with |
| 169 the same shared_user_story_state_class. |
| 170 |
| 171 Example: |
| 172 Assume A1, A2, A3 are user stories with same shared user story class, and |
| 173 similar for B1, B2. |
| 174 If their orders in user story set is A1 A2 B1 B2 A3, then the grouping will |
| 175 be [A1 A2] [B1 B2] [A3]. |
| 176 |
| 177 It's purposefully done this way to make sure that order of user |
| 178 stories are the same of that defined in user_story_set. It's recommended that |
| 179 user stories with the same states should be arranged next to each others in |
| 180 user story sets to reduce the overhead of setting up & tearing down the |
| 181 shared user story state. |
| 182 """ |
| 183 user_story_groups = [] |
| 184 user_story_groups.append( |
| 185 UserStoryGroup(user_story_set[0].shared_user_story_state_class)) |
| 186 for user_story in user_story_set: |
| 187 if (user_story.shared_user_story_state_class is not |
| 188 user_story_groups[-1].shared_user_story_state_class): |
| 189 user_story_groups.append( |
| 190 UserStoryGroup(user_story.shared_user_story_state_class)) |
| 191 user_story_groups[-1].AddUserStory(user_story) |
| 192 return user_story_groups |
| 193 |
| 194 |
| 195 def Run(test, user_story_set, expectations, finder_options, results): |
| 146 """Runs a given test against a given page_set with the given options.""" | 196 """Runs a given test against a given page_set with the given options.""" |
| 147 test.ValidatePageSet(page_set) | 197 test.ValidatePageSet(user_story_set) |
| 148 | 198 |
| 149 # Reorder page set based on options. | 199 # Reorder page set based on options. |
| 150 pages = _ShuffleAndFilterPageSet(page_set, finder_options) | 200 user_stories = _ShuffleAndFilterUserStorySet(user_story_set, finder_options) |
| 151 | 201 |
| 152 if not finder_options.use_live_sites: | 202 if (not finder_options.use_live_sites and |
| 153 if finder_options.browser_options.wpr_mode != wpr_modes.WPR_RECORD: | 203 finder_options.browser_options.wpr_mode != wpr_modes.WPR_RECORD and |
| 154 _UpdatePageSetArchivesIfChanged(page_set) | 204 # TODO(nednguyen): also handle these logic for user_story_set in next |
| 155 pages = _CheckArchives(page_set, pages, results) | 205 # patch. |
| 206 isinstance(user_story_set, page_set_module.PageSet)): |
| 207 _UpdateUserStoryArchivesIfChanged(user_story_set) |
| 208 user_stories = _CheckArchives(user_story_set, user_stories, results) |
| 156 | 209 |
| 157 for page in list(pages): | 210 for user_story in list(user_stories): |
| 158 if not test.CanRunForPage(page): | 211 if not test.CanRunForPage(user_story): |
| 159 results.WillRunPage(page) | 212 results.WillRunPage(user_story) |
| 160 logging.debug('Skipping test: it cannot run for %s', page.url) | 213 logging.debug('Skipping test: it cannot run for %s', |
| 161 results.AddValue(skip.SkipValue(page, 'Test cannot run')) | 214 user_story.display_name) |
| 162 results.DidRunPage(page) | 215 results.AddValue(skip.SkipValue(user_story, 'Test cannot run')) |
| 163 pages.remove(page) | 216 results.DidRunPage(user_story) |
| 217 user_stories.remove(user_story) |
| 164 | 218 |
| 165 if not pages: | 219 if not user_stories: |
| 166 return | 220 return |
| 167 | 221 |
| 168 state = shared_page_state.SharedPageState(test, finder_options, page_set) | 222 user_story_with_discarded_first_results = set() |
| 169 pages_with_discarded_first_result = set() | |
| 170 max_failures = finder_options.max_failures # command-line gets priority | 223 max_failures = finder_options.max_failures # command-line gets priority |
| 171 if max_failures is None: | 224 if max_failures is None: |
| 172 max_failures = test.max_failures # may be None | 225 max_failures = test.max_failures # may be None |
| 226 user_story_groups = GetUserStoryGroupsWithSameSharedUserStoryClass( |
| 227 user_stories) |
| 173 | 228 |
| 174 try: | 229 test.WillRunTest(finder_options) |
| 175 test.WillRunTest(finder_options) | 230 state = None |
| 176 for _ in xrange(finder_options.pageset_repeat): | 231 for _ in xrange(finder_options.pageset_repeat): |
| 177 for page in pages: | 232 for group in user_story_groups: |
| 178 if test.IsExiting(): | 233 try: |
| 179 break | 234 state = group.shared_user_story_state_class( |
| 180 for _ in xrange(finder_options.page_repeat): | 235 test, finder_options, user_story_set) |
| 181 results.WillRunPage(page) | 236 for user_story in group.user_stories: |
| 182 try: | 237 if test.IsExiting(): |
| 183 _WaitForThermalThrottlingIfNeeded(state.platform) | 238 break |
| 184 _RunPageAndHandleExceptionIfNeeded( | 239 for _ in xrange(finder_options.page_repeat): |
| 185 test, page_set, expectations, page, results, state) | 240 results.WillRunPage(user_story) |
| 186 except Exception: | 241 try: |
| 187 # Tear down & restart the state for unhandled exceptions thrown by | 242 _WaitForThermalThrottlingIfNeeded(state.platform) |
| 188 # _RunPageAndHandleExceptionIfNeeded. | 243 _RunUserStoryAndProcessErrorIfNeeded( |
| 189 results.AddValue(failure.FailureValue(page, sys.exc_info())) | 244 test, expectations, user_story, results, state) |
| 190 state.TearDown(results) | 245 except Exception: |
| 191 state = shared_page_state.SharedPageState( | 246 # Tear down & restart the state for unhandled exceptions thrown by |
| 192 test, finder_options, page_set) | 247 # _RunUserStoryAndProcessErrorIfNeeded. |
| 193 finally: | 248 results.AddValue(failure.FailureValue(user_story, sys.exc_info())) |
| 194 _CheckThermalThrottling(state.platform) | 249 state.TearDownState(results) |
| 195 discard_run = (test.discard_first_result and | 250 state = group.shared_user_story_state_class( |
| 196 page not in pages_with_discarded_first_result) | 251 test, finder_options, user_story_set) |
| 197 if discard_run: | 252 finally: |
| 198 pages_with_discarded_first_result.add(page) | 253 _CheckThermalThrottling(state.platform) |
| 199 results.DidRunPage(page, discard_run=discard_run) | 254 discard_run = (test.discard_first_result and |
| 200 if max_failures is not None and len(results.failures) > max_failures: | 255 user_story not in |
| 201 logging.error('Too many failures. Aborting.') | 256 user_story_with_discarded_first_results) |
| 202 test.RequestExit() | 257 if discard_run: |
| 203 finally: | 258 user_story_with_discarded_first_results.add(user_story) |
| 204 state.TearDown(results) | 259 results.DidRunPage(user_story, discard_run=discard_run) |
| 260 if max_failures is not None and len(results.failures) > max_failures: |
| 261 logging.error('Too many failures. Aborting.') |
| 262 test.RequestExit() |
| 263 finally: |
| 264 if state: |
| 265 state.TearDownState(results) |
| 205 | 266 |
| 206 | 267 def _ShuffleAndFilterUserStorySet(user_story_set, finder_options): |
| 207 def _ShuffleAndFilterPageSet(page_set, finder_options): | |
| 208 if finder_options.pageset_shuffle_order_file: | 268 if finder_options.pageset_shuffle_order_file: |
| 209 return page_set.ReorderPageSet(finder_options.pageset_shuffle_order_file) | 269 if isinstance(user_story_set, page_set_module.PageSet): |
| 210 pages = [page for page in page_set.pages[:] | 270 return page_set_module.ReorderPageSet( |
| 211 if user_story_filter.UserStoryFilter.IsSelected(page)] | 271 finder_options.pageset_shuffle_order_file) |
| 272 else: |
| 273 raise Exception( |
| 274 'pageset-shuffle-order-file flag can only be used with page set') |
| 275 user_stories = [u for u in user_story_set[:] |
| 276 if user_story_filter.UserStoryFilter.IsSelected(u)] |
| 212 if finder_options.pageset_shuffle: | 277 if finder_options.pageset_shuffle: |
| 213 random.shuffle(pages) | 278 random.shuffle(user_stories) |
| 214 return pages | 279 return user_stories |
| 215 | 280 |
| 216 | 281 |
| 217 def _CheckArchives(page_set, pages, results): | 282 def _CheckArchives(page_set, pages, results): |
| 218 """Returns a subset of pages that are local or have WPR archives. | 283 """Returns a subset of pages that are local or have WPR archives. |
| 219 | 284 |
| 220 Logs warnings if any are missing. | 285 Logs warnings if any are missing. |
| 221 """ | 286 """ |
| 222 # Warn of any problems with the entire page set. | 287 # Warn of any problems with the entire page set. |
| 223 if any(not p.is_local for p in pages): | 288 if any(not p.is_local for p in pages): |
| 224 if not page_set.archive_data_file: | 289 if not page_set.archive_data_file: |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 276 logging.warning('Device is thermally throttled before running ' | 341 logging.warning('Device is thermally throttled before running ' |
| 277 'performance tests, results will vary.') | 342 'performance tests, results will vary.') |
| 278 | 343 |
| 279 | 344 |
| 280 def _CheckThermalThrottling(platform): | 345 def _CheckThermalThrottling(platform): |
| 281 if not platform.CanMonitorThermalThrottling(): | 346 if not platform.CanMonitorThermalThrottling(): |
| 282 return | 347 return |
| 283 if platform.HasBeenThermallyThrottled(): | 348 if platform.HasBeenThermallyThrottled(): |
| 284 logging.warning('Device has been thermally throttled during ' | 349 logging.warning('Device has been thermally throttled during ' |
| 285 'performance tests, results will vary.') | 350 'performance tests, results will vary.') |
| OLD | NEW |