blob: e5b58368a9c83220792101ae33267d4c480f61f2 [file] [log] [blame]
/*
* Copyright (C) 2019 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.testtype.binary;
import com.android.annotations.VisibleForTesting;
import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.StubDevice;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.FailureDescription;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import com.android.tradefed.testtype.IDeviceTest;
import com.android.tradefed.util.AdbUtils;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.TestRunnerUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Test runner for executable running on the host. The runner implements {@link IDeviceTest} since
* the host binary might communicate to a device. If the received device is not a {@link StubDevice}
* the serial will be passed to the binary to be used.
*/
@OptionClass(alias = "executable-host-test")
public class ExecutableHostTest extends ExecutableBaseTest {
private static final String ANDROID_SERIAL = "ANDROID_SERIAL";
private static final String LD_LIBRARY_PATH = "LD_LIBRARY_PATH";
private static final String LOG_STDOUT_TAG = "-binary-stdout-";
private static final String LOG_STDERR_TAG = "-binary-stderr-";
@Option(
name = "relative-path-execution",
description =
"Some scripts assume a relative location to their tests file, this allows to"
+ " execute with that relative location."
)
private boolean mExecuteRelativeToScript = false;
@Override
public String findBinary(String binary) {
File bin = new File(binary);
// If it's a local path or absolute path
if (bin.exists()) {
return bin.getAbsolutePath();
}
if (getTestInfo().getBuildInfo() instanceof IDeviceBuildInfo) {
IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo) getTestInfo().getBuildInfo();
File testsDir = deviceBuild.getTestsDir();
List<File> scanDirs = new ArrayList<>();
// If it exists, always look first in the ANDROID_HOST_OUT_TESTCASES
File targetTestCases = deviceBuild.getFile(BuildInfoFileKey.HOST_LINKED_DIR);
if (targetTestCases != null) {
scanDirs.add(targetTestCases);
}
if (testsDir != null) {
scanDirs.add(testsDir);
}
try {
// Search the full tests dir if no target dir is available.
File src = FileUtil.findFile(binary, getAbi(), scanDirs.toArray(new File[] {}));
if (src != null) {
return src.getAbsolutePath();
}
} catch (IOException e) {
CLog.e("Failed to find test files from directory.");
}
}
return null;
}
@Override
public void runBinary(
String binaryPath, ITestInvocationListener listener, TestDescription description)
throws DeviceNotAvailableException, IOException {
IRunUtil runUtil = createRunUtil();
// Output everything in stdout
runUtil.setRedirectStderrToStdout(true);
// If we are running against a real device, set ANDROID_SERIAL to the proper serial.
if (!(getTestInfo().getDevice().getIDevice() instanceof StubDevice)) {
runUtil.setEnvVariable(ANDROID_SERIAL, getTestInfo().getDevice().getSerialNumber());
}
String ldLibraryPath = TestRunnerUtil.getLdLibraryPath(new File(binaryPath));
// Also add the directory of the binary path as the test may package library as data
// dependency.
String cwd = new File(binaryPath).getParentFile().getAbsolutePath();
if (ldLibraryPath != null) {
ldLibraryPath = String.format("%s%s%s", ldLibraryPath, java.io.File.pathSeparator, cwd);
} else {
ldLibraryPath = cwd;
}
runUtil.setEnvVariable(LD_LIBRARY_PATH, ldLibraryPath);
// Set Tradefed adb on $PATH of binary
AdbUtils.updateAdb(getTestInfo(), runUtil, getAdbPath());
// Ensure its executable
FileUtil.chmodRWXRecursively(new File(binaryPath));
List<String> command = new ArrayList<>();
String scriptName = new File(binaryPath).getName();
if (mExecuteRelativeToScript) {
String parentDir = new File(binaryPath).getParent();
command.add("bash");
command.add("-c");
command.add(String.format("pushd %s; ./%s;", parentDir, scriptName));
} else {
command.add(binaryPath);
}
File stdout = FileUtil.createTempFile(scriptName + LOG_STDOUT_TAG, ".txt");
File stderr = FileUtil.createTempFile(scriptName + LOG_STDERR_TAG, ".txt");
try (FileOutputStream stdoutStream = new FileOutputStream(stdout);
FileOutputStream stderrStream = new FileOutputStream(stderr); ) {
CommandResult res =
runUtil.runTimedCmd(
getTimeoutPerBinaryMs(),
stdoutStream,
stderrStream,
command.toArray(new String[0]));
if (!CommandStatus.SUCCESS.equals(res.getStatus())) {
FailureStatus status = FailureStatus.TEST_FAILURE;
// Everything should be outputted in stdout with our redirect above.
String errorMessage = FileUtil.readStringFromFile(stdout);
if (CommandStatus.TIMED_OUT.equals(res.getStatus())) {
errorMessage += "\nTimeout.";
status = FailureStatus.TIMED_OUT;
}
if (res.getExitCode() != null) {
errorMessage += String.format("\nExit Code: %s", res.getExitCode());
}
listener.testFailed(
description,
FailureDescription.create(errorMessage).setFailureStatus(status));
}
} finally {
logFile(stdout, listener);
logFile(stderr, listener);
}
if (!(getTestInfo().getDevice().getIDevice() instanceof StubDevice)) {
// Ensure that the binary did not leave the device offline.
CLog.d("Checking whether device is still online after %s", binaryPath);
try {
getTestInfo().getDevice().waitForDeviceAvailable();
} catch (DeviceNotAvailableException e) {
FailureDescription failure =
FailureDescription.create(
String.format(
"Device became unavailable after %s.", binaryPath),
FailureStatus.LOST_SYSTEM_UNDER_TEST)
.setErrorIdentifier(DeviceErrorIdentifier.DEVICE_UNAVAILABLE)
.setCause(e);
listener.testRunFailed(failure);
throw e;
}
}
}
@VisibleForTesting
IRunUtil createRunUtil() {
return new RunUtil();
}
@VisibleForTesting
String getAdbPath() {
return GlobalConfiguration.getDeviceManagerInstance().getAdbPath();
}
private void logFile(File logFile, ITestLogger logger) {
try (FileInputStreamSource source = new FileInputStreamSource(logFile, true)) {
logger.testLog(logFile.getName(), LogDataType.TEXT, source);
}
}
}