Skip to content

Commit

Permalink
Moves the logic from Asserts for testing type equality into `TypeSu…
Browse files Browse the repository at this point in the history
…bject`.

This is in preparation for deleting equality assertions from `Asserts` and migrating callers.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=209460746
  • Loading branch information
nreid260 authored and blickly committed Aug 27, 2018
1 parent e376ae1 commit 930358e
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 85 deletions.
79 changes: 5 additions & 74 deletions src/com/google/javascript/rhino/testing/Asserts.java
Expand Up @@ -39,10 +39,10 @@


package com.google.javascript.rhino.testing; package com.google.javascript.rhino.testing;


import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.javascript.rhino.testing.TypeSubject.types;


import com.google.common.base.Joiner;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.javascript.rhino.ErrorReporter; import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.JSType;
Expand Down Expand Up @@ -73,75 +73,16 @@ public static void assertTypeNotEquals(JSType a, JSType b) {
assertTypeNotEquals("", a, b); assertTypeNotEquals("", a, b);
} }


public static void assertTypeNotEquals(String message, JSType a, JSType b) { public static void assertTypeNotEquals(String message, JSType expected, JSType actual) {
if (!message.isEmpty()) { assertWithMessage(message).about(types()).that(actual).isNotEqualTo(expected);
message += "\n";
}
String aDebug = debugStringOf(a);
String bDebug = debugStringOf(b);

Assert.assertFalse(
lines(
message,
"Type should not be equal.",
"First type : " + aDebug,
"was equal to second type: " + bDebug),
a.isEquivalentTo(b));
Assert.assertFalse(
lines(
message,
"Inequality was found to be asymmetric.",
"Type should not be equal.",
"First type : " + aDebug,
"was equal to second type: " + bDebug),
b.isEquivalentTo(a));
} }


public static void assertTypeEquals(JSType a, JSType b) { public static void assertTypeEquals(JSType a, JSType b) {
assertTypeEquals("", a, b); assertTypeEquals("", a, b);
} }


public static void assertTypeEquals(String message, JSType expected, JSType actual) { public static void assertTypeEquals(String message, JSType expected, JSType actual) {
checkNotNull(expected); assertWithMessage(message).about(types()).that(actual).isStructurallyEqualTo(expected);
checkNotNull(actual);

if (!message.isEmpty()) {
message += "\n";
}
String expectedDebug = debugStringOf(expected);
String actualDebug = debugStringOf(actual);

Assert.assertTrue(
lines(
message, //
"Types should be equal.",
"Expected: " + expectedDebug,
"Actual : " + actualDebug),
expected.isEquivalentTo(actual, true));
Assert.assertTrue(
lines(
message,
"Equality was found to be asymmetric.",
"Types should be equal.",
"Expected: " + expectedDebug,
"Actual : " + actualDebug),
actual.isEquivalentTo(expected, true));

// Recall the `equals-hashCode` contract: if two objects report being equal, their hashcodes
// must also be equal. Breaking this contract breaks structures that depend on it (e.g.
// `HashMap`).
//
// The implementations of `hashCode()` and `equals()` are a bit tricky in some of the `JSType`
// classes, so we want to check specifically that this contract is fulfilled in all the unit
// tests involving them.
Assert.assertEquals(
lines(
message,
"Types violate the `equals-hashCode`: types report e `hashCode()`s do not match.",
"Expected: " + expected.hashCode() + " [on " + expectedDebug + "]",
"Actual : " + actual.hashCode() + " [on " + actualDebug + "]"),
expected.hashCode(),
actual.hashCode());
} }


public static <T extends JSType, S extends JSType> void public static <T extends JSType, S extends JSType> void
Expand Down Expand Up @@ -183,14 +124,4 @@ public static void assertEquivalenceOperations(JSType a, JSType b) {
Assert.assertTrue(b.canCastTo(b)); Assert.assertTrue(b.canCastTo(b));
Assert.assertTrue(b.canCastTo(a)); Assert.assertTrue(b.canCastTo(a));
} }

