Add helper for comparing a bundle of Change-related entities
Currently this just does a field-by-field comparison of all entities rooted at a single change. Eventually it will understand the subtle differences between ReviewDb and NoteDb (e.g. timestamp rounding), and will be used for consistency checks before/after migration. Change-Id: I8bd97bcee7a907bed2126aaf6f77bba993374884
This commit is contained in:
@@ -55,21 +55,6 @@ public final class PatchLineComment {
|
||||
public void set(String newValue) {
|
||||
uuid = newValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("PatchLineComment.Key{");
|
||||
builder.append("Change.Id=")
|
||||
.append(getParentKey().getParentKey().getParentKey().get()).append(',');
|
||||
builder.append("PatchSet.Id=")
|
||||
.append(getParentKey().getParentKey().get()).append(',');
|
||||
builder.append("filename=")
|
||||
.append(getParentKey().getFileName()).append(',');
|
||||
builder.append("uuid=").append(get());
|
||||
builder.append("}");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static final char STATUS_DRAFT = 'd';
|
||||
|
@@ -198,6 +198,7 @@ java_test(
|
||||
'//lib:grappa',
|
||||
'//lib:guava',
|
||||
'//lib:guava-retrying',
|
||||
'//lib:protobuf',
|
||||
'//lib/dropwizard:dropwizard-core',
|
||||
'//lib/guice:guice-assistedinject',
|
||||
'//lib/prolog:runtime',
|
||||
|
@@ -0,0 +1,366 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.notedb;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||
import com.google.gerrit.reviewdb.client.Patch;
|
||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.PatchLineCommentsUtil;
|
||||
import com.google.gwtorm.client.Column;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* A bundle of all entities rooted at a single {@link Change} entity.
|
||||
* <p>
|
||||
* See the {@link Change} Javadoc for a depiction of this tree. Bundles may be
|
||||
* compared using {@link #differencesFrom(ChangeBundle)}, which normalizes out
|
||||
* the minor implementation differences between ReviewDb and NoteDb.
|
||||
*/
|
||||
public class ChangeBundle {
|
||||
public static ChangeBundle fromReviewDb(ReviewDb db, Change.Id id)
|
||||
throws OrmException {
|
||||
db.changes().beginTransaction(id);
|
||||
try {
|
||||
return new ChangeBundle(
|
||||
db.changes().get(id),
|
||||
db.changeMessages().byChange(id),
|
||||
db.patchSets().byChange(id),
|
||||
db.patchSetApprovals().byChange(id),
|
||||
db.patchComments().byChange(id));
|
||||
} finally {
|
||||
db.rollback();
|
||||
}
|
||||
}
|
||||
|
||||
public static ChangeBundle fromNotes(PatchLineCommentsUtil plcUtil,
|
||||
ChangeNotes notes) throws OrmException {
|
||||
return new ChangeBundle(
|
||||
notes.getChange(),
|
||||
notes.getChangeMessages(),
|
||||
notes.getPatchSets().values(),
|
||||
notes.getApprovals().values(),
|
||||
Iterables.concat(
|
||||
plcUtil.draftByChange(null, notes),
|
||||
plcUtil.publishedByChange(null, notes)));
|
||||
}
|
||||
|
||||
private static Map<ChangeMessage.Key, ChangeMessage> changeMessageMap(
|
||||
Iterable<ChangeMessage> in) {
|
||||
Map<ChangeMessage.Key, ChangeMessage> out = new TreeMap<>(
|
||||
new Comparator<ChangeMessage.Key>() {
|
||||
@Override
|
||||
public int compare(ChangeMessage.Key a, ChangeMessage.Key b) {
|
||||
return ComparisonChain.start()
|
||||
.compare(a.getParentKey().get(), b.getParentKey().get())
|
||||
.compare(a.get(), b.get())
|
||||
.result();
|
||||
}
|
||||
});
|
||||
for (ChangeMessage cm : in) {
|
||||
out.put(cm.getKey(), cm);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Map<PatchSet.Id, PatchSet> patchSetMap(Iterable<PatchSet> in) {
|
||||
Map<PatchSet.Id, PatchSet> out = new TreeMap<>(
|
||||
new Comparator<PatchSet.Id>() {
|
||||
@Override
|
||||
public int compare(PatchSet.Id a, PatchSet.Id b) {
|
||||
return patchSetIdChain(a, b).result();
|
||||
}
|
||||
});
|
||||
for (PatchSet ps : in) {
|
||||
out.put(ps.getId(), ps);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Map<PatchSetApproval.Key, PatchSetApproval>
|
||||
patchSetApprovalMap(Iterable<PatchSetApproval> in) {
|
||||
Map<PatchSetApproval.Key, PatchSetApproval> out = new TreeMap<>(
|
||||
new Comparator<PatchSetApproval.Key>() {
|
||||
@Override
|
||||
public int compare(PatchSetApproval.Key a, PatchSetApproval.Key b) {
|
||||
return patchSetIdChain(a.getParentKey(), b.getParentKey())
|
||||
.compare(a.getAccountId().get(), b.getAccountId().get())
|
||||
.compare(a.getLabelId(), b.getLabelId())
|
||||
.result();
|
||||
}
|
||||
});
|
||||
for (PatchSetApproval psa : in) {
|
||||
out.put(psa.getKey(), psa);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Map<PatchLineComment.Key, PatchLineComment>
|
||||
patchLineCommentMap(Iterable<PatchLineComment> in) {
|
||||
Map<PatchLineComment.Key, PatchLineComment> out = new TreeMap<>(
|
||||
new Comparator<PatchLineComment.Key>() {
|
||||
@Override
|
||||
public int compare(PatchLineComment.Key a, PatchLineComment.Key b) {
|
||||
Patch.Key pka = a.getParentKey();
|
||||
Patch.Key pkb = b.getParentKey();
|
||||
return patchSetIdChain(pka.getParentKey(), pkb.getParentKey())
|
||||
.compare(pka.get(), pkb.get())
|
||||
.compare(a.get(), b.get())
|
||||
.result();
|
||||
}
|
||||
});
|
||||
for (PatchLineComment plc : in) {
|
||||
out.put(plc.getKey(), plc);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static ComparisonChain patchSetIdChain(PatchSet.Id a, PatchSet.Id b) {
|
||||
return ComparisonChain.start()
|
||||
.compare(a.getParentKey().get(), b.getParentKey().get())
|
||||
.compare(a.get(), b.get());
|
||||
}
|
||||
|
||||
private static void checkColumns(Class<?> clazz, Integer... expected) {
|
||||
Set<Integer> ids = new TreeSet<>();
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
Column col = f.getAnnotation(Column.class);
|
||||
if (col != null) {
|
||||
ids.add(col.id());
|
||||
}
|
||||
}
|
||||
Set<Integer> expectedIds = Sets.newTreeSet(Arrays.asList(expected));
|
||||
checkState(ids.equals(expectedIds),
|
||||
"Unexpected column set for %s: %s != %s",
|
||||
clazz.getSimpleName(), ids, expectedIds);
|
||||
}
|
||||
|
||||
static {
|
||||
// Initialization-time checks that the column set hasn't changed since the
|
||||
// last time this file was updated.
|
||||
checkColumns(Change.Id.class, 1);
|
||||
checkColumns(Change.class, 1, 2, 3, 4, 5, 7, 8, 10, 12, 13, 14, 17, 18);
|
||||
checkColumns(ChangeMessage.Key.class, 1, 2);
|
||||
checkColumns(ChangeMessage.class, 1, 2, 3, 4, 5);
|
||||
checkColumns(PatchSet.Id.class, 1, 2);
|
||||
checkColumns(PatchSet.class, 1, 2, 3, 4, 5, 6, 8);
|
||||
checkColumns(PatchSetApproval.Key.class, 1, 2, 3);
|
||||
checkColumns(PatchSetApproval.class, 1, 2, 3);
|
||||
checkColumns(PatchLineComment.Key.class, 1, 2);
|
||||
checkColumns(PatchLineComment.class, 1, 2, 3, 4, 5, 6, 7, 8, 9);
|
||||
}
|
||||
|
||||
private final Change change;
|
||||
private final ImmutableMap<ChangeMessage.Key, ChangeMessage> changeMessages;
|
||||
private final ImmutableMap<PatchSet.Id, PatchSet> patchSets;
|
||||
private final ImmutableMap<PatchSetApproval.Key, PatchSetApproval>
|
||||
patchSetApprovals;
|
||||
private final ImmutableMap<PatchLineComment.Key, PatchLineComment>
|
||||
patchLineComments;
|
||||
|
||||
@VisibleForTesting
|
||||
ChangeBundle(
|
||||
Change change,
|
||||
Iterable<ChangeMessage> changeMessages,
|
||||
Iterable<PatchSet> patchSets,
|
||||
Iterable<PatchSetApproval> patchSetApprovals,
|
||||
Iterable<PatchLineComment> patchLineComments) {
|
||||
this.change = checkNotNull(change);
|
||||
this.changeMessages = ImmutableMap.copyOf(changeMessageMap(changeMessages));
|
||||
this.patchSets = ImmutableMap.copyOf(patchSetMap(patchSets));
|
||||
this.patchSetApprovals =
|
||||
ImmutableMap.copyOf(patchSetApprovalMap(patchSetApprovals));
|
||||
this.patchLineComments =
|
||||
ImmutableMap.copyOf(patchLineCommentMap(patchLineComments));
|
||||
|
||||
for (ChangeMessage.Key k : this.changeMessages.keySet()) {
|
||||
checkArgument(k.getParentKey().equals(change.getId()));
|
||||
}
|
||||
for (PatchSet.Id id : this.patchSets.keySet()) {
|
||||
checkArgument(id.getParentKey().equals(change.getId()));
|
||||
}
|
||||
for (PatchSetApproval.Key k : this.patchSetApprovals.keySet()) {
|
||||
checkArgument(k.getParentKey().getParentKey().equals(change.getId()));
|
||||
}
|
||||
for (PatchLineComment.Key k : this.patchLineComments.keySet()) {
|
||||
checkArgument(k.getParentKey().getParentKey().getParentKey()
|
||||
.equals(change.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableList<String> differencesFrom(ChangeBundle o) {
|
||||
List<String> diffs = new ArrayList<>();
|
||||
diffChanges(diffs, this, o);
|
||||
diffChangeMessages(diffs, this, o);
|
||||
diffPatchSets(diffs, this, o);
|
||||
diffPatchSetApprovals(diffs, this, o);
|
||||
diffPatchLineComments(diffs, this, o);
|
||||
return ImmutableList.copyOf(diffs);
|
||||
}
|
||||
|
||||
private static void diffChanges(List<String> diffs, ChangeBundle bundleA,
|
||||
ChangeBundle bundleB) {
|
||||
Change a = bundleA.change;
|
||||
Change b = bundleB.change;
|
||||
String desc = a.getId().equals(b.getId()) ? describe(a.getId()) : "Changes";
|
||||
diffColumns(diffs, Change.class, desc, a, b);
|
||||
}
|
||||
|
||||
private static void diffChangeMessages(List<String> diffs,
|
||||
ChangeBundle bundleA, ChangeBundle bundleB) {
|
||||
Map<ChangeMessage.Key, ChangeMessage> as = bundleA.changeMessages;
|
||||
Map<ChangeMessage.Key, ChangeMessage> bs = bundleB.changeMessages;
|
||||
for (ChangeMessage.Key k : diffKeySets(diffs, as, bs)) {
|
||||
diffColumns(
|
||||
diffs, ChangeMessage.class, describe(k), as.get(k), bs.get(k));
|
||||
}
|
||||
}
|
||||
|
||||
private static void diffPatchSets(List<String> diffs, ChangeBundle bundleA,
|
||||
ChangeBundle bundleB) {
|
||||
Map<PatchSet.Id, PatchSet> as = bundleA.patchSets;
|
||||
Map<PatchSet.Id, PatchSet> bs = bundleB.patchSets;
|
||||
for (PatchSet.Id id : diffKeySets(diffs, as, bs)) {
|
||||
diffColumns(diffs, PatchSet.class, describe(id), as.get(id), bs.get(id));
|
||||
}
|
||||
}
|
||||
|
||||
private static void diffPatchSetApprovals(List<String> diffs,
|
||||
ChangeBundle bundleA, ChangeBundle bundleB) {
|
||||
Map<PatchSetApproval.Key, PatchSetApproval> as = bundleA.patchSetApprovals;
|
||||
Map<PatchSetApproval.Key, PatchSetApproval> bs = bundleB.patchSetApprovals;
|
||||
for (PatchSetApproval.Key k : diffKeySets(diffs, as, bs)) {
|
||||
diffColumns(
|
||||
diffs, PatchSetApproval.class, describe(k), as.get(k), bs.get(k));
|
||||
}
|
||||
}
|
||||
|
||||
private static void diffPatchLineComments(List<String> diffs,
|
||||
ChangeBundle bundleA, ChangeBundle bundleB) {
|
||||
Map<PatchLineComment.Key, PatchLineComment> as = bundleA.patchLineComments;
|
||||
Map<PatchLineComment.Key, PatchLineComment> bs = bundleB.patchLineComments;
|
||||
for (PatchLineComment.Key k : diffKeySets(diffs, as, bs)) {
|
||||
diffColumns(
|
||||
diffs, PatchLineComment.class, describe(k), as.get(k), bs.get(k));
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> Set<T> diffKeySets(List<String> diffs, Map<T, ?> a,
|
||||
Map<T, ?> b) {
|
||||
Set<T> as = a.keySet();
|
||||
Set<T> bs = b.keySet();
|
||||
if (as.isEmpty() && bs.isEmpty()) {
|
||||
return as;
|
||||
}
|
||||
String clazz = keyClass((!as.isEmpty() ? as : bs).iterator().next());
|
||||
|
||||
Set<T> aNotB = Sets.difference(as, bs);
|
||||
Set<T> bNotA = Sets.difference(bs, as);
|
||||
if (aNotB.isEmpty() && bNotA.isEmpty()) {
|
||||
return as;
|
||||
}
|
||||
diffs.add(clazz + " sets differ: " + aNotB + " only in A; "
|
||||
+ bNotA + " only in B");
|
||||
return Sets.intersection(as, bs);
|
||||
}
|
||||
|
||||
private static <T> void diffColumns(List<String> diffs, Class<T> clazz,
|
||||
String desc, T a, T b) {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
Column col = f.getAnnotation(Column.class);
|
||||
if (col == null) {
|
||||
continue;
|
||||
}
|
||||
f.setAccessible(true);
|
||||
try {
|
||||
diffValues(diffs, desc, f.get(a), f.get(b), f.getName());
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void diffValues(List<String> diffs, String desc, Object va,
|
||||
Object vb, String name) {
|
||||
if (!Objects.equals(va, vb)) {
|
||||
diffs.add(
|
||||
name + " differs for " + desc + ": {" + va + "} != {" + vb + "}");
|
||||
}
|
||||
}
|
||||
|
||||
private static String describe(Object key) {
|
||||
return keyClass(key) + " " + key;
|
||||
}
|
||||
|
||||
private static String keyClass(Object obj) {
|
||||
Class<?> clazz = obj.getClass();
|
||||
String name = clazz.getSimpleName();
|
||||
checkArgument(name.equals("Key") || name.equals("Id"),
|
||||
"not an Id/Key class: %s", name);
|
||||
return clazz.getEnclosingClass().getSimpleName() + "." + name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof ChangeBundle)) {
|
||||
return false;
|
||||
}
|
||||
return differencesFrom((ChangeBundle) o).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
change.getId(),
|
||||
changeMessages.keySet(),
|
||||
patchSets.keySet(),
|
||||
patchSetApprovals.keySet(),
|
||||
patchLineComments.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "{id=" + change.getId()
|
||||
+ ", ChangeMessage[" + changeMessages.size() + "]"
|
||||
+ ", PatchSet[" + patchSets.size() + "]"
|
||||
+ ", PatchSetApproval[" + patchSetApprovals.size() + "]"
|
||||
+ ", PatchLineComment[" + patchLineComments.size() + "]"
|
||||
+ "}";
|
||||
}
|
||||
}
|
@@ -0,0 +1,367 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.notedb;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import com.google.gerrit.common.TimeUtil;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||
import com.google.gerrit.reviewdb.client.LabelId;
|
||||
import com.google.gerrit.reviewdb.client.Patch;
|
||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.RevId;
|
||||
import com.google.gerrit.testutil.TestChanges;
|
||||
import com.google.gerrit.testutil.TestTimeUtil;
|
||||
import com.google.gwtorm.client.KeyUtil;
|
||||
import com.google.gwtorm.protobuf.CodecFactory;
|
||||
import com.google.gwtorm.protobuf.ProtobufCodec;
|
||||
import com.google.gwtorm.server.StandardKeyEncoder;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class ChangeBundleTest {
|
||||
static {
|
||||
KeyUtil.setEncoderImpl(new StandardKeyEncoder());
|
||||
}
|
||||
|
||||
private static final ProtobufCodec<Change> CHANGE_CODEC =
|
||||
CodecFactory.encoder(Change.class);
|
||||
private static final ProtobufCodec<ChangeMessage> CHANGE_MESSAGE_CODEC =
|
||||
CodecFactory.encoder(ChangeMessage.class);
|
||||
private static final ProtobufCodec<PatchSet> PATCH_SET_CODEC =
|
||||
CodecFactory.encoder(PatchSet.class);
|
||||
private static final ProtobufCodec<PatchSetApproval>
|
||||
PATCH_SET_APPROVAL_CODEC = CodecFactory.encoder(PatchSetApproval.class);
|
||||
private static final ProtobufCodec<PatchLineComment>
|
||||
PATCH_LINE_COMMENT_CODEC = CodecFactory.encoder(PatchLineComment.class);
|
||||
|
||||
private String systemTimeZoneProperty;
|
||||
private TimeZone systemTimeZone;
|
||||
|
||||
private Project.NameKey project;
|
||||
private Account.Id accountId;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
String tz = "US/Eastern";
|
||||
systemTimeZoneProperty = System.setProperty("user.timezone", tz);
|
||||
systemTimeZone = TimeZone.getDefault();
|
||||
TimeZone.setDefault(TimeZone.getTimeZone(tz));
|
||||
TestTimeUtil.resetWithClockStep(1, SECONDS);
|
||||
project = new Project.NameKey("project");
|
||||
accountId = new Account.Id(100);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestTimeUtil.useSystemTime();
|
||||
System.setProperty("user.timezone", systemTimeZoneProperty);
|
||||
TimeZone.setDefault(systemTimeZone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffChangesDifferentIds() throws Exception {
|
||||
Change c1 = TestChanges.newChange(project, accountId);
|
||||
int id1 = c1.getId().get();
|
||||
Change c2 = TestChanges.newChange(project, accountId);
|
||||
int id2 = c2.getId().get();
|
||||
ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
|
||||
comments());
|
||||
ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
|
||||
comments());
|
||||
|
||||
assertDiffs(b1, b2,
|
||||
"changeId differs for Changes: {" + id1 + "} != {" + id2 + "}",
|
||||
"createdOn differs for Changes:"
|
||||
+ " {2009-09-30 17:00:00.0} != {2009-09-30 17:00:01.0}",
|
||||
"lastUpdatedOn differs for Changes:"
|
||||
+ " {2009-09-30 17:00:00.0} != {2009-09-30 17:00:01.0}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffChangesSameId() throws Exception {
|
||||
Change c1 = TestChanges.newChange(
|
||||
new Project.NameKey("project"), new Account.Id(100));
|
||||
Change c2 = clone(c1);
|
||||
ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
|
||||
comments());
|
||||
ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
|
||||
comments());
|
||||
|
||||
assertNoDiffs(b1, b2);
|
||||
|
||||
c2.setTopic("topic");
|
||||
assertDiffs(b1, b2,
|
||||
"topic differs for Change.Id "+ c1.getId() + ": {null} != {topic}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffChangeMessageKeySets() throws Exception {
|
||||
Change c = TestChanges.newChange(project, accountId);
|
||||
int id = c.getId().get();
|
||||
ChangeMessage cm1 = new ChangeMessage(
|
||||
new ChangeMessage.Key(c.getId(), "uuid1"),
|
||||
accountId, TimeUtil.nowTs(), c.currentPatchSetId());
|
||||
ChangeMessage cm2 = new ChangeMessage(
|
||||
new ChangeMessage.Key(c.getId(), "uuid2"),
|
||||
accountId, TimeUtil.nowTs(), c.currentPatchSetId());
|
||||
ChangeBundle b1 = new ChangeBundle(c, messages(cm1), patchSets(),
|
||||
approvals(), comments());
|
||||
ChangeBundle b2 = new ChangeBundle(c, messages(cm2), patchSets(),
|
||||
approvals(), comments());
|
||||
|
||||
assertDiffs(b1, b2,
|
||||
"ChangeMessage.Key sets differ:"
|
||||
+ " [" + id + ",uuid1] only in A; [" + id + ",uuid2] only in B");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffChangeMessages() throws Exception {
|
||||
Change c = TestChanges.newChange(project, accountId);
|
||||
ChangeMessage cm1 = new ChangeMessage(
|
||||
new ChangeMessage.Key(c.getId(), "uuid"),
|
||||
accountId, TimeUtil.nowTs(), c.currentPatchSetId());
|
||||
cm1.setMessage("message 1");
|
||||
ChangeMessage cm2 = clone(cm1);
|
||||
ChangeBundle b1 = new ChangeBundle(c, messages(cm1), patchSets(),
|
||||
approvals(), comments());
|
||||
ChangeBundle b2 = new ChangeBundle(c, messages(cm2), patchSets(),
|
||||
approvals(), comments());
|
||||
|
||||
assertNoDiffs(b1, b2);
|
||||
|
||||
cm2.setMessage("message 2");
|
||||
assertDiffs(b1, b2,
|
||||
"message differs for ChangeMessage.Key " + c.getId() + ",uuid:"
|
||||
+ " {message 1} != {message 2}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffPatchSetIdSets() throws Exception {
|
||||
Change c = TestChanges.newChange(project, accountId);
|
||||
TestChanges.incrementPatchSet(c);
|
||||
|
||||
PatchSet ps1 = new PatchSet(new PatchSet.Id(c.getId(), 1));
|
||||
ps1.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
|
||||
ps1.setUploader(accountId);
|
||||
ps1.setCreatedOn(TimeUtil.nowTs());
|
||||
PatchSet ps2 = new PatchSet(new PatchSet.Id(c.getId(), 2));
|
||||
ps2.setRevision(new RevId("badc0feebadc0feebadc0feebadc0feebadc0fee"));
|
||||
ps2.setUploader(accountId);
|
||||
ps2.setCreatedOn(TimeUtil.nowTs());
|
||||
|
||||
ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(ps2),
|
||||
approvals(), comments());
|
||||
ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(ps1, ps2),
|
||||
approvals(), comments());
|
||||
|
||||
assertDiffs(b1, b2,
|
||||
"PatchSet.Id sets differ:"
|
||||
+ " [] only in A; [" + c.getId() + ",1] only in B");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffPatchSets() throws Exception {
|
||||
Change c = TestChanges.newChange(project, accountId);
|
||||
PatchSet ps1 = new PatchSet(c.currentPatchSetId());
|
||||
ps1.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
|
||||
ps1.setUploader(accountId);
|
||||
ps1.setCreatedOn(TimeUtil.nowTs());
|
||||
PatchSet ps2 = clone(ps1);
|
||||
ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(ps1),
|
||||
approvals(), comments());
|
||||
ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(ps2),
|
||||
approvals(), comments());
|
||||
|
||||
assertNoDiffs(b1, b2);
|
||||
|
||||
ps2.setRevision(new RevId("badc0feebadc0feebadc0feebadc0feebadc0fee"));
|
||||
assertDiffs(b1, b2,
|
||||
"revision differs for PatchSet.Id " + c.getId() + ",1:"
|
||||
+ " {RevId{deadbeefdeadbeefdeadbeefdeadbeefdeadbeef}}"
|
||||
+ " != {RevId{badc0feebadc0feebadc0feebadc0feebadc0fee}}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffPatchSetApprovalKeySets() throws Exception {
|
||||
Change c = TestChanges.newChange(project, accountId);
|
||||
int id = c.getId().get();
|
||||
PatchSetApproval a1 = new PatchSetApproval(
|
||||
new PatchSetApproval.Key(
|
||||
c.currentPatchSetId(), accountId, new LabelId("Code-Review")),
|
||||
(short) 1,
|
||||
TimeUtil.nowTs());
|
||||
PatchSetApproval a2 = new PatchSetApproval(
|
||||
new PatchSetApproval.Key(
|
||||
c.currentPatchSetId(), accountId, new LabelId("Verified")),
|
||||
(short) 1,
|
||||
TimeUtil.nowTs());
|
||||
|
||||
ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(),
|
||||
approvals(a1), comments());
|
||||
ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(),
|
||||
approvals(a2), comments());
|
||||
|
||||
assertDiffs(b1, b2,
|
||||
"PatchSetApproval.Key sets differ:"
|
||||
+ " [" + id + "%2C1,100,Code-Review] only in A;"
|
||||
+ " [" + id + "%2C1,100,Verified] only in B");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffPatchSetApprovals() throws Exception {
|
||||
Change c = TestChanges.newChange(project, accountId);
|
||||
PatchSetApproval a1 = new PatchSetApproval(
|
||||
new PatchSetApproval.Key(
|
||||
c.currentPatchSetId(), accountId, new LabelId("Code-Review")),
|
||||
(short) 1,
|
||||
TimeUtil.nowTs());
|
||||
PatchSetApproval a2 = clone(a1);
|
||||
ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(),
|
||||
approvals(a1), comments());
|
||||
ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(),
|
||||
approvals(a2), comments());
|
||||
|
||||
assertNoDiffs(b1, b2);
|
||||
|
||||
a2.setValue((short) -1);
|
||||
assertDiffs(b1, b2,
|
||||
"value differs for PatchSetApproval.Key "
|
||||
+ c.getId() + "%2C1,100,Code-Review: {1} != {-1}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffPatchLineCommentKeySets() throws Exception {
|
||||
Change c = TestChanges.newChange(project, accountId);
|
||||
int id = c.getId().get();
|
||||
PatchLineComment c1 = new PatchLineComment(
|
||||
new PatchLineComment.Key(
|
||||
new Patch.Key(c.currentPatchSetId(), "filename1"), "uuid1"),
|
||||
5, accountId, null, TimeUtil.nowTs());
|
||||
PatchLineComment c2 = new PatchLineComment(
|
||||
new PatchLineComment.Key(
|
||||
new Patch.Key(c.currentPatchSetId(), "filename2"), "uuid2"),
|
||||
5, accountId, null, TimeUtil.nowTs());
|
||||
|
||||
ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(),
|
||||
approvals(), comments(c1));
|
||||
ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(),
|
||||
approvals(), comments(c2));
|
||||
|
||||
assertDiffs(b1, b2,
|
||||
"PatchLineComment.Key sets differ:"
|
||||
+ " [" + id + ",1,filename1,uuid1] only in A;"
|
||||
+ " [" + id + ",1,filename2,uuid2] only in B");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffPatchLineComments() throws Exception {
|
||||
Change c = TestChanges.newChange(project, accountId);
|
||||
PatchLineComment c1 = new PatchLineComment(
|
||||
new PatchLineComment.Key(
|
||||
new Patch.Key(c.currentPatchSetId(), "filename"), "uuid"),
|
||||
5, accountId, null, TimeUtil.nowTs());
|
||||
PatchLineComment c2 = clone(c1);
|
||||
ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(),
|
||||
approvals(), comments(c1));
|
||||
ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(),
|
||||
approvals(), comments(c2));
|
||||
|
||||
assertNoDiffs(b1, b2);
|
||||
|
||||
c2.setStatus(PatchLineComment.Status.PUBLISHED);
|
||||
assertDiffs(b1, b2,
|
||||
"status differs for PatchLineComment.Key "
|
||||
+ c.getId() + ",1,filename,uuid: {d} != {P}");
|
||||
}
|
||||
|
||||
private static void assertNoDiffs(ChangeBundle a, ChangeBundle b) {
|
||||
assertThat(a.differencesFrom(b)).isEmpty();
|
||||
assertThat(b.differencesFrom(a)).isEmpty();
|
||||
assertThat(a).isEqualTo(b);
|
||||
assertThat(b).isEqualTo(a);
|
||||
assertThat(a.hashCode()).isEqualTo(b.hashCode());
|
||||
}
|
||||
|
||||
private static void assertDiffs(ChangeBundle a, ChangeBundle b, String first,
|
||||
String... rest) {
|
||||
List<String> actual = a.differencesFrom(b);
|
||||
if (actual.size() == 1 && rest.length == 0) {
|
||||
// This error message is much easier to read.
|
||||
assertThat(actual.get(0)).isEqualTo(first);
|
||||
} else {
|
||||
List<String> expected = new ArrayList<>(1 + rest.length);
|
||||
expected.add(first);
|
||||
Collections.addAll(expected, rest);
|
||||
assertThat(actual).containsExactlyElementsIn(expected).inOrder();
|
||||
}
|
||||
assertThat(a).isNotEqualTo(b);
|
||||
}
|
||||
|
||||
private static List<ChangeMessage> messages(ChangeMessage... ents) {
|
||||
return Arrays.asList(ents);
|
||||
}
|
||||
|
||||
private static List<PatchSet> patchSets(PatchSet... ents) {
|
||||
return Arrays.asList(ents);
|
||||
}
|
||||
|
||||
private static List<PatchSetApproval> approvals(PatchSetApproval... ents) {
|
||||
return Arrays.asList(ents);
|
||||
}
|
||||
|
||||
private static List<PatchLineComment> comments(PatchLineComment... ents) {
|
||||
return Arrays.asList(ents);
|
||||
}
|
||||
|
||||
private static Change clone(Change ent) {
|
||||
return clone(CHANGE_CODEC, ent);
|
||||
}
|
||||
|
||||
private static ChangeMessage clone(ChangeMessage ent) {
|
||||
return clone(CHANGE_MESSAGE_CODEC, ent);
|
||||
}
|
||||
|
||||
private static PatchSet clone(PatchSet ent) {
|
||||
return clone(PATCH_SET_CODEC, ent);
|
||||
}
|
||||
|
||||
private static PatchSetApproval clone(PatchSetApproval ent) {
|
||||
return clone(PATCH_SET_APPROVAL_CODEC, ent);
|
||||
}
|
||||
|
||||
private static PatchLineComment clone(PatchLineComment ent) {
|
||||
return clone(PATCH_LINE_COMMENT_CODEC, ent);
|
||||
}
|
||||
|
||||
private static <T> T clone(ProtobufCodec<T> codec, T obj) {
|
||||
return codec.decode(codec.encodeToByteArray(obj));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user