blob: eb750881a55b772656c47e15fa8773985e170bc7 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tradefed.targetprep;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.ISdkBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceManager;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.IDeviceManager;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.TestDeviceState;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import junit.framework.Assert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A {@link ITargetPreparer} that will create an avd and launch an emulator
*/
public class SdkAvdPreparer implements ITargetPreparer {
private static final int ANDROID_TIMEOUT_MS = 15 * 1000;
@Option(name = "sdk-target", description = "the name of SDK target to launch. " +
"If unspecified, will use first target found")
private String mTargetName = null;
@Option(name = "boot-time", description =
"the maximum time in minutes to wait for emulator to boot.")
private long mMaxBootTime = 5;
@Option(name = "window", description = "launch emulator with a graphical window display.")
private boolean mWindow = false;
@Option(name = "launch-attempts", description = "max number of attempts to launch emulator")
private int mLaunchAttempts = 1;
private final IRunUtil mRunUtil;
private final IDeviceManager mDeviceManager;
/**
* Creates a {@link SdkAvdPreparer}.
*/
public SdkAvdPreparer() {
this(new RunUtil(), DeviceManager.getInstance());
}
/**
* Alternate constructor for injecting dependencies.
*
* @param runUtil
*/
SdkAvdPreparer(IRunUtil runUtil, IDeviceManager deviceManager) {
mRunUtil = runUtil;
mDeviceManager = deviceManager;
}
/**
* {@inheritDoc}
*/
@Override
public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
DeviceNotAvailableException, BuildError {
Assert.assertTrue("Provided build is not a ISdkBuildInfo",
buildInfo instanceof ISdkBuildInfo);
ISdkBuildInfo sdkBuild = (ISdkBuildInfo)buildInfo;
String[] targets = getSdkTargets(sdkBuild);
setAndroidSdkHome(sdkBuild);
String target = findTargetToLaunch(targets);
String avd = createAvdForTarget(sdkBuild, target);
launchEmulatorForAvd(sdkBuild, device, avd);
}
/**
* Gets the list of sdk targets from the given sdk.
*
* @param sdkBuild
* @return a list of defined targets
* @throws TargetSetupError if could not get targets
*/
private String[] getSdkTargets(ISdkBuildInfo sdkBuild) throws TargetSetupError {
CommandResult result = mRunUtil.runTimedCmd(ANDROID_TIMEOUT_MS,
sdkBuild.getAndroidToolPath(), "list", "targets", "--compact");
if (!result.getStatus().equals(CommandStatus.SUCCESS)) {
throw new TargetSetupError(String.format(
"Unable to get list of SDK targets using %s. Result %s, err %s",
sdkBuild.getAndroidToolPath(), result.getStatus(), result.getStderr()));
}
String[] targets = result.getStdout().split("\n");
if (result.getStdout().trim().isEmpty() || targets.length == 0) {
throw new TargetSetupError(String.format("No targets found in SDK %s.",
sdkBuild.getSdkDir().getAbsolutePath()));
}
return targets;
}
/**
* Sets the ANDROID_SDK_HOME environment variable. The SDK home directory is used as the
* location for SDK file storage of AVD definition files, etc.
*
* @param sdkBuild
*/
private void setAndroidSdkHome(ISdkBuildInfo sdkBuild) {
// store avds etc in sdk build location
// this has advantage that they will be cleaned up after run
mRunUtil.setEnvVariable("ANDROID_SDK_HOME", sdkBuild.getSdkDir().getAbsolutePath());
}
/**
* Find the SDK target to use.
* <p/>
* Will use the 'sdk-target' option if specified, otherwise will return last target in target
* list.
*
* @param targets the list of targets in SDK
* @return the SDK target name
* @throws TargetSetupError if specified 'sdk-target' cannot be found
*/
private String findTargetToLaunch(String[] targets) throws TargetSetupError {
if (mTargetName != null) {
for (String foundTarget : targets) {
if (foundTarget.equals(mTargetName)) {
return mTargetName;
}
}
throw new TargetSetupError(String.format("Could not find target %s in sdk",
mTargetName));
}
// just return last target
return targets[targets.length - 1];
}
/**
* Create an AVD for given SDK target
*
* @param sdkBuild the {@link ISdkBuildInfo}
* @param target the SDK target name
* @return the created AVD name
* @throws TargetSetupError if failed to create the AVD
*/
private String createAvdForTarget(ISdkBuildInfo sdkBuild, String target)
throws BuildError {
// answer 'no' when prompted for creating a custom hardware profile
final String cmdInput = "no\r\n";
final String successPattern = String.format("Created AVD '%s'", target);
CLog.d("Creating avd for target %s with name %s", target, target);
CommandResult result = mRunUtil.runTimedCmdWithInput(ANDROID_TIMEOUT_MS, cmdInput,
sdkBuild.getAndroidToolPath(), "create", "avd", "--target", target, "--name",
target, "--sdcard", "10M", "--force");
if (!result.getStatus().equals(CommandStatus.SUCCESS) || result.getStdout() == null ||
!result.getStdout().contains(successPattern)) {
// stdout usually doesn't contain useful data, so don't want to add it to the
// exception message. However, log it here as a debug log so the info is captured
// in log
CLog.d("AVD creation failed. stdout: %s", result.getStdout());
// treat as BuildError
throw new BuildError(String.format(
"Unable to create avd for target '%s'. stderr: '%s'", target,
result.getStderr()));
}
return target;
}
/**
* Launch an emulator for given avd, and wait for it to become available.
*
* @param avd the avd to launch
* @throws DeviceNotAvailableException
* @throws TargetSetupError
* @throws BuildError if emulator fails to boot
*/
private void launchEmulatorForAvd(ISdkBuildInfo sdkBuild, ITestDevice device, String avd)
throws DeviceNotAvailableException, TargetSetupError, BuildError {
if (!device.getDeviceState().equals(TestDeviceState.NOT_AVAILABLE)) {
CLog.w("Emulator %s is already running, killing", device.getSerialNumber());
mDeviceManager.killEmulator(device);
}
List<String> emulatorArgs = new ArrayList<String>(Arrays.asList(
sdkBuild.getEmulatorToolPath(), "-avd", avd));
if (!mWindow) {
emulatorArgs.add("-no-window");
}
launchEmulator(device, avd, emulatorArgs);
if (!device.getIDevice().getAvdName().equals(avd)) {
// not good. Either emulator isn't reporting its avd name properly, or somehow
// the wrong emulator launched. Treat as a BuildError
throw new BuildError(String.format(
"Emulator booted with incorrect avd name '%s'. Expected: '%s'.",
device.getIDevice().getAvdName(), avd));
}
}
/**
* Launch emulator, performing multiple attempts if necessary as specified.
*
* @param device
* @param avd
* @param emulatorArgs
* @throws BuildError
*/
void launchEmulator(ITestDevice device, String avd, List<String> emulatorArgs)
throws BuildError {
for (int i = 1; i <= mLaunchAttempts; i++) {
try {
mDeviceManager.launchEmulator(device, mMaxBootTime * 60 * 1000, mRunUtil,
emulatorArgs);
// hack alert! adb to emulator communication on first boot is notoriously flaky
// b/4644136
// send it a few adb commands to ensure the communication channel is stable
CLog.d("Testing adb to %s communication", device.getSerialNumber());
for (int j = 0; j < 3; j++) {
device.executeShellCommand("pm list instrumentation");
mRunUtil.sleep(2 * 1000);
}
// hurray - launched!
return;
} catch (DeviceNotAvailableException e) {
CLog.w("Emulator for avd '%s' failed to launch on attempt %d of %d. Cause: %s",
avd, i, mLaunchAttempts, e);
}
try {
// ensure process has been killed
mDeviceManager.killEmulator(device);
} catch (DeviceNotAvailableException e) {
// ignore
}
}
throw new BuildError(String.format("Emulator for avd '%s' failed to boot.", avd));
}
/**
* Sets the number of launch attempts to perform
*
* @param mLaunchAttempts
*/
void setLaunchAttempts(int launchAttempts) {
mLaunchAttempts = launchAttempts;
}
}