Check when device is unavailable that it's gone
Ensure if device is really gone or not before marking it
as FREE_UNKNOWN which could results in removing the device
from the device list.
Test: unit tests
Bug: 62088635
Change-Id: If4fad78cb61b7307bbea19df5a774f6ddbd83d79
(cherry picked from commit 50f55c8ebce12c91c3f290d87e626945823148a1)
diff --git a/src/com/android/tradefed/device/DeviceManager.java b/src/com/android/tradefed/device/DeviceManager.java
index 06ae4c8..b1a0667 100644
--- a/src/com/android/tradefed/device/DeviceManager.java
+++ b/src/com/android/tradefed/device/DeviceManager.java
@@ -59,6 +59,7 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
@OptionClass(alias = "dmgr", global_namespace = false)
public class DeviceManager implements IDeviceManager {
@@ -85,6 +86,17 @@
private static final String EMULATOR_SERIAL_PREFIX = "emulator";
private static final String TCP_DEVICE_SERIAL_PREFIX = "tcp-device";
+ /**
+ * Pattern for a device listed by 'adb devices':
+ *
+ * <p>List of devices attached
+ *
+ * <p>serial1 device
+ *
+ * <p>serial2 device
+ */
+ private static final String DEVICE_LIST_PATTERN = "(.*)(\n)(%s)(\\s+)(device)(.*?)";
+
protected DeviceMonitorMultiplexer mDvcMon = new DeviceMonitorMultiplexer();
private boolean mIsInitialized = false;
@@ -528,17 +540,28 @@
/**
* Helper method to convert from a {@link com.android.tradefed.device.FreeDeviceState} to a
* {@link com.android.tradefed.device.DeviceEvent}
+ *
* @param managedDevice
*/
- static DeviceEvent getEventFromFree(IManagedTestDevice managedDevice, FreeDeviceState deviceState) {
+ private DeviceEvent getEventFromFree(
+ IManagedTestDevice managedDevice, FreeDeviceState deviceState) {
switch (deviceState) {
case UNRESPONSIVE:
return DeviceEvent.FREE_UNRESPONSIVE;
case AVAILABLE:
return DeviceEvent.FREE_AVAILABLE;
case UNAVAILABLE:
- if (managedDevice.getDeviceState() == TestDeviceState.NOT_AVAILABLE) {
- return DeviceEvent.FREE_UNKNOWN;
+ // We double check if device is still showing in adb or not to confirm the
+ // connection is gone.
+ if (TestDeviceState.NOT_AVAILABLE.equals(managedDevice.getDeviceState())) {
+ String devices = executeGlobalAdbCommand("devices");
+ Pattern p =
+ Pattern.compile(
+ String.format(
+ DEVICE_LIST_PATTERN, managedDevice.getSerialNumber()));
+ if (devices == null || !p.matcher(devices).find()) {
+ return DeviceEvent.FREE_UNKNOWN;
+ }
}
return DeviceEvent.FREE_UNAVAILABLE;
case IGNORE:
diff --git a/tests/src/com/android/tradefed/device/DeviceManagerTest.java b/tests/src/com/android/tradefed/device/DeviceManagerTest.java
index 16dfbb9..ee0f797 100644
--- a/tests/src/com/android/tradefed/device/DeviceManagerTest.java
+++ b/tests/src/com/android/tradefed/device/DeviceManagerTest.java
@@ -115,7 +115,6 @@
public int waitFor() throws InterruptedException {
return 0;
}
-
}
/**
@@ -890,6 +889,197 @@
}
/**
+ * Test freeing a device that was unable but showing in adb devices. Device will become
+ * Unavailable but still seen by the DeviceManager.
+ */
+ public void testFreeDevice_unavailable() {
+ EasyMock.expect(mMockIDevice.isEmulator()).andStubReturn(Boolean.FALSE);
+ EasyMock.expect(mMockIDevice.getState()).andReturn(DeviceState.ONLINE);
+ EasyMock.expect(mMockStateMonitor.waitForDeviceShell(EasyMock.anyLong()))
+ .andReturn(Boolean.TRUE);
+ mMockStateMonitor.setState(TestDeviceState.NOT_AVAILABLE);
+
+ CommandResult stubAdbDevices = new CommandResult(CommandStatus.SUCCESS);
+ stubAdbDevices.setStdout("List of devices attached\nserial\tdevice\n");
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(), EasyMock.eq("adb"), EasyMock.eq("devices")))
+ .andReturn(stubAdbDevices);
+
+ replayMocks();
+ IManagedTestDevice testDevice = new TestDevice(mMockIDevice, mMockStateMonitor, null);
+ DeviceManager manager = createDeviceManagerNoInit();
+ manager.init(
+ null,
+ null,
+ new ManagedTestDeviceFactory(false, null, null) {
+ @Override
+ public IManagedTestDevice createDevice(IDevice idevice) {
+ mMockTestDevice.setIDevice(idevice);
+ return testDevice;
+ }
+
+ @Override
+ protected CollectingOutputReceiver createOutputReceiver() {
+ return new CollectingOutputReceiver() {
+ @Override
+ public String getOutput() {
+ return "/system/bin/pm";
+ }
+ };
+ }
+
+ @Override
+ public void setFastbootEnabled(boolean enable) {
+ // ignore
+ }
+ });
+
+ mDeviceListener.deviceConnected(mMockIDevice);
+
+ IManagedTestDevice device = (IManagedTestDevice) manager.allocateDevice();
+ assertNotNull(device);
+ // device becomes unavailable
+ device.setDeviceState(TestDeviceState.NOT_AVAILABLE);
+ // a freed 'unavailable' device becomes UNAVAILABLE state
+ manager.freeDevice(device, FreeDeviceState.UNAVAILABLE);
+ // ensure device cannot be allocated again
+ ITestDevice device2 = manager.allocateDevice();
+ assertNull(device2);
+ verifyMocks();
+ // We still have the device in the list
+ assertEquals(1, manager.getDeviceList().size());
+ }
+
+ /**
+ * Test that when freeing an Unavailable device that is not in 'adb devices' we correctly remove
+ * it from our tracking list.
+ */
+ public void testFreeDevice_unknown() {
+ EasyMock.expect(mMockIDevice.isEmulator()).andStubReturn(Boolean.FALSE);
+ EasyMock.expect(mMockIDevice.getState()).andReturn(DeviceState.ONLINE);
+ EasyMock.expect(mMockStateMonitor.waitForDeviceShell(EasyMock.anyLong()))
+ .andReturn(Boolean.TRUE);
+ mMockStateMonitor.setState(TestDeviceState.NOT_AVAILABLE);
+
+ CommandResult stubAdbDevices = new CommandResult(CommandStatus.SUCCESS);
+ // device serial is not in the list
+ stubAdbDevices.setStdout("List of devices attached\n");
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(), EasyMock.eq("adb"), EasyMock.eq("devices")))
+ .andReturn(stubAdbDevices);
+
+ replayMocks();
+ IManagedTestDevice testDevice = new TestDevice(mMockIDevice, mMockStateMonitor, null);
+ DeviceManager manager = createDeviceManagerNoInit();
+ manager.init(
+ null,
+ null,
+ new ManagedTestDeviceFactory(false, null, null) {
+ @Override
+ public IManagedTestDevice createDevice(IDevice idevice) {
+ mMockTestDevice.setIDevice(idevice);
+ return testDevice;
+ }
+
+ @Override
+ protected CollectingOutputReceiver createOutputReceiver() {
+ return new CollectingOutputReceiver() {
+ @Override
+ public String getOutput() {
+ return "/system/bin/pm";
+ }
+ };
+ }
+
+ @Override
+ public void setFastbootEnabled(boolean enable) {
+ // ignore
+ }
+ });
+
+ mDeviceListener.deviceConnected(mMockIDevice);
+
+ IManagedTestDevice device = (IManagedTestDevice) manager.allocateDevice();
+ assertNotNull(device);
+ // device becomes unavailable
+ device.setDeviceState(TestDeviceState.NOT_AVAILABLE);
+ // a freed 'unavailable' device becomes UNAVAILABLE state
+ manager.freeDevice(device, FreeDeviceState.UNAVAILABLE);
+ // ensure device cannot be allocated again
+ ITestDevice device2 = manager.allocateDevice();
+ assertNull(device2);
+ verifyMocks();
+ // We have 0 device in the list since it was removed
+ assertEquals(0, manager.getDeviceList().size());
+ }
+
+ /**
+ * Test that when freeing an Unavailable device that is not in 'adb devices' we correctly remove
+ * it from our tracking list even if its serial is a substring of another serial.
+ */
+ public void testFreeDevice_unknown_subName() {
+ EasyMock.expect(mMockIDevice.isEmulator()).andStubReturn(Boolean.FALSE);
+ EasyMock.expect(mMockIDevice.getState()).andReturn(DeviceState.ONLINE);
+ EasyMock.expect(mMockStateMonitor.waitForDeviceShell(EasyMock.anyLong()))
+ .andReturn(Boolean.TRUE);
+ mMockStateMonitor.setState(TestDeviceState.NOT_AVAILABLE);
+
+ CommandResult stubAdbDevices = new CommandResult(CommandStatus.SUCCESS);
+ // device serial is not in the list
+ stubAdbDevices.setStdout("List of devices attached\n2serial\tdevice\n");
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(), EasyMock.eq("adb"), EasyMock.eq("devices")))
+ .andReturn(stubAdbDevices);
+
+ replayMocks();
+ IManagedTestDevice testDevice = new TestDevice(mMockIDevice, mMockStateMonitor, null);
+ DeviceManager manager = createDeviceManagerNoInit();
+ manager.init(
+ null,
+ null,
+ new ManagedTestDeviceFactory(false, null, null) {
+ @Override
+ public IManagedTestDevice createDevice(IDevice idevice) {
+ mMockTestDevice.setIDevice(idevice);
+ return testDevice;
+ }
+
+ @Override
+ protected CollectingOutputReceiver createOutputReceiver() {
+ return new CollectingOutputReceiver() {
+ @Override
+ public String getOutput() {
+ return "/system/bin/pm";
+ }
+ };
+ }
+
+ @Override
+ public void setFastbootEnabled(boolean enable) {
+ // ignore
+ }
+ });
+
+ mDeviceListener.deviceConnected(mMockIDevice);
+
+ IManagedTestDevice device = (IManagedTestDevice) manager.allocateDevice();
+ assertNotNull(device);
+ // device becomes unavailable
+ device.setDeviceState(TestDeviceState.NOT_AVAILABLE);
+ // a freed 'unavailable' device becomes UNAVAILABLE state
+ manager.freeDevice(device, FreeDeviceState.UNAVAILABLE);
+ // ensure device cannot be allocated again
+ ITestDevice device2 = manager.allocateDevice();
+ assertNull(device2);
+ verifyMocks();
+ // We have 0 device in the list since it was removed
+ assertEquals(0, manager.getDeviceList().size());
+ }
+
+ /**
* Helper to set the expectation when a {@link DeviceDescriptor} is expected.
*/
private void setDeviceDescriptorExpectation() {