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