8229186: Improve error messages for TestStringIntrinsics failures

Reviewed-by: iignatyev, lmesnik
This commit is contained in:
Evgeny Nikitin 2020-10-09 16:48:49 +00:00 committed by Igor Ignatyev
parent 6d2c1a66bb
commit 52e45a3677
6 changed files with 1190 additions and 14 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2020, 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
@ -25,12 +25,16 @@
* @test
* @bug 8054307
* @summary Tests correctness of string related intrinsics and C2 optimizations.
* @library /test/lib
*
* @run main/timeout=240 compiler.intrinsics.string.TestStringIntrinsics
*/
package compiler.intrinsics.string;
import jdk.test.lib.format.Format;
import jdk.test.lib.format.ArrayCodec;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -77,8 +81,8 @@ public class TestStringIntrinsics {
for (Method m : TestStringIntrinsics.class.getMethods()) {
if (m.isAnnotationPresent(Test.class)) {
System.out.print("Checking " + m.getName() + "... ");
Operation op = m.getAnnotation(Test.class).op();
Test antn = m.getAnnotation(Test.class);
Operation op = antn.op();
if (isStringConcatTest(op)) {
checkStringConcat(op, m, antn);
} else {
@ -121,13 +125,13 @@ public class TestStringIntrinsics {
switch (op) {
case ARR_EQUALS_B:
invokeAndCheck(m, (incL == 0), latin1.getBytes("ISO-8859-1"), latin1Copy.getBytes("ISO-8859-1"));
invokeAndCheck(m, true, new byte[] {1, 2, 3}, new byte[] {1, 2, 3});
invokeAndCheck(m, true, new byte[] {1}, new byte[] {1});
invokeAndCheck(m, true, new byte[] {}, new byte[] {});
invokeAndCompareArrays(m, (incL == 0), latin1.getBytes("ISO-8859-1"), latin1Copy.getBytes("ISO-8859-1"));
invokeAndCompareArrays(m, true, new byte[] {1, 2, 3}, new byte[] {1, 2, 3});
invokeAndCompareArrays(m, true, new byte[] {1}, new byte[] {1});
invokeAndCompareArrays(m, true, new byte[] {}, new byte[] {});
break;
case ARR_EQUALS_C:
invokeAndCheck(m, (incU == 0), utf16.toCharArray(), arrU);
invokeAndCompareArrays(m, (incU == 0), utf16.toCharArray(), arrU);
break;
case EQUALS:
invokeAndCheck(m, (incL == 0), latin1, latin1Copy);
@ -240,18 +244,49 @@ public class TestStringIntrinsics {
}
}
/**
* Invokes method 'm' by passing arguments the two 'args' (which are supposed to be arrays)
* checks if the returned value. In case of error and arrays being not equal, prints their difference.
*/
private void invokeAndCompareArrays(Method m, boolean expectedResult, Object arg0, Object arg1) throws Exception {
boolean result = (Boolean)m.invoke(null, arg0, arg1);
if (expectedResult == result)
return;
String cause = String.format("Result: (%b) of '%s' is not equal to expected (%b)",
result, m.getName(), expectedResult);
if (expectedResult == true) {
System.err.println(cause);
System.err.println(Format.arrayDiff(arg0, arg1));
} else {
System.err.println(cause);
System.err.printf("First array argument: %n %s%n", ArrayCodec.format(arg0));
}
throw new RuntimeException(cause);
}
/**
* Invokes method 'm' by passing arguments 'args' and checks if the
* returned value equals 'expectedResult'.
*/
private void invokeAndCheck(Method m, Object expectedResult, Object... args) throws Exception {
Object result = m.invoke(null, args);
if (!result.equals(expectedResult)) {
// System.out.println("Expected:");
// System.out.println(expectedResult);
// System.out.println("Returned:");
// System.out.println(result);
throw new RuntimeException("Result of '" + m.getName() + "' not equal to expected value.");
Object actualResult = m.invoke(null, args);
if (!actualResult.equals(expectedResult)) {
var nl = System.lineSeparator();
StringBuilder msgBuilder = new StringBuilder();
msgBuilder.append("Actual result of '" + m.getName() + "' is not equal to expected value." + nl);
msgBuilder.append("Expected: " + Format.asLiteral(expectedResult) + nl);
msgBuilder.append("Actual: " + Format.asLiteral(actualResult));
for (int i = 0; i < args.length; i++) {
msgBuilder.append(nl + " Arg" + i + ": " + Format.asLiteral(args[i]));
}
final String message = msgBuilder.toString();
System.err.println(message);
throw new RuntimeException(message);
}
}

View File

@ -0,0 +1,409 @@
/*
* Copyright (c) 2020, 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.
*/
package jdk.test.lib.format;
import org.testng.annotations.Test;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertEquals;
/*
* @test
* @summary Check ArrayDiff formatting
* @library /test/lib
* @run testng jdk.test.lib.format.ArrayDiffTest
*/
public class ArrayDiffTest {
@Test
public void testEqualArrays() {
char[] first = new char[] {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
char[] second = new char[] {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
assertTrue(ArrayDiff.of(first, second).areEqual());
}
@Test
public void testOutputFitsWidth() {
new AssertBuilder()
.withDefaultParams()
.withArrays(
new byte[] {7, 8, 9, 10, 11, 12, 13},
new byte[] {7, 8, 9, 10, 125, 12, 13})
.thatResultIs(false)
.thatFormattedValuesAre(
4,
"[7, 8, 9, 10, 11, 12, 13]",
"[7, 8, 9, 10, 125, 12, 13]",
" ^^^^")
.assertTwoWay();
}
@Test
public void testIntegers() {
new AssertBuilder()
.withDefaultParams()
.withArrays(
new int[] {7, 8, 10, 11, 12},
new int[] {7, 8, 9, 10, 11, 12, 13})
.thatResultIs(false)
.thatFormattedValuesAre(
2,
"[7, 8, 10, 11, 12]",
"[7, 8, 9, 10, 11, 12, 13]",
" ^^^")
.assertTwoWay();
}
@Test
public void testLongs() {
new AssertBuilder()
.withDefaultParams()
.withArrays(
new long[] {1, 2, 3, 4},
new long[] {1, 2, 3, 10})
.thatResultIs(false)
.thatFormattedValuesAre(
3,
"[1, 2, 3, 4]",
"[1, 2, 3, 10]",
" ^^^")
.assertTwoWay();
}
@Test
public void testFirstElementIsWrong() {
new AssertBuilder()
.withDefaultParams()
.withArrays(
new byte[] {122},
new byte[] {7, 8, 9, 10, 125, 12, 13})
.thatResultIs(false)
.thatFormattedValuesAre(
0,
"[122]",
"[ 7, 8, 9, 10, 125, 12, 13]",
" ^^^")
.assertTwoWay();
}
@Test
public void testOneElementIsEmpty() {
new AssertBuilder()
.withDefaultParams()
.withArrays(
new byte[] {7, 8, 9, 10, 125, 12, 13},
new byte[] {})
.thatResultIs(false)
.thatFormattedValuesAre(
0,
"[7, 8, 9, 10, 125, 12, 13]",
"[]",
" ^")
.assertTwoWay();
}
@Test
public void testOutputDoesntFitWidth() {
new AssertBuilder()
.withParams(20, Integer.MAX_VALUE)
.withArrays(
new char[] {'1', '2', '3', '4', '5', '6', '7'},
new char[] {'1', 'F', '3', '4', '5', '6', '7'})
.thatResultIs(false)
.thatFormattedValuesAre(
1,
"[1, 2, 3, 4, 5, ...",
"[1, F, 3, 4, 5, ...",
" ^^")
.assertTwoWay();
}
@Test
public void testVariableElementWidthOutputDoesntFitWidth() {
new AssertBuilder()
.withParams(20, Integer.MAX_VALUE)
.withArrays(
new byte[] {1, 2, 3, 4, 5, 6, 7},
new byte[] {1, 112, 3, 4, 5, 6, 7})
.thatResultIs(false)
.thatFormattedValuesAre(
1,
"[1, 2, 3, 4, 5, ...",
"[1, 112, 3, 4, 5, ...",
" ^^^^")
.assertTwoWay();
}
@Test
public void testContextBefore() {
new AssertBuilder()
.withParams(20, 2)
.withArrays(
new char[] {'1', '2', '3', '4', '5', '6', '7'},
new char[] {'1', '2', '3', '4', 'F', '6', '7'})
.thatResultIs(false)
.thatFormattedValuesAre(
4,
"... 3, 4, 5, 6, 7]",
"... 3, 4, F, 6, 7]",
" ^^")
.assertTwoWay();
}
@Test
public void testBoundedBytesWithDifferentWidth() {
new AssertBuilder()
.withParams(24, 2)
.withArrays(
new byte[] {0, 1, 2, 3, 125, 5, 6, 7},
new byte[] {0, 1, 2, 3, 4, 5, 6, 7})
.thatResultIs(false)
.thatFormattedValuesAre(
4,
"... 2, 3, 125, 5, 6, 7]",
"... 2, 3, 4, 5, 6, 7]",
" ^^^^")
.assertTwoWay();
}
@Test
public void testBoundedFirstElementIsWrong() {
new AssertBuilder()
.withParams(25, 2)
.withArrays(
new byte[] {101, 102, 103, 104, 105, 110},
new byte[] {2})
.thatResultIs(false)
.thatFormattedValuesAre(
0,
"[101, 102, 103, 104, ...",
"[ 2]",
" ^^^")
.assertTwoWay();
}
@Test
public void testBoundedOneArchiveIsEmpty() {
new AssertBuilder()
.withParams(10, 2)
.withArrays(
new char[] {'a', 'b', 'c', 'd', 'e'},
new char[] {})
.thatResultIs(false)
.thatFormattedValuesAre(
0,
"[a, b, ...",
"[]",
" ^")
.assertTwoWay();
}
@Test
public void testUnboundedOneArchiveIsEmpty() {
new AssertBuilder()
.withDefaultParams()
.withArrays(
new char[] {'a', 'b', 'c', 'd', 'e'},
new char[] {})
.thatResultIs(false)
.thatFormattedValuesAre(
0,
"[a, b, c, d, e]",
"[]",
" ^")
.assertTwoWay();
}
@Test
public void testUnprintableCharFormatting() {
new AssertBuilder()
.withDefaultParams()
.withArrays(
new char[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
new char[] {0, 1, 2, 3, 4, 5, 6, 125, 8, 9, 10, 11, 12, 13, 14, 15, 16})
.thatResultIs(false)
.thatFormattedValuesAre(
7,
"... \\u0005, \\u0006, \\u0007, \\u0008, \\u0009, \\n, \\u000B, \\u000C, \\r, \\u000E, ...",
"... \\u0005, \\u0006, }, \\u0008, \\u0009, \\n, \\u000B, \\u000C, \\r, \\u000E, ...",
" ^^^^^^^")
.assertTwoWay();
}
@Test
public void testStringElements() {
new AssertBuilder()
.withDefaultParams()
.withArrays(
new String[] {"first", "second", "third", "u\nprintable"},
new String[] {"first", "second", "incorrect", "u\nprintable"})
.thatResultIs(false)
.thatFormattedValuesAre(
2,
"[\"first\", \"second\", \"third\", \"u\\nprintable\"]",
"[\"first\", \"second\", \"incorrect\", \"u\\nprintable\"]",
" ^^^^^^^^^^^^")
.assertTwoWay();
}
@Test
public void testToStringableObjects() {
class StrObj {
private final String value;
public boolean equals(Object another) { return ((StrObj)another).value.equals(value); }
public StrObj(String value) { this.value = value; }
public String toString() { return value; }
}
new AssertBuilder()
.withDefaultParams()
.withArrays(
new StrObj[] {new StrObj("1"), new StrObj("Unp\rintable"), new StrObj("5")},
new StrObj[] {new StrObj("1"), new StrObj("2"), new StrObj("5")})
.thatResultIs(false)
.thatFormattedValuesAre(
1,
"[1, Unp\\rintable, 5]",
"[1, 2, 5]",
" ^^^^^^^^^^^^^")
.assertTwoWay();
}
@Test
public void testNullElements() {
new AssertBuilder()
.withDefaultParams()
.withArrays(
new String[] {"Anna", null, "Bill", "Julia"},
new String[] {"Anna", "null", "William", "Julia"})
.thatResultIs(false)
.thatFormattedValuesAre(
1,
"[\"Anna\", null, \"Bill\", \"Julia\"]",
"[\"Anna\", \"null\", \"William\", \"Julia\"]",
" ^^^^^^^")
.assertTwoWay();
}
@Test (expectedExceptions = NullPointerException.class)
public void testFirstArrayIsNull() {
var diff = ArrayDiff.of(null, new String[] {"a", "b"});
}
@Test (expectedExceptions = NullPointerException.class)
public void testSecondArrayIsNull() {
var diff = ArrayDiff.of(null, new String[] {"a", "b"});
}
class AssertBuilder {
private boolean defaultParameters;
private int width;
private int contextBefore;
private Object firstArray;
private Object secondArray;
private boolean expectedResult;
private int expectedIndex;
private String firstFormattedArray;
private String secondFormattedArray;
private String failureMark;
public AssertBuilder withDefaultParams() {
defaultParameters = true;
return this;
}
public AssertBuilder withParams(int width, int contextBefore) {
defaultParameters = false;
this.width = width;
this.contextBefore = contextBefore;
return this;
}
public AssertBuilder withArrays(Object first, Object second) {
firstArray = first;
secondArray = second;
return this;
}
public AssertBuilder thatResultIs(boolean result) {
expectedResult = result;
return this;
}
public AssertBuilder thatFormattedValuesAre(
int idx, String first, String second, String mark) {
expectedIndex = idx;
firstFormattedArray = first;
secondFormattedArray = second;
failureMark = mark;
return this;
}
public void assertTwoWay() {
ArrayDiff diff;
// Direct
if (defaultParameters) {
diff = ArrayDiff.of(firstArray, secondArray);
} else {
diff = ArrayDiff.of(firstArray, secondArray, width, contextBefore);
}
if (expectedResult == true) {
assertTrue(diff.areEqual());
} else {
String expected = String.format(
"Arrays differ starting from [index: %d]:%n" +
"%s%n" + "%s%n" + "%s",
expectedIndex, firstFormattedArray, secondFormattedArray, failureMark);
assertFalse(diff.areEqual());
assertEquals(diff.format(), expected);
}
// Reversed
if (defaultParameters) {
diff = ArrayDiff.of(secondArray, firstArray);
} else {
diff = ArrayDiff.of(secondArray, firstArray, width, contextBefore);
}
if (expectedResult == true) {
assertTrue(diff.areEqual());
} else {
String expected = String.format(
"Arrays differ starting from [index: %d]:%n" +
"%s%n" + "%s%n" + "%s",
expectedIndex, secondFormattedArray, firstFormattedArray, failureMark);
assertFalse(diff.areEqual());
assertEquals(diff.format(), expected);
}
}
}
}

View File

@ -0,0 +1,340 @@
/*
* Copyright (c) 2020, 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.
*/
package jdk.test.lib.format;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* A codec helping representing arrays in a string form.
*
* Encoding can be done in a controllable fashion (allowing the user to encode two
* or more arrays in a time) or as a single operation.
*/
public class ArrayCodec<E> {
private static final String ELLIPSIS = "...";
private boolean exhausted;
private StringBuilder encoded;
private List<E> source;
private String element;
private boolean bounded = false;
private int maxWidth;
private int idx;
private ArrayCodec(List<E> source) {
this.source = source;
}
/**
* Creates a codec for a char array
*
* @param array source array
* @return an ArrayCodec for the provided array
*/
public static ArrayCodec<Character> of(char[] array) {
var source = new ArrayList<Character>(array.length);
for (char value: array) {
source.add(value);
}
return new ArrayCodec<>(source);
}
/**
* Creates a codec for a byte array
*
* @param array source array
* @return an ArrayCodec for the provided array
*/
public static ArrayCodec<Byte> of(byte[] array) {
var source = new ArrayList<Byte>(array.length);
for (byte value: array) {
source.add(value);
}
return new ArrayCodec<>(source);
}
/**
* Creates a codec for an int array
*
* @param array source array
* @return an ArrayCodec for the provided array
*/
public static ArrayCodec<Integer> of(int[] array) {
var source = new ArrayList<Integer>(array.length);
for (int value: array) {
source.add(value);
}
return new ArrayCodec<>(source);
}
/**
* Creates a codec for a long array
*
* @param array source array
* @return an ArrayCodec for the provided array
*/
public static ArrayCodec<Long> of(long[] array) {
var source = new ArrayList<Long>(array.length);
for (long value: array) {
source.add(value);
}
return new ArrayCodec<>(source);
}
/**
* Creates a codec for a String array
*
* @param array source array
* @return an ArrayCodec for the provided array
*/
public static ArrayCodec<String> of(String[] array) {
var source = new ArrayList<String>(array.length);
for (String value: array) {
source.add(value);
}
return new ArrayCodec<>(source);
}
/**
* Creates a codec for a generic Object array
*
* @param array source array
* @return an ArrayCodec for the provided array
*/
public static ArrayCodec<Object> of(Object[] array) {
var source = new ArrayList<Object>(array.length);
for (Object value: array) {
source.add(value);
}
return new ArrayCodec<Object>(source);
}
/**
* Creates a codec for a generic array, trying to recognize its component type
*
* @param array source array
* @throws IllegalArgumentException if {@code array}'s component type is not supported
* @return an ArrayCodec for the provided array
*/
public static ArrayCodec of(Object array) {
var type = array.getClass().getComponentType();
if (type == byte.class) {
return ArrayCodec.of((byte[])array);
} else if (type == int.class) {
return ArrayCodec.of((int[])array);
} else if (type == long.class) {
return ArrayCodec.of((long[])array);
} else if (type == char.class) {
return ArrayCodec.of((char[])array);
} else if (type == String.class) {
return ArrayCodec.of((String[])array);
} else if (!type.isPrimitive() && !type.isArray()) {
return ArrayCodec.of((Object[])array);
}
throw new IllegalArgumentException("Unsupported array component type: " + type);
}
/**
* Formats an array at-once.
* The array is enclosed in brackets, its elements are separated with
* commas. String elements are additionally surrounded by double quotes.
* Unprintable symbols are C-stye escaped.
*
* <p>Sample outputs:
*
* <pre>
* [0, 1, 2, 3, 4]
* ["one", "first", "tree"]
* [object1, object2, object3]
* [a, b, \n, &#92;u0002/, c]
* </pre>
*
* @throws IllegalArgumentException if {@code array}'s component type is not supported
* @return an ArrayCodec for the provided array
*/
public static String format(Object array) {
var codec = ArrayCodec.of(array);
codec.startFormatting(0, -1);
while (!codec.isExhausted()) {
codec.formatNext();
codec.appendFormatted();
}
return codec.getEncoded();
}
/**
* Starts formatting with the given parameters.
*
* @param startIdx first element's index to start formattig with
* @param maxWidth maximum allowed formatting width (in characters).
* @return an ArrayCodec for the provided array
*/
public void startFormatting(int startIdx, int maxWidth) {
encoded = new StringBuilder(startIdx == 0 ? "[" : ELLIPSIS);
exhausted = false;
this.maxWidth = maxWidth;
bounded = (maxWidth > 0);
idx = startIdx;
}
/**
* Format next element, store it in the internal element storage.
*/
public void formatNext() {
int limit = source.size();
String prefix = idx == 0 || idx >= limit ? "" : " ";
String suffix = (idx + 1 == limit) || (source.isEmpty() && idx == 0)
? "]"
: idx >= limit ? "" : ",";
element = prefix +
(idx >= limit ? "" : Format.asLiteral(source.get(idx)))
+ suffix;
}
/**
* Append formatted element to internal StringBuilder.
*
* The formatted-so-far string can be accessed via {@link #getEncoded}
* no elements in array left the method silently does nothing.
*/
public void appendFormatted() {
if (exhausted) {
return;
}
boolean isLast = idx == source.size() - 1;
if (isLast || source.isEmpty()) {
exhausted = true;
}
if (bounded && encoded.length() + element.length() > maxWidth - ELLIPSIS.length()) {
encoded.append(isLast ? element : " " + ELLIPSIS);
exhausted = true;
} else {
encoded.append(element);
}
idx++;
}
/**
* Aligns the element by another codec.
*
* If another codec's last encoded element string is longer than this
* codec's, widens this codec's encoded element with spaces so the
* two strings have the same length;
*
* @param another Another codec to compare encoded element width with
*/
public void alignBy(ArrayCodec<E> another) {
if (!element.equals("") && !element.equals("]")) {
int delta = another.element.length() - element.length();
if (delta > 0) {
element = Format.paddingForWidth(delta) + element;
}
}
}
/**
* Indicates if there are no elements left in the source array
*
* @return {@code true} if there are no elements left, {@code false} otherwise
*/
public boolean isExhausted() {
return exhausted;
}
/**
* Returns the string encoded-so-far
*
* @return the string encoded-so-far
*/
public String getEncoded() {
return encoded.toString();
}
/**
* Returns the length of the string encoded-so-far
*
* @return the length of the string encoded-so-far
*/
public int getEncodedLength() {
return encoded.length();
}
/**
* Returns the length of the last encoded element
*
* @return the length of the last encoded element
*/
public int getElementLength() {
return element.length();
}
/**
* Finds and returns the first mismatch index in another codec
*
* @param another a codec mismatch with whom is to be found
* @return the first mismatched element's index or -1 if arrays are identical
*/
public int findMismatchIndex(ArrayCodec<E> another) {
int result = 0;
while ((source.size() > result) && (another.source.size() > result)) {
Object first = source.get(result);
Object second = another.source.get(result);
if (first == null || second == null) {
if (first == null && second == null) {
continue; // Both elements are null (i.e. equal)
} else {
return result; // Only one element is null, here's the failure index
}
}
if (!first.equals(second)) {
return result;
}
result++;
}
return source.size() != another.source.size()
? result // Lengths are different, but the shorter arrays is a preffix to the longer array.
: -1; // Arrays are identical, there's no mismatch index
}
/**
* Indicates whether source array for another codec is equal to this codec's array
*
* @return {@code true} if source arrays are equal, {@code false} otherwise
*/
public boolean equals(ArrayCodec<E> another) {
return source.equals(another.source);
}
}

View File

@ -0,0 +1,207 @@
/*
* Copyright (c) 2020, 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.
*/
package jdk.test.lib.format;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* A difference between two arrays, which can be pretty formatted.
* For the calculated difference, user can request if the two arrays
* are equal (in terms of {@link Object#equals Object.equals()} for their
* elements). For the arrays that differ, a human-readable difference can
* be provided.
*
* <p>The difference is represented as a four-line text block, comprising of the
* first different element index, arrays printouts in the difference area,
* and a difference mark. For Primitive and Object elements in the source
* arrays their C-style escaped {@link String#valueOf String.valueOf()} are
* printed, element in String[] arrays are additionally surrounded with quotes.
* Additional formatting parameters, like maximum allowed width and number of
* elements printed before difference, can be specified.
*
* <p>Output examples:
*
* <p> two int arrays: </p>
* <pre>
* Arrays differ starting from [index: 4]:
* ... 3, 4, 5, 6, 7]
* ... 3, 4, 225, 6, 7]
* ^^^^
* </pre>
* <p> two String arrays: </p>
* <pre>
* Arrays differ starting from [index: 2]:
* ["first", "second", "third", "u\nprintable"]
* ["first", "second", "incorrect", "u\nprintable"]
* ^^^^^^^^^^^^
* </pre>
* <p> two char arrays arrays: </p>
* <pre>
* Arrays differ starting from [index: 7]:
* ... &#92;u0001, &#92;u0002, &#92;u0007, a, b, \n, ...
* ... &#92;u0001, &#92;u0002, }, a, b, \n, ...
* ^^^^^^^
* </pre>
*/
public class ArrayDiff<E> implements Diff {
private int failureIdx;
private final int maxWidth;
private final int contextBefore;
private final ArrayCodec<E> first;
private final ArrayCodec<E> second;
private ArrayDiff(ArrayCodec<E> first, ArrayCodec<E> second,
int width, int getContextBefore) {
this.first = first;
this.second = second;
this.maxWidth = width;
this.contextBefore = getContextBefore;
failureIdx = first.findMismatchIndex(second);
}
/**
* Creates an ArrayDiff fom two arrays and default limits. The given arguments must be of the same
* component type.
*
* @param first the first array
* @param second the second array
* @return an ArrayDiff instance for the two arrays
*/
public static ArrayDiff of(Object first, Object second) {
return ArrayDiff.of(first, second, Diff.Defaults.WIDTH, Diff.Defaults.CONTEXT_BEFORE);
}
/**
* Creates an ArrayDiff fom two arrays with the given limits. The given arguments must be of the same
* component type.
*
* @param first the first array
* @param second the second array
* @param width the maximum allowed width in characters for the formatting
* @param contextBefore maximum number of elements to print before those that differ
* @throws IllegalArgumentException if component types of arrays is not supported or are not the same
* @throws NullPointerException if at least one of the arrays is null
* @return an ArrayDiff instance for the two arrays and formatting parameters provided
*/
public static ArrayDiff of(Object first, Object second, int width, int contextBefore) {
Objects.requireNonNull(first);
Objects.requireNonNull(second);
boolean bothAreArrays = first.getClass().isArray() && second.getClass().isArray();
boolean componentTypesAreSame =
first.getClass().getComponentType() == second.getClass().getComponentType();
if (!bothAreArrays || !componentTypesAreSame) {
throw new IllegalArgumentException("Both arguments should be arrays of the same type");
}
return new ArrayDiff(
ArrayCodec.of(first),
ArrayCodec.of(second),
width, contextBefore);
}
/**
* Formats the given diff.
*
* @return formatted difference representation.
*/
@Override
public String format() {
if (areEqual()) {
return "";
}
return format(false)
.orElseGet(() -> format(true).get());
}
/**
* Indicates whether the two source arrays are equal
*
* @return {@code true} if the arrays are different, {@code false} otherwise
*/
@Override
public boolean areEqual() {
return first.equals(second);
}
private void extractAndAlignElements() {
first.formatNext();
second.formatNext();
first.alignBy(second);
second.alignBy(first);
}
private static String failureMarkForWidth(int width) {
return new String("^").repeat(width);
}
private Optional<String> format(boolean bounded) {
int idx = bounded ? Math.max(0, failureIdx - contextBefore) : 0;
first.startFormatting(idx, bounded ? maxWidth : -1);
second.startFormatting(idx, bounded ? maxWidth : -1);
StringBuilder failureMark = new StringBuilder(
Format.paddingForWidth(first.getEncodedLength()));
for (; !(first.isExhausted() && second.isExhausted()); idx++) {
extractAndAlignElements();
first.appendFormatted();
second.appendFormatted();
{ // Process failure mark
if (idx < failureIdx) {
failureMark.append(Format.paddingForWidth(first.getElementLength()));
} else if (idx == failureIdx) {
int markLength = Math.max(first.getElementLength(), second.getElementLength()) - 1;
failureMark.append(failureMarkForWidth(markLength));
}
}
final int maxEncodedLength = Math.max(
first.getEncodedLength(),
second.getEncodedLength());
if (!bounded && maxEncodedLength > maxWidth) {
return Optional.empty();
}
}
return Optional.of(String.format(
"Arrays differ starting from [index: %d]:%n%s%n%s%n%s",
failureIdx,
first.getEncoded(),
second.getEncoded(),
failureMark.toString()));
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2020, 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.
*/
/*
* Represents a possible difference between two objects.
*/
package jdk.test.lib.format;
/**
* An abstraction representing formattable difference between two or more objects
*/
public interface Diff {
/**
* Default limits for formatters
*/
public static class Defaults {
private Defaults() { } // This class should not be instantiated
public final static int WIDTH = 80;
public final static int CONTEXT_BEFORE = 2;
}
/**
* Formats the given diff. Different implementations can provide different
* result and formatting style.
*
* @return formatted difference representation.
*/
String format();
/**
* Indicates whether the two source arrays are equal. Different
* implementations can treat this notion differently.
*
* @return {@code true} if the source objects are different, {@code false} otherwise
*/
boolean areEqual();
}

View File

@ -0,0 +1,127 @@
/*
* Copyright (c) 2020, 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.
*/
package jdk.test.lib.format;
/**
* A collection of formatting utilities
*/
public class Format {
/**
* Formats character as literal, using C-style escaping for unprintable symbols
*
* @param c character to format
* @return formatted string representation of the character
*/
public static String asLiteral(char c) {
StringBuilder sb = new StringBuilder();
appendCharToSb(c, sb);
return sb.toString();
}
/**
* Escapes String in C-style
*
* @param src source string
* @return C-style escaped source string
*/
public static String escapeString(String src) {
StringBuilder sb = new StringBuilder();
src.chars().forEachOrdered(
(c) -> appendCharToSb((char) c, sb));
return sb.toString();
}
/**
* Formats Object as literal, using its String representation C-style escaped.
*
* @param o object to format
* @return C-style escaped String representation of the object
*/
public static String asLiteral(Object o) {
if (o instanceof String) {
return '"' + escapeString((String)o) + '"';
} else if (o instanceof Character) {
return asLiteral((char) o);
} else if (o instanceof Byte) {
return String.valueOf(o);
} else {
return escapeString(String.valueOf(o));
}
}
/**
* Formats a difference between two arrays with index of the first mismatch element,
* and slices of arrays necessary to understand the problem, along with a failure mark.
*
* @param first first array to compare
* @param second second array to compare
* @return the difference, generated by the {@link ArrayDiff ArrayDiff}
*/
public static String arrayDiff(Object first, Object second) {
return ArrayDiff.of(first, second).format();
}
/**
* Formats a difference between two arrays with index of the first mismatch element,
* and slices of arrays necessary to understand the problem, along with a failure mark.
* Takes into account maximum allowed width and context (in elements) before the mismatch.
*
* @param first first array to compare
* @param second second array to compare
* @param width the maximum allowed width in characters for the formatting
* @param contextBefore maximum number of elements to print before those that differ
* @return the difference, generated by the {@link ArrayDiff ArrayDiff}
*/
public static String arrayDiff(Object first, Object second, int width, int contextBefore) {
return ArrayDiff.of(first, second, width, contextBefore).format();
}
/**
* Returns a string of spaces with length specified.
*
* @param width number of spaces in the resulting string
* @return Padding string of spaces
*/
public static String paddingForWidth(int width) {
return " ".repeat(width);
}
private static void appendCharToSb(char c, StringBuilder sb) {
if (c == 10) {
sb.append("\\n");
} else if (c == 13) {
sb.append("\\r");
} else if (c == 92) {
sb.append("\\\\");
} else if (c == 34) {
sb.append("\\\"");
} else if (c < 32 || c > 126) {
sb.append("\\u" + String.format("%04X", (int) c));
} else {
sb.append(c);
}
}
}