summaryrefslogtreecommitdiff
path: root/chromium/mojo/tools
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@theqtcompany.com>2015-08-14 11:38:45 +0200
committerAllan Sandfeld Jensen <allan.jensen@theqtcompany.com>2015-08-14 17:16:47 +0000
commit3a97ca8dd9b96b599ae2d33e40df0dd2f7ea5859 (patch)
tree43cc572ba067417c7341db81f71ae7cc6e0fcc3e /chromium/mojo/tools
parentf61ab1ac7f855cd281809255c0aedbb1895e1823 (diff)
downloadqtwebengine-chromium-3a97ca8dd9b96b599ae2d33e40df0dd2f7ea5859.tar.gz
BASELINE: Update chromium to 45.0.2454.40
Change-Id: Id2121d9f11a8fc633677236c65a3e41feef589e4 Reviewed-by: Andras Becsi <andras.becsi@theqtcompany.com>
Diffstat (limited to 'chromium/mojo/tools')
-rwxr-xr-xchromium/mojo/tools/android_mojo_shell.py29
-rwxr-xr-xchromium/mojo/tools/apptest_runner.py180
-rw-r--r--chromium/mojo/tools/data/apptests61
-rw-r--r--chromium/mojo/tools/mopy/android.py537
-rw-r--r--chromium/mojo/tools/mopy/config.py125
-rw-r--r--chromium/mojo/tools/mopy/dart_apptest.py38
-rw-r--r--chromium/mojo/tools/mopy/gn.py141
-rw-r--r--chromium/mojo/tools/mopy/gtest.py265
-rw-r--r--chromium/mojo/tools/mopy/log.py29
-rw-r--r--chromium/mojo/tools/mopy/paths.py73
-rw-r--r--chromium/mojo/tools/mopy/print_process_error.py22
-rw-r--r--chromium/mojo/tools/mopy/test_util.py95
-rwxr-xr-xchromium/mojo/tools/rev_sdk.py15
13 files changed, 669 insertions, 941 deletions
diff --git a/chromium/mojo/tools/android_mojo_shell.py b/chromium/mojo/tools/android_mojo_shell.py
index 73541d2bd6f..032557f5795 100755
--- a/chromium/mojo/tools/android_mojo_shell.py
+++ b/chromium/mojo/tools/android_mojo_shell.py
@@ -9,7 +9,6 @@ import sys
from mopy.android import AndroidShell
from mopy.config import Config
-from mopy.paths import Paths
USAGE = ("android_mojo_shell.py [<shell-and-app-args>] [<mojo-app>]")
@@ -24,26 +23,26 @@ def main():
debug_group.add_argument('--release', help='Release build', default=False,
dest='debug', action='store_false')
parser.add_argument('--target-cpu', help='CPU architecture to run for.',
- choices=['x64', 'x86', 'arm'])
+ choices=['x64', 'x86', 'arm'], default='arm')
parser.add_argument('--origin', help='Origin for mojo: URLs.',
default='localhost')
- parser.add_argument('--target-device', help='Device to run on.')
- launcher_args, args = parser.parse_known_args()
+ parser.add_argument('--device', help='Serial number of the target device.')
+ parser.add_argument("--verbose", default=False, action='store_true')
+ runner_args, args = parser.parse_known_args()
+
+ logger = logging.getLogger()
+ logging.basicConfig(stream=sys.stdout, format="%(levelname)s:%(message)s")
+ logger.setLevel(logging.DEBUG if runner_args.verbose else logging.WARNING)
+ logger.debug("Initialized logging: level=%s" % logger.level)
config = Config(target_os=Config.OS_ANDROID,
- target_cpu=launcher_args.target_cpu,
- is_debug=launcher_args.debug,
+ target_cpu=runner_args.target_cpu,
+ is_debug=runner_args.debug,
apk_name="MojoRunner.apk")
- paths = Paths(config)
- shell = AndroidShell(paths.target_mojo_shell_path, paths.build_dir,
- paths.adb_path, launcher_args.target_device)
-
- extra_shell_args = shell.PrepareShellRun(launcher_args.origin)
- args.extend(extra_shell_args)
-
- shell.CleanLogs()
+ shell = AndroidShell(config)
+ shell.InitShell(runner_args.origin, runner_args.device)
p = shell.ShowLogs()
- shell.StartShell(args, sys.stdout, p.terminate)
+ shell.StartActivity('MojoShellActivity', args, sys.stdout, p.terminate)
return 0
diff --git a/chromium/mojo/tools/apptest_runner.py b/chromium/mojo/tools/apptest_runner.py
index 21f25fda82b..6e0d2a3885c 100755
--- a/chromium/mojo/tools/apptest_runner.py
+++ b/chromium/mojo/tools/apptest_runner.py
@@ -6,84 +6,140 @@
"""A test runner for gtest application tests."""
import argparse
+import json
import logging
+import os
import sys
+import time
-from mopy import dart_apptest
from mopy import gtest
-from mopy.android import AndroidShell
from mopy.config import Config
-from mopy.gn import ConfigForGNArgs, ParseGNConfig
-from mopy.log import InitLogging
-from mopy.paths import Paths
-
-
-_logger = logging.getLogger()
def main():
- parser = argparse.ArgumentParser(description="A test runner for application "
- "tests.")
-
- parser.add_argument("--verbose", help="be verbose (multiple times for more)",
- default=0, dest="verbose_count", action="count")
- parser.add_argument("test_list_file", type=file,
- help="a file listing apptests to run")
- parser.add_argument("build_dir", type=str,
- help="the build output directory")
+ parser = argparse.ArgumentParser(description="An application test runner.")
+ parser.add_argument("build_dir", type=str, help="The build output directory.")
+ parser.add_argument("--verbose", default=False, action='store_true',
+ help="Print additional logging information.")
+ parser.add_argument('--repeat-count', default=1, metavar='INT',
+ action='store', type=int,
+ help="The number of times to repeat the set of tests.")
+ parser.add_argument('--write-full-results-to', metavar='FILENAME',
+ help='The path to write the JSON list of full results.')
+ parser.add_argument("--test-list-file", metavar='FILENAME', type=file,
+ default=os.path.abspath(os.path.join(__file__, os.pardir,
+ "data", "apptests")),
+ help="The file listing apptests to run.")
args = parser.parse_args()
- InitLogging(args.verbose_count)
- config = ConfigForGNArgs(ParseGNConfig(args.build_dir))
+ gtest.set_color()
+ logger = logging.getLogger()
+ logging.basicConfig(stream=sys.stdout, format="%(levelname)s:%(message)s")
+ logger.setLevel(logging.DEBUG if args.verbose else logging.WARNING)
+ logger.debug("Initialized logging: level=%s" % logger.level)
- _logger.debug("Test list file: %s", args.test_list_file)
+ logger.debug("Test list file: %s", args.test_list_file)
+ config = Config(args.build_dir)
execution_globals = {"config": config}
exec args.test_list_file in execution_globals
test_list = execution_globals["tests"]
- _logger.debug("Test list: %s" % test_list)
+ logger.debug("Test list: %s" % test_list)
- extra_args = []
+ shell = None
if config.target_os == Config.OS_ANDROID:
- paths = Paths(config)
- shell = AndroidShell(paths.target_mojo_shell_path, paths.build_dir,
- paths.adb_path)
- extra_args.extend(shell.PrepareShellRun('localhost'))
- else:
- shell = None
-
- gtest.set_color()
-
- exit_code = 0
- for test_dict in test_list:
- test = test_dict["test"]
- test_name = test_dict.get("name", test)
- test_type = test_dict.get("type", "gtest")
- test_args = test_dict.get("test-args", [])
- shell_args = test_dict.get("shell-args", []) + extra_args
-
- _logger.info("Will start: %s" % test_name)
- print "Running %s...." % test_name,
- sys.stdout.flush()
-
- if test_type == "dart":
- apptest_result = dart_apptest.run_test(config, shell, test_dict,
- shell_args, {test: test_args})
- elif test_type == "gtest":
- apptest_result = gtest.run_fixtures(config, shell, test_dict,
- test, False,
- test_args, shell_args)
- elif test_type == "gtest_isolated":
- apptest_result = gtest.run_fixtures(config, shell, test_dict,
- test, True, test_args, shell_args)
- else:
- apptest_result = "Invalid test type in %r" % test_dict
-
- if apptest_result != "Succeeded":
- exit_code = 1
- print apptest_result
- _logger.info("Completed: %s" % test_name)
-
- return exit_code
+ from mopy.android import AndroidShell
+ shell = AndroidShell(config)
+ result = shell.InitShell()
+ if result != 0:
+ return result
+
+ tests = []
+ failed = []
+ failed_suites = 0
+ for _ in range(args.repeat_count):
+ for test_dict in test_list:
+ test = test_dict["test"]
+ test_name = test_dict.get("name", test)
+ test_type = test_dict.get("type", "gtest")
+ test_args = test_dict.get("args", [])
+
+ print "Running %s...%s" % (test_name, ("\n" if args.verbose else "")),
+ sys.stdout.flush()
+
+ assert test_type in ("gtest", "gtest_isolated")
+ isolate = test_type == "gtest_isolated"
+ (test, fail) = gtest.run_apptest(config, shell, test_args, test, isolate)
+ tests.extend(test)
+ failed.extend(fail)
+ result = test and not fail
+ print "[ PASSED ]" if result else "[ FAILED ]",
+ print test_name if args.verbose or not result else ""
+ # Abort when 3 apptest suites, or a tenth of all, have failed.
+ # base::TestLauncher does this for timeouts and unknown results.
+ failed_suites += 0 if result else 1
+ if failed_suites >= max(3, len(test_list) / 10):
+ print "Too many failing suites (%d), exiting now." % failed_suites
+ failed.append("Test runner aborted for excessive failures.")
+ break;
+
+ if failed:
+ break;
+
+ print "[==========] %d tests ran." % len(tests)
+ print "[ PASSED ] %d tests." % (len(tests) - len(failed))
+ if failed:
+ print "[ FAILED ] %d tests, listed below:" % len(failed)
+ for failure in failed:
+ print "[ FAILED ] %s" % failure
+
+ if args.write_full_results_to:
+ _WriteJSONResults(tests, failed, args.write_full_results_to)
+
+ return 1 if failed else 0
+
+
+def _WriteJSONResults(tests, failed, write_full_results_to):
+ """Write the apptest results in the Chromium JSON test results format.
+ See <http://www.chromium.org/developers/the-json-test-results-format>
+ TODO(msw): Use Chromium and TYP testing infrastructure.
+ TODO(msw): Use GTest Suite.Fixture names, not the apptest names.
+ Adapted from chrome/test/mini_installer/test_installer.py
+ """
+ results = {
+ 'interrupted': False,
+ 'path_delimiter': '.',
+ 'version': 3,
+ 'seconds_since_epoch': time.time(),
+ 'num_failures_by_type': {
+ 'FAIL': len(failed),
+ 'PASS': len(tests) - len(failed),
+ },
+ 'tests': {}
+ }
+
+ for test in tests:
+ value = {
+ 'expected': 'PASS',
+ 'actual': 'FAIL' if test in failed else 'PASS',
+ 'is_unexpected': True if test in failed else False,
+ }
+ _AddPathToTrie(results['tests'], test, value)
+
+ with open(write_full_results_to, 'w') as fp:
+ json.dump(results, fp, indent=2)
+ fp.write('\n')
+
+ return results
+
+
+def _AddPathToTrie(trie, path, value):
+ if '.' not in path:
+ trie[path] = value
+ return
+ directory, rest = path.split('.', 1)
+ if directory not in trie:
+ trie[directory] = {}
+ _AddPathToTrie(trie[directory], rest, value)
if __name__ == '__main__':
diff --git a/chromium/mojo/tools/data/apptests b/chromium/mojo/tools/data/apptests
index 54a26adbc3c..140ee6a60aa 100644
--- a/chromium/mojo/tools/data/apptests
+++ b/chromium/mojo/tools/data/apptests
@@ -11,15 +11,12 @@
# # Optional display name (otherwise the entry for "test" above is used).
# "name": "mojo:test_app_url (more details)",
# # Optional test type. Valid values:
-# # * "gtest" (default)
-# # * "gtest_isolated": like "gtest", but run with fixture isolation,
-# # i.e., each test in a fresh mojo_shell)
-# # * "dart".
+# # * "gtest": (default)
+# # * "gtest_isolated": like "gtest", but run with fixture isolation.
+# # i.e., each test in a fresh mojo_shell
# "type": "gtest",
-# # Optional arguments for the apptest.
-# "test-args": ["--an_arg", "another_arg"],
-# # Optional arguments for the shell.
-# "shell-args": ["--some-flag-for-the-shell", "--another-flag"],
+# # Optional arguments for the shell or test.
+# "args": ["--some-flag-for-the-shell", "--some-flag-for-the-test"],
# }
#
# TODO(vtl|msw): Add a way of specifying data dependencies.
@@ -27,32 +24,64 @@
tests = [
{
"test": "mojo:clipboard_apptests",
+ "type": "gtest_isolated",
},
{
"test": "mojo:network_service_apptests",
+ "type": "gtest_isolated",
},
- # TODO(msw|jam): Fix and enable the shell_apptests: http://crbug.com/479316
+ # TODO(msw|jam): Fix and enable the runner_apptests: http://crbug.com/479316
#{
- # "test": "mojo:shell_apptests",
+ # "test": "mojo:runner_apptests",
+ # "type": "gtest_isolated",
#},
+ {
+ "test": "mojo:view_manager_apptests",
+ "type": "gtest_isolated",
+ "args": ["--use-headless-config"]
+ },
]
# TODO(msw): Get these tests passing on Android too. http://crbug.com/486220
if config.target_os != config.OS_ANDROID:
tests += [
{
+ "test": "mojo:filesystem_apptests",
+ "type": "gtest_isolated",
+ },
+ {
"test": "mojo:html_viewer_apptests",
- "shell-args": ["--is-headless"],
+ "type": "gtest_isolated",
+ "args": ["--use-test-config"]
+ },
+ {
+ "test": "mojo:html_viewer_apptests",
+ "type": "gtest_isolated",
+ "args": ["--use-test-config", "--oopifs",
+ "--enable-html-viewer-test-interface"]
},
{
- "test": "mojo:view_manager_apptests",
+ "test": "mojo:mandoline_browser_apptests",
+ "type": "gtest_isolated",
+ "args": ["--use-headless-config"]
+ },
+ {
+ "test": "mojo:mandoline_frame_apptests",
+ "type": "gtest_isolated",
+ "args": ["--use-headless-config"]
+ },
+ # TODO(xhwang): Fix and enable mojo:media_pipeline_integration_apptests.
+ # http://crbug.com/501417
+ {
+ "test": "mojo:media_apptests",
"type": "gtest_isolated",
- "shell-args": [
- "--use-headless-config",
- "--url-mappings=mojo:window_manager=mojo:test_window_manager"
- ]
},
{
"test": "mojo:resource_provider_apptests",
+ "type": "gtest_isolated",
+ },
+ {
+ "test": "mojo:sql_apptests",
+ "type": "gtest_isolated",
},
]
diff --git a/chromium/mojo/tools/mopy/android.py b/chromium/mojo/tools/mopy/android.py
index 2354c8e5f91..ec275db37f0 100644
--- a/chromium/mojo/tools/mopy/android.py
+++ b/chromium/mojo/tools/mopy/android.py
@@ -3,24 +3,26 @@
# found in the LICENSE file.
import atexit
-import datetime
-import email.utils
-import hashlib
import itertools
-import json
import logging
-import math
import os
-import os.path
-import random
+import signal
import subprocess
import sys
import threading
import time
import urlparse
-import SimpleHTTPServer
-import SocketServer
+from .paths import Paths
+
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir,
+ os.pardir, 'build', 'android'))
+from pylib import constants
+from pylib.base import base_test_runner
+from pylib.device import device_errors
+from pylib.device import device_utils
+from pylib.utils import base_error
+from pylib.utils import apk_helper
# Tags used by the mojo shell application logs.
@@ -33,274 +35,93 @@ LOGCAT_TAGS = [
'chromium',
]
-MOJO_SHELL_PACKAGE_NAME = 'org.chromium.mojo.shell'
-
MAPPING_PREFIX = '--map-origin='
-ZERO = datetime.timedelta(0)
-
-class UTC_TZINFO(datetime.tzinfo):
- """UTC time zone representation."""
-
- def utcoffset(self, _):
- return ZERO
-
- def tzname(self, _):
- return "UTC"
-
- def dst(self, _):
- return ZERO
-
-UTC = UTC_TZINFO()
-
-_logger = logging.getLogger()
-
-
-class _SilentTCPServer(SocketServer.TCPServer):
- """
- A TCPServer that won't display any error, unless debugging is enabled. This is
- useful because the client might stop while it is fetching an URL, which causes
- spurious error messages.
- """
- def handle_error(self, request, client_address):
- """
- Override the base class method to have conditional logging.
- """
- if logging.getLogger().isEnabledFor(logging.DEBUG):
- SocketServer.TCPServer.handle_error(self, request, client_address)
-
-
-def _GetHandlerClassForPath(base_path):
- class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
- """
- Handler for SocketServer.TCPServer that will serve the files from
- |base_path| directory over http.
- """
-
- def __init__(self, *args, **kwargs):
- self.etag = None
- SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs)
-
- def get_etag(self):
- if self.etag:
- return self.etag
-
- path = self.translate_path(self.path)
- if not os.path.isfile(path):
- return None
-
- sha256 = hashlib.sha256()
- BLOCKSIZE = 65536
- with open(path, 'rb') as hashed:
- buf = hashed.read(BLOCKSIZE)
- while len(buf) > 0:
- sha256.update(buf)
- buf = hashed.read(BLOCKSIZE)
- self.etag = '"%s"' % sha256.hexdigest()
- return self.etag
-
- def send_head(self):
- # Always close the connection after each request, as the keep alive
- # support from SimpleHTTPServer doesn't like when the client requests to
- # close the connection before downloading the full response content.
- # pylint: disable=W0201
- self.close_connection = 1
-
- path = self.translate_path(self.path)
- if os.path.isfile(path):
- # Handle If-None-Match
- etag = self.get_etag()
- if ('If-None-Match' in self.headers and
- etag == self.headers['If-None-Match']):
- self.send_response(304)
- return None
-
- # Handle If-Modified-Since
- if ('If-None-Match' not in self.headers and
- 'If-Modified-Since' in self.headers):
- last_modified = datetime.datetime.fromtimestamp(
- math.floor(os.stat(path).st_mtime), tz=UTC)
- ims = datetime.datetime(
- *email.utils.parsedate(self.headers['If-Modified-Since'])[:6],
- tzinfo=UTC)
- if last_modified <= ims:
- self.send_response(304)
- return None
-
- return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
-
- def end_headers(self):
- path = self.translate_path(self.path)
-
- if os.path.isfile(path):
- etag = self.get_etag()
- if etag:
- self.send_header('ETag', etag)
- self.send_header('Cache-Control', 'must-revalidate')
-
- return SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self)
-
- def translate_path(self, path):
- path_from_current = (
- SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path))
- return os.path.join(base_path, os.path.relpath(path_from_current))
-
- def log_message(self, *_):
- """
- Override the base class method to disable logging.
- """
- pass
-
- RequestHandler.protocol_version = 'HTTP/1.1'
- return RequestHandler
-
-
-def _IsMapOrigin(arg):
- """Returns whether arg is a --map-origin argument."""
- return arg.startswith(MAPPING_PREFIX)
-
-
-def _Split(l, pred):
- positive = []
- negative = []
- for v in l:
- if pred(v):
- positive.append(v)
- else:
- negative.append(v)
- return (positive, negative)
-
def _ExitIfNeeded(process):
- """
- Exits |process| if it is still alive.
- """
+ """Exits |process| if it is still alive."""
if process.poll() is None:
process.kill()
class AndroidShell(object):
- """ Allows to set up and run a given mojo shell binary on an Android device.
-
- Args:
- shell_apk_path: path to the shell Android binary
- local_dir: directory where locally build Mojo apps will be served, optional
- adb_path: path to adb, optional if adb is in PATH
- target_device: device to run on, if multiple devices are connected
"""
- def __init__(
- self, shell_apk_path, local_dir=None, adb_path="adb", target_device=None,
- target_package=MOJO_SHELL_PACKAGE_NAME):
- self.shell_apk_path = shell_apk_path
- self.adb_path = adb_path
- self.local_dir = local_dir
- self.target_device = target_device
- self.target_package = target_package
-
+ Used to set up and run a given mojo shell binary on an Android device.
+ |config| is the mopy.config.Config for the build.
+ """
+ def __init__(self, config):
+ self.adb_path = constants.GetAdbPath()
+ self.config = config
+ self.paths = Paths(config)
+ self.device = None
+ self.shell_args = []
+ self.target_package = apk_helper.GetPackageName(self.paths.apk_path)
+ self.temp_gdb_dir = None
+ # This is used by decive_utils.Install to check if the apk needs updating.
+ constants.SetOutputDirectory(self.paths.build_dir)
+
+ # TODO(msw): Use pylib's adb_wrapper and device_utils instead.
def _CreateADBCommand(self, args):
- adb_command = [self.adb_path]
- if self.target_device:
- adb_command.extend(['-s', self.target_device])
+ adb_command = [self.adb_path, '-s', self.device.adb.GetDeviceSerial()]
adb_command.extend(args)
+ logging.getLogger().debug("Command: %s", " ".join(adb_command))
return adb_command
- def _ReadFifo(self, fifo_path, pipe, on_fifo_closed, max_attempts=5):
+ def _ReadFifo(self, path, pipe, on_fifo_closed, max_attempts=5):
"""
- Reads |fifo_path| on the device and write the contents to |pipe|. Calls
- |on_fifo_closed| when the fifo is closed. This method will try to find the
- path up to |max_attempts|, waiting 1 second between each attempt. If it
- cannot find |fifo_path|, a exception will be raised.
+ Reads the fifo at |path| on the device and write the contents to |pipe|.
+ Calls |on_fifo_closed| when the fifo is closed. This method will try to find
+ the path up to |max_attempts|, waiting 1 second between each attempt. If it
+ cannot find |path|, a exception will be raised.
"""
- fifo_command = self._CreateADBCommand(
- ['shell', 'test -e "%s"; echo $?' % fifo_path])
-
def Run():
def _WaitForFifo():
for _ in xrange(max_attempts):
- if subprocess.check_output(fifo_command)[0] == '0':
+ if self.device.FileExists(path):
return
time.sleep(1)
- if on_fifo_closed:
- on_fifo_closed()
- raise Exception("Unable to find fifo.")
+ on_fifo_closed()
+ raise Exception("Unable to find fifo: %s" % path)
_WaitForFifo()
stdout_cat = subprocess.Popen(self._CreateADBCommand([
- 'shell',
- 'cat',
- fifo_path]),
+ 'shell',
+ 'cat',
+ path]),
stdout=pipe)
atexit.register(_ExitIfNeeded, stdout_cat)
stdout_cat.wait()
- if on_fifo_closed:
- on_fifo_closed()
+ on_fifo_closed()
thread = threading.Thread(target=Run, name="StdoutRedirector")
thread.start()
- def _MapPort(self, device_port, host_port):
+ def _StartHttpServerForDirectory(self, path):
+ test_server_helper = base_test_runner.BaseTestRunner(self.device, None)
+ ports = test_server_helper.LaunchTestHttpServer(path)
+ atexit.register(test_server_helper.ShutdownHelperToolsForTestSuite)
+ print 'Hosting %s at http://127.0.0.1:%d' % (path, ports[1])
+ return 'http://127.0.0.1:%d/' % ports[0]
+
+ def _StartHttpServerForOriginMapping(self, mapping):
"""
- Maps the device port to the host port. If |device_port| is 0, a random
- available port is chosen. Returns the device port.
+ If |mapping| points at a local file starts an http server to serve files
+ from the directory and returns the new mapping. This is intended to be
+ called for every --map-origin value.
"""
- def _FindAvailablePortOnDevice():
- opened = subprocess.check_output(
- self._CreateADBCommand(['shell', 'netstat']))
- opened = [int(x.strip().split()[3].split(':')[1])
- for x in opened if x.startswith(' tcp')]
- while True:
- port = random.randint(4096, 16384)
- if port not in opened:
- return port
- if device_port == 0:
- device_port = _FindAvailablePortOnDevice()
- subprocess.Popen(self._CreateADBCommand([
- "reverse",
- "tcp:%d" % device_port,
- "tcp:%d" % host_port])).wait()
-
- unmap_command = self._CreateADBCommand(["reverse", "--remove",
- "tcp:%d" % device_port])
-
- def _UnmapPort():
- subprocess.Popen(unmap_command)
- atexit.register(_UnmapPort)
- return device_port
-
- def _StartHttpServerForDirectory(self, path, port=0):
- """Starts an http server serving files from |path|. Returns the local
- url."""
- assert path
- httpd = _SilentTCPServer(('127.0.0.1', 0), _GetHandlerClassForPath(path))
- atexit.register(httpd.shutdown)
-
- http_thread = threading.Thread(target=httpd.serve_forever)
- http_thread.daemon = True
- http_thread.start()
-
- print 'Hosting %s at http://127.0.0.1:%d' % (path, httpd.server_address[1])
- return 'http://127.0.0.1:%d/' % self._MapPort(port, httpd.server_address[1])
-
- def _StartHttpServerForOriginMapping(self, mapping, port):
- """If |mapping| points at a local file starts an http server to serve files
- from the directory and returns the new mapping.
-
- This is intended to be called for every --map-origin value."""
parts = mapping.split('=')
if len(parts) != 2:
return mapping
dest = parts[1]
- # If the destination is a url, don't map it.
+ # If the destination is a URL, don't map it.
if urlparse.urlparse(dest)[0]:
return mapping
# Assume the destination is a local file. Start a local server that
# redirects to it.
- localUrl = self._StartHttpServerForDirectory(dest, port)
- print 'started server at %s for %s' % (dest, localUrl)
+ localUrl = self._StartHttpServerForDirectory(dest)
return parts[0] + '=' + localUrl
def _StartHttpServerForOriginMappings(self, map_parameters):
- """Calls _StartHttpServerForOriginMapping for every --map-origin
- argument."""
+ """Calls _StartHttpServerForOriginMapping for every --map-origin arg."""
if not map_parameters:
return []
@@ -309,41 +130,123 @@ class AndroidShell(object):
sorted(original_values)
result = []
for value in original_values:
- result.append(self._StartHttpServerForOriginMapping(value, 0))
+ result.append(self._StartHttpServerForOriginMapping(value))
return [MAPPING_PREFIX + ','.join(result)]
- def PrepareShellRun(self, origin=None):
- """ Prepares for StartShell: runs adb as root and installs the apk. If the
- origin specified is 'localhost', a local http server will be set up to serve
- files from the build directory along with port forwarding.
-
- Returns arguments that should be appended to shell argument list."""
- if 'cannot run as root' in subprocess.check_output(
- self._CreateADBCommand(['root'])):
- raise Exception("Unable to run adb as root.")
- subprocess.check_call(
- self._CreateADBCommand(['install', '-r', self.shell_apk_path, '-i',
- self.target_package]))
- atexit.register(self.StopShell)
-
- extra_shell_args = []
+ def InitShell(self, origin='localhost', device=None):
+ """
+ Runs adb as root, starts an origin server, and installs the apk as needed.
+ |origin| is the origin for mojo: URLs; if its value is 'localhost', a local
+ http server will be set up to serve files from the build directory.
+ |device| is the target device to run on, if multiple devices are connected.
+ Returns 0 on success or a non-zero exit code on a terminal failure.
+ """
+ try:
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ if device:
+ self.device = next((d for d in devices if d == device), None)
+ if not self.device:
+ raise device_errors.DeviceUnreachableError(device)
+ elif devices:
+ self.device = devices[0]
+ else:
+ raise device_errors.NoDevicesError()
+
+ logging.getLogger().debug("Using device: %s", self.device)
+ # Clean the logs on the device to avoid displaying prior activity.
+ subprocess.check_call(self._CreateADBCommand(['logcat', '-c']))
+ self.device.EnableRoot()
+ self.device.Install(self.paths.apk_path)
+ except base_error.BaseError as e:
+ # Report "device not found" as infra failures. See http://crbug.com/493900
+ print "Exception in AndroidShell.InitShell:\n%s" % str(e)
+ if e.is_infra_error or "error: device not found" in str(e):
+ return constants.INFRA_EXIT_CODE
+ return constants.ERROR_EXIT_CODE
+
if origin is 'localhost':
- origin = self._StartHttpServerForDirectory(self.local_dir, 0)
+ origin = self._StartHttpServerForDirectory(self.paths.build_dir)
if origin:
- extra_shell_args.append("--origin=" + origin)
- return extra_shell_args
+ self.shell_args.append("--origin=" + origin)
+ return 0
+
+ def _GetProcessId(self, process):
+ """Returns the process id of the process on the remote device."""
+ while True:
+ line = process.stdout.readline()
+ pid_command = 'launcher waiting for GDB. pid: '
+ index = line.find(pid_command)
+ if index != -1:
+ return line[index + len(pid_command):].strip()
+ return 0
+
+ def _GetLocalGdbPath(self):
+ """Returns the path to the android gdb."""
+ if self.config.target_cpu == "arm":
+ return os.path.join(constants.ANDROID_NDK_ROOT, "toolchains",
+ "arm-linux-androideabi-4.9", "prebuilt",
+ "linux-x86_64", "bin", "arm-linux-androideabi-gdb")
+ elif self.config.target_cpu == "x86":
+ return os.path.join(constants.ANDROID_NDK_ROOT, "toolchains",
+ "x86-4.9", "prebuilt", "linux-x86_64", "bin",
+ "i686-linux-android-gdb")
+ elif self.config.target_cpu == "x64":
+ return os.path.join(constants.ANDROID_NDK_ROOT, "toolchains",
+ "x86_64-4.9", "prebuilt", "linux-x86_64", "bin",
+ "x86_64-linux-android-gdb")
+ else:
+ raise Exception("Unknown target_cpu: %s" % self.config.target_cpu)
- def StartShell(self,
- arguments,
- stdout=None,
- on_application_stop=None):
+ def _WaitForProcessIdAndStartGdb(self, process):
"""
- Starts the mojo shell, passing it the given arguments.
-
- The |arguments| list must contain the "--origin=" arg from PrepareShellRun.
- If |stdout| is not None, it should be a valid argument for subprocess.Popen.
+ Waits until we see the process id from the remote device, starts up
+ gdbserver on the remote device, and gdb on the local device.
+ """
+ # Wait until we see "PID"
+ pid = self._GetProcessId(process)
+ assert pid != 0
+ # No longer need the logcat process.
+ process.kill()
+ # Disable python's processing of SIGINT while running gdb. Otherwise
+ # control-c doesn't work well in gdb.
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
+ gdbserver_process = subprocess.Popen(self._CreateADBCommand(['shell',
+ 'gdbserver',
+ '--attach',
+ ':5039',
+ pid]))
+ atexit.register(_ExitIfNeeded, gdbserver_process)
+
+ gdbinit_path = os.path.join(self.temp_gdb_dir, 'gdbinit')
+ _CreateGdbInit(self.temp_gdb_dir, gdbinit_path, self.paths.build_dir)
+
+ # Wait a second for gdb to start up on the device. Without this the local
+ # gdb starts before the remote side has registered the port.
+ # TODO(sky): maybe we should try a couple of times and then give up?
+ time.sleep(1)
+
+ local_gdb_process = subprocess.Popen([self._GetLocalGdbPath(),
+ "-x",
+ gdbinit_path],
+ cwd=self.temp_gdb_dir)
+ atexit.register(_ExitIfNeeded, local_gdb_process)
+ local_gdb_process.wait()
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+
+ def StartActivity(self,
+ activity_name,
+ arguments,
+ stdout,
+ on_fifo_closed,
+ temp_gdb_dir=None):
"""
- STDOUT_PIPE = "/data/data/%s/stdout.fifo" % self.target_package
+ Starts the shell with the given |arguments|, directing output to |stdout|.
+ |on_fifo_closed| will be run if the FIFO can't be found or when it's closed.
+ |temp_gdb_dir| is set to a location with appropriate symlinks for gdb to
+ find when attached to the device's remote process on startup.
+ """
+ assert self.device
+ arguments += self.shell_args
cmd = self._CreateADBCommand([
'shell',
@@ -351,57 +254,81 @@ class AndroidShell(object):
'start',
'-S',
'-a', 'android.intent.action.VIEW',
- '-n', '%s/%s.MojoShellActivity' % (self.target_package,
- MOJO_SHELL_PACKAGE_NAME)])
-
- parameters = []
- if stdout or on_application_stop:
- subprocess.check_call(self._CreateADBCommand(
- ['shell', 'rm', '-f', STDOUT_PIPE]))
- parameters.append('--fifo-path=%s' % STDOUT_PIPE)
- max_attempts = 5
- if '--wait-for-debugger' in arguments:
- max_attempts = 200
- self._ReadFifo(STDOUT_PIPE, stdout, on_application_stop,
- max_attempts=max_attempts)
-
- # Extract map-origin arguments.
- map_parameters, other_parameters = _Split(arguments, _IsMapOrigin)
- parameters += other_parameters
+ '-n', '%s/%s.%s' % (self.target_package,
+ self.target_package,
+ activity_name)])
+
+ logcat_process = None
+ if temp_gdb_dir:
+ self.temp_gdb_dir = temp_gdb_dir
+ arguments.append('--wait-for-debugger')
+ # Remote debugging needs a port forwarded.
+ self.device.adb.Forward('tcp:5039', 'tcp:5039')
+ logcat_process = self.ShowLogs(stdout=subprocess.PIPE)
+
+ fifo_path = "/data/data/%s/stdout.fifo" % self.target_package
+ subprocess.check_call(self._CreateADBCommand(
+ ['shell', 'rm', '-f', fifo_path]))
+ arguments.append('--fifo-path=%s' % fifo_path)
+ max_attempts = 200 if '--wait-for-debugger' in arguments else 5
+ self._ReadFifo(fifo_path, stdout, on_fifo_closed, max_attempts)
+
+ # Extract map-origin args and add the extras array with commas escaped.
+ parameters = [a for a in arguments if not a.startswith(MAPPING_PREFIX)]
+ map_parameters = [a for a in arguments if a.startswith(MAPPING_PREFIX)]
parameters += self._StartHttpServerForOriginMappings(map_parameters)
+ parameters = [p.replace(',', '\,') for p in parameters]
+ cmd += ['--esa', '%s.extras' % self.target_package, ','.join(parameters)]
- if parameters:
- encodedParameters = json.dumps(parameters)
- cmd += ['--es', 'encodedParameters', encodedParameters]
-
+ atexit.register(self.kill)
with open(os.devnull, 'w') as devnull:
- subprocess.Popen(cmd, stdout=devnull).wait()
+ cmd_process = subprocess.Popen(cmd, stdout=devnull)
+ if logcat_process:
+ self._WaitForProcessIdAndStartGdb(logcat_process)
+ cmd_process.wait()
- def StopShell(self):
- """
- Stops the mojo shell.
- """
- subprocess.check_call(self._CreateADBCommand(['shell',
- 'am',
- 'force-stop',
- self.target_package]))
-
- def CleanLogs(self):
- """
- Cleans the logs on the device.
- """
- subprocess.check_call(self._CreateADBCommand(['logcat', '-c']))
+ def kill(self):
+ """Stops the mojo shell; matches the Popen.kill method signature."""
+ self.device.ForceStop(self.target_package)
- def ShowLogs(self):
- """
- Displays the log for the mojo shell.
-
- Returns the process responsible for reading the logs.
- """
+ def ShowLogs(self, stdout=sys.stdout):
+ """Displays the mojo shell logs and returns the process reading the logs."""
logcat = subprocess.Popen(self._CreateADBCommand([
'logcat',
'-s',
' '.join(LOGCAT_TAGS)]),
- stdout=sys.stdout)
+ stdout=stdout)
atexit.register(_ExitIfNeeded, logcat)
return logcat
+
+
+def _CreateGdbInit(tmp_dir, gdb_init_path, build_dir):
+ """
+ Creates the gdbinit file.
+
+ Args:
+ tmp_dir: the directory where the gdbinit and other files lives.
+ gdb_init_path: path to gdbinit
+ build_dir: path where build files are located.
+ """
+ gdbinit = ('target remote localhost:5039\n'
+ 'def reload-symbols\n'
+ ' set solib-search-path %s:%s\n'
+ 'end\n'
+ 'def info-symbols\n'
+ ' info sharedlibrary\n'
+ 'end\n'
+ 'reload-symbols\n'
+ 'echo \\n\\n'
+ 'You are now in gdb and need to type continue (or c) to continue '
+ 'execution.\\n'
+ 'gdb is in the directory %s\\n'
+ 'The following functions have been defined:\\n'
+ 'reload-symbols: forces reloading symbols. If after a crash you\\n'
+ 'still do not see symbols you likely need to create a link in\\n'
+ 'the directory you are in.\\n'
+ 'info-symbols: shows status of current shared libraries.\\n'
+ 'NOTE: you may need to type reload-symbols again after a '
+ 'crash.\\n\\n' % (tmp_dir, build_dir, tmp_dir))
+ with open(gdb_init_path, 'w') as f:
+ f.write(gdbinit)
diff --git a/chromium/mojo/tools/mopy/config.py b/chromium/mojo/tools/mopy/config.py
index ba43bd03fa7..8496d031d75 100644
--- a/chromium/mojo/tools/mopy/config.py
+++ b/chromium/mojo/tools/mopy/config.py
@@ -2,75 +2,55 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-"""Build/test configurations, which are just dictionaries. This
-"defines" the schema and provides some wrappers."""
-
+import ast
+import os.path
import platform
+import re
import sys
class Config(object):
- """A Config is basically just a wrapper around a dictionary that species a
- build/test configuration. The dictionary is accessible through the values
- member."""
+ """A Config contains a dictionary that species a build configuration."""
- # Valid values for target_os (None is also valid):
+ # Valid values for target_os:
OS_ANDROID = "android"
OS_CHROMEOS = "chromeos"
OS_LINUX = "linux"
OS_MAC = "mac"
OS_WINDOWS = "windows"
- # Valid values for target_cpu (None is also valid):
+ # Valid values for target_cpu:
ARCH_X86 = "x86"
ARCH_X64 = "x64"
ARCH_ARM = "arm"
- # Valid values for sanitizer (None is also valid):
- SANITIZER_ASAN = "asan"
-
- # Standard values for test types (test types are arbitrary strings; other
- # values are allowed).
- TEST_TYPE_DEFAULT = "default"
- TEST_TYPE_UNIT = "unit"
- TEST_TYPE_PERF = "perf"
- TEST_TYPE_INTEGRATION = "integration"
-
- def __init__(self, target_os=None, target_cpu=None, is_debug=True,
- is_clang=None, sanitizer=None, dcheck_always_on=False,
- apk_name="MojoRunner.apk", **kwargs):
- """Constructs a Config with key-value pairs specified via keyword arguments.
- If target_os is not specified, it will be set to the host OS."""
-
+ def __init__(self, build_dir=None, target_os=None, target_cpu=None,
+ is_debug=None, apk_name="MojoRunner.apk"):
+ """Function arguments take precedence over GN args and default values."""
assert target_os in (None, Config.OS_ANDROID, Config.OS_CHROMEOS,
Config.OS_LINUX, Config.OS_MAC, Config.OS_WINDOWS)
assert target_cpu in (None, Config.ARCH_X86, Config.ARCH_X64,
- Config.ARCH_ARM)
- assert isinstance(is_debug, bool)
- assert is_clang is None or isinstance(is_clang, bool)
- assert sanitizer in (None, Config.SANITIZER_ASAN)
- if "test_types" in kwargs:
- assert isinstance(kwargs["test_types"], list)
-
- self.values = {}
- self.values["target_os"] = (self.GetHostOS() if target_os is None else
- target_os)
-
- if target_cpu is None:
- if target_os == Config.OS_ANDROID:
- target_cpu = Config.ARCH_ARM
- else:
- target_cpu = self.GetHostCPUArch()
- self.values["target_cpu"] = target_cpu
-
- self.values["is_debug"] = is_debug
- self.values["is_clang"] = is_clang
- self.values["sanitizer"] = sanitizer
- self.values["dcheck_always_on"] = dcheck_always_on
- self.values["apk_name"] = apk_name
-
- self.values.update(kwargs)
+ Config.ARCH_ARM)
+ assert is_debug in (None, True, False)
+
+ self.values = {
+ "build_dir": build_dir,
+ "target_os": self.GetHostOS(),
+ "target_cpu": self.GetHostCPU(),
+ "is_debug": True,
+ "dcheck_always_on": False,
+ "is_asan": False,
+ "apk_name": apk_name,
+ }
+
+ self._ParseGNArgs()
+ if target_os is not None:
+ self.values["target_os"] = target_os
+ if target_cpu is not None:
+ self.values["target_cpu"] = target_cpu
+ if is_debug is not None:
+ self.values["is_debug"] = is_debug
@staticmethod
def GetHostOS():
@@ -83,21 +63,44 @@ class Config(object):
raise NotImplementedError("Unsupported host OS")
@staticmethod
- def GetHostCPUArch():
+ def GetHostCPU():
# Derived from //native_client/pynacl/platform.py
machine = platform.machine()
if machine in ("x86", "x86-32", "x86_32", "x8632", "i386", "i686", "ia32",
"32"):
return Config.ARCH_X86
- if machine in ("x86-64", "amd64", "x86_64", "x8664", "64"):
+ if machine in ("x86-64", "amd64", "AMD64", "x86_64", "x8664", "64"):
return Config.ARCH_X64
if machine.startswith("arm"):
return Config.ARCH_ARM
raise Exception("Cannot identify CPU arch: %s" % machine)
+ def _ParseGNArgs(self):
+ """Parse the gn config file from the build directory, if it exists."""
+ TRANSLATIONS = { "true": "True", "false": "False", }
+ if self.values["build_dir"] is None:
+ return
+ gn_file = os.path.join(self.values["build_dir"], "args.gn")
+ if not os.path.isfile(gn_file):
+ return
+
+ with open(gn_file, "r") as f:
+ for line in f:
+ line = re.sub("\s*#.*", "", line)
+ result = re.match("^\s*(\w+)\s*=\s*(.*)\s*$", line)
+ if result:
+ key = result.group(1)
+ value = result.group(2)
+ self.values[key] = ast.literal_eval(TRANSLATIONS.get(value, value))
+
# Getters for standard fields ------------------------------------------------
@property
+ def build_dir(self):
+ """Build directory path."""
+ return self.values["build_dir"]
+
+ @property
def target_os(self):
"""OS of the build/test target."""
return self.values["target_os"]
@@ -118,21 +121,11 @@ class Config(object):
return self.values["dcheck_always_on"]
@property
+ def is_asan(self):
+ """Is ASAN build?"""
+ return self.values["is_asan"]
+
+ @property
def apk_name(self):
"""Name of the APK file to run"""
return self.values["apk_name"]
-
- @property
- def is_clang(self):
- """Should use clang?"""
- return self.values["is_clang"]
-
- @property
- def sanitizer(self):
- """Sanitizer to use, if any."""
- return self.values["sanitizer"]
-
- @property
- def test_types(self):
- """List of test types to run."""
- return self.values.get("test_types", [Config.TEST_TYPE_DEFAULT])
diff --git a/chromium/mojo/tools/mopy/dart_apptest.py b/chromium/mojo/tools/mopy/dart_apptest.py
deleted file mode 100644
index 5e12b17417f..00000000000
--- a/chromium/mojo/tools/mopy/dart_apptest.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright 2014 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
-
-_logging = logging.getLogger()
-
-from mopy import test_util
-from mopy.print_process_error import print_process_error
-
-
-# TODO(erg): Support android, launched services and fixture isolation.
-def run_test(config, shell, apptest_dict, shell_args, apps_and_args=None):
- """Runs a command line and checks the output for signs of gtest failure.
-
- Args:
- config: The mopy.config.Config object for the build.
- shell_args: The arguments for mojo_shell.
- apps_and_args: A Dict keyed by application URL associated to the
- application's specific arguments.
- """
- apps_and_args = apps_and_args or {}
- output = test_util.try_run_test(config, shell, shell_args, apps_and_args)
- # Fail on output with dart unittests' "FAIL:"/"ERROR:" or a lack of "PASS:".
- # The latter condition ensures failure on broken command lines or output.
- # Check output instead of exit codes because mojo_shell always exits with 0.
- if (not output or
- '\nFAIL: ' in output or
- '\nERROR: ' in output or
- '\nPASS: ' not in output):
- print "Failed test:"
- print_process_error(
- test_util.build_command_line(config, shell_args, apps_and_args),
- output)
- return "Failed test(s) in %r" % apptest_dict
- _logging.debug("Succeeded with output:\n%s" % output)
- return "Succeeded"
diff --git a/chromium/mojo/tools/mopy/gn.py b/chromium/mojo/tools/mopy/gn.py
deleted file mode 100644
index 560e8db78dc..00000000000
--- a/chromium/mojo/tools/mopy/gn.py
+++ /dev/null
@@ -1,141 +0,0 @@
-# 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.
-
-"""
-GN-related configuration functions, e.g., to produce a Config object from a GN
-args.gn file).
-"""
-
-
-import ast
-import os.path
-import re
-
-from .config import Config
-
-
-def BuildDirectoryForConfig(config, src_root):
- """
- Returns the build directory for the given configuration.
- """
- subdir = ""
- if config.target_os == Config.OS_ANDROID:
- subdir += "android_"
- if config.target_cpu != Config.ARCH_ARM:
- subdir += config.target_cpu + "_"
- elif config.target_os == Config.OS_CHROMEOS:
- subdir += "chromeos_"
- subdir += "Debug" if config.is_debug else "Release"
- if config.sanitizer == Config.SANITIZER_ASAN:
- subdir += "_asan"
- if not(config.is_debug) and config.dcheck_always_on:
- subdir += "_dcheck"
- return os.path.join(src_root, "out", subdir)
-
-
-def GNArgsForConfig(config):
- """
- Return the arguments for gn for the given configuration. This function returns
- a dictionary with boolean values as boolean.
- """
- gn_args = {}
-
- gn_args["is_debug"] = bool(config.is_debug)
- gn_args["is_asan"] = config.sanitizer == Config.SANITIZER_ASAN
-
- if config.is_clang is not None:
- gn_args["is_clang"] = bool(config.is_clang)
- else:
- gn_args["is_clang"] = config.target_os not in (Config.OS_ANDROID,
- Config.OS_WINDOWS)
-
- if config.values.get("use_goma"):
- gn_args["use_goma"] = True
- gn_args["goma_dir"] = config.values["goma_dir"]
- else:
- gn_args["use_goma"] = False
-
- gn_args["dcheck_always_on"] = config.dcheck_always_on
-
- gn_args["mojo_use_nacl"] = config.values.get("use_nacl", False)
-
- if config.target_os == Config.OS_ANDROID:
- gn_args["os"] = "android"
- elif config.target_os == Config.OS_CHROMEOS:
- gn_args["os"] = "chromeos"
- gn_args["use_glib"] = False
- gn_args["use_system_harfbuzz"] = False
- elif config.target_os == Config.OS_LINUX:
- gn_args["use_aura"] = False
- gn_args["use_glib"] = False
- gn_args["use_system_harfbuzz"] = False
-
- gn_args["target_cpu"] = config.target_cpu
-
- extra_args = config.values.get("gn_args")
- if extra_args:
- for arg in extra_args.split():
- (name, val) = arg.split('=')
- gn_args[name] = val
-
- return gn_args
-
-
-def CommandLineForGNArgs(gn_args):
- """
- Returns the list of gn arguments to use with the gn command line.
- """
- def _ToCommandLine(key, value):
- if type(value) is bool:
- return "%s=%s" % (key, "true" if value else "false")
- return "%s=\"%s\"" % (key, value)
- return [_ToCommandLine(x, y) for x, y in gn_args.iteritems()]
-
-
-def ConfigForGNArgs(args):
- """
- Return the Config object for the given gn arguments. This function takes a
- dictionary with boolean values as boolean.
- """
- config_args = {}
- config_args["is_debug"] = args.get("is_debug", False)
- config_args["sanitizer"] = (
- Config.SANITIZER_ASAN if args.get("is_asan") else None)
- config_args["is_clang"] = args.get("is_clang", False)
- config_args["use_goma"] = args.get("use_goma", False)
- if config_args["use_goma"]:
- config_args["goma_dir"] = args.get("goma_dir")
- config_args["use_nacl"] = args.get("mojo_use_nacl", False)
- config_args["target_os"] = args.get("target_os")
- config_args["target_cpu"] = args.get("target_cpu")
- config_args["dcheck_always_on"] = args.get("dcheck_always_on")
- return Config(**config_args)
-
-
-def ParseGNConfig(build_dir):
- """
- Parse the gn config file present in |build_dir|. This function returns a
- dictionary with boolean values as boolean.
- """
- TRANSLATIONS = {
- "true": "True",
- "false": "False",
- }
- gn_file = os.path.join(build_dir, "args.gn")
- values = {}
- with open(gn_file, "r") as f:
- for line in f.readlines():
- line = re.sub("\s*#.*", "", line)
- result = re.match("^\s*(\w+)\s*=\s*(.*)\s*$", line)
- if result:
- key = result.group(1)
- value = result.group(2)
- values[key] = ast.literal_eval(TRANSLATIONS.get(value, value))
-
- # TODO(msw): The build dir is derived from GN args 'is_debug' and 'target_os'.
- # The script should probably use its 'build_dir' argument instead.
- if not "is_debug" in values:
- values["is_debug"] = "Debug" in build_dir
-
- return values
diff --git a/chromium/mojo/tools/mopy/gtest.py b/chromium/mojo/tools/mopy/gtest.py
index 1cde8ecee23..b72aad36c99 100644
--- a/chromium/mojo/tools/mopy/gtest.py
+++ b/chromium/mojo/tools/mopy/gtest.py
@@ -4,129 +4,192 @@
import logging
import os
+import Queue
import re
+import subprocess
import sys
+import threading
+import time
-from mopy import test_util
-from mopy.print_process_error import print_process_error
-
-
-_logger = logging.getLogger()
+from mopy.config import Config
+from mopy.paths import Paths
def set_color():
- """Run gtests with color if we're on a TTY (and we're not being told
- explicitly what to do)."""
+ """Run gtests with color on TTY, unless its environment variable is set."""
if sys.stdout.isatty() and "GTEST_COLOR" not in os.environ:
- _logger.debug("Setting GTEST_COLOR=yes")
+ logging.getLogger().debug("Setting GTEST_COLOR=yes")
os.environ["GTEST_COLOR"] = "yes"
-# TODO(vtl): The return value is bizarre. Should just make it either return
-# True/False, or a list of failing fixtures. But the dart_apptest runner would
-# also need to be updated in the same way.
-def run_fixtures(config, shell, apptest_dict, apptest, isolate, test_args,
- shell_args):
- """Run the gtest fixtures in isolation."""
-
- if not isolate:
- if not RunApptestInShell(config, shell, apptest, test_args, shell_args):
- return "Failed test(s) in %r" % apptest_dict
- return "Succeeded"
-
- # List the apptest fixtures so they can be run independently for isolation.
- fixtures = get_fixtures(config, shell, shell_args, apptest)
-
- if not fixtures:
- return "Failed with no tests found."
-
- apptest_result = "Succeeded"
- for fixture in fixtures:
- apptest_args = test_args + ["--gtest_filter=%s" % fixture]
- success = RunApptestInShell(config, shell, apptest, apptest_args,
- shell_args)
-
- if not success:
- apptest_result = "Failed test(s) in %r" % apptest_dict
- return apptest_result
+def run_apptest(config, shell, args, apptest, isolate):
+ """Run the apptest; optionally isolating fixtures across shell invocations.
-
-def run_test(config, shell, shell_args, apps_and_args=None):
- """Runs a command line and checks the output for signs of gtest failure.
+ Returns the list of tests run and the list of failures.
Args:
- config: The mopy.config.Config object for the build.
- shell_args: The arguments for mojo_shell.
- apps_and_args: A Dict keyed by application URL associated to the
- application's specific arguments.
+ config: The mopy.config.Config for the build.
+ shell: The mopy.android.AndroidShell, if Android is the target platform.
+ args: The arguments for the shell or apptest.
+ apptest: The application test URL.
+ isolate: True if the test fixtures should be run in isolation.
"""
- apps_and_args = apps_and_args or {}
- output = test_util.try_run_test(config, shell, shell_args, apps_and_args)
+ tests = [apptest]
+ failed = []
+ if not isolate:
+ # TODO(msw): Parse fixture-granular successes and failures in this case.
+ # TODO(msw): Retry fixtures that failed, not the entire apptest suite.
+ if not _run_apptest_with_retry(config, shell, args, apptest):
+ failed.append(apptest)
+ else:
+ tests = _get_fixtures(config, shell, args, apptest)
+ for fixture in tests:
+ arguments = args + ["--gtest_filter=%s" % fixture]
+ if not _run_apptest_with_retry(config, shell, arguments, apptest):
+ failed.append(fixture)
+ # Abort when 20 fixtures, or a tenth of the apptest fixtures, have failed.
+ # base::TestLauncher does this for timeouts and unknown results.
+ if len(failed) >= max(20, len(tests) / 10):
+ print "Too many failing fixtures (%d), exiting now." % len(failed)
+ return (tests, failed + [apptest + " aborted for excessive failures."])
+ return (tests, failed)
+
+
+# TODO(msw): Determine proper test retry counts; allow configuration.
+def _run_apptest_with_retry(config, shell, args, apptest, try_count=3):
+ """Runs an apptest, retrying on failure; returns True if any run passed."""
+ for try_number in range(try_count):
+ if _run_apptest(config, shell, args, apptest):
+ return True
+ print "Failed %s/%s test run attempts." % (try_number + 1, try_count)
+ return False
+
+
+def _run_apptest(config, shell, args, apptest):
+ """Runs an apptest and checks the output for signs of gtest failure."""
+ command = _build_command_line(config, args, apptest)
+ logging.getLogger().debug("Command: %s" % " ".join(command))
+ start_time = time.time()
+
+ try:
+ output = _run_test_with_timeout(config, shell, args, apptest)
+ except Exception as e:
+ _print_exception(command, e)
+ return False
+
# Fail on output with gtest's "[ FAILED ]" or a lack of "[ PASSED ]".
# The latter condition ensures failure on broken command lines or output.
- # Check output instead of exit codes because mojo_shell always exits with 0.
- if (output is None or
- (output.find("[ FAILED ]") != -1 or output.find("[ PASSED ]") == -1)):
- print "Failed test:"
- print_process_error(
- test_util.build_command_line(config, shell_args, apps_and_args),
- output)
+ # Check output instead of exit codes because mojo shell always exits with 0.
+ if output.find("[ FAILED ]") != -1 or output.find("[ PASSED ]") == -1:
+ _print_exception(command, output)
return False
- _logger.debug("Succeeded with output:\n%s" % output)
- return True
-
-def get_fixtures(config, shell, shell_args, apptest):
- """Returns the "Test.Fixture" list from an apptest using mojo_shell.
+ ms = int(round(1000 * (time.time() - start_time)))
+ logging.getLogger().debug("Passed with output (%d ms):\n%s" % (ms, output))
+ return True
- Tests are listed by running the given apptest in mojo_shell and passing
- --gtest_list_tests. The output is parsed and reformatted into a list like
- [TestSuite.TestFixture, ... ]
- An empty list is returned on failure, with errors logged.
- Args:
- config: The mopy.config.Config object for the build.
- apptest: The URL of the test application to run.
- """
+def _get_fixtures(config, shell, args, apptest):
+ """Returns an apptest's "Suite.Fixture" list via --gtest_list_tests output."""
+ arguments = args + ["--gtest_list_tests"]
+ command = _build_command_line(config, arguments, apptest)
+ logging.getLogger().debug("Command: %s" % " ".join(command))
try:
- apps_and_args = {apptest: ["--gtest_list_tests"]}
- list_output = test_util.run_test(config, shell, shell_args, apps_and_args)
- _logger.debug("Tests listed:\n%s" % list_output)
- return _gtest_list_tests(list_output)
+ tests = _run_test_with_timeout(config, shell, arguments, apptest)
+ logging.getLogger().debug("Tests for %s:\n%s" % (apptest, tests))
+ # Remove log lines from the output and ensure it matches known formatting.
+ tests = re.sub("^(\[|WARNING: linker:).*\n", "", tests, flags=re.MULTILINE)
+ if not re.match("^(\w*\.\r?\n( \w*\r?\n)+)+", tests):
+ raise Exception("Unrecognized --gtest_list_tests output:\n%s" % tests)
+ tests = tests.split("\n")
+ test_list = []
+ for line in tests:
+ if not line:
+ continue
+ if line[0] != " ":
+ suite = line.strip()
+ continue
+ test_list.append(suite + line.strip())
+ return test_list
except Exception as e:
- print "Failed to get test fixtures:"
- print_process_error(
- test_util.build_command_line(config, shell_args, apps_and_args), e)
+ _print_exception(command, e)
return []
-def _gtest_list_tests(gtest_list_tests_output):
- """Returns a list of strings formatted as TestSuite.TestFixture from the
- output of running --gtest_list_tests on a GTEST application."""
-
- # Remove log lines.
- gtest_list_tests_output = re.sub("^(\[|WARNING: linker:).*\n",
- "",
- gtest_list_tests_output,
- flags=re.MULTILINE)
-
- if not re.match("^(\w*\.\r?\n( \w*\r?\n)+)+", gtest_list_tests_output):
- raise Exception("Unrecognized --gtest_list_tests output:\n%s" %
- gtest_list_tests_output)
-
- output_lines = gtest_list_tests_output.split("\n")
-
- test_list = []
- for line in output_lines:
- if not line:
- continue
- if line[0] != " ":
- suite = line.strip()
- continue
- test_list.append(suite + line.strip())
-
- return test_list
-
-
-def RunApptestInShell(config, shell, application, application_args, shell_args):
- return run_test(config, shell, shell_args, {application: application_args})
+def _print_exception(command_line, exception):
+ """Print a formatted exception raised from a failed command execution."""
+ exit_code = ""
+ if hasattr(exception, 'returncode'):
+ exit_code = " (exit code %d)" % exception.returncode
+ print "\n[ FAILED ] Command%s: %s" % (exit_code, " ".join(command_line))
+ print 72 * "-"
+ if hasattr(exception, 'output'):
+ print exception.output
+ print str(exception)
+ print 72 * "-"
+
+
+def _build_command_line(config, args, apptest):
+ """Build the apptest command line. This value isn't executed on Android."""
+ paths = Paths(config)
+ # On Linux, always run tests with xvfb, but not for --gtest_list_tests.
+ use_xvfb = (config.target_os == Config.OS_LINUX and
+ not "--gtest_list_tests" in args)
+ prefix = [paths.xvfb, paths.build_dir] if use_xvfb else []
+ return prefix + [paths.mojo_runner] + args + [apptest]
+
+
+# TODO(msw): Determine proper test timeout durations (starting small).
+def _run_test_with_timeout(config, shell, args, apptest, timeout_in_seconds=10):
+ """Run the test with a timeout; return the output or raise an exception."""
+ result = Queue.Queue()
+ thread = threading.Thread(target=_run_test,
+ args=(config, shell, args, apptest, result))
+ thread.start()
+ process_or_shell = result.get()
+ thread.join(timeout_in_seconds)
+
+ if thread.is_alive():
+ try:
+ process_or_shell.kill()
+ except OSError:
+ pass # The process may have ended after checking |is_alive|.
+ return "Error: Test timeout after %s seconds" % timeout_in_seconds
+
+ if not result.empty():
+ (output, exception) = result.get()
+ if exception:
+ raise Exception("%s%s%s" % (output, "\n" if output else "", exception))
+ return output
+
+ return "Error: Test exited with no output."
+
+
+def _run_test(config, shell, args, apptest, result):
+ """Run the test and put the output and any exception in |result|."""
+ output = ""
+ exception = ""
+ try:
+ if (config.target_os != Config.OS_ANDROID):
+ command = _build_command_line(config, args, apptest)
+ process = subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ result.put(process)
+ process.wait()
+ if not process.poll():
+ output = str(process.stdout.read())
+ else:
+ exception = "Error: Test exited with code: %d" % process.returncode
+ else:
+ assert shell
+ result.put(shell)
+ (r, w) = os.pipe()
+ with os.fdopen(r, "r") as rf:
+ with os.fdopen(w, "w") as wf:
+ arguments = args + [apptest]
+ shell.StartActivity('MojoShellActivity', arguments, wf, wf.close)
+ output = rf.read()
+ except Exception as e:
+ output = e.output if hasattr(e, 'output') else ""
+ exception = str(e)
+ result.put((output, exception))
diff --git a/chromium/mojo/tools/mopy/log.py b/chromium/mojo/tools/mopy/log.py
deleted file mode 100644
index af572320818..00000000000
--- a/chromium/mojo/tools/mopy/log.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# 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.
-
-"""Logging utilities, for use with the standard logging module."""
-
-
-import logging
-
-
-def InitLogging(verbose_count):
- """Ensures that the logger (obtained via logging.getLogger(), as usual) is
- initialized, with the log level set as appropriate for |verbose_count|
- instances of --verbose on the command line."""
-
- assert(verbose_count >= 0)
- if verbose_count == 0:
- level = logging.WARNING
- elif verbose_count == 1:
- level = logging.INFO
- else: # verbose_count >= 2
- level = logging.DEBUG
-
- logging.basicConfig(format="%(relativeCreated).3f:%(levelname)s:%(message)s")
- logger = logging.getLogger()
- logger.setLevel(level)
-
- logger.debug("Initialized logging: verbose_count=%d, level=%d" %
- (verbose_count, level))
diff --git a/chromium/mojo/tools/mopy/paths.py b/chromium/mojo/tools/mopy/paths.py
index 751629a3c85..767732921bd 100644
--- a/chromium/mojo/tools/mopy/paths.py
+++ b/chromium/mojo/tools/mopy/paths.py
@@ -5,43 +5,41 @@
import os
from .config import Config
-from .gn import BuildDirectoryForConfig
+
class Paths(object):
"""Provides commonly used paths"""
- def __init__(self, config=None, build_dir=None):
- """Specify either a config or a build_dir to generate paths to binary
- artifacts."""
+ def __init__(self, config):
+ """Generate paths to binary artifacts from a Config object."""
self.src_root = os.path.abspath(os.path.join(__file__,
os.pardir, os.pardir, os.pardir, os.pardir))
self.mojo_dir = os.path.join(self.src_root, "mojo")
- self.adb_path = os.path.join(self.src_root, 'third_party', 'android_tools',
- 'sdk', 'platform-tools', 'adb')
-
- if config:
- self.build_dir = BuildDirectoryForConfig(config, self.src_root)
- elif build_dir is not None:
- self.build_dir = os.path.abspath(build_dir)
- else:
- self.build_dir = None
- if self.build_dir is not None:
- self.mojo_shell_path = os.path.join(self.build_dir, "mojo_runner")
- # TODO(vtl): Use the host OS here, since |config| may not be available.
- # In any case, if the target is Windows, but the host isn't, using
- # |os.path| isn't correct....
- if Config.GetHostOS() == Config.OS_WINDOWS:
- self.mojo_shell_path += ".exe"
- if config and config.target_os == Config.OS_ANDROID:
- self.target_mojo_shell_path = os.path.join(self.build_dir,
- "apks",
- config.apk_name)
- else:
- self.target_mojo_shell_path = self.mojo_shell_path
- else:
- self.mojo_shell_path = None
- self.target_mojo_shell_path = None
+ self.build_dir = config.build_dir
+ if self.build_dir is None:
+ subdir = ""
+ if config.target_os == Config.OS_ANDROID:
+ subdir += "android_"
+ if config.target_cpu != Config.ARCH_ARM:
+ subdir += config.target_cpu + "_"
+ elif config.target_os == Config.OS_CHROMEOS:
+ subdir += "chromeos_"
+ subdir += "Debug" if config.is_debug else "Release"
+ if config.is_asan:
+ subdir += "_asan"
+ if not(config.is_debug) and config.dcheck_always_on:
+ subdir += "_dcheck"
+ self.build_dir = os.path.join(self.src_root, "out", subdir)
+
+ self.mojo_runner = os.path.join(self.build_dir, "mojo_runner")
+ if config.target_os == Config.OS_WINDOWS:
+ self.mojo_runner += ".exe"
+ if config.target_os == Config.OS_ANDROID:
+ self.apk_path = os.path.join(self.build_dir, "apks", config.apk_name)
+ self.mojo_runner = os.path.join(self.src_root, "mojo", "tools",
+ "android_mojo_shell.py")
+ self.xvfb = os.path.join(self.src_root, "testing", "xvfb.py")
def RelPath(self, path):
"""Returns the given path, relative to the current directory."""
@@ -50,20 +48,3 @@ class Paths(object):
def SrcRelPath(self, path):
"""Returns the given path, relative to self.src_root."""
return os.path.relpath(path, self.src_root)
-
- def FileFromUrl(self, url):
- """Given an app URL (<scheme>:<appname>), return 'build_dir/appname.mojo'.
- If self.build_dir is None, just return appname.mojo
- """
- (_, name) = url.split(':')
- if self.build_dir:
- return os.path.join(self.build_dir, name + '.mojo')
- return name + '.mojo'
-
- @staticmethod
- def IsValidAppUrl(url):
- """Returns False if url is malformed, True otherwise."""
- try:
- return len(url.split(':')) == 2
- except ValueError:
- return False
diff --git a/chromium/mojo/tools/mopy/print_process_error.py b/chromium/mojo/tools/mopy/print_process_error.py
deleted file mode 100644
index ec565d14e40..00000000000
--- a/chromium/mojo/tools/mopy/print_process_error.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2014 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.
-
-def print_process_error(command_line, error):
- """Properly format an exception raised from a failed command execution."""
-
- if command_line:
- print 'Failed command: %r' % command_line
- else:
- print 'Failed command:'
- print 72 * '-'
-
- if hasattr(error, 'returncode'):
- print ' with exit code %d' % error.returncode
- print 72 * '-'
-
- if hasattr(error, 'output'):
- print error.output
- else:
- print error
- print 72 * '-'
diff --git a/chromium/mojo/tools/mopy/test_util.py b/chromium/mojo/tools/mopy/test_util.py
deleted file mode 100644
index cb8886a42ef..00000000000
--- a/chromium/mojo/tools/mopy/test_util.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# 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 subprocess
-import time
-
-from mopy.config import Config
-from mopy.paths import Paths
-from mopy.print_process_error import print_process_error
-
-
-_logger = logging.getLogger()
-
-
-def build_shell_arguments(shell_args, apps_and_args=None):
- """Build the list of arguments for the shell. |shell_args| are the base
- arguments, |apps_and_args| is a dictionary that associates each application to
- its specific arguments|. Each app included will be run by the shell.
- """
- result = shell_args[:]
- if apps_and_args:
- # TODO(msw): Mojo's script uses --args-for; Chromium lacks support for that.
- for app_and_args in apps_and_args.items():
- result += app_and_args[1]
- result += apps_and_args.keys()
- return result
-
-
-def get_shell_executable(config):
- paths = Paths(config=config)
- if config.target_os == Config.OS_ANDROID:
- return os.path.join(paths.src_root, "mojo", "tools",
- "android_mojo_shell.py")
- else:
- return paths.mojo_shell_path
-
-
-def build_command_line(config, shell_args, apps_and_args):
- executable = get_shell_executable(config)
- return "%s %s" % (executable, " ".join(["%r" % x for x in
- build_shell_arguments(
- shell_args, apps_and_args)]))
-
-
-def run_test_android(shell, shell_args, apps_and_args):
- """Run the given test on the single/default android device."""
- assert shell
- (r, w) = os.pipe()
- with os.fdopen(r, "r") as rf:
- with os.fdopen(w, "w") as wf:
- arguments = build_shell_arguments(shell_args, apps_and_args)
- _logger.debug("Starting shell with arguments: %s" % arguments)
- start_time = time.time()
- # TODO(vtl): Do more logging in lower layers.
- shell.StartShell(arguments, wf, wf.close)
- rv = rf.read()
- run_time = time.time() - start_time
- _logger.debug("Shell completed")
- # Only log if it took more than 3 seconds.
- if run_time >= 3:
- _logger.info("Shell test took %.3f seconds; arguments: %s" %
- (run_time, arguments))
- return rv
-
-
-def run_test(config, shell, shell_args, apps_and_args):
- """Run the given test."""
- if (config.target_os == Config.OS_ANDROID):
- return run_test_android(shell, shell_args, apps_and_args)
-
- executable = get_shell_executable(config)
- command = ([executable] + build_shell_arguments(shell_args, apps_and_args))
- _logger.debug("Starting: %s" % " ".join(command))
- start_time = time.time()
- rv = subprocess.check_output(command, stderr=subprocess.STDOUT)
- run_time = time.time() - start_time
- _logger.debug("Completed: %s" % " ".join(command))
- # Only log if it took more than 1 second.
- if run_time >= 1:
- _logger.info("Test took %.3f seconds: %s" % (run_time, " ".join(command)))
- return rv
-
-
-def try_run_test(config, shell, shell_args, apps_and_args):
- """Returns the output of a command line or an empty string on error."""
- command_line = build_command_line(config, shell_args, apps_and_args)
- _logger.debug("Running command line: %s" % command_line)
- try:
- return run_test(config, shell, shell_args, apps_and_args)
- except Exception as e:
- print_process_error(command_line, e)
- return None
diff --git a/chromium/mojo/tools/rev_sdk.py b/chromium/mojo/tools/rev_sdk.py
index a33c37eb399..db810342512 100755
--- a/chromium/mojo/tools/rev_sdk.py
+++ b/chromium/mojo/tools/rev_sdk.py
@@ -23,6 +23,8 @@ sdk_dirs_to_clone = [
sdk_dirs_to_not_clone = [
"mojo/public/cpp/application",
"mojo/public/interfaces/application",
+ "mojo/public/interfaces/network",
+ "mojo/public/java/application",
]
# Individual files to preserve within the target repository during roll. These
@@ -42,8 +44,9 @@ for sdk_dir in sdk_dirs_to_clone:
sdk_dir_in_chromium = os.path.join(sdk_prefix_in_chromium, sdk_dir)
dirs_to_clone[sdk_dir] = sdk_dir_in_chromium
-def rev(source_dir, chromium_dir):
- src_commit = system(["git", "show-ref", "HEAD", "-s"], cwd=source_dir).strip()
+def rev(source_dir, chromium_dir, mojo_revision):
+ src_commit = system(["git", "rev-parse", mojo_revision],
+ cwd=source_dir).strip()
for input_dir, dest_dir in dirs_to_clone.iteritems():
if os.path.exists(os.path.join(chromium_dir, dest_dir)):
@@ -88,8 +91,10 @@ def rev(source_dir, chromium_dir):
commit("Update mojo sdk to rev " + src_commit, cwd=chromium_dir)
-if len(sys.argv) != 2:
- print "usage: rev_sdk.py <mojo source dir>"
+if len(sys.argv) < 2:
+ print "usage: rev_sdk.py <mojo source dir> [<mojo revision>]"
sys.exit(1)
-rev(sys.argv[1], chromium_root_dir)
+# Allow override of the roll revision.
+revision = sys.argv[2] if len(sys.argv) == 3 else 'origin/HEAD'
+rev(sys.argv[1], chromium_root_dir, revision)