• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2017 Google, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.truth.extensions.proto;
18 
19 import com.google.auto.value.AutoValue;
20 import com.google.auto.value.extension.memoized.Memoized;
21 import com.google.common.base.Optional;
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.collect.ImmutableListMultimap;
24 import com.google.common.collect.Iterables;
25 import com.google.common.collect.Ordering;
26 import com.google.common.collect.Sets;
27 import com.google.common.truth.extensions.proto.RecursableDiffEntity.WithResultCode.Result;
28 import com.google.errorprone.annotations.CanIgnoreReturnValue;
29 import com.google.errorprone.annotations.ForOverride;
30 import com.google.protobuf.Descriptors.FieldDescriptor;
31 import com.google.protobuf.Message;
32 import com.google.protobuf.TextFormat;
33 import com.google.protobuf.UnknownFieldSet;
34 import java.io.IOException;
35 import java.util.Set;
36 
37 /**
38  * Structural summary of the difference between two messages.
39  *
40  * <p>A {@code DiffResult} has singular fields, repeated fields, and unknowns, each with their own
41  * class. The inner classes may also contain their own {@code DiffResult}s for submessages.
42  *
43  * <p>These classes form a recursive, hierarchical relationship. Much of the common recursion logic
44  * across all the classes is in {@link RecursableDiffEntity}.
45  */
46 @AutoValue
47 abstract class DiffResult extends RecursableDiffEntity.WithoutResultCode {
48   /**
49    * Structural summary of the difference between two singular (non-repeated) fields.
50    *
51    * <p>It is possible for the result to be {@code ADDED} or {@code REMOVED}, even if {@code
52    * actual()} and {@code expected()} are identical. This occurs if the config does not have {@link
53    * FluentEqualityConfig#ignoringFieldAbsence()} enabled, one message had the field explicitly set
54    * to the default value, and the other did not.
55    */
56   @AutoValue
57   abstract static class SingularField extends RecursableDiffEntity.WithResultCode {
58     /** The type information for this field. May be absent if result code is {@code IGNORED}. */
subScopeId()59     abstract Optional<SubScopeId> subScopeId();
60 
61     /** The display name for this field. May include an array-index specifier. */
fieldName()62     abstract String fieldName();
63 
64     /** The field under test. */
actual()65     abstract Optional<Object> actual();
66 
67     /** The expected value of said field. */
expected()68     abstract Optional<Object> expected();
69 
70     /**
71      * The detailed breakdown of the comparison, only present if both objects are set on this
72      * instance and they are messages.
73      *
74      * <p>This does not necessarily mean the messages were set on the input protos.
75      */
breakdown()76     abstract Optional<DiffResult> breakdown();
77 
78     /**
79      * The detailed breakdown of the comparison, only present if both objects are set and they are
80      * {@link UnknownFieldSet}s.
81      *
82      * <p>This will only ever be set inside a parent {@link UnknownFieldSetDiff}. The top {@link
83      * UnknownFieldSetDiff} is set on the {@link DiffResult}, not here.
84      */
unknownsBreakdown()85     abstract Optional<UnknownFieldSetDiff> unknownsBreakdown();
86 
87     /** Returns {@code actual().get()}, or {@code expected().get()}, whichever is available. */
88     @Memoized
actualOrExpected()89     Object actualOrExpected() {
90       return actual().or(expected()).get();
91     }
92 
93     @Memoized
94     @Override
childEntities()95     Iterable<? extends RecursableDiffEntity> childEntities() {
96       return ImmutableList.copyOf(
97           Iterables.concat(breakdown().asSet(), unknownsBreakdown().asSet()));
98     }
99 
100     @Override
printContents(boolean includeMatches, String fieldPrefix, StringBuilder sb)101     final void printContents(boolean includeMatches, String fieldPrefix, StringBuilder sb) {
102       if (!includeMatches && isMatched()) {
103         return;
104       }
105 
106       fieldPrefix = newFieldPrefix(fieldPrefix, fieldName());
107       switch (result()) {
108         case ADDED:
109           sb.append("added: ").append(fieldPrefix).append(": ");
110           if (actual().get() instanceof Message) {
111             sb.append("\n").append(actual().get());
112           } else {
113             sb.append(valueString(subScopeId().get(), actual().get())).append("\n");
114           }
115           return;
116         case IGNORED:
117           sb.append("ignored: ").append(fieldPrefix).append("\n");
118           return;
119         case MATCHED:
120           sb.append("matched: ").append(fieldPrefix);
121           if (actualOrExpected() instanceof Message) {
122             sb.append("\n");
123             printChildContents(includeMatches, fieldPrefix, sb);
124           } else {
125             sb.append(": ")
126                 .append(valueString(subScopeId().get(), actualOrExpected()))
127                 .append("\n");
128           }
129           return;
130         case MODIFIED:
131           sb.append("modified: ").append(fieldPrefix);
132           if (actualOrExpected() instanceof Message) {
133             sb.append("\n");
134             printChildContents(includeMatches, fieldPrefix, sb);
135           } else {
136             sb.append(": ")
137                 .append(valueString(subScopeId().get(), expected().get()))
138                 .append(" -> ")
139                 .append(valueString(subScopeId().get(), actual().get()))
140                 .append("\n");
141           }
142           return;
143         case REMOVED:
144           sb.append("deleted: ").append(fieldPrefix).append(": ");
145           if (expected().get() instanceof Message) {
146             sb.append("\n").append(expected().get());
147           } else {
148             sb.append(valueString(subScopeId().get(), expected().get())).append("\n");
149           }
150           return;
151         default:
152           throw new AssertionError("Impossible: " + result());
153       }
154     }
155 
156     @Override
isContentEmpty()157     final boolean isContentEmpty() {
158       return false;
159     }
160 
ignored(String fieldName)161     static SingularField ignored(String fieldName) {
162       return newBuilder().setFieldName(fieldName).setResult(Result.IGNORED).build();
163     }
164 
newBuilder()165     static Builder newBuilder() {
166       return new AutoValue_DiffResult_SingularField.Builder();
167     }
168 
169     /** Builder for {@link SingularField}. */
170     @CanIgnoreReturnValue
171     @AutoValue.Builder
172     abstract static class Builder {
setResult(Result result)173       abstract Builder setResult(Result result);
174 
setSubScopeId(SubScopeId subScopeId)175       abstract Builder setSubScopeId(SubScopeId subScopeId);
176 
setFieldName(String fieldName)177       abstract Builder setFieldName(String fieldName);
178 
setActual(Object actual)179       abstract Builder setActual(Object actual);
180 
setExpected(Object expected)181       abstract Builder setExpected(Object expected);
182 
setBreakdown(DiffResult breakdown)183       abstract Builder setBreakdown(DiffResult breakdown);
184 
setUnknownsBreakdown(UnknownFieldSetDiff unknownsBreakdown)185       abstract Builder setUnknownsBreakdown(UnknownFieldSetDiff unknownsBreakdown);
186 
build()187       abstract SingularField build();
188     }
189   }
190 
191   /**
192    * Structural summary of the difference between two repeated fields.
193    *
194    * <p>This is only present if the user specified {@code ignoringRepeatedFieldOrder()}. Otherwise,
195    * the repeated elements are compared as singular fields, and there are no 'move' semantics.
196    */
197   @AutoValue
198   abstract static class RepeatedField extends RecursableDiffEntity.WithoutResultCode {
199 
200     /**
201      * Structural summary of the difference between two elements in two corresponding repeated
202      * fields, in the context of a {@link RepeatedField} diff.
203      *
204      * <p>The field indexes will only be present if the corresponding object is also present. If an
205      * object is absent, the PairResult represents an extra/missing element in the repeated field.
206      * If both are present but the indexes differ, it represents a 'move'.
207      */
208     @AutoValue
209     abstract static class PairResult extends RecursableDiffEntity.WithResultCode {
210       /** The {@link FieldDescriptor} describing the repeated field for this pair. */
fieldDescriptor()211       abstract FieldDescriptor fieldDescriptor();
212 
213       /** The index of the element in the {@code actual()} list that was matched. */
actualFieldIndex()214       abstract Optional<Integer> actualFieldIndex();
215 
216       /** The index of the element in the {@code expected()} list that was matched. */
expectedFieldIndex()217       abstract Optional<Integer> expectedFieldIndex();
218 
219       /** The element in the {@code actual()} list that was matched. */
actual()220       abstract Optional<Object> actual();
221 
222       /** The element in the {@code expected()} list that was matched. */
expected()223       abstract Optional<Object> expected();
224 
225       /**
226        * A detailed breakdown of the comparison between the messages. Present iff {@code actual()}
227        * and {@code expected()} are {@link Message}s.
228        */
breakdown()229       abstract Optional<DiffResult> breakdown();
230 
231       @Memoized
232       @Override
childEntities()233       Iterable<? extends RecursableDiffEntity> childEntities() {
234         return breakdown().asSet();
235       }
236 
237       /** Returns true if actual() and expected() contain Message types. */
238       @Memoized
isMessage()239       boolean isMessage() {
240         return actual().orNull() instanceof Message || expected().orNull() instanceof Message;
241       }
242 
indexed(String fieldPrefix, Optional<Integer> fieldIndex)243       private static String indexed(String fieldPrefix, Optional<Integer> fieldIndex) {
244         String index = fieldIndex.isPresent() ? fieldIndex.get().toString() : "?";
245         return fieldPrefix + "[" + index + "]";
246       }
247 
248       @Override
printContents(boolean includeMatches, String fieldPrefix, StringBuilder sb)249       final void printContents(boolean includeMatches, String fieldPrefix, StringBuilder sb) {
250         printContentsForRepeatedField(
251             /* includeSelfAlways = */ false, includeMatches, fieldPrefix, sb);
252       }
253 
254       // When printing results for a repeated field, we want to print matches even if
255       // !includeMatches if there's a mismatch on the repeated field itself, but not recursively.
256       // So we define a second printing method for use by the parent.
printContentsForRepeatedField( boolean includeSelfAlways, boolean includeMatches, String fieldPrefix, StringBuilder sb)257       final void printContentsForRepeatedField(
258           boolean includeSelfAlways, boolean includeMatches, String fieldPrefix, StringBuilder sb) {
259         if (!includeSelfAlways && !includeMatches && isMatched()) {
260           return;
261         }
262 
263         switch (result()) {
264           case ADDED:
265             sb.append("added: ").append(indexed(fieldPrefix, actualFieldIndex())).append(": ");
266             if (isMessage()) {
267               sb.append("\n").append(actual().get());
268             } else {
269               sb.append(valueString(fieldDescriptor(), actual().get())).append("\n");
270             }
271             return;
272           case IGNORED:
273             sb.append("ignored: ");
274             if (actualFieldIndex().equals(expectedFieldIndex())) {
275               sb.append(indexed(fieldPrefix, actualFieldIndex()));
276             } else {
277               sb.append(indexed(fieldPrefix, expectedFieldIndex()))
278                   .append(" -> ")
279                   .append(indexed(fieldPrefix, actualFieldIndex()));
280             }
281 
282             // We output the message contents for ignored pair results, since it's likely not clear
283             // from the index alone why they were ignored.
284             sb.append(":");
285             if (isMessage()) {
286               sb.append("\n");
287               printChildContents(includeMatches, indexed(fieldPrefix, actualFieldIndex()), sb);
288             } else {
289               sb.append(" ").append(valueString(fieldDescriptor(), actual().get())).append("\n");
290             }
291             return;
292           case MATCHED:
293             if (actualFieldIndex().get().equals(expectedFieldIndex().get())) {
294               sb.append("matched: ").append(indexed(fieldPrefix, actualFieldIndex()));
295             } else {
296               sb.append("moved: ")
297                   .append(indexed(fieldPrefix, expectedFieldIndex()))
298                   .append(" -> ")
299                   .append(indexed(fieldPrefix, actualFieldIndex()));
300             }
301             sb.append(":");
302             if (isMessage()) {
303               sb.append("\n");
304               printChildContents(includeMatches, indexed(fieldPrefix, actualFieldIndex()), sb);
305             } else {
306               sb.append(" ").append(valueString(fieldDescriptor(), actual().get())).append("\n");
307             }
308             return;
309           case MOVED_OUT_OF_ORDER:
310             sb.append("out_of_order: ")
311                 .append(indexed(fieldPrefix, expectedFieldIndex()))
312                 .append(" -> ")
313                 .append(indexed(fieldPrefix, actualFieldIndex()));
314             sb.append(":");
315             if (isMessage()) {
316               sb.append("\n");
317               printChildContents(includeMatches, indexed(fieldPrefix, actualFieldIndex()), sb);
318             } else {
319               sb.append(" ").append(valueString(fieldDescriptor(), actual().get())).append("\n");
320             }
321             return;
322           case MODIFIED:
323             sb.append("modified: ");
324             if (actualFieldIndex().get().equals(expectedFieldIndex().get())) {
325               sb.append(indexed(fieldPrefix, actualFieldIndex()));
326             } else {
327               sb.append(indexed(fieldPrefix, expectedFieldIndex()))
328                   .append(" -> ")
329                   .append(indexed(fieldPrefix, actualFieldIndex()));
330             }
331             sb.append(":");
332             if (isMessage()) {
333               sb.append("\n");
334               printChildContents(includeMatches, indexed(fieldPrefix, actualFieldIndex()), sb);
335             } else {
336               sb.append(" ")
337                   .append(valueString(fieldDescriptor(), expected().get()))
338                   .append(" -> ")
339                   .append(valueString(fieldDescriptor(), actual().get()));
340             }
341             return;
342           case REMOVED:
343             sb.append("deleted: ").append(indexed(fieldPrefix, expectedFieldIndex())).append(": ");
344             if (isMessage()) {
345               sb.append("\n").append(expected().get());
346             } else {
347               sb.append(valueString(fieldDescriptor(), expected().get())).append("\n");
348             }
349             return;
350         }
351         throw new AssertionError("Impossible: " + result());
352       }
353 
354       @Override
isContentEmpty()355       final boolean isContentEmpty() {
356         return false;
357       }
358 
toBuilder()359       abstract Builder toBuilder();
360 
newBuilder()361       static Builder newBuilder() {
362         return new AutoValue_DiffResult_RepeatedField_PairResult.Builder();
363       }
364 
365       @CanIgnoreReturnValue
366       @AutoValue.Builder
367       abstract static class Builder {
setResult(Result result)368         abstract Builder setResult(Result result);
369 
setFieldDescriptor(FieldDescriptor fieldDescriptor)370         abstract Builder setFieldDescriptor(FieldDescriptor fieldDescriptor);
371 
setActualFieldIndex(int actualFieldIndex)372         abstract Builder setActualFieldIndex(int actualFieldIndex);
373 
setExpectedFieldIndex(int expectedFieldIndex)374         abstract Builder setExpectedFieldIndex(int expectedFieldIndex);
375 
setActual(Object actual)376         abstract Builder setActual(Object actual);
377 
setExpected(Object expected)378         abstract Builder setExpected(Object expected);
379 
setBreakdown(DiffResult breakdown)380         abstract Builder setBreakdown(DiffResult breakdown);
381 
build()382         abstract PairResult build();
383       }
384     }
385 
386     /** The {@link FieldDescriptor} for this repeated field. */
fieldDescriptor()387     abstract FieldDescriptor fieldDescriptor();
388 
389     /** The elements under test. */
actual()390     abstract ImmutableList<Object> actual();
391 
392     /** The elements expected. */
expected()393     abstract ImmutableList<Object> expected();
394 
395     // TODO(user,peteg): Also provide a minimum-edit-distance pairing between unmatched elements,
396     // and the diff report between them.
397 
398     /** Pairs of elements which were diffed against each other. */
pairResults()399     abstract ImmutableList<PairResult> pairResults();
400 
401     @Memoized
402     @Override
childEntities()403     Iterable<? extends RecursableDiffEntity> childEntities() {
404       return pairResults();
405     }
406 
407     @Override
printContents(boolean includeMatches, String fieldPrefix, StringBuilder sb)408     final void printContents(boolean includeMatches, String fieldPrefix, StringBuilder sb) {
409       fieldPrefix = newFieldPrefix(fieldPrefix, fieldDescriptor().getName());
410       for (PairResult pairResult : pairResults()) {
411         pairResult.printContentsForRepeatedField(
412             /* includeSelfAlways = */ !isMatched(), includeMatches, fieldPrefix, sb);
413       }
414     }
415 
416     @Override
isContentEmpty()417     final boolean isContentEmpty() {
418       return pairResults().isEmpty();
419     }
420 
newBuilder()421     static Builder newBuilder() {
422       return new AutoValue_DiffResult_RepeatedField.Builder();
423     }
424 
425     @CanIgnoreReturnValue
426     @AutoValue.Builder
427     abstract static class Builder {
setFieldDescriptor(FieldDescriptor fieldDescriptor)428       abstract Builder setFieldDescriptor(FieldDescriptor fieldDescriptor);
429 
setActual(Iterable<?> actual)430       abstract Builder setActual(Iterable<?> actual);
431 
setExpected(Iterable<?> expected)432       abstract Builder setExpected(Iterable<?> expected);
433 
434       @ForOverride
pairResultsBuilder()435       abstract ImmutableList.Builder<PairResult> pairResultsBuilder();
436 
addPairResult(PairResult pairResult)437       final Builder addPairResult(PairResult pairResult) {
438         pairResultsBuilder().add(pairResult);
439         return this;
440       }
441 
build()442       abstract RepeatedField build();
443     }
444   }
445 
446   /** Structural summary of the difference between two unknown field sets. */
447   @AutoValue
448   abstract static class UnknownFieldSetDiff extends RecursableDiffEntity.WithoutResultCode {
449     /** The {@link UnknownFieldSet} being tested. */
actual()450     abstract Optional<UnknownFieldSet> actual();
451 
452     /** The {@link UnknownFieldSet} expected. */
expected()453     abstract Optional<UnknownFieldSet> expected();
454 
455     /**
456      * A list of top-level singular field comparison results.
457      *
458      * <p>All unknown fields are treated as repeated and with {@code ignoringRepeatedFieldOrder()}
459      * off, because we don't know their nature. If they're optional, only last element matters, but
460      * if they're repeated, all elements matter.
461      */
singularFields()462     abstract ImmutableListMultimap<Integer, SingularField> singularFields();
463 
464     @Memoized
465     @Override
childEntities()466     Iterable<? extends RecursableDiffEntity> childEntities() {
467       return singularFields().values();
468     }
469 
470     @Override
printContents(boolean includeMatches, String fieldPrefix, StringBuilder sb)471     final void printContents(boolean includeMatches, String fieldPrefix, StringBuilder sb) {
472       if (!includeMatches && isMatched()) {
473         return;
474       }
475 
476       for (int fieldNumber : singularFields().keySet()) {
477         for (SingularField singularField : singularFields().get(fieldNumber)) {
478           singularField.printContents(includeMatches, fieldPrefix, sb);
479         }
480       }
481     }
482 
483     @Override
isContentEmpty()484     final boolean isContentEmpty() {
485       return singularFields().isEmpty();
486     }
487 
newBuilder()488     static Builder newBuilder() {
489       return new AutoValue_DiffResult_UnknownFieldSetDiff.Builder();
490     }
491 
492     @CanIgnoreReturnValue
493     @AutoValue.Builder
494     abstract static class Builder {
setActual(UnknownFieldSet actual)495       abstract Builder setActual(UnknownFieldSet actual);
496 
setExpected(UnknownFieldSet expected)497       abstract Builder setExpected(UnknownFieldSet expected);
498 
499       @ForOverride
singularFieldsBuilder()500       abstract ImmutableListMultimap.Builder<Integer, SingularField> singularFieldsBuilder();
501 
addSingularField(int fieldNumber, SingularField singularField)502       final Builder addSingularField(int fieldNumber, SingularField singularField) {
503         singularFieldsBuilder().put(fieldNumber, singularField);
504         return this;
505       }
506 
addAllSingularFields(int fieldNumber, Iterable<SingularField> singularFields)507       final Builder addAllSingularFields(int fieldNumber, Iterable<SingularField> singularFields) {
508         singularFieldsBuilder().putAll(fieldNumber, singularFields);
509         return this;
510       }
511 
build()512       abstract UnknownFieldSetDiff build();
513     }
514   }
515 
516   /** The {@link Message} being tested. */
actual()517   abstract Message actual();
518 
519   /** The {@link Message} expected. */
expected()520   abstract Message expected();
521 
522   /** A list of top-level singular field comparison results grouped by field number. */
singularFields()523   abstract ImmutableListMultimap<Integer, SingularField> singularFields();
524 
525   /**
526    * A list of top-level repeated field comparison results grouped by field number.
527    *
528    * <p>This is only populated if {@link FluentEqualityConfig#ignoreRepeatedFieldOrder()} is set.
529    * Otherwise, repeated fields are compared strictly in index order, as singular fields.
530    */
repeatedFields()531   abstract ImmutableListMultimap<Integer, RepeatedField> repeatedFields();
532 
533   /**
534    * The result of comparing the message's {@link UnknownFieldSet}s. Not present if unknown fields
535    * were not compared.
536    */
unknownFields()537   abstract Optional<UnknownFieldSetDiff> unknownFields();
538 
539   @Memoized
540   @Override
childEntities()541   Iterable<? extends RecursableDiffEntity> childEntities() {
542     // Assemble the diffs in field number order so it most closely matches the schema.
543     ImmutableList.Builder<RecursableDiffEntity> builder =
544         ImmutableList.builderWithExpectedSize(
545             singularFields().size() + repeatedFields().size() + unknownFields().asSet().size());
546     Set<Integer> fieldNumbers = Sets.union(singularFields().keySet(), repeatedFields().keySet());
547     for (int fieldNumber : Ordering.natural().sortedCopy(fieldNumbers)) {
548       builder.addAll(singularFields().get(fieldNumber));
549       builder.addAll(repeatedFields().get(fieldNumber));
550     }
551     builder.addAll(unknownFields().asSet());
552     return builder.build();
553   }
554 
555   /** Prints the full {@link DiffResult} to a human-readable string, for use in test outputs. */
printToString(boolean reportMismatchesOnly)556   final String printToString(boolean reportMismatchesOnly) {
557     StringBuilder sb = new StringBuilder();
558 
559     if (!isMatched()) {
560       sb.append("Differences were found:\n");
561       printContents(/* includeMatches = */ false, /* fieldPrefix = */ "", sb);
562 
563       if (!reportMismatchesOnly && isAnyChildMatched()) {
564         sb.append("\nFull diff report:\n");
565         printContents(/* includeMatches = */ true, /* fieldPrefix = */ "", sb);
566       }
567     } else {
568       sb.append("No differences were found.");
569       if (!reportMismatchesOnly) {
570         if (isAnyChildIgnored()) {
571           sb.append("\nSome fields were ignored for comparison, however.\n");
572         } else {
573           sb.append("\nFull diff report:\n");
574         }
575         printContents(/* includeMatches = */ true, /* fieldPrefix = */ "", sb);
576       }
577     }
578 
579     return sb.toString();
580   }
581 
582   @Override
printContents(boolean includeMatches, String fieldPrefix, StringBuilder sb)583   final void printContents(boolean includeMatches, String fieldPrefix, StringBuilder sb) {
584     for (RecursableDiffEntity child : childEntities()) {
585       child.printContents(includeMatches, fieldPrefix, sb);
586     }
587   }
588 
589   @Override
isContentEmpty()590   final boolean isContentEmpty() {
591     return Iterables.isEmpty(childEntities());
592   }
593 
newBuilder()594   static Builder newBuilder() {
595     return new AutoValue_DiffResult.Builder();
596   }
597 
newFieldPrefix(String rootFieldPrefix, String toAdd)598   private static String newFieldPrefix(String rootFieldPrefix, String toAdd) {
599     return rootFieldPrefix.isEmpty() ? toAdd : (rootFieldPrefix + "." + toAdd);
600   }
601 
valueString(SubScopeId subScopeId, Object o)602   private static String valueString(SubScopeId subScopeId, Object o) {
603     switch (subScopeId.kind()) {
604       case FIELD_DESCRIPTOR:
605         return valueString(subScopeId.fieldDescriptor(), o);
606       case UNKNOWN_FIELD_DESCRIPTOR:
607         return valueString(subScopeId.unknownFieldDescriptor(), o);
608     }
609     throw new AssertionError(subScopeId.kind());
610   }
611 
valueString(FieldDescriptor fieldDescriptor, Object o)612   private static String valueString(FieldDescriptor fieldDescriptor, Object o) {
613     StringBuilder sb = new StringBuilder();
614     try {
615       TextFormat.printFieldValue(fieldDescriptor, o, sb);
616       return sb.toString();
617     } catch (IOException impossible) {
618       throw new AssertionError(impossible);
619     }
620   }
621 
valueString(UnknownFieldDescriptor unknownFieldDescriptor, Object o)622   private static String valueString(UnknownFieldDescriptor unknownFieldDescriptor, Object o) {
623     StringBuilder sb = new StringBuilder();
624     try {
625       TextFormat.printUnknownFieldValue(unknownFieldDescriptor.type().wireType(), o, sb);
626       return sb.toString();
627     } catch (IOException impossible) {
628       throw new AssertionError(impossible);
629     }
630   }
631 
632   @CanIgnoreReturnValue
633   @AutoValue.Builder
634   abstract static class Builder {
setActual(Message actual)635     abstract Builder setActual(Message actual);
636 
setExpected(Message expected)637     abstract Builder setExpected(Message expected);
638 
639     @ForOverride
singularFieldsBuilder()640     abstract ImmutableListMultimap.Builder<Integer, SingularField> singularFieldsBuilder();
641 
addSingularField(int fieldNumber, SingularField singularField)642     final Builder addSingularField(int fieldNumber, SingularField singularField) {
643       singularFieldsBuilder().put(fieldNumber, singularField);
644       return this;
645     }
646 
addAllSingularFields(int fieldNumber, Iterable<SingularField> singularFields)647     final Builder addAllSingularFields(int fieldNumber, Iterable<SingularField> singularFields) {
648       singularFieldsBuilder().putAll(fieldNumber, singularFields);
649       return this;
650     }
651 
652     @ForOverride
repeatedFieldsBuilder()653     abstract ImmutableListMultimap.Builder<Integer, RepeatedField> repeatedFieldsBuilder();
654 
addRepeatedField(int fieldNumber, RepeatedField repeatedField)655     final Builder addRepeatedField(int fieldNumber, RepeatedField repeatedField) {
656       repeatedFieldsBuilder().put(fieldNumber, repeatedField);
657       return this;
658     }
659 
setUnknownFields(UnknownFieldSetDiff unknownFields)660     abstract Builder setUnknownFields(UnknownFieldSetDiff unknownFields);
661 
build()662     abstract DiffResult build();
663   }
664 }
665