PointerTest.java

/*
 * Copyright (C) 2016-2022 Samuel Audet
 *
 * Licensed either under the Apache License, Version 2.0, or (at your option)
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation (subject to the "Classpath" exception),
 * either version 2, or any later version (collectively, 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
 *     http://www.gnu.org/licenses/
 *     http://www.gnu.org/software/classpath/license.html
 *
 * or as provided in the LICENSE.txt file that accompanied this code.
 * 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 org.bytedeco.javacpp;

import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.MappedByteBuffer;
import java.nio.ShortBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.bytedeco.javacpp.annotation.Allocator;
import org.bytedeco.javacpp.annotation.Platform;
import org.bytedeco.javacpp.tools.Builder;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;

/**
 * Test cases for the set of base Pointer classes. Relies on other classes from JavaCPP.
 *
 * @author Samuel Audet
 */
@Platform(define = {"NATIVE_ALLOCATOR malloc", "NATIVE_DEALLOCATOR free"})
public class PointerTest {

    static final int allocatorMax = 11;
    static final long maxBytes = 1024 * 1024 * 1024; /* 1g */

    @Allocator(max = allocatorMax)
    static class TestFunction extends FunctionPointer {
        public TestFunction(Pointer p) { super(p); }
        public TestFunction() { allocate(); }
        private native void allocate();
        public native int call(String s);
        public native Pointer get();
        public native TestFunction put(Pointer address);
    }

    @BeforeClass public static void setUpClass() throws Exception {
        System.out.println("Builder");
        Class c = PointerTest.class;
        Builder builder = new Builder().classesOrPackages(c.getName());
        File[] outputFiles = builder.build();

        System.out.println("Loader");
        Loader.load(c);

        int totalProcessors = Loader.totalProcessors();
        int totalCores = Loader.totalCores();
        int totalChips = Loader.totalChips();
        System.out.println(totalProcessors + " " + totalCores + " " + totalChips);
        assertTrue(totalProcessors > 0 && totalProcessors >= Runtime.getRuntime().availableProcessors());
        assertTrue(totalCores > 0 && totalCores <= totalProcessors);
        assertTrue(totalChips > 0 && totalChips <= totalCores);

        assertNotEquals(null, Loader.getJavaVM());

        Pointer globalRef = Loader.newGlobalRef(c);
        assertNotEquals(null, globalRef);
        assertEquals(null, Loader.newGlobalRef(null));
        assertEquals(c, (Class)Loader.accessGlobalRef(globalRef));
        Loader.deleteGlobalRef(globalRef);
        Loader.deleteGlobalRef(null);
    }

    @Test public void testFunctionPointer() {
        System.out.println("FunctionPointer");

        Pointer address = Loader.addressof("strlen");
        assertNotNull(address);
        TestFunction function = new TestFunction().put(address);
        assertEquals(address, function.get());
        assertEquals(5, function.call("12345"));
        function.deallocate();

        TestFunction[] functions = new TestFunction[allocatorMax];
        Pointer prevp = new Pointer();
        for (int i = 0; i < allocatorMax; i++) {
            final int n = i;
            functions[i] = new TestFunction() {
                @Override public int call(String s) { return n; }
            };
            Pointer p = functions[i].get();
            System.out.println(p);
            assertNotNull(p);
            assertNotEquals(prevp, p);
            prevp = p;
        }

        TestFunction f = new TestFunction() {
            @Override public int call(String s) { return allocatorMax; }
        };
        assertNull(f.get());

        for (int i = 0; i < allocatorMax; i++) {
            functions[i].deallocate();
        }

        TestFunction f2 = new TestFunction() {
            @Override public int call(String s) { return allocatorMax; }
        };
        assertNotNull(f2.get());
    }

    static Object fieldReference;

    @Test public void testPointer() {
        System.out.println("Pointer");
        assertEquals(maxBytes, Pointer.maxBytes);
        assertEquals(3, Pointer.maxRetries);
        assertTrue(new Pointer().equals(null));
        Pointer p = new Pointer() { { address = 0xDEADBEEF; }};
        assertEquals(p, new Pointer(p));

        Pointer p2 = p.getPointer(Pointer.class, 10);
        assertEquals(p.address() + 10, p2.address());
        assertEquals(0, p2.limit());
        assertEquals(0, p2.capacity());

        long physicalBytes = Pointer.physicalBytes();
        long totalPhysicalBytes = Pointer.totalPhysicalBytes();
        long availablePhysicalBytes = Pointer.availablePhysicalBytes();
        System.out.println(physicalBytes);
        System.out.println(totalPhysicalBytes);
        System.out.println(availablePhysicalBytes);
        assertTrue(physicalBytes > 0);
        assertTrue(totalPhysicalBytes > 0 && physicalBytes < totalPhysicalBytes);
        assertTrue(availablePhysicalBytes > 0 && availablePhysicalBytes < totalPhysicalBytes);

        p = Pointer.malloc(1000);
        assertTrue(!p.isNull());
        p = Pointer.calloc(1000, 1000);
        assertTrue(!p.isNull());
        p = Pointer.realloc(p, 1000);
        assertTrue(!p.isNull());
        Pointer.free(p);
    }

