Skip to content

Commit

Permalink
Add comparingExpectedFieldsOnly() to ProtoTruth.
Browse files Browse the repository at this point in the history
RELNOTES=Add `comparingExpectedFieldsOnly()` to ProtoTruth.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=190975698
  • Loading branch information
torquestomp authored and ronshapiro committed Apr 4, 2018
1 parent 4df2dc8 commit 08908c1
Show file tree
Hide file tree
Showing 11 changed files with 565 additions and 197 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.common.truth.extensions.proto;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.extensions.proto.FieldScopeUtil.join;

import com.google.auto.value.AutoValue;
Expand All @@ -25,10 +26,13 @@
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.truth.Correspondence;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Message;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;

/**
Expand All @@ -44,6 +48,7 @@ abstract class FluentEqualityConfig {
new AutoValue_FluentEqualityConfig.Builder()
.setIgnoreFieldAbsence(false)
.setIgnoreRepeatedFieldOrder(false)
.setCompareExpectedFieldsOnly(false)
.setFieldScopeLogic(FieldScopeLogic.all())
.setReportMismatchesOnly(false)
.setUsingCorrespondenceStringFunction(Functions.constant(""))
Expand Down Expand Up @@ -77,6 +82,16 @@ public ProtoTruthMessageDifferencer load(Descriptor descriptor) {

abstract Optional<Correspondence<Number, Number>> floatCorrespondence();

abstract boolean compareExpectedFieldsOnly();

// The full list of non-null Messages in the 'expected' part of the assertion. When set, the
// FieldScopeLogic should be narrowed appropriately if 'compareExpectedFieldsOnly()' is true.
//
// This field will be absent while the assertion is being composed, but *must* be set before
// passed to a message differencer. We check this to ensure no assertion path forgets to pass
// along the expected protos.
abstract Optional<ImmutableList<Message>> expectedMessages();

abstract FieldScopeLogic fieldScopeLogic();

// For pretty-printing, does not affect behavior.
Expand Down Expand Up @@ -125,6 +140,28 @@ final FluentEqualityConfig usingFloatTolerance(float tolerance) {
.build();
}

final FluentEqualityConfig comparingExpectedFieldsOnly() {
return toBuilder()
.setCompareExpectedFieldsOnly(true)
.addUsingCorrespondenceString(".comparingExpectedFieldsOnly()")
.build();
}

final FluentEqualityConfig withExpectedMessages(Iterable<? extends Message> messages) {
ImmutableList.Builder<Message> listBuilder = ImmutableList.builder();
for (Message message : messages) {
if (message != null) {
listBuilder.add(message);
}
}
Builder builder = toBuilder().setExpectedMessages(listBuilder.build());
if (compareExpectedFieldsOnly()) {
builder.setFieldScopeLogic(
FieldScopeLogic.and(fieldScopeLogic(), FieldScopes.fromSetFields(messages).logic()));
}
return builder.build();
}

final FluentEqualityConfig withPartialScope(FieldScope partialScope) {
return toBuilder()
.setFieldScopeLogic(FieldScopeLogic.and(fieldScopeLogic(), partialScope.logic()))
Expand Down Expand Up @@ -160,11 +197,13 @@ final FluentEqualityConfig ignoringFieldScope(FieldScope fieldScope) {
//////////////////////////////////////////////////////////////////////////////////////////////////

final ProtoTruthMessageDifferencer toMessageDifferencer(Descriptor descriptor) {
checkState(expectedMessages().isPresent(), "expectedMessages() not set");
return messageDifferencers.getUnchecked(descriptor);
}

final <M extends Message> Correspondence<M, M> toCorrespondence(
final Optional<Descriptor> optDescriptor) {
checkState(expectedMessages().isPresent(), "expectedMessages() not set");
return new Correspondence<M, M>() {
@Override
public final boolean compare(@Nullable M actual, @Nullable M expected) {
Expand Down Expand Up @@ -200,6 +239,7 @@ public final String toString() {

abstract Builder toBuilder();

@CanIgnoreReturnValue
@AutoValue.Builder
abstract static class Builder {
abstract Builder setIgnoreFieldAbsence(boolean ignoringFieldAbsence);
Expand All @@ -212,8 +252,13 @@ abstract static class Builder {

abstract Builder setFloatCorrespondence(Correspondence<Number, Number> floatCorrespondence);

abstract Builder setCompareExpectedFieldsOnly(boolean compare);

abstract Builder setExpectedMessages(ImmutableList<Message> messages);

abstract Builder setFieldScopeLogic(FieldScopeLogic fieldScopeLogic);

@CheckReturnValue
abstract Function<? super Optional<Descriptor>, String> usingCorrespondenceStringFunction();

abstract Builder setUsingCorrespondenceStringFunction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,22 @@
*/
package com.google.common.truth.extensions.proto;

