| Index: tools/telemetry/telemetry/internal/backends/mandoline/android_mandoline_backend.py
|
| diff --git a/tools/telemetry/telemetry/internal/backends/chrome/android_browser_backend.py b/tools/telemetry/telemetry/internal/backends/mandoline/android_mandoline_backend.py
|
| similarity index 27%
|
| copy from tools/telemetry/telemetry/internal/backends/chrome/android_browser_backend.py
|
| copy to tools/telemetry/telemetry/internal/backends/mandoline/android_mandoline_backend.py
|
| index 37874d484c44982d6ded459f255c6e9d27b092ab..3aadab34a9284d721dedbf361d605f7d39e7cd2a 100644
|
| --- a/tools/telemetry/telemetry/internal/backends/chrome/android_browser_backend.py
|
| +++ b/tools/telemetry/telemetry/internal/backends/mandoline/android_mandoline_backend.py
|
| @@ -1,48 +1,45 @@
|
| -# Copyright 2013 The Chromium Authors. All rights reserved.
|
| +# Copyright 2015 The Chromium Authors. All rights reserved.
|
| # Use of this source code is governed by a BSD-style license that can be
|
| # found in the LICENSE file.
|
|
|
| import logging
|
| +import os
|
| +import random
|
| +import re
|
| import sys
|
|
|
| from telemetry.core import exceptions
|
| +from telemetry.core import util
|
| +from telemetry.internal.backends.mandoline import android
|
| +from telemetry.internal.backends.mandoline import config
|
| +from telemetry.internal.backends.mandoline import mandoline_browser_backend
|
| from telemetry.internal.platform import android_platform_backend as \
|
| android_platform_backend_module
|
| -from telemetry.core import util
|
| -from telemetry.internal.backends import android_command_line_backend
|
| -from telemetry.internal.backends import browser_backend
|
| -from telemetry.internal.backends.chrome import chrome_browser_backend
|
| -from telemetry.internal import forwarders
|
|
|
| util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android')
|
| -from pylib.device import intent
|
| -
|
| -
|
| -class AndroidBrowserBackend(chrome_browser_backend.ChromeBrowserBackend):
|
| - """The backend for controlling a browser instance running on Android."""
|
| - def __init__(self, android_platform_backend, browser_options,
|
| - backend_settings, output_profile_path, extensions_to_load,
|
| - target_arch):
|
| +try:
|
| + from pylib import constants
|
| +except ImportError:
|
| + pass
|
| +
|
| +class AndroidMandolineBackend(
|
| + mandoline_browser_backend.MandolineBrowserBackend):
|
| + """The backend for controlling a mandoline browser instance running on
|
| + Android."""
|
| +
|
| + def __init__(self, android_platform_backend, browser_options, target_arch,
|
| + browser_type, build_path, package):
|
| assert isinstance(android_platform_backend,
|
| android_platform_backend_module.AndroidPlatformBackend)
|
| - super(AndroidBrowserBackend, self).__init__(
|
| + super(AndroidMandolineBackend, self).__init__(
|
| android_platform_backend,
|
| - supports_tab_control=backend_settings.supports_tab_control,
|
| - supports_extensions=False, browser_options=browser_options,
|
| - output_profile_path=output_profile_path,
|
| - extensions_to_load=extensions_to_load)
|
| - if len(extensions_to_load) > 0:
|
| - raise browser_backend.ExtensionsNotSupportedException(
|
| - 'Android browser does not support extensions.')
|
| -
|
| - # Initialize fields so that an explosion during init doesn't break in Close.
|
| - self._backend_settings = backend_settings
|
| - self._target_arch = target_arch
|
| - self._saved_sslflag = ''
|
| + browser_options=browser_options)
|
|
|
| - # TODO(tonyg): This is flaky because it doesn't reserve the port that it
|
| - # allocates. Need to fix this.
|
| - self._port = util.GetUnreservedAvailableLocalPort()
|
| + self._target_arch = target_arch
|
| + self._browser_type = browser_type
|
| + self._build_path = build_path
|
| + self._package = package
|
| + self._device_port = None
|
|
|
| # TODO(wuhu): Move to network controller backend.
|
| self.platform_backend.InstallTestCa()
|
| @@ -50,98 +47,88 @@ class AndroidBrowserBackend(chrome_browser_backend.ChromeBrowserBackend):
|
| # Kill old browser.
|
| self._KillBrowser()
|
|
|
| - if self.device.HasRoot() or self.device.NeedsSU():
|
| - if self.browser_options.profile_dir:
|
| - self.platform_backend.PushProfile(
|
| - self._backend_settings.package,
|
| - self.browser_options.profile_dir)
|
| - elif not self.browser_options.dont_override_profile:
|
| - self.platform_backend.RemoveProfile(
|
| - self._backend_settings.package,
|
| - self._backend_settings.profile_ignore_list)
|
| -
|
| - if self.browser_options.netsim:
|
| - assert self.platform_backend.use_rndis_forwarder, (
|
| - 'Netsim requires RNDIS forwarding.')
|
| - self.wpr_port_pairs = forwarders.PortPairs(
|
| - http=forwarders.PortPair(0, 80),
|
| - https=forwarders.PortPair(0, 443),
|
| - dns=forwarders.PortPair(0, 53))
|
| -
|
| - # Set the debug app if needed.
|
| - self.platform_backend.SetDebugApp(self._backend_settings.package)
|
| -
|
| @property
|
| def device(self):
|
| return self.platform_backend.device
|
|
|
| def _KillBrowser(self):
|
| if self.device.IsUserBuild():
|
| - self.platform_backend.StopApplication(self._backend_settings.package)
|
| + self.platform_backend.StopApplication(self._package)
|
| else:
|
| - self.platform_backend.KillApplication(self._backend_settings.package)
|
| + self.platform_backend.KillApplication(self._package)
|
|
|
| def Start(self):
|
| self.device.RunShellCommand('logcat -c')
|
| - if self.browser_options.startup_url:
|
| - url = self.browser_options.startup_url
|
| - elif self.browser_options.profile_dir:
|
| - url = None
|
| - else:
|
| - # If we have no existing tabs start with a blank page since default
|
| - # startup with the NTP can lead to race conditions with Telemetry
|
| - url = 'about:blank'
|
|
|
| self.platform_backend.DismissCrashDialogIfNeeded()
|
|
|
| - browser_startup_args = self.GetBrowserStartupArgs()
|
| - with android_command_line_backend.SetUpCommandLineFlags(
|
| - self.device, self._backend_settings, browser_startup_args):
|
| - self.device.StartActivity(
|
| - intent.Intent(package=self._backend_settings.package,
|
| - activity=self._backend_settings.activity,
|
| - action=None, data=url, category=None),
|
| - blocking=True)
|
| -
|
| - remote_devtools_port = self._backend_settings.GetDevtoolsRemotePort(
|
| - self.device)
|
| - self.platform_backend.ForwardHostToDevice(self._port,
|
| - remote_devtools_port)
|
| - try:
|
| - self._WaitForBrowserToComeUp()
|
| - self._InitDevtoolsClientBackend(remote_devtools_port)
|
| - except exceptions.BrowserGoneException:
|
| - logging.critical('Failed to connect to browser.')
|
| - if not (self.device.HasRoot() or self.device.NeedsSU()):
|
| - logging.critical(
|
| - 'Resolve this by either: '
|
| - '(1) Flashing to a userdebug build OR '
|
| - '(2) Manually enabling web debugging in Chrome at '
|
| - 'Settings > Developer tools > Enable USB Web debugging.')
|
| - sys.exit(1)
|
| - except:
|
| - import traceback
|
| - traceback.print_exc()
|
| - self.Close()
|
| - raise
|
| + self._port = util.GetUnreservedAvailableLocalPort()
|
| + self._device_port = self._GetAvailableDevicePort()
|
| +
|
| + self.device.adb.Forward('tcp:%d' % self._port, 'tcp:%d' % self._device_port)
|
| + logging.info('Forwarded host port %d to device port %d.' %
|
| + (self._port, self._device_port))
|
| +
|
| + args = self.GetBrowserStartupArgs()
|
| +
|
| + if self.browser_options.startup_url:
|
| + # TODO(yzshen): For now "about:blank" is not supported yet.
|
| + if self.browser_options.startup_url != "about:blank":
|
| + args.append(self.browser_options.startup_url)
|
| + else:
|
| + logging.warning('Ignoring startup URL about:blank.')
|
| +
|
| + logging.debug('Starting Mandoline %s', args)
|
| +
|
| + is_debug = 'debug' in self._browser_type
|
| + mandoline_config = config.Config(build_dir=self._build_path,
|
| + target_os=config.Config.OS_ANDROID,
|
| + target_cpu=self._target_arch,
|
| + is_debug=is_debug,
|
| + apk_name='Mandoline.apk')
|
| + shell = android.AndroidShell(mandoline_config)
|
| + shell.InitShell(self.device)
|
| +
|
| + output = sys.stdout
|
| + if not self.browser_options.show_stdout:
|
| + output = open(os.devnull, 'w')
|
| + logging_process = shell.ShowLogs(output)
|
| +
|
| + # Unlock device screen.
|
| + self.device.SendKeyEvent(constants.keyevent.KEYCODE_MENU)
|
| + shell.StartActivity(self.activity, args, output, logging_process.terminate)
|
| +
|
| + try:
|
| + self._WaitForBrowserToComeUp()
|
| + self._InitDevtoolsClientBackend(self._port)
|
| + except exceptions.BrowserGoneException:
|
| + logging.critical('Failed to connect to browser.')
|
| + # TODO(yzshen): It seems wrong to catch exception and then exit, but this
|
| + # matches the behavior of AndroidBrowserBackend. Consider fixing both.
|
| + sys.exit(1)
|
| + except:
|
| + import traceback
|
| + traceback.print_exc()
|
| + self.Close()
|
| + raise
|
|
|
| def GetBrowserStartupArgs(self):
|
| - args = super(AndroidBrowserBackend, self).GetBrowserStartupArgs()
|
| - args.append('--enable-remote-debugging')
|
| - args.append('--disable-fre')
|
| - args.append('--disable-external-intent-requests')
|
| + args = super(AndroidMandolineBackend, self).GetBrowserStartupArgs()
|
| + # TODO(yzshen): Mandoline on Android doesn't support excessively long
|
| + # command line yet. Remove fieldtrial-related flags as a temp fix because
|
| + # they are long and not processed by Mandoline anyway.
|
| + # http://crbug.com/514285
|
| + args = [arg for arg in args if arg.find('--force-fieldtrials=') == -1 and
|
| + arg.find('--force-fieldtrial-params=') == -1]
|
| + args.append('--remote-debugging-port=%i' % self._device_port)
|
| return args
|
|
|
| @property
|
| def pid(self):
|
| - pids = self.device.GetPids(self._backend_settings.package)
|
| - if not pids or self._backend_settings.package not in pids:
|
| + pids = self.device.GetPids(self._package)
|
| + if not pids or self._package not in pids:
|
| raise exceptions.BrowserGoneException(self.browser)
|
| - if len(pids[self._backend_settings.package]) > 1:
|
| - raise Exception(
|
| - 'At most one instance of process %s expected but found pids: '
|
| - '%s' % (self._backend_settings.package, pids))
|
| - return int(pids[self._backend_settings.package][0])
|
| + return int(pids[self._package])
|
|
|
| @property
|
| def browser_directory(self):
|
| @@ -149,32 +136,34 @@ class AndroidBrowserBackend(chrome_browser_backend.ChromeBrowserBackend):
|
|
|
| @property
|
| def profile_directory(self):
|
| - return self._backend_settings.profile_dir
|
| + raise NotImplementedError()
|
|
|
| @property
|
| def package(self):
|
| - return self._backend_settings.package
|
| + return self._package
|
|
|
| @property
|
| def activity(self):
|
| - return self._backend_settings.activity
|
| + return 'MandolineActivity'
|
|
|
| def __del__(self):
|
| self.Close()
|
|
|
| def Close(self):
|
| - super(AndroidBrowserBackend, self).Close()
|
| + super(AndroidMandolineBackend, self).Close()
|
|
|
| self.platform_backend.RemoveTestCa()
|
|
|
| self._KillBrowser()
|
|
|
| - if self._output_profile_path:
|
| - self.platform_backend.PullProfile(
|
| - self._backend_settings.package, self._output_profile_path)
|
| + if self._port:
|
| + # TODO(yzshen): Consider remove the port forwarding. Currently AdbWrapper
|
| + # doesn't expose necessary API.
|
| + self._port = None
|
| + self._device_port = None
|
|
|
| def IsBrowserRunning(self):
|
| - return self.platform_backend.IsAppRunning(self._backend_settings.package)
|
| + return self.platform_backend.IsAppRunning(self._package)
|
|
|
| def GetStandardOutput(self):
|
| return self.platform_backend.GetStandardOutput()
|
| @@ -185,3 +174,31 @@ class AndroidBrowserBackend(chrome_browser_backend.ChromeBrowserBackend):
|
| @property
|
| def should_ignore_certificate_errors(self):
|
| return not self.platform_backend.is_test_ca_installed
|
| +
|
| + def _GetAvailableDevicePort(self):
|
| + used_ports = []
|
| + netstat_results = self.device.RunShellCommand(
|
| + ['netstat', '-a'], check_return=True, large_output=True)
|
| + pattern = re.compile(r"^(127\.0\.0\.1:|localhost:)(\d+)$")
|
| + for single_connect in netstat_results:
|
| + # Column 3 is the local address which we want to check with.
|
| + connect_results = single_connect.split()
|
| + if connect_results[0] != 'tcp':
|
| + continue
|
| + if len(connect_results) < 6:
|
| + raise Exception('Unexpected format while parsing netstat line: ' +
|
| + single_connect)
|
| + result = re.match(pattern, connect_results[3])
|
| + if result:
|
| + used_ports.append(int(result.group(2)))
|
| +
|
| + # The range of ephemeral ports on Android.
|
| + dynamic_port_begin = 32768
|
| + dynamic_port_end = 61000
|
| + assert len(used_ports) < dynamic_port_end - dynamic_port_begin + 1
|
| +
|
| + available_port = random.randint(dynamic_port_begin, dynamic_port_end)
|
| + while available_port in used_ports:
|
| + available_port = random.randint(dynamic_port_begin, dynamic_port_end)
|
| +
|
| + return available_port
|
|
|