2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
16 class LaunchFailure(Exception):
21 if sys
.platform
== 'darwin':
23 elif sys
.platform
.startswith('linux'):
25 elif sys
.platform
in ('cygwin', 'win32'):
28 raise LaunchFailure('Unknown platform: %s' % sys
.platform
)
32 PLATFORM
= GetPlatform()
35 def SelectRunCommand():
36 # The subprocess module added support for .kill in Python 2.6
37 assert (sys
.version_info
[0] >= 3 or (sys
.version_info
[0] == 2 and
38 sys
.version_info
[1] >= 6))
39 if PLATFORM
== 'linux':
40 return browserprocess
.RunCommandInProcessGroup
42 return browserprocess
.RunCommandWithSubprocess
45 RunCommand
= SelectRunCommand()
47 def RemoveDirectory(path
):
54 # Windows processes sometime hang onto files too long
57 time
.sleep(sleep_time
)
60 # No luck - don't mask the error
68 # In Windows, subprocess seems to have an issue with file names that
70 def EscapeSpaces(path
):
71 if PLATFORM
== 'windows' and ' ' in path
:
77 env
= dict(os
.environ
)
78 # Enable PPAPI Dev interfaces for testing.
79 env
['NACL_ENABLE_PPAPI_DEV'] = str(options
.enable_ppapi_dev
)
81 env
['NACL_PLUGIN_DEBUG'] = '1'
82 # env['NACL_SRPC_DEBUG'] = '1'
86 class BrowserLauncher(object):
90 SLEEP_TIME
= float(WAIT_TIME
) / WAIT_STEPS
92 def __init__(self
, options
):
93 self
.options
= options
96 self
.tool_log_dir
= None
99 raise NotImplementedError
101 def BinaryName(self
):
102 raise NotImplementedError
104 def CreateProfile(self
):
105 raise NotImplementedError
107 def MakeCmd(self
, url
, host
, port
):
108 raise NotImplementedError
110 def CreateToolLogDir(self
):
111 self
.tool_log_dir
= tempfile
.mkdtemp(prefix
='vglogs_')
112 return self
.tool_log_dir
114 def FindBinary(self
):
115 if self
.options
.browser_path
:
116 return self
.options
.browser_path
118 path
= self
.KnownPath()
119 if path
is None or not os
.path
.exists(path
):
120 raise LaunchFailure('Cannot find the browser directory')
121 binary
= os
.path
.join(path
, self
.BinaryName())
122 if not os
.path
.exists(binary
):
123 raise LaunchFailure('Cannot find the browser binary')
126 def WaitForProcessDeath(self
):
127 self
.browser_process
.Wait(self
.WAIT_STEPS
, self
.SLEEP_TIME
)
130 self
.browser_process
.Kill()
132 RemoveDirectory(self
.profile
)
133 if self
.tool_log_dir
is not None:
134 RemoveDirectory(self
.tool_log_dir
)
136 def MakeProfileDirectory(self
):
137 self
.profile
= tempfile
.mkdtemp(prefix
='browserprofile_')
140 def SetStandardStream(self
, env
, var_name
, redirect_file
, is_output
):
141 if redirect_file
is None:
143 file_prefix
= 'file:'
145 debug_warning
= 'DEBUG_ONLY:'
146 # logic must match src/trusted/service_runtime/nacl_resource.*
147 # resource specification notation. file: is the default
148 # interpretation, so we must have an exhaustive list of
149 # alternative schemes accepted. if we remove the file-is-default
150 # interpretation, replace with
151 # is_file = redirect_file.startswith(file_prefix)
152 # and remove the list of non-file schemes.
153 is_file
= (not (redirect_file
.startswith(dev_prefix
) or
154 redirect_file
.startswith(debug_warning
+ dev_prefix
)))
156 if redirect_file
.startswith(file_prefix
):
157 bare_file
= redirect_file
[len(file_prefix
)]
159 bare_file
= redirect_file
160 # why always abspath? does chrome chdir or might it in the
161 # future? this means we do not test/use the relative path case.
162 redirect_file
= file_prefix
+ os
.path
.abspath(bare_file
)
164 bare_file
= None # ensure error if used without checking is_file
165 env
[var_name
] = redirect_file
167 # sel_ldr appends program output to the file so we need to clear it
168 # in order to get the stable result.
170 if os
.path
.exists(bare_file
):
172 parent_dir
= os
.path
.dirname(bare_file
)
173 # parent directory may not exist.
174 if not os
.path
.exists(parent_dir
):
175 os
.makedirs(parent_dir
)
177 def Launch(self
, cmd
, env
):
178 browser_path
= cmd
[0]
179 if not os
.path
.exists(browser_path
):
180 raise LaunchFailure('Browser does not exist %r'% browser_path
)
181 if not os
.access(browser_path
, os
.X_OK
):
182 raise LaunchFailure('Browser cannot be executed %r (Is this binary on an '
183 'NFS volume?)' % browser_path
)
184 if self
.options
.sel_ldr
:
185 env
['NACL_SEL_LDR'] = self
.options
.sel_ldr
186 if self
.options
.sel_ldr_bootstrap
:
187 env
['NACL_SEL_LDR_BOOTSTRAP'] = self
.options
.sel_ldr_bootstrap
188 if self
.options
.irt_library
:
189 env
['NACL_IRT_LIBRARY'] = self
.options
.irt_library
190 self
.SetStandardStream(env
, 'NACL_EXE_STDIN',
191 self
.options
.nacl_exe_stdin
, False)
192 self
.SetStandardStream(env
, 'NACL_EXE_STDOUT',
193 self
.options
.nacl_exe_stdout
, True)
194 self
.SetStandardStream(env
, 'NACL_EXE_STDERR',
195 self
.options
.nacl_exe_stderr
, True)
196 print 'ENV:', ' '.join(['='.join(pair
) for pair
in env
.iteritems()])
197 print 'LAUNCHING: %s' % ' '.join(cmd
)
199 self
.browser_process
= RunCommand(cmd
, env
=env
)
202 return self
.browser_process
.IsRunning()
204 def GetReturnCode(self
):
205 return self
.browser_process
.GetReturnCode()
207 def Run(self
, url
, host
, port
):
208 self
.binary
= EscapeSpaces(self
.FindBinary())
209 self
.profile
= self
.CreateProfile()
210 if self
.options
.tool
is not None:
211 self
.tool_log_dir
= self
.CreateToolLogDir()
212 cmd
= self
.MakeCmd(url
, host
, port
)
213 self
.Launch(cmd
, MakeEnv(self
.options
))
216 def EnsureDirectory(path
):
217 if not os
.path
.exists(path
):
221 def EnsureDirectoryForFile(path
):
222 EnsureDirectory(os
.path
.dirname(path
))
225 class ChromeLauncher(BrowserLauncher
):
228 if PLATFORM
== 'linux':
229 # TODO(ncbray): look in path?
230 return '/opt/google/chrome'
231 elif PLATFORM
== 'mac':
232 return '/Applications/Google Chrome.app/Contents/MacOS'
234 homedir
= os
.path
.expanduser('~')
235 path
= os
.path
.join(homedir
, r
'AppData\Local\Google\Chrome\Application')
238 def BinaryName(self
):
239 if PLATFORM
== 'mac':
240 return 'Google Chrome'
241 elif PLATFORM
== 'windows':
246 def MakeEmptyJSONFile(self
, path
):
247 EnsureDirectoryForFile(path
)
252 def CreateProfile(self
):
253 profile
= self
.MakeProfileDirectory()
255 # Squelch warnings by creating bogus files.
256 self
.MakeEmptyJSONFile(os
.path
.join(profile
, 'Default', 'Preferences'))
257 self
.MakeEmptyJSONFile(os
.path
.join(profile
, 'Local State'))
261 def NetLogName(self
):
262 return os
.path
.join(self
.profile
, 'netlog.json')
264 def MakeCmd(self
, url
, host
, port
):
266 # --enable-logging enables stderr output from Chromium subprocesses
268 # https://code.google.com/p/chromium/issues/detail?id=171836)
270 '--disable-web-resources',
271 '--disable-preconnect',
272 # This is speculative, sync should not occur with a clean profile.
274 # This prevents Chrome from making "hidden" network requests at
275 # startup. These requests could be a source of non-determinism,
276 # and they also add noise to the netlogs.
277 '--dns-prefetch-disable',
279 '--no-default-browser-check',
281 '--safebrowsing-disable-auto-update',
282 '--disable-default-apps',
283 # Suppress metrics reporting. This prevents misconfigured bots,
284 # people testing at their desktop, etc from poisoning the UMA data.
285 '--metrics-recording-only',
286 # Chrome explicitly blacklists some ports as "unsafe" because
287 # certain protocols use them. Chrome gives an error like this:
288 # Error 312 (net::ERR_UNSAFE_PORT): Unknown error
289 # Unfortunately, the browser tester can randomly choose a
290 # blacklisted port. To work around this, the tester whitelists
291 # whatever port it is using.
292 '--explicitly-allowed-ports=%d' % port
,
293 '--user-data-dir=%s' % self
.profile
]
294 # Log network requests to assist debugging.
295 cmd
.append('--log-net-log=%s' % self
.NetLogName())
296 if PLATFORM
== 'linux':
297 # Explicitly run with mesa on linux. The test infrastructure doesn't have
298 # sufficient native GL contextes to run these tests.
299 cmd
.append('--use-gl=osmesa')
300 if self
.options
.ppapi_plugin
is None:
301 cmd
.append('--enable-nacl')
302 disable_sandbox
= False
303 # Chrome process can't access file within sandbox
304 disable_sandbox |
= self
.options
.nacl_exe_stdin
is not None
305 disable_sandbox |
= self
.options
.nacl_exe_stdout
is not None
306 disable_sandbox |
= self
.options
.nacl_exe_stderr
is not None
308 cmd
.append('--no-sandbox')
310 cmd
.append('--register-pepper-plugins=%s;%s'
311 % (self
.options
.ppapi_plugin
,
312 self
.options
.ppapi_plugin_mimetype
))
313 cmd
.append('--no-sandbox')
314 if self
.options
.browser_extensions
:
315 cmd
.append('--load-extension=%s' %
316 ','.join(self
.options
.browser_extensions
))
317 cmd
.append('--enable-experimental-extension-apis')
318 if self
.options
.enable_crash_reporter
:
319 cmd
.append('--enable-crash-reporter-for-testing')
320 if self
.options
.tool
== 'memcheck':
321 cmd
= ['src/third_party/valgrind/memcheck.sh',
325 '--gen-suppressions=all',
327 '--trace-children=yes',
328 '--nacl-file=%s' % (self
.options
.files
[0],),
330 '../tools/valgrind/memcheck/suppressions.txt',
331 '--xml-file=%s/xml.%%p' % (self
.tool_log_dir
,),
332 '--log-file=%s/log.%%p' % (self
.tool_log_dir
,)] + cmd
333 elif self
.options
.tool
== 'tsan':
334 cmd
= ['src/third_party/valgrind/tsan.sh',
337 '--trace-children=yes',
338 '--nacl-file=%s' % (self
.options
.files
[0],),
339 '--ignore=../tools/valgrind/tsan/ignores.txt',
340 '--suppressions=../tools/valgrind/tsan/suppressions.txt',
341 '--log-file=%s/log.%%p' % (self
.tool_log_dir
,)] + cmd
342 elif self
.options
.tool
!= None:
343 raise LaunchFailure('Invalid tool name "%s"' % (self
.options
.tool
,))
344 if self
.options
.enable_sockets
:
345 cmd
.append('--allow-nacl-socket-api=%s' % host
)
346 cmd
.extend(self
.options
.browser_flags
)