blob: ce73bd4bf7764e167af57427925ed67511160a64 [file] [log] [blame]
/*
* Copyright (C) 2017 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.python;
import com.android.annotations.VisibleForTesting;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.StubDevice;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.ResultForwarder;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.IDeviceTest;
import com.android.tradefed.testtype.IInvocationContextReceiver;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.PythonUnitTestResultParser;
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.SubprocessTestResultsParser;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** Host test meant to run a python binary file from the Android Build system (Soong) */
@OptionClass(alias = "python-host")
public class PythonBinaryHostTest
implements IRemoteTest, IDeviceTest, IBuildReceiver, IInvocationContextReceiver {
protected static final String PYTHON_OUTPUT = "python-output";
protected static final String ANDROID_SERIAL_VAR = "ANDROID_SERIAL";
@Option(name = "par-file-name", description = "The binary names inside the build info to run.")
private Set<String> mBinaryNames = new HashSet<>();
@Option(
name = "python-binaries",
description = "The full path to a runnable python binary. Can be repeated."
)
private Set<File> mBinaries = new HashSet<>();
@Option(
name = "test-timeout",
description = "Timeout for a single par file to terminate.",
isTimeVal = true
)
private long mTestTimeout = 20 * 1000L;
@Option(
name = "inject-serial-option",
description = "Whether or not to pass a -s <serialnumber> option to the binary")
private boolean mInjectSerial = false;
@Option(
name = "inject-android-serial",
description = "Whether or not to pass a ANDROID_SERIAL variable to the process.")
private boolean mInjectAndroidSerialVar = true;
private ITestDevice mDevice;
private IBuildInfo mBuildInfo;
private IInvocationContext mContext;
private IRunUtil mRunUtil;
@Override
public void setDevice(ITestDevice device) {
mDevice = device;
}
@Override
public ITestDevice getDevice() {
return mDevice;
}
@Override
public void setBuild(IBuildInfo buildInfo) {
mBuildInfo = buildInfo;
}
@Override
public void setInvocationContext(IInvocationContext invocationContext) {
mContext = invocationContext;
}
@Override
public final void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
List<File> pythonFilesList = findParFiles();
for (File pyFile : pythonFilesList) {
if (!pyFile.exists()) {
CLog.d(
"ignoring %s which doesn't look like a test file.",
pyFile.getAbsolutePath());
continue;
}
pyFile.setExecutable(true);
runSinglePythonFile(listener, pyFile);
}
}
private List<File> findParFiles() {
File testsDir = null;
if (mBuildInfo instanceof IDeviceBuildInfo) {
testsDir = ((IDeviceBuildInfo) mBuildInfo).getTestsDir();
}
List<File> files = new ArrayList<>();
for (String parFileName : mBinaryNames) {
File res = null;
// search tests dir
if (testsDir != null) {
res = FileUtil.findFile(testsDir, parFileName);
}
// TODO: is there other places to search?
if (res == null) {
throw new RuntimeException(
String.format("Couldn't find a par file %s", parFileName));
}
files.add(res);
}
files.addAll(mBinaries);
return files;
}
private void runSinglePythonFile(ITestInvocationListener listener, File pyFile) {
List<String> commandLine = new ArrayList<>();
commandLine.add(pyFile.getAbsolutePath());
// If we have a physical device, pass it to the python test by serial
if (!(getDevice().getIDevice() instanceof StubDevice) && mInjectSerial) {
// TODO: support multi-device python tests?
commandLine.add("-s");
commandLine.add(getDevice().getSerialNumber());
}
if (mInjectAndroidSerialVar) {
getRunUtil().setEnvVariable(ANDROID_SERIAL_VAR, getDevice().getSerialNumber());
}
CommandResult result =
getRunUtil().runTimedCmd(mTestTimeout, commandLine.toArray(new String[0]));
PythonForwarder forwarder = new PythonForwarder(listener, pyFile.getName());
if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
CLog.e(
"Something went wrong when running the python binary:\nstdout: "
+ "%s\nstderr:%s",
result.getStdout(), result.getStderr());
}
File resultFile = null;
try {
resultFile = FileUtil.createTempFile("python-res", ".txt");
FileUtil.writeToFile(result.getStderr(), resultFile);
try (FileInputStreamSource data = new FileInputStreamSource(resultFile)) {
listener.testLog(PYTHON_OUTPUT, LogDataType.TEXT, data);
}
// If it doesn't have the std output TEST_RUN_STARTED, use regular parser.
if (!result.getStderr().contains("TEST_RUN_STARTED")) {
// Attempt to parse the pure python output
PythonUnitTestResultParser pythonParser =
new PythonUnitTestResultParser(forwarder, "python-run");
pythonParser.processNewLines(result.getStderr().split("\n"));
} else {
try (SubprocessTestResultsParser parser =
new SubprocessTestResultsParser(forwarder, mContext)) {
parser.parseFile(resultFile);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
FileUtil.deleteFile(resultFile);
}
}
@VisibleForTesting
IRunUtil getRunUtil() {
if (mRunUtil == null) {
mRunUtil = new RunUtil();
}
return mRunUtil;
}
/** Result forwarder to replace the run name by the binary name. */
public class PythonForwarder extends ResultForwarder {
private String mRunName;
/** Ctor with the run name using the binary name. */
public PythonForwarder(ITestInvocationListener listener, String name) {
super(listener);
mRunName = name;
}
@Override
public void testRunStarted(String runName, int testCount) {
// Replace run name
super.testRunStarted(mRunName, testCount);
}
}
}