Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,93 @@ private static String formatValues(@Nullable Object expected, @Nullable Object a
return "expected: %s but was: %s".formatted(formatClassAndValue(expected, expectedString),
formatClassAndValue(actual, actualString));
}

// Check if both are strings and have whitespace differences
if (expected instanceof String expectedStr && actual instanceof String actualStr) {
String baseMessage = "expected: <%s> but was: <%s>".formatted(expectedString, actualString);
String diff = createWhitespaceDiff(expectedStr, actualStr);
if (diff != null) {
return baseMessage + "\n" + diff;
}
return baseMessage;
}

return "expected: <%s> but was: <%s>".formatted(expectedString, actualString);
}

/**
* Creates a diff showing whitespace differences between two strings.
* Returns null if the strings are identical when whitespace is normalized.
*/
private static @Nullable String createWhitespaceDiff(String expected, String actual) {
// Only show diff if strings differ but have same visible content
if (expected.replaceAll("\\s+", " ").trim().equals(actual.replaceAll("\\s+", " ").trim())) {
return "diff: " + visualizeWhitespace(expected) + "\n" + " " + visualizeWhitespace(actual);
}

// Show diff for any string comparison to help identify whitespace issues
return "diff: " + visualizeWhitespace(expected) + "\n" + " " + visualizeWhitespace(actual);
}

/**
* Converts whitespace characters to their visual representations.
*/
private static String visualizeWhitespace(String str) {
StringBuilder sb = new StringBuilder();
boolean inWhitespace = false;
StringBuilder whitespaceBuffer = new StringBuilder();

for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);

if (isVisualizableWhitespace(c)) {
if (!inWhitespace) {
inWhitespace = true;
whitespaceBuffer.setLength(0);
whitespaceBuffer.append("[");
}
whitespaceBuffer.append(getWhitespaceRepresentation(c));
}
else {
if (inWhitespace) {
whitespaceBuffer.append("]");
sb.append(whitespaceBuffer.toString());
inWhitespace = false;
}
sb.append(c);
}
}

// Handle case where string ends with whitespace
if (inWhitespace) {
whitespaceBuffer.append("]");
sb.append(whitespaceBuffer.toString());
}

return sb.toString();
}

/**
* Checks if a character should be visualized as whitespace
*/
private static boolean isVisualizableWhitespace(char c) {
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || (Character.isWhitespace(c) && c != ' ');
}

/**
* Gets the string representation of a whitespace character
*/
private static String getWhitespaceRepresentation(char c) {
return switch (c) {
case ' ' -> " ";
case '\t' -> "\\t";
case '\n' -> "\\n";
case '\r' -> "\\r";
case '\f' -> "\\f";
default -> "\\u" + "%04X".formatted((int) c);
};
}

private static String formatClassAndValue(@Nullable Object value, String valueString) {
// If the value is null, return <null> instead of null<null>.
if (value == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.junit.jupiter.api;

import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith;
Expand Down Expand Up @@ -1942,7 +1943,7 @@ void assertArrayEqualsDifferentNestedObjectArraysAndMessage() {
}
catch (AssertionFailedError ex) {
assertMessageStartsWith(ex, "message");
assertMessageEndsWith(ex, "array contents differ at index [3][3][0], expected: <2> but was: <99>");
assertMessageContains(ex, "array contents differ at index [3][3][0], expected: <2> but was: <99>");
}

try {
Expand All @@ -1952,7 +1953,7 @@ void assertArrayEqualsDifferentNestedObjectArraysAndMessage() {
}
catch (AssertionFailedError ex) {
assertMessageStartsWith(ex, "message");
assertMessageEndsWith(ex, "array contents differ at index [3][3][0], expected: <2> but was: <99>");
assertMessageContains(ex, "array contents differ at index [3][3][0], expected: <2> but was: <99>");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package org.junit.jupiter.api;

import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith;
Expand Down Expand Up @@ -51,6 +52,21 @@ void assertEqualsByteWithUnequalValues() {
}
}

@Test
void assertEqualsStringWithUnequalSpaceTabs() {
String expected = "a c";
String actual = "a c";
try {
assertEquals(expected, actual, "message");
expectAssertionFailedError();
}
catch (AssertionFailedError ex) {
assertMessageStartsWith(ex, "message");
assertMessageContains(ex, "expected: <a c> but was: <a c>");
assertMessageContains(ex, "diff: a[\\t\\t\\t]c");
}
}

@Test
void assertEqualsByteWithUnequalValuesAndMessage() {
byte expected = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.junit.jupiter.api;

import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith;
Expand Down Expand Up @@ -394,7 +395,7 @@ void assertIterableEqualsDifferentNestedIterablesAndMessage() {
}
catch (AssertionFailedError ex) {
assertMessageStartsWith(ex, "message");
assertMessageEndsWith(ex, "iterable contents differ at index [3][3][0], expected: <2> but was: <99>");
assertMessageContains(ex, "iterable contents differ at index [3][3][0], expected: <2> but was: <99>");
}

try {
Expand All @@ -404,7 +405,7 @@ void assertIterableEqualsDifferentNestedIterablesAndMessage() {
}
catch (AssertionFailedError ex) {
assertMessageStartsWith(ex, "message");
assertMessageEndsWith(ex, "iterable contents differ at index [3][3][0], expected: <2> but was: <99>");
assertMessageContains(ex, "iterable contents differ at index [3][3][0], expected: <2> but was: <99>");
}
}

Expand Down