import com.google.common.base.Function;
import com.google.common.truth.IterableSubject;
import com.google.common.truth.Ordered;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Message;
import javax.annotation.Nullable;

/**
* Fluent API to perform detailed, customizable comparison of iterables of protocol buffers. The
* same comparison rules are applied to all pairs of protocol buffers which get compared.
*
* <p>Methods may be chained in any order, but the chain should terminate with a method that doesn't
* return an IterableOfProtosFluentAssertion, such as {@link #containsExactly}, or {@link
* #containsAnyIn}.
* <p>Methods may be chained in any order, but the chain should terminate with a method from {@link
* IterableOfProtosUsingCorrespondence}.
*
* <p>The state of an {@code IterableOfProtosFluentAssertion} object after each method is called is
* left undefined. Users should not retain references to {@code IterableOfProtosFluentAssertion}
* instances.
*/
public interface IterableOfProtosFluentAssertion<M extends Message> {
public interface IterableOfProtosFluentAssertion<M extends Message>
extends IterableOfProtosUsingCorrespondence<M> {

/**
* Specifies that the 'has' bit of individual fields should be ignored when comparing for
Expand Down Expand Up @@ -133,6 +128,21 @@ public interface IterableOfProtosFluentAssertion<M extends Message> {
*/
IterableOfProtosFluentAssertion<M> usingFloatTolerance(float tolerance);

/**
* Limits the comparison of Protocol buffers to the fields set in the expected proto(s). When
* multiple protos are specified, the comparison is limited to the union of set fields in all the
* expected protos.
*
* <p>The "expected proto(s)" are those passed to the method in {@link
* IterableOfProtosUsingCorrespondence} at the end of the call-chain.
*
* <p>Fields not set in the expected proto(s) are ignored. In particular, proto3 fields which have
* their default values are ignored, as these are indistinguishable from unset fields. If you want
* to assert that a proto3 message has certain fields with default values, you cannot use this
* method.
*/
IterableOfProtosFluentAssertion<M> comparingExpectedFieldsOnly();

/**
* Limits the comparison of Protocol buffers to the defined {@link FieldScope}.
*
Expand Down Expand Up @@ -221,162 +231,6 @@ IterableOfProtosFluentAssertion<M> ignoringFieldDescriptors(
*/
IterableOfProtosFluentAssertion<M> reportingMismatchesOnly();

/**
* Specifies a way to pair up unexpected and missing elements in the message when an assertion
* fails. For example:
*
* <pre>{@code
* assertThat(actualFoos)
* .ignoringRepeatedFieldOrder()
* .ignoringFields(Foo.BAR_FIELD_NUMBER)
* .displayingDiffsPairedBy(Foo::getId)
* .containsExactlyElementsIn(expectedFoos);
* }</pre>
*
* <p>On assertions where it makes sense to do so, the elements are paired as follows: they are
* keyed by {@code keyFunction}, and if an unexpected element and a missing element have the same
* non-null key then the they are paired up. (Elements with null keys are not paired.) The failure
* message will show paired elements together, and a diff will be shown.
*
* <p>The expected elements given in the assertion should be uniquely keyed by {@link
* keyFunction}. If multiple missing elements have the same key then the pairing will be skipped.
*
* <p>Useful key functions will have the property that key equality is less strict than the
* already specified equality rules; i.e. given {@code actual} and {@code expected} values with
* keys {@code actualKey} and {@code expectedKey}, if {@code actual} and {@code expected} compare
* equal given the rest of the directives such as {@code ignoringRepeatedFieldOrder} and {@code
* ignoringFields}, then it is guaranteed that {@code actualKey} is equal to {@code expectedKey},
* but there are cases where {@code actualKey} is equal to {@code expectedKey} but the direct
* comparison fails.
*
* <p>Note that calling this method makes no difference to whether a test passes or fails, it just
* improves the message if it fails.
*/
IterableSubject.UsingCorrespondence<M, M> displayingDiffsPairedBy(
Function<? super M, ?> keyFunction);

/**
* Checks that the subject contains at least one element that corresponds to the given expected
* element.
*/
void contains(@Nullable M expected);

/** Checks that none of the actual elements correspond to the given element. */
void doesNotContain(@Nullable M excluded);

/**
* Checks that subject contains exactly elements that correspond to the expected elements, i.e.
* that there is a 1:1 mapping between the actual elements and the expected elements where each
* pair of elements correspond.
*
* <p>To also test that the contents appear in the given order, make a call to {@code inOrder()}
* on the object returned by this method.
*
* <p>To test that the iterable contains the same elements as an array, prefer {@link
* #containsExactlyElementsIn(Message[])}. It makes clear that the given array is a list of
* elements, not an element itself.
*/
@CanIgnoreReturnValue
Ordered containsExactly(@Nullable M... expected);

/**
* Checks that subject contains exactly elements that correspond to the expected elements, i.e.
* that there is a 1:1 mapping between the actual elements and the expected elements where each
* pair of elements correspond.
*
* <p>To also test that the contents appear in the given order, make a call to {@code inOrder()}
* on the object returned by this method.
*/
@CanIgnoreReturnValue
Ordered containsExactlyElementsIn(Iterable<? extends M> expected);

/**
* Checks that subject contains exactly elements that correspond to the expected elements, i.e.
* that there is a 1:1 mapping between the actual elements and the expected elements where each
* pair of elements correspond.
*
* <p>To also test that the contents appear in the given order, make a call to {@code inOrder()}
* on the object returned by this method.
*/
@CanIgnoreReturnValue
Ordered containsExactlyElementsIn(M[] expected);

/**
* Checks that the subject contains elements that corresponds to all of the expected elements,
* i.e. that there is a 1:1 mapping between any subset of the actual elements and the expected
* elements where each pair of elements correspond.
*
* <p>To also test that the contents appear in the given order, make a call to {@code inOrder()}
* on the object returned by this method. The elements must appear in the given order within the
* subject, but they are not required to be consecutive.
*/
@CanIgnoreReturnValue
Ordered containsAllOf(@Nullable M first, @Nullable M second, @Nullable M... rest);

/**
* Checks that the subject contains elements that corresponds to all of the expected elements,
* i.e. that there is a 1:1 mapping between any subset of the actual elements and the expected
* elements where each pair of elements correspond.
*
* <p>To also test that the contents appear in the given order, make a call to {@code inOrder()}
* on the object returned by this method. The elements must appear in the given order within the
* subject, but they are not required to be consecutive.
*/
@CanIgnoreReturnValue
Ordered containsAllIn(Iterable<? extends M> expected);

/**
* Checks that the subject contains elements that corresponds to all of the expected elements,
* i.e. that there is a 1:1 mapping between any subset of the actual elements and the expected
* elements where each pair of elements correspond.
*
* <p>To also test that the contents appear in the given order, make a call to {@code inOrder()}
* on the object returned by this method. The elements must appear in the given order within the
* subject, but they are not required to be consecutive.
*/
@CanIgnoreReturnValue
Ordered containsAllIn(M[] expected);

/**
* Checks that the subject contains at least one element that corresponds to at least one of the
* expected elements.
*/
void containsAnyOf(@Nullable M first, @Nullable M second, @Nullable M... rest);

/**
* Checks that the subject contains at least one element that corresponds to at least one of the
* expected elements.
*/
void containsAnyIn(Iterable<? extends M> expected);

/**
* Checks that the subject contains at least one element that corresponds to at least one of the
* expected elements.
*/
void containsAnyIn(M[] expected);

/**
* Checks that the subject contains no elements that correspond to any of the given elements.
* (Duplicates are irrelevant to this test, which fails if any of the subject elements correspond
* to any of the given elements.)
*/
void containsNoneOf(
@Nullable M firstExcluded, @Nullable M secondExcluded, @Nullable M... restOfExcluded);

/**
* Checks that the subject contains no elements that correspond to any of the given elements.
* (Duplicates are irrelevant to this test, which fails if any of the subject elements correspond
* to any of the given elements.)
*/
void containsNoneIn(Iterable<? extends M> excluded);

/**
* Checks that the subject contains no elements that correspond to any of the given elements.
* (Duplicates are irrelevant to this test, which fails if any of the subject elements correspond
* to any of the given elements.)
*/
void containsNoneIn(M[] excluded);

/**
* @deprecated Do not call {@code equals()} on a {@code IterableOfProtosFluentAssertion}.
* @see com.google.common.truth.Subject#equals(Object)
Expand Down
Loading

0 comments on commit 08908c1

Please sign in to comment.