    @Test public void testBytePointer() {
        System.out.println("BytePointer");

        int byteSize = Byte.SIZE / 8;
        assertEquals(byteSize, Loader.sizeof(BytePointer.class));

        byte[] array = new byte[8192];
        BytePointer pointer = new BytePointer(array);
        assertEquals(array.length, pointer.limit());
        assertEquals(array.length, pointer.capacity());

        BytePointer p2 = pointer.getPointer(10);
        assertEquals(pointer.address() + 10, p2.address());
        assertEquals(array.length - 10, p2.limit());
        assertEquals(array.length - 10, p2.capacity());
        Pointer p3 = pointer.getPointer(Pointer.class, 20);
        assertEquals(pointer.address() + 20, p3.address());
        assertEquals(1 * array.length - 20, p3.limit());
        assertEquals(1 * array.length - 20, p3.capacity());

        for (int i = 0; i < array.length; i++) {
            array[i] = (byte)i;
            pointer.put(i, (byte)i);
            assertEquals(array[i], pointer.get(i));
        }

        for (int i = 0; i < array.length; i++) {
            pointer.position(i).put(array[i]);
            assertEquals(array[i], pointer.position(i).get());
        }

        short shortValue = 0x0102;
        int intValue = 0x01020304;
        long longValue = 0x0102030405060708L;
        if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
            shortValue = Short.reverseBytes(shortValue);
            intValue = Integer.reverseBytes(intValue);
            longValue = Long.reverseBytes(longValue);
        }
        float floatValue = Float.intBitsToFloat(intValue);
        double doubleValue = Double.longBitsToDouble(longValue);
        pointer.position(0);
        assertEquals(shortValue, pointer.getShort(1));
        assertEquals(intValue, pointer.getInt(1));
        assertEquals(longValue, pointer.getLong(1));
        assertEquals(floatValue, pointer.getFloat(1), 0.0);
        assertEquals(doubleValue, pointer.getDouble(1), 0.0);
        assertEquals(false, pointer.getBool(0));
        assertEquals(true, pointer.getBool(1));
        assertEquals(shortValue, pointer.getChar(1));
        assertEquals(Loader.sizeof(Pointer.class) == 4 ? intValue : longValue, pointer.getPointerValue(1).address);

        byte[] array2 = new byte[array.length];
        pointer.position(0).get(array2);
        assertArrayEquals(array, array2);

        ByteBuffer buffer = pointer.position(5 + 7).asBuffer();
        ByteBuffer arrayBuffer = ByteBuffer.wrap(array, 5, array.length - 5);
        assertTrue(buffer.compareTo((ByteBuffer)arrayBuffer.position(arrayBuffer.position() + 7)) == 0);
        assertEquals(pointer.address(), new BytePointer(buffer).address());
        assertEquals(pointer.position(), new BytePointer(buffer).position());
        assertEquals(pointer.limit(), new BytePointer(buffer).limit());
        assertEquals(pointer.capacity(), new BytePointer(buffer).capacity());

        int offset = 42;
        pointer.put(array, offset, array.length - offset);
        pointer.get(array2, offset, array.length - offset);
        assertArrayEquals(array, array2);

        BytePointer pointer2 = new BytePointer(array.length).zero();
        pointer2.position(10).limit(30).fill(0xFF);
        pointer2.position(20).put(pointer.position(20).limit(30));
        pointer.position(0);
        pointer2.position(0);
        for (int i = 0; i < array.length; i++) {
            if (i < 10) {
                assertEquals(0, pointer2.get(i));
            } else if (i < 20) {
                assertEquals((byte)0xFF, pointer2.get(i));
            } else if (i < 30) {
                assertEquals(pointer.get(i), pointer2.get(i));
            } else {
                assertEquals(0, pointer2.get(i));
            }
        }

