blob: 0357a77f0777fe72051eb8245a494e70f3ae0972 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2016 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.
"""Blimp Client + Engine integration test system
Set up Client and Engine
Set up Forward to connect machine host with client device.
Start Engine and run blimp on Android Client.
"""
import argparse
import json
import logging
import posixpath
import os
import re
import signal
import subprocess
import sys
SRC_PATH = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..'))
DEVIL_PATH = os.path.join(SRC_PATH, 'third_party', 'catapult',
'devil')
if DEVIL_PATH not in sys.path:
sys.path.append(DEVIL_PATH)
from devil.android import device_blacklist
from devil.android import device_utils
from devil.android import forwarder
from devil.android.sdk import intent
from devil.android.sdk import version_codes
from devil.utils import cmd_helper
_CLIENT_TOKEN_PATH = posixpath.join('/', 'data', 'data',
'org.chromium.chrome',
'blimp_client_token')
_TOKEN_FILE_PATH = os.path.join(SRC_PATH, 'blimp', 'test', 'data',
'test_client_token')
PORT_PATTERN = re.compile(r'.*Engine port #: (\d+)')
ARGS_JSON_FILE = 'blimp_script_args.json'
def AddStartArguments(parser):
parser.add_argument('-d',
'--device',
help='Serial number of device we should use.')
parser.add_argument('--blacklist-file',
default=None,
help='Device blacklist JSON file.')
parser.add_argument('--engine-ip',
default='127.0.0.1',
help='Blimp engine IP.')
def _IsNonEmptyFile(fpath):
return os.path.isfile(fpath) and os.path.getsize(fpath) > 0
def RunClient(device, optional_url):
"""Run Blimp client.
Args:
device: (DeviceUtils) device to run the tests on.
optional_url: (str) URL to navigate to.
"""
run_client_intent = intent.Intent(
action='android.intent.action.VIEW',
package='org.chromium.chrome',
activity='com.google.android.apps.chrome.Main',
data=optional_url)
device.StartActivity(run_client_intent, blocking=True)
def RunEngine(output_linux_directory, token_file_path, device):
"""Start running engine
Args:
output_linux_directory: (str) Path to the root linux build directory.
token_file_path: (str) Path to the client auth token file.
Returns:
port: (str) Engine port number generated by engine session.
"""
port = '0'
blimp_engine_app = os.path.join(output_linux_directory,
'blimp_engine_app')
sub_dir = "marshmallow"
if device.build_version_sdk == version_codes.KITKAT:
sub_dir = "kitkat"
blimp_fonts_path = os.path.join(output_linux_directory, 'gen',
'third_party', 'blimp_fonts',
'font_bundle', sub_dir)
run_engine_cmd = [
blimp_engine_app +
' --android-fonts-path=' + blimp_fonts_path +
' --blimp-client-token-path=' + token_file_path +
' --enable-logging=stderr' +
' -v=0' +
' --vmodule="blimp*=1"']
p = subprocess.Popen(run_engine_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
preexec_fn=os.setsid)
for line in iter(p.stdout.readline, ''):
sys.stdout.write(line)
l = line.rstrip()
match = re.match(PORT_PATTERN, l)
if match:
port = match.group(1)
break
return port, p
def SetCommandFlag(device, engine_ip, engine_port):
"""Set up adb Chrome command line flags
Args:
device: (str) Serial number of device we should use.
engine_ip: (str) Blimp engine IP address.
engine_port: (str) Port on the engine.
"""
cmd_helper.GetCmdStatusAndOutput([
os.path.join(SRC_PATH, 'build', 'android',
'adb_chrome_public_command_line'),
'--device=' + str(device),
'--enable-blimp',
'--engine-ip=' + engine_ip,
'--engine-port=' + engine_port,
'--engine-transport=tcp',
'-v=0',
'--vmodule=*blimp*=1',
'--blimp-client-token-path=' + _CLIENT_TOKEN_PATH])
def _JsonEncodeDefault(obj):
if isinstance(obj, argparse.Namespace):
result = obj.__dict__
result.update({'_type': 'args'})
return result
raise TypeError("Json object must be an instance of argparse.Namespace")
def _FromJson(json_object):
if json_object.get('_type') == 'args':
result = argparse.Namespace()
for key, value in json_object.iteritems():
setattr(result, key, value)
return result
return json_object
def _Start(args, json_file_path, device):
"""Start engine and forwarder
Args:
args: parsed command line arguments.
json_file_path: json file path which keeps the script variables.
device: (DeviceUtils) device to run the tests on.
"""
if _IsNonEmptyFile(json_file_path):
logging.error('Error: An engine instance is already running.')
sys.exit(1)
json_args = argparse.Namespace()
for k in args.__dict__:
if not k == "func":
setattr(json_args, k, args.__dict__[k])
json_object = {'args': json_args}
device.EnableRoot()
host_device_tuples = [(_TOKEN_FILE_PATH, _CLIENT_TOKEN_PATH)]
device.PushChangedFiles(host_device_tuples)
port_number, engine_process = RunEngine(
args.output_linux_directory, _TOKEN_FILE_PATH, device)
json_object['port_number'] = port_number
json_object['pid'] = engine_process.pid
logging.info('Engine port number: %s', port_number)
logging.info('Engine running PID: %d', engine_process.pid)
if engine_process.poll() is not None:
logging.error('Engine failed to start. Return code: %d',
engine_process.poll())
else:
try:
port_pairs = [(port_number, port_number)]
forwarder.Forwarder.Map(port_pairs, device)
SetCommandFlag(device, args.engine_ip, port_number)
print "Blimp engine started"
return engine_process
finally:
with open(json_file_path, 'w') as f:
json.dump(json_object, f, default=_JsonEncodeDefault)
def _Run(args, json_file_path, device):
"""Start engine and forwarder and keep runnning.
Args:
args: parsed command line arguments.
json_file_path: json file path which keeps the script variables.
device: (DeviceUtils) device to run the tests on.
"""
try:
engine_process = _Start(args, json_file_path, device)
while True:
nextline = engine_process.stdout.readline()
if nextline == '' and engine_process.poll() is not None:
# The engine died.
sys.exit(1)
sys.stdout.write(nextline)
sys.stdout.flush()
except KeyboardInterrupt:
sys.exit(0)
finally:
_Stop(args, json_file_path, device)
def _Load(args, json_file_path, device): # pylint: disable=unused-argument
"""Start client and load the url
Args:
args: parsed command line arguments.
json_file_path: json file path which keeps the script variables.
device: (DeviceUtils) device to run the tests on.
"""
device.Install(os.path.join(SRC_PATH, args.apk_path),
reinstall=True)
run_client_intent = intent.Intent(
action='android.intent.action.VIEW',
package='org.chromium.chrome',
activity='com.google.android.apps.chrome.Main',
data=args.optional_url)
device.StartActivity(run_client_intent, blocking=True)
def _Stop(args, json_file_path, device): # pylint: disable=unused-argument
"""Stop engine and forwarder
Args:
args: (unused) parsed command line arguments.
json_file_path: json file path which keeps the script variables.
device: (DeviceUtils) device to run the tests on.
"""
if not _IsNonEmptyFile(json_file_path):
logging.error('Error: cannot find json file: ' + json_file_path)
sys.exit(1)
try:
with open(json_file_path, 'r') as f:
jsonarg = json.load(f, object_hook=_FromJson)
pid = int(jsonarg['pid'])
try:
forwarder.Forwarder.UnmapAllDevicePorts(device)
finally:
os.kill(pid, signal.SIGKILL)
finally:
os.remove(json_file_path)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-l',
'--output-linux-directory',
required=True,
help='Path to the root linux build directory.'
' Example: "out-linux/Debug"')
subparsers = parser.add_subparsers(dest="subparser_name")
start_parser = subparsers.add_parser('start')
start_parser.set_defaults(func=_Start)
AddStartArguments(start_parser)
run_parser = subparsers.add_parser('run')
run_parser.set_defaults(func=_Run)
AddStartArguments(run_parser)
load_parser = subparsers.add_parser('load')
load_parser.set_defaults(func=_Load)
load_parser.add_argument('-p',
'--apk-path',
required=True,
help='The path to the APK to install. '
'Including the name of the apk containing '
'the application (with the .apk extension).')
load_parser.add_argument('-u',
'--optional-url',
help='URL to navigate to.')
stop_parser = subparsers.add_parser('stop')
stop_parser.set_defaults(func=_Stop)
args = parser.parse_args()
json_file_path = os.path.join(SRC_PATH, args.output_linux_directory,
ARGS_JSON_FILE)
blacklist_file = ''
serial = ''
if args.subparser_name == 'start' or args.subparser_name == 'run':
blacklist_file = args.blacklist_file
serial = args.device
else:
if not _IsNonEmptyFile(json_file_path):
logging.error('Cannot find json file: %s' + json_file_path)
logging.error('Run `client_engine_integration.py {run,start}` first.')
sys.exit(1)
with open(json_file_path, 'r') as f:
file_lines = f.readlines()
jsonarg = json.loads(file_lines[0], object_hook=_FromJson)
blacklist_file = jsonarg['args'].blacklist_file
serial = jsonarg['args'].device
blacklist = (device_blacklist.Blacklist(blacklist_file)
if blacklist_file
else None)
device = device_utils.DeviceUtils.HealthyDevices(
blacklist=blacklist, device_arg=serial)[0]
args.func(args, json_file_path, device)
if __name__ == '__main__':
main()