1# Copyright 2016 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5 6from recipe_engine import recipe_api 7from recipe_engine import recipe_test_api 8 9from . import default 10import subprocess # TODO(borenet): No! Remove this. 11 12 13"""Android flavor, used for running code on Android.""" 14 15 16class AndroidFlavor(default.DefaultFlavor): 17 def __init__(self, m, app_name): 18 super(AndroidFlavor, self).__init__(m, app_name) 19 self._ever_ran_adb = False 20 self.ADB_BINARY = '/usr/bin/adb.1.0.35' 21 self.ADB_PUB_KEY = '/home/chrome-bot/.android/adbkey' 22 if 'skia' not in self.m.vars.swarming_bot_id: 23 self.ADB_BINARY = '/opt/infra-android/tools/adb' 24 self.ADB_PUB_KEY = ('/home/chrome-bot/.android/' 25 'chrome_infrastructure_adbkey') 26 27 # Data should go in android_data_dir, which may be preserved across runs. 28 android_data_dir = '/sdcard/revenge_of_the_skiabot/' 29 self.device_dirs = default.DeviceDirs( 30 bin_dir = '/data/local/tmp/', 31 dm_dir = android_data_dir + 'dm_out', 32 perf_data_dir = android_data_dir + 'perf', 33 resource_dir = android_data_dir + 'resources', 34 images_dir = android_data_dir + 'images', 35 lotties_dir = android_data_dir + 'lotties', 36 skp_dir = android_data_dir + 'skps', 37 svg_dir = android_data_dir + 'svgs', 38 mskp_dir = android_data_dir + 'mskp', 39 tmp_dir = android_data_dir, 40 texttraces_dir = android_data_dir + 'text_blob_traces') 41 42 # A list of devices we can't root. If rooting fails and a device is not 43 # on the list, we fail the task to avoid perf inconsistencies. 44 self.cant_root = ['GalaxyS7_G930FD', 'GalaxyS9', 45 'GalaxyS20', 'MotoG4', 'NVIDIA_Shield', 46 'P30', 'Pixel4','Pixel4XL', 'Pixel5', 'TecnoSpark3Pro', 'JioNext'] 47 48 # Maps device type -> CPU ids that should be scaled for nanobench. 49 # Many devices have two (or more) different CPUs (e.g. big.LITTLE 50 # on Nexus5x). The CPUs listed are the biggest cpus on the device. 51 # The CPUs are grouped together, so we only need to scale one of them 52 # (the one listed) in order to scale them all. 53 # E.g. Nexus5x has cpu0-3 as one chip and cpu4-5 as the other. Thus, 54 # if one wants to run a single-threaded application (e.g. nanobench), one 55 # can disable cpu0-3 and scale cpu 4 to have only cpu4 and 5 at the same 56 # frequency. See also disable_for_nanobench. 57 self.cpus_to_scale = { 58 'Nexus5x': [4], 59 'Pixel': [2], 60 'Pixel2XL': [4] 61 } 62 63 # Maps device type -> CPU ids that should be turned off when running 64 # single-threaded applications like nanobench. The devices listed have 65 # multiple, differnt CPUs. We notice a lot of noise that seems to be 66 # caused by nanobench running on the slow CPU, then the big CPU. By 67 # disabling this, we see less of that noise by forcing the same CPU 68 # to be used for the performance testing every time. 69 self.disable_for_nanobench = { 70 'Nexus5x': range(0, 4), 71 'Pixel': range(0, 2), 72 'Pixel2XL': range(0, 4), 73 'Pixel6': range(4,8), # Only use the 4 small cores. 74 'Pixel7': range(4,8), 75 } 76 77 self.gpu_scaling = { 78 "Nexus5": 450000000, 79 "Nexus5x": 600000000, 80 } 81 82 def _wait_for_device(self, title, attempt): 83 self.m.run(self.m.step, 84 'adb kill-server after failure of \'%s\' (attempt %d)' % ( 85 title, attempt), 86 cmd=[self.ADB_BINARY, 'kill-server'], 87 infra_step=True, timeout=30, abort_on_failure=False, 88 fail_build_on_failure=False) 89 self.m.run(self.m.step, 90 'wait for device after failure of \'%s\' (attempt %d)' % ( 91 title, attempt), 92 cmd=[self.ADB_BINARY, 'wait-for-device'], infra_step=True, 93 timeout=180, abort_on_failure=False, 94 fail_build_on_failure=False) 95 self.m.run(self.m.step, 96 'adb devices -l after failure of \'%s\' (attempt %d)' % ( 97 title, attempt), 98 cmd=[self.ADB_BINARY, 'devices', '-l'], 99 infra_step=True, timeout=30, abort_on_failure=False, 100 fail_build_on_failure=False) 101 self.m.run(self.m.step, 102 'adb reboot device after failure of \'%s\' (attempt %d)' % ( 103 title, attempt), 104 cmd=[self.ADB_BINARY, 'reboot'], 105 infra_step=True, timeout=30, abort_on_failure=False, 106 fail_build_on_failure=False) 107 self.m.run(self.m.step, 108 'wait for device after failure of \'%s\' (attempt %d)' % ( 109 title, attempt), 110 cmd=[ 111 self.ADB_BINARY, 'wait-for-device', 'shell', 112 # Wait until the boot is actually complete. 113 # https://android.stackexchange.com/a/164050 114 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done', 115 ], 116 timeout=180, abort_on_failure=False, 117 fail_build_on_failure=False) 118 device = self.m.vars.builder_cfg.get('model') 119 if (device in self.cant_root): # pragma: nocover 120 return 121 self.m.run(self.m.step, 122 'adb root', 123 cmd=[ 124 self.ADB_BINARY, 'root' 125 ], 126 timeout=180, abort_on_failure=False, 127 fail_build_on_failure=False) 128 129 def _adb(self, title, *cmd, **kwargs): 130 # The only non-infra adb steps (dm / nanobench) happen to not use _adb(). 131 if 'infra_step' not in kwargs: 132 kwargs['infra_step'] = True 133 134 self._ever_ran_adb = True 135 # ADB seems to be occasionally flaky on every device, so always retry. 136 attempts = kwargs.pop('attempts', 3) 137 138 def wait_for_device(attempt): 139 return self._wait_for_device(title, attempt) 140 141 with self.m.context(cwd=self.m.path['start_dir'].join('skia')): 142 with self.m.env({'ADB_VENDOR_KEYS': self.ADB_PUB_KEY}): 143 return self.m.run.with_retry(self.m.step, title, attempts, 144 cmd=[self.ADB_BINARY]+list(cmd), 145 between_attempts_fn=wait_for_device, 146 **kwargs) 147 148 def _scale_for_dm(self): 149 device = self.m.vars.builder_cfg.get('model') 150 if (device in self.cant_root or 151 self.m.vars.internal_hardware_label): 152 return 153 154 # This is paranoia... any CPUs we disabled while running nanobench 155 # ought to be back online now that we've restarted the device. 156 for i in self.disable_for_nanobench.get(device, []): 157 self._set_cpu_online(i, 1) # enable 158 159 scale_up = self.cpus_to_scale.get(device, [0]) 160 # For big.LITTLE devices, make sure we scale the LITTLE cores up; 161 # there is a chance they are still in powersave mode from when 162 # swarming slows things down for cooling down and charging. 163 if 0 not in scale_up: 164 scale_up.append(0) 165 for i in scale_up: 166 # AndroidOne doesn't support ondemand governor. hotplug is similar. 167 if device == 'AndroidOne': 168 self._set_governor(i, 'hotplug') 169 elif device in ['Pixel3a', 'Pixel4', 'Pixel4a', 'Wembley', 'Pixel6', 'Pixel7']: 170 # Pixel3a/4/4a have userspace powersave performance schedutil. 171 # performance seems like a reasonable choice. 172 self._set_governor(i, 'performance') 173 else: 174 self._set_governor(i, 'ondemand') 175 176 def _scale_for_nanobench(self): 177 device = self.m.vars.builder_cfg.get('model') 178 if (device in self.cant_root or 179 self.m.vars.internal_hardware_label): 180 return 181 182 # Set to 'powersave' for Pixel6 and Pixel7. 183 for i in self.cpus_to_scale.get(device, [0]): 184 if device in ['Pixel6', 'Pixel7']: 185 self._set_governor(i, 'powersave') 186 else: 187 self._set_governor(i, 'userspace') 188 self._scale_cpu(i, 0.6) 189 190 for i in self.disable_for_nanobench.get(device, []): 191 self._set_cpu_online(i, 0) # disable 192 193 if device in self.gpu_scaling: 194 #https://developer.qualcomm.com/qfile/28823/lm80-p0436-11_adb_commands.pdf 195 # Section 3.2.1 Commands to put the GPU in performance mode 196 # Nexus 5 is 320000000 by default 197 # Nexus 5x is 180000000 by default 198 gpu_freq = self.gpu_scaling[device] 199 self.m.run.with_retry(self.m.python.inline, 200 "Lock GPU to %d (and other perf tweaks)" % gpu_freq, 201 3, # attempts 202 program=""" 203import os 204import subprocess 205import sys 206import time 207ADB = sys.argv[1] 208freq = sys.argv[2] 209idle_timer = "10000" 210 211log = subprocess.check_output([ADB, 'root']).decode('utf-8') 212# check for message like 'adbd cannot run as root in production builds' 213print(log) 214if 'cannot' in log: 215 raise Exception('adb root failed') 216 217subprocess.check_output([ADB, 'shell', 'stop', 'thermald']).decode('utf-8') 218 219subprocess.check_output([ADB, 'shell', 'echo "%s" > ' 220 '/sys/class/kgsl/kgsl-3d0/gpuclk' % freq]).decode('utf-8') 221 222actual_freq = subprocess.check_output([ADB, 'shell', 'cat ' 223 '/sys/class/kgsl/kgsl-3d0/gpuclk']).decode('utf-8').strip() 224if actual_freq != freq: 225 raise Exception('Frequency (actual, expected) (%s, %s)' 226 % (actual_freq, freq)) 227 228subprocess.check_call([ADB, 'shell', 'echo "%s" > ' 229 '/sys/class/kgsl/kgsl-3d0/idle_timer' % idle_timer]) 230 231actual_timer = subprocess.check_output([ADB, 'shell', 'cat ' 232 '/sys/class/kgsl/kgsl-3d0/idle_timer']).decode('utf-8').strip() 233if actual_timer != idle_timer: 234 raise Exception('idle_timer (actual, expected) (%s, %s)' 235 % (actual_timer, idle_timer)) 236 237for s in ['force_bus_on', 'force_rail_on', 'force_clk_on']: 238 subprocess.check_call([ADB, 'shell', 'echo "1" > ' 239 '/sys/class/kgsl/kgsl-3d0/%s' % s]) 240 actual_set = subprocess.check_output([ADB, 'shell', 'cat ' 241 '/sys/class/kgsl/kgsl-3d0/%s' % s]).decode('utf-8').strip() 242 if actual_set != "1": 243 raise Exception('%s (actual, expected) (%s, 1)' 244 % (s, actual_set)) 245""", 246 args = [self.ADB_BINARY, gpu_freq], 247 infra_step=True, 248 timeout=30) 249 250 def _set_governor(self, cpu, gov): 251 self._ever_ran_adb = True 252 self.m.run.with_retry(self.m.python.inline, 253 "Set CPU %d's governor to %s" % (cpu, gov), 254 3, # attempts 255 program=""" 256import os 257import subprocess 258import sys 259import time 260ADB = sys.argv[1] 261cpu = int(sys.argv[2]) 262gov = sys.argv[3] 263 264log = subprocess.check_output([ADB, 'root']).decode('utf-8') 265# check for message like 'adbd cannot run as root in production builds' 266print(log) 267if 'cannot' in log: 268 raise Exception('adb root failed') 269 270subprocess.check_output([ 271 ADB, 'shell', 272 'echo "%s" > /sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % ( 273 gov, cpu)]).decode('utf-8') 274actual_gov = subprocess.check_output([ 275 ADB, 'shell', 'cat /sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % 276 cpu]).decode('utf-8').strip() 277if actual_gov != gov: 278 raise Exception('(actual, expected) (%s, %s)' 279 % (actual_gov, gov)) 280""", 281 args = [self.ADB_BINARY, cpu, gov], 282 infra_step=True, 283 timeout=30) 284 285 286 def _set_cpu_online(self, cpu, value): 287 """Set /sys/devices/system/cpu/cpu{N}/online to value (0 or 1).""" 288 self._ever_ran_adb = True 289 msg = 'Disabling' 290 if value: 291 msg = 'Enabling' 292 293 def wait_for_device(attempt): 294 return self._wait_for_device("set cpu online", attempt) # pragma: nocover 295 296 self.m.run.with_retry(self.m.python.inline, 297 '%s CPU %d' % (msg, cpu), 298 3, # attempts 299 program=""" 300import os 301import subprocess 302import sys 303import time 304ADB = sys.argv[1] 305cpu = int(sys.argv[2]) 306value = int(sys.argv[3]) 307 308log = subprocess.check_output([ADB, 'root']).decode('utf-8') 309# check for message like 'adbd cannot run as root in production builds' 310print(log) 311if 'cannot' in log: 312 raise Exception('adb root failed') 313 314# If we try to echo 1 to an already online cpu, adb returns exit code 1. 315# So, check the value before trying to write it. 316prior_status = subprocess.check_output([ADB, 'shell', 'cat ' 317 '/sys/devices/system/cpu/cpu%d/online' % cpu]).decode('utf-8').strip() 318if prior_status == str(value): 319 print('CPU %d online already %d' % (cpu, value)) 320 sys.exit() 321 322subprocess.check_call([ADB, 'shell', 'echo %s > ' 323 '/sys/devices/system/cpu/cpu%d/online' % (value, cpu)]) 324actual_status = subprocess.check_output([ADB, 'shell', 'cat ' 325 '/sys/devices/system/cpu/cpu%d/online' % cpu]).decode('utf-8').strip() 326if actual_status != str(value): 327 raise Exception('(actual, expected) (%s, %d)' 328 % (actual_status, value)) 329""", 330 args = [self.ADB_BINARY, cpu, value], 331 infra_step=True, 332 between_attempts_fn=wait_for_device, 333 timeout=30) 334 335 336 def _scale_cpu(self, cpu, target_percent): 337 self._ever_ran_adb = True 338 339 def wait_for_device(attempt): 340 return self._wait_for_device("scale cpu", attempt) 341 342 self.m.run.with_retry(self.m.python.inline, 343 'Scale CPU %d to %f' % (cpu, target_percent), 344 3, # attempts 345 program=""" 346import os 347import subprocess 348import sys 349import time 350ADB = sys.argv[1] 351target_percent = float(sys.argv[2]) 352cpu = int(sys.argv[3]) 353log = subprocess.check_output([ADB, 'root']).decode('utf-8') 354# check for message like 'adbd cannot run as root in production builds' 355print(log) 356if 'cannot' in log: 357 raise Exception('adb root failed') 358 359root = '/sys/devices/system/cpu/cpu%d/cpufreq' %cpu 360 361# All devices we test on give a list of their available frequencies. 362available_freqs = subprocess.check_output([ADB, 'shell', 363 'cat %s/scaling_available_frequencies' % root]).decode('utf-8') 364 365# Check for message like '/system/bin/sh: file not found' 366if available_freqs and '/system/bin/sh' not in available_freqs: 367 available_freqs = sorted( 368 int(i) for i in available_freqs.strip().split()) 369else: 370 raise Exception('Could not get list of available frequencies: %s' % 371 available_freqs) 372 373maxfreq = available_freqs[-1] 374target = int(round(maxfreq * target_percent)) 375freq = maxfreq 376for f in reversed(available_freqs): 377 if f <= target: 378 freq = f 379 break 380 381print('Setting frequency to %d' % freq) 382 383# If scaling_max_freq is lower than our attempted setting, it won't take. 384# We must set min first, because if we try to set max to be less than min 385# (which sometimes happens after certain devices reboot) it returns a 386# perplexing permissions error. 387subprocess.check_call([ADB, 'shell', 'echo 0 > ' 388 '%s/scaling_min_freq' % root]) 389subprocess.check_call([ADB, 'shell', 'echo %d > ' 390 '%s/scaling_max_freq' % (freq, root)]) 391subprocess.check_call([ADB, 'shell', 'echo %d > ' 392 '%s/scaling_setspeed' % (freq, root)]) 393time.sleep(5) 394actual_freq = subprocess.check_output([ADB, 'shell', 'cat ' 395 '%s/scaling_cur_freq' % root]).decode('utf-8').strip() 396if actual_freq != str(freq): 397 raise Exception('(actual, expected) (%s, %d)' 398 % (actual_freq, freq)) 399""", 400 args = [self.ADB_BINARY, str(target_percent), cpu], 401 infra_step=True, 402 between_attempts_fn=wait_for_device, 403 timeout=30) 404 405 406 def _asan_setup_path(self): 407 return self.m.vars.workdir.join( 408 'android_ndk_linux', 'toolchains', 'llvm', 'prebuilt', 'linux-x86_64', 409 'lib64', 'clang', '9.0.8', 'bin', 'asan_device_setup') 410 411 412 def install(self): 413 self._adb('mkdir ' + self.device_dirs.resource_dir, 414 'shell', 'mkdir', '-p', self.device_dirs.resource_dir) 415 if self.m.vars.builder_cfg.get('model') in ['GalaxyS20', 'GalaxyS9']: 416 # See skia:10184, should be moot once upgraded to Android 11? 417 self._adb('cp libGLES_mali.so to ' + self.device_dirs.bin_dir, 418 'shell', 'cp', 419 '/vendor/lib64/egl/libGLES_mali.so', 420 self.device_dirs.bin_dir + 'libvulkan.so') 421 if 'ASAN' in self.m.vars.extra_tokens: 422 self._ever_ran_adb = True 423 self.m.run(self.m.python.inline, 'Setting up device to run ASAN', 424 program=""" 425import os 426import subprocess 427import sys 428import time 429ADB = sys.argv[1] 430ASAN_SETUP = sys.argv[2] 431 432def wait_for_device(): 433 while True: 434 time.sleep(5) 435 print('Waiting for device') 436 subprocess.check_call([ADB, 'wait-for-device']) 437 bit1 = subprocess.check_output([ADB, 'shell', 'getprop', 438 'dev.bootcomplete']).decode('utf-8') 439 bit2 = subprocess.check_output([ADB, 'shell', 'getprop', 440 'sys.boot_completed']).decode('utf-8') 441 if '1' in bit1 and '1' in bit2: 442 print('Device detected') 443 break 444 445log = subprocess.check_output([ADB, 'root']).decode('utf-8') 446# check for message like 'adbd cannot run as root in production builds' 447print(log) 448if 'cannot' in log: 449 raise Exception('adb root failed') 450 451output = subprocess.check_output([ADB, 'disable-verity']).decode('utf-8') 452print(output) 453 454if 'already disabled' not in output: 455 print('Rebooting device') 456 subprocess.check_call([ADB, 'reboot']) 457 wait_for_device() 458 459def installASAN(revert=False): 460 # ASAN setup script is idempotent, either it installs it or 461 # says it's installed. Returns True on success, false otherwise. 462 out = subprocess.check_output([ADB, 'wait-for-device']).decode('utf-8') 463 print(out) 464 cmd = [ASAN_SETUP] 465 if revert: 466 cmd = [ASAN_SETUP, '--revert'] 467 process = subprocess.Popen(cmd, env={'ADB': ADB}, 468 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 469 470 # this also blocks until command finishes 471 (stdout, stderr) = process.communicate() 472 print(stdout.decode('utf-8')) 473 print('Stderr: %s' % stderr.decode('utf-8')) 474 return process.returncode == 0 475 476if not installASAN(): 477 print('Trying to revert the ASAN install and then re-install') 478 # ASAN script sometimes has issues if it was interrupted or partially applied 479 # Try reverting it, then re-enabling it 480 if not installASAN(revert=True): 481 raise Exception('reverting ASAN install failed') 482 483 # Sleep because device does not reboot instantly 484 time.sleep(10) 485 486 if not installASAN(): 487 raise Exception('Tried twice to setup ASAN and failed.') 488 489# Sleep because device does not reboot instantly 490time.sleep(10) 491wait_for_device() 492# Sleep again to hopefully avoid error "secure_mkdirs failed: No such file or 493# directory" when pushing resources to the device. 494time.sleep(60) 495""", 496 args = [self.ADB_BINARY, self._asan_setup_path()], 497 infra_step=True, 498 timeout=300, 499 abort_on_failure=True) 500 if self.app_name: 501 if (self.app_name == 'nanobench'): 502 self._scale_for_nanobench() 503 else: 504 self._scale_for_dm() 505 app_path = self.host_dirs.bin_dir.join(self.app_name) 506 self._adb('push %s' % self.app_name, 507 'push', app_path, self.device_dirs.bin_dir) 508 509 510 511 def cleanup_steps(self): 512 self.m.run(self.m.step, 513 'adb reboot device', 514 cmd=[self.ADB_BINARY, 'reboot'], 515 infra_step=True, timeout=30, abort_on_failure=False, 516 fail_build_on_failure=False) 517 self.m.run(self.m.step, 518 'wait for device after rebooting', 519 cmd=[ 520 self.ADB_BINARY, 'wait-for-device', 'shell', 521 # Wait until the boot is actually complete. 522 # https://android.stackexchange.com/a/164050 523 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done', 524 ], 525 timeout=180, abort_on_failure=False, 526 fail_build_on_failure=False) 527 528 if 'ASAN' in self.m.vars.extra_tokens: 529 self._ever_ran_adb = True 530 # Remove ASAN. 531 self.m.run(self.m.step, 532 'wait for device before uninstalling ASAN', 533 cmd=[self.ADB_BINARY, 'wait-for-device', 'shell', 534 # Wait until the boot is actually complete. 535 # https://android.stackexchange.com/a/164050 536 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done', 537 ], infra_step=True, 538 timeout=180, abort_on_failure=False, 539 fail_build_on_failure=False) 540 self.m.run(self.m.step, 'uninstall ASAN', 541 cmd=[self._asan_setup_path(), '--revert'], 542 infra_step=True, timeout=300, 543 abort_on_failure=False, fail_build_on_failure=False) 544 545 if self._ever_ran_adb: 546 self.m.run(self.m.python.inline, 'dump log', program=""" 547 import os 548 import subprocess 549 import sys 550 out = sys.argv[1] 551 log = subprocess.check_output([ 552 '%s', 'logcat', '-d']).decode('utf-8', errors='ignore') 553 for line in log.split('\\n'): 554 tokens = line.split() 555 if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc': 556 addr, path = tokens[-2:] 557 local = os.path.join(out, os.path.basename(path)) 558 if os.path.exists(local): 559 try: 560 sym = subprocess.check_output([ 561 'addr2line', '-Cfpe', local, addr]).decode('utf-8') 562 line = line.replace(addr, addr + ' ' + sym.strip()) 563 except subprocess.CalledProcessError: 564 pass 565 print(line) 566 """ % self.ADB_BINARY, 567 args=[self.host_dirs.bin_dir], 568 infra_step=True, 569 timeout=300, 570 abort_on_failure=False) 571 572 # Only quarantine the bot if the first failed step 573 # is an infra step. If, instead, we did this for any infra failures, we 574 # would do this too much. For example, if a Nexus 10 died during dm 575 # and the following pull step would also fail "device not found" - causing 576 # us to run the shutdown command when the device was probably not in a 577 # broken state; it was just rebooting. 578 if (self.m.run.failed_steps and 579 isinstance(self.m.run.failed_steps[0], recipe_api.InfraFailure)): 580 bot_id = self.m.vars.swarming_bot_id 581 self.m.file.write_text('Quarantining Bot', 582 '/home/chrome-bot/%s.force_quarantine' % bot_id, 583 ' ') 584 585 # if self._ever_ran_adb: 586 # self._adb('kill adb server', 'kill-server') 587 588 def step(self, name, cmd): 589 sh = '%s.sh' % cmd[0] 590 self.m.run.writefile(self.m.vars.tmp_dir.join(sh), 591 'set -x; LD_LIBRARY_PATH=%s %s%s; echo $? >%src' % ( 592 self.device_dirs.bin_dir, 593 self.device_dirs.bin_dir, subprocess.list2cmdline(map(str, cmd)), 594 self.device_dirs.bin_dir)) 595 self._adb('push %s' % sh, 596 'push', self.m.vars.tmp_dir.join(sh), self.device_dirs.bin_dir) 597 598 self._adb('clear log', 'logcat', '-c') 599 self.m.python.inline('%s' % cmd[0], """ 600 import subprocess 601 import sys 602 bin_dir = sys.argv[1] 603 sh = sys.argv[2] 604 subprocess.check_call(['%s', 'shell', 'sh', bin_dir + sh]) 605 try: 606 sys.exit(int(subprocess.check_output([ 607 '%s', 'shell', 'cat', bin_dir + 'rc']).decode('utf-8'))) 608 except ValueError: 609 print("Couldn't read the return code. Probably killed for OOM.") 610 sys.exit(1) 611 """ % (self.ADB_BINARY, self.ADB_BINARY), 612 args=[self.device_dirs.bin_dir, sh]) 613 614 def copy_file_to_device(self, host, device): 615 self._adb('push %s %s' % (host, device), 'push', host, device) 616 617 def copy_directory_contents_to_device(self, host, device): 618 contents = self.m.file.glob_paths('ls %s/*' % host, 619 host, '*', 620 test_data=['foo.png', 'bar.jpg']) 621 args = contents + [device] 622 self._adb('push %s/* %s' % (host, device), 'push', *args) 623 624 def copy_directory_contents_to_host(self, device, host): 625 # TODO(borenet): When all of our devices are on Android 6.0 and up, we can 626 # switch to using tar to zip up the results before pulling. 627 with self.m.step.nest('adb pull'): 628 tmp = self.m.path.mkdtemp('adb_pull') 629 self._adb('pull %s' % device, 'pull', device, tmp) 630 paths = self.m.file.glob_paths( 631 'list pulled files', 632 tmp, 633 self.m.path.basename(device) + self.m.path.sep + '*', 634 test_data=['%d.png' % i for i in (1, 2)]) 635 for p in paths: 636 self.m.file.copy('copy %s' % self.m.path.basename(p), p, host) 637 638 def read_file_on_device(self, path, **kwargs): 639 testKwargs = { 640 'attempts': 1, 641 'abort_on_failure': False, 642 'fail_build_on_failure': False, 643 } 644 rv = self._adb('check if %s exists' % path, 645 'shell', 'test', '-f', path, **testKwargs) 646 if not rv: # pragma: nocover 647 return None 648 649 rv = self._adb('read %s' % path, 650 'shell', 'cat', path, stdout=self.m.raw_io.output(), 651 **kwargs) 652 return rv.stdout.decode('utf-8').rstrip() if rv and rv.stdout else None 653 654 def remove_file_on_device(self, path): 655 self.m.run.with_retry(self.m.python.inline, 'rm %s' % path, 3, program=""" 656 import subprocess 657 import sys 658 659 # Remove the path. 660 adb = sys.argv[1] 661 path = sys.argv[2] 662 print('Removing %s' % path) 663 cmd = [adb, 'shell', 'rm', '-rf', path] 664 print(' '.join(cmd)) 665 subprocess.check_call(cmd) 666 667 # Verify that the path was deleted. 668 print('Checking for existence of %s' % path) 669 cmd = [adb, 'shell', 'ls', path] 670 print(' '.join(cmd)) 671 try: 672 output = subprocess.check_output( 673 cmd, stderr=subprocess.STDOUT).decode('utf-8') 674 except subprocess.CalledProcessError as e: 675 output = e.output.decode('utf-8') 676 print('Output was:') 677 print('======') 678 print(output) 679 print('======') 680 if 'No such file or directory' not in output: 681 raise Exception('%s exists despite being deleted' % path) 682 """, 683 args=[self.ADB_BINARY, path], 684 infra_step=True) 685 686 def create_clean_device_dir(self, path): 687 self.remove_file_on_device(path) 688 self._adb('mkdir %s' % path, 'shell', 'mkdir', '-p', path) 689