| #!/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() |