Skip to content

Commit

Permalink
Pretty-print integral numbers in failure messages for isWithin asse…
Browse files Browse the repository at this point in the history
…rtions.

RELNOTES=Changed failure messages for `isWithin` assertions to pretty-print integral numbers.
PiperOrigin-RevId: 721576493
  • Loading branch information
kluever authored and Google Java Core Libraries committed Jan 31, 2025
1 parent 996fa8f commit 07318c2
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 33 deletions.
62 changes: 58 additions & 4 deletions core/src/main/java/com/google/common/truth/Fact.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package com.google.common.truth;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.padEnd;
import static com.google.common.base.Strings.padStart;
import static java.lang.Math.max;

import com.google.common.collect.ImmutableList;
Expand All @@ -40,7 +42,7 @@ public final class Fact implements Serializable {
* value." The value is converted to a string by calling {@code String.valueOf} on it.
*/
public static Fact fact(String key, @Nullable Object value) {
return new Fact(key, String.valueOf(value));
return new Fact(key, String.valueOf(value), false);
}

/**
Expand All @@ -59,15 +61,59 @@ public static Fact fact(String key, @Nullable Object value) {
* </ul>
*/
public static Fact simpleFact(String key) {
return new Fact(key, null);
return new Fact(key, null, false);
}

/**
* Creates a fact with the given key and value, which will be printed in a format like "key:
* value." The numeric value is converted to a string with delimiting commas.
*/
static Fact numericFact(String key, @Nullable Long value) {
return new Fact(key, formatNumericValue(value), true);
}

/**
* Creates a fact with the given key and value, which will be printed in a format like "key:
* value." The numeric value is converted to a string with delimiting commas.
*/
static Fact numericFact(String key, @Nullable Integer value) {
return new Fact(key, formatNumericValue(value), true);
}

static String formatNumericValue(@Nullable Object value) {
if (value == null) {
return "null";
}

// We only support Long and Integer for now; maybe FP numbers in the future?
checkArgument(value instanceof Long || value instanceof Integer);

// DecimalFormat is not available on all platforms
String stringValue = String.valueOf(value);

boolean isNegative = stringValue.startsWith("-");
if (isNegative) {
stringValue = stringValue.substring(1);
}

StringBuilder builder = new StringBuilder();
for (int i = 0; i < stringValue.length(); i++) {
builder.append(stringValue.charAt(i));
if ((stringValue.length() - i - 1) % 3 == 0 && i != stringValue.length() - 1) {
builder.append(',');
}
}
return isNegative ? "-" + builder : builder.toString();
}

final String key;
final @Nullable String value;
final boolean padStart;

private Fact(String key, @Nullable String value) {
private Fact(String key, @Nullable String value, boolean padStart) {
this.key = checkNotNull(key);
this.value = value;
this.padStart = padStart;
}

/**
Expand All @@ -86,10 +132,14 @@ public String toString() {
*/
static String makeMessage(ImmutableList<String> messages, ImmutableList<Fact> facts) {
int longestKeyLength = 0;
int longestValueLength = 0;
boolean seenNewlineInValue = false;
for (Fact fact : facts) {
if (fact.value != null) {
longestKeyLength = max(longestKeyLength, fact.key.length());
if (fact.padStart) {
longestValueLength = max(longestValueLength, fact.value.length());
}
// TODO(cpovirk): Look for other kinds of newlines.
seenNewlineInValue |= fact.value.contains("\n");
}
Expand Down Expand Up @@ -121,7 +171,11 @@ static String makeMessage(ImmutableList<String> messages, ImmutableList<Fact> fa
} else {
builder.append(padEnd(fact.key, longestKeyLength, ' '));
builder.append(": ");
builder.append(fact.value);
if (fact.padStart) {
builder.append(padStart(fact.value, longestValueLength, ' '));
} else {
builder.append(fact.value);
}
}
builder.append('\n');
}
Expand Down
14 changes: 7 additions & 7 deletions core/src/main/java/com/google/common/truth/IntegerSubject.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Fact.fact;
import static com.google.common.truth.Fact.numericFact;
import static com.google.common.truth.MathUtil.equalWithinTolerance;

import org.jspecify.annotations.Nullable;
Expand Down Expand Up @@ -101,9 +101,9 @@ public void of(int expected) {

if (!equalWithinTolerance(actual, expected, tolerance)) {
failWithoutActual(
fact("expected", Integer.toString(expected)),
butWas(),
fact("outside tolerance", Integer.toString(tolerance)));
numericFact("expected", expected),
numericFact("but was", actual),
numericFact("outside tolerance", tolerance));
}
}
};
Expand All @@ -128,9 +128,9 @@ public void of(int expected) {

if (equalWithinTolerance(actual, expected, tolerance)) {
failWithoutActual(
fact("expected not to be", Integer.toString(expected)),
butWas(),
fact("within tolerance", Integer.toString(tolerance)));
numericFact("expected not to be", expected),
numericFact("but was", actual),
numericFact("within tolerance", tolerance));
}
}
};
Expand Down
14 changes: 7 additions & 7 deletions core/src/main/java/com/google/common/truth/LongSubject.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Fact.fact;
import static com.google.common.truth.Fact.numericFact;
import static com.google.common.truth.MathUtil.equalWithinTolerance;

