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 |