private static String debugStringOf(JSType type) {
return (type == null)
? "<Java null>"
: type.toString() + " [instanceof " + type.getClass().getName() + "]";
}

private static final String lines(String... lines) {
return Joiner.on("\n").join(lines);
}
} }
133 changes: 122 additions & 11 deletions src/com/google/javascript/rhino/testing/TypeSubject.java
Expand Up @@ -39,12 +39,16 @@


package com.google.javascript.rhino.testing; package com.google.javascript.rhino.testing;


import static com.google.common.truth.Fact.fact;
import static com.google.common.truth.Fact.simpleFact;
import static com.google.common.truth.Truth.assertAbout; import static com.google.common.truth.Truth.assertAbout;
import static com.google.common.truth.Truth.assertThat;


import com.google.common.truth.FailureMetadata; import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject; import com.google.common.truth.Subject;
import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.JSType;
import javax.annotation.CheckReturnValue; import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;


/** /**
* A Truth Subject for the JSType interface. Usage: * A Truth Subject for the JSType interface. Usage:
Expand All @@ -70,32 +74,49 @@ private TypeSubject(FailureMetadata failureMetadata, JSType type) {
super(failureMetadata, type); super(failureMetadata, type);
} }


@Override
public void isEqualTo(@Nullable Object provided) {
if (provided != null) {
assertThat(provided).isInstanceOf(JSType.class);
}

checkEqualityAgainst((JSType) provided, true, NATURAL_EQUIVALENCE);
}

public void isStructurallyEqualTo(@Nullable JSType provided) {
checkEqualityAgainst(provided, true, STRUCTURAL_EQUIVALENCE);
}

public void isNotEqualTo(@Nullable JSType provided) {
checkEqualityAgainst(provided, false, NATURAL_EQUIVALENCE);
}

public void isNumber() { public void isNumber() {
check("isNumberValueType()").that(actual().isNumberValueType()).isTrue(); check("isNumberValueType()").that(actualNonNull().isNumberValueType()).isTrue();
} }


public void isString() { public void isString() {
check("isStringValueType()").that(actual().isStringValueType()).isTrue(); check("isStringValueType()").that(actualNonNull().isStringValueType()).isTrue();
} }


public void isBoolean() { public void isBoolean() {
check("isBooleanValueType()").that(actual().isBooleanValueType()).isTrue(); check("isBooleanValueType()").that(actualNonNull().isBooleanValueType()).isTrue();
} }


public void isUnknown() { public void isUnknown() {
check("isUnknownType()").that(actual().isUnknownType()).isTrue(); check("isUnknownType()").that(actualNonNull().isUnknownType()).isTrue();
} }


public void isNotUnknown() { public void isNotUnknown() {
check("isUnknownType()").that(actual().isUnknownType()).isFalse(); check("isUnknownType()").that(actualNonNull().isUnknownType()).isFalse();
} }


public void isNotEmpty() { public void isNotEmpty() {
check("isEmptyType()").that(actual().isEmptyType()).isFalse(); check("isEmptyType()").that(actualNonNull().isEmptyType()).isFalse();
} }


public void isLiteralObject() { public void isLiteralObject() {
check("isLiteralObject()").that(actual().isLiteralObject()).isTrue(); check("isLiteralObject()").that(actualNonNull().isLiteralObject()).isTrue();
} }


public TypeSubject isObjectTypeWithProperty(String propName) { public TypeSubject isObjectTypeWithProperty(String propName) {
Expand All @@ -111,11 +132,11 @@ public TypeSubject isObjectTypeWithProperty(String propName) {
* so it should be run after {@link #isObjectTypeWithProperty}. * so it should be run after {@link #isObjectTypeWithProperty}.
*/ */
public TypeSubject withTypeOfProp(String propName) { public TypeSubject withTypeOfProp(String propName) {
check("isObjectType()").that(actual().isObjectType()).isTrue(); check("isObjectType()").that(actualNonNull().isObjectType()).isTrue();


return check("toMaybeObjectType().getPropertyType(%s)", propName) return check("toMaybeObjectType().getPropertyType(%s)", propName)
.about(types()) .about(types())
.that(actual().toMaybeObjectType().getPropertyType(propName)); .that(actualNonNull().toMaybeObjectType().getPropertyType(propName));
} }


