Skip to content

Commit de78553

Browse files
klueverGoogle Java Core Libraries
authored andcommitted
Pretty-print floating point numbers in failure messages for isWithin assertions.
RELNOTES=Pretty-print floating point numbers in failure messages for `isWithin` assertions. PiperOrigin-RevId: 721848489
1 parent 31c254d commit de78553

File tree

8 files changed

+174
-54
lines changed

8 files changed

+174
-54
lines changed

core/src/main/java/com/google/common/truth/BigDecimalSubject.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
package com.google.common.truth;
1717

1818
import static com.google.common.base.Preconditions.checkNotNull;
19-
import static com.google.common.truth.Fact.fact;
19+
import static com.google.common.truth.Fact.numericFact;
2020
import static com.google.common.truth.Fact.simpleFact;
2121

2222
import java.math.BigDecimal;
@@ -95,7 +95,10 @@ public void isEquivalentAccordingToCompareTo(@Nullable BigDecimal expected) {
9595

9696
private void compareValues(@Nullable BigDecimal expected) {
9797
if (checkNotNull(actual).compareTo(checkNotNull(expected)) != 0) {
98-
failWithoutActual(fact("expected", expected), butWas(), simpleFact("(scale is ignored)"));
98+
failWithoutActual(
99+
numericFact("expected", expected),
100+
numericFact("but was", actual),
101+
simpleFact("(scale is ignored)"));
99102
}
100103
}
101104
}

core/src/main/java/com/google/common/truth/DoubleSubject.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
2020
import static com.google.common.base.Preconditions.checkNotNull;
21-
import static com.google.common.truth.Fact.fact;
21+
import static com.google.common.truth.Fact.numericFact;
2222
import static com.google.common.truth.Fact.simpleFact;
2323
import static com.google.common.truth.MathUtil.equalWithinTolerance;
2424
import static com.google.common.truth.MathUtil.notEqualWithinTolerance;
25-
import static com.google.common.truth.Platform.doubleToString;
2625
import static java.lang.Double.NaN;
2726
import static java.lang.Double.doubleToLongBits;
2827

@@ -115,9 +114,9 @@ public void of(double expected) {
115114

116115
if (!equalWithinTolerance(actual, expected, tolerance)) {
117116
failWithoutActual(
118-
fact("expected", doubleToString(expected)),
119-
butWas(),
120-
fact("outside tolerance", doubleToString(tolerance)));
117+
numericFact("expected", expected),
118+
numericFact("but was", actual),
119+
numericFact("outside tolerance", tolerance));
121120
}
122121
}
123122
};
@@ -154,9 +153,9 @@ public void of(double expected) {
154153

155154
if (!notEqualWithinTolerance(actual, expected, tolerance)) {
156155
failWithoutActual(
157-
fact("expected not to be", doubleToString(expected)),
158-
butWas(),
159-
fact("within tolerance", doubleToString(tolerance)));
156+
numericFact("expected not to be", expected),
157+
numericFact("but was", actual),
158+
numericFact("within tolerance", tolerance));
160159
}
161160
}
162161
};

core/src/main/java/com/google/common/truth/Fact.java

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020
import static com.google.common.base.Preconditions.checkNotNull;
2121
import static com.google.common.base.Strings.padEnd;
2222
import static com.google.common.base.Strings.padStart;
23+
import static com.google.common.truth.Platform.doubleToString;
24+
import static com.google.common.truth.Platform.floatToString;
2325
import static java.lang.Math.max;
2426

2527
import com.google.common.collect.ImmutableList;
2628
import java.io.Serializable;
29+
import java.math.BigDecimal;
2730
import org.jspecify.annotations.Nullable;
2831

