Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / core / backends / chrome / desktop_browser_backend.py
1 # Copyright 2013 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 glob
6 import heapq
7 import logging
8 import os
9 import subprocess as subprocess
10 import shutil
11 import sys
12 import tempfile
13 import time
14
15 from telemetry.core import exceptions
16 from telemetry.core import util
17 from telemetry.core.backends import browser_backend
18 from telemetry.core.backends.chrome import chrome_browser_backend
19
20
21 class DesktopBrowserBackend(chrome_browser_backend.ChromeBrowserBackend):
22   """The backend for controlling a locally-executed browser instance, on Linux,
23   Mac or Windows.
24   """
25   def __init__(self, browser_options, executable, flash_path, is_content_shell,
26                browser_directory, output_profile_path, extensions_to_load):
27     super(DesktopBrowserBackend, self).__init__(
28         is_content_shell=is_content_shell,
29         supports_extensions=not is_content_shell,
30         browser_options=browser_options,
31         output_profile_path=output_profile_path,
32         extensions_to_load=extensions_to_load)
33
34     # Initialize fields so that an explosion during init doesn't break in Close.
35     self._proc = None
36     self._tmp_profile_dir = None
37     self._tmp_output_file = None
38
39     self._executable = executable
40     if not self._executable:
41       raise Exception('Cannot create browser, no executable found!')
42
43     self._flash_path = flash_path
44     if (browser_options.warn_if_no_flash
45         and self._flash_path and not os.path.exists(self._flash_path)):
46       logging.warning(('Could not find flash at %s. Running without flash.\n\n'
47                        'To fix this see http://go/read-src-internal') %
48                       self._flash_path)
49       self._flash_path = None
50
51     if len(extensions_to_load) > 0 and is_content_shell:
52       raise browser_backend.ExtensionsNotSupportedException(
53           'Content shell does not support extensions.')
54
55     self._browser_directory = browser_directory
56     self._port = None
57     self._profile_dir = None
58     self._tmp_minidump_dir = tempfile.mkdtemp()
59
60     self._SetupProfile()
61
62   def _SetupProfile(self):
63     if not self.browser_options.dont_override_profile:
64       if self._output_profile_path:
65         # If both |_output_profile_path| and |profile_dir| are specified then
66         # the calling code will throw an exception, so we don't need to worry
67         # about that case here.
68         self._tmp_profile_dir = self._output_profile_path
69       else:
70         self._tmp_profile_dir = tempfile.mkdtemp()
71       profile_dir = self._profile_dir or self.browser_options.profile_dir
72       if profile_dir:
73         if self.is_content_shell:
74           logging.critical('Profiles cannot be used with content shell')
75           sys.exit(1)
76         logging.info("Using profile directory:'%s'." % profile_dir)
77         shutil.rmtree(self._tmp_profile_dir)
78         shutil.copytree(profile_dir, self._tmp_profile_dir)
79
80   def _LaunchBrowser(self):
81     args = [self._executable]
82     args.extend(self.GetBrowserStartupArgs())
83     if self.browser_options.startup_url:
84       args.append(self.browser_options.startup_url)
85     env = os.environ.copy()
86     env['CHROME_HEADLESS'] = '1'  # Don't upload minidumps.
87     env['BREAKPAD_DUMP_LOCATION'] = self._tmp_minidump_dir
88     logging.debug('Starting Chrome %s', args)
89     if not self.browser_options.show_stdout:
90       self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0)
91       self._proc = subprocess.Popen(
92           args, stdout=self._tmp_output_file, stderr=subprocess.STDOUT, env=env)
93     else:
94       self._proc = subprocess.Popen(args, env=env)
95
96     try:
97       self._WaitForBrowserToComeUp()
98       self._PostBrowserStartupInitialization()
99     except:
100       self.Close()
101       raise
102
103   def HasBrowserFinishedLaunching(self):
104     # In addition to the functional check performed by the base class, quickly
105     # check if the browser process is still alive.
106     self._proc.poll()
107     if self._proc.returncode:
108       raise exceptions.ProcessGoneException(
109           "Return code: %d" % self._proc.returncode)
110     return super(DesktopBrowserBackend, self).HasBrowserFinishedLaunching()
111
112   def GetBrowserStartupArgs(self):
113     args = super(DesktopBrowserBackend, self).GetBrowserStartupArgs()
114     self._port = util.GetUnreservedAvailableLocalPort()
115     args.append('--remote-debugging-port=%i' % self._port)
116     args.append('--enable-crash-reporter-for-testing')
117     if not self.is_content_shell:
118       args.append('--window-size=1280,1024')
119       if self._flash_path:
120         args.append('--ppapi-flash-path=%s' % self._flash_path)
121       if not self.browser_options.dont_override_profile:
122         args.append('--user-data-dir=%s' % self._tmp_profile_dir)
123     return args
124
125   def SetProfileDirectory(self, profile_dir):
126     # Make sure _profile_dir hasn't already been set.
127     assert self._profile_dir is None
128
129     if self.is_content_shell:
130       logging.critical('Profile creation cannot be used with content shell')
131       sys.exit(1)
132
133     self._profile_dir = profile_dir
134
135   def Start(self):
136     self._LaunchBrowser()
137
138   @property
139   def pid(self):
140     if self._proc:
141       return self._proc.pid
142     return None
143
144   @property
145   def browser_directory(self):
146     return self._browser_directory
147
148   @property
149   def profile_directory(self):
150     return self._tmp_profile_dir
151
152   def IsBrowserRunning(self):
153     return self._proc.poll() == None
154
155   def GetStandardOutput(self):
156     if not self._tmp_output_file:
157       if self.browser_options.show_stdout:
158         # This can happen in the case that loading the Chrome binary fails.
159         # We print rather than using logging here, because that makes a
160         # recursive call to this function.
161         print >> sys.stderr, "Can't get standard output with --show_stdout"
162       return ''
163     self._tmp_output_file.flush()
164     try:
165       with open(self._tmp_output_file.name) as f:
166         return f.read()
167     except IOError:
168       return ''
169
170   def GetStackTrace(self):
171     stackwalk = util.FindSupportBinary('minidump_stackwalk')
172     if not stackwalk:
173       logging.warning('minidump_stackwalk binary not found. Must build it to '
174                       'symbolize crash dumps. Returning browser stdout.')
175       return self.GetStandardOutput()
176
177     dumps = glob.glob(os.path.join(self._tmp_minidump_dir, '*.dmp'))
178     if not dumps:
179       logging.warning('No crash dump found. Returning browser stdout.')
180       return self.GetStandardOutput()
181     most_recent_dump = heapq.nlargest(1, dumps, os.path.getmtime)[0]
182     if os.path.getmtime(most_recent_dump) < (time.time() - (5 * 60)):
183       logging.warning('Crash dump is older than 5 minutes. May not be correct.')
184
185     symbols = glob.glob(os.path.join(self._browser_directory, '*.breakpad*'))
186     if not symbols:
187       logging.warning('No breakpad symbols found. Returning browser stdout.')
188       return self.GetStandardOutput()
189
190     minidump = most_recent_dump + '.stripped'
191     with open(most_recent_dump, 'rb') as infile:
192       with open(minidump, 'wb') as outfile:
193         outfile.write(''.join(infile.read().partition('MDMP')[1:]))
194
195     symbols_path = os.path.join(self._tmp_minidump_dir, 'symbols')
196     for symbol in sorted(symbols, key=os.path.getmtime, reverse=True):
197       if not os.path.isfile(symbol):
198         continue
199       with open(symbol, 'r') as f:
200         fields = f.readline().split()
201         if not fields:
202           continue
203         sha = fields[3]
204         binary = ' '.join(fields[4:])
205       symbol_path = os.path.join(symbols_path, binary, sha)
206       if os.path.exists(symbol_path):
207         continue
208       os.makedirs(symbol_path)
209       shutil.copyfile(symbol, os.path.join(symbol_path, binary + '.sym'))
210
211     error = tempfile.NamedTemporaryFile('w', 0)
212     return subprocess.Popen(
213         [stackwalk, minidump, symbols_path],
214         stdout=subprocess.PIPE, stderr=error).communicate()[0]
215
216   def __del__(self):
217     self.Close()
218
219   def Close(self):
220     super(DesktopBrowserBackend, self).Close()
221
222     if self._proc:
223
224       def IsClosed():
225         if not self._proc:
226           return True
227         return self._proc.poll() != None
228
229       # Try to politely shutdown, first.
230       if not IsClosed():
231         self._proc.terminate()
232         try:
233           util.WaitFor(IsClosed, timeout=5)
234           self._proc = None
235         except util.TimeoutException:
236           logging.warning('Failed to gracefully shutdown. Proceeding to kill.')
237
238       # Kill it.
239       if not IsClosed():
240         self._proc.kill()
241         try:
242           util.WaitFor(IsClosed, timeout=10)
243         except util.TimeoutException:
244           raise Exception('Could not shutdown the browser.')
245         finally:
246           self._proc = None
247
248     if self._output_profile_path:
249       # If we need the output then double check that it exists.
250       if not (self._tmp_profile_dir and os.path.exists(self._tmp_profile_dir)):
251         raise Exception("No profile directory generated by Chrome: '%s'." %
252             self._tmp_profile_dir)
253     else:
254       # If we don't need the profile after the run then cleanup.
255       if self._tmp_profile_dir and os.path.exists(self._tmp_profile_dir):
256         shutil.rmtree(self._tmp_profile_dir, ignore_errors=True)
257         self._tmp_profile_dir = None
258
259     if self._tmp_output_file:
260       self._tmp_output_file.close()
261       self._tmp_output_file = None