1/*
2 * Copyright (C) 2016 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 libcore.libcore.util;
18
19import junit.framework.TestCase;
20
21import libcore.util.NativeAllocationRegistry;
22
23public class NativeAllocationRegistryTest extends TestCase {
24
25    static {
26        System.loadLibrary("javacoretests");
27    }
28
29    private ClassLoader classLoader = NativeAllocationRegistryTest.class.getClassLoader();
30
31    private static class TestConfig {
32        public boolean useAllocator;
33        public boolean shareRegistry;
34
35        public TestConfig(boolean useAllocator, boolean shareRegistry) {
36            this.useAllocator = useAllocator;
37            this.shareRegistry = shareRegistry;
38        }
39    }
40
41    private static class Allocation {
42        public byte[] javaAllocation;
43        public long nativeAllocation;
44    }
45
46    // Verify that NativeAllocations and their referents are freed before we run
47    // out of space for new allocations.
48    private void testNativeAllocation(TestConfig config) {
49        Runtime.getRuntime().gc();
50        long max = Runtime.getRuntime().maxMemory();
51        long total = Runtime.getRuntime().totalMemory();
52        int size = 1024*1024;
53        int expectedMaxNumAllocations = (int)(max-total)/size;
54        int numSavedAllocations = expectedMaxNumAllocations/2;
55        Allocation[] saved = new Allocation[numSavedAllocations];
56
57        final int nativeSize = size/2;
58        int javaSize = size/2;
59        NativeAllocationRegistry registry = new NativeAllocationRegistry(
60                classLoader, getNativeFinalizer(), nativeSize);
61
62        // Allocate more native allocations than will fit in memory. This should
63        // not throw OutOfMemoryError because the few allocations we save
64        // references to should easily fit.
65        for (int i = 0; i < expectedMaxNumAllocations * 10; i++) {
66            if (!config.shareRegistry) {
67                registry = new NativeAllocationRegistry(
68                    classLoader, getNativeFinalizer(), nativeSize);
69            }
70
71            final Allocation alloc = new Allocation();
72            alloc.javaAllocation = new byte[javaSize];
73            if (config.useAllocator) {
74                NativeAllocationRegistry.Allocator allocator
75                  = new NativeAllocationRegistry.Allocator() {
76                    public long allocate() {
77                        alloc.nativeAllocation = doNativeAllocation(nativeSize);
78                        return alloc.nativeAllocation;
79                    }
80                };
81                registry.registerNativeAllocation(alloc, allocator);
82            } else {
83                alloc.nativeAllocation = doNativeAllocation(nativeSize);
84                registry.registerNativeAllocation(alloc, alloc.nativeAllocation);
85            }
86
87            saved[i%numSavedAllocations] = alloc;
88        }
89
90        // Verify most of the allocations have been freed.
91        long nativeBytes = getNumNativeBytesAllocated();
92        assertTrue("Excessive native bytes still allocated (" + nativeBytes + ")"
93                + " given max memory of (" + max + ")", nativeBytes < 2 * max);
94    }
95
96    public void testNativeAllocationAllocatorAndSharedRegistry() {
97        testNativeAllocation(new TestConfig(true, true));
98    }
99
100    public void testNativeAllocationNoAllocatorAndSharedRegistry() {
101        testNativeAllocation(new TestConfig(false, true));
102    }
103
104    public void testNativeAllocationAllocatorAndNoSharedRegistry() {
105        testNativeAllocation(new TestConfig(true, false));
106    }
107
108    public void testNativeAllocationNoAllocatorAndNoSharedRegistry() {
109        testNativeAllocation(new TestConfig(false, false));
110    }
111
112    public void testBadSize() {
113        assertThrowsIllegalArgumentException(new Runnable() {
114            public void run() {
115                NativeAllocationRegistry registry = new NativeAllocationRegistry(
116                        classLoader, getNativeFinalizer(), -8);
117            }
118        });
119    }
120
121    public void testEarlyFree() {
122        long size = 1234;
123        NativeAllocationRegistry registry
124            = new NativeAllocationRegistry(classLoader, getNativeFinalizer(), size);
125        long nativePtr = doNativeAllocation(size);
126        Object referent = new Object();
127        Runnable cleaner = registry.registerNativeAllocation(referent, nativePtr);
128        long numBytesAllocatedBeforeClean = getNumNativeBytesAllocated();
129
130        // Running the cleaner should cause the native finalizer to run.
131        cleaner.run();
132        long numBytesAllocatedAfterClean = getNumNativeBytesAllocated();
133        assertEquals(numBytesAllocatedBeforeClean - size, numBytesAllocatedAfterClean);
134
135        // Running the cleaner again should have no effect.
136        cleaner.run();
137        assertEquals(numBytesAllocatedAfterClean, getNumNativeBytesAllocated());
138
139        // There shouldn't be any problems when the referent object is GC'd.
140        referent = null;
141        Runtime.getRuntime().gc();
142    }
143
144    public void testNullArguments() {
145        final NativeAllocationRegistry registry
146            = new NativeAllocationRegistry(classLoader, getNativeFinalizer(), 1024);
147        final long dummyNativePtr = 0x1;
148        final Object referent = new Object();
149
150        // referent should not be null
151        assertThrowsIllegalArgumentException(new Runnable() {
152            public void run() {
153                registry.registerNativeAllocation(null, dummyNativePtr);
154            }
155        });
156
157        // nativePtr should not be null
158        assertThrowsIllegalArgumentException(new Runnable() {
159            public void run() {
160                registry.registerNativeAllocation(referent, 0);
161            }
162        });
163
164        // referent should not be null
165        assertThrowsIllegalArgumentException(new Runnable() {
166            public void run() {
167                registry.registerNativeAllocation(null,
168                        new NativeAllocationRegistry.Allocator() {
169                            public long allocate() {
170                                // The allocate function ought not to be called.
171                                fail("allocate function called");
172                                return dummyNativePtr;
173                            }
174                        });
175            }
176        });
177
178        // Allocation that returns null should have no effect.
179        assertNull(registry.registerNativeAllocation(referent,
180                    new NativeAllocationRegistry.Allocator() {
181                        public long allocate() {
182                            return 0;
183                        }
184                    }));
185    }
186
187    private static void assertThrowsIllegalArgumentException(Runnable runnable) {
188        try {
189            runnable.run();
190        } catch (IllegalArgumentException ex) {
191            return;
192        }
193        fail("Expected IllegalArgumentException, but no exception was thrown.");
194    }
195
196    private static native long getNativeFinalizer();
197    private static native long doNativeAllocation(long size);
198    private static native long getNumNativeBytesAllocated();
199}
200