diff --git a/.gitignore b/.gitignore index f9917b3..f972353 100644 --- a/.gitignore +++ b/.gitignore @@ -4,15 +4,13 @@ ################ # IntelliJ # ################ -/.idea/workspace.xml -/.idea/tasks.xml +/.idea/* +jadb.iml ############# # Maven # ############# -.idea/workspace.xml -.idea/libraries out/ target/ diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index f329bae..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index b6da4f3..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/jadb.iml b/jadb.iml deleted file mode 100644 index c419de5..0000000 --- a/jadb.iml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1490413..30d7a96 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ UTF-8 1.7 1.7 + 2.7.17 @@ -39,6 +40,13 @@ 4.10 test + + + + org.mockito + mockito-core + ${mockito-core.version} + @@ -114,7 +122,7 @@ **/*.java - + se.vidstige.jadb.test.integration.* **/data/* @@ -131,8 +139,8 @@ **/*.java - - se.vidstige.jadb.test.unit.* + + se.vidstige.jadb.test.integration.* **/data/* **/fakes/* diff --git a/src/se/vidstige/jadb/ConnectionToRemoteDeviceException.java b/src/se/vidstige/jadb/ConnectionToRemoteDeviceException.java new file mode 100644 index 0000000..f4719bb --- /dev/null +++ b/src/se/vidstige/jadb/ConnectionToRemoteDeviceException.java @@ -0,0 +1,7 @@ +package se.vidstige.jadb; + +public class ConnectionToRemoteDeviceException extends Exception { + public ConnectionToRemoteDeviceException(String message) { + super(message); + } +} diff --git a/src/se/vidstige/jadb/HostConnectToRemoteTcpDevice.java b/src/se/vidstige/jadb/HostConnectToRemoteTcpDevice.java new file mode 100644 index 0000000..2b1f300 --- /dev/null +++ b/src/se/vidstige/jadb/HostConnectToRemoteTcpDevice.java @@ -0,0 +1,75 @@ +package se.vidstige.jadb; + +import java.io.IOException; +import java.net.InetSocketAddress; + +class HostConnectToRemoteTcpDevice { + private final Transport transport; + private final ResponseValidator responseValidator; + + HostConnectToRemoteTcpDevice(Transport transport) { + this.transport = transport; + this.responseValidator = new ResponseValidatorImp(); + } + + //Visible for testing + HostConnectToRemoteTcpDevice(Transport transport, ResponseValidator responseValidator) { + this.transport = transport; + this.responseValidator = responseValidator; + } + + InetSocketAddress connect(InetSocketAddress inetSocketAddress) + throws IOException, JadbException, ConnectionToRemoteDeviceException { + transport.send(String.format("host:connect:%s:%d", inetSocketAddress.getHostString(), inetSocketAddress.getPort())); + verifyTransportLevel(); + verifyProtocolLevel(); + + return inetSocketAddress; + } + + private void verifyTransportLevel() throws IOException, JadbException { + transport.verifyResponse(); + } + + private void verifyProtocolLevel() throws IOException, ConnectionToRemoteDeviceException { + String status = transport.readString(); + responseValidator.validate(status); + } + + //@VisibleForTesting + interface ResponseValidator { + void validate(String response) throws ConnectionToRemoteDeviceException; + } + + final static class ResponseValidatorImp implements ResponseValidator { + private final static String SUCCESSFULLY_CONNECTED = "connected to"; + private final static String ALREADY_CONNECTED = "already connected to"; + + + ResponseValidatorImp() { + } + + public void validate(String response) throws ConnectionToRemoteDeviceException { + if (!checkIfConnectedSuccessfully(response) && !checkIfAlreadyConnected(response)) { + throw new ConnectionToRemoteDeviceException(extractError(response)); + } + } + + private boolean checkIfConnectedSuccessfully(String response) { + return response.startsWith(SUCCESSFULLY_CONNECTED); + } + + private boolean checkIfAlreadyConnected(String response) { + return response.startsWith(ALREADY_CONNECTED); + } + + private String extractError(String response) { + int lastColon = response.lastIndexOf(":"); + if (lastColon != -1) { + return response.substring(lastColon, response.length()); + } else { + return response; + } + } + } +} diff --git a/src/se/vidstige/jadb/HostDisconnectFromRemoteTcpDevice.java b/src/se/vidstige/jadb/HostDisconnectFromRemoteTcpDevice.java new file mode 100644 index 0000000..1c9aead --- /dev/null +++ b/src/se/vidstige/jadb/HostDisconnectFromRemoteTcpDevice.java @@ -0,0 +1,75 @@ +package se.vidstige.jadb; + +import java.io.IOException; +import java.net.InetSocketAddress; + +public class HostDisconnectFromRemoteTcpDevice { + private final Transport transport; + private final ResponseValidator responseValidator; + + HostDisconnectFromRemoteTcpDevice(Transport transport) { + this.transport = transport; + this.responseValidator = new ResponseValidatorImp(); + } + + //Visible for testing + HostDisconnectFromRemoteTcpDevice(Transport transport, ResponseValidator responseValidator) { + this.transport = transport; + this.responseValidator = responseValidator; + } + + InetSocketAddress disconnect(InetSocketAddress inetSocketAddress) + throws IOException, JadbException, ConnectionToRemoteDeviceException { + transport.send(String.format("host:disconnect:%s:%d", inetSocketAddress.getHostString(), inetSocketAddress.getPort())); + verifyTransportLevel(); + verifyProtocolLevel(); + + return inetSocketAddress; + } + + private void verifyTransportLevel() throws IOException, JadbException { + transport.verifyResponse(); + } + + private void verifyProtocolLevel() throws IOException, ConnectionToRemoteDeviceException { + String status = transport.readString(); + responseValidator.validate(status); + } + + //@VisibleForTesting + interface ResponseValidator { + void validate(String response) throws ConnectionToRemoteDeviceException; + } + + final static class ResponseValidatorImp implements ResponseValidator { + private final static String SUCCESSFULLY_DISCONNECTED = "disconnected"; + private final static String ALREADY_DISCONNECTED = "error: no such device"; + + + ResponseValidatorImp() { + } + + public void validate(String response) throws ConnectionToRemoteDeviceException { + if (!checkIfConnectedSuccessfully(response) && !checkIfAlreadyConnected(response)) { + throw new ConnectionToRemoteDeviceException(extractError(response)); + } + } + + private boolean checkIfConnectedSuccessfully(String response) { + return response.startsWith(SUCCESSFULLY_DISCONNECTED); + } + + private boolean checkIfAlreadyConnected(String response) { + return response.startsWith(ALREADY_DISCONNECTED); + } + + private String extractError(String response) { + int lastColon = response.lastIndexOf(":"); + if (lastColon != -1) { + return response.substring(lastColon, response.length()); + } else { + return response; + } + } + } +} diff --git a/src/se/vidstige/jadb/JadbConnection.java b/src/se/vidstige/jadb/JadbConnection.java index 7a76ec1..2eee46d 100644 --- a/src/se/vidstige/jadb/JadbConnection.java +++ b/src/se/vidstige/jadb/JadbConnection.java @@ -1,6 +1,8 @@ package se.vidstige.jadb; + import java.io.IOException; +import java.net.InetSocketAddress; import java.net.Socket; import java.util.ArrayList; import java.util.List; @@ -34,6 +36,26 @@ public class JadbConnection implements ITransportFactory { return version; } + public InetSocketAddress connectToTcpDevice(InetSocketAddress inetSocketAddress) + throws IOException, JadbException, ConnectionToRemoteDeviceException { + Transport transport = createTransport(); + try { + return new HostConnectToRemoteTcpDevice(transport).connect(inetSocketAddress); + } finally { + transport.close(); + } + } + + public InetSocketAddress disconnectFromTcpDevice(InetSocketAddress tcpAddressEntity) + throws IOException, JadbException, ConnectionToRemoteDeviceException { + Transport transport = createTransport(); + try { + return new HostDisconnectFromRemoteTcpDevice(transport).disconnect(tcpAddressEntity); + } finally { + transport.close(); + } + } + public List getDevices() throws IOException, JadbException { Transport devices = createTransport(); devices.send("host:devices"); diff --git a/test/se/vidstige/jadb/HostConnectToRemoteTcpDeviceTest.java b/test/se/vidstige/jadb/HostConnectToRemoteTcpDeviceTest.java new file mode 100644 index 0000000..b3d5113 --- /dev/null +++ b/test/se/vidstige/jadb/HostConnectToRemoteTcpDeviceTest.java @@ -0,0 +1,91 @@ +package se.vidstige.jadb; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentCaptor.forClass; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class HostConnectToRemoteTcpDeviceTest { + + @Test + public void testNormalConnection() throws ConnectionToRemoteDeviceException, IOException, JadbException { + //Prepare + Transport transport = mock(Transport.class); + when(transport.readString()).thenReturn("connected to somehost:1"); + + InetSocketAddress inetSocketAddress = new InetSocketAddress("somehost", 1); + + //Do + HostConnectToRemoteTcpDevice hostConnectToRemoteTcpDevice = new HostConnectToRemoteTcpDevice(transport); + InetSocketAddress resultTcpAddressEntity = hostConnectToRemoteTcpDevice.connect(inetSocketAddress); + + //Validate + assertEquals(resultTcpAddressEntity, inetSocketAddress); + + ArgumentCaptor argument = forClass(String.class); + verify(transport, times(1)).send(argument.capture()); + assertEquals("host:connect:somehost:1", argument.getValue()); + } + + @Test(expected = JadbException.class) + public void testTransportLevelException() throws ConnectionToRemoteDeviceException, IOException, JadbException { + //Prepare + Transport transport = mock(Transport.class); + doThrow(new JadbException("Fake exception")).when(transport).verifyResponse(); + + InetSocketAddress tcpAddressEntity = new InetSocketAddress("somehost", 1); + + //Do + HostConnectToRemoteTcpDevice hostConnectToRemoteTcpDevice = new HostConnectToRemoteTcpDevice(transport); + hostConnectToRemoteTcpDevice.connect(tcpAddressEntity); + + //Validate + verify(transport, times(1)).send(anyString()); + verify(transport, times(1)).verifyResponse(); + } + + @Test(expected = ConnectionToRemoteDeviceException.class) + public void testProtocolException() throws ConnectionToRemoteDeviceException, IOException, JadbException { + //Prepare + Transport transport = mock(Transport.class); + when(transport.readString()).thenReturn("connected to somehost:1"); + HostConnectToRemoteTcpDevice.ResponseValidator responseValidator = mock(HostConnectToRemoteTcpDevice.ResponseValidator.class); + doThrow(new ConnectionToRemoteDeviceException("Fake exception")).when(responseValidator).validate(anyString()); + + InetSocketAddress tcpAddressEntity = new InetSocketAddress("somehost", 1); + + //Do + HostConnectToRemoteTcpDevice hostConnectToRemoteTcpDevice = new HostConnectToRemoteTcpDevice(transport, responseValidator); + hostConnectToRemoteTcpDevice.connect(tcpAddressEntity); + + //Validate + verify(transport, times(1)).send(anyString()); + verify(transport, times(1)).verifyResponse(); + verify(responseValidator, times(1)).validate(anyString()); + } + + @Test + public void testProtocolResponseValidatorSuccessfullyConnected() throws ConnectionToRemoteDeviceException, IOException, JadbException { + new HostConnectToRemoteTcpDevice.ResponseValidatorImp().validate("connected to somehost:1"); + } + + @Test + public void testProtocolResponseValidatorAlreadyConnected() throws ConnectionToRemoteDeviceException, IOException, JadbException { + new HostConnectToRemoteTcpDevice.ResponseValidatorImp().validate("already connected to somehost:1"); + } + + @Test(expected = ConnectionToRemoteDeviceException.class) + public void testProtocolResponseValidatorErrorInValidate() throws ConnectionToRemoteDeviceException, IOException, JadbException { + new HostConnectToRemoteTcpDevice.ResponseValidatorImp().validate("some error occurred"); + } +} \ No newline at end of file diff --git a/test/se/vidstige/jadb/HostDisconnectFromRemoteTcpDeviceTest.java b/test/se/vidstige/jadb/HostDisconnectFromRemoteTcpDeviceTest.java new file mode 100644 index 0000000..86c87d1 --- /dev/null +++ b/test/se/vidstige/jadb/HostDisconnectFromRemoteTcpDeviceTest.java @@ -0,0 +1,74 @@ +package se.vidstige.jadb; + +import org.junit.Test; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class HostDisconnectFromRemoteTcpDeviceTest { + + @Test + public void testNormalConnection() throws ConnectionToRemoteDeviceException, IOException, JadbException { + //Prepare + Transport transport = mock(Transport.class); + when(transport.readString()).thenReturn("disconnected host:1"); + + InetSocketAddress inetSocketAddress = new InetSocketAddress("host", 1); + + //Do + HostDisconnectFromRemoteTcpDevice hostConnectToRemoteTcpDevice = new HostDisconnectFromRemoteTcpDevice(transport); + InetSocketAddress resultInetSocketAddress = hostConnectToRemoteTcpDevice.disconnect(inetSocketAddress); + + //Validate + assertEquals(inetSocketAddress, resultInetSocketAddress); + } + + @Test(expected = JadbException.class) + public void testTransportLevelException() throws ConnectionToRemoteDeviceException, IOException, JadbException { + //Prepare + Transport transport = mock(Transport.class); + doThrow(new JadbException("Fake exception")).when(transport).verifyResponse(); + + InetSocketAddress inetSocketAddress = new InetSocketAddress("host", 1); + + //Do + HostDisconnectFromRemoteTcpDevice hostConnectToRemoteTcpDevice = new HostDisconnectFromRemoteTcpDevice(transport); + hostConnectToRemoteTcpDevice.disconnect(inetSocketAddress); + } + + @Test(expected = ConnectionToRemoteDeviceException.class) + public void testProtocolException() throws ConnectionToRemoteDeviceException, IOException, JadbException { + //Prepare + Transport transport = mock(Transport.class); + when(transport.readString()).thenReturn("any string"); + HostDisconnectFromRemoteTcpDevice.ResponseValidator responseValidator = mock(HostDisconnectFromRemoteTcpDevice.ResponseValidator.class); + doThrow(new ConnectionToRemoteDeviceException("Fake exception")).when(responseValidator).validate(anyString()); + + InetSocketAddress inetSocketAddress = new InetSocketAddress("host", 1); + + //Do + HostDisconnectFromRemoteTcpDevice hostConnectToRemoteTcpDevice = new HostDisconnectFromRemoteTcpDevice(transport, responseValidator); + hostConnectToRemoteTcpDevice.disconnect(inetSocketAddress); + } + + @Test + public void testProtocolResponseValidatorSuccessfullyConnected() throws ConnectionToRemoteDeviceException, IOException, JadbException { + new HostDisconnectFromRemoteTcpDevice.ResponseValidatorImp().validate("disconnected 127.0.0.1:10001"); + } + + @Test + public void testProtocolResponseValidatorAlreadyConnected() throws ConnectionToRemoteDeviceException, IOException, JadbException { + new HostDisconnectFromRemoteTcpDevice.ResponseValidatorImp().validate("error: no such device '127.0.0.1:10001'"); + } + + @Test(expected = ConnectionToRemoteDeviceException.class) + public void testProtocolResponseValidatorErrorInValidate() throws ConnectionToRemoteDeviceException, IOException, JadbException { + new HostDisconnectFromRemoteTcpDevice.ResponseValidatorImp().validate("some error occurred"); + } +} diff --git a/test/se/vidstige/jadb/test/fakes/FakeAdbServer.java b/test/se/vidstige/jadb/test/fakes/FakeAdbServer.java index a82a0dd..05b7547 100644 --- a/test/se/vidstige/jadb/test/fakes/FakeAdbServer.java +++ b/test/se/vidstige/jadb/test/fakes/FakeAdbServer.java @@ -20,7 +20,7 @@ import java.util.List; */ public class FakeAdbServer implements AdbResponder { private final AdbServer server; - private List devices = new ArrayList(); + private final List devices = new ArrayList(); public FakeAdbServer(int port) { server = new AdbServer(this, port); diff --git a/test/se/vidstige/jadb/test/integration/RealDeviceTestCases.java b/test/se/vidstige/jadb/test/integration/RealDeviceTestCases.java index be6120c..9bb1339 100644 --- a/test/se/vidstige/jadb/test/integration/RealDeviceTestCases.java +++ b/test/se/vidstige/jadb/test/integration/RealDeviceTestCases.java @@ -13,8 +13,13 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.InetSocketAddress; import java.util.List; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + public class RealDeviceTestCases { private JadbConnection jadb; @@ -117,4 +122,40 @@ public class RealDeviceTestCases { if (outputStream != null) outputStream.close(); } } + + /** + * This test requires emulator running on non-standard tcp port - this may be achieve by executing such command: + * ${ANDROID_HOME}/emulator -verbose -avd ${NAME} -ports 10000,10001 + * + * @throws IOException + * @throws JadbException + * @throws ConnectionToRemoteDeviceException + */ + @Test + public void testConnectionToTcpDevice() throws IOException, JadbException, ConnectionToRemoteDeviceException { + jadb.connectToTcpDevice(new InetSocketAddress("127.0.0.1", 10001)); + List devices = jadb.getDevices(); + + assertNotNull(devices); + assertFalse(devices.isEmpty()); + } + + /** + * @see #testConnectionToTcpDevice() + * + * @throws IOException + * @throws JadbException + * @throws ConnectionToRemoteDeviceException + */ + @Test + public void testDisconnectionToTcpDevice() throws IOException, JadbException, ConnectionToRemoteDeviceException { + testConnectionToTcpDevice(); + + jadb.disconnectFromTcpDevice(new InetSocketAddress("127.0.0.1", 10001)); + jadb.getDevices(); + + List devices = jadb.getDevices(); + assertNotNull(devices); + assertTrue(devices.isEmpty()); + } }