OLD | NEW |
| (Empty) |
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. | |
4 import codecs | |
5 import logging | |
6 import os | |
7 import time | |
8 import traceback | |
9 import urlparse | |
10 import random | |
11 | |
12 from telemetry import browser_gone_exception | |
13 from telemetry import page_filter as page_filter_module | |
14 from telemetry import page_test | |
15 from telemetry import tab_crash_exception | |
16 from telemetry import util | |
17 from telemetry import wpr_modes | |
18 | |
19 class PageState(object): | |
20 def __init__(self): | |
21 self.did_login = False | |
22 | |
23 class _RunState(object): | |
24 def __init__(self): | |
25 self.first_browser = True | |
26 self.browser = None | |
27 self.tab = None | |
28 self.is_tracing = False | |
29 | |
30 def Close(self): | |
31 self.is_tracing = False | |
32 | |
33 if self.tab: | |
34 self.tab.Disconnect() | |
35 self.tab = None | |
36 | |
37 if self.browser: | |
38 self.browser.Close() | |
39 self.browser = None | |
40 | |
41 def _ShuffleAndFilterPageSet(page_set, options): | |
42 if options.pageset_shuffle_order_file and not options.pageset_shuffle: | |
43 raise Exception('--pageset-shuffle-order-file requires --pageset-shuffle.') | |
44 | |
45 if options.pageset_shuffle_order_file: | |
46 return page_set.ReorderPageSet(options.pageset_shuffle_order_file) | |
47 | |
48 page_filter = page_filter_module.PageFilter(options) | |
49 pages = [page for page in page_set.pages[:] | |
50 if not page.disabled and page_filter.IsSelected(page)] | |
51 | |
52 if options.pageset_shuffle: | |
53 random.Random().shuffle(pages) | |
54 return [page | |
55 for _ in xrange(int(options.pageset_repeat)) | |
56 for page in pages | |
57 for _ in xrange(int(options.page_repeat))] | |
58 | |
59 class PageRunner(object): | |
60 """Runs a given test against a given test.""" | |
61 def __init__(self, page_set): | |
62 self.page_set = page_set | |
63 | |
64 def __enter__(self): | |
65 return self | |
66 | |
67 def __exit__(self, *args): | |
68 self.Close() | |
69 | |
70 def Run(self, options, possible_browser, test, results): | |
71 # Check if we can run against WPR. | |
72 for page in self.page_set.pages: | |
73 parsed_url = urlparse.urlparse(page.url) | |
74 if parsed_url.scheme == 'file': | |
75 continue | |
76 if not page.archive_path: | |
77 logging.warning(""" | |
78 No page set archive provided for the page %s. Benchmarking against live sites! | |
79 Results won't be repeatable or comparable. | |
80 """, page.url) | |
81 elif options.wpr_mode != wpr_modes.WPR_RECORD: | |
82 # The page has an archive, and we're not recording. | |
83 if not os.path.isfile(page.archive_path): | |
84 logging.warning(""" | |
85 The page set archive %s for page %s does not exist, benchmarking against live | |
86 sites! Results won't be repeatable or comparable. | |
87 | |
88 To fix this, either add svn-internal to your .gclient using | |
89 http://goto/read-src-internal, or create a new archive using record_wpr. | |
90 """, os.path.relpath(page.archive_path), page.url) | |
91 | |
92 # Verify credentials path. | |
93 credentials_path = None | |
94 if self.page_set.credentials_path: | |
95 credentials_path = os.path.join(os.path.dirname(self.page_set.file_path), | |
96 self.page_set.credentials_path) | |
97 if not os.path.exists(credentials_path): | |
98 credentials_path = None | |
99 | |
100 # Set up user agent. | |
101 if self.page_set.user_agent_type: | |
102 options.browser_user_agent_type = self.page_set.user_agent_type | |
103 | |
104 for page in self.page_set: | |
105 test.CustomizeBrowserOptionsForPage(page, possible_browser.options) | |
106 | |
107 # Check tracing directory. | |
108 if options.trace_dir: | |
109 if not os.path.exists(options.trace_dir): | |
110 os.mkdir(options.trace_dir) | |
111 if not os.path.isdir(options.trace_dir): | |
112 raise Exception('--trace-dir isn\'t a directory: %s' % | |
113 options.trace_dir) | |
114 elif os.listdir(options.trace_dir): | |
115 raise Exception('Trace directory isn\'t empty: %s' % options.trace_dir) | |
116 | |
117 # Reorder page set based on options. | |
118 pages = _ShuffleAndFilterPageSet(self.page_set, options) | |
119 | |
120 state = _RunState() | |
121 last_archive_path = None | |
122 try: | |
123 for page in pages: | |
124 if options.wpr_mode != wpr_modes.WPR_RECORD: | |
125 if page.archive_path and os.path.isfile(page.archive_path): | |
126 possible_browser.options.wpr_mode = wpr_modes.WPR_REPLAY | |
127 else: | |
128 possible_browser.options.wpr_mode = wpr_modes.WPR_OFF | |
129 if last_archive_path != page.archive_path: | |
130 state.Close() | |
131 state = _RunState() | |
132 last_archive_path = page.archive_path | |
133 tries = 3 | |
134 while tries: | |
135 try: | |
136 if not state.browser: | |
137 self._SetupBrowser(state, test, possible_browser, | |
138 credentials_path, page.archive_path) | |
139 if not state.tab: | |
140 if len(state.browser.tabs) == 0: | |
141 state.browser.tabs.New() | |
142 state.tab = state.browser.tabs[0] | |
143 if options.trace_dir: | |
144 self._SetupTracingTab(state) | |
145 | |
146 try: | |
147 self._RunPage(options, page, state.tab, test, results) | |
148 except tab_crash_exception.TabCrashException: | |
149 stdout = '' | |
150 if not options.show_stdout: | |
151 stdout = state.browser.GetStandardOutput() | |
152 stdout = (('\nStandard Output:\n') + | |
153 ('*' * 80) + | |
154 '\n\t' + stdout.replace('\n', '\n\t') + '\n' + | |
155 ('*' * 80)) | |
156 logging.warning('Tab crashed: %s%s', page.url, stdout) | |
157 state.Close() | |
158 | |
159 if options.trace_dir: | |
160 self._EndTracing(state, options, page) | |
161 | |
162 if test.needs_browser_restart_after_each_run: | |
163 state.Close() | |
164 | |
165 break | |
166 except browser_gone_exception.BrowserGoneException: | |
167 logging.warning('Lost connection to browser. Retrying.') | |
168 state.Close() | |
169 tries -= 1 | |
170 if not tries: | |
171 logging.error('Lost connection to browser 3 times. Failing.') | |
172 raise | |
173 finally: | |
174 state.Close() | |
175 | |
176 def _RunPage(self, options, page, tab, test, results): | |
177 if not test.CanRunForPage(page): | |
178 logging.warning('Skiping test: it cannot run for %s', page.url) | |
179 results.AddSkippedPage(page, 'Test cannot run', '') | |
180 return | |
181 | |
182 logging.info('Running %s' % page.url) | |
183 | |
184 page_state = PageState() | |
185 try: | |
186 did_prepare = self._PreparePage(page, tab, page_state, test, results) | |
187 except util.TimeoutException, ex: | |
188 logging.warning('Timed out waiting for reply on %s. This is unusual.', | |
189 page.url) | |
190 results.AddFailure(page, ex, traceback.format_exc()) | |
191 return | |
192 except tab_crash_exception.TabCrashException, ex: | |
193 results.AddFailure(page, ex, traceback.format_exc()) | |
194 raise | |
195 except browser_gone_exception.BrowserGoneException: | |
196 raise | |
197 except Exception, ex: | |
198 logging.error('Unexpected failure while running %s: %s', | |
199 page.url, traceback.format_exc()) | |
200 self._CleanUpPage(page, tab, page_state) | |
201 raise | |
202 | |
203 if not did_prepare: | |
204 self._CleanUpPage(page, tab, page_state) | |
205 return | |
206 | |
207 try: | |
208 test.Run(options, page, tab, results) | |
209 except page_test.Failure, ex: | |
210 logging.info('%s: %s', ex, page.url) | |
211 results.AddFailure(page, ex, traceback.format_exc()) | |
212 return | |
213 except util.TimeoutException, ex: | |
214 logging.warning('Timed out while running %s', page.url) | |
215 results.AddFailure(page, ex, traceback.format_exc()) | |
216 return | |
217 except tab_crash_exception.TabCrashException, ex: | |
218 results.AddFailure(page, ex, traceback.format_exc()) | |
219 raise | |
220 except browser_gone_exception.BrowserGoneException: | |
221 raise | |
222 except Exception, ex: | |
223 logging.error('Unexpected failure while running %s: %s', | |
224 page.url, traceback.format_exc()) | |
225 raise | |
226 finally: | |
227 self._CleanUpPage(page, tab, page_state) | |
228 | |
229 results.AddSuccess(page) | |
230 | |
231 def Close(self): | |
232 pass | |
233 | |
234 def _SetupBrowser(self, state, test, possible_browser, credentials_path, | |
235 archive_path): | |
236 assert not state.tab | |
237 state.browser = possible_browser.Create() | |
238 state.browser.credentials.credentials_path = credentials_path | |
239 test.SetUpBrowser(state.browser) | |
240 | |
241 if state.first_browser: | |
242 state.browser.credentials.WarnIfMissingCredentials(self.page_set) | |
243 state.first_browser = False | |
244 | |
245 state.browser.SetReplayArchivePath(archive_path) | |
246 | |
247 def _SetupTracingTab(self, state): | |
248 if state.browser.supports_tracing: | |
249 state.is_tracing = True | |
250 state.browser.StartTracing() | |
251 | |
252 def _EndTracing(self, state, options, page): | |
253 if state.is_tracing: | |
254 assert state.browser | |
255 state.is_tracing = False | |
256 state.browser.StopTracing() | |
257 trace_result = state.browser.GetTraceResultAndReset() | |
258 logging.info('Processing trace...') | |
259 | |
260 trace_file_base = os.path.join( | |
261 options.trace_dir, page.url_as_file_safe_name) | |
262 | |
263 if options.page_repeat != 1 or options.pageset_repeat != 1: | |
264 trace_file_index = 0 | |
265 | |
266 while True: | |
267 trace_file = '%s_%03d.json' % (trace_file_base, trace_file_index) | |
268 if not os.path.exists(trace_file): | |
269 break | |
270 trace_file_index = trace_file_index + 1 | |
271 else: | |
272 trace_file = '%s.json' % trace_file_base | |
273 with codecs.open(trace_file, 'w', | |
274 encoding='utf-8') as trace_file: | |
275 trace_result.Serialize(trace_file) | |
276 logging.info('Trace saved.') | |
277 | |
278 def _PreparePage(self, page, tab, page_state, test, results): | |
279 parsed_url = urlparse.urlparse(page.url) | |
280 if parsed_url[0] == 'file': | |
281 dirname, filename = page.url_base_dir_and_file | |
282 tab.browser.SetHTTPServerDirectory(dirname) | |
283 target_side_url = tab.browser.http_server.UrlOf(filename) | |
284 else: | |
285 target_side_url = page.url | |
286 | |
287 if page.credentials: | |
288 page_state.did_login = tab.browser.credentials.LoginNeeded( | |
289 tab, page.credentials) | |
290 if not page_state.did_login: | |
291 msg = 'Could not login to %s on %s' % (page.credentials, | |
292 target_side_url) | |
293 logging.info(msg) | |
294 results.AddFailure(page, msg, "") | |
295 return False | |
296 | |
297 test.WillNavigateToPage(page, tab) | |
298 tab.Navigate(target_side_url) | |
299 test.DidNavigateToPage(page, tab) | |
300 | |
301 # Wait for unpredictable redirects. | |
302 if page.wait_time_after_navigate: | |
303 time.sleep(page.wait_time_after_navigate) | |
304 page.WaitToLoad(tab, 60) | |
305 tab.WaitForDocumentReadyStateToBeInteractiveOrBetter() | |
306 | |
307 return True | |
308 | |
309 def _CleanUpPage(self, page, tab, page_state): # pylint: disable=R0201 | |
310 if page.credentials and page_state.did_login: | |
311 tab.browser.credentials.LoginNoLongerNeeded(tab, page.credentials) | |
312 try: | |
313 tab.EvaluateJavaScript("""window.chrome && chrome.benchmarking && | |
314 chrome.benchmarking.closeConnections()""") | |
315 except Exception: | |
316 pass | |
317 | |
318 @staticmethod | |
319 def AddCommandLineOptions(parser): | |
320 page_filter_module.PageFilter.AddCommandLineOptions(parser) | |
OLD | NEW |