2932
/**
@@ -68,29 +71,58 @@ public static Fact simpleFact(String key) {
6871
* Creates a fact with the given key and value, which will be printed in a format like "key:
6972
* value." The numeric value is converted to a string with delimiting commas.
7073
*/
71-
static Fact numericFact(String key, @Nullable Long value) {
74+
static Fact numericFact(String key, @Nullable Number value) {
7275
return new Fact(key, formatNumericValue(value), true);
7376
}
7477

7578
/**
76-
* Creates a fact with the given key and value, which will be printed in a format like "key:
77-
* value." The numeric value is converted to a string with delimiting commas.
79+
* Formats the given numeric value as a string with delimiting commas.
80+
*
81+
* <p><b>Note:</b> only {@code Long}, {@code Integer}, {@code Float}, {@code Double} and {@link
82+
* BigDecimal} are supported.
7883
*/
79-
static Fact numericFact(String key, @Nullable Integer value) {
80-
return new Fact(key, formatNumericValue(value), true);
81-
}
82-
83-
static String formatNumericValue(@Nullable Object value) {
84+
static String formatNumericValue(@Nullable Number value) {
8485
if (value == null) {
8586
return "null";
8687
}
88+
// the value must be a numeric type
89+
checkArgument(
90+
value instanceof Long
91+
|| value instanceof Integer
92+
|| value instanceof Float
93+
|| value instanceof Double
94+
|| value instanceof BigDecimal,
95+
"Value (%s) must be either a Long, Integer, Float, Double, or BigDecimal.",
96+
value);
97+
98+
// DecimalFormat is not available on all platforms, so we do the formatting manually.
99+
100+
if (!(value instanceof BigDecimal) && isInfiniteOrNaN(value.doubleValue())) {
101+
return value.toString();
102+
}
103+
String stringValue =
104+
value instanceof Double
105+
? doubleToString((double) value)
106+
: value instanceof Float //
107+
? floatToString((float) value)
108+
: value.toString();
109+
if (stringValue.contains("E")) {
110+
return stringValue;
111+
}
112+
int decimalIndex = stringValue.indexOf('.');
113+
if (decimalIndex == -1) {
114+
return formatWholeNumericValue(stringValue);
115+
}
116+
String wholeNumbers = stringValue.substring(0, decimalIndex);
117+
String decimal = stringValue.substring(decimalIndex);
118+
return formatWholeNumericValue(wholeNumbers) + decimal;
119+
}
87120

88-
// We only support Long and Integer for now; maybe FP numbers in the future?
89-
checkArgument(value instanceof Long || value instanceof Integer);
90-
91-
// DecimalFormat is not available on all platforms
92-
String stringValue = String.valueOf(value);
121+
private static boolean isInfiniteOrNaN(double d) {
122+
return Double.isInfinite(d) || Double.isNaN(d);
123+
}
93124

125+
private static String formatWholeNumericValue(String stringValue) {
94126
boolean isNegative = stringValue.startsWith("-");
95127
if (isNegative) {
96128
stringValue = stringValue.substring(1);
@@ -132,13 +164,18 @@ public String toString() {
132164
*/
133165
static String makeMessage(ImmutableList<String> messages, ImmutableList<Fact> facts) {
134166
int longestKeyLength = 0;
135-
int longestValueLength = 0;
167+
int longestIntPartValueLength = 0;
136168
boolean seenNewlineInValue = false;
137169
for (Fact fact : facts) {
138170
if (fact.value != null) {
139171
longestKeyLength = max(longestKeyLength, fact.key.length());
140172
if (fact.padStart) {
141-
longestValueLength = max(longestValueLength, fact.value.length());
173+
int decimalIndex = fact.value.indexOf('.');
174+
if (decimalIndex != -1) {
175+
longestIntPartValueLength = max(longestIntPartValueLength, decimalIndex);
176+
} else {
177+
longestIntPartValueLength = max(longestIntPartValueLength, fact.value.length());
178+
}
142179
}
143180
// TODO(cpovirk): Look for other kinds of newlines.
144181
seenNewlineInValue |= fact.value.contains("\n");
@@ -172,7 +209,14 @@ static String makeMessage(ImmutableList<String> messages, ImmutableList<Fact> fa
172209
builder.append(padEnd(fact.key, longestKeyLength, ' '));
173210
builder.append(": ");
174211
if (fact.padStart) {
175-
builder.append(padStart(fact.value, longestValueLength, ' '));
212+
int decimalIndex = fact.value.indexOf('.');
213+
if (decimalIndex != -1) {
214+
builder.append(
215+
padStart(fact.value.substring(0, decimalIndex), longestIntPartValueLength, ' '));
216+
builder.append(fact.value.substring(decimalIndex));
217+
} else {
218+
builder.append(padStart(fact.value, longestIntPartValueLength, ' '));
219+
}
176220
} else {
177221
builder.append(fact.value);
178222
}

core/src/main/java/com/google/common/truth/FloatSubject.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
2020
import static com.google.common.base.Preconditions.checkNotNull;
21-
import static com.google.common.truth.Fact.fact;
21+
import static com.google.common.truth.Fact.numericFact;
2222
import static com.google.common.truth.Fact.simpleFact;
2323
import static com.google.common.truth.MathUtil.equalWithinTolerance;
2424
import static com.google.common.truth.MathUtil.notEqualWithinTolerance;
25-
import static com.google.common.truth.Platform.floatToString;
2625
import static java.lang.Float.NaN;
2726
import static java.lang.Float.floatToIntBits;
2827

@@ -123,9 +122,9 @@ public void of(float expected) {
123122

124123
if (!equalWithinTolerance(actual, expected, tolerance)) {
125124
failWithoutActual(
126-
fact("expected", floatToString(expected)),
127-
butWas(),
128-
fact("outside tolerance", floatToString(tolerance)));
125+
numericFact("expected", expected),
126+
numericFact("but was", actual),
127+
numericFact("outside tolerance", tolerance));
129128
}
130129
}
131130
};
@@ -162,9 +161,9 @@ public void of(float expected) {
162161

163162
if (!notEqualWithinTolerance(actual, expected, tolerance)) {
164163
failWithoutActual(
165-
fact("expected not to be", floatToString(expected)),
166-
butWas(),
167-
fact("within tolerance", floatToString(tolerance)));
164+
numericFact("expected not to be", expected),
165+
numericFact("but was", actual),
166+
numericFact("within tolerance", tolerance));
168167
}
169168
}
170169
};

core/src/test/java/com/google/common/truth/BigDecimalSubjectTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,19 @@ public void isEqualToIgnoringScale_string() {
8383
assertFailureValue("but was", "10");
8484
}
8585

86+
@Test
87+
public void isEqualToIgnoringScale_stringWithDecimals() {
88+
BigDecimal tenFour = new BigDecimal("10.4");
89+
assertThat(tenFour).isEqualToIgnoringScale("10.4");
90+
assertThat(tenFour).isEqualToIgnoringScale("10.4");
91+
assertThat(tenFour).isEqualToIgnoringScale("10.40");
92+
assertThat(tenFour).isEqualToIgnoringScale("10.400");
93+
expectFailureWhenTestingThat(tenFour).isEqualToIgnoringScale("3.4");
94+
assertFailureKeys("expected", "but was", "(scale is ignored)");
95+
assertFailureValue("expected", "3.4");
96+
assertFailureValue("but was", "10.4");
97+
}
98+
8699
private BigDecimalSubject expectFailureWhenTestingThat(BigDecimal actual) {
87100
return expectFailure.whenTesting().that(actual);
88101
}

core/src/test/java/com/google/common/truth/DoubleSubjectTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
package com.google.common.truth;
1717

1818
import static com.google.common.truth.ExpectFailure.assertThat;
19-
import static com.google.common.truth.Platform.doubleToString;
19+
import static com.google.common.truth.Fact.formatNumericValue;
2020
import static com.google.common.truth.Truth.assertThat;
2121
import static org.junit.Assert.fail;
2222

@@ -109,9 +109,9 @@ public void invokeAssertion(SimpleSubjectBuilder<DoubleSubject, Double> expect)
109109
.factKeys()
110110
.containsExactly("expected", "but was", "outside tolerance")
111111
.inOrder();
112-
assertThat(failure).factValue("expected").isEqualTo(doubleToString(expected));
113-
assertThat(failure).factValue("but was").isEqualTo(doubleToString(actual));
114-
assertThat(failure).factValue("outside tolerance").isEqualTo(doubleToString(tolerance));
112+
assertThat(failure).factValue("expected").isEqualTo(formatNumericValue(expected));
113+
assertThat(failure).factValue("but was").isEqualTo(formatNumericValue(actual));
114+
assertThat(failure).factValue("outside tolerance").isEqualTo(formatNumericValue(tolerance));
115115
}
116116

117117
@Test
@@ -137,8 +137,8 @@ public void invokeAssertion(SimpleSubjectBuilder<DoubleSubject, Double> expect)
137137
}
138138
};
139139
AssertionError failure = expectFailure(callback);
140-
assertThat(failure).factValue("expected not to be").isEqualTo(doubleToString(expected));
141-
assertThat(failure).factValue("within tolerance").isEqualTo(doubleToString(tolerance));
140+
assertThat(failure).factValue("expected not to be").isEqualTo(formatNumericValue(expected));
141+
assertThat(failure).factValue("within tolerance").isEqualTo(formatNumericValue(tolerance));
142142
}
143143

144144
@Test

0 commit comments

Comments
 (0)