diff options
author | Sho Amano <samano@xevo.com> | 2018-10-12 18:02:21 +0900 |
---|---|---|
committer | Sho Amano <samano@xevo.com> | 2018-10-12 21:20:35 +0900 |
commit | 453c806c99fe0162fbd74f4627191de423577c45 (patch) | |
tree | c6a3a232d7be4248ababb873248eb903cc52e622 | |
parent | f362309c3c9532a46a972c64640dd366f2b6c6a3 (diff) | |
download | sdl_android-453c806c99fe0162fbd74f4627191de423577c45.tar.gz |
Add unit tests for WiFiSocketFactory
-rw-r--r-- | sdl_android/src/androidTest/java/com/smartdevicelink/test/transport/WiFiSocketFactoryTest.java | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/sdl_android/src/androidTest/java/com/smartdevicelink/test/transport/WiFiSocketFactoryTest.java b/sdl_android/src/androidTest/java/com/smartdevicelink/test/transport/WiFiSocketFactoryTest.java new file mode 100644 index 000000000..0f2df233d --- /dev/null +++ b/sdl_android/src/androidTest/java/com/smartdevicelink/test/transport/WiFiSocketFactoryTest.java @@ -0,0 +1,352 @@ +package com.smartdevicelink.test.transport; + +import android.Manifest; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.os.Build; +import android.util.Log; + +import com.smartdevicelink.transport.utl.WiFiSocketFactory; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; + +import javax.net.SocketFactory; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This is a unit test class for the WiFiSocketFactory class: + * {@link com.smartdevicelink.transport.utl.WiFiSocketFactory} + * + * Requires LOLLIPOP or later since the tests use android.net.NetworkCapabilities class + */ +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class WiFiSocketFactoryTest extends TestCase { + + private static final String TAG = WiFiSocketFactoryTest.class.getSimpleName(); + + private Context mMockContext; + private PackageManager mMockPackageManager; + private ConnectivityManager mMockConnMan; + private SocketFactory mMockSocketFactory; // this is the SocketFactory that creates mWiFiBoundSocket + private Socket mWiFiBoundSocket; + + private enum FactoryRet { + RETURNS_NULL, + RETURNS_CORRECT_FACTORY, + RETURNS_ANOTHER_FACTORY, + } + + private class MockNetworkConfig { + // true to make a null Network + boolean isNull; + // specify the transport type of the Network + int transportType; + // spcify the type of SocketFactory returned from this Network + FactoryRet factoryReturnValue; + + MockNetworkConfig(boolean isNull, int transportType, FactoryRet factoryReturnValue) { + this.isNull = isNull; + this.transportType = transportType; + this.factoryReturnValue = factoryReturnValue; + } + } + + private void setupMockNetworks(MockNetworkConfig[] configs) { + if (configs == null) { + when(mMockConnMan.getAllNetworks()).thenReturn(null); + return; + } + + List<Network> networkList = new ArrayList<Network>(configs.length); + + for (MockNetworkConfig config : configs) { + if (config.isNull) { + networkList.add(null); + continue; + } + + Network network = mock(Network.class); + + NetworkCapabilities networkCapabilities = createNetworkCapabilitiesWithTransport(config.transportType); + when(mMockConnMan.getNetworkCapabilities(network)).thenReturn(networkCapabilities); + + SocketFactory factory = null; + switch (config.factoryReturnValue) { + case RETURNS_NULL: + break; + case RETURNS_CORRECT_FACTORY: + factory = mMockSocketFactory; + break; + case RETURNS_ANOTHER_FACTORY: + // create another mock SocketFactory instance + factory = mock(SocketFactory.class); + break; + } + when(network.getSocketFactory()).thenReturn(factory); + + networkList.add(network); + } + + when(mMockConnMan.getAllNetworks()).thenReturn(networkList.toArray(new Network[networkList.size()])); + } + + private static NetworkCapabilities createNetworkCapabilitiesWithTransport(int transport) { + // Creates a dummy NetworkCapabilities instance. + // Since NetworkCapabilities class is 'final', we cannot create its mock. To create a dummy + // instance, here we use reflection to call its constructor and a method that are marked + // with "@hide". + // It is possible that these methods will not be available in a future version of Android. + // In that case we need to update our code accordingly. + Class<NetworkCapabilities> c = NetworkCapabilities.class; + try { + Method addTransportTypeMethod = c.getMethod("addTransportType", int.class); + addTransportTypeMethod.setAccessible(true); + + NetworkCapabilities instance = c.getDeclaredConstructor().newInstance(); + addTransportTypeMethod.invoke(instance, transport); + Log.e(TAG, "Yes successful"); + return instance; + } catch (Exception e) { + Log.e(TAG, "Failed to create NetworkCapabilities instance using reflection: ", e); + return null; + } + } + + // from https://stackoverflow.com/questions/40300469/mock-build-version-with-mockito + // and https://stackoverflow.com/questions/13755117/android-changing-private-static-final-field-using-java-reflection + private static void setFinalStatic(Field field, Object newValue) throws Exception { + field.setAccessible(true); +// Field modifiersField = Field.class.getDeclaredField("modifiers"); + // This call might fail on some devices (for example, Nexus 6 with Android 5.0.1). + // If that's the issue, we might want to introduce PowerMock. + Field modifiersField = Field.class.getDeclaredField("accessFlags"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + field.set(null, newValue); + } + + + @Override + public void setUp() throws Exception { + super.setUp(); + + mMockContext = mock(Context.class); + mMockPackageManager = mock(PackageManager.class); + mMockConnMan = mock(ConnectivityManager.class); + + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockContext.getPackageName()).thenReturn("dummyPackageName"); + when(mMockContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE))).thenReturn(mMockConnMan); + + when(mMockPackageManager.checkPermission(eq(Manifest.permission.ACCESS_NETWORK_STATE), anyString())).thenReturn(PackageManager.PERMISSION_GRANTED); + + mMockSocketFactory = mock(SocketFactory.class); + mWiFiBoundSocket = new Socket(); + when(mMockSocketFactory.createSocket()).thenReturn(mWiFiBoundSocket); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + + // test the happy path + public void testWithWiFiNetwork() { + setupMockNetworks(new MockNetworkConfig[] { + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_CELLULAR, FactoryRet.RETURNS_ANOTHER_FACTORY), + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY), + }); + + Socket ret = WiFiSocketFactory.createSocket(mMockContext); + + assertNotNull("createSocket() should always return a Socket instance", ret); + assertEquals("Returned Socket should be created through SocketFactory", mWiFiBoundSocket, ret); + } + + // test the case where SDK_INT is less than 21 + public void testPriorToLollipop() throws Exception { + setupMockNetworks(new MockNetworkConfig[] { + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_CELLULAR, FactoryRet.RETURNS_ANOTHER_FACTORY), + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY), + }); + + // simulate SDK_INT to less than LOLLIPOP + int previousSDKInt = Build.VERSION.SDK_INT; + setFinalStatic(Build.VERSION.class.getField("SDK_INT"), Build.VERSION_CODES.KITKAT_WATCH); + + Socket ret = WiFiSocketFactory.createSocket(mMockContext); + + // make sure we revert our change + setFinalStatic(Build.VERSION.class.getField("SDK_INT"), previousSDKInt); + + assertNotNull("createSocket() should always return a Socket instance", ret); + assertNotSame("Returned Socket shouldn't be created through SocketFactory since it is not available prior to LOLLIPOP", + mWiFiBoundSocket, ret); + } + + // test the case where we do not have ACCESS_NETWORK_STATE permission + public void testWithoutPermission() { + setupMockNetworks(new MockNetworkConfig[] { + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY), + }); + + // simulate the case where required permission isn't available + when(mMockPackageManager.checkPermission(eq(Manifest.permission.ACCESS_NETWORK_STATE), anyString())).thenReturn(PackageManager.PERMISSION_DENIED); + + Socket ret = WiFiSocketFactory.createSocket(mMockContext); + + assertNotNull("createSocket() should always return a Socket instance", ret); + assertNotSame("Returned Socket shouldn't be created through SocketFactory since we don't have required permission", + mWiFiBoundSocket, ret); + } + + // test the case where context.getPackageManager() returns null + public void testPackageManagerNull() { + setupMockNetworks(new MockNetworkConfig[] { + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY), + }); + + // simulate the case where ConnectivityManager isn't available + when(mMockContext.getPackageManager()).thenReturn(null); + + Socket ret = WiFiSocketFactory.createSocket(mMockContext); + + assertNotNull("createSocket() should always return a Socket instance", ret); + assertNotSame("Returned Socket shouldn't be created through SocketFactory since PackageManager isn't available", + mWiFiBoundSocket, ret); + } + + // test the case where getSystemService() returns null + public void testConnectivityManagerNull() { + setupMockNetworks(new MockNetworkConfig[] { + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY), + }); + + // simulate the case where ConnectivityManager isn't available + when(mMockContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE))).thenReturn(null); + + Socket ret = WiFiSocketFactory.createSocket(mMockContext); + + assertNotNull("createSocket() should always return a Socket instance", ret); + assertNotSame("Returned Socket shouldn't be created through SocketFactory since ConnectivityManager isn't working", + mWiFiBoundSocket, ret); + } + + // test the case where ConnectivityManager returns null for the network list + public void testNetworkListNull() { + setupMockNetworks(null); + + Socket ret = WiFiSocketFactory.createSocket(mMockContext); + + assertNotNull("createSocket() should always return a Socket instance", ret); + assertNotSame("Returned Socket shouldn't be created through SocketFactory since Network list isn't available", + mWiFiBoundSocket, ret); + } + + // test the case where the network list contains a null for Network instance + public void testNetworkListHasNull() { + setupMockNetworks(new MockNetworkConfig[] { + // multiple Network instances in the list, the first one being NULL + new MockNetworkConfig(true, 0, FactoryRet.RETURNS_ANOTHER_FACTORY), + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY), + }); + + Socket ret = WiFiSocketFactory.createSocket(mMockContext); + + assertNotNull("createSocket() should always return a Socket instance", ret); + assertEquals("Returned Socket should be created through SocketFactory", mWiFiBoundSocket, ret); + } + + // test the case where the phone isn't connected to Wi-Fi network + public void testNoWiFiNetwork() { + setupMockNetworks(new MockNetworkConfig[] { + // none of the instances has TRANSPORT_WIFI in their capabilities + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_CELLULAR, FactoryRet.RETURNS_ANOTHER_FACTORY), + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_BLUETOOTH, FactoryRet.RETURNS_ANOTHER_FACTORY), + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_VPN, FactoryRet.RETURNS_ANOTHER_FACTORY), + }); + + Socket ret = WiFiSocketFactory.createSocket(mMockContext); + + assertNotNull("createSocket() should always return a Socket instance", ret); + assertNotSame("Returned Socket shouldn't be created through SocketFactory since Wi-Fi network isn't available", + mWiFiBoundSocket, ret); + } + + // test the case where we get null for SocketFactory + public void testSocketFactoryNull() { + setupMockNetworks(new MockNetworkConfig[] { + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_CELLULAR, FactoryRet.RETURNS_ANOTHER_FACTORY), + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_NULL), + }); + + Socket ret = WiFiSocketFactory.createSocket(mMockContext); + + assertNotNull("createSocket() should always return a Socket instance", ret); + assertNotSame("Returned Socket shouldn't be created through SocketFactory since SocketFactory isn't available", + mWiFiBoundSocket, ret); + } + + // test the case where we get a null for SocketFactory, then a valid one for another + public void testSocketFactoryNull2() { + setupMockNetworks(new MockNetworkConfig[] { + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_CELLULAR, FactoryRet.RETURNS_ANOTHER_FACTORY), + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_NULL), + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY), + }); + + Socket ret = WiFiSocketFactory.createSocket(mMockContext); + + assertNotNull("createSocket() should always return a Socket instance", ret); + assertEquals("Returned Socket should be created through SocketFactory", mWiFiBoundSocket, ret); + } + + // test the case where we get an exception with SocketFactory.createSocket() + public void testFactoryReturnsException() throws IOException { + setupMockNetworks(new MockNetworkConfig[] { + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY), + }); + + when(mMockSocketFactory.createSocket()).thenThrow(new IOException("Dummy IOException for testing!")); + + Socket ret = WiFiSocketFactory.createSocket(mMockContext); + + assertNotNull("createSocket() should always return a Socket instance", ret); + assertNotSame("Returned Socket shouldn't be created through SocketFactory since it throws an IOException", + mWiFiBoundSocket, ret); + } + + // Test the case we get multiple Network instances with Wi-Fi transport, and the SocketFactory of + // the first one throws Exception and the other one succeeds. + // This is to simulate Samsung Galaxy S9. + public void testFactoryReturnsException2() throws IOException { + setupMockNetworks(new MockNetworkConfig[] { + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY), + new MockNetworkConfig(false, NetworkCapabilities.TRANSPORT_WIFI, FactoryRet.RETURNS_CORRECT_FACTORY), + }); + + when(mMockSocketFactory.createSocket()).thenThrow(new IOException("Dummy IOException for testing!")) + .thenReturn(mWiFiBoundSocket); + + Socket ret = WiFiSocketFactory.createSocket(mMockContext); + + assertNotNull("createSocket() should always return a Socket instance", ret); + assertEquals("Returned Socket should be created through SocketFactory", mWiFiBoundSocket, ret); + } +} |