| /* | |
| * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| /* | |
| * @test | |
| * @bug 8033148 8141409 | |
| * @summary tests for array equals and compare | |
| * @run testng ArraysEqCmpTest | |
| */ | |
| import org.testng.Assert; | |
| import org.testng.annotations.DataProvider; | |
| import org.testng.annotations.Test; | |
| import java.lang.invoke.MethodHandle; | |
| import java.lang.invoke.MethodHandles; | |
| import java.lang.invoke.MethodType; | |
| import java.lang.reflect.Array; | |
| import java.util.Arrays; | |
| import java.util.Comparator; | |
| import java.util.HashMap; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Objects; | |
| import java.util.function.BiFunction; | |
| import java.util.function.LongFunction; | |
| import java.util.stream.IntStream; | |
| public class ArraysEqCmpTest { | |
| // Maximum width in bits | |
| static final int MAX_WIDTH = 512; | |
| static final Map<Class, Integer> typeToWidth; | |
| static { | |
| typeToWidth = new HashMap<>(); | |
| typeToWidth.put(boolean.class, Byte.SIZE); | |
| typeToWidth.put(byte.class, Byte.SIZE); | |
| typeToWidth.put(short.class, Short.SIZE); | |
| typeToWidth.put(char.class, Character.SIZE); | |
| typeToWidth.put(int.class, Integer.SIZE); | |
| typeToWidth.put(long.class, Long.SIZE); | |
| typeToWidth.put(float.class, Float.SIZE); | |
| typeToWidth.put(double.class, Double.SIZE); | |
| typeToWidth.put(Object.class, Integer.SIZE); // @@@ 32 or 64? | |
| } | |
| static int arraySizeFor(Class<?> type) { | |
| type = type.isPrimitive() ? type : Object.class; | |
| return 4 * MAX_WIDTH / typeToWidth.get(type); | |
| } | |
| static abstract class ArrayType<T> { | |
| final Class<?> arrayType; | |
| final Class<?> componentType; | |
| final boolean unsigned; | |
| final MethodHandle cpy; | |
| final MethodHandle eq; | |
| final MethodHandle eqr; | |
| final MethodHandle cmp; | |
| final MethodHandle cmpr; | |
| final MethodHandle mm; | |
| final MethodHandle mmr; | |
| final MethodHandle getter; | |
| final MethodHandle toString; | |
| public ArrayType(Class<T> arrayType) { | |
| this(arrayType, false); | |
| } | |
| public ArrayType(Class<T> arrayType, boolean unsigned) { | |
| this.arrayType = arrayType; | |
| this.componentType = arrayType.getComponentType(); | |
| this.unsigned = unsigned; | |
| try { | |
| MethodHandles.Lookup l = MethodHandles.lookup(); | |
| getter = MethodHandles.arrayElementGetter(arrayType); | |
| if (componentType.isPrimitive()) { | |
| cpy = l.findStatic(Arrays.class, "copyOfRange", | |
| MethodType.methodType(arrayType, arrayType, int.class, int.class)); | |
| MethodType eqt = MethodType.methodType( | |
| boolean.class, arrayType, arrayType); | |
| MethodType eqrt = MethodType.methodType( | |
| boolean.class, arrayType, int.class, int.class, arrayType, int.class, int.class); | |
| eq = l.findStatic(Arrays.class, "equals", eqt); | |
| eqr = l.findStatic(Arrays.class, "equals", eqrt); | |
| String compareName = unsigned ? "compareUnsigned" : "compare"; | |
| cmp = l.findStatic(Arrays.class, compareName, | |
| eqt.changeReturnType(int.class)); | |
| cmpr = l.findStatic(Arrays.class, compareName, | |
| eqrt.changeReturnType(int.class)); | |
| mm = l.findStatic(Arrays.class, "mismatch", | |
| eqt.changeReturnType(int.class)); | |
| mmr = l.findStatic(Arrays.class, "mismatch", | |
| eqrt.changeReturnType(int.class)); | |
| toString = l.findStatic(Arrays.class, "toString", | |
| MethodType.methodType(String.class, arrayType)); | |
| } | |
| else { | |
| cpy = l.findStatic(Arrays.class, "copyOfRange", | |
| MethodType.methodType(Object[].class, Object[].class, int.class, int.class)); | |
| MethodType eqt = MethodType.methodType( | |
| boolean.class, Object[].class, Object[].class); | |
| MethodType eqrt = MethodType.methodType( | |
| boolean.class, Object[].class, int.class, int.class, Object[].class, int.class, int.class); | |
| eq = l.findStatic(Arrays.class, "equals", eqt); | |
| eqr = l.findStatic(Arrays.class, "equals", eqrt); | |
| MethodType cmpt = MethodType.methodType( | |
| int.class, Comparable[].class, Comparable[].class); | |
| MethodType cmprt = MethodType.methodType( | |
| int.class, Comparable[].class, int.class, int.class, Comparable[].class, int.class, int.class); | |
| cmp = l.findStatic(Arrays.class, "compare", cmpt); | |
| cmpr = l.findStatic(Arrays.class, "compare", cmprt); | |
| mm = l.findStatic(Arrays.class, "mismatch", | |
| eqt.changeReturnType(int.class)); | |
| mmr = l.findStatic(Arrays.class, "mismatch", | |
| eqrt.changeReturnType(int.class)); | |
| toString = l.findStatic(Arrays.class, "toString", | |
| MethodType.methodType(String.class, Object[].class)); | |
| } | |
| } | |
| catch (Exception e) { | |
| throw new Error(e); | |
| } | |
| } | |
| @Override | |
| public String toString() { | |
| String s = arrayType.getCanonicalName(); | |
| return unsigned ? "unsigned " + s : s; | |
| } | |
| Object construct(int length) { | |
| return Array.newInstance(componentType, length); | |
| } | |
| Object copyOf(Object a) { | |
| return copyOf(a, 0, Array.getLength(a)); | |
| } | |
| Object copyOf(Object a, int from, int to) { | |
| try { | |
| return (Object) cpy.invoke(a, from, to); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| Object get(Object a, int i) { | |
| try { | |
| return (Object) getter.invoke(a, i); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| abstract void set(Object a, int i, Object v); | |
| boolean equals(Object a, Object b) { | |
| try { | |
| return (boolean) eq.invoke(a, b); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| boolean equals(Object a, int aFromIndex, int aToIndex, | |
| Object b, int bFromIndex, int bToIndex) { | |
| try { | |
| return (boolean) eqr.invoke(a, aFromIndex, aToIndex, b, bFromIndex, bToIndex); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| int compare(Object a, Object b) { | |
| try { | |
| return (int) cmp.invoke(a, b); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| int compare(Object a, int aFromIndex, int aToIndex, | |
| Object b, int bFromIndex, int bToIndex) { | |
| try { | |
| return (int) cmpr.invoke(a, aFromIndex, aToIndex, b, bFromIndex, bToIndex); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| int mismatch(Object a, Object b) { | |
| try { | |
| return (int) mm.invoke(a, b); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| int mismatch(Object a, int aFromIndex, int aToIndex, | |
| Object b, int bFromIndex, int bToIndex) { | |
| try { | |
| return (int) mmr.invoke(a, aFromIndex, aToIndex, b, bFromIndex, bToIndex); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| String toString(Object a) { | |
| try { | |
| return (String) toString.invoke(a); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| static class BoxedIntegers extends ArrayType<Integer[]> { | |
| public BoxedIntegers() { | |
| super(Integer[].class); | |
| } | |
| @Override | |
| void set(Object a, int i, Object v) { | |
| // Ensure unique reference | |
| ((Integer[]) a)[i] = v != null ? new Integer((Integer) v) : null; | |
| } | |
| } | |
| static class BoxedIntegersWithReverseComparator extends BoxedIntegers { | |
| final Comparator<Integer> c = (a, b) -> { | |
| // Nulls sort after non-nulls | |
| if (a == null || b == null) | |
| return a == null ? b == null ? 0 : 1 : -1; | |
| return Integer.compare(b, a); | |
| }; | |
| final MethodHandle eqc; | |
| final MethodHandle eqcr; | |
| final MethodHandle cmpc; | |
| final MethodHandle cmpcr; | |
| final MethodHandle mismatchc; | |
| final MethodHandle mismatchcr; | |
| public BoxedIntegersWithReverseComparator() { | |
| try { | |
| MethodHandles.Lookup l = MethodHandles.lookup(); | |
| MethodType cmpt = MethodType.methodType( | |
| int.class, Object[].class, Object[].class, Comparator.class); | |
| MethodType cmprt = MethodType.methodType( | |
| int.class, Object[].class, int.class, int.class, | |
| Object[].class, int.class, int.class, Comparator.class); | |
| eqc = l.findStatic(Arrays.class, "equals", cmpt.changeReturnType(boolean.class)); | |
| eqcr = l.findStatic(Arrays.class, "equals", cmprt.changeReturnType(boolean.class)); | |
| cmpc = l.findStatic(Arrays.class, "compare", cmpt); | |
| cmpcr = l.findStatic(Arrays.class, "compare", cmprt); | |
| mismatchc = l.findStatic(Arrays.class, "mismatch", cmpt); | |
| mismatchcr = l.findStatic(Arrays.class, "mismatch", cmprt); | |
| } | |
| catch (Exception e) { | |
| throw new Error(e); | |
| } | |
| } | |
| @Override | |
| boolean equals(Object a, Object b) { | |
| try { | |
| return (boolean) eqc.invoke(a, b, c); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| @Override | |
| boolean equals(Object a, int aFromIndex, int aToIndex, | |
| Object b, int bFromIndex, int bToIndex) { | |
| try { | |
| return (boolean) eqcr.invoke(a, aFromIndex, aToIndex, b, bFromIndex, bToIndex, c); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| @Override | |
| int compare(Object a, Object b) { | |
| try { | |
| return (int) cmpc.invoke(a, b, c); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| @Override | |
| int compare(Object a, int aFromIndex, int aToIndex, | |
| Object b, int bFromIndex, int bToIndex) { | |
| try { | |
| return (int) cmpcr.invoke(a, aFromIndex, aToIndex, b, bFromIndex, bToIndex, c); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| @Override | |
| int mismatch(Object a, Object b) { | |
| try { | |
| return (int) mismatchc.invoke(a, b, c); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| @Override | |
| int mismatch(Object a, int aFromIndex, int aToIndex, | |
| Object b, int bFromIndex, int bToIndex) { | |
| try { | |
| return (int) mismatchcr.invoke(a, aFromIndex, aToIndex, b, bFromIndex, bToIndex, c); | |
| } | |
| catch (RuntimeException | Error e) { | |
| throw e; | |
| } | |
| catch (Throwable t) { | |
| throw new Error(t); | |
| } | |
| } | |
| @Override | |
| public String toString() { | |
| return arrayType.getCanonicalName() + " with Comparator"; | |
| } | |
| } | |
| static class Booleans extends ArrayType<boolean[]> { | |
| public Booleans() { | |
| super(boolean[].class); | |
| } | |
| @Override | |
| void set(Object a, int i, Object v) { | |
| boolean pv; | |
| if (v instanceof Boolean) { | |
| pv = (Boolean) v; | |
| } | |
| else if (v instanceof Integer) { | |
| pv = ((Integer) v) >= 0; | |
| } | |
| else throw new IllegalStateException(); | |
| ((boolean[]) a)[i] = pv; | |
| } | |
| } | |
| static class Bytes extends ArrayType<byte[]> { | |
| public Bytes(boolean unsigned) { | |
| super(byte[].class, unsigned); | |
| } | |
| @Override | |
| void set(Object a, int i, Object v) { | |
| byte pv; | |
| if (v instanceof Byte) { | |
| pv = (Byte) v; | |
| } | |
| else if (v instanceof Integer) { | |
| pv = ((Integer) v).byteValue(); | |
| } | |
| else throw new IllegalStateException(); | |
| ((byte[]) a)[i] = pv; | |
| } | |
| } | |
| static class Characters extends ArrayType<char[]> { | |
| public Characters() { | |
| super(char[].class); | |
| } | |
| @Override | |
| void set(Object a, int i, Object v) { | |
| char pv; | |
| if (v instanceof Character) { | |
| pv = (Character) v; | |
| } | |
| else if (v instanceof Integer) { | |
| pv = (char) ((Integer) v).intValue(); | |
| } | |
| else throw new IllegalStateException(); | |
| ((char[]) a)[i] = pv; | |
| } | |
| } | |
| static class Shorts extends ArrayType<short[]> { | |
| public Shorts(boolean unsigned) { | |
| super(short[].class, unsigned); | |
| } | |
| @Override | |
| void set(Object a, int i, Object v) { | |
| short pv; | |
| if (v instanceof Short) { | |
| pv = (Short) v; | |
| } | |
| else if (v instanceof Integer) { | |
| pv = ((Integer) v).shortValue(); | |
| } | |
| else throw new IllegalStateException(); | |
| ((short[]) a)[i] = pv; | |
| } | |
| } | |
| static class Integers extends ArrayType<int[]> { | |
| public Integers(boolean unsigned) { | |
| super(int[].class, unsigned); | |
| } | |
| @Override | |
| void set(Object a, int i, Object v) { | |
| int pv; | |
| if (v instanceof Integer) { | |
| pv = ((Integer) v).shortValue(); | |
| } | |
| else throw new IllegalStateException(); | |
| ((int[]) a)[i] = pv; | |
| } | |
| } | |
| static class Longs extends ArrayType<long[]> { | |
| public Longs(boolean unsigned) { | |
| super(long[].class, unsigned); | |
| } | |
| @Override | |
| void set(Object a, int i, Object v) { | |
| long pv; | |
| if (v instanceof Long) { | |
| pv = (Long) v; | |
| } | |
| else if (v instanceof Integer) { | |
| pv = ((Integer) v).longValue(); | |
| } | |
| else throw new IllegalStateException(); | |
| ((long[]) a)[i] = pv; | |
| } | |
| } | |
| static class Floats extends ArrayType<float[]> { | |
| public Floats() { | |
| super(float[].class); | |
| } | |
| @Override | |
| void set(Object a, int i, Object v) { | |
| float pv; | |
| if (v instanceof Float) { | |
| pv = (Float) v; | |
| } | |
| else if (v instanceof Integer) { | |
| pv = ((Integer) v).floatValue(); | |
| } | |
| else throw new IllegalStateException(); | |
| ((float[]) a)[i] = pv; | |
| } | |
| } | |
| static class Doubles extends ArrayType<double[]> { | |
| public Doubles() { | |
| super(double[].class); | |
| } | |
| @Override | |
| void set(Object a, int i, Object v) { | |
| double pv; | |
| if (v instanceof Double) { | |
| pv = (Double) v; | |
| } | |
| else if (v instanceof Integer) { | |
| pv = ((Integer) v).doubleValue(); | |
| } | |
| else throw new IllegalStateException(); | |
| ((double[]) a)[i] = pv; | |
| } | |
| } | |
| } | |
| static Object[][] arrayTypes; | |
| @DataProvider | |
| public static Object[][] arrayTypesProvider() { | |
| if (arrayTypes == null) { | |
| arrayTypes = new Object[][]{ | |
| new Object[]{new ArrayType.BoxedIntegers()}, | |
| new Object[]{new ArrayType.BoxedIntegersWithReverseComparator()}, | |
| new Object[]{new ArrayType.Booleans()}, | |
| new Object[]{new ArrayType.Bytes(false)}, | |
| new Object[]{new ArrayType.Bytes(true)}, | |
| new Object[]{new ArrayType.Characters()}, | |
| new Object[]{new ArrayType.Shorts(false)}, | |
| new Object[]{new ArrayType.Shorts(true)}, | |
| new Object[]{new ArrayType.Integers(false)}, | |
| new Object[]{new ArrayType.Integers(true)}, | |
| new Object[]{new ArrayType.Longs(false)}, | |
| new Object[]{new ArrayType.Longs(true)}, | |
| new Object[]{new ArrayType.Floats()}, | |
| new Object[]{new ArrayType.Doubles()}, | |
| }; | |
| } | |
| return arrayTypes; | |
| } | |
| static Object[][] floatArrayTypes; | |
| @DataProvider | |
| public static Object[][] floatArrayTypesProvider() { | |
| if (floatArrayTypes == null) { | |
| LongFunction<Object> bTof = rb -> Float.intBitsToFloat((int) rb); | |
| LongFunction<Object> bToD = Double::longBitsToDouble; | |
| floatArrayTypes = new Object[][]{ | |
| new Object[]{new ArrayType.Floats(), 0x7fc00000L, 0x7f800001L, bTof}, | |
| new Object[]{new ArrayType.Doubles(), 0x7ff8000000000000L, 0x7ff0000000000001L, bToD}, | |
| }; | |
| } | |
| return floatArrayTypes; | |
| } | |
| static Object[][] objectArrayTypes; | |
| @DataProvider | |
| public static Object[][] objectArrayTypesProvider() { | |
| if (objectArrayTypes == null) { | |
| LongFunction<Object> bTof = rb -> Float.intBitsToFloat((int) rb); | |
| LongFunction<Object> bToD = Double::longBitsToDouble; | |
| objectArrayTypes = new Object[][]{ | |
| new Object[]{new ArrayType.BoxedIntegers()}, | |
| new Object[]{new ArrayType.BoxedIntegersWithReverseComparator()}, | |
| }; | |
| } | |
| return objectArrayTypes; | |
| } | |
| static Object[][] signedUnsignedArrayTypes; | |
| @DataProvider | |
| public static Object[][] signedUnsignedArrayTypes() { | |
| if (signedUnsignedArrayTypes == null) { | |
| signedUnsignedArrayTypes = new Object[][]{ | |
| new Object[]{new ArrayType.Bytes(false), new ArrayType.Bytes(true)}, | |
| new Object[]{new ArrayType.Shorts(false), new ArrayType.Shorts(true)}, | |
| new Object[]{new ArrayType.Integers(false), new ArrayType.Integers(true)}, | |
| new Object[]{new ArrayType.Longs(false), new ArrayType.Longs(true)}, | |
| }; | |
| } | |
| return signedUnsignedArrayTypes; | |
| } | |
| // Equality and comparison tests | |
| @Test(dataProvider = "arrayTypesProvider") | |
| public void testArray(ArrayType<?> arrayType) { | |
| BiFunction<ArrayType<?>, Integer, Object> constructor = (at, s) -> { | |
| Object a = at.construct(s); | |
| for (int x = 0; x < s; x++) { | |
| at.set(a, x, x % 8); | |
| } | |
| return a; | |
| }; | |
| BiFunction<ArrayType<?>, Object, Object> cloner = (at, a) -> | |
| constructor.apply(at, Array.getLength(a)); | |
| testArrayType(arrayType, constructor, cloner); | |
| } | |
| @Test(dataProvider = "floatArrayTypesProvider") | |
| public void testPrimitiveFloatArray( | |
| ArrayType<?> arrayType, | |
| long canonicalNanRawBits, long nonCanonicalNanRawBits, | |
| LongFunction<Object> bitsToFloat) { | |
| Object canonicalNan = bitsToFloat.apply(canonicalNanRawBits); | |
| // If conversion is a signalling NaN it may be subject to conversion to a | |
| // quiet NaN on some processors, even if a copy is performed | |
| // The tests assume that if conversion occurs it does not convert to the | |
| // canonical NaN | |
| Object nonCanonicalNan = bitsToFloat.apply(nonCanonicalNanRawBits); | |
| BiFunction<ArrayType<?>, Integer, Object> canonicalNaNs = (at, s) -> { | |
| Object a = at.construct(s); | |
| for (int x = 0; x < s; x++) { | |
| at.set(a, x, canonicalNan); | |
| } | |
| return a; | |
| }; | |
| BiFunction<ArrayType<?>, Object, Object> nonCanonicalNaNs = (at, a) -> { | |
| int s = Array.getLength(a); | |
| Object ac = at.construct(s); | |
| for (int x = 0; x < s; x++) { | |
| at.set(ac, x, nonCanonicalNan); | |
| } | |
| return ac; | |
| }; | |
| BiFunction<ArrayType<?>, Object, Object> halfNonCanonicalNaNs = (at, a) -> { | |
| int s = Array.getLength(a); | |
| Object ac = at.construct(s); | |
| for (int x = 0; x < s / 2; x++) { | |
| at.set(ac, x, nonCanonicalNan); | |
| } | |
| for (int x = s / 2; x < s; x++) { | |
| at.set(ac, x, 1); | |
| } | |
| return ac; | |
| }; | |
| testArrayType(arrayType, canonicalNaNs, nonCanonicalNaNs); | |
| testArrayType(arrayType, canonicalNaNs, halfNonCanonicalNaNs); | |
| } | |
| @Test(dataProvider = "objectArrayTypesProvider") | |
| public void testNullElementsInObjectArray(ArrayType<?> arrayType) { | |
| BiFunction<ArrayType<?>, Object, Object> cloner = ArrayType::copyOf; | |
| // All nulls | |
| testArrayType(arrayType, | |
| (at, s) -> { | |
| Object a = at.construct(s); | |
| for (int x = 0; x < s; x++) { | |
| at.set(a, x, null); | |
| } | |
| return a; | |
| }, | |
| cloner); | |
| // Some nulls | |
| testArrayType(arrayType, | |
| (at, s) -> { | |
| Object a = at.construct(s); | |
| for (int x = 0; x < s; x++) { | |
| int v = x % 8; | |
| at.set(a, x, v == 0 ? null : v); | |
| } | |
| return a; | |
| }, | |
| cloner); | |
| Integer[] a = new Integer[]{null, 0}; | |
| Integer[] b = new Integer[]{0, 0}; | |
| Assert.assertTrue(Arrays.compare(a, b) < 0); | |
| Assert.assertTrue(Arrays.compare(b, a) > 0); | |
| } | |
| @Test(dataProvider = "objectArrayTypesProvider") | |
| public void testSameRefElementsInObjectArray(ArrayType<?> arrayType) { | |
| BiFunction<ArrayType<?>, Object, Object> cloner = ArrayType::copyOf; | |
| // One ref | |
| Integer one = 1; | |
| testArrayType(arrayType, | |
| (at, s) -> { | |
| Integer[] a = (Integer[]) at.construct(s); | |
| for (int x = 0; x < s; x++) { | |
| a[x] = one; | |
| } | |
| return a; | |
| }, | |
| cloner); | |
| // All ref | |
| testArrayType(arrayType, | |
| (at, s) -> { | |
| Integer[] a = (Integer[]) at.construct(s); | |
| for (int x = 0; x < s; x++) { | |
| a[x] = Integer.valueOf(s); | |
| } | |
| return a; | |
| }, | |
| cloner); | |
| // Some same ref | |
| testArrayType(arrayType, | |
| (at, s) -> { | |
| Integer[] a = (Integer[]) at.construct(s); | |
| for (int x = 0; x < s; x++) { | |
| int v = x % 8; | |
| a[x] = v == 1 ? one : new Integer(v); | |
| } | |
| return a; | |
| }, | |
| cloner); | |
| } | |
| @Test(dataProvider = "signedUnsignedArrayTypes") | |
| public void testSignedUnsignedArray(ArrayType<?> sat, ArrayType<?> uat) { | |
| BiFunction<ArrayType<?>, Integer, Object> constructor = (at, s) -> { | |
| Object a = at.construct(s); | |
| for (int x = 0; x < s; x++) { | |
| at.set(a, x, 1); | |
| } | |
| return a; | |
| }; | |
| int n = arraySizeFor(sat.componentType); | |
| for (int s : ranges(0, n)) { | |
| Object a = constructor.apply(sat, s); | |
| for (int aFrom : ranges(0, s)) { | |
| for (int aTo : ranges(aFrom, s)) { | |
| int aLength = aTo - aFrom; | |
| if (aLength > 0) { | |
| for (int i = aFrom; i < aTo; i++) { | |
| Object ac = sat.copyOf(a); | |
| // Create common prefix with a length of i - aFrom | |
| sat.set(ac, i, -1); | |
| int sc = sat.compare(ac, aFrom, aTo, a, aFrom, aTo); | |
| int uc = uat.compare(ac, aFrom, aTo, a, aFrom, aTo); | |
| Assert.assertTrue(sc < 0); | |
| Assert.assertTrue(uc > 0); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void testArrayType(ArrayType<?> at, | |
| BiFunction<ArrayType<?>, Integer, Object> constructor, | |
| BiFunction<ArrayType<?>, Object, Object> cloner) { | |
| int n = arraySizeFor(at.componentType); | |
| for (int s : ranges(0, n)) { | |
| Object a = constructor.apply(at, s); | |
| Object b = cloner.apply(at, a); | |
| for (int aFrom : ranges(0, s)) { | |
| for (int aTo : ranges(aFrom, s)) { | |
| int aLength = aTo - aFrom; | |
| for (int bFrom : ranges(0, s)) { | |
| for (int bTo : ranges(bFrom, s)) { | |
| int bLength = bTo - bFrom; | |
| Object anr = at.copyOf(a, aFrom, aTo); | |
| Object bnr = at.copyOf(b, bFrom, bTo); | |
| boolean eq = isEqual(at, a, aFrom, aTo, b, bFrom, bTo); | |
| Assert.assertEquals(at.equals(a, aFrom, aTo, b, bFrom, bTo), eq); | |
| Assert.assertEquals(at.equals(b, bFrom, bTo, a, aFrom, aTo), eq); | |
| Assert.assertEquals(at.equals(anr, bnr), eq); | |
| Assert.assertEquals(at.equals(bnr, anr), eq); | |
| if (eq) { | |
| Assert.assertEquals(at.compare(a, aFrom, aTo, b, bFrom, bTo), 0); | |
| Assert.assertEquals(at.compare(b, bFrom, bTo, a, aFrom, aTo), 0); | |
| Assert.assertEquals(at.compare(anr, bnr), 0); | |
| Assert.assertEquals(at.compare(bnr, anr), 0); | |
| Assert.assertEquals(at.mismatch(a, aFrom, aTo, b, bFrom, bTo), -1); | |
| Assert.assertEquals(at.mismatch(b, bFrom, bTo, a, aFrom, aTo), -1); | |
| Assert.assertEquals(at.mismatch(anr, bnr), -1); | |
| Assert.assertEquals(at.mismatch(bnr, anr), -1); | |
| } | |
| else { | |
| int aCb = at.compare(a, aFrom, aTo, b, bFrom, bTo); | |
| int bCa = at.compare(b, bFrom, bTo, a, aFrom, aTo); | |
| int v = Integer.signum(aCb) * Integer.signum(bCa); | |
| Assert.assertTrue(v == -1); | |
| int anrCbnr = at.compare(anr, bnr); | |
| int bnrCanr = at.compare(bnr, anr); | |
| Assert.assertEquals(anrCbnr, aCb); | |
| Assert.assertEquals(bnrCanr, bCa); | |
| int aMb = at.mismatch(a, aFrom, aTo, b, bFrom, bTo); | |
| int bMa = at.mismatch(b, bFrom, bTo, a, aFrom, aTo); | |
| int anrMbnr = at.mismatch(anr, bnr); | |
| int bnrManr = at.mismatch(bnr, anr); | |
| Assert.assertNotEquals(aMb, -1); | |
| Assert.assertEquals(aMb, bMa); | |
| Assert.assertNotEquals(anrMbnr, -1); | |
| Assert.assertEquals(anrMbnr, bnrManr); | |
| Assert.assertEquals(aMb, anrMbnr); | |
| Assert.assertEquals(bMa, bnrManr); | |
| // Common or proper prefix | |
| Assert.assertTrue(at.equals(a, aFrom, aFrom + aMb, b, bFrom, bFrom + aMb)); | |
| if (aMb < Math.min(aLength, bLength)) { | |
| // Common prefix | |
| Assert.assertFalse(isEqual(at, a, aFrom + aMb, b, bFrom + aMb)); | |
| } | |
| } | |
| } | |
| } | |
| if (aLength > 0) { | |
| for (int i = aFrom; i < aTo; i++) { | |
| Object ac = at.copyOf(a); | |
| // Create common prefix with a length of i - aFrom | |
| at.set(ac, i, -1); | |
| Object acnr = at.copyOf(ac, aFrom, aTo); | |
| Object anr = at.copyOf(a, aFrom, aTo); | |
| Assert.assertFalse(at.equals(ac, aFrom, aTo, a, aFrom, aTo)); | |
| Assert.assertFalse(at.equals(acnr, anr)); | |
| int acCa = at.compare(ac, aFrom, aTo, a, aFrom, aTo); | |
| int aCac = at.compare(a, aFrom, aTo, ac, aFrom, aTo); | |
| int v = Integer.signum(acCa) * Integer.signum(aCac); | |
| Assert.assertTrue(v == -1); | |
| int acnrCanr = at.compare(acnr, anr); | |
| int anrCacnr = at.compare(anr, acnr); | |
| Assert.assertEquals(acnrCanr, acCa); | |
| Assert.assertEquals(anrCacnr, aCac); | |
| int acMa = at.mismatch(ac, aFrom, aTo, a, aFrom, aTo); | |
| int aMac = at.mismatch(a, aFrom, aTo, ac, aFrom, aTo); | |
| Assert.assertEquals(acMa, aMac); | |
| Assert.assertEquals(acMa, i - aFrom); | |
| int acnrManr = at.mismatch(acnr, anr); | |
| int anrMacnr = at.mismatch(anr, acnr); | |
| Assert.assertEquals(acnrManr, anrMacnr); | |
| Assert.assertEquals(acnrManr, i - aFrom); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| static boolean isEqual(ArrayType<?> at, Object a, int aFromIndex, int aToIndex, | |
| Object b, int bFromIndex, int bToIndex) { | |
| int aLength = aToIndex - aFromIndex; | |
| int bLength = bToIndex - bFromIndex; | |
| if (aLength != bLength) | |
| return false; | |
| for (int i = 0; i < aLength; i++) { | |
| Object av = at.get(a, aFromIndex++); | |
| Object bv = at.get(b, bFromIndex++); | |
| if (!Objects.equals(av, bv)) return false; | |
| } | |
| return true; | |
| } | |
| static boolean isEqual(ArrayType<?> at, Object a, int aFrom, Object b, int bFrom) { | |
| Object av = at.get(a, aFrom); | |
| Object bv = at.get(b, bFrom); | |
| return Objects.equals(av, bv); | |
| } | |
| static int[] ranges(int from, int to) { | |
| int width = to - from; | |
| switch (width) { | |
| case 0: | |
| return new int[]{}; | |
| case 1: | |
| return new int[]{from, to}; | |
| case 2: | |
| return new int[]{from, from + 1, to}; | |
| case 3: | |
| return new int[]{from, from + 1, from + 2, to}; | |
| default: | |
| return IntStream.of(from, from + 1, from + 2, to / 2 - 1, to / 2, to / 2 + 1, to - 2, to - 1, to) | |
| .filter(i -> i >= from && i <= to) | |
| .distinct().toArray(); | |
| } | |
| } | |
| // Null array reference tests | |
| @Test(dataProvider = "arrayTypesProvider") | |
| public void testNullArrayRefs(ArrayType<?> arrayType) { | |
| Object n = null; | |
| Object a = arrayType.construct(0); | |
| Assert.assertTrue(arrayType.equals(n, n)); | |
| Assert.assertFalse(arrayType.equals(n, a)); | |
| Assert.assertFalse(arrayType.equals(a, n)); | |
| Assert.assertEquals(arrayType.compare(n, n), 0); | |
| Assert.assertTrue(arrayType.compare(n, a) < 0); | |
| Assert.assertTrue(arrayType.compare(a, n) > 0); | |
| } | |
| // Exception throwing tests | |
| @Test(dataProvider = "arrayTypesProvider") | |
| public void testNPEs(ArrayType<?> arrayType) { | |
| Object[] values = new Object[]{null, arrayType.construct(0)}; | |
| for (Object o1 : values) { | |
| for (Object o2 : values) { | |
| if (o1 != null && o2 != null) | |
| continue; | |
| testNPE(() -> arrayType.equals(o1, 0, 0, o2, 0, 0)); | |
| testNPE(() -> arrayType.compare(o1, 0, 0, o2, 0, 0)); | |
| testNPE(() -> arrayType.mismatch(o1, o2)); | |
| testNPE(() -> arrayType.mismatch(o1, 0, 0, o2, 0, 0)); | |
| } | |
| } | |
| } | |
| @Test | |
| public void testObjectNPEs() { | |
| String[][] values = new String[][]{null, new String[0]}; | |
| Comparator<String> c = String::compareTo; | |
| Comparator[] cs = new Comparator[]{null, c}; | |
| for (String[] o1 : values) { | |
| for (String[] o2 : values) { | |
| for (Comparator o3 : cs) { | |
| if (o1 != null && o2 != null && o3 != null) | |
| continue; | |
| if (o3 == null) { | |
| testNPE(() -> Arrays.equals(o1, o2, o3)); | |
| testNPE(() -> Arrays.compare(o1, o2, o3)); | |
| testNPE(() -> Arrays.mismatch(o1, o2, o3)); | |
| } | |
| testNPE(() -> Arrays.equals(o1, 0, 0, o2, 0, 0, o3)); | |
| testNPE(() -> Arrays.compare(o1, 0, 0, o2, 0, 0, o3)); | |
| testNPE(() -> Arrays.mismatch(o1, 0, 0, o2, 0, 0, o3)); | |
| } | |
| } | |
| } | |
| } | |
| @Test(dataProvider = "arrayTypesProvider") | |
| public void testIAEs(ArrayType<?> arrayType) { | |
| List<Integer> values = Arrays.asList(0, 1); | |
| for (int s : values) { | |
| Object a = arrayType.construct(s); | |
| for (int o1 : values) { | |
| for (int o2 : values) { | |
| if (o1 <= o2) continue; | |
| testIAE(() -> arrayType.equals(a, o1, 0, a, o2, 0)); | |
| testIAE(() -> arrayType.compare(a, o1, 0, a, o2, 0)); | |
| testIAE(() -> arrayType.mismatch(a, o1, 0, a, o2, 0)); | |
| } | |
| } | |
| } | |
| } | |
| @Test(dataProvider = "arrayTypesProvider") | |
| public void testAIOBEs(ArrayType<?> arrayType) { | |
| List<Integer> froms = Arrays.asList(-1, 0); | |
| for (int s : Arrays.asList(0, 1)) { | |
| List<Integer> tos = Arrays.asList(s, s + 1); | |
| Object a = arrayType.construct(s); | |
| for (int aFrom : froms) { | |
| for (int aTo : tos) { | |
| for (int bFrom : froms) { | |
| for (int bTo : tos) { | |
| if (aFrom >= 0 && aTo <= s && | |
| bFrom >= 0 && bTo <= s) continue; | |
| testAIOBE(() -> arrayType.equals(a, aFrom, aTo, a, bFrom, bTo)); | |
| testAIOBE(() -> arrayType.compare(a, aFrom, aTo, a, bFrom, bTo)); | |
| testAIOBE(() -> arrayType.mismatch(a, aFrom, aTo, a, bFrom, bTo)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| static void testNPE(Runnable r) { | |
| testThrowable(r, NullPointerException.class); | |
| } | |
| static void testIAE(Runnable r) { | |
| testThrowable(r, IllegalArgumentException.class); | |
| } | |
| static void testAIOBE(Runnable r) { | |
| testThrowable(r, ArrayIndexOutOfBoundsException.class); | |
| } | |
| static void testThrowable(Runnable r, Class<? extends Throwable> expected) { | |
| Throwable caught = null; | |
| try { | |
| r.run(); | |
| } | |
| catch (Throwable t) { | |
| caught = t; | |
| } | |
| Assert.assertNotNull(caught); | |
| Assert.assertTrue(expected.isInstance(caught)); | |
| } | |
| } |