Add support for python unit tests and adb use cases
- Handle pure python unit tests in par files.
- Handle the adb required setup in a preparer for adb
testing.
Test: tf unit tests
atest adb_integration_test_adb with a temporary AndroidTest.xml
http://sponge.corp.google.com/invocation?tab=Test+Cases&show=FAILED&show=INTERNAL_ERROR&id=3d282e52-4cbe-434c-9d16-36323902419c
Bug: 112104122
Change-Id: I95f3a3ac9b41543d9f332b20c97716d5c6d949b0
diff --git a/src/com/android/tradefed/targetprep/adb/AdbStopServerPreparer.java b/src/com/android/tradefed/targetprep/adb/AdbStopServerPreparer.java
index b070bd6..66462ed 100644
--- a/src/com/android/tradefed/targetprep/adb/AdbStopServerPreparer.java
+++ b/src/com/android/tradefed/targetprep/adb/AdbStopServerPreparer.java
@@ -15,24 +15,42 @@
*/
package com.android.tradefed.targetprep.adb;
+import com.android.annotations.VisibleForTesting;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.IDeviceManager;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.targetprep.BaseTargetPreparer;
import com.android.tradefed.targetprep.BuildError;
import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.SemaphoreTokenTargetPreparer;
import com.android.tradefed.targetprep.TargetSetupError;
+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;
-/** Target preparer to stop adb server on the host before and after running adb tests. */
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Target preparer to stop adb server on the host before and after running adb tests.
+ *
+ * <p>This preparer should be used with care as it stops and restart adb on the hosts. It should
+ * usually be tight with {@link SemaphoreTokenTargetPreparer} to avoid other tests from running at
+ * the same time.
+ */
public class AdbStopServerPreparer extends BaseTargetPreparer implements ITargetCleaner {
- private IRunUtil mRunUtil = RunUtil.getDefault();
-
+ private static final String PATH = "PATH";
private static final long CMD_TIMEOUT = 60000L;
+ private static final String ANDROID_HOST_OUT = "ANDROID_HOST_OUT";
+
+ private IRunUtil mRunUtil;
+ private File mTmpDir;
/** {@inheritDoc} */
@Override
@@ -42,29 +60,89 @@
getDeviceManager().stopAdbBridge();
// Kill the default adb server
- mRunUtil.runTimedCmd(CMD_TIMEOUT, "adb", "kill-server");
- // Wait 1000ms for "adb kill-server" to finish. See b/37104408
- mRunUtil.sleep(1 * 1000);
+ getRunUtil().runTimedCmd(CMD_TIMEOUT, "adb", "kill-server");
+
+ File adb = null;
+ if (System.getenv(ANDROID_HOST_OUT) != null) {
+ String hostOut = System.getenv(ANDROID_HOST_OUT);
+ adb = new File(hostOut, "bin/adb");
+ if (adb.exists()) {
+ adb.setExecutable(true);
+ } else {
+ adb = null;
+ }
+ }
+
+ if (adb == null && buildInfo.getFile("adb") != null) {
+ adb = buildInfo.getFile("adb");
+ adb = renameAdbBinary(adb);
+ }
+
+ if (adb != null) {
+ CLog.d("Restarting adb from %s", adb.getAbsolutePath());
+ IRunUtil restartAdb = createRunUtil();
+ restartAdb.setEnvVariable(PATH, adb.getAbsolutePath());
+ CommandResult result =
+ restartAdb.runTimedCmd(CMD_TIMEOUT, adb.getAbsolutePath(), "start-server");
+ if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
+ throw new TargetSetupError(
+ "Failed to restart adb with the build info one.",
+ device.getDeviceDescriptor());
+ }
+ } else {
+ getRunUtil().runTimedCmd(CMD_TIMEOUT, "adb", "start-server");
+ throw new TargetSetupError(
+ "Could not find a new version of adb to tests.", device.getDeviceDescriptor());
+ }
}
/** {@inheritDoc} */
@Override
public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
throws DeviceNotAvailableException {
+ FileUtil.recursiveDelete(mTmpDir);
// Kill the test adb server
- mRunUtil.runTimedCmd(CMD_TIMEOUT, "adb", "kill-server");
- // Wait 1000ms for "adb kill-server" to finish. See b/37104408
- mRunUtil.sleep(1 * 1000);
- // Restart the default adb server found in system PATH. Ensure adb server is already
- // running the next time TF executes a command over adb bridge. Otherwise adb bridge may
- // see a "Connection refused" exception.
- mRunUtil.runTimedCmd(CMD_TIMEOUT, "adb", "start-server");
- mRunUtil.sleep(1 * 1000);
-
+ getRunUtil().runTimedCmd(CMD_TIMEOUT, "adb", "kill-server");
+ // Restart the one from the parent PATH (original one)
+ getRunUtil().runTimedCmd(CMD_TIMEOUT, "adb", "start-server");
+ // Restart device manager monitor
getDeviceManager().restartAdbBridge();
}
+ @VisibleForTesting
IDeviceManager getDeviceManager() {
return GlobalConfiguration.getDeviceManagerInstance();
}
+
+ @VisibleForTesting
+ IRunUtil createRunUtil() {
+ return new RunUtil();
+ }
+
+ private IRunUtil getRunUtil() {
+ if (mRunUtil == null) {
+ mRunUtil = createRunUtil();
+ }
+ return mRunUtil;
+ }
+
+ private File renameAdbBinary(File originalAdb) {
+ try {
+ mTmpDir = FileUtil.createTempDir("adb");
+ } catch (IOException e) {
+ CLog.e("Cannot create temp directory");
+ FileUtil.recursiveDelete(mTmpDir);
+ return null;
+ }
+ File renamedAdbBinary = new File(mTmpDir, "adb");
+ if (!originalAdb.renameTo(renamedAdbBinary)) {
+ CLog.e("Cannot rename adb binary");
+ return null;
+ }
+ if (!renamedAdbBinary.setExecutable(true)) {
+ CLog.e("Cannot set adb binary executable");
+ return null;
+ }
+ return renamedAdbBinary;
+ }
}
diff --git a/src/com/android/tradefed/testtype/PythonUnitTestResultParser.java b/src/com/android/tradefed/testtype/PythonUnitTestResultParser.java
index 70e68b4..b36c5e4 100644
--- a/src/com/android/tradefed/testtype/PythonUnitTestResultParser.java
+++ b/src/com/android/tradefed/testtype/PythonUnitTestResultParser.java
@@ -20,6 +20,8 @@
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.TestDescription;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@@ -95,7 +97,7 @@
private int mFailedTestCount;
// General state
- private final Collection<ITestInvocationListener> mListeners;
+ private Collection<ITestInvocationListener> mListeners = new ArrayList<>();
private final String mRunName;
private Map<TestDescription, String> mTestResultCache;
// Use a special entry to mark skipped test in mTestResultCache
@@ -145,11 +147,19 @@
/**
* Create a new {@link PythonUnitTestResultParser} that reports to the given {@link
+ * ITestInvocationListener}.
+ */
+ public PythonUnitTestResultParser(ITestInvocationListener listener, String runName) {
+ this(Arrays.asList(listener), runName);
+ }
+
+ /**
+ * Create a new {@link PythonUnitTestResultParser} that reports to the given {@link
* ITestInvocationListener}s.
*/
public PythonUnitTestResultParser(
Collection<ITestInvocationListener> listeners, String runName) {
- mListeners = listeners;
+ mListeners.addAll(listeners);
mRunName = runName;
mTestResultCache = new HashMap<>();
}
diff --git a/src/com/android/tradefed/testtype/python/PythonBinaryHostTest.java b/src/com/android/tradefed/testtype/python/PythonBinaryHostTest.java
index dd07c17..ce73bd4 100644
--- a/src/com/android/tradefed/testtype/python/PythonBinaryHostTest.java
+++ b/src/com/android/tradefed/testtype/python/PythonBinaryHostTest.java
@@ -33,12 +33,12 @@
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.StreamUtil;
import com.android.tradefed.util.SubprocessTestResultsParser;
import java.io.File;
@@ -72,7 +72,14 @@
)
private long mTestTimeout = 20 * 1000L;
- @Option(name = "inject-android-serial")
+ @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;
@@ -143,10 +150,8 @@
private void runSinglePythonFile(ITestInvocationListener listener, File pyFile) {
List<String> commandLine = new ArrayList<>();
commandLine.add(pyFile.getAbsolutePath());
- // Run with -q (quiet) to avoid extraneous outputs
- commandLine.add("-q");
// If we have a physical device, pass it to the python test by serial
- if (!(getDevice().getIDevice() instanceof StubDevice)) {
+ if (!(getDevice().getIDevice() instanceof StubDevice) && mInjectSerial) {
// TODO: support multi-device python tests?
commandLine.add("-s");
commandLine.add(getDevice().getSerialNumber());
@@ -160,17 +165,12 @@
getRunUtil().runTimedCmd(mTestTimeout, commandLine.toArray(new String[0]));
PythonForwarder forwarder = new PythonForwarder(listener, pyFile.getName());
if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
- // If the binary finishes we an error code, it could simply be a test failure, but if
- // it does not even have a TEST_RUN_STARTED tag, then we probably have binary setup
- // issue.
- if (!result.getStderr().contains("TEST_RUN_STARTED")) {
- throw new RuntimeException(
- String.format(
- "Something went wrong when running the python binary: %s",
- result.getStderr()));
- }
+ CLog.e(
+ "Something went wrong when running the python binary:\nstdout: "
+ + "%s\nstderr:%s",
+ result.getStdout(), result.getStderr());
}
- SubprocessTestResultsParser parser = new SubprocessTestResultsParser(forwarder, mContext);
+
File resultFile = null;
try {
resultFile = FileUtil.createTempFile("python-res", ".txt");
@@ -178,12 +178,22 @@
try (FileInputStreamSource data = new FileInputStreamSource(resultFile)) {
listener.testLog(PYTHON_OUTPUT, LogDataType.TEXT, data);
}
- parser.parseFile(resultFile);
+ // 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);
- StreamUtil.close(parser);
}
}
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 1a51701..b571ad3 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -161,6 +161,7 @@
import com.android.tradefed.targetprep.TestAppInstallSetupTest;
import com.android.tradefed.targetprep.TestFilePushSetupTest;
import com.android.tradefed.targetprep.TimeSetterTargetPreparerTest;
+import com.android.tradefed.targetprep.adb.AdbStopServerPreparerTest;
import com.android.tradefed.targetprep.multi.MergeMultiBuildTargetPreparerTest;
import com.android.tradefed.targetprep.suite.SuiteApkInstallerTest;
import com.android.tradefed.testtype.AndroidJUnitTestTest;
@@ -471,6 +472,9 @@
TestFilePushSetupTest.class,
TimeSetterTargetPreparerTest.class,
+ // targetprep.adb
+ AdbStopServerPreparerTest.class,
+
// targetprep.multi
MergeMultiBuildTargetPreparerTest.class,
diff --git a/tests/src/com/android/tradefed/targetprep/adb/AdbStopServerPreparerTest.java b/tests/src/com/android/tradefed/targetprep/adb/AdbStopServerPreparerTest.java
new file mode 100644
index 0000000..0eba115
--- /dev/null
+++ b/tests/src/com/android/tradefed/targetprep/adb/AdbStopServerPreparerTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2018 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.adb;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.build.BuildInfo;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.IDeviceManager;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.targetprep.TargetSetupError;
+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 org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+
+/** Unit tests for {@link AdbStopServerPreparer}. */
+@RunWith(JUnit4.class)
+public class AdbStopServerPreparerTest {
+
+ private AdbStopServerPreparer mPreparer;
+ private IRunUtil mMockRunUtil;
+ private IDeviceManager mMockManager;
+
+ private ITestDevice mMockDevice;
+ private IBuildInfo mMockBuild;
+ private File mFakeAdbFile;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockRunUtil = EasyMock.createMock(IRunUtil.class);
+ mMockManager = EasyMock.createMock(IDeviceManager.class);
+ mMockDevice = EasyMock.createMock(ITestDevice.class);
+ mPreparer =
+ new AdbStopServerPreparer() {
+ @Override
+ IRunUtil createRunUtil() {
+ return mMockRunUtil;
+ }
+
+ @Override
+ IDeviceManager getDeviceManager() {
+ return mMockManager;
+ }
+ };
+ mMockBuild = new BuildInfo();
+ mFakeAdbFile = FileUtil.createTempFile("adb", "");
+ mMockBuild.setFile("adb", mFakeAdbFile, "v1");
+
+ EasyMock.expect(mMockDevice.getDeviceDescriptor()).andStubReturn(null);
+ }
+
+ @After
+ public void tearDown() {
+ FileUtil.deleteFile(mFakeAdbFile);
+ }
+
+ /** Test that during setup the adb server is stopped then restart with a new found version. */
+ @Test
+ public void testSetup_tearDown() throws Exception {
+ mMockManager.stopAdbBridge();
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ // setUp
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(), EasyMock.eq("adb"), EasyMock.eq("kill-server")))
+ .andReturn(result);
+ mMockRunUtil.setEnvVariable(EasyMock.eq("PATH"), EasyMock.anyObject());
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(),
+ EasyMock.contains("adb"),
+ EasyMock.eq("start-server")))
+ .andReturn(result);
+ // tear down
+ mockTearDown();
+ EasyMock.replay(mMockRunUtil, mMockManager, mMockDevice);
+ mPreparer.setUp(mMockDevice, mMockBuild);
+ mPreparer.tearDown(mMockDevice, mMockBuild, null);
+ EasyMock.verify(mMockRunUtil, mMockManager, mMockDevice);
+ }
+
+ /** Test when restarting the adb server fails. */
+ @Test
+ public void testSetup_fail_tearDown() throws Exception {
+ mMockManager.stopAdbBridge();
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ // setUp
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(), EasyMock.eq("adb"), EasyMock.eq("kill-server")))
+ .andReturn(result);
+ mMockRunUtil.setEnvVariable(EasyMock.eq("PATH"), EasyMock.anyObject());
+
+ CommandResult failedResult = new CommandResult(CommandStatus.FAILED);
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(),
+ EasyMock.contains("adb"),
+ EasyMock.eq("start-server")))
+ .andReturn(failedResult);
+
+ // tear down
+ mockTearDown();
+ EasyMock.replay(mMockRunUtil, mMockManager, mMockDevice);
+ try {
+ mPreparer.setUp(mMockDevice, mMockBuild);
+ fail("Should have thrown an exception.");
+ } catch (TargetSetupError expected) {
+ // Expected
+ }
+ mPreparer.tearDown(mMockDevice, mMockBuild, null);
+ EasyMock.verify(mMockRunUtil, mMockManager, mMockDevice);
+ }
+
+ /** Test that we fail the preparation when there is no adb to test. */
+ @Test
+ public void testNoAdb() throws Exception {
+ mMockManager.stopAdbBridge();
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ // setUp
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(), EasyMock.eq("adb"), EasyMock.eq("kill-server")))
+ .andReturn(result);
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(),
+ EasyMock.eq("adb"),
+ EasyMock.eq("start-server")))
+ .andReturn(result);
+ EasyMock.replay(mMockRunUtil, mMockManager, mMockDevice);
+ try {
+ mPreparer.setUp(mMockDevice, new BuildInfo());
+ fail("Should have thrown an exception.");
+ } catch (TargetSetupError expected) {
+ // Expected
+ assertTrue(
+ expected.getMessage()
+ .contains("Could not find a new version of adb to tests."));
+ }
+ EasyMock.verify(mMockRunUtil, mMockManager, mMockDevice);
+ }
+
+ private void mockTearDown() {
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(), EasyMock.eq("adb"), EasyMock.eq("kill-server")))
+ .andReturn(result);
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(),
+ EasyMock.eq("adb"),
+ EasyMock.eq("start-server")))
+ .andReturn(result);
+ mMockManager.restartAdbBridge();
+ }
+}
diff --git a/tests/src/com/android/tradefed/testtype/python/PythonBinaryHostTestTest.java b/tests/src/com/android/tradefed/testtype/python/PythonBinaryHostTestTest.java
index 26c3b83..fd01f4f 100644
--- a/tests/src/com/android/tradefed/testtype/python/PythonBinaryHostTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/python/PythonBinaryHostTestTest.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.testtype.python;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import com.android.tradefed.build.IBuildInfo;
@@ -76,9 +77,7 @@
res.setStderr("TEST_RUN_STARTED {\"testCount\": 5, \"runName\": \"TestSuite\"}");
EasyMock.expect(
mMockRunUtil.runTimedCmd(
- EasyMock.anyLong(),
- EasyMock.eq(binary.getAbsolutePath()),
- EasyMock.eq("-q")))
+ EasyMock.anyLong(), EasyMock.eq(binary.getAbsolutePath())))
.andReturn(res);
mMockListener.testRunStarted(binary.getName(), 5);
mMockListener.testLog(
@@ -110,17 +109,22 @@
res.setStderr("Could not execute.");
EasyMock.expect(
mMockRunUtil.runTimedCmd(
- EasyMock.anyLong(),
- EasyMock.eq(binary.getAbsolutePath()),
- EasyMock.eq("-q")))
+ EasyMock.anyLong(), EasyMock.eq(binary.getAbsolutePath())))
.andReturn(res);
EasyMock.expect(mMockDevice.getIDevice()).andReturn(new StubDevice("serial"));
+
+ mMockListener.testLog(
+ EasyMock.eq("python-output"),
+ EasyMock.eq(LogDataType.TEXT),
+ EasyMock.anyObject());
+
EasyMock.replay(mMockRunUtil, mMockBuildInfo, mMockListener, mMockDevice);
try {
mTest.run(mMockListener);
fail("Should have thrown an exception.");
} catch (RuntimeException expected) {
// expected
+ assertEquals("Failed to parse Python unittest result", expected.getMessage());
}
EasyMock.verify(mMockRunUtil, mMockBuildInfo, mMockListener, mMockDevice);
} finally {
@@ -143,9 +147,7 @@
res.setStderr("TEST_RUN_STARTED {\"testCount\": 5, \"runName\": \"TestSuite\"}");
EasyMock.expect(
mMockRunUtil.runTimedCmd(
- EasyMock.anyLong(),
- EasyMock.eq(binary.getAbsolutePath()),
- EasyMock.eq("-q")))
+ EasyMock.anyLong(), EasyMock.eq(binary.getAbsolutePath())))
.andReturn(res);
mMockListener.testRunStarted(binary.getName(), 5);
mMockListener.testLog(