public void isObjectTypeWithoutProperty(String propName) { public void isObjectTypeWithoutProperty(String propName) {
Expand All @@ -124,10 +145,100 @@ public void isObjectTypeWithoutProperty(String propName) {
} }


public void isSubtypeOf(JSType superType) { public void isSubtypeOf(JSType superType) {
check("isSubtypeOf(%s)", superType).that(actual().isSubtypeOf(superType)).isTrue(); check("isSubtypeOf(%s)", superType).that(actualNonNull().isSubtypeOf(superType)).isTrue();
} }


public void toStringIsEqualTo(String typeString) { public void toStringIsEqualTo(String typeString) {
check("toString()").that(actual().toString()).isEqualTo(typeString); check("toString()").that(actualNonNull().toString()).isEqualTo(typeString);
}

private JSType actualNonNull() {
isNotNull();
return actual();
}

private void checkEqualityAgainst(
@Nullable JSType provided, boolean expectation, Equivalence equivalence) {
String providedString = debugStringOf(provided);
String actualString = debugStringOf(actual());

boolean actualEqualsProvided = equivalence.test(actual(), provided);
if (actualEqualsProvided != expectation) {
failWithActual(
fact("Types expected to be equal", expectation), //
fact(equivalence.stringify(actualString, providedString), actualEqualsProvided), //
fact("provided", providedString));
}

boolean providedEqualsActual = equivalence.test(provided, actual());
if (actualEqualsProvided != providedEqualsActual) {
failWithActual(
simpleFact("Equality should be symmetric"), //
fact(equivalence.stringify(actualString, providedString), actualEqualsProvided),
fact(equivalence.stringify(providedString, actualString), providedEqualsActual),
fact("provided", providedString));
}

if (expectation) {
// TODO(nickreid): Use a `hash` method defined on `Equivalence`.
if (actual().hashCode() != provided.hashCode()) {
failWithActual(
simpleFact("If two types are equal their hashcodes must also be equal"), //
fact("actual.hashCode()", actual().hashCode()),
fact("provided.hashCode()", provided.hashCode()),
fact("provided", providedString));
}
}
}

private abstract static class Equivalence {
public final boolean test(@Nullable JSType receiver, @Nullable JSType parameter) {
// As long as a real value is provided for `receiver` we want to see how its methods handle
// any value of 'parameter', including `null`.
return (receiver == null) ? (parameter == null) : nullUnsafeTest(receiver, parameter);
}

/** Calls a method on {@code receiver}, passing {@code parameter}, that defines an equality. */
public abstract boolean nullUnsafeTest(JSType receiver, @Nullable JSType parameter);

/** Returns a representation of {@link #test()} on {@code receiver} and {@code parameter}. */
public abstract String stringify(@Nullable String receiver, @Nullable String parameter);
}

private static final Equivalence NATURAL_EQUIVALENCE =
new Equivalence() {
@Override
public boolean nullUnsafeTest(JSType receiver, @Nullable JSType parameter) {
return receiver.equals(parameter);
}

@Override
public String stringify(@Nullable String receiver, @Nullable String parameter) {
return "(" + receiver + ").equals(" + parameter + ")";
}
};

private static final Equivalence STRUCTURAL_EQUIVALENCE =
new Equivalence() {
@Override
public boolean nullUnsafeTest(JSType receiver, @Nullable JSType parameter) {
return receiver.isEquivalentTo(parameter, true);
}

@Override
public String stringify(@Nullable String receiver, @Nullable String parameter) {
return "(" + receiver + ").isEquivalentTo((" + parameter + "), true)";
}
};

@Override
protected String actualCustomStringRepresentation() {
return debugStringOf(actual());
}

private static String debugStringOf(JSType type) {
return (type == null)
? "[Java null]"
: type.toString() + " [instanceof " + type.getClass().getName() + "]";
} }
} }

0 comments on commit 930358e

Please sign in to comment.