/* * 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)); } }