| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # Copyright 2016 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """Blimp Client + Engine integration test system | |
| 7 | |
| 8 Set up Client and Engine | |
| 9 Set up Forward to connect machine host with client device. | |
| 10 Start Engine and run blimp on Android Client. | |
| 11 """ | |
| 12 | |
| 13 import argparse | |
| 14 import json | |
| 15 import logging | |
| 16 import posixpath | |
| 17 import os | |
| 18 import re | |
| 19 import signal | |
| 20 import subprocess | |
| 21 import sys | |
| 22 | |
| 23 SRC_PATH = os.path.abspath( | |
| 24 os.path.join(os.path.dirname(__file__), '..', '..')) | |
| 25 | |
| 26 DEVIL_PATH = os.path.join(SRC_PATH, 'third_party', 'catapult', | |
| 27 'devil') | |
| 28 DEVIL_CHROMIUM_PATH = os.path.join(SRC_PATH, 'build', 'android') | |
| 29 | |
| 30 if DEVIL_CHROMIUM_PATH not in sys.path: | |
| 31 sys.path.append(DEVIL_CHROMIUM_PATH) | |
| 32 | |
| 33 import devil_chromium | |
| 34 | |
| 35 if DEVIL_PATH not in sys.path: | |
| 36 sys.path.append(DEVIL_PATH) | |
| 37 | |
| 38 from devil.android import device_blacklist | |
| 39 from devil.android import device_utils | |
| 40 from devil.android import forwarder | |
| 41 from devil.android.sdk import intent | |
| 42 from devil.android.sdk import version_codes | |
| 43 from devil.utils import cmd_helper | |
| 44 | |
| 45 _CLIENT_TOKEN_PATH = posixpath.join('/', 'data', 'data', | |
| 46 'org.chromium.chrome', | |
| 47 'blimp_client_token') | |
| 48 _TOKEN_FILE_PATH = os.path.join(SRC_PATH, 'blimp', 'test', 'data', | |
| 49 'test_client_token') | |
| 50 PORT_PATTERN = re.compile(r'.*Engine port #: (\d+)') | |
| 51 ARGS_JSON_FILE = 'blimp_script_args.json' | |
| 52 | |
| 53 def AddStartArguments(parser): | |
| 54 parser.add_argument('-d', | |
| 55 '--device', | |
| 56 help='Serial number of device we should use.') | |
| 57 parser.add_argument('--blacklist-file', | |
| 58 default=None, | |
| 59 help='Device blacklist JSON file.') | |
| 60 parser.add_argument('--engine-ip', | |
| 61 default='127.0.0.1', | |
| 62 help='Blimp engine IP.') | |
| 63 | |
| 64 | |
| 65 def _IsNonEmptyFile(fpath): | |
| 66 return os.path.isfile(fpath) and os.path.getsize(fpath) > 0 | |
| 67 | |
| 68 | |
| 69 def RunClient(device, optional_url): | |
| 70 """Run Blimp client. | |
| 71 | |
| 72 Args: | |
| 73 device: (DeviceUtils) device to run the tests on. | |
| 74 optional_url: (str) URL to navigate to. | |
| 75 """ | |
| 76 run_client_intent = intent.Intent( | |
| 77 action='android.intent.action.VIEW', | |
| 78 package='org.chromium.chrome', | |
| 79 activity='com.google.android.apps.chrome.Main', | |
| 80 data=optional_url) | |
| 81 device.StartActivity(run_client_intent, blocking=True) | |
| 82 | |
| 83 | |
| 84 def RunEngine(output_linux_directory, token_file_path, device): | |
| 85 """Start running engine | |
| 86 | |
| 87 Args: | |
| 88 output_linux_directory: (str) Path to the root linux build directory. | |
| 89 token_file_path: (str) Path to the client auth token file. | |
| 90 | |
| 91 Returns: | |
| 92 port: (str) Engine port number generated by engine session. | |
| 93 """ | |
| 94 port = '0' | |
| 95 blimp_engine_app = os.path.join(output_linux_directory, | |
| 96 'blimp_engine_app') | |
| 97 | |
| 98 sub_dir = "marshmallow" | |
| 99 if device.build_version_sdk == version_codes.KITKAT: | |
| 100 sub_dir = "kitkat" | |
| 101 blimp_fonts_path = os.path.join(output_linux_directory, 'gen', | |
| 102 'third_party', 'blimp_fonts', | |
| 103 'font_bundle', sub_dir) | |
| 104 | |
| 105 run_engine_cmd = [ | |
| 106 blimp_engine_app + | |
| 107 ' --android-fonts-path=' + blimp_fonts_path + | |
| 108 ' --blimp-client-token-path=' + token_file_path + | |
| 109 ' --enable-logging=stderr' + | |
| 110 ' -v=0' + | |
| 111 ' --vmodule="blimp*=1"'] | |
| 112 p = subprocess.Popen(run_engine_cmd, shell=True, | |
| 113 stdout=subprocess.PIPE, | |
| 114 stderr=subprocess.STDOUT, | |
| 115 preexec_fn=os.setsid) | |
| 116 | |
| 117 for line in iter(p.stdout.readline, ''): | |
| 118 sys.stdout.write(line) | |
| 119 l = line.rstrip() | |
| 120 match = re.match(PORT_PATTERN, l) | |
| 121 if match: | |
| 122 port = match.group(1) | |
| 123 break | |
| 124 | |
| 125 return port, p | |
| 126 | |
| 127 | |
| 128 def SetCommandFlag(device, engine_ip, engine_port): | |
| 129 """Set up adb Chrome command line flags | |
| 130 | |
| 131 Args: | |
| 132 device: (str) Serial number of device we should use. | |
| 133 engine_ip: (str) Blimp engine IP address. | |
| 134 engine_port: (str) Port on the engine. | |
| 135 """ | |
| 136 cmd_helper.GetCmdStatusAndOutput([ | |
| 137 os.path.join(SRC_PATH, 'build', 'android', | |
| 138 'adb_chrome_public_command_line'), | |
| 139 '--device=' + str(device), | |
| 140 '--enable-blimp', | |
| 141 '--engine-ip=' + engine_ip, | |
| 142 '--engine-port=' + engine_port, | |
| 143 '--engine-transport=tcp', | |
| 144 '-v=0', | |
| 145 '--vmodule=*blimp*=1', | |
| 146 '--blimp-client-token-path=' + _CLIENT_TOKEN_PATH]) | |
| 147 | |
| 148 | |
| 149 def _JsonEncodeDefault(obj): | |
| 150 if isinstance(obj, argparse.Namespace): | |
| 151 result = obj.__dict__ | |
| 152 result.update({'_type': 'args'}) | |
| 153 return result | |
| 154 raise TypeError("Json object must be an instance of argparse.Namespace") | |
| 155 | |
| 156 | |
| 157 def _FromJson(json_object): | |
| 158 if json_object.get('_type') == 'args': | |
| 159 result = argparse.Namespace() | |
| 160 for key, value in json_object.iteritems(): | |
| 161 setattr(result, key, value) | |
| 162 return result | |
| 163 return json_object | |
| 164 | |
| 165 | |
| 166 def _Start(args, json_file_path, device): | |
| 167 """Start engine and forwarder | |
| 168 | |
| 169 Args: | |
| 170 args: parsed command line arguments. | |
| 171 json_file_path: json file path which keeps the script variables. | |
| 172 device: (DeviceUtils) device to run the tests on. | |
| 173 """ | |
| 174 if _IsNonEmptyFile(json_file_path): | |
| 175 logging.error('Error: An engine instance is already running.') | |
| 176 sys.exit(1) | |
| 177 | |
| 178 json_args = argparse.Namespace() | |
| 179 for k in args.__dict__: | |
| 180 if not k == "func": | |
| 181 setattr(json_args, k, args.__dict__[k]) | |
| 182 json_object = {'args': json_args} | |
| 183 device.EnableRoot() | |
| 184 host_device_tuples = [(_TOKEN_FILE_PATH, _CLIENT_TOKEN_PATH)] | |
| 185 device.PushChangedFiles(host_device_tuples) | |
| 186 | |
| 187 port_number, engine_process = RunEngine( | |
| 188 args.output_linux_directory, _TOKEN_FILE_PATH, device) | |
| 189 json_object['port_number'] = port_number | |
| 190 json_object['pid'] = engine_process.pid | |
| 191 logging.info('Engine port number: %s', port_number) | |
| 192 logging.info('Engine running PID: %d', engine_process.pid) | |
| 193 | |
| 194 if engine_process.poll() is not None: | |
| 195 logging.error('Engine failed to start. Return code: %d', | |
| 196 engine_process.poll()) | |
| 197 else: | |
| 198 try: | |
| 199 port_pairs = [(port_number, port_number)] | |
| 200 SetCommandFlag(device, args.engine_ip, port_number) | |
| 201 forwarder.Forwarder.Map(port_pairs, device) | |
| 202 print "Blimp engine started" | |
| 203 return engine_process | |
| 204 | |
| 205 finally: | |
| 206 with open(json_file_path, 'w') as f: | |
| 207 json.dump(json_object, f, default=_JsonEncodeDefault) | |
| 208 | |
| 209 | |
| 210 def _Run(args, json_file_path, device): | |
| 211 """Start engine and forwarder and keep runnning. | |
| 212 | |
| 213 Args: | |
| 214 args: parsed command line arguments. | |
| 215 json_file_path: json file path which keeps the script variables. | |
| 216 device: (DeviceUtils) device to run the tests on. | |
| 217 """ | |
| 218 try: | |
| 219 engine_process = _Start(args, json_file_path, device) | |
| 220 while True: | |
| 221 nextline = engine_process.stdout.readline() | |
| 222 if nextline == '' and engine_process.poll() is not None: | |
| 223 # The engine died. | |
| 224 sys.exit(1) | |
| 225 sys.stdout.write(nextline) | |
| 226 sys.stdout.flush() | |
| 227 | |
| 228 except KeyboardInterrupt: | |
| 229 sys.exit(0) | |
| 230 finally: | |
| 231 _Stop(args, json_file_path, device) | |
| 232 | |
| 233 | |
| 234 def _Load(args, json_file_path, device): # pylint: disable=unused-argument | |
| 235 """Start client and load the url | |
| 236 | |
| 237 Args: | |
| 238 args: parsed command line arguments. | |
| 239 json_file_path: json file path which keeps the script variables. | |
| 240 device: (DeviceUtils) device to run the tests on. | |
| 241 """ | |
| 242 device.Install(os.path.join(SRC_PATH, args.apk_path), | |
| 243 reinstall=True) | |
| 244 run_client_intent = intent.Intent( | |
| 245 action='android.intent.action.VIEW', | |
| 246 package='org.chromium.chrome', | |
| 247 activity='com.google.android.apps.chrome.Main', | |
| 248 data=args.optional_url) | |
| 249 device.StartActivity(run_client_intent, blocking=True) | |
| 250 | |
| 251 | |
| 252 def _Stop(args, json_file_path, device): # pylint: disable=unused-argument | |
| 253 """Stop engine and forwarder | |
| 254 | |
| 255 Args: | |
| 256 args: (unused) parsed command line arguments. | |
| 257 json_file_path: json file path which keeps the script variables. | |
| 258 device: (DeviceUtils) device to run the tests on. | |
| 259 """ | |
| 260 if not _IsNonEmptyFile(json_file_path): | |
| 261 logging.error('Error: cannot find json file: ' + json_file_path) | |
| 262 sys.exit(1) | |
| 263 try: | |
| 264 with open(json_file_path, 'r') as f: | |
| 265 jsonarg = json.load(f, object_hook=_FromJson) | |
| 266 pid = int(jsonarg['pid']) | |
| 267 try: | |
| 268 forwarder.Forwarder.UnmapAllDevicePorts(device) | |
| 269 finally: | |
| 270 os.kill(pid, signal.SIGKILL) | |
| 271 finally: | |
| 272 os.remove(json_file_path) | |
| 273 | |
| 274 | |
| 275 def main(): | |
| 276 parser = argparse.ArgumentParser() | |
| 277 parser.add_argument('-l', | |
| 278 '--output-linux-directory', | |
| 279 required=True, | |
| 280 help='Path to the root linux build directory.' | |
| 281 ' Example: "out-linux/Debug"') | |
| 282 parser.add_argument('--adb-path', | |
| 283 type=os.path.abspath, | |
| 284 help='Path to the adb binary.') | |
| 285 subparsers = parser.add_subparsers(dest="subparser_name") | |
| 286 | |
| 287 start_parser = subparsers.add_parser('start') | |
| 288 start_parser.set_defaults(func=_Start) | |
| 289 AddStartArguments(start_parser) | |
| 290 | |
| 291 run_parser = subparsers.add_parser('run') | |
| 292 run_parser.set_defaults(func=_Run) | |
| 293 AddStartArguments(run_parser) | |
| 294 | |
| 295 load_parser = subparsers.add_parser('load') | |
| 296 load_parser.set_defaults(func=_Load) | |
| 297 load_parser.add_argument('-p', | |
| 298 '--apk-path', | |
| 299 required=True, | |
| 300 help='The path to the APK to install. ' | |
| 301 'Including the name of the apk containing ' | |
| 302 'the application (with the .apk extension).') | |
| 303 load_parser.add_argument('-u', | |
| 304 '--optional-url', | |
| 305 help='URL to navigate to.') | |
| 306 | |
| 307 stop_parser = subparsers.add_parser('stop') | |
| 308 stop_parser.set_defaults(func=_Stop) | |
| 309 | |
| 310 args = parser.parse_args() | |
| 311 devil_chromium.Initialize(adb_path=args.adb_path) | |
| 312 | |
| 313 json_file_path = os.path.join(SRC_PATH, args.output_linux_directory, | |
| 314 ARGS_JSON_FILE) | |
| 315 blacklist_file = '' | |
| 316 serial = '' | |
| 317 if args.subparser_name == 'start' or args.subparser_name == 'run': | |
| 318 blacklist_file = args.blacklist_file | |
| 319 serial = args.device | |
| 320 else: | |
| 321 if not _IsNonEmptyFile(json_file_path): | |
| 322 logging.error('Cannot find json file: %s' + json_file_path) | |
| 323 logging.error('Run `client_engine_integration.py {run,start}` first.') | |
| 324 sys.exit(1) | |
| 325 with open(json_file_path, 'r') as f: | |
| 326 file_lines = f.readlines() | |
| 327 jsonarg = json.loads(file_lines[0], object_hook=_FromJson) | |
| 328 blacklist_file = jsonarg['args'].blacklist_file | |
| 329 serial = jsonarg['args'].device | |
| 330 | |
| 331 blacklist = (device_blacklist.Blacklist(blacklist_file) | |
| 332 if blacklist_file | |
| 333 else None) | |
| 334 device = device_utils.DeviceUtils.HealthyDevices( | |
| 335 blacklist=blacklist, device_arg=serial)[0] | |
| 336 | |
| 337 args.func(args, json_file_path, device) | |
| 338 | |
| 339 | |
| 340 if __name__ == '__main__': | |
| 341 main() | |
| OLD | NEW |