import org.jspecify.annotations.Nullable;
Expand Down Expand Up @@ -102,9 +102,9 @@ public void of(long expected) {

if (!equalWithinTolerance(actual, expected, tolerance)) {
failWithoutActual(
fact("expected", Long.toString(expected)),
butWas(),
fact("outside tolerance", Long.toString(tolerance)));
numericFact("expected", expected),
numericFact("but was", actual),
numericFact("outside tolerance", tolerance));
}
}
};
Expand All @@ -129,9 +129,9 @@ public void of(long expected) {

if (equalWithinTolerance(actual, expected, tolerance)) {
failWithoutActual(
fact("expected not to be", Long.toString(expected)),
butWas(),
fact("within tolerance", Long.toString(tolerance)));
numericFact("expected not to be", expected),
numericFact("but was", actual),
numericFact("within tolerance", tolerance));
}
}
};
Expand Down
69 changes: 64 additions & 5 deletions core/src/test/java/com/google/common/truth/FactTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
package com.google.common.truth;

import static com.google.common.truth.Fact.fact;
import static com.google.common.truth.Fact.formatNumericValue;
import static com.google.common.truth.Fact.makeMessage;
import static com.google.common.truth.Fact.numericFact;
import static com.google.common.truth.Fact.simpleFact;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand All @@ -29,6 +33,9 @@
/** Tests for {@link Fact}. */
@RunWith(JUnit4.class)
public class FactTest {

private static final Joiner TEXT = Joiner.on("\n");

@Test
public void string() {
assertThat(fact("foo", "bar").toString()).isEqualTo("foo: bar");
Expand All @@ -51,7 +58,10 @@ public void twoFacts() {
makeMessage(
ImmutableList.<String>of(),
ImmutableList.of(fact("foo", "bar"), fact("longer name", "other value"))))
.isEqualTo("foo : bar\nlonger name: other value");
.isEqualTo(
TEXT.join(
"foo : bar", // force a line break
"longer name: other value"));
}

@Test
Expand All @@ -60,10 +70,14 @@ public void numericFacts() {
makeMessage(
ImmutableList.<String>of(),
ImmutableList.of(
fact("expected", 802604),
fact("but was", 773804),
fact("outside tolerance", 9599))))
.isEqualTo("expected : 802604\nbut was : 773804\noutside tolerance: 9599");
numericFact("expected", 802604),
numericFact("but was", 773804),
numericFact("outside tolerance", 9599))))
.isEqualTo(
TEXT.join(
"expected : 802,604",
"but was : 773,804",
"outside tolerance: 9,599"));
}

@Test
Expand Down Expand Up @@ -101,4 +115,49 @@ public void withMessage() {
assertThat(makeMessage(ImmutableList.<String>of("hello"), ImmutableList.of(fact("foo", "bar"))))
.isEqualTo("hello\nfoo: bar");
}

@Test
public void formatNumericValue_null() {
assertThat(formatNumericValue(null)).isEqualTo("null");
}

@Test
public void formatNumericValue_zero() {
assertThat(formatNumericValue(0)).isEqualTo("0");
assertThat(formatNumericValue(0L)).isEqualTo("0");
assertThat(formatNumericValue(-0)).isEqualTo("0");
assertThat(formatNumericValue(-0L)).isEqualTo("0");
}

@Test
public void formatNumericValue_positive() {
assertThat(formatNumericValue(9)).isEqualTo("9");
assertThat(formatNumericValue(999L)).isEqualTo("999");
assertThat(formatNumericValue(9599)).isEqualTo("9,599");
assertThat(formatNumericValue(20000L)).isEqualTo("20,000");
assertThat(formatNumericValue(802604)).isEqualTo("802,604");
assertThat(formatNumericValue(1234567890)).isEqualTo("1,234,567,890");
assertThat(formatNumericValue(1234567890L)).isEqualTo("1,234,567,890");
assertThat(formatNumericValue(Integer.MAX_VALUE)).isEqualTo("2,147,483,647");
assertThat(formatNumericValue(Long.MAX_VALUE)).isEqualTo("9,223,372,036,854,775,807");
}

