Merge "Add option to not rescale device screenshot."
diff --git a/prod-tests/src/com/android/ota/tests/SideloadOtaStabilityTest.java b/prod-tests/src/com/android/ota/tests/SideloadOtaStabilityTest.java
index 018bc4b..e77b70b 100644
--- a/prod-tests/src/com/android/ota/tests/SideloadOtaStabilityTest.java
+++ b/prod-tests/src/com/android/ota/tests/SideloadOtaStabilityTest.java
@@ -20,8 +20,6 @@
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
-import com.android.ddmlib.AdbCommandRejectedException;
-import com.android.ddmlib.TimeoutException;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.build.OtaDeviceBuildInfo;
@@ -46,6 +44,7 @@
import com.android.tradefed.testtype.IDeviceTest;
import com.android.tradefed.testtype.IResumableTest;
import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.DeviceRecoveryModeUtil;
import com.android.tradefed.util.StreamUtil;
import org.junit.Assert;
@@ -227,20 +226,8 @@
// so we should just reboot in that case since we no longer need to be in
// recovery
CLog.i("Device is not online, attempting to recover before capturing logs");
- if (managedDevice.getDeviceState().equals(TestDeviceState.RECOVERY)) {
- CLog.i("Rebooting to exit recovery");
- try {
- // we don't want to enable root until the reboot is fully finished and
- // the device is available, or it may get stuck in recovery and time out
- managedDevice.getIDevice().reboot(null);
- managedDevice.waitForDeviceAvailable(mMaxRebootTimeSec * 1000);
- managedDevice.postBootSetup();
- } catch (TimeoutException | AdbCommandRejectedException | IOException e) {
- CLog.e("Failed to reboot, trying last-ditch recovery");
- CLog.e(e);
- managedDevice.recoverDevice();
- }
- }
+ DeviceRecoveryModeUtil.bootOutOfRecovery((IManagedTestDevice) mDevice,
+ mMaxInstallOnlineTimeSec * 1000);
}
double updateTime = sendRecoveryLog(listener);
Map<String, String> metrics = new HashMap<String, String>(1);
diff --git a/src/com/android/tradefed/command/CommandFileWatcher.java b/src/com/android/tradefed/command/CommandFileWatcher.java
index a01589e..3d53d9d 100644
--- a/src/com/android/tradefed/command/CommandFileWatcher.java
+++ b/src/com/android/tradefed/command/CommandFileWatcher.java
@@ -145,6 +145,7 @@
*/
public void cancel() {
mCancelled = true;
+ interrupt();
}
/**
diff --git a/src/com/android/tradefed/command/CommandScheduler.java b/src/com/android/tradefed/command/CommandScheduler.java
index d108490..81ceb93 100644
--- a/src/com/android/tradefed/command/CommandScheduler.java
+++ b/src/com/android/tradefed/command/CommandScheduler.java
@@ -67,6 +67,8 @@
import com.android.tradefed.util.TableFormatter;
import com.android.tradefed.util.TimeUtil;
import com.android.tradefed.util.hostmetric.IHostMonitor;
+import com.android.tradefed.util.hostmetric.IHostMonitor.HostDataPoint;
+import com.android.tradefed.util.hostmetric.IHostMonitor.HostMetricType;
import com.android.tradefed.util.keystore.IKeyStoreClient;
import com.android.tradefed.util.keystore.IKeyStoreFactory;
import com.android.tradefed.util.keystore.KeyStoreException;
@@ -504,6 +506,7 @@
* for the next iteration of testing.
*/
private static final long CHECK_WAIT_DEVICE_AVAIL_MS = 30 * 1000;
+ private static final int EXPECTED_THREAD_COUNT = 1;
private final IScheduledInvocationListener[] mListeners;
private final IInvocationContext mInvocationContext;
@@ -599,6 +602,8 @@
device.setRecoveryMode(RecoveryMode.AVAILABLE);
}
+ checkStrayThreads();
+
for (final IScheduledInvocationListener listener : mListeners) {
try {
listener.invocationComplete(mInvocationContext, deviceStates);
@@ -613,6 +618,30 @@
}
}
+ /** Check the number of thread in the ThreadGroup, only one should exists (itself). */
+ private void checkStrayThreads() {
+ int numThread = this.getThreadGroup().activeCount();
+ if (numThread == EXPECTED_THREAD_COUNT) {
+ // No stray thread detected at the end of invocation
+ return;
+ }
+ List<String> cmd = Arrays.asList(mCmd.getCommandTracker().getArgs());
+ CLog.e(
+ "Stray thread detected for command %d, %s. %d threads instead of %d",
+ mCmd.getCommandTracker().getId(), cmd, numThread, EXPECTED_THREAD_COUNT);
+ // This is the best we have for debug, it prints to std out.
+ this.getThreadGroup().list();
+ List<IHostMonitor> hostMonitors = GlobalConfiguration.getHostMonitorInstances();
+ if (hostMonitors != null) {
+ for (IHostMonitor hm : hostMonitors) {
+ HostDataPoint data = new HostDataPoint("numThread", numThread, cmd.toString());
+ hm.addHostEvent(HostMetricType.INVOCATION_STRAY_THREAD, data);
+ }
+ }
+ // printing to stderr will help to catch them.
+ System.err.println(String.format("We have %s threads instead of 1", numThread));
+ }
+
/** Helper to log an invocation ended event. */
private void logInvocationEndedEvent(
int invocId, long elapsedTime, final IInvocationContext context) {
@@ -1410,7 +1439,7 @@
if (!isShuttingDown()) {
CLog.d("initiating shutdown");
removeAllCommands();
- if (mReloadCmdfiles) {
+ if (mCommandFileWatcher != null) {
mCommandFileWatcher.cancel();
}
if (mCommandTimer != null) {
diff --git a/src/com/android/tradefed/config/Configuration.java b/src/com/android/tradefed/config/Configuration.java
index 64599ec..80e2aa3 100644
--- a/src/com/android/tradefed/config/Configuration.java
+++ b/src/com/android/tradefed/config/Configuration.java
@@ -1178,23 +1178,48 @@
for (ISystemStatusChecker checker : getSystemStatusCheckers()) {
dumpClassToXml(serializer, SYSTEM_STATUS_CHECKER_TYPE_NAME, checker);
}
- // TODO: fix device specific config object for multi device
- dumpClassToXml(serializer, BUILD_PROVIDER_TYPE_NAME, getBuildProvider());
- for (ITargetPreparer preparer : getTargetPreparers()) {
- dumpClassToXml(serializer, TARGET_PREPARER_TYPE_NAME, preparer);
+
+ if (getDeviceConfig().size() > 1) {
+ // Handle multi device.
+ for (IDeviceConfiguration deviceConfig : getDeviceConfig()) {
+ serializer.startTag(null, Configuration.DEVICE_NAME);
+ serializer.attribute(null, "name", deviceConfig.getDeviceName());
+ dumpClassToXml(
+ serializer, BUILD_PROVIDER_TYPE_NAME, deviceConfig.getBuildProvider());
+ for (ITargetPreparer preparer : deviceConfig.getTargetPreparers()) {
+ dumpClassToXml(serializer, TARGET_PREPARER_TYPE_NAME, preparer);
+ }
+ dumpClassToXml(
+ serializer, DEVICE_RECOVERY_TYPE_NAME, deviceConfig.getDeviceRecovery());
+ dumpClassToXml(
+ serializer,
+ DEVICE_REQUIREMENTS_TYPE_NAME,
+ deviceConfig.getDeviceRequirements());
+ dumpClassToXml(
+ serializer, DEVICE_OPTIONS_TYPE_NAME, deviceConfig.getDeviceOptions());
+ serializer.endTag(null, Configuration.DEVICE_NAME);
+ }
+ } else {
+ // Put single device tags
+ dumpClassToXml(serializer, BUILD_PROVIDER_TYPE_NAME, getBuildProvider());
+ for (ITargetPreparer preparer : getTargetPreparers()) {
+ dumpClassToXml(serializer, TARGET_PREPARER_TYPE_NAME, preparer);
+ }
+ dumpClassToXml(serializer, DEVICE_RECOVERY_TYPE_NAME, getDeviceRecovery());
+ dumpClassToXml(serializer, DEVICE_REQUIREMENTS_TYPE_NAME, getDeviceRequirements());
+ dumpClassToXml(serializer, DEVICE_OPTIONS_TYPE_NAME, getDeviceOptions());
}
for (IRemoteTest test : getTests()) {
dumpClassToXml(serializer, TEST_TYPE_NAME, test);
}
- dumpClassToXml(serializer, DEVICE_RECOVERY_TYPE_NAME, getDeviceRecovery());
+
dumpClassToXml(serializer, LOGGER_TYPE_NAME, getLogOutput());
dumpClassToXml(serializer, LOG_SAVER_TYPE_NAME, getLogSaver());
for (ITestInvocationListener listener : getTestInvocationListeners()) {
dumpClassToXml(serializer, RESULT_REPORTER_TYPE_NAME, listener);
}
dumpClassToXml(serializer, CMD_OPTIONS_TYPE_NAME, getCommandOptions());
- dumpClassToXml(serializer, DEVICE_REQUIREMENTS_TYPE_NAME, getDeviceRequirements());
- dumpClassToXml(serializer, DEVICE_OPTIONS_TYPE_NAME, getDeviceOptions());
+
dumpClassToXml(serializer, TEST_PROFILER_TYPE_NAME, getProfiler());
serializer.endTag(null, CONFIGURATION_NAME);
diff --git a/src/com/android/tradefed/result/CollectingTestListener.java b/src/com/android/tradefed/result/CollectingTestListener.java
index e61f06a..1ca1cc1 100644
--- a/src/com/android/tradefed/result/CollectingTestListener.java
+++ b/src/com/android/tradefed/result/CollectingTestListener.java
@@ -299,10 +299,10 @@
}
/**
- * Return total number of tests in a failure state (failed, assumption failure)
+ * Return total number of tests in a failure state (only failed, assumption failures do not
+ * count toward it).
*/
public int getNumAllFailedTests() {
- return getNumTestsInState(TestStatus.FAILURE) +
- getNumTestsInState(TestStatus.ASSUMPTION_FAILURE);
+ return getNumTestsInState(TestStatus.FAILURE);
}
}
diff --git a/src/com/android/tradefed/suite/checker/SystemServerFileDescriptorChecker.java b/src/com/android/tradefed/suite/checker/SystemServerFileDescriptorChecker.java
new file mode 100644
index 0000000..9ed6d7b
--- /dev/null
+++ b/src/com/android/tradefed/suite/checker/SystemServerFileDescriptorChecker.java
@@ -0,0 +1,64 @@
+/*
+ * 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.suite.checker;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/** Checks if system server appears to be running out of FDs. */
+public class SystemServerFileDescriptorChecker implements ISystemStatusChecker {
+ /** Process will fail to allocate beyond 1024, so heuristic considers 900 a bad state */
+ private static final int MAX_EXPECTED_FDS = 900;
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean postExecutionCheck(ITestDevice device) throws DeviceNotAvailableException {
+ Integer pid = getIntegerFromCommand(device, "pidof system_server");
+ if (pid == null) {
+ CLog.d("Unable to find system_server pid.");
+ return true;
+ }
+
+ Integer fds = getIntegerFromCommand(device, "ls /proc/" + pid + "/fd | wc -l");
+ if (fds == null) {
+ CLog.d("Unable to query system_server fd count.");
+ return true;
+ }
+
+ if (fds > MAX_EXPECTED_FDS) {
+ CLog.w("FDs currently allocated in system server " + fds);
+ return false;
+ }
+ return true;
+ }
+
+ private static Integer getIntegerFromCommand(ITestDevice device, String command)
+ throws DeviceNotAvailableException {
+ String output = device.executeShellCommand(command);
+ if (output == null) {
+ CLog.w("no shell output for command: " + command);
+ return null;
+ }
+ output = output.trim();
+ try {
+ return Integer.parseInt(output);
+ } catch (NumberFormatException e) {
+ CLog.w("unable to parse result of '" + command + "' : " + output);
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/tradefed/testtype/PythonUnitTestResultParser.java b/src/com/android/tradefed/testtype/PythonUnitTestResultParser.java
index 3ad8789..3b5b9dd 100644
--- a/src/com/android/tradefed/testtype/PythonUnitTestResultParser.java
+++ b/src/com/android/tradefed/testtype/PythonUnitTestResultParser.java
@@ -277,11 +277,10 @@
// mark each test passed or failed
for (Entry<TestIdentifier, String> test : mTestResultCache.entrySet()) {
listener.testStarted(test.getKey());
- if (test.getValue() == null) {
- listener.testEnded(test.getKey(), Collections.<String, String>emptyMap());
- } else {
+ if (test.getValue() != null) {
listener.testFailed(test.getKey(), test.getValue());
}
+ listener.testEnded(test.getKey(), Collections.<String, String>emptyMap());
}
// mark the whole run as passed or failed
diff --git a/src/com/android/tradefed/testtype/VersionedTfLauncher.java b/src/com/android/tradefed/testtype/VersionedTfLauncher.java
index f7f9641..1b5b5fb 100644
--- a/src/com/android/tradefed/testtype/VersionedTfLauncher.java
+++ b/src/com/android/tradefed/testtype/VersionedTfLauncher.java
@@ -21,9 +21,10 @@
import com.android.tradefed.config.OptionCopier;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.NullDevice;
-import com.android.tradefed.util.QuotationAwareTokenizer;
+import com.android.tradefed.util.StringEscapeUtils;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
/**
@@ -35,9 +36,9 @@
public class VersionedTfLauncher extends SubprocessTfLauncher
implements IMultiDeviceTest, IStrictShardableTest {
- @Option(name = "tf-command-line", description = "The string of original command line "
+ @Option(name = "tf-command-line", description = "The list string of original command line "
+ "arguments.")
- private String mTfCommandline = null;
+ private List<String> mTfCommandline = new ArrayList<>();
private Map<ITestDevice, IBuildInfo> mDeviceInfos = null;
@@ -67,8 +68,8 @@
protected void preRun() {
super.preRun();
- if (mTfCommandline != null) {
- mCmdArgs.addAll(Arrays.asList(QuotationAwareTokenizer.tokenizeLine(mTfCommandline)));
+ if (!mTfCommandline.isEmpty()) {
+ mCmdArgs.addAll(StringEscapeUtils.paramsToArgs(mTfCommandline));
}
// TODO: support multiple device test.
diff --git a/src/com/android/tradefed/util/DeviceRecoveryModeUtil.java b/src/com/android/tradefed/util/DeviceRecoveryModeUtil.java
new file mode 100644
index 0000000..0441fd3
--- /dev/null
+++ b/src/com/android/tradefed/util/DeviceRecoveryModeUtil.java
@@ -0,0 +1,74 @@
+/*
+ * 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.util;
+
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.TimeoutException;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.IManagedTestDevice;
+import com.android.tradefed.device.TestDeviceState;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.io.IOException;
+
+public class DeviceRecoveryModeUtil {
+
+ /**
+ * Boots a device in recovery mode back into the main OS.
+ *
+ * @param device the {@link IManagedTestDevice} to boot
+ * @param timeoutMs how long to wait for the device to be in the recovery state
+ * @throws DeviceNotAvailableException
+ */
+ public static void bootOutOfRecovery(IManagedTestDevice device, long timeoutMs)
+ throws DeviceNotAvailableException {
+ bootOutOfRecovery(device, timeoutMs, 0);
+ }
+
+ /**
+ * Boots a device in recovery mode back into the main OS. Can optionally wait after the
+ * RECOVERY state begins, as it may not be immediately possible to send adb commands when
+ * recovery starts.
+ *
+ * @param managedDevice the {@link IManagedTestDevice} to boot
+ * @param timeoutMs how long to wait for the device to be in the recovery state
+ * @param bufferMs the number of ms to wait after the device enters the recovery state
+ * @throws DeviceNotAvailableException
+ */
+ public static void bootOutOfRecovery(IManagedTestDevice managedDevice,
+ long timeoutMs, long bufferMs)
+ throws DeviceNotAvailableException {
+ if (managedDevice.getDeviceState().equals(TestDeviceState.RECOVERY)) {
+ CLog.i("Rebooting to exit recovery");
+ if (bufferMs > 0) {
+ CLog.i("Pausing for %d ms while recovery loads", bufferMs);
+ RunUtil.getDefault().sleep(bufferMs);
+ }
+ try {
+ // we don't want to enable root until the reboot is fully finished and
+ // the device is available, or it may get stuck in recovery and time out
+ managedDevice.getIDevice().reboot(null);
+ managedDevice.waitForDeviceAvailable(timeoutMs);
+ managedDevice.postBootSetup();
+ } catch (TimeoutException | AdbCommandRejectedException | IOException e) {
+ CLog.e("Failed to reboot, trying last-ditch recovery");
+ CLog.e(e);
+ managedDevice.recoverDevice();
+ }
+ }
+ }
+}
diff --git a/src/com/android/tradefed/util/StringEscapeUtils.java b/src/com/android/tradefed/util/StringEscapeUtils.java
index bbb0aab..1b7ea44 100644
--- a/src/com/android/tradefed/util/StringEscapeUtils.java
+++ b/src/com/android/tradefed/util/StringEscapeUtils.java
@@ -16,6 +16,9 @@
package com.android.tradefed.util;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
/**
* Utility class for escaping strings for specific formats.
@@ -51,4 +54,31 @@
}
return out.toString();
}
+
+ /**
+ * Converts the provided parameters via options to command line args to sub process
+ *
+ * <p>This method will do a simplistic generic unescape for each parameter in the list. It
+ * replaces \[char] with [char]. For example, \" is converted to ". This allows string with
+ * escaped double quotes to stay as a string after being parsed by QuotationAwareTokenizer.
+ * Without this QuotationAwareTokenizer will break the string into sections if it has space in
+ * it.
+ *
+ * @param params parameters received via options
+ * @return list of string representing command line args
+ */
+ public static List<String> paramsToArgs(List<String> params) {
+ List<String> result = new ArrayList<>();
+ for (String param : params) {
+ // doing a simplistic generic unescape here: \<char> is replaced with <char>; note that
+ // this may lead to incorrect results such as \n -> n, or \t -> t, but it's unclear why
+ // a command line param would have \t anyways
+ param = param.replaceAll("\\\\(.)", "$1");
+ String[] args = QuotationAwareTokenizer.tokenizeLine(param);
+ if (args.length != 0) {
+ result.addAll(Arrays.asList(args));
+ }
+ }
+ return result;
+ }
}
diff --git a/src/com/android/tradefed/util/hostmetric/AbstractHostMonitor.java b/src/com/android/tradefed/util/hostmetric/AbstractHostMonitor.java
index de72c0d..83cb8e0 100644
--- a/src/com/android/tradefed/util/hostmetric/AbstractHostMonitor.java
+++ b/src/com/android/tradefed/util/hostmetric/AbstractHostMonitor.java
@@ -45,7 +45,7 @@
@Option(name = "event-tag", description = "Event Tag that will be accepted by the Monitor.")
private HostMetricType mTag = HostMetricType.NONE;
- protected Queue<DataPoint> mHostEvents = new LinkedBlockingQueue<DataPoint>();
+ protected Queue<HostDataPoint> mHostEvents = new LinkedBlockingQueue<HostDataPoint>();
protected Map<String, String> mHostData = new HashMap<>();
@@ -83,7 +83,7 @@
/** {@inheritDoc} */
@Override
- public synchronized void addHostEvent(HostMetricType tag, DataPoint event) {
+ public synchronized void addHostEvent(HostMetricType tag, HostDataPoint event) {
if (getTag().equals(tag)) {
mHostEvents.add(event);
}
diff --git a/src/com/android/tradefed/util/hostmetric/IHostMonitor.java b/src/com/android/tradefed/util/hostmetric/IHostMonitor.java
index 504b420..20a4d3b 100644
--- a/src/com/android/tradefed/util/hostmetric/IHostMonitor.java
+++ b/src/com/android/tradefed/util/hostmetric/IHostMonitor.java
@@ -31,27 +31,25 @@
public void start();
/** A method that will be called to add a special event to be sent. */
- public void addHostEvent(HostMetricType tag, DataPoint event);
+ public void addHostEvent(HostMetricType tag, HostDataPoint event);
/**
* A method that will be called to stop the Host Monitor.
*/
public void terminate();
- /**
- * Generic class for data to be reported.
- */
- static class DataPoint {
+ /** Generic class for data to be reported. */
+ public static class HostDataPoint {
public String name;
public int value;
public String additionalInfo = null;
- public DataPoint(String name, int value) {
+ public HostDataPoint(String name, int value) {
this.name = name;
this.value = value;
}
- public DataPoint(String name, int value, String additionalInfo) {
+ public HostDataPoint(String name, int value, String additionalInfo) {
this.name = name;
this.value = value;
this.additionalInfo = additionalInfo;
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index c98e1fc..2efc216 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -90,6 +90,7 @@
import com.android.tradefed.result.TestSummaryTest;
import com.android.tradefed.result.XmlResultReporterTest;
import com.android.tradefed.suite.checker.KeyguardStatusCheckerTest;
+import com.android.tradefed.suite.checker.SystemServerFileDescriptorCheckerTest;
import com.android.tradefed.suite.checker.SystemServerStatusCheckerTest;
import com.android.tradefed.targetprep.AllTestAppsInstallSetupTest;
import com.android.tradefed.targetprep.AppSetupTest;
@@ -326,6 +327,7 @@
// suite/checker
KeyguardStatusCheckerTest.class,
+ SystemServerFileDescriptorCheckerTest.class,
SystemServerStatusCheckerTest.class,
// testtype
diff --git a/tests/src/com/android/tradefed/command/CommandRunnerTest.java b/tests/src/com/android/tradefed/command/CommandRunnerTest.java
index 7960516..f724193 100644
--- a/tests/src/com/android/tradefed/command/CommandRunnerTest.java
+++ b/tests/src/com/android/tradefed/command/CommandRunnerTest.java
@@ -33,7 +33,9 @@
import com.android.tradefed.util.FileUtil;
import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -56,7 +58,23 @@
private Throwable mThrowable = null;
private String mStackTraceOutput = null;
- private ICommandScheduler mOriginalScheduler = null;
+ private static ICommandScheduler sOriginalScheduler = null;
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ // We have some global state that cannot be re-entered so we ensure they do not throw.
+ try {
+ GlobalConfiguration.createGlobalConfiguration(new String[] {});
+ } catch (IllegalStateException e) {
+ // ignore re-init.
+ }
+ sOriginalScheduler = GlobalConfiguration.getInstance().getCommandScheduler();
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ GlobalConfiguration.getInstance().setCommandScheduler(sOriginalScheduler);
+ }
@Before
public void setUp() {
@@ -66,15 +84,6 @@
new CommandRunner() {
@Override
public void initGlobalConfig(String[] args) throws ConfigurationException {
- // We have some global state that cannot be re-entered so we ensure they do
- // not throw.
- try {
- GlobalConfiguration.createGlobalConfiguration(args);
- } catch (IllegalStateException e) {
- // ignore re-init.
- }
- mOriginalScheduler =
- GlobalConfiguration.getInstance().getCommandScheduler();
GlobalConfiguration.getInstance()
.setCommandScheduler(
new CommandScheduler() {
@@ -130,7 +139,9 @@
@After
public void tearDown() {
- GlobalConfiguration.getInstance().setCommandScheduler(mOriginalScheduler);
+ GlobalConfiguration.getInstance()
+ .getCommandScheduler()
+ .setLastInvocationExitCode(ExitCode.NO_ERROR, null);
}
/**
diff --git a/tests/src/com/android/tradefed/config/ConfigurationTest.java b/tests/src/com/android/tradefed/config/ConfigurationTest.java
index 09014c0..d16d5d4 100644
--- a/tests/src/com/android/tradefed/config/ConfigurationTest.java
+++ b/tests/src/com/android/tradefed/config/ConfigurationTest.java
@@ -641,4 +641,27 @@
FileUtil.deleteFile(test);
}
}
+
+ /**
+ * Test that {@link Configuration#dumpXml(PrintWriter)} produce the xml output even for a multi
+ * device situation.
+ */
+ public void testDumpXml_multi_device() throws Exception {
+ List<IDeviceConfiguration> deviceObjectList = new ArrayList<IDeviceConfiguration>();
+ deviceObjectList.add(new DeviceConfigurationHolder("device1"));
+ deviceObjectList.add(new DeviceConfigurationHolder("device2"));
+ mConfig.setConfigurationObjectList(Configuration.DEVICE_NAME, deviceObjectList);
+ File test = FileUtil.createTempFile("dumpxml", "xml");
+ try {
+ PrintWriter out = new PrintWriter(test);
+ mConfig.dumpXml(out);
+ out.flush();
+ String content = FileUtil.readStringFromFile(test);
+ assertTrue(content.length() > 100);
+ assertTrue(content.contains("<device name=\"device1\">"));
+ assertTrue(content.contains("<device name=\"device2\">"));
+ } finally {
+ FileUtil.deleteFile(test);
+ }
+ }
}
diff --git a/tests/src/com/android/tradefed/suite/checker/SystemServerFileDescriptorCheckerTest.java b/tests/src/com/android/tradefed/suite/checker/SystemServerFileDescriptorCheckerTest.java
new file mode 100644
index 0000000..62f0f64
--- /dev/null
+++ b/tests/src/com/android/tradefed/suite/checker/SystemServerFileDescriptorCheckerTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.suite.checker;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.ITestDevice;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link SystemServerFileDescriptorChecker} */
+@RunWith(JUnit4.class)
+public class SystemServerFileDescriptorCheckerTest {
+
+ private SystemServerFileDescriptorChecker mChecker;
+ private ITestDevice mMockDevice;
+
+ @Before
+ public void setUp() {
+ mMockDevice = EasyMock.createMock(ITestDevice.class);
+ mChecker = new SystemServerFileDescriptorChecker();
+ }
+
+ @Test
+ public void testFailToGetPid() throws Exception {
+ EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("pidof system_server")))
+ .andReturn("not found\n");
+ EasyMock.replay(mMockDevice);
+ assertTrue(mChecker.preExecutionCheck(mMockDevice));
+ assertTrue(mChecker.postExecutionCheck(mMockDevice));
+ EasyMock.verify(mMockDevice);
+ }
+
+ @Test
+ public void testFailToGetFdCount() throws Exception {
+ EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("pidof system_server")))
+ .andReturn("1024\n");
+ EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("ls /proc/1024/fd | wc -l")))
+ .andReturn("not found\n");
+ EasyMock.replay(mMockDevice);
+ assertTrue(mChecker.preExecutionCheck(mMockDevice));
+ assertTrue(mChecker.postExecutionCheck(mMockDevice));
+ EasyMock.verify(mMockDevice);
+ }
+
+ @Test
+ public void testAcceptableFdCount() throws Exception {
+ EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("pidof system_server")))
+ .andReturn("914\n");
+ EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("ls /proc/914/fd | wc -l")))
+ .andReturn("382 \n");
+ EasyMock.replay(mMockDevice);
+ assertTrue(mChecker.preExecutionCheck(mMockDevice));
+ assertTrue(mChecker.postExecutionCheck(mMockDevice));
+ EasyMock.verify(mMockDevice);
+ }
+
+ @Test
+ public void testUnacceptableFdCount() throws Exception {
+ EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("pidof system_server")))
+ .andReturn("914\n");
+ EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("ls /proc/914/fd | wc -l")))
+ .andReturn("1002 \n");
+ EasyMock.replay(mMockDevice);
+ assertTrue(mChecker.preExecutionCheck(mMockDevice)); // Noop
+ assertFalse(mChecker.postExecutionCheck(mMockDevice));
+ EasyMock.verify(mMockDevice);
+ }
+}
diff --git a/tests/src/com/android/tradefed/testtype/PythonUnitTestResultParserTest.java b/tests/src/com/android/tradefed/testtype/PythonUnitTestResultParserTest.java
index 79908a6..8afdfd1 100644
--- a/tests/src/com/android/tradefed/testtype/PythonUnitTestResultParserTest.java
+++ b/tests/src/com/android/tradefed/testtype/PythonUnitTestResultParserTest.java
@@ -234,7 +234,7 @@
mMockListener.testFailed(eq(ids[i]), (String)anyObject());
expectLastCall().times(1);
mMockListener.testEnded(ids[i], Collections.<String, String>emptyMap());
- expectLastCall().andThrow(new AssertionFailedError()).anyTimes();
+ expectLastCall().times(1);
}
}
}
diff --git a/tests/src/com/android/tradefed/testtype/VersionedTfLauncherTest.java b/tests/src/com/android/tradefed/testtype/VersionedTfLauncherTest.java
index 32f5e6c..5a647f7 100644
--- a/tests/src/com/android/tradefed/testtype/VersionedTfLauncherTest.java
+++ b/tests/src/com/android/tradefed/testtype/VersionedTfLauncherTest.java
@@ -50,8 +50,14 @@
private static final String CONFIG_NAME = "FAKE_CONFIG";
private static final String TF_COMMAND_LINE_TEMPLATE = "--template:map";
private static final String TF_COMMAND_LINE_TEST = "test=tf/fake";
+ // Test option value with empty spaces should be parsed correctly.
+ private static final String TF_COMMAND_LINE_OPTION = "--option";
+ private static final String TF_COMMAND_LINE_OPTION_VALUE = "value1 value2";
+ private static final String TF_COMMAND_LINE_OPTION_VALUE_QUOTED =
+ ("\\\"" + TF_COMMAND_LINE_OPTION_VALUE + "\\\"");
private static final String TF_COMMAND_LINE =
- (TF_COMMAND_LINE_TEMPLATE + " " + TF_COMMAND_LINE_TEST);
+ (TF_COMMAND_LINE_TEMPLATE + " " + TF_COMMAND_LINE_TEST + " " + TF_COMMAND_LINE_OPTION +
+ " " + TF_COMMAND_LINE_OPTION_VALUE_QUOTED);
private VersionedTfLauncher mVersionedTfLauncher;
private ITestInvocationListener mMockListener;
@@ -100,6 +106,8 @@
EasyMock.eq(CONFIG_NAME),
EasyMock.eq(TF_COMMAND_LINE_TEMPLATE),
EasyMock.eq(TF_COMMAND_LINE_TEST),
+ EasyMock.eq(TF_COMMAND_LINE_OPTION),
+ EasyMock.eq(TF_COMMAND_LINE_OPTION_VALUE),
EasyMock.eq("--serial"),
EasyMock.eq(FAKE_SERIAL),
EasyMock.eq("--subprocess-report-file"),
@@ -149,6 +157,8 @@
EasyMock.eq(CONFIG_NAME),
EasyMock.eq(TF_COMMAND_LINE_TEMPLATE),
EasyMock.eq(TF_COMMAND_LINE_TEST),
+ EasyMock.eq(TF_COMMAND_LINE_OPTION),
+ EasyMock.eq(TF_COMMAND_LINE_OPTION_VALUE),
EasyMock.eq("--null-device"),
EasyMock.eq("--subprocess-report-file"),
(String) EasyMock.anyObject()))
@@ -206,6 +216,8 @@
EasyMock.eq(CONFIG_NAME),
EasyMock.eq(TF_COMMAND_LINE_TEMPLATE),
EasyMock.eq(TF_COMMAND_LINE_TEST),
+ EasyMock.eq(TF_COMMAND_LINE_OPTION),
+ EasyMock.eq(TF_COMMAND_LINE_OPTION_VALUE),
EasyMock.eq("--serial"),
EasyMock.eq(FAKE_SERIAL),
EasyMock.eq("--shard-count"),
diff --git a/tests/src/com/android/tradefed/util/DeviceRecoveryModeUtilTest.java b/tests/src/com/android/tradefed/util/DeviceRecoveryModeUtilTest.java
new file mode 100644
index 0000000..7e923d9
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/DeviceRecoveryModeUtilTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.util;
+
+import com.android.ddmlib.IDevice;
+import com.android.tradefed.device.IManagedTestDevice;
+import com.android.tradefed.device.TestDeviceState;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DeviceRecoveryModeUtilTest {
+
+ private IManagedTestDevice mMockManagedDevice;
+ private IDevice mMockDevice;
+
+ @Before
+ public void setUp() throws Throwable {
+ mMockManagedDevice = EasyMock.createMock(IManagedTestDevice.class);
+ mMockDevice = EasyMock.createMock(IDevice.class);
+ EasyMock.expect(mMockManagedDevice.getIDevice()).andReturn(mMockDevice).anyTimes();
+ }
+
+ @Test
+ public void testBootOutOfRecovery() throws Throwable {
+ mMockDevice.reboot((String) EasyMock.anyObject());
+ EasyMock.expectLastCall();
+ mMockManagedDevice.waitForDeviceAvailable(EasyMock.anyLong());
+ EasyMock.expectLastCall();
+ mMockManagedDevice.postBootSetup();
+ EasyMock.expectLastCall();
+ EasyMock.expect(mMockManagedDevice.getDeviceState()).andReturn(TestDeviceState.RECOVERY);
+ EasyMock.replay(mMockDevice, mMockManagedDevice);
+ DeviceRecoveryModeUtil.bootOutOfRecovery(mMockManagedDevice, 1000000000);
+ }
+}
diff --git a/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java b/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java
index a1e104f..2001274 100644
--- a/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java
+++ b/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java
@@ -15,17 +15,77 @@
*/
package com.android.tradefed.util;
-import junit.framework.TestCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* Unit tests for {@link StringEscapeUtils}
*/
-public class StringEscapeUtilsTest extends TestCase {
+@RunWith(JUnit4.class)
+public class StringEscapeUtilsTest {
/**
* Simple test that {@link StringEscapeUtils#escapeShell(String)} escapes the dollar sign.
*/
+ @Test
public void testEscapesDollarSigns() {
String escaped_str = StringEscapeUtils.escapeShell("$money$signs");
assertEquals("\\$money\\$signs", escaped_str);
}
+
+ /**
+ * Test {@link StringEscapeUtils#paramsToArgs(List) returns proper result with no quoting
+ * or spaces
+ */
+ @Test
+ public void testParams_noQuotesNoSpaces() {
+ List<String> expected = new ArrayList<>();
+ expected.add("foo");
+ expected.add("bar");
+ assertArrayEquals(expected.toArray(),
+ StringEscapeUtils.paramsToArgs(expected).toArray());
+ }
+
+ /**
+ * Test {@link StringEscapeUtils#paramsToArgs(List) returns proper result with no quoting
+ * but with spaces
+ */
+ @Test
+ public void testParams_noQuotesWithSpaces() {
+ List<String> expected = new ArrayList<>();
+ expected.add("foo");
+ expected.add("bar bar");
+ assertArrayEquals(new String[]{"foo", "bar", "bar"},
+ StringEscapeUtils.paramsToArgs(expected).toArray());
+ }
+
+ /**
+ * Test {@link StringEscapeUtils#paramsToArgs(List) returns proper result with plain quoting
+ */
+ @Test
+ public void testParams_plainQuotes() {
+ List<String> expected = new ArrayList<>();
+ expected.add("foo");
+ expected.add("\"bar bar\"");
+ assertArrayEquals(new String[]{"foo", "bar bar"},
+ StringEscapeUtils.paramsToArgs(expected).toArray());
+ }
+
+ /**
+ * Test {@link StringEscapeUtils#paramsToArgs(List) returns proper result with escaped quoting
+ */
+ @Test
+ public void testParams_escapedQuotes() {
+ List<String> expected = new ArrayList<>();
+ expected.add("foo");
+ expected.add("\\\"bar bar\\\"");
+ assertArrayEquals(new String[]{"foo", "bar bar"},
+ StringEscapeUtils.paramsToArgs(expected).toArray());
+ }
}
diff --git a/tests/src/com/android/tradefed/util/hostmetric/AbstractHostMonitorTest.java b/tests/src/com/android/tradefed/util/hostmetric/AbstractHostMonitorTest.java
index ad3ce56..b7046cb 100644
--- a/tests/src/com/android/tradefed/util/hostmetric/AbstractHostMonitorTest.java
+++ b/tests/src/com/android/tradefed/util/hostmetric/AbstractHostMonitorTest.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.util.hostmetric;
+import com.android.tradefed.util.hostmetric.IHostMonitor.HostDataPoint;
import com.android.tradefed.util.hostmetric.IHostMonitor.HostMetricType;
import junit.framework.TestCase;
@@ -38,12 +39,12 @@
}
/**
- * Test {@link AbstractHostMonitor#addHostEvent(HostMetricType, IHostMonitor.DataPoint)} when
- * the event is properly added.
+ * Test {@link AbstractHostMonitor#addHostEvent(HostMetricType, HostDataPoint)} when the event
+ * is properly added.
*/
public void testaddHostEvent() {
assertTrue(mHostMonitor.getQueueSize() == 0);
- IHostMonitor.DataPoint fakeDataPoint = new IHostMonitor.DataPoint("test", 5);
+ HostDataPoint fakeDataPoint = new HostDataPoint("test", 5);
mHostMonitor.addHostEvent(mHostMonitor.getTag(), fakeDataPoint);
assertTrue(mHostMonitor.getQueueSize() == 1);
mHostMonitor.addHostEvent(mHostMonitor.getTag(), fakeDataPoint);
@@ -51,12 +52,12 @@
}
/**
- * Test {@link AbstractHostMonitor#addHostEvent(HostMetricType, IHostMonitor.DataPoint)} when
- * the event has a different tag than the Monitor, it should not be added.
+ * Test {@link AbstractHostMonitor#addHostEvent(HostMetricType, HostDataPoint)} when the event
+ * has a different tag than the Monitor, it should not be added.
*/
public void testaddHostEvent_differentTag() {
assertTrue(mHostMonitor.getQueueSize() == 0);
- IHostMonitor.DataPoint fakeDataPoint = new IHostMonitor.DataPoint("test", 5);
+ HostDataPoint fakeDataPoint = new HostDataPoint("test", 5);
// expected NONE key for hostmonitor
mHostMonitor.addHostEvent(HostMetricType.INVOCATION_STRAY_THREAD, fakeDataPoint);
assertTrue(mHostMonitor.getQueueSize() == 0);