下面列出了com.google.common.collect.MapDifference#areEqual ( ) 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
@Override
protected boolean doEquivalent(SolrInputField o1, SolrInputField o2) {
if (o1.getValue() instanceof SolrInputDocument) {
if (!(o2.getValue() instanceof SolrInputDocument)) {
return false;
}
final MapDifference<String, SolrInputField> difference = Maps.difference(
(SolrInputDocument) o1.getValue(),
(SolrInputDocument) o2.getValue(),
this
);
if (!difference.areEqual()) {
return false;
}
} else {
if (o1.getValue() != o2.getValue()) {
return false;
}
}
return true;
}
private boolean getDiff(String captureInstanceName, Map<String, Integer> sourceTableColumnInfo, Map<String, Integer> cdcTableColumnInfo) {
MapDifference<String, Integer> diff = Maps.difference(sourceTableColumnInfo, cdcTableColumnInfo);
if (!diff.areEqual()) {
if (LOG.isTraceEnabled()) {
LOG.trace(
"Detected drift for table {} - new columns: {}, drop columns: {}",
captureInstanceName,
StringUtils.join(diff.entriesOnlyOnLeft().keySet(), ","),
StringUtils.join(diff.entriesOnlyOnRight().keySet(), ",")
);
}
return true;
}
return false;
}
private static final void loadAndCompareCloudSqlList(ClaimsListShard datastoreList) {
Optional<ClaimsList> maybeCloudSqlList = ClaimsListDao.getLatestRevision();
if (maybeCloudSqlList.isPresent()) {
ClaimsList cloudSqlList = maybeCloudSqlList.get();
MapDifference<String, String> diff =
Maps.difference(datastoreList.labelsToKeys, cloudSqlList.getLabelsToKeys());
if (!diff.areEqual()) {
if (diff.entriesDiffering().size() > 10) {
logger.atWarning().log(
String.format(
"Unequal claims lists detected, Cloud SQL list with revision id %d has %d"
+ " different records than the current Datastore list.",
cloudSqlList.getRevisionId(), diff.entriesDiffering().size()));
} else {
StringBuilder diffMessage = new StringBuilder("Unequal claims lists detected:\n");
diff.entriesDiffering().entrySet().stream()
.forEach(
entry -> {
String label = entry.getKey();
ValueDifference<String> valueDiff = entry.getValue();
diffMessage.append(
String.format(
"Domain label %s has key %s in Datastore and key %s in Cloud"
+ " SQL.\n",
label, valueDiff.leftValue(), valueDiff.rightValue()));
});
logger.atWarning().log(diffMessage.toString());
}
}
} else {
logger.atWarning().log("Claims list in Cloud SQL is empty.");
}
}
static <K, V> void assertMapsEqual(
Map<K, V> expected, Map<K, V> result, Equivalence<V> valueEquivalence) {
MapDifference<K, V> diff = Maps.difference(expected, result, valueEquivalence);
if (!diff.areEqual()) {
fail(
String.format(
"Maps differ. Entries differing: %s%nMissing entries: %s%nExtra entries: %s%n",
diff.entriesDiffering(), diff.entriesOnlyOnLeft(), diff.entriesOnlyOnRight()));
}
}
boolean isModified(Namespace namespace) {
Release release = releaseService.findLatestActiveRelease(namespace);
List<Item> items = itemService.findItemsWithoutOrdered(namespace.getId());
if (release == null) {
return hasNormalItems(items);
}
Map<String, String> releasedConfiguration = gson.fromJson(release.getConfigurations(), GsonType.CONFIG);
Map<String, String> configurationFromItems = generateConfigurationFromItems(namespace, items);
MapDifference<String, String> difference = Maps.difference(releasedConfiguration, configurationFromItems);
return !difference.areEqual();
}
private static final void loadAndCompareCloudSqlList(ReservedList datastoreList) {
Optional<google.registry.schema.tld.ReservedList> maybeCloudSqlList =
ReservedListDao.getLatestRevision(datastoreList.getName());
if (maybeCloudSqlList.isPresent()) {
Map<String, ReservedEntry> datastoreLabelsToReservations =
datastoreList.reservedListMap.entrySet().parallelStream()
.collect(
toImmutableMap(
Map.Entry::getKey,
entry ->
ReservedEntry.create(
entry.getValue().reservationType, entry.getValue().comment)));
google.registry.schema.tld.ReservedList cloudSqlList = maybeCloudSqlList.get();
MapDifference<String, ReservedEntry> diff =
Maps.difference(datastoreLabelsToReservations, cloudSqlList.getLabelsToReservations());
if (!diff.areEqual()) {
if (diff.entriesDiffering().size() > 10) {
logger.atWarning().log(
String.format(
"Unequal reserved lists detected, Cloud SQL list with revision"
+ " id %d has %d different records than the current"
+ " Datastore list.",
cloudSqlList.getRevisionId(), diff.entriesDiffering().size()));
} else {
StringBuilder diffMessage = new StringBuilder("Unequal reserved lists detected:\n");
diff.entriesDiffering()
.forEach(
(label, valueDiff) ->
diffMessage.append(
String.format(
"Domain label %s has entry %s in Datastore and entry"
+ " %s in Cloud SQL.\n",
label, valueDiff.leftValue(), valueDiff.rightValue())));
logger.atWarning().log(diffMessage.toString());
}
}
} else {
logger.atWarning().log("Reserved list in Cloud SQL is empty.");
}
}
@Test
public void testWriteFormatUnchanged() {
// Attempts to flag any changes in the storage format. While thorough, this check is not
// complete. It attempts to capture the entire schema by synthesizing a fully-populated
// instance of each Op type. For TUnions, the struct generator picks an arbitrary field to set,
// meaning that it will only see one of the multiple possible schemas for any given TUnion.
// These generated structs effectively give a view of the struct schema, which is compared to
// golden files in `goldens/current`.
Map<String, String> schemasByName = generateOpSchemas();
File goldensDir = getGoldensDir("current");
Map<String, String> goldensByName = loadGoldenSchemas(goldensDir);
MapDifference<String, String> difference = Maps.difference(goldensByName, schemasByName);
if (difference.areEqual()) {
return;
}
StringBuilder error = new StringBuilder();
StringBuilder remedy = new StringBuilder();
Set<String> removedOps = difference.entriesOnlyOnLeft().keySet();
if (!removedOps.isEmpty()) {
error.append("Removal of storage Op(s): ").append(removedOps)
.append("\nOps may only be removed after a release that")
.append("\n * formally deprecates the Op in release notes")
.append("\n * performs a no-op read of the Op type")
.append("\n * included warning logging when the Op was read")
.append("\n * ensures the Op is removed from storage")
.append("\n\nHowever, you should also consider leaving the Op indefinitely and removing")
.append("\nall fields as a safer alternative.");
remedy.append("deleting the files ")
.append(removedOps.stream()
.map(removed -> new File(goldensDir, removed).getAbsolutePath())
.collect(Collectors.joining(", ")));
}
String goldenChangeInstructions = Streams.concat(
difference.entriesOnlyOnRight().entrySet().stream(),
difference.entriesDiffering().entrySet().stream()
.map(entry ->
new SimpleImmutableEntry<>(entry.getKey(), entry.getValue().rightValue())))
.map(entry -> new StringBuilder()
.append("\n").append(new File(goldensDir, entry.getKey()).getPath()).append(":")
.append("\n").append(entry.getValue())
.toString())
.collect(Collectors.joining("\n"));
Set<String> addedOps = difference.entriesOnlyOnRight().keySet();
if (!addedOps.isEmpty()) {
error.append("Addition of storage Op(s): ").append(addedOps)
.append("\nOps may only be introduced")
.append("\n a.) in a release that supports reading but not writing the Op")
.append("\n b.) in a release that writes the Op only with an operator-controlled flag");
remedy.append("creating the following files")
.append(goldenChangeInstructions);
}
Map<String, ValueDifference<String>> modified = difference.entriesDiffering();
if (!modified.isEmpty()) {
error.append("Schema changes to Op(s): " + modified.keySet())
.append("\nThis check detects that changes occurred, not how the schema changed.")
.append("\nSome guidelines for evolving schemas:")
.append("\n * Introducing fields: you must handle reading records that do not")
.append("\n yet have the field set. This can be done with a backfill routine during")
.append("\n storage recovery if a field is required in some parts of the code")
.append("\n * Removing fields: must only be done after a release in which the field")
.append("\n is unused and announced as deprecated")
.append("\n * Changed fields: the type or thrift field ID of a field must never change");
remedy.append("changing the following files")
.append(goldenChangeInstructions);
}
fail(new StringBuilder()
.append("**** Storage compatibility change detected ****")
.append("\n")
.append(error)
.append("\n\nIf the necessary compatibility procedures have been performed,")
.append("\nyou may clear this check by ")
.append(remedy)
.toString());
}
/**
* Compares sets of config options, and returns the difference as a map of 'section.key' strings
* to pairs containing the different values.
*/
@VisibleForTesting
public static Map<String, ConfigChange> compare(
ImmutableMap<String, ImmutableMap<String, String>> rawConfig1,
ImmutableMap<String, ImmutableMap<String, String>> rawConfig2) {
MapDifference<String, ImmutableMap<String, String>> diffSections =
Maps.difference(rawConfig1, rawConfig2);
if (!diffSections.areEqual()) {
ImmutableMap.Builder<String, ConfigChange> result = ImmutableMap.builder();
BiConsumer<String, Map<String, ValueDifference<String>>> appendChange =
(section, diff) ->
diff.forEach(
(option, value) ->
result.put(
section + "." + option,
ImmutableConfigChange.of(value.leftValue(), value.rightValue())));
BiConsumer<String, Map<String, String>> appendLeft =
(section, diff) ->
diff.forEach(
(option, value) ->
result.put(section + "." + option, ImmutableConfigChange.of(value, null)));
BiConsumer<String, Map<String, String>> appendRight =
(section, diff) ->
diff.forEach(
(option, value) ->
result.put(section + "." + option, ImmutableConfigChange.of(null, value)));
diffSections
.entriesDiffering()
.forEach(
(section, diff) -> {
MapDifference<String, String> sectionDiff =
Maps.difference(diff.leftValue(), diff.rightValue());
appendChange.accept(section, sectionDiff.entriesDiffering());
appendLeft.accept(section, sectionDiff.entriesOnlyOnLeft());
appendRight.accept(section, sectionDiff.entriesOnlyOnRight());
});
diffSections.entriesOnlyOnLeft().forEach(appendLeft);
diffSections.entriesOnlyOnRight().forEach(appendRight);
return result.build();
}
return ImmutableMap.of();
}