blob: cf8f715f23af08606d6b52e59fc7bceafaf845d0 [file] [log] [blame]
Benedict Wong409c8ca2017-10-26 19:41:43 -07001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertNull;
21import static org.mockito.Matchers.anyInt;
22import static org.mockito.Matchers.anyObject;
23import static org.mockito.Matchers.eq;
24import static org.mockito.Mockito.doThrow;
25import static org.mockito.Mockito.mock;
26import static org.mockito.Mockito.spy;
27import static org.mockito.Mockito.times;
28import static org.mockito.Mockito.verify;
29
30import android.content.Context;
31import android.os.Binder;
32import android.os.IBinder;
33import android.os.RemoteException;
34import android.support.test.filters.SmallTest;
35import android.support.test.runner.AndroidJUnit4;
36
37import com.android.server.IpSecService.IResource;
38import com.android.server.IpSecService.RefcountedResource;
39
40import java.util.ArrayList;
41import java.util.HashSet;
42import java.util.List;
43import java.util.Set;
44import java.util.concurrent.ThreadLocalRandom;
45
46import org.junit.Before;
47import org.junit.Test;
48import org.junit.runner.RunWith;
49
50/** Unit tests for {@link IpSecService.RefcountedResource}. */
51@SmallTest
52@RunWith(AndroidJUnit4.class)
53public class IpSecServiceRefcountedResourceTest {
54 Context mMockContext;
55 IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
56 IpSecService mIpSecService;
57
58 @Before
59 public void setUp() throws Exception {
60 mMockContext = mock(Context.class);
61 mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
62 mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
63 }
64
65 private void assertResourceState(
66 RefcountedResource<IResource> resource,
67 int refCount,
68 int userReleaseCallCount,
69 int releaseReferenceCallCount,
70 int invalidateCallCount,
71 int freeUnderlyingResourcesCallCount)
72 throws RemoteException {
73 // Check refcount on RefcountedResource
74 assertEquals(refCount, resource.mRefCount);
75
76 // Check call count of RefcountedResource
77 verify(resource, times(userReleaseCallCount)).userRelease();
78 verify(resource, times(releaseReferenceCallCount)).releaseReference();
79
80 // Check call count of IResource
81 verify(resource.getResource(), times(invalidateCallCount)).invalidate();
82 verify(resource.getResource(), times(freeUnderlyingResourcesCallCount))
83 .freeUnderlyingResources();
84 }
85
86 /** Adds mockito instrumentation */
87 private RefcountedResource<IResource> getTestRefcountedResource(
88 RefcountedResource... children) {
89 return getTestRefcountedResource(new Binder(), children);
90 }
91
92 /** Adds mockito instrumentation with provided binder */
93 private RefcountedResource<IResource> getTestRefcountedResource(
94 IBinder binder, RefcountedResource... children) {
95 return spy(
96 mIpSecService
97 .new RefcountedResource<IResource>(mock(IResource.class), binder, children));
98 }
99
100 @Test
101 public void testConstructor() throws RemoteException {
102 IBinder binderMock = mock(IBinder.class);
103 RefcountedResource<IResource> resource = getTestRefcountedResource(binderMock);
104
105 // Verify resource's refcount starts at 1 (for user-reference)
106 assertResourceState(resource, 1, 0, 0, 0, 0);
107
108 // Verify linking to binder death
109 verify(binderMock).linkToDeath(anyObject(), anyInt());
110 }
111
112 @Test
113 public void testConstructorWithChildren() throws RemoteException {
114 IBinder binderMockChild = mock(IBinder.class);
115 IBinder binderMockParent = mock(IBinder.class);
116 RefcountedResource<IResource> childResource = getTestRefcountedResource(binderMockChild);
117 RefcountedResource<IResource> parentResource =
118 getTestRefcountedResource(binderMockParent, childResource);
119
120 // Verify parent's refcount starts at 1 (for user-reference)
121 assertResourceState(parentResource, 1, 0, 0, 0, 0);
122
123 // Verify child's refcounts were incremented
124 assertResourceState(childResource, 2, 0, 0, 0, 0);
125
126 // Verify linking to binder death
127 verify(binderMockChild).linkToDeath(anyObject(), anyInt());
128 verify(binderMockParent).linkToDeath(anyObject(), anyInt());
129 }
130
131 @Test
132 public void testFailLinkToDeath() throws RemoteException {
133 IBinder binderMock = mock(IBinder.class);
134 doThrow(new RemoteException()).when(binderMock).linkToDeath(anyObject(), anyInt());
135
136 RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock);
137
138 // Verify that cleanup is performed (Spy limitations prevent verification of method calls
139 // for binder death scenario; check refcount to determine if cleanup was performed.)
140 assertEquals(-1, refcountedResource.mRefCount);
141 }
142
143 @Test
144 public void testCleanupAndRelease() throws RemoteException {
145 IBinder binderMock = mock(IBinder.class);
146 RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock);
147
148 // Verify user-initiated cleanup path decrements refcount and calls full cleanup flow
149 refcountedResource.userRelease();
150 assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
151
152 // Verify user-initated cleanup path unlinks from binder
153 verify(binderMock).unlinkToDeath(eq(refcountedResource), eq(0));
154 assertNull(refcountedResource.mBinder);
155 }
156
157 @Test
158 public void testMultipleCallsToCleanupAndRelease() throws RemoteException {
159 RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
160
161 // Verify calling userRelease multiple times does not trigger any other cleanup
162 // methods
163 refcountedResource.userRelease();
164 assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
165
166 refcountedResource.userRelease();
167 refcountedResource.userRelease();
168 assertResourceState(refcountedResource, -1, 3, 1, 1, 1);
169 }
170
171 @Test
172 public void testBinderDeathAfterCleanupAndReleaseDoesNothing() throws RemoteException {
173 RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
174
175 refcountedResource.userRelease();
176 assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
177
178 // Verify binder death call does not trigger any other cleanup methods if called after
179 // userRelease()
180 refcountedResource.binderDied();
181 assertResourceState(refcountedResource, -1, 2, 1, 1, 1);
182 }
183
184 @Test
185 public void testBinderDeath() throws RemoteException {
186 RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
187
188 // Verify binder death caused cleanup
189 refcountedResource.binderDied();
190 verify(refcountedResource, times(1)).binderDied();
191 assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
192 assertNull(refcountedResource.mBinder);
193 }
194
195 @Test
196 public void testCleanupParentDecrementsChildRefcount() throws RemoteException {
197 RefcountedResource<IResource> childResource = getTestRefcountedResource();
198 RefcountedResource<IResource> parentResource = getTestRefcountedResource(childResource);
199
200 parentResource.userRelease();
201
202 // Verify parent gets cleaned up properly, and triggers releaseReference on
203 // child
204 assertResourceState(childResource, 1, 0, 1, 0, 0);
205 assertResourceState(parentResource, -1, 1, 1, 1, 1);
206 }
207
208 @Test
209 public void testCleanupReferencedChildDoesNotTriggerRelease() throws RemoteException {
210 RefcountedResource<IResource> childResource = getTestRefcountedResource();
211 RefcountedResource<IResource> parentResource = getTestRefcountedResource(childResource);
212
213 childResource.userRelease();
214
215 // Verify that child does not clean up kernel resources and quota.
216 assertResourceState(childResource, 1, 1, 1, 1, 0);
217 assertResourceState(parentResource, 1, 0, 0, 0, 0);
218 }
219
220 @Test
221 public void testTwoParents() throws RemoteException {
222 RefcountedResource<IResource> childResource = getTestRefcountedResource();
223 RefcountedResource<IResource> parentResource1 = getTestRefcountedResource(childResource);
224 RefcountedResource<IResource> parentResource2 = getTestRefcountedResource(childResource);
225
226 // Verify that child does not cleanup kernel resources and quota until all references
227 // have been released. Assumption: parents release correctly based on
228 // testCleanupParentDecrementsChildRefcount()
229 childResource.userRelease();
230 assertResourceState(childResource, 2, 1, 1, 1, 0);
231
232 parentResource1.userRelease();
233 assertResourceState(childResource, 1, 1, 2, 1, 0);
234
235 parentResource2.userRelease();
236 assertResourceState(childResource, -1, 1, 3, 1, 1);
237 }
238
239 @Test
240 public void testTwoChildren() throws RemoteException {
241 RefcountedResource<IResource> childResource1 = getTestRefcountedResource();
242 RefcountedResource<IResource> childResource2 = getTestRefcountedResource();
243 RefcountedResource<IResource> parentResource =
244 getTestRefcountedResource(childResource1, childResource2);
245
246 childResource1.userRelease();
247 assertResourceState(childResource1, 1, 1, 1, 1, 0);
248 assertResourceState(childResource2, 2, 0, 0, 0, 0);
249
250 parentResource.userRelease();
251 assertResourceState(childResource1, -1, 1, 2, 1, 1);
252 assertResourceState(childResource2, 1, 0, 1, 0, 0);
253
254 childResource2.userRelease();
255 assertResourceState(childResource1, -1, 1, 2, 1, 1);
256 assertResourceState(childResource2, -1, 1, 2, 1, 1);
257 }
258
259 @Test
260 public void testSampleUdpEncapTranform() throws RemoteException {
261 RefcountedResource<IResource> spi1 = getTestRefcountedResource();
262 RefcountedResource<IResource> spi2 = getTestRefcountedResource();
263 RefcountedResource<IResource> udpEncapSocket = getTestRefcountedResource();
264 RefcountedResource<IResource> transform =
265 getTestRefcountedResource(spi1, spi2, udpEncapSocket);
266
267 // Pretend one SPI goes out of reference (releaseManagedResource -> userRelease)
268 spi1.userRelease();
269
270 // User called releaseManagedResource on udpEncap socket
271 udpEncapSocket.userRelease();
272
273 // User dies, and binder kills the rest
274 spi2.binderDied();
275 transform.binderDied();
276
277 // Check resource states
278 assertResourceState(spi1, -1, 1, 2, 1, 1);
279 assertResourceState(spi2, -1, 1, 2, 1, 1);
280 assertResourceState(udpEncapSocket, -1, 1, 2, 1, 1);
281 assertResourceState(transform, -1, 1, 1, 1, 1);
282 }
283
284 @Test
285 public void testSampleDualTransformEncapSocket() throws RemoteException {
286 RefcountedResource<IResource> spi1 = getTestRefcountedResource();
287 RefcountedResource<IResource> spi2 = getTestRefcountedResource();
288 RefcountedResource<IResource> spi3 = getTestRefcountedResource();
289 RefcountedResource<IResource> spi4 = getTestRefcountedResource();
290 RefcountedResource<IResource> udpEncapSocket = getTestRefcountedResource();
291 RefcountedResource<IResource> transform1 =
292 getTestRefcountedResource(spi1, spi2, udpEncapSocket);
293 RefcountedResource<IResource> transform2 =
294 getTestRefcountedResource(spi3, spi4, udpEncapSocket);
295
296 // Pretend one SPIs goes out of reference (releaseManagedResource -> userRelease)
297 spi1.userRelease();
298
299 // User called releaseManagedResource on udpEncap socket and spi4
300 udpEncapSocket.userRelease();
301 spi4.userRelease();
302
303 // User dies, and binder kills the rest
304 spi2.binderDied();
305 spi3.binderDied();
306 transform2.binderDied();
307 transform1.binderDied();
308
309 // Check resource states
310 assertResourceState(spi1, -1, 1, 2, 1, 1);
311 assertResourceState(spi2, -1, 1, 2, 1, 1);
312 assertResourceState(spi3, -1, 1, 2, 1, 1);
313 assertResourceState(spi4, -1, 1, 2, 1, 1);
314 assertResourceState(udpEncapSocket, -1, 1, 3, 1, 1);
315 assertResourceState(transform1, -1, 1, 1, 1, 1);
316 assertResourceState(transform2, -1, 1, 1, 1, 1);
317 }
318
319 @Test
320 public void fuzzTest() throws RemoteException {
321 List<RefcountedResource<IResource>> resources = new ArrayList<>();
322
323 // Build a tree of resources
324 for (int i = 0; i < 100; i++) {
325 // Choose a random number of children from the existing list
326 int numChildren = ThreadLocalRandom.current().nextInt(0, resources.size() + 1);
327
328 // Build a (random) list of children
329 Set<RefcountedResource<IResource>> children = new HashSet<>();
330 for (int j = 0; j < numChildren; j++) {
331 int childIndex = ThreadLocalRandom.current().nextInt(0, resources.size());
332 children.add(resources.get(childIndex));
333 }
334
335 RefcountedResource<IResource> newRefcountedResource =
336 getTestRefcountedResource(
337 children.toArray(new RefcountedResource[children.size()]));
338 resources.add(newRefcountedResource);
339 }
340
341 // Cleanup all resources in a random order
342 List<RefcountedResource<IResource>> clonedResources =
343 new ArrayList<>(resources); // shallow copy
344 while (!clonedResources.isEmpty()) {
345 int index = ThreadLocalRandom.current().nextInt(0, clonedResources.size());
346 RefcountedResource<IResource> refcountedResource = clonedResources.get(index);
347 refcountedResource.userRelease();
348 clonedResources.remove(index);
349 }
350
351 // Verify all resources were cleaned up properly
352 for (RefcountedResource<IResource> refcountedResource : resources) {
353 assertEquals(-1, refcountedResource.mRefCount);
354 }
355 }
356}