Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(47)

Side by Side Diff: build/android/buildbot/bb_device_status_check.py

Issue 1123263005: [Android] Remove hand-rolled adb devices call from bb_device_status_check. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebase Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | build/android/pylib/device/adb_wrapper.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # 2 #
3 # Copyright 2013 The Chromium Authors. All rights reserved. 3 # Copyright 2013 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be 4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file. 5 # found in the LICENSE file.
6 6
7 """A class to keep track of devices across builds and report state.""" 7 """A class to keep track of devices across builds and report state."""
8 import json 8 import json
9 import logging 9 import logging
10 import optparse 10 import optparse
11 import os 11 import os
12 import psutil 12 import psutil
13 import re 13 import re
14 import signal 14 import signal
15 import smtplib 15 import smtplib
16 import subprocess 16 import subprocess
17 import sys 17 import sys
18 import time 18 import time
19 import urllib 19 import urllib
20 20
21 import bb_annotations 21 import bb_annotations
22 import bb_utils 22 import bb_utils
23 23
24 sys.path.append(os.path.join(os.path.dirname(__file__), 24 sys.path.append(os.path.join(os.path.dirname(__file__),
25 os.pardir, os.pardir, 'util', 'lib', 25 os.pardir, os.pardir, 'util', 'lib',
26 'common')) 26 'common'))
27 import perf_tests_results_helper # pylint: disable=F0401 27 import perf_tests_results_helper # pylint: disable=F0401
28 28
29 sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 29 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
30 from pylib import android_commands
31 from pylib import constants 30 from pylib import constants
32 from pylib.cmd_helper import GetCmdOutput 31 from pylib.cmd_helper import GetCmdOutput
32 from pylib.device import adb_wrapper
33 from pylib.device import battery_utils 33 from pylib.device import battery_utils
34 from pylib.device import device_blacklist 34 from pylib.device import device_blacklist
35 from pylib.device import device_errors 35 from pylib.device import device_errors
36 from pylib.device import device_list 36 from pylib.device import device_list
37 from pylib.device import device_utils 37 from pylib.device import device_utils
38 from pylib.utils import run_tests_helper 38 from pylib.utils import run_tests_helper
39 39
40 _RE_DEVICE_ID = re.compile('Device ID = (\d+)') 40 _RE_DEVICE_ID = re.compile('Device ID = (\d+)')
41 41
42 def DeviceInfo(serial, options): 42 def DeviceInfo(device, options):
43 """Gathers info on a device via various adb calls. 43 """Gathers info on a device via various adb calls.
44 44
45 Args: 45 Args:
46 serial: The serial of the attached device to construct info about. 46 device: A DeviceUtils instance for the device to construct info about.
47 47
48 Returns: 48 Returns:
49 Tuple of device type, build id, report as a string, error messages, and 49 Tuple of device type, build id, report as a string, error messages, and
50 boolean indicating whether or not device can be used for testing. 50 boolean indicating whether or not device can be used for testing.
51 """ 51 """
52 device = device_utils.DeviceUtils(serial)
53 battery = battery_utils.BatteryUtils(device) 52 battery = battery_utils.BatteryUtils(device)
54 53
55 build_product = '' 54 build_product = ''
56 build_id = '' 55 build_id = ''
57 battery_level = 100 56 battery_level = 100
58 errors = [] 57 errors = []
59 dev_good = True 58 dev_good = True
60 json_data = {} 59 json_data = {}
61 60
62 try: 61 try:
63 build_product = device.build_product 62 build_product = device.build_product
64 build_id = device.build_id 63 build_id = device.build_id
65 64
66 json_data = { 65 json_data = {
67 'serial': serial, 66 'serial': device.adb.GetDeviceSerial(),
68 'type': build_product, 67 'type': build_product,
69 'build': build_id, 68 'build': build_id,
70 'build_detail': device.GetProp('ro.build.fingerprint'), 69 'build_detail': device.GetProp('ro.build.fingerprint'),
71 'battery': {}, 70 'battery': {},
72 'imei_slice': 'Unknown', 71 'imei_slice': 'Unknown',
73 'wifi_ip': device.GetProp('dhcp.wlan0.ipaddress'), 72 'wifi_ip': device.GetProp('dhcp.wlan0.ipaddress'),
74 } 73 }
75 74
76 battery_info = {} 75 battery_info = {}
77 try: 76 try:
78 battery_info = battery.GetBatteryInfo(timeout=5) 77 battery_info = battery.GetBatteryInfo(timeout=5)
79 battery_level = int(battery_info.get('level', battery_level)) 78 battery_level = int(battery_info.get('level', battery_level))
80 json_data['battery'] = battery_info 79 json_data['battery'] = battery_info
81 except device_errors.CommandFailedError: 80 except device_errors.CommandFailedError:
82 logging.exception('Failed to get battery information for %s', serial) 81 logging.exception('Failed to get battery information for %s', str(device))
83 82
84 try: 83 try:
85 for l in device.RunShellCommand(['dumpsys', 'iphonesubinfo'], 84 for l in device.RunShellCommand(['dumpsys', 'iphonesubinfo'],
86 check_return=True, timeout=5): 85 check_return=True, timeout=5):
87 m = _RE_DEVICE_ID.match(l) 86 m = _RE_DEVICE_ID.match(l)
88 if m: 87 if m:
89 json_data['imei_slice'] = m.group(1)[-6:] 88 json_data['imei_slice'] = m.group(1)[-6:]
90 except device_errors.CommandFailedError: 89 except device_errors.CommandFailedError:
91 logging.exception('Failed to get IMEI slice for %s', serial) 90 logging.exception('Failed to get IMEI slice for %s', str(device))
92 91
93 if battery_level < 15: 92 if battery_level < 15:
94 errors += ['Device critically low in battery.'] 93 errors += ['Device critically low in battery.']
95 dev_good = False 94 dev_good = False
96 if not battery.GetCharging(): 95 if not battery.GetCharging():
97 battery.SetCharging(True) 96 battery.SetCharging(True)
98 if not options.no_provisioning_check: 97 if not options.no_provisioning_check:
99 setup_wizard_disabled = ( 98 setup_wizard_disabled = (
100 device.GetProp('ro.setupwizard.mode') == 'DISABLED') 99 device.GetProp('ro.setupwizard.mode') == 'DISABLED')
101 if not setup_wizard_disabled and device.build_type != 'user': 100 if not setup_wizard_disabled and device.build_type != 'user':
102 errors += ['Setup wizard not disabled. Was it provisioned correctly?'] 101 errors += ['Setup wizard not disabled. Was it provisioned correctly?']
103 if (device.product_name == 'mantaray' and 102 if (device.product_name == 'mantaray' and
104 battery_info.get('AC powered', None) != 'true'): 103 battery_info.get('AC powered', None) != 'true'):
105 errors += ['Mantaray device not connected to AC power.'] 104 errors += ['Mantaray device not connected to AC power.']
106 except device_errors.CommandFailedError: 105 except device_errors.CommandFailedError:
107 logging.exception('Failure while getting device status.') 106 logging.exception('Failure while getting device status.')
108 dev_good = False 107 dev_good = False
109 except device_errors.CommandTimeoutError: 108 except device_errors.CommandTimeoutError:
110 logging.exception('Timeout while getting device status.') 109 logging.exception('Timeout while getting device status.')
111 dev_good = False 110 dev_good = False
112 111
113 return (build_product, build_id, battery_level, errors, dev_good, json_data) 112 return (build_product, build_id, battery_level, errors, dev_good, json_data)
114 113
115 114
116 def CheckForMissingDevices(options, adb_online_devs): 115 def CheckForMissingDevices(options, devices):
117 """Uses file of previous online devices to detect broken phones. 116 """Uses file of previous online devices to detect broken phones.
118 117
119 Args: 118 Args:
120 options: out_dir parameter of options argument is used as the base 119 options: out_dir parameter of options argument is used as the base
121 directory to load and update the cache file. 120 directory to load and update the cache file.
122 adb_online_devs: A list of serial numbers of the currently visible 121 devices: A list of DeviceUtils instance for the currently visible and
123 and online attached devices. 122 online attached devices.
124 """ 123 """
125 out_dir = os.path.abspath(options.out_dir) 124 out_dir = os.path.abspath(options.out_dir)
125 device_serials = set(d.adb.GetDeviceSerial() for d in devices)
126 126
127 # last_devices denotes all known devices prior to this run 127 # last_devices denotes all known devices prior to this run
128 last_devices_path = os.path.join(out_dir, device_list.LAST_DEVICES_FILENAME) 128 last_devices_path = os.path.join(out_dir, device_list.LAST_DEVICES_FILENAME)
129 last_missing_devices_path = os.path.join(out_dir, 129 last_missing_devices_path = os.path.join(out_dir,
130 device_list.LAST_MISSING_DEVICES_FILENAME) 130 device_list.LAST_MISSING_DEVICES_FILENAME)
131 try: 131 try:
132 last_devices = device_list.GetPersistentDeviceList(last_devices_path) 132 last_devices = device_list.GetPersistentDeviceList(last_devices_path)
133 except IOError: 133 except IOError:
134 # Ignore error, file might not exist 134 # Ignore error, file might not exist
135 last_devices = [] 135 last_devices = []
136 136
137 try: 137 try:
138 last_missing_devices = device_list.GetPersistentDeviceList( 138 last_missing_devices = device_list.GetPersistentDeviceList(
139 last_missing_devices_path) 139 last_missing_devices_path)
140 except IOError: 140 except IOError:
141 last_missing_devices = [] 141 last_missing_devices = []
142 142
143 missing_devs = list(set(last_devices) - set(adb_online_devs)) 143 missing_devs = list(set(last_devices) - device_serials)
144 new_missing_devs = list(set(missing_devs) - set(last_missing_devices)) 144 new_missing_devs = list(set(missing_devs) - set(last_missing_devices))
145 145
146 if new_missing_devs and os.environ.get('BUILDBOT_SLAVENAME'): 146 if new_missing_devs and os.environ.get('BUILDBOT_SLAVENAME'):
147 logging.info('new_missing_devs %s' % new_missing_devs) 147 logging.info('new_missing_devs %s' % new_missing_devs)
148 devices_missing_msg = '%d devices not detected.' % len(missing_devs) 148 devices_missing_msg = '%d devices not detected.' % len(missing_devs)
149 bb_annotations.PrintSummaryText(devices_missing_msg) 149 bb_annotations.PrintSummaryText(devices_missing_msg)
150 150
151 from_address = 'chrome-bot@chromium.org' 151 from_address = 'chrome-bot@chromium.org'
152 to_addresses = ['chrome-labs-tech-ticket@google.com', 152 to_addresses = ['chrome-labs-tech-ticket@google.com',
153 'chrome-android-device-alert@google.com'] 153 'chrome-android-device-alert@google.com']
154 cc_addresses = ['chrome-android-device-alert@google.com'] 154 cc_addresses = ['chrome-android-device-alert@google.com']
155 subject = 'Devices offline on %s, %s, %s' % ( 155 subject = 'Devices offline on %s, %s, %s' % (
156 os.environ.get('BUILDBOT_SLAVENAME'), 156 os.environ.get('BUILDBOT_SLAVENAME'),
157 os.environ.get('BUILDBOT_BUILDERNAME'), 157 os.environ.get('BUILDBOT_BUILDERNAME'),
158 os.environ.get('BUILDBOT_BUILDNUMBER')) 158 os.environ.get('BUILDBOT_BUILDNUMBER'))
159 msg = ('Please reboot the following devices:\n%s' % 159 msg = ('Please reboot the following devices:\n%s' %
160 '\n'.join(map(str, new_missing_devs))) 160 '\n'.join(map(str, new_missing_devs)))
161 SendEmail(from_address, to_addresses, cc_addresses, subject, msg) 161 SendEmail(from_address, to_addresses, cc_addresses, subject, msg)
162 162
163 all_known_devices = list(set(adb_online_devs) | set(last_devices)) 163 all_known_devices = list(device_serials | set(last_devices))
164 device_list.WritePersistentDeviceList(last_devices_path, all_known_devices) 164 device_list.WritePersistentDeviceList(last_devices_path, all_known_devices)
165 device_list.WritePersistentDeviceList(last_missing_devices_path, missing_devs) 165 device_list.WritePersistentDeviceList(last_missing_devices_path, missing_devs)
166 166
167 if not all_known_devices: 167 if not all_known_devices:
168 # This can happen if for some reason the .last_devices file is not 168 # This can happen if for some reason the .last_devices file is not
169 # present or if it was empty. 169 # present or if it was empty.
170 return ['No online devices. Have any devices been plugged in?'] 170 return ['No online devices. Have any devices been plugged in?']
171 if missing_devs: 171 if missing_devs:
172 devices_missing_msg = '%d devices not detected.' % len(missing_devs) 172 devices_missing_msg = '%d devices not detected.' % len(missing_devs)
173 bb_annotations.PrintSummaryText(devices_missing_msg) 173 bb_annotations.PrintSummaryText(devices_missing_msg)
174 174 return ['Current online devices: %s' % ', '.join(d for d in device_serials),
175 # TODO(navabi): Debug by printing both output from GetCmdOutput and 175 '%s are no longer visible. Were they removed?' % missing_devs]
176 # GetAttachedDevices to compare results.
177 crbug_link = ('https://code.google.com/p/chromium/issues/entry?summary='
178 '%s&comment=%s&labels=Restrict-View-Google,OS-Android,Infra' %
179 (urllib.quote('Device Offline'),
180 urllib.quote('Buildbot: %s %s\n'
181 'Build: %s\n'
182 '(please don\'t change any labels)' %
183 (os.environ.get('BUILDBOT_BUILDERNAME'),
184 os.environ.get('BUILDBOT_SLAVENAME'),
185 os.environ.get('BUILDBOT_BUILDNUMBER')))))
186 return ['Current online devices: %s' % adb_online_devs,
187 '%s are no longer visible. Were they removed?' % missing_devs,
188 'SHERIFF:',
189 '@@@STEP_LINK@Click here to file a bug@%s@@@' % crbug_link,
190 'Cache file: %s' % last_devices_path,
191 'adb devices: %s' % GetCmdOutput(['adb', 'devices']),
192 'adb devices(GetAttachedDevices): %s' % adb_online_devs]
193 else: 176 else:
194 new_devs = set(adb_online_devs) - set(last_devices) 177 new_devs = device_serials - set(last_devices)
195 if new_devs and os.path.exists(last_devices_path): 178 if new_devs and os.path.exists(last_devices_path):
196 bb_annotations.PrintWarning() 179 bb_annotations.PrintWarning()
197 bb_annotations.PrintSummaryText( 180 bb_annotations.PrintSummaryText(
198 '%d new devices detected' % len(new_devs)) 181 '%d new devices detected' % len(new_devs))
199 logging.info('New devices detected:') 182 logging.info('New devices detected:')
200 for d in new_devs: 183 for d in new_devs:
201 logging.info(' %s', d) 184 logging.info(' %s', d)
202 185
203 186
204 def SendEmail(from_address, to_addresses, cc_addresses, subject, msg): 187 def SendEmail(from_address, to_addresses, cc_addresses, subject, msg):
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
296 run_tests_helper.SetLogLevel(options.verbose) 279 run_tests_helper.SetLogLevel(options.verbose)
297 280
298 # Remove the last build's "bad devices" before checking device statuses. 281 # Remove the last build's "bad devices" before checking device statuses.
299 device_blacklist.ResetBlacklist() 282 device_blacklist.ResetBlacklist()
300 283
301 try: 284 try:
302 expected_devices = device_list.GetPersistentDeviceList( 285 expected_devices = device_list.GetPersistentDeviceList(
303 os.path.join(options.out_dir, device_list.LAST_DEVICES_FILENAME)) 286 os.path.join(options.out_dir, device_list.LAST_DEVICES_FILENAME))
304 except IOError: 287 except IOError:
305 expected_devices = [] 288 expected_devices = []
306 devices = android_commands.GetAttachedDevices() 289 devices = device_utils.DeviceUtils.HealthyDevices()
290 device_serials = [d.adb.GetDeviceSerial() for d in devices]
307 # Only restart usb if devices are missing. 291 # Only restart usb if devices are missing.
308 if set(expected_devices) != set(devices): 292 if set(expected_devices) != set(device_serials):
309 logging.warning('expected_devices: %s', expected_devices) 293 logging.warning('expected_devices: %s', expected_devices)
310 logging.warning('devices: %s', devices) 294 logging.warning('devices: %s', device_serials)
311 KillAllAdb() 295 KillAllAdb()
312 retries = 5 296 retries = 5
313 usb_restarted = True 297 usb_restarted = True
314 if options.restart_usb: 298 if options.restart_usb:
315 if not RestartUsb(): 299 if not RestartUsb():
316 usb_restarted = False 300 usb_restarted = False
317 bb_annotations.PrintWarning() 301 bb_annotations.PrintWarning()
318 logging.error('USB reset stage failed, ' 302 logging.error('USB reset stage failed, '
319 'wait for any device to come back.') 303 'wait for any device to come back.')
320 while retries: 304 while retries:
321 logging.info('retry adb devices...') 305 logging.info('retry adb devices...')
322 time.sleep(1) 306 time.sleep(1)
323 devices = android_commands.GetAttachedDevices() 307 devices = device_utils.DeviceUtils.HealthyDevices()
324 if set(expected_devices) == set(devices): 308 device_serials = [d.adb.GetDeviceSerial() for d in devices]
309 if set(expected_devices) == set(device_serials):
325 # All devices are online, keep going. 310 # All devices are online, keep going.
326 break 311 break
327 if not usb_restarted and devices: 312 if not usb_restarted and devices:
328 # The USB wasn't restarted, but there's at least one device online. 313 # The USB wasn't restarted, but there's at least one device online.
329 # No point in trying to wait for all devices. 314 # No point in trying to wait for all devices.
330 break 315 break
331 retries -= 1 316 retries -= 1
332 317
333 # TODO(navabi): Test to make sure this fails and then fix call
334 offline_devices = android_commands.GetAttachedDevices(
335 hardware=False, emulator=False, offline=True)
336
337 types, builds, batteries, errors, devices_ok, json_data = ( 318 types, builds, batteries, errors, devices_ok, json_data = (
338 [], [], [], [], [], []) 319 [], [], [], [], [], [])
339 if devices: 320 if devices:
340 types, builds, batteries, errors, devices_ok, json_data = ( 321 types, builds, batteries, errors, devices_ok, json_data = (
341 zip(*[DeviceInfo(dev, options) for dev in devices])) 322 zip(*[DeviceInfo(dev, options) for dev in devices]))
342 323
343 # Write device info to file for buildbot info display. 324 # Write device info to file for buildbot info display.
344 if os.path.exists('/home/chrome-bot'): 325 if os.path.exists('/home/chrome-bot'):
345 with open('/home/chrome-bot/.adb_device_info', 'w') as f: 326 with open('/home/chrome-bot/.adb_device_info', 'w') as f:
346 for device in json_data: 327 for device in json_data:
(...skipping 15 matching lines...) Expand all
362 for j in json_data: 343 for j in json_data:
363 logging.info('Device %s (%s)', j.get('serial'), j.get('type')) 344 logging.info('Device %s (%s)', j.get('serial'), j.get('type'))
364 logging.info(' Build: %s (%s)', j.get('build'), j.get('build_detail')) 345 logging.info(' Build: %s (%s)', j.get('build'), j.get('build_detail'))
365 logging.info(' Current Battery Service state:') 346 logging.info(' Current Battery Service state:')
366 for k, v in j.get('battery', {}).iteritems(): 347 for k, v in j.get('battery', {}).iteritems():
367 logging.info(' %s: %s', k, v) 348 logging.info(' %s: %s', k, v)
368 logging.info(' IMEI slice: %s', j.get('imei_slice')) 349 logging.info(' IMEI slice: %s', j.get('imei_slice'))
369 logging.info(' WiFi IP: %s', j.get('wifi_ip')) 350 logging.info(' WiFi IP: %s', j.get('wifi_ip'))
370 351
371 352
372 for serial, dev_errors in zip(devices, errors): 353 for dev, dev_errors in zip(devices, errors):
373 if dev_errors: 354 if dev_errors:
374 err_msg += ['%s errors:' % serial] 355 err_msg += ['%s errors:' % str(dev)]
375 err_msg += [' %s' % error for error in dev_errors] 356 err_msg += [' %s' % error for error in dev_errors]
376 357
377 if err_msg: 358 if err_msg:
378 bb_annotations.PrintWarning() 359 bb_annotations.PrintWarning()
379 for e in err_msg: 360 for e in err_msg:
380 logging.error(e) 361 logging.error(e)
381 from_address = 'buildbot@chromium.org' 362 from_address = 'buildbot@chromium.org'
382 to_addresses = ['chromium-android-device-alerts@google.com'] 363 to_addresses = ['chromium-android-device-alerts@google.com']
383 bot_name = os.environ.get('BUILDBOT_BUILDERNAME') 364 bot_name = os.environ.get('BUILDBOT_BUILDERNAME')
384 slave_name = os.environ.get('BUILDBOT_SLAVENAME') 365 slave_name = os.environ.get('BUILDBOT_SLAVENAME')
385 subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name) 366 subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name)
386 SendEmail(from_address, to_addresses, [], subject, '\n'.join(err_msg)) 367 SendEmail(from_address, to_addresses, [], subject, '\n'.join(err_msg))
387 368
388 if options.device_status_dashboard: 369 if options.device_status_dashboard:
370 offline_devices = [
371 device_utils.DeviceUtils(a)
372 for a in adb_wrapper.AdbWrapper.Devices(is_ready=False)
373 if a.GetState() == 'offline']
374
389 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OnlineDevices', 375 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OnlineDevices',
390 [len(devices)], 'devices') 376 [len(devices)], 'devices')
391 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OfflineDevices', 377 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OfflineDevices',
392 [len(offline_devices)], 'devices', 378 [len(offline_devices)], 'devices',
393 'unimportant') 379 'unimportant')
394 for serial, battery in zip(devices, batteries): 380 for dev, battery in zip(devices, batteries):
395 perf_tests_results_helper.PrintPerfResult('DeviceBattery', serial, 381 perf_tests_results_helper.PrintPerfResult('DeviceBattery', str(dev),
396 [battery], '%', 382 [battery], '%',
397 'unimportant') 383 'unimportant')
398 384
399 if options.json_output: 385 if options.json_output:
400 with open(options.json_output, 'wb') as f: 386 with open(options.json_output, 'wb') as f:
401 f.write(json.dumps(json_data, indent=4)) 387 f.write(json.dumps(json_data, indent=4))
402 388
403 num_failed_devs = 0 389 num_failed_devs = 0
404 for device_ok, device in zip(devices_ok, devices): 390 for device_ok, device in zip(devices_ok, devices):
405 if not device_ok: 391 if not device_ok:
406 logging.warning('Blacklisting %s', str(device)) 392 logging.warning('Blacklisting %s', str(device))
407 device_blacklist.ExtendBlacklist([str(device)]) 393 device_blacklist.ExtendBlacklist([str(device)])
408 num_failed_devs += 1 394 num_failed_devs += 1
409 395
410 if num_failed_devs == len(devices): 396 if num_failed_devs == len(devices):
411 return 2 397 return 2
412 398
413 if not devices: 399 if not devices:
414 return 1 400 return 1
415 401
416 402
417 if __name__ == '__main__': 403 if __name__ == '__main__':
418 sys.exit(main()) 404 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | build/android/pylib/device/adb_wrapper.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698