diff options
Diffstat (limited to 'tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java')
-rw-r--r-- | tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java new file mode 100644 index 000000000000..cf8f715f23af --- /dev/null +++ b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java @@ -0,0 +1,356 @@ +/* + * 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.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.server.IpSecService.IResource; +import com.android.server.IpSecService.RefcountedResource; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link IpSecService.RefcountedResource}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class IpSecServiceRefcountedResourceTest { + Context mMockContext; + IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig; + IpSecService mIpSecService; + + @Before + public void setUp() throws Exception { + mMockContext = mock(Context.class); + mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class); + mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig); + } + + private void assertResourceState( + RefcountedResource<IResource> resource, + int refCount, + int userReleaseCallCount, + int releaseReferenceCallCount, + int invalidateCallCount, + int freeUnderlyingResourcesCallCount) + throws RemoteException { + // Check refcount on RefcountedResource + assertEquals(refCount, resource.mRefCount); + + // Check call count of RefcountedResource + verify(resource, times(userReleaseCallCount)).userRelease(); + verify(resource, times(releaseReferenceCallCount)).releaseReference(); + + // Check call count of IResource + verify(resource.getResource(), times(invalidateCallCount)).invalidate(); + verify(resource.getResource(), times(freeUnderlyingResourcesCallCount)) + .freeUnderlyingResources(); + } + + /** Adds mockito instrumentation */ + private RefcountedResource<IResource> getTestRefcountedResource( + RefcountedResource... children) { + return getTestRefcountedResource(new Binder(), children); + } + + /** Adds mockito instrumentation with provided binder */ + private RefcountedResource<IResource> getTestRefcountedResource( + IBinder binder, RefcountedResource... children) { + return spy( + mIpSecService + .new RefcountedResource<IResource>(mock(IResource.class), binder, children)); + } + + @Test + public void testConstructor() throws RemoteException { + IBinder binderMock = mock(IBinder.class); + RefcountedResource<IResource> resource = getTestRefcountedResource(binderMock); + + // Verify resource's refcount starts at 1 (for user-reference) + assertResourceState(resource, 1, 0, 0, 0, 0); + + // Verify linking to binder death + verify(binderMock).linkToDeath(anyObject(), anyInt()); + } + + @Test + public void testConstructorWithChildren() throws RemoteException { + IBinder binderMockChild = mock(IBinder.class); + IBinder binderMockParent = mock(IBinder.class); + RefcountedResource<IResource> childResource = getTestRefcountedResource(binderMockChild); + RefcountedResource<IResource> parentResource = + getTestRefcountedResource(binderMockParent, childResource); + + // Verify parent's refcount starts at 1 (for user-reference) + assertResourceState(parentResource, 1, 0, 0, 0, 0); + + // Verify child's refcounts were incremented + assertResourceState(childResource, 2, 0, 0, 0, 0); + + // Verify linking to binder death + verify(binderMockChild).linkToDeath(anyObject(), anyInt()); + verify(binderMockParent).linkToDeath(anyObject(), anyInt()); + } + + @Test + public void testFailLinkToDeath() throws RemoteException { + IBinder binderMock = mock(IBinder.class); + doThrow(new RemoteException()).when(binderMock).linkToDeath(anyObject(), anyInt()); + + RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock); + + // Verify that cleanup is performed (Spy limitations prevent verification of method calls + // for binder death scenario; check refcount to determine if cleanup was performed.) + assertEquals(-1, refcountedResource.mRefCount); + } + + @Test + public void testCleanupAndRelease() throws RemoteException { + IBinder binderMock = mock(IBinder.class); + RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock); + + // Verify user-initiated cleanup path decrements refcount and calls full cleanup flow + refcountedResource.userRelease(); + assertResourceState(refcountedResource, -1, 1, 1, 1, 1); + + // Verify user-initated cleanup path unlinks from binder + verify(binderMock).unlinkToDeath(eq(refcountedResource), eq(0)); + assertNull(refcountedResource.mBinder); + } + + @Test + public void testMultipleCallsToCleanupAndRelease() throws RemoteException { + RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(); + + // Verify calling userRelease multiple times does not trigger any other cleanup + // methods + refcountedResource.userRelease(); + assertResourceState(refcountedResource, -1, 1, 1, 1, 1); + + refcountedResource.userRelease(); + refcountedResource.userRelease(); + assertResourceState(refcountedResource, -1, 3, 1, 1, 1); + } + + @Test + public void testBinderDeathAfterCleanupAndReleaseDoesNothing() throws RemoteException { + RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(); + + refcountedResource.userRelease(); + assertResourceState(refcountedResource, -1, 1, 1, 1, 1); + + // Verify binder death call does not trigger any other cleanup methods if called after + // userRelease() + refcountedResource.binderDied(); + assertResourceState(refcountedResource, -1, 2, 1, 1, 1); + } + + @Test + public void testBinderDeath() throws RemoteException { + RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(); + + // Verify binder death caused cleanup + refcountedResource.binderDied(); + verify(refcountedResource, times(1)).binderDied(); + assertResourceState(refcountedResource, -1, 1, 1, 1, 1); + assertNull(refcountedResource.mBinder); + } + + @Test + public void testCleanupParentDecrementsChildRefcount() throws RemoteException { + RefcountedResource<IResource> childResource = getTestRefcountedResource(); + RefcountedResource<IResource> parentResource = getTestRefcountedResource(childResource); + + parentResource.userRelease(); + + // Verify parent gets cleaned up properly, and triggers releaseReference on + // child + assertResourceState(childResource, 1, 0, 1, 0, 0); + assertResourceState(parentResource, -1, 1, 1, 1, 1); + } + + @Test + public void testCleanupReferencedChildDoesNotTriggerRelease() throws RemoteException { + RefcountedResource<IResource> childResource = getTestRefcountedResource(); + RefcountedResource<IResource> parentResource = getTestRefcountedResource(childResource); + + childResource.userRelease(); + + // Verify that child does not clean up kernel resources and quota. + assertResourceState(childResource, 1, 1, 1, 1, 0); + assertResourceState(parentResource, 1, 0, 0, 0, 0); + } + + @Test + public void testTwoParents() throws RemoteException { + RefcountedResource<IResource> childResource = getTestRefcountedResource(); + RefcountedResource<IResource> parentResource1 = getTestRefcountedResource(childResource); + RefcountedResource<IResource> parentResource2 = getTestRefcountedResource(childResource); + + // Verify that child does not cleanup kernel resources and quota until all references + // have been released. Assumption: parents release correctly based on + // testCleanupParentDecrementsChildRefcount() + childResource.userRelease(); + assertResourceState(childResource, 2, 1, 1, 1, 0); + + parentResource1.userRelease(); + assertResourceState(childResource, 1, 1, 2, 1, 0); + + parentResource2.userRelease(); + assertResourceState(childResource, -1, 1, 3, 1, 1); + } + + @Test + public void testTwoChildren() throws RemoteException { + RefcountedResource<IResource> childResource1 = getTestRefcountedResource(); + RefcountedResource<IResource> childResource2 = getTestRefcountedResource(); + RefcountedResource<IResource> parentResource = + getTestRefcountedResource(childResource1, childResource2); + + childResource1.userRelease(); + assertResourceState(childResource1, 1, 1, 1, 1, 0); + assertResourceState(childResource2, 2, 0, 0, 0, 0); + + parentResource.userRelease(); + assertResourceState(childResource1, -1, 1, 2, 1, 1); + assertResourceState(childResource2, 1, 0, 1, 0, 0); + + childResource2.userRelease(); + assertResourceState(childResource1, -1, 1, 2, 1, 1); + assertResourceState(childResource2, -1, 1, 2, 1, 1); + } + + @Test + public void testSampleUdpEncapTranform() throws RemoteException { + RefcountedResource<IResource> spi1 = getTestRefcountedResource(); + RefcountedResource<IResource> spi2 = getTestRefcountedResource(); + RefcountedResource<IResource> udpEncapSocket = getTestRefcountedResource(); + RefcountedResource<IResource> transform = + getTestRefcountedResource(spi1, spi2, udpEncapSocket); + + // Pretend one SPI goes out of reference (releaseManagedResource -> userRelease) + spi1.userRelease(); + + // User called releaseManagedResource on udpEncap socket + udpEncapSocket.userRelease(); + + // User dies, and binder kills the rest + spi2.binderDied(); + transform.binderDied(); + + // Check resource states + assertResourceState(spi1, -1, 1, 2, 1, 1); + assertResourceState(spi2, -1, 1, 2, 1, 1); + assertResourceState(udpEncapSocket, -1, 1, 2, 1, 1); + assertResourceState(transform, -1, 1, 1, 1, 1); + } + + @Test + public void testSampleDualTransformEncapSocket() throws RemoteException { + RefcountedResource<IResource> spi1 = getTestRefcountedResource(); + RefcountedResource<IResource> spi2 = getTestRefcountedResource(); + RefcountedResource<IResource> spi3 = getTestRefcountedResource(); + RefcountedResource<IResource> spi4 = getTestRefcountedResource(); + RefcountedResource<IResource> udpEncapSocket = getTestRefcountedResource(); + RefcountedResource<IResource> transform1 = + getTestRefcountedResource(spi1, spi2, udpEncapSocket); + RefcountedResource<IResource> transform2 = + getTestRefcountedResource(spi3, spi4, udpEncapSocket); + + // Pretend one SPIs goes out of reference (releaseManagedResource -> userRelease) + spi1.userRelease(); + + // User called releaseManagedResource on udpEncap socket and spi4 + udpEncapSocket.userRelease(); + spi4.userRelease(); + + // User dies, and binder kills the rest + spi2.binderDied(); + spi3.binderDied(); + transform2.binderDied(); + transform1.binderDied(); + + // Check resource states + assertResourceState(spi1, -1, 1, 2, 1, 1); + assertResourceState(spi2, -1, 1, 2, 1, 1); + assertResourceState(spi3, -1, 1, 2, 1, 1); + assertResourceState(spi4, -1, 1, 2, 1, 1); + assertResourceState(udpEncapSocket, -1, 1, 3, 1, 1); + assertResourceState(transform1, -1, 1, 1, 1, 1); + assertResourceState(transform2, -1, 1, 1, 1, 1); + } + + @Test + public void fuzzTest() throws RemoteException { + List<RefcountedResource<IResource>> resources = new ArrayList<>(); + + // Build a tree of resources + for (int i = 0; i < 100; i++) { + // Choose a random number of children from the existing list + int numChildren = ThreadLocalRandom.current().nextInt(0, resources.size() + 1); + + // Build a (random) list of children + Set<RefcountedResource<IResource>> children = new HashSet<>(); + for (int j = 0; j < numChildren; j++) { + int childIndex = ThreadLocalRandom.current().nextInt(0, resources.size()); + children.add(resources.get(childIndex)); + } + + RefcountedResource<IResource> newRefcountedResource = + getTestRefcountedResource( + children.toArray(new RefcountedResource[children.size()])); + resources.add(newRefcountedResource); + } + + // Cleanup all resources in a random order + List<RefcountedResource<IResource>> clonedResources = + new ArrayList<>(resources); // shallow copy + while (!clonedResources.isEmpty()) { + int index = ThreadLocalRandom.current().nextInt(0, clonedResources.size()); + RefcountedResource<IResource> refcountedResource = clonedResources.get(index); + refcountedResource.userRelease(); + clonedResources.remove(index); + } + + // Verify all resources were cleaned up properly + for (RefcountedResource<IResource> refcountedResource : resources) { + assertEquals(-1, refcountedResource.mRefCount); + } + } +} |