blob: f38e677918437d7290154b5ce029444bf523aeae [file] [log] [blame]
/*
* Copyright (c) 2014 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.truth.Correspondence.tolerance;
import static com.google.common.truth.DoubleSubject.checkTolerance;
import static com.google.common.truth.MathUtil.equalWithinTolerance;
import static com.google.common.truth.MathUtil.notEqualWithinTolerance;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Doubles;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
/**
* A Subject to handle testing propositions for {@code double[]}.
*
* <p>Note: this class deprecates some common methods because the operation of equality and
* comparison on floating point numbers requires additional specification. Alternative equality
* tests are provided.
*
* @author Christian Gruber (cgruber@israfil.net)
*/
public final class PrimitiveDoubleArraySubject
extends AbstractArraySubject<PrimitiveDoubleArraySubject, double[]> {
PrimitiveDoubleArraySubject(FailureStrategy failureStrategy, @Nullable double[] o) {
super(failureStrategy, o);
}
@Override
protected String underlyingType() {
return "double";
}
@Override
protected List<Double> listRepresentation() {
return Doubles.asList(actual());
}
/**
* This form is unsafe for double-precision floating point types, and will throw an {@link
* UnsupportedOperationException}.
*
* @deprecated use either {@code
* usingTolerance(someTolerance).containsExactly(someValues).inOrder()} or {@code
* usingExactEquality().containsExactly(someValues).inOrder()}
*/
@Deprecated
@Override
public void isEqualTo(Object expected) {
throw new UnsupportedOperationException(
"Comparing raw equality of doubles is often unsafe. Use either "
+ "usingTolerance(someTolerance).containsExactly(someValues).inOrder() to compare with"
+ "a tolerance or usingExactEquality().containsExactly(someValues).inOrder() if you"
+ "really want exact equality instead.");
}
/**
* A proposition that the provided double[] is an array of the same length and type, and contains
* elements such that each element in {@code expected} is equal to each element in the subject,
* and in the same position.
*
* <p>Behaviour for non-finite values ({@link Double#POSITIVE_INFINITY POSITIVE_INFINITY}, {@link
* Double#NEGATIVE_INFINITY NEGATIVE_INFINITY}, and {@link Double#NaN NaN}) is as follows: If the
* subject and the object of the assertion are the same array, the test will pass. If not
* (including if one is a clone of the other) then non-finite values are considered not equal so
* the any non-finite value in either argument will cause the test to fail.
*
* @deprecated use {@code usingTolerance(someTolerance).containsExactly(someValues).inOrder()},
* noting the different behaviour for non-finite values
*/
@Deprecated
public void isEqualTo(Object expected, double tolerance) {
double[] actual = actual();
if (actual == expected) {
return; // short-cut.
}
try {
double[] expectedArray = (double[]) expected;
if (expectedArray.length != actual.length) {
failWithRawMessage(
"Arrays are of different lengths. expected: %s, actual %s",
Doubles.asList(expectedArray), Doubles.asList(actual));
}
List<Integer> unequalIndices = new ArrayList<Integer>();
for (int i = 0; i < expectedArray.length; i++) {
if (!equalWithinTolerance(actual[i], expectedArray[i], tolerance)) {
unequalIndices.add(i);
}
}
if (!unequalIndices.isEmpty()) {
fail("is equal to", Doubles.asList(expectedArray));
}
} catch (ClassCastException e) {
failWithBadType(expected);
}
}
/**
* This form is unsafe for double-precision floating point types, and will throw an {@link
* UnsupportedOperationException}.
*
* @deprecated If you really want this, convert the array to a list, possibly using {@link
* Doubles#asList}, and do the assertion on that, e.g. {@code
* assertThat(asList(actualDoubleArray)).isNotEqualTo(asList(expectedDoubleArray));}.
*/
@Deprecated
@Override
public void isNotEqualTo(Object expected) {
throw new UnsupportedOperationException(
"Comparing raw equality of doubles is unsafe, "
+ "use isNotEqualTo(double[] array, double tolerance) instead.");
}
/**
* A proposition that the provided double[] is not an array of the same length or type, or has at
* least one element that does not pass an equality test within the given tolerance.
*
* <p>Behaviour for non-finite values ({@link Double#POSITIVE_INFINITY POSITIVE_INFINITY}, {@link
* Double#NEGATIVE_INFINITY NEGATIVE_INFINITY}, and {@link Double#NaN NaN}) is as follows: If the
* subject and the object of the assertion are the same array, the test will fail. If not
* (including if one is a clone of the other) then non-finite values are considered not equal so
* the any non-finite value in either argument will cause the test to pass.
*
* @deprecated Write a for loop over the values looking for mismatches (see this implementation
* for an example)
*/
@Deprecated
public void isNotEqualTo(Object expectedArray, double tolerance) {
double[] actual = actual();
try {
double[] expected = (double[]) expectedArray;
if (actual == expected) {
failWithRawMessage(
"%s unexpectedly equal to %s.", actualAsString(), Doubles.asList(expected));
}
if (expected.length != actual.length) {
return; // Unequal-lengthed arrays are not equal.
}
List<Integer> unequalIndices = new ArrayList<Integer>();
for (int i = 0; i < expected.length; i++) {
if (!equalWithinTolerance(actual[i], expected[i], tolerance)) {
unequalIndices.add(i);
}
}
if (unequalIndices.isEmpty()) {
failWithRawMessage(
"%s unexpectedly equal to %s.", actualAsString(), Doubles.asList(expected));
}
} catch (ClassCastException ignored) {
// Unequal since they are of different types.
}
}
/**
* A partially specified proposition about an approximate relationship to a {@code double[]}
* subject using a tolerance.
*/
public abstract static class TolerantPrimitiveDoubleArrayComparison {
// Prevent subclassing outside of this class
private TolerantPrimitiveDoubleArrayComparison() {}
/**
* Fails if the values in the subject were expected to be within the tolerance of the given
* values but were not <i>or</i> if they were expected <i>not</i> to be within the tolerance but
* were. The subject and tolerance are specified earlier in the fluent call chain.
*/
public void of(double... expected) {
ofElementsIn(Doubles.asList(expected));
}
/**
* Fails if the values in the subject were expected to be within the tolerance of the given
* values but were not <i>or</i> if they were expected <i>not</i> to be within the tolerance but
* were. The subject and tolerance are specified earlier in the fluent call chain. The values
* will be cast to doubles if necessary, which might lose precision.
*/
public abstract void ofElementsIn(Iterable<? extends Number> expected);
/**
* @throws UnsupportedOperationException always
* @deprecated {@link Object#equals(Object)} is not supported on
* TolerantPrimitiveDoubleArrayComparison. If you meant to compare double arrays, use {@link
* #of} or {@link #ofElementsIn} instead.
*/
@Deprecated
@Override
public boolean equals(@Nullable Object o) {
throw new UnsupportedOperationException(
"If you meant to compare double arrays, use .of() or .ofElementsIn() instead.");
}
/**
* @throws UnsupportedOperationException always
* @deprecated {@link Object#hashCode()} is not supported on
* TolerantPrimitiveDoubleArrayComparison
*/
@Deprecated
@Override
public int hashCode() {
throw new UnsupportedOperationException("Subject.hashCode() is not supported.");
}
}
/**
* Prepares for a check that the subject and object are arrays both (a) of the same length, and
* (b) where the values at all corresponding positions in each array are finite values within
* {@code tolerance} of each other, that is {@code
* assertThat(actual[i]).isWithin(tolerance).of(expected[i])} passes for all {@code i} (see the
* {@link DoubleSubject#isWithin isWithin} assertion for doubles).
*
* <p>The check will fail if any value in either the subject array or the object array is {@link
* Double#POSITIVE_INFINITY}, {@link Double#NEGATIVE_INFINITY}, or {@link Double#NaN}.
*
* @param tolerance an inclusive upper bound on the difference between the subject and object
* allowed by the check, which must be a non-negative finite value, i.e. not {@link
* Double#NaN}, {@link Double#POSITIVE_INFINITY}, or negative, including {@code -0.0}
*/
public TolerantPrimitiveDoubleArrayComparison hasValuesWithin(final double tolerance) {
return new TolerantPrimitiveDoubleArrayComparison() {
@Override
public void ofElementsIn(Iterable<? extends Number> expected) {
checkTolerance(tolerance);
double[] actual = checkNotNull(actual());
List<Integer> mismatches = new ArrayList<Integer>();
int expectedCount = 0;
for (Number expectedValue : expected) {
// if expected is longer than actual, we can skip the excess values: this case is covered
// by the length check below
if (expectedCount < actual.length
&& !equalWithinTolerance(
actual[expectedCount], expectedValue.doubleValue(), tolerance)) {
mismatches.add(expectedCount);
}
expectedCount++;
}
if (actual.length != expectedCount) {
failWithRawMessage(
"Not true that %s has values within %s of <%s>. Expected length <%s> but got <%s>",
actualAsString(),
tolerance,
Iterables.toString(expected),
expectedCount,
actual.length);
}
if (!mismatches.isEmpty()) {
failWithBadResults(
"has values within " + tolerance + " of",
Iterables.toString(expected),
"differs at indexes",
mismatches);
}
}
};
}
/**
* Prepares for a check that the subject and object are arrays either (a) of the different
* lengths, or (b) of the same length but where the values at at least one corresponding position
* in each array are finite values not within {@code tolerance} of each other, that is {@code
* assertThat(actual[i]).isNotWithin(tolerance).of(expected[i])} passes for at least one {@code i}
* (see the {@link DoubleSubject#isNotWithin isNotWithin} assertion for doubles).
*
* <p>In the case (b), a pair of subject and object values will not cause the test to pass if
* either of them is {@link Double#POSITIVE_INFINITY}, {@link Double#NEGATIVE_INFINITY}, or {@link
* Double#NaN}.
*
* @param tolerance an exclusive lower bound on the difference between the subject and object
* allowed by the check, which must be a non-negative finite value, i.e. not {@code
* Double.NaN}, {@code Double.POSITIVE_INFINITY}, or negative, including {@code -0.0}
* @deprecated Write a for loop over the values looking for mismatches (see this implementation
* for an example)
*/
@Deprecated
public TolerantPrimitiveDoubleArrayComparison hasValuesNotWithin(
final double tolerance) {
return new TolerantPrimitiveDoubleArrayComparison() {
@Override
public void ofElementsIn(Iterable<? extends Number> expected) {
checkTolerance(tolerance);
double[] actual = checkNotNull(actual());
int expectedCount = 0;
for (Number expectedValue : expected) {
// if expected is longer than actual, we can skip the excess values: this case is covered
// by the length check below
if (expectedCount < actual.length
&& notEqualWithinTolerance(
actual[expectedCount], expectedValue.doubleValue(), tolerance)) {
return;
}
expectedCount++;
}
// By the method contract, the assertion passes if the lengths are different. This is so
// that hasValuesNotWithin behaves like isNotEqualTo with a tolerance (and different
// handling of non-finite values).
if (actual.length == expectedCount) {
fail("has values not within " + tolerance + " of", Iterables.toString(expected));
}
}
};
}
/**
* Starts a method chain for a test proposition in which the actual values (i.e. the elements of
* the array under test) are compared to expected elements using a {@link Correspondence} which
* considers values to correspond if they are finite values within {@code tolerance} of each
* other. The proposition is actually executed by continuing the method chain. For example:
* <pre> {@code
* assertThat(actualDoubleArray).usingTolerance(1.0e-5).contains(3.14159);}</pre>
*
* <ul>
* <li>It does not consider values to correspond if either value is infinite or NaN.
* <li>It considers {@code -0.0} to be within any tolerance of {@code 0.0}.
* <li>The expected values provided later in the chain will be {@link Number} instances which will
* be converted to doubles, which may result in a loss of precision for some numeric types.
* <li>The subsequent methods in the chain may throw a {@link NullPointerException} if any
* expected {@link Number} instance is null.
* </ul>
*
* @param tolerance an inclusive upper bound on the difference between the double values of the
* actual and expected numbers, which must be a non-negative finite value, i.e. not {@link
* Double#NaN}, {@link Double#POSITIVE_INFINITY}, or negative, including {@code -0.0}
*/
public IterableSubject.UsingCorrespondence<Number, Number> usingTolerance(double tolerance) {
return new IterableSubject(failureStrategy, listRepresentation())
.comparingElementsUsing(tolerance(tolerance));
}
private static final Correspondence<Double, Number> EXACT_EQUALITY_CORRESPONDENCE =
new Correspondence<Double, Number>() {
@Override
public boolean compare(Double actual, Number expected) {
return actual.equals(checkedToDouble(expected));
}
@Override
public String toString() {
return "is exactly equal to";
}
};
private static double checkedToDouble(Number expected) {
checkNotNull(expected);
checkArgument(
expected instanceof Double
|| expected instanceof Float
|| expected instanceof Integer
|| expected instanceof Long,
"Expected value in assertion using exact double equality was of unsupported type %s "
+ "(it may not have an exact double representation)",
expected.getClass());
if (expected instanceof Long) {
checkArgument(
Math.abs((Long) expected) <= 1L << 53,
"Expected value %s in assertion using exact double equality was a long with an absolute "
+ "value greater than 2^52 which has no exact double representation",
expected);
}
return expected.doubleValue();
}
/**
* Starts a method chain for a test proposition in which the actual values (i.e. the elements of
* the array under test) are compared to expected elements using a {@link Correspondence} which
* considers values to correspond if they are exactly equal, with equality defined by {@link
* Double#equals}. This method is <i>not</i> recommended when the code under test is doing any
* kind of arithmetic: use {@link #usingTolerance} with a suitable tolerance in that case.
* (Remember that the exact result of floating point arithmetic is sensitive to apparently trivial
* changes such as replacing {@code (a + b) + c} with {@code a + (b + c)}, and that unless {@code
* strictfp} is in force even the result of {@code (a + b) + c} is sensitive to the JVM's choice
* of precision for the intermediate result.) This method is recommended when the code under test
* is specified as either copying a value without modification from its input or returning a
* well-defined literal or constant value. The proposition is actually executed by continuing the
* method chain. For example:
* <pre> {@code
* assertThat(actualDoubleArray).usingExactEquality().contains(3.14159);}</pre>
*
* <p>For convenience, some subsequent methods accept expected values as {@link Number} instances.
* These numbers must be either of type {@link Double}, {@link Float}, {@link Integer}, or
* {@link Long}, and if they are {@link Long} then their absolute values must not exceed 2^53
* which is just over 9e15. (This restriction ensures that the expected values have exact
* {@link Double} representations: using exact equality makes no sense if they do not.)
*
* <ul>
* <li>It considers {@link Double#POSITIVE_INFINITY}, {@link Double#NEGATIVE_INFINITY}, and
* {@link Double#NaN} to be equal to themselves.
* <li>It does <i>not</i> consider {@code -0.0} to be equal to {@code 0.0}.
* <li>The subsequent methods in the chain may throw a {@link NullPointerException} if any
* expected {@link Double} instance is null.
* </ul>
*/
public IterableSubject.UsingCorrespondence<Double, Number> usingExactEquality() {
return new IterableSubject(failureStrategy, listRepresentation())
.comparingElementsUsing(EXACT_EQUALITY_CORRESPONDENCE);
}
}