        assertEquals(maxBytes, Pointer.maxBytes);
        int chunks = 8;
        BytePointer[] pointers = new BytePointer[chunks];
        long chunkSize = Pointer.maxBytes / byteSize / chunks + 1;
        for (int j = 0; j < chunks - 1; j++) {
            pointers[j] = new BytePointer(chunkSize);
        }
        assertTrue(Pointer.DeallocatorReference.totalBytes >= (chunks - 1) * chunkSize * byteSize);
        try {
            fieldReference = pointers;
            System.out.println("Note: OutOfMemoryError should get thrown here and printed below.");
            new BytePointer(chunkSize);
            fail("OutOfMemoryError should have been thrown.");
        } catch (OutOfMemoryError e) {
            System.out.println(e);
            System.out.println(e.getCause());
        }
        for (int j = 0; j < chunks; j++) {
            pointers[j] = null;
        }
        // make sure garbage collection runs
        fieldReference = null;
        pointers[0] = new BytePointer(chunkSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes < (chunks - 1) * chunkSize * byteSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes >= chunkSize * byteSize);
        System.out.println(Pointer.DeallocatorReference.totalBytes + " " + chunkSize * byteSize);
    }

    @Test public void testShortPointer() {
        System.out.println("ShortPointer");

        int shortSize = Short.SIZE / 8;
        assertEquals(shortSize, Loader.sizeof(ShortPointer.class));

        short[] array = new short[8192];
        ShortPointer pointer = new ShortPointer(array);
        assertEquals(array.length, pointer.limit());
        assertEquals(array.length, pointer.capacity());

        ShortPointer p2 = pointer.getPointer(10);
        assertEquals(pointer.address() + 20, p2.address());
        assertEquals(array.length - 10, p2.limit());
        assertEquals(array.length - 10, p2.capacity());
        Pointer p3 = pointer.getPointer(Pointer.class, 20);
        assertEquals(pointer.address() + 20, p3.address());
        assertEquals(2 * array.length - 20, p3.limit());
        assertEquals(2 * array.length - 20, p3.capacity());

        for (int i = 0; i < array.length; i++) {
            array[i] = (short)i;
            pointer.put(i, (short)i);
            assertEquals(array[i], pointer.get(i));
        }

        for (int i = 0; i < array.length; i++) {
            pointer.position(i).put(array[i]);
            assertEquals(array[i], pointer.position(i).get());
        }

        short[] array2 = new short[array.length];
        pointer.position(0).get(array2);
        assertArrayEquals(array, array2);

        ShortBuffer buffer = pointer.position(5 + 7).asBuffer();
        ShortBuffer arrayBuffer = ShortBuffer.wrap(array, 5, array.length - 5);
        assertTrue(buffer.compareTo((ShortBuffer)arrayBuffer.position(arrayBuffer.position() + 7)) == 0);
        assertEquals(pointer.address() + arrayBuffer.position() * pointer.sizeof(), new ShortPointer(buffer).address());
        assertEquals(pointer.limit() - arrayBuffer.position(), new ShortPointer(buffer).limit());
        assertEquals(pointer.capacity() - arrayBuffer.position(), new ShortPointer(buffer).capacity());

        int offset = 42;
        pointer.put(array, offset, array.length - offset);
        pointer.get(array2, offset, array.length - offset);
        assertArrayEquals(array, array2);

        ShortPointer pointer2 = new ShortPointer(array.length).zero();
        pointer2.position(10).limit(30).fill(0xFF);
        pointer2.position(20).put(pointer.position(20).limit(30));
        pointer.position(0);
        pointer2.position(0);
        for (int i = 0; i < array.length; i++) {
            if (i < 10) {
                assertEquals(0, pointer2.get(i));
            } else if (i < 20) {
                assertEquals((short)0xFFFF, pointer2.get(i));
            } else if (i < 30) {
                assertEquals(pointer.get(i), pointer2.get(i));
            } else {
                assertEquals(0, pointer2.get(i));
            }
        }

        assertEquals(maxBytes, Pointer.maxBytes);
        int chunks = 8;
        ShortPointer[] pointers = new ShortPointer[chunks];
        long chunkSize = Pointer.maxBytes / shortSize / chunks + 1;
        for (int j = 0; j < chunks - 1; j++) {
            pointers[j] = new ShortPointer(chunkSize);
        }
        assertTrue(Pointer.DeallocatorReference.totalBytes >= (chunks - 1) * chunkSize * shortSize);
        try {
            fieldReference = pointers;
            System.out.println("Note: OutOfMemoryError should get thrown here and printed below.");
            new ShortPointer(chunkSize);
            fail("OutOfMemoryError should have been thrown.");
        } catch (OutOfMemoryError e) {
            System.out.println(e);
            System.out.println(e.getCause());
        }
        for (int j = 0; j < chunks; j++) {
            pointers[j] = null;
        }
        // make sure garbage collection runs
        fieldReference = null;
        pointers[0] = new ShortPointer(chunkSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes < (chunks - 1) * chunkSize * shortSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes >= chunkSize * shortSize);
        System.out.println(Pointer.DeallocatorReference.totalBytes + " " + chunkSize * shortSize);
    }

    @Test public void testIntPointer() {
        System.out.println("IntPointer");

        int intSize = Integer.SIZE / 8;
        assertEquals(intSize, Loader.sizeof(IntPointer.class));

        int[] array = new int[8192];
        IntPointer pointer = new IntPointer(array);
        assertEquals(array.length, pointer.limit());
        assertEquals(array.length, pointer.capacity());

        IntPointer p2 = pointer.getPointer(10);
        assertEquals(pointer.address() + 40, p2.address());
        assertEquals(array.length - 10, p2.limit());
        assertEquals(array.length - 10, p2.capacity());
        Pointer p3 = pointer.getPointer(Pointer.class, 20);
        assertEquals(pointer.address() + 20, p3.address());
        assertEquals(4 * array.length - 20, p3.limit());
        assertEquals(4 * array.length - 20, p3.capacity());

        for (int i = 0; i < array.length; i++) {
            array[i] = i;
            pointer.put(i, i);
            assertEquals(array[i], pointer.get(i));
        }

        for (int i = 0; i < array.length; i++) {
            pointer.position(i).put(array[i]);
            assertEquals(array[i], pointer.position(i).get());
        }

        int[] array2 = new int[array.length];
        pointer.position(0).get(array2);
        assertArrayEquals(array, array2);

        IntBuffer buffer = pointer.position(5 + 7).asBuffer();
        IntBuffer arrayBuffer = IntBuffer.wrap(array, 5, array.length - 5);
        assertTrue(buffer.compareTo((IntBuffer)arrayBuffer.position(arrayBuffer.position() + 7)) == 0);
        assertEquals(pointer.address() + arrayBuffer.position() * pointer.sizeof(), new IntPointer(buffer).address());
        assertEquals(pointer.limit() - arrayBuffer.position(), new IntPointer(buffer).limit());
        assertEquals(pointer.capacity() - arrayBuffer.position(), new IntPointer(buffer).capacity());

        int offset = 42;
        pointer.put(array, offset, array.length - offset);
        pointer.get(array2, offset, array.length - offset);
        assertArrayEquals(array, array2);

        IntPointer pointer2 = new IntPointer(array.length).zero();
        pointer2.position(10).limit(30).fill(0xFF);
        pointer2.position(20).put(pointer.position(20).limit(30));
        pointer.position(0);
        pointer2.position(0);
        for (int i = 0; i < array.length; i++) {
            if (i < 10) {
                assertEquals(0, pointer2.get(i));
            } else if (i < 20) {
                assertEquals(0xFFFFFFFF, pointer2.get(i));
            } else if (i < 30) {
                assertEquals(pointer.get(i), pointer2.get(i));
            } else {
                assertEquals(0, pointer2.get(i));
            }
        }

        assertEquals(maxBytes, Pointer.maxBytes);
        int chunks = 8;
        IntPointer[] pointers = new IntPointer[chunks];
        long chunkSize = Pointer.maxBytes / intSize / chunks + 1;
        for (int j = 0; j < chunks - 1; j++) {
            pointers[j] = new IntPointer(chunkSize);
        }
        assertTrue(Pointer.DeallocatorReference.totalBytes >= (chunks - 1) * chunkSize * intSize);
        try {
            fieldReference = pointers;
            System.out.println("Note: OutOfMemoryError should get thrown here and printed below.");
            new IntPointer(chunkSize);
            fail("OutOfMemoryError should have been thrown.");
        } catch (OutOfMemoryError e) {
            System.out.println(e);
            System.out.println(e.getCause());
        }
        for (int j = 0; j < chunks; j++) {
            pointers[j] = null;
        }
        // make sure garbage collection runs
        fieldReference = null;
        pointers[0] = new IntPointer(chunkSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes < (chunks - 1) * chunkSize * intSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes >= chunkSize * intSize);
        System.out.println(Pointer.DeallocatorReference.totalBytes + " " + chunkSize * intSize);
    }

    @Test public void testLongPointer() {
        System.out.println("LongPointer");

        int longSize = Long.SIZE / 8;
        assertEquals(longSize, Loader.sizeof(LongPointer.class));

        long[] array = new long[8192];
        LongPointer pointer = new LongPointer(array);
        assertEquals(array.length, pointer.limit());
        assertEquals(array.length, pointer.capacity());

        LongPointer p2 = pointer.getPointer(10);
        assertEquals(pointer.address() + 80, p2.address());
        assertEquals(array.length - 10, p2.limit());
        assertEquals(array.length - 10, p2.capacity());
        Pointer p3 = pointer.getPointer(Pointer.class, 20);
        assertEquals(pointer.address() + 20, p3.address());
        assertEquals(8 * array.length - 20, p3.limit());
        assertEquals(8 * array.length - 20, p3.capacity());

        for (int i = 0; i < array.length; i++) {
            array[i] = i;
            pointer.put(i, i);
            assertEquals(array[i], pointer.get(i));
        }

        for (int i = 0; i < array.length; i++) {
            pointer.position(i).put(array[i]);
            assertEquals(array[i], pointer.position(i).get());
        }

        long[] array2 = new long[array.length];
        pointer.position(0).get(array2);
        assertArrayEquals(array, array2);

        LongBuffer buffer = pointer.position(5 + 7).asBuffer();
        LongBuffer arrayBuffer = LongBuffer.wrap(array, 5, array.length - 5);
        assertTrue(buffer.compareTo((LongBuffer)arrayBuffer.position(arrayBuffer.position() + 7)) == 0);
        assertEquals(pointer.address() + arrayBuffer.position() * pointer.sizeof(), new LongPointer(buffer).address());
        assertEquals(pointer.limit() - arrayBuffer.position(), new LongPointer(buffer).limit());
        assertEquals(pointer.capacity() - arrayBuffer.position(), new LongPointer(buffer).capacity());

        int offset = 42;
        pointer.put(array, offset, array.length - offset);
        pointer.get(array2, offset, array.length - offset);
        assertArrayEquals(array, array2);

        LongPointer pointer2 = new LongPointer(array.length).zero();
        pointer2.position(10).limit(30).fill(0xFF);
        pointer2.position(20).put(pointer.position(20).limit(30));
        pointer.position(0);
        pointer2.position(0);
        for (int i = 0; i < array.length; i++) {
            if (i < 10) {
                assertEquals(0, pointer2.get(i));
            } else if (i < 20) {
                assertEquals((long)-1, pointer2.get(i));
            } else if (i < 30) {
                assertEquals(pointer.get(i), pointer2.get(i));
            } else {
                assertEquals(0, pointer2.get(i));
            }
        }

        assertEquals(maxBytes, Pointer.maxBytes);
        int chunks = 8;
        LongPointer[] pointers = new LongPointer[chunks];
        long chunkSize = Pointer.maxBytes / longSize / chunks + 1;
        for (int j = 0; j < chunks - 1; j++) {
            pointers[j] = new LongPointer(chunkSize);
        }
        assertTrue(Pointer.DeallocatorReference.totalBytes >= (chunks - 1) * chunkSize * longSize);
        try {
            fieldReference = pointers;
            System.out.println("Note: OutOfMemoryError should get thrown here and printed below.");
            new LongPointer(chunkSize);
            fail("OutOfMemoryError should have been thrown.");
        } catch (OutOfMemoryError e) {
            System.out.println(e);
            System.out.println(e.getCause());
        }
        for (int j = 0; j < chunks; j++) {
            pointers[j] = null;
        }
        // make sure garbage collection runs
        fieldReference = null;
        pointers[0] = new LongPointer(chunkSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes < (chunks - 1) * chunkSize * longSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes >= chunkSize * longSize);
        System.out.println(Pointer.DeallocatorReference.totalBytes + " " + chunkSize * longSize);
    }

    @Test public void testFloatPointer() {
        System.out.println("FloatPointer");

        int floatSize = Float.SIZE / 8;
        assertEquals(floatSize, Loader.sizeof(FloatPointer.class));

        float[] array = new float[8192];
        FloatPointer pointer = new FloatPointer(array);
        assertEquals(array.length, pointer.limit());
        assertEquals(array.length, pointer.capacity());

        FloatPointer p2 = pointer.getPointer(10);
        assertEquals(pointer.address() + 40, p2.address());
        assertEquals(array.length - 10, p2.limit());
        assertEquals(array.length - 10, p2.capacity());
        Pointer p3 = pointer.getPointer(Pointer.class, 20);
        assertEquals(pointer.address() + 20, p3.address());
        assertEquals(4 * array.length - 20, p3.limit());
        assertEquals(4 * array.length - 20, p3.capacity());

        for (int i = 0; i < array.length; i++) {
            array[i] = i;
            pointer.put(i, i);
            assertEquals(array[i], pointer.get(i), 0);
        }

        for (int i = 0; i < array.length; i++) {
            pointer.position(i).put(array[i]);
            assertEquals(array[i], pointer.position(i).get(), 0);
        }

        float[] array2 = new float[array.length];
        pointer.position(0).get(array2);
        assertArrayEquals(array, array2, 0);

        FloatBuffer buffer = pointer.position(5 + 7).asBuffer();
        FloatBuffer arrayBuffer = FloatBuffer.wrap(array, 5, array.length - 5);
        assertTrue(buffer.compareTo((FloatBuffer)arrayBuffer.position(arrayBuffer.position() + 7)) == 0);
        assertEquals(pointer.address() + arrayBuffer.position() * pointer.sizeof(), new FloatPointer(buffer).address());
        assertEquals(pointer.limit() - arrayBuffer.position(), new FloatPointer(buffer).limit());
        assertEquals(pointer.capacity() - arrayBuffer.position(), new FloatPointer(buffer).capacity());

        int offset = 42;
        pointer.put(array, offset, array.length - offset);
        pointer.get(array2, offset, array.length - offset);
        assertArrayEquals(array, array2, 0);

        FloatPointer pointer2 = new FloatPointer(array.length).zero();
        pointer2.position(10).limit(30).fill(0xFF);
        pointer2.position(20).put(pointer.position(20).limit(30));
        pointer.position(0);
        pointer2.position(0);
        for (int i = 0; i < array.length; i++) {
            if (i < 10) {
                assertEquals(0, pointer2.get(i), 0);
            } else if (i < 20) {
                assertEquals(Float.intBitsToFloat(0xFFFFFFFF), pointer2.get(i), 0);
            } else if (i < 30) {
                assertEquals(pointer.get(i), pointer2.get(i), 0);
            } else {
                assertEquals(0, pointer2.get(i), 0);
            }
        }

        assertEquals(maxBytes, Pointer.maxBytes);
        int chunks = 8;
        FloatPointer[] pointers = new FloatPointer[chunks];
        long chunkSize = Pointer.maxBytes / floatSize / chunks + 1;
        for (int j = 0; j < chunks - 1; j++) {
            pointers[j] = new FloatPointer(chunkSize);
        }
        assertTrue(Pointer.DeallocatorReference.totalBytes >= (chunks - 1) * chunkSize * floatSize);
        try {
            fieldReference = pointers;
            System.out.println("Note: OutOfMemoryError should get thrown here and printed below.");
            new FloatPointer(chunkSize);
            fail("OutOfMemoryError should have been thrown.");
        } catch (OutOfMemoryError e) {
            System.out.println(e);
            System.out.println(e.getCause());
        }
        for (int j = 0; j < chunks; j++) {
            pointers[j] = null;
        }
        // make sure garbage collection runs
        fieldReference = null;
        pointers[0] = new FloatPointer(chunkSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes < (chunks - 1) * chunkSize * floatSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes >= chunkSize * floatSize);
        System.out.println(Pointer.DeallocatorReference.totalBytes + " " + chunkSize * floatSize);
    }

    @Test public void testDoublePointer() {
        System.out.println("DoublePointer");

        int doubleSize = Double.SIZE / 8;
        assertEquals(doubleSize, Loader.sizeof(DoublePointer.class));

        double[] array = new double[8192];
        DoublePointer pointer = new DoublePointer(array);
        assertEquals(array.length, pointer.limit());
        assertEquals(array.length, pointer.capacity());

        DoublePointer p2 = pointer.getPointer(10);
        assertEquals(pointer.address() + 80, p2.address());
        assertEquals(array.length - 10, p2.limit());
        assertEquals(array.length - 10, p2.capacity());
        Pointer p3 = pointer.getPointer(Pointer.class, 20);
        assertEquals(pointer.address() + 20, p3.address());
        assertEquals(8 * array.length - 20, p3.limit());
        assertEquals(8 * array.length - 20, p3.capacity());

        for (int i = 0; i < array.length; i++) {
            array[i] = i;
            pointer.put(i, i);
            assertEquals(array[i], pointer.get(i), 0);
        }

        for (int i = 0; i < array.length; i++) {
            pointer.position(i).put(array[i]);
            assertEquals(array[i], pointer.position(i).get(), 0);
        }

        double[] array2 = new double[array.length];
        pointer.position(0).get(array2);
        assertArrayEquals(array, array2, 0);

        DoubleBuffer buffer = pointer.position(5 + 7).asBuffer();
        DoubleBuffer arrayBuffer = DoubleBuffer.wrap(array, 5, array.length - 5);
        assertTrue(buffer.compareTo((DoubleBuffer)arrayBuffer.position(arrayBuffer.position() + 7)) == 0);
        assertEquals(pointer.address() + arrayBuffer.position() * pointer.sizeof(), new DoublePointer(buffer).address());
        assertEquals(pointer.limit() - arrayBuffer.position(), new DoublePointer(buffer).limit());
        assertEquals(pointer.capacity() - arrayBuffer.position(), new DoublePointer(buffer).capacity());

        int offset = 42;
        pointer.put(array, offset, array.length - offset);
        pointer.get(array2, offset, array.length - offset);
        assertArrayEquals(array, array2, 0);

        DoublePointer pointer2 = new DoublePointer(array.length).zero();
        pointer2.position(10).limit(30).fill(0xFF);
        pointer2.position(20).put(pointer.position(20).limit(30));
        pointer.position(0);
        pointer2.position(0);
        for (int i = 0; i < array.length; i++) {
            if (i < 10) {
                assertEquals(0, pointer2.get(i), 0);
            } else if (i < 20) {
                assertEquals(Double.longBitsToDouble((long)-1), pointer2.get(i), 0);
            } else if (i < 30) {
                assertEquals(pointer.get(i), pointer2.get(i), 0);
            } else {
                assertEquals(0, pointer2.get(i), 0);
            }
        }

        assertEquals(maxBytes, Pointer.maxBytes);
        int chunks = 8;
        DoublePointer[] pointers = new DoublePointer[chunks];
        long chunkSize = Pointer.maxBytes / doubleSize / chunks + 1;
        for (int j = 0; j < chunks - 1; j++) {
            pointers[j] = new DoublePointer(chunkSize);
        }
        assertTrue(Pointer.DeallocatorReference.totalBytes >= (chunks - 1) * chunkSize * doubleSize);
        try {
            fieldReference = pointers;
            System.out.println("Note: OutOfMemoryError should get thrown here and printed below.");
            new DoublePointer(chunkSize);
            fail("OutOfMemoryError should have been thrown.");
        } catch (OutOfMemoryError e) {
            System.out.println(e);
            System.out.println(e.getCause());
        }
        for (int j = 0; j < chunks; j++) {
            pointers[j] = null;
        }
        // make sure garbage collection runs
        fieldReference = null;
        pointers[0] = new DoublePointer(chunkSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes < (chunks - 1) * chunkSize * doubleSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes >= chunkSize * doubleSize);
        System.out.println(Pointer.DeallocatorReference.totalBytes + " " + chunkSize * doubleSize);
    }

    @Test public void testCharPointer() {
        System.out.println("CharPointer");

        int charSize = Character.SIZE / 8;
        assertEquals(charSize, Loader.sizeof(CharPointer.class));

        char[] array = new char[8192];
        CharPointer pointer = new CharPointer(array);
        assertEquals(array.length, pointer.limit());
        assertEquals(array.length, pointer.capacity());

        CharPointer p2 = pointer.getPointer(10);
        assertEquals(pointer.address() + 20, p2.address());
        assertEquals(array.length - 10, p2.limit());
        assertEquals(array.length - 10, p2.capacity());
        Pointer p3 = pointer.getPointer(Pointer.class, 20);
        assertEquals(pointer.address() + 20, p3.address());
        assertEquals(2 * array.length - 20, p3.limit());
        assertEquals(2 * array.length - 20, p3.capacity());

        for (int i = 0; i < array.length; i++) {
            array[i] = (char)i;
            pointer.put(i, (char)i);
            assertEquals(array[i], pointer.get(i));
        }

        for (int i = 0; i < array.length; i++) {
            pointer.position(i).put(array[i]);
            assertEquals(array[i], pointer.position(i).get());
        }

        char[] array2 = new char[array.length];
        pointer.position(0).get(array2);
        assertArrayEquals(array, array2);

        CharBuffer buffer = pointer.position(5 + 7).asBuffer();
        CharBuffer arrayBuffer = CharBuffer.wrap(array, 5, array.length - 5);
        assertTrue(buffer.compareTo((CharBuffer)arrayBuffer.position(arrayBuffer.position() + 7)) == 0);
        assertEquals(pointer.address() + arrayBuffer.position() * pointer.sizeof(), new CharPointer(buffer).address());
        assertEquals(pointer.limit() - arrayBuffer.position(), new CharPointer(buffer).limit());
        assertEquals(pointer.capacity() - arrayBuffer.position(), new CharPointer(buffer).capacity());

        int offset = 42;
        pointer.put(array, offset, array.length - offset);
        pointer.get(array2, offset, array.length - offset);
        assertArrayEquals(array, array2);

        CharPointer pointer2 = new CharPointer(array.length).zero();
        pointer2.position(10).limit(30).fill(0xFF);
        pointer2.position(20).put(pointer.position(20).limit(30));
        pointer.position(0);
        pointer2.position(0);
        for (int i = 0; i < array.length; i++) {
            if (i < 10) {
                assertEquals(0, pointer2.get(i));
            } else if (i < 20) {
                assertEquals((char)0xFFFF, pointer2.get(i));
            } else if (i < 30) {
                assertEquals(pointer.get(i), pointer2.get(i));
            } else {
                assertEquals(0, pointer2.get(i));
            }
        }

        assertEquals(maxBytes, Pointer.maxBytes);
        int chunks = 8;
        CharPointer[] pointers = new CharPointer[chunks];
        long chunkSize = Pointer.maxBytes / charSize / chunks + 1;
        for (int j = 0; j < chunks - 1; j++) {
            pointers[j] = new CharPointer(chunkSize);
        }
        assertTrue(Pointer.DeallocatorReference.totalBytes >= (chunks - 1) * chunkSize * charSize);
        try {
            fieldReference = pointers;
            System.out.println("Note: OutOfMemoryError should get thrown here and printed below.");
            new CharPointer(chunkSize);
            fail("OutOfMemoryError should have been thrown.");
        } catch (OutOfMemoryError e) {
            System.out.println(e);
            System.out.println(e.getCause());
        }
        for (int j = 0; j < chunks; j++) {
            pointers[j] = null;
        }
        // make sure garbage collection runs
        fieldReference = null;
        pointers[0] = new CharPointer(chunkSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes < (chunks - 1) * chunkSize * charSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes >= chunkSize * charSize);
        System.out.println(Pointer.DeallocatorReference.totalBytes + " " + chunkSize * charSize);
    }

    @Test public void testBooleanPointer() {
        System.out.println("BooleanPointer");

        int booleanSize = 1;
        assertEquals(booleanSize, Loader.sizeof(BooleanPointer.class));

        boolean[] array = new boolean[8192];
        BooleanPointer pointer = new BooleanPointer(array);
        assertEquals(array.length, pointer.limit());
        assertEquals(array.length, pointer.capacity());

        BooleanPointer p2 = pointer.getPointer(10);
        assertEquals(pointer.address() + 10, p2.address());
        assertEquals(array.length - 10, p2.limit());
        assertEquals(array.length - 10, p2.capacity());
        LongPointer p3 = pointer.getPointer(LongPointer.class, 20);
        assertEquals(pointer.address() + 8 * 20, p3.address());
        assertEquals(array.length / 8 - 20, p3.limit());
        assertEquals(array.length / 8 - 20, p3.capacity());

        for (int i = 0; i < array.length; i++) {
            array[i] = i % 2 != 0;
            pointer.put(i, i % 2 != 0);
            assertEquals(array[i], pointer.get(i));
        }

        for (int i = 0; i < array.length; i++) {
            pointer.position(i).put(array[i]);
            assertEquals(array[i], pointer.position(i).get());
        }

        boolean[] array2 = new boolean[array.length];
        pointer.position(0).get(array2);
        assertArrayEquals(array, array2);

        ByteBuffer buffer = pointer.asByteBuffer();
        assertEquals(pointer.address(), new BooleanPointer(buffer).address());
        assertEquals(pointer.position(), new BooleanPointer(buffer).position());
        assertEquals(pointer.limit(), new BooleanPointer(buffer).limit());
        assertEquals(pointer.capacity(), new BooleanPointer(buffer).capacity());

        int offset = 42;
        pointer.put(array, offset, array.length - offset);
        pointer.get(array2, offset, array.length - offset);
        assertArrayEquals(array, array2);

        BooleanPointer pointer2 = new BooleanPointer(array.length).zero();
        pointer2.position(10).limit(30).fill(0xFF);
        pointer2.position(20).put(pointer.position(20).limit(30));
        pointer.position(0);
        pointer2.position(0);
        for (int i = 0; i < array.length; i++) {
            if (i < 10) {
                assertEquals(false, pointer2.get(i));
            } else if (i < 20) {
                assertEquals(true, pointer2.get(i));
            } else if (i < 30) {
                assertEquals(pointer.get(i), pointer2.get(i));
            } else {
                assertEquals(false, pointer2.get(i));
            }
        }

        assertEquals(maxBytes, Pointer.maxBytes);
        int chunks = 8;
        BooleanPointer[] pointers = new BooleanPointer[chunks];
        long chunkSize = Pointer.maxBytes / booleanSize / chunks + 1;
        for (int j = 0; j < chunks - 1; j++) {
            pointers[j] = new BooleanPointer(chunkSize);
        }
        assertTrue(Pointer.DeallocatorReference.totalBytes >= (chunks - 1) * chunkSize * booleanSize);
        try {
            fieldReference = pointers;
            System.out.println("Note: OutOfMemoryError should get thrown here and printed below.");
            new BooleanPointer(chunkSize);
            fail("OutOfMemoryError should have been thrown.");
        } catch (OutOfMemoryError e) {
            System.out.println(e);
            System.out.println(e.getCause());
        }
        for (int j = 0; j < chunks; j++) {
            pointers[j] = null;
        }
        // make sure garbage collection runs
        fieldReference = null;
        pointers[0] = new BooleanPointer(chunkSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes < (chunks - 1) * chunkSize * booleanSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes >= chunkSize * booleanSize);
        System.out.println(Pointer.DeallocatorReference.totalBytes + " " + chunkSize * booleanSize);
    }

    @Test public void testPointerPointer() {
        System.out.println("PointerPointer");

        int pointerSize = Loader.sizeof(Pointer.class);
        assertEquals(pointerSize, Loader.sizeof(PointerPointer.class));

        Pointer[] array = new Pointer[8192];
        PointerPointer pointer = new PointerPointer(array);
        assertEquals(array.length, pointer.limit());
        assertEquals(array.length, pointer.capacity());

        PointerPointer p2 = pointer.getPointer(10);
        assertEquals(pointer.address() + 10 * pointerSize, p2.address());
        assertEquals(array.length - 10, p2.limit());
        assertEquals(array.length - 10, p2.capacity());
        BytePointer p3 = pointer.getPointer(BytePointer.class, 20);
        assertEquals(pointer.address() + 20, p3.address());
        assertEquals(pointerSize * array.length - 20, p3.limit());
        assertEquals(pointerSize * array.length - 20, p3.capacity());

        for (int i = 0; i < array.length; i++) {
            final int j = i;
            Pointer p = new Pointer() { { address = j; } };
            array[i] = p;
            pointer.put(i, p);
            assertEquals(array[i], pointer.get(i));
        }

        for (int i = 0; i < array.length; i++) {
            pointer.position(i).put(array[i]);
            assertEquals(array[i], pointer.position(i).get());
        }

        Pointer[] array2 = new Pointer[array.length];
        for (int i = 0; i < array2.length; i++) {
            array2[i] = pointer.position(0).get(i);
        }
        assertArrayEquals(array, array2);

        PointerPointer stringArray = new PointerPointer("one", "two");
        assertEquals(2, stringArray.capacity());
        assertEquals("one", stringArray.getString(0));
        assertEquals("two", stringArray.getString(1));

        int offset = 42;
        for (int i = 0; i < array.length - offset; i++) {
            pointer.put(i, array[offset + i]);
            array2[offset + i] = pointer.get(i);
        }
        assertArrayEquals(array, array2);

        PointerPointer pointer2 = new PointerPointer(array.length).zero();
        pointer2.position(10).limit(30).fill(0xFF);
        pointer.position(20);
        pointer2.position(20);
        for (int i = 0; i < 10; i++) {
            pointer.put(i, pointer2.get(i));
        }
        pointer.position(0);
        pointer2.position(0);
        for (int i = 0; i < array.length; i++) {
            if (i < 10) {
                assertNull(pointer2.get(i));
            } else if (i < 20) {
                assertEquals(0xFFFFFFFFL, pointer2.get(i).address() & 0xFFFFFFFFL);
            } else if (i < 30) {
                assertEquals(pointer.get(i), pointer2.get(i));
            } else {
                assertNull(pointer2.get(i));
            }
        }

        assertEquals(maxBytes, Pointer.maxBytes);
        int chunks = 8;
        PointerPointer[] pointers = new PointerPointer[chunks];
        long chunkSize = Pointer.maxBytes / pointerSize / chunks + 1;
        for (int j = 0; j < chunks - 1; j++) {
            pointers[j] = new PointerPointer(chunkSize);
        }
        assertTrue(Pointer.DeallocatorReference.totalBytes >= (chunks - 1) * chunkSize * pointerSize);
        try {
            fieldReference = pointers;
            System.out.println("Note: OutOfMemoryError should get thrown here and printed below.");
            new PointerPointer(chunkSize);
            fail("OutOfMemoryError should have been thrown.");
        } catch (OutOfMemoryError e) {
            System.out.println(e);
            System.out.println(e.getCause());
        }
        for (int j = 0; j < chunks; j++) {
            pointers[j] = null;
        }
        // make sure garbage collection runs
        fieldReference = null;
        pointers[0] = new PointerPointer(chunkSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes < (chunks - 1) * chunkSize * pointerSize);
        assertTrue(Pointer.DeallocatorReference.totalBytes >= chunkSize * pointerSize);
        System.out.println(Pointer.DeallocatorReference.totalBytes + " " + chunkSize * pointerSize);
    }

    @Test public void testDeallocator() throws InterruptedException {
        System.out.println("Deallocator");
        System.out.println("maxBytes = " + Pointer.maxBytes());
        System.out.println("maxPhysicalBytes = " + Pointer.maxPhysicalBytes());

        final boolean[] failed = { false };
        ExecutorService pool = Executors.newFixedThreadPool(24);
        long time = System.nanoTime();
        for (int i = 0; i < 24; i++) {
            pool.execute(new Runnable() {
                @Override public void run() {
                    try {
                        for (int i = 0; i < 2000; i++) {
                            BytePointer p = new BytePointer(2000000);
                            if (i % 3 == 0) {
                                p.retainReference();
                            }
                            if (Pointer.deallocatorThread == null) {
                                if (i % 2 == 0) {
                                    p.deallocate();
                                } else {
                                    p.releaseReference();
                                }
                            }
                        }
                    } catch (OutOfMemoryError e) {
                        failed[0] = true;
                        fail(e.getMessage());
                    }
                }
            });
        }
        pool.shutdown();
        pool.awaitTermination(1, TimeUnit.MINUTES);
        if (failed[0]) {
            fail("OutOfMemoryError should not have been thrown.");
        }
        if (Pointer.deallocatorThread == null) {
            assertEquals(0, Pointer.totalBytes());
        }
        System.out.println("Took " + (System.nanoTime() - time) / 1000000 + " ms");
    }

    @Test public void testPointerScope() {
        System.out.println("PointerScope");
        IntPointer outside = new IntPointer(1);
        IntPointer attached = new IntPointer(1), detached, inside, inside1, inside2, retained1, retained2, inside5;

        try (PointerScope scope = new PointerScope()) {
            scope.attach(attached);

            detached = new IntPointer(1);
            detached.retainReference();
            scope.detach(detached);

            inside = new IntPointer(1);
            try (PointerScope scope1 = new PointerScope()) {
                inside1 = new IntPointer(1);
                inside2 = new IntPointer(1);
            }
            try (PointerScope scope2 = new PointerScope()) {
                retained1 = new IntPointer(1);
                retained2 = new IntPointer(1);
                retained1.retainReference();
                scope.attach(retained2);
            }
            retained2.retainReference();
            inside5 = new IntPointer(1);
        }

        IntPointer outside2 = new IntPointer(1);

        assertFalse(outside.isNull());
        assertTrue(attached.isNull());
        assertFalse(detached.isNull());
        assertTrue(inside.isNull());
        assertTrue(inside1.isNull());
        assertTrue(inside2.isNull());
        assertFalse(retained1.isNull());
        assertFalse(retained2.isNull());
        assertTrue(inside5.isNull());
        assertFalse(outside2.isNull());

        outside.releaseReference();
        detached.releaseReference();
        retained1.releaseReference();
        retained2.releaseReference();
        outside2.releaseReference();

        assertTrue(outside.isNull());
        assertTrue(detached.isNull());
        assertTrue(retained1.isNull());
        assertTrue(retained2.isNull());
        assertTrue(outside2.isNull());

        IntPointer intPointer;
        FloatPointer floatPointer;
        try (PointerScope globalScope = new PointerScope()) {
            PointerScope extendedLocalScope = null;
            try (PointerScope localScope = new PointerScope(IntPointer.class)) {
                intPointer = new IntPointer(1);
                floatPointer = new FloatPointer(1);

                try {
                    System.out.println("Note: IllegalArgumentException should get thrown here and printed below.");
                    localScope.attach(floatPointer);
                    fail("IllegalArgumentException should have been thrown.");
                } catch (IllegalArgumentException e) {
                    System.out.println(e);
                }
                extendedLocalScope = localScope.extend();
            }
            assertFalse(intPointer.isNull());
            extendedLocalScope.close();
            assertTrue(intPointer.isNull());
            assertFalse(floatPointer.isNull());
        }
        assertTrue(floatPointer.isNull());
    }

    @Test public void testParseBytesWithRelativeUnits() {
        System.out.println("ParseBytesWithRelativeUnits");
        long arbitraryAmountOfMemory = 300000;

        assertEquals(0, Pointer.parseBytes("0%", arbitraryAmountOfMemory));
        assertEquals(arbitraryAmountOfMemory * 10 / 100, Pointer.parseBytes("10%", arbitraryAmountOfMemory));
        try {
            System.out.println("Note: NumberFormatException should get thrown here and printed below.");
            Pointer.parseBytes("%", arbitraryAmountOfMemory);
            fail("NumberFormatException should have been thrown.");
        } catch (NumberFormatException e) {
            System.out.println(e);
        }
    }

    @Test public void testNoFileBytes() throws Exception {
        System.out.println("NoFileBytes");

        System.gc();
        Thread.sleep(100);
        long bytesBefore = Pointer.physicalBytes();

        Path file = Files.createTempFile("javacpp", "tmp");
        FileChannel channel = FileChannel.open(file,
                StandardOpenOption.CREATE, StandardOpenOption.DELETE_ON_CLOSE,
                StandardOpenOption.READ, StandardOpenOption.WRITE);
        MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 100_000_000);
        for (int i = 0; i < 100_000_000; i++) {
            buffer.put(i, (byte)i);
        }

        System.gc();
        Thread.sleep(100);
        long bytesAfter = Pointer.physicalBytes();

        System.out.println(bytesBefore + " " + bytesAfter);
        assertTrue(Math.abs(bytesAfter - bytesBefore) < 100_000_000 + buffer.get(0));
    }
}