@Test
public void formatNumericValue_negative() {
assertThat(formatNumericValue(-9)).isEqualTo("-9");
assertThat(formatNumericValue(-999L)).isEqualTo("-999");
assertThat(formatNumericValue(-9599)).isEqualTo("-9,599");
assertThat(formatNumericValue(-20000L)).isEqualTo("-20,000");
assertThat(formatNumericValue(-802604)).isEqualTo("-802,604");
assertThat(formatNumericValue(-1234567890)).isEqualTo("-1,234,567,890");
assertThat(formatNumericValue(-1234567890L)).isEqualTo("-1,234,567,890");
assertThat(formatNumericValue(Integer.MIN_VALUE)).isEqualTo("-2,147,483,648");
assertThat(formatNumericValue(Long.MIN_VALUE)).isEqualTo("-9,223,372,036,854,775,808");
}

@Test
public void formatNumericValue_throwsExceptionForNonNumericValue() {
assertThrows(IllegalArgumentException.class, () -> formatNumericValue("foo"));
assertThrows(IllegalArgumentException.class, () -> formatNumericValue(2.2));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.google.common.truth;

import static com.google.common.truth.ExpectFailure.assertThat;
import static com.google.common.truth.Fact.formatNumericValue;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;

Expand Down Expand Up @@ -155,9 +156,9 @@ public void invokeAssertion(SimpleSubjectBuilder<IntegerSubject, Integer> expect
.factKeys()
.containsExactly("expected", "but was", "outside tolerance")
.inOrder();
assertThat(failure).factValue("expected").isEqualTo(Integer.toString(expected));
assertThat(failure).factValue("but was").isEqualTo(Integer.toString(actual));
assertThat(failure).factValue("outside tolerance").isEqualTo(Integer.toString(tolerance));
assertThat(failure).factValue("expected").isEqualTo(formatNumericValue(expected));
assertThat(failure).factValue("but was").isEqualTo(formatNumericValue(actual));
assertThat(failure).factValue("outside tolerance").isEqualTo(formatNumericValue(tolerance));
}

@Test
Expand Down Expand Up @@ -191,8 +192,8 @@ public void invokeAssertion(SimpleSubjectBuilder<IntegerSubject, Integer> expect
}
};
AssertionError failure = expectFailure(callback);
assertThat(failure).factValue("expected not to be").isEqualTo(Integer.toString(expected));
assertThat(failure).factValue("within tolerance").isEqualTo(Integer.toString(tolerance));
assertThat(failure).factValue("expected not to be").isEqualTo(formatNumericValue(expected));
assertThat(failure).factValue("within tolerance").isEqualTo(formatNumericValue(tolerance));
}

@Test
Expand Down
11 changes: 6 additions & 5 deletions core/src/test/java/com/google/common/truth/LongSubjectTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.google.common.truth;

import static com.google.common.truth.ExpectFailure.assertThat;
import static com.google.common.truth.Fact.formatNumericValue;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;

Expand Down Expand Up @@ -169,9 +170,9 @@ public void invokeAssertion(SimpleSubjectBuilder<LongSubject, Long> expect) {
.factKeys()
.containsExactly("expected", "but was", "outside tolerance")
.inOrder();
assertThat(failure).factValue("expected").isEqualTo(Long.toString(expected));
assertThat(failure).factValue("but was").isEqualTo(Long.toString(actual));
assertThat(failure).factValue("outside tolerance").isEqualTo(Long.toString(tolerance));
assertThat(failure).factValue("expected").isEqualTo(formatNumericValue(expected));
assertThat(failure).factValue("but was").isEqualTo(formatNumericValue(actual));
assertThat(failure).factValue("outside tolerance").isEqualTo(formatNumericValue(tolerance));
}

@Test
Expand Down Expand Up @@ -205,8 +206,8 @@ public void invokeAssertion(SimpleSubjectBuilder<LongSubject, Long> expect) {
}
};
AssertionError failure = expectFailure(callback);
assertThat(failure).factValue("expected not to be").isEqualTo(Long.toString(expected));
assertThat(failure).factValue("within tolerance").isEqualTo(Long.toString(tolerance));
assertThat(failure).factValue("expected not to be").isEqualTo(formatNumericValue(expected));
assertThat(failure).factValue("within tolerance").isEqualTo(formatNumericValue(tolerance));
}

@Test
Expand Down

0 comments on commit 07318c2

Please sign in to comment.