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 glob | |
5 import heapq | |
6 import logging | |
7 import os | |
8 import subprocess as subprocess | |
9 import shutil | |
10 import sys | |
11 import tempfile | |
12 import time | |
13 | |
14 from telemetry.core import util | |
15 from telemetry.core.backends import browser_backend | |
16 from telemetry.core.backends.chrome import chrome_browser_backend | |
17 | |
18 class DesktopBrowserBackend(chrome_browser_backend.ChromeBrowserBackend): | |
19 """The backend for controlling a locally-executed browser instance, on Linux, | |
20 Mac or Windows. | |
21 """ | |
22 def __init__(self, options, executable, flash_path, is_content_shell, | |
23 browser_directory, delete_profile_dir_after_run=True): | |
24 super(DesktopBrowserBackend, self).__init__( | |
25 is_content_shell=is_content_shell, | |
26 supports_extensions=not is_content_shell, | |
27 options=options) | |
28 | |
29 # Initialize fields so that an explosion during init doesn't break in Close. | |
30 self._proc = None | |
31 self._tmp_profile_dir = None | |
32 self._tmp_output_file = None | |
33 | |
34 self._executable = executable | |
35 if not self._executable: | |
36 raise Exception('Cannot create browser, no executable found!') | |
37 | |
38 self._flash_path = flash_path | |
39 if self._flash_path and not os.path.exists(self._flash_path): | |
40 logging.warning(('Could not find flash at %s. Running without flash.\n\n' | |
41 'To fix this see http://go/read-src-internal') % | |
42 self._flash_path) | |
43 self._flash_path = None | |
44 | |
45 if len(options.extensions_to_load) > 0 and is_content_shell: | |
46 raise browser_backend.ExtensionsNotSupportedException( | |
47 'Content shell does not support extensions.') | |
48 | |
49 self._browser_directory = browser_directory | |
50 self._port = util.GetAvailableLocalPort() | |
51 self._profile_dir = None | |
52 self._supports_net_benchmarking = True | |
53 self._delete_profile_dir_after_run = delete_profile_dir_after_run | |
54 self._tmp_minidump_dir = tempfile.mkdtemp() | |
55 | |
56 self._SetupProfile() | |
57 | |
58 def _SetupProfile(self): | |
59 if not self.options.dont_override_profile: | |
60 self._tmp_profile_dir = tempfile.mkdtemp() | |
61 profile_dir = self._profile_dir or self.options.profile_dir | |
62 if profile_dir: | |
63 if self.is_content_shell: | |
64 logging.critical('Profiles cannot be used with content shell') | |
65 sys.exit(1) | |
66 shutil.rmtree(self._tmp_profile_dir) | |
67 shutil.copytree(profile_dir, self._tmp_profile_dir) | |
68 | |
69 def _LaunchBrowser(self): | |
70 args = [self._executable] | |
71 args.extend(self.GetBrowserStartupArgs()) | |
72 env = os.environ.copy() | |
73 env['CHROME_HEADLESS'] = '1' # Don't upload minidumps. | |
74 env['BREAKPAD_DUMP_LOCATION'] = self._tmp_minidump_dir | |
75 if not self.options.show_stdout: | |
76 self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0) | |
77 self._proc = subprocess.Popen( | |
78 args, stdout=self._tmp_output_file, stderr=subprocess.STDOUT, env=env) | |
79 else: | |
80 self._proc = subprocess.Popen(args, env=env) | |
81 | |
82 try: | |
83 self._WaitForBrowserToComeUp() | |
84 self._PostBrowserStartupInitialization() | |
85 except: | |
86 self.Close() | |
87 raise | |
88 | |
89 def GetBrowserStartupArgs(self): | |
90 args = super(DesktopBrowserBackend, self).GetBrowserStartupArgs() | |
91 args.append('--remote-debugging-port=%i' % self._port) | |
92 args.append('--enable-crash-reporter-for-testing') | |
93 if not self.is_content_shell: | |
94 args.append('--window-size=1280,1024') | |
95 if self._flash_path: | |
96 args.append('--ppapi-flash-path=%s' % self._flash_path) | |
97 if self._supports_net_benchmarking: | |
98 args.append('--enable-net-benchmarking') | |
99 else: | |
100 args.append('--enable-benchmarking') | |
101 if not self.options.dont_override_profile: | |
102 args.append('--user-data-dir=%s' % self._tmp_profile_dir) | |
103 return args | |
104 | |
105 def SetProfileDirectory(self, profile_dir): | |
106 # Make sure _profile_dir hasn't already been set. | |
107 assert self._profile_dir is None | |
108 | |
109 if self.is_content_shell: | |
110 logging.critical('Profile creation cannot be used with content shell') | |
111 sys.exit(1) | |
112 | |
113 self._profile_dir = profile_dir | |
114 | |
115 def Start(self): | |
116 self._LaunchBrowser() | |
117 | |
118 # For old chrome versions, might have to relaunch to have the | |
119 # correct net_benchmarking switch. | |
120 if self._chrome_branch_number < 1418: | |
121 self.Close() | |
122 self._supports_net_benchmarking = False | |
123 self._LaunchBrowser() | |
124 | |
125 @property | |
126 def pid(self): | |
127 if self._proc: | |
128 return self._proc.pid | |
129 return None | |
130 | |
131 @property | |
132 def browser_directory(self): | |
133 return self._browser_directory | |
134 | |
135 @property | |
136 def profile_directory(self): | |
137 return self._tmp_profile_dir | |
138 | |
139 def IsBrowserRunning(self): | |
140 return self._proc.poll() == None | |
141 | |
142 def GetStandardOutput(self): | |
143 assert self._tmp_output_file, "Can't get standard output with show_stdout" | |
144 self._tmp_output_file.flush() | |
145 try: | |
146 with open(self._tmp_output_file.name) as f: | |
147 return f.read() | |
148 except IOError: | |
149 return '' | |
150 | |
151 def GetStackTrace(self): | |
152 executable_dir = os.path.dirname(self._executable) | |
153 stackwalk = os.path.join(executable_dir, 'minidump_stackwalk') | |
154 if not os.path.exists(stackwalk): | |
155 logging.warning('minidump_stackwalk binary not found. Must build it to ' | |
156 'symbolize crash dumps. Returning browser stdout.') | |
157 return self.GetStandardOutput() | |
158 | |
159 dumps = glob.glob(os.path.join(self._tmp_minidump_dir, '*.dmp')) | |
160 if not dumps: | |
161 logging.warning('No crash dump found. Returning browser stdout.') | |
162 return self.GetStandardOutput() | |
163 most_recent_dump = heapq.nlargest(1, dumps, os.path.getmtime)[0] | |
164 if os.path.getmtime(most_recent_dump) < (time.time() - (5 * 60)): | |
165 logging.warn('Crash dump is older than 5 minutes. May not be correct.') | |
166 | |
167 minidump = most_recent_dump + '.stripped' | |
168 with open(most_recent_dump, 'rb') as infile: | |
169 with open(minidump, 'wb') as outfile: | |
170 outfile.write(''.join(infile.read().partition('MDMP')[1:])) | |
171 | |
172 symbols = glob.glob(os.path.join(executable_dir, 'chrome.breakpad.*'))[0] | |
173 if not symbols: | |
174 logging.warning('No breakpad symbols found. Returning browser stdout.') | |
175 return self.GetStandardOutput() | |
176 | |
177 symbols_path = os.path.join(self._tmp_minidump_dir, 'symbols') | |
178 with open(symbols, 'r') as f: | |
179 _, _, _, sha, binary = f.readline().split() | |
180 symbol_path = os.path.join(symbols_path, binary, sha) | |
181 os.makedirs(symbol_path) | |
182 shutil.copyfile(symbols, os.path.join(symbol_path, binary + '.sym')) | |
183 | |
184 error = tempfile.NamedTemporaryFile('w', 0) | |
185 return subprocess.Popen( | |
186 [stackwalk, minidump, symbols_path], | |
187 stdout=subprocess.PIPE, stderr=error).communicate()[0] | |
188 | |
189 def __del__(self): | |
190 self.Close() | |
191 | |
192 def Close(self): | |
193 super(DesktopBrowserBackend, self).Close() | |
194 | |
195 if self._proc: | |
196 | |
197 def IsClosed(): | |
198 if not self._proc: | |
199 return True | |
200 return self._proc.poll() != None | |
201 | |
202 # Try to politely shutdown, first. | |
203 self._proc.terminate() | |
204 try: | |
205 util.WaitFor(IsClosed, timeout=1) | |
206 self._proc = None | |
207 except util.TimeoutException: | |
208 pass | |
209 | |
210 # Kill it. | |
211 if not IsClosed(): | |
212 self._proc.kill() | |
213 try: | |
214 util.WaitFor(IsClosed, timeout=5) | |
215 self._proc = None | |
216 except util.TimeoutException: | |
217 self._proc = None | |
218 raise Exception('Could not shutdown the browser.') | |
219 | |
220 if self._delete_profile_dir_after_run and \ | |
221 self._tmp_profile_dir and os.path.exists(self._tmp_profile_dir): | |
222 shutil.rmtree(self._tmp_profile_dir, ignore_errors=True) | |
223 self._tmp_profile_dir = None | |
224 | |
225 if self._tmp_output_file: | |
226 self._tmp_output_file.close() | |
227 self._tmp_output_file = None | |
228 | |
229 def CreateForwarder(self, *port_pairs): | |
230 return browser_backend.DoNothingForwarder(*port_pairs) | |
OLD | NEW |