| # Copyright 2020 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. |
| import re |
| from contextlib import contextmanager |
| from recipe_engine import recipe_api |
| |
| |
| class AndroidVirtualDeviceApi(recipe_api.RecipeApi): |
| """Installs and manages an Android AVD. |
| """ |
| |
| def download(self, avd_root, env, env_prefixes, version=None): |
| """Installs the android avd emulator package. |
| |
| Args: |
| env(dict): Current environment variables. |
| env_prefixes(dict): Current environment prefixes variables. |
| avd_root: The root path to install the AVD package. |
| """ |
| assert self.m.platform.is_linux |
| self.avd_root = avd_root |
| self.version = version |
| with self.m.step.nest('download avd package'): |
| self.m.file.ensure_directory('Ensure avd cache', self.avd_root) |
| with self.m.context(env=env, env_prefixes=env_prefixes, cwd=self.avd_root), self.m.depot_tools.on_path(): |
| # Download and install AVD |
| self.m.cipd.ensure( |
| self.avd_root, |
| self.m.cipd.EnsureFile().add_package( |
| 'chromium/tools/android/avd/linux-amd64', |
| '1EQ8d87vhYyuBlCFZU8po6bpNXScHFB-9PGR27JlIS8C' |
| ) |
| ) |
| |
| adb_root = self.avd_root.join( |
| 'src', 'third_party', 'android_sdk', 'public', 'platform-tools' |
| ) |
| self.adb_path = adb_root.join('adb') |
| paths = env_prefixes.get('PATH', []) |
| paths.append(adb_root) |
| env_prefixes['PATH'] = paths |
| env['AVD_ROOT'] = self.avd_root |
| env['ADB_PATH'] = self.adb_path |
| |
| def start(self, env, env_prefixes, version=None): |
| """Starts an android avd emulator. |
| |
| Args: |
| env(dict): Current environment variables. |
| env_prefixes(dict): Current environment prefixes variables. |
| version(string): The android API version of the emulator as a string. |
| """ |
| self.version = version or self.version or '31' |
| self.emulator_pid = '' |
| with self.m.step.nest('start avd'): |
| with self.m.context(env=env, env_prefixes=env_prefixes, cwd=self.avd_root), self.m.depot_tools.on_path(): |
| avd_script_path = self.avd_root.join( |
| 'src', 'tools', 'android', 'avd', 'avd.py' |
| ) |
| avd_config = self.avd_root.join( |
| 'src', 'tools', 'android', 'avd', 'proto', 'generic_android%s.textpb' % self.version |
| ) |
| self.m.step( |
| 'Install Android emulator (API level %s)' % self.version, |
| ['vpython3', avd_script_path, 'install', '--avd-config', avd_config], |
| stdout=self.m.raw_io.output_text(add_output_log=True) |
| ) |
| output = self.m.step( |
| 'Start Android emulator (API level %s)' % self.version, |
| ['vpython3', avd_script_path, 'start', '--no-read-only', '--wipe-data', '--writable-system', '--debug-tags', 'all', '--avd-config', avd_config], |
| stdout=self.m.raw_io.output_text(add_output_log=True) |
| ).stdout |
| |
| m = re.search(r'.*pid: (\d+)\)', output) |
| self.emulator_pid = m.group(1) |
| |
| env['EMULATOR_PID'] = self.emulator_pid |
| return self.emulator_pid |
| |
| def setup(self, env, env_prefixes): |
| """Configures a running emulator and waits for it to reach the home screen. |
| |
| Args: |
| env(dict): Current environment variables. |
| env_prefixes(dict): Current environment prefixes variables. |
| """ |
| with self.m.step.nest('avd setup'): |
| with self.m.context(env=env, env_prefixes=env_prefixes, cwd=self.avd_root): |
| # Only supported on linux. Do not run this on other platforms. |
| resource_name = self.resource('avd_setup.sh') |
| self.m.step( |
| 'Set execute permission', |
| ['chmod', '755', resource_name], |
| infra_step=True, |
| ) |
| self.m.test_utils.run_test('avd_setup.sh', [resource_name, str(self.adb_path)], infra_step=True) |
| |
| def kill(self, emulator_pid=None): |
| """Kills the emulator and cleans up any zombie QEMU processes. |
| |
| Args: |
| emulator_pid(string): The PID of the emulator process. |
| """ |
| assert self.m.platform.is_linux |
| with self.m.step.nest('kill and cleanup avd'): |
| pid_to_kill = emulator_pid or self.emulator_pid |
| self.m.step('Kill emulator cleanup', ['kill', '-9', pid_to_kill]) |
| |
| # Kill zombie processes left over by QEMU on the host. |
| step_result = self.m.step('list processes', |
| ['ps', '-axww'], |
| stdout=self.m.raw_io.output_text(add_output_log=True), |
| stderr=self.m.raw_io.output_text(add_output_log=True)) |
| zombieList = ['qemu-system'] |
| killCommand = ['kill', '-9'] |
| for line in step_result.stdout.splitlines(): |
| # Check if current process has zombie process substring. |
| if any(zombie in line for zombie in zombieList): |
| killCommand.append(line.split(None, 1)[0]) |
| if len(killCommand) > 2: |
| self.m.step('Kill zombie processes', killCommand, ok_ret='any') |