Add converter for Change protobuf messages

The use of ProtobufCodec requires that value classes need to be
annotated with @Column, which won't be possible as soon as we have
removed gwtorm. Hence, provide a hand-written converter for
Change protobuf messages.

As protobuf Changes are currently used in indices
and we don't want to invalidate those, we have to ensure binary
compatibility. Prove that the new converter generates binary compatible
results via dedicated tests. Those tests will be removed after this
change.

Change-Id: Ie1dcd3dd28c628cb8c20d0f95147417e8b2fd260
This commit is contained in:
Alice Kober-Sotzek
2018-12-14 15:41:19 +01:00
committed by Dave Borowitz
parent 8bd35ec888
commit 921d9d67f9
13 changed files with 952 additions and 19 deletions

View File

@@ -99,7 +99,7 @@ abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
.collect(toImmutableList());
}
private static <P extends MessageLite, T> T parseProtoFrom(
protected static <P extends MessageLite, T> T parseProtoFrom(
byte[] bytes, ProtoConverter<P, T> converter) {
P message = Protos.parseUnchecked(converter.getParser(), bytes);
return converter.fromProto(message);

View File

@@ -14,7 +14,6 @@
package com.google.gerrit.elasticsearch;
import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.CHANGE_CODEC;
import static com.google.gerrit.server.index.change.ChangeIndexRewriter.CLOSED_STATUSES;
import static com.google.gerrit.server.index.change.ChangeIndexRewriter.OPEN_STATUSES;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -43,6 +42,7 @@ import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Id;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.converter.ChangeProtoConverter;
import com.google.gerrit.reviewdb.converter.PatchSetApprovalProtoConverter;
import com.google.gerrit.reviewdb.converter.PatchSetProtoConverter;
import com.google.gerrit.server.ReviewerByEmailSet;
@@ -218,7 +218,8 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
}
ChangeData cd =
changeDataFactory.create(CHANGE_CODEC.decode(Base64.decodeBase64(c.getAsString())));
changeDataFactory.create(
parseProtoFrom(Base64.decodeBase64(c.getAsString()), ChangeProtoConverter.INSTANCE));
// Any decoding that is done here must also be done in {@link LuceneChangeIndex}.

View File

@@ -16,7 +16,6 @@ package com.google.gerrit.lucene;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.lucene.AbstractLuceneIndex.sortFieldName;
import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.CHANGE_CODEC;
import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE;
import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID;
import static com.google.gerrit.server.index.change.ChangeField.PROJECT;
@@ -46,6 +45,7 @@ import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.converter.ChangeProtoConverter;
import com.google.gerrit.reviewdb.converter.PatchSetApprovalProtoConverter;
import com.google.gerrit.reviewdb.converter.PatchSetProtoConverter;
import com.google.gerrit.reviewdb.converter.ProtoConverter;
@@ -445,7 +445,7 @@ public class LuceneChangeIndex implements ChangeIndex {
IndexableField cb = Iterables.getFirst(doc.get(CHANGE_FIELD), null);
if (cb != null) {
BytesRef proto = cb.binaryValue();
cd = changeDataFactory.create(CHANGE_CODEC.decode(proto.bytes, proto.offset, proto.length));
cd = changeDataFactory.create(parseProtoFrom(proto, ChangeProtoConverter.INSTANCE));
} else {
IndexableField f = Iterables.getFirst(doc.get(idFieldName), null);
Change.Id id = new Change.Id(f.numericValue().intValue());

View File

@@ -0,0 +1,47 @@
// Copyright (C) 2018 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.reviewdb.converter;
import com.google.gerrit.proto.reviewdb.Reviewdb;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.protobuf.Parser;
public enum BranchNameKeyProtoConverter
implements ProtoConverter<Reviewdb.Branch_NameKey, Branch.NameKey> {
INSTANCE;
private final ProtoConverter<Reviewdb.Project_NameKey, Project.NameKey> projectNameConverter =
ProjectNameKeyProtoConverter.INSTANCE;
@Override
public Reviewdb.Branch_NameKey toProto(Branch.NameKey nameKey) {
return Reviewdb.Branch_NameKey.newBuilder()
.setProjectName(projectNameConverter.toProto(nameKey.getParentKey()))
.setBranchName(nameKey.get())
.build();
}
@Override
public Branch.NameKey fromProto(Reviewdb.Branch_NameKey proto) {
return new Branch.NameKey(
projectNameConverter.fromProto(proto.getProjectName()), proto.getBranchName());
}
@Override
public Parser<Reviewdb.Branch_NameKey> getParser() {
return Reviewdb.Branch_NameKey.parser();
}
}

View File

@@ -12,15 +12,27 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.reviewdb.server;
package com.google.gerrit.reviewdb.converter;
import com.google.gerrit.proto.reviewdb.Reviewdb;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwtorm.protobuf.CodecFactory;
import com.google.gwtorm.protobuf.ProtobufCodec;
import com.google.protobuf.Parser;
/** {@link ProtobufCodec} instances for ReviewDb types. */
public class ReviewDbCodecs {
public static final ProtobufCodec<Change> CHANGE_CODEC = CodecFactory.encoder(Change.class);
public enum ChangeKeyProtoConverter implements ProtoConverter<Reviewdb.Change_Key, Change.Key> {
INSTANCE;
private ReviewDbCodecs() {}
@Override
public Reviewdb.Change_Key toProto(Change.Key key) {
return Reviewdb.Change_Key.newBuilder().setId(key.get()).build();
}
@Override
public Change.Key fromProto(Reviewdb.Change_Key proto) {
return new Change.Key(proto.getId());
}
@Override
public Parser<Reviewdb.Change_Key> getParser() {
return Reviewdb.Change_Key.parser();
}
}

View File

@@ -0,0 +1,126 @@
// Copyright (C) 2018 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.reviewdb.converter;
import com.google.gerrit.proto.reviewdb.Reviewdb;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.protobuf.Parser;
import java.sql.Timestamp;
public enum ChangeProtoConverter implements ProtoConverter<Reviewdb.Change, Change> {
INSTANCE;
private final ProtoConverter<Reviewdb.Change_Id, Change.Id> changeIdConverter =
ChangeIdProtoConverter.INSTANCE;
private final ProtoConverter<Reviewdb.Change_Key, Change.Key> changeKeyConverter =
ChangeKeyProtoConverter.INSTANCE;
private final ProtoConverter<Reviewdb.Account_Id, Account.Id> accountIdConverter =
AccountIdProtoConverter.INSTANCE;
private final ProtoConverter<Reviewdb.Branch_NameKey, Branch.NameKey> branchNameConverter =
BranchNameKeyProtoConverter.INSTANCE;
@Override
public Reviewdb.Change toProto(Change change) {
Reviewdb.Change.Builder builder =
Reviewdb.Change.newBuilder()
.setChangeId(changeIdConverter.toProto(change.getId()))
.setRowVersion(change.getRowVersion())
.setChangeKey(changeKeyConverter.toProto(change.getKey()))
.setCreatedOn(change.getCreatedOn().getTime())
.setLastUpdatedOn(change.getLastUpdatedOn().getTime())
.setOwnerAccountId(accountIdConverter.toProto(change.getOwner()))
.setDest(branchNameConverter.toProto(change.getDest()))
.setStatus(change.getStatus().getCode())
.setIsPrivate(change.isPrivate())
.setWorkInProgress(change.isWorkInProgress())
.setReviewStarted(change.hasReviewStarted());
PatchSet.Id currentPatchSetId = change.currentPatchSetId();
// Special behavior necessary to ensure binary compatibility.
builder.setCurrentPatchSetId(currentPatchSetId == null ? 0 : currentPatchSetId.get());
String subject = change.getSubject();
if (subject != null) {
builder.setSubject(subject);
}
String topic = change.getTopic();
if (topic != null) {
builder.setTopic(topic);
}
String originalSubject = change.getOriginalSubjectOrNull();
if (originalSubject != null) {
builder.setOriginalSubject(originalSubject);
}
String submissionId = change.getSubmissionId();
if (submissionId != null) {
builder.setSubmissionId(submissionId);
}
Account.Id assignee = change.getAssignee();
if (assignee != null) {
builder.setAssignee(accountIdConverter.toProto(assignee));
}
Change.Id revertOf = change.getRevertOf();
if (revertOf != null) {
builder.setRevertOf(changeIdConverter.toProto(revertOf));
}
return builder.build();
}
@Override
public Change fromProto(Reviewdb.Change proto) {
Change.Id changeId = changeIdConverter.fromProto(proto.getChangeId());
Change.Key key =
proto.hasChangeKey() ? changeKeyConverter.fromProto(proto.getChangeKey()) : null;
Account.Id owner =
proto.hasOwnerAccountId() ? accountIdConverter.fromProto(proto.getOwnerAccountId()) : null;
Branch.NameKey destination =
proto.hasDest() ? branchNameConverter.fromProto(proto.getDest()) : null;
Change change =
new Change(key, changeId, owner, destination, new Timestamp(proto.getCreatedOn()));
if (proto.hasLastUpdatedOn()) {
change.setLastUpdatedOn(new Timestamp(proto.getLastUpdatedOn()));
}
Change.Status status = Change.Status.forCode((char) proto.getStatus());
if (status != null) {
change.setStatus(status);
}
String subject = proto.hasSubject() ? proto.getSubject() : null;
String originalSubject = proto.hasOriginalSubject() ? proto.getOriginalSubject() : null;
change.setCurrentPatchSet(
new PatchSet.Id(changeId, proto.getCurrentPatchSetId()), subject, originalSubject);
if (proto.hasTopic()) {
change.setTopic(proto.getTopic());
}
if (proto.hasSubmissionId()) {
change.setSubmissionId(proto.getSubmissionId());
}
if (proto.hasAssignee()) {
change.setAssignee(accountIdConverter.fromProto(proto.getAssignee()));
}
change.setPrivate(proto.getIsPrivate());
change.setWorkInProgress(proto.getWorkInProgress());
change.setReviewStarted(proto.getReviewStarted());
if (proto.hasRevertOf()) {
change.setRevertOf(changeIdConverter.fromProto(proto.getRevertOf()));
}
return change;
}
@Override
public Parser<Reviewdb.Change> getParser() {
return Reviewdb.Change.parser();
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (C) 2018 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.reviewdb.converter;
import com.google.gerrit.proto.reviewdb.Reviewdb;
import com.google.gerrit.reviewdb.client.Project;
import com.google.protobuf.Parser;
public enum ProjectNameKeyProtoConverter
implements ProtoConverter<Reviewdb.Project_NameKey, Project.NameKey> {
INSTANCE;
@Override
public Reviewdb.Project_NameKey toProto(Project.NameKey nameKey) {
return Reviewdb.Project_NameKey.newBuilder().setName(nameKey.get()).build();
}
@Override
public Project.NameKey fromProto(Reviewdb.Project_NameKey proto) {
return new Project.NameKey(proto.getName());
}
@Override
public Parser<Reviewdb.Project_NameKey> getParser() {
return Reviewdb.Project_NameKey.parser();
}
}

View File

@@ -23,7 +23,6 @@ import static com.google.gerrit.index.FieldDef.integer;
import static com.google.gerrit.index.FieldDef.prefix;
import static com.google.gerrit.index.FieldDef.storedOnly;
import static com.google.gerrit.index.FieldDef.timestamp;
import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.CHANGE_CODEC;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
@@ -53,6 +52,7 @@ 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.RefNames;
import com.google.gerrit.reviewdb.converter.ChangeProtoConverter;
import com.google.gerrit.reviewdb.converter.PatchSetApprovalProtoConverter;
import com.google.gerrit.reviewdb.converter.PatchSetProtoConverter;
import com.google.gerrit.reviewdb.converter.ProtoConverter;
@@ -468,7 +468,8 @@ public class ChangeField {
/** Serialized change object, used for pre-populating results. */
public static final FieldDef<ChangeData, byte[]> CHANGE =
storedOnly("_change").build(changeGetter(CHANGE_CODEC::encodeToByteArray));
storedOnly("_change")
.build(changeGetter(change -> toProto(ChangeProtoConverter.INSTANCE, change)));
/** Serialized approvals for the current patch set, used for pre-populating results. */
public static final FieldDef<ChangeData, Iterable<byte[]>> APPROVAL =
@@ -854,11 +855,11 @@ public class ChangeField {
}
private static <T> List<byte[]> toProtos(ProtoConverter<?, T> converter, Collection<T> objects) {
return objects
.stream()
.map(converter::toProto)
.map(Protos::toByteArray)
.collect(toImmutableList());
return objects.stream().map(object -> toProto(converter, object)).collect(toImmutableList());
}
private static <T> byte[] toProto(ProtoConverter<?, T> converter, T object) {
return Protos.toByteArray(converter.toProto(object));
}
private static <T> FieldDef.Getter<ChangeData, T> changeGetter(Function<Change, T> func) {

View File

@@ -0,0 +1,83 @@
// Copyright (C) 2018 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.reviewdb.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.proto.reviewdb.Reviewdb;
import com.google.gerrit.proto.testing.SerializedClassSubject;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.protobuf.Parser;
import java.lang.reflect.Type;
import org.junit.Test;
public class BranchNameKeyProtoConverterTest {
private final BranchNameKeyProtoConverter branchNameKeyProtoConverter =
BranchNameKeyProtoConverter.INSTANCE;
@Test
public void allValuesConvertedToProto() {
Branch.NameKey nameKey = new Branch.NameKey(new Project.NameKey("project-13"), "branch-72");
Reviewdb.Branch_NameKey proto = branchNameKeyProtoConverter.toProto(nameKey);
Reviewdb.Branch_NameKey expectedProto =
Reviewdb.Branch_NameKey.newBuilder()
.setProjectName(Reviewdb.Project_NameKey.newBuilder().setName("project-13"))
.setBranchName("refs/heads/branch-72")
.build();
assertThat(proto).isEqualTo(expectedProto);
}
@Test
public void allValuesConvertedToProtoAndBackAgain() {
Branch.NameKey nameKey = new Branch.NameKey(new Project.NameKey("project-52"), "branch 14");
Branch.NameKey convertedNameKey =
branchNameKeyProtoConverter.fromProto(branchNameKeyProtoConverter.toProto(nameKey));
assertThat(convertedNameKey).isEqualTo(nameKey);
}
@Test
public void protoCanBeParsedFromBytes() throws Exception {
Reviewdb.Branch_NameKey proto =
Reviewdb.Branch_NameKey.newBuilder()
.setProjectName(Reviewdb.Project_NameKey.newBuilder().setName("project 1"))
.setBranchName("branch 36")
.build();
byte[] bytes = proto.toByteArray();
Parser<Reviewdb.Branch_NameKey> parser = branchNameKeyProtoConverter.getParser();
Reviewdb.Branch_NameKey parsedProto = parser.parseFrom(bytes);
assertThat(parsedProto).isEqualTo(proto);
}
/** See {@link SerializedClassSubject} for background and what to do if this test fails. */
@Test
public void fieldsExistAsExpected() {
assertThatSerializedClass(Branch.NameKey.class)
.hasFields(
ImmutableMap.<String, Type>builder()
.put("projectName", Project.NameKey.class)
.put("branchName", String.class)
.build());
}
}

View File

@@ -0,0 +1,123 @@
// Copyright (C) 2018 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.reviewdb.converter;
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.proto.Protos;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwtorm.protobuf.CodecFactory;
import com.google.gwtorm.protobuf.ProtobufCodec;
import java.sql.Timestamp;
import org.junit.Test;
// TODO(aliceks): Delete after proving binary compatibility.
public class ChangeConverterCompatibilityTest {
private final ProtobufCodec<Change> changeCodec = CodecFactory.encoder(Change.class);
private final ChangeProtoConverter changeProtoConverter = ChangeProtoConverter.INSTANCE;
@Test
public void changeIndexFieldWithAllValuesIsBinaryCompatible() {
Change change =
new Change(
new Change.Key("change 1"),
new Change.Id(14),
new Account.Id(35),
new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
new Timestamp(987654L));
change.setLastUpdatedOn(new Timestamp(1234567L));
change.setStatus(Change.Status.MERGED);
change.setCurrentPatchSet(
new PatchSet.Id(new Change.Id(14), 23), "subject XYZ", "original subject ABC");
change.setTopic("my topic");
change.setSubmissionId("submission ID 234");
change.setAssignee(new Account.Id(100001));
change.setPrivate(true);
change.setWorkInProgress(true);
change.setReviewStarted(true);
change.setRevertOf(new Change.Id(180));
byte[] resultOfOldConverter = convertToProto_old(changeCodec, change);
byte[] resultOfNewConverter = convertToProto_new(changeProtoConverter, change);
assertThat(resultOfNewConverter).isEqualTo(resultOfOldConverter);
}
@Test
public void changeIndexFieldWithMandatoryValuesIsBinaryCompatible() {
Change change =
new Change(
new Change.Key("change 1"),
new Change.Id(14),
new Account.Id(35),
new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
new Timestamp(987654L));
byte[] resultOfOldConverter = convertToProto_old(changeCodec, change);
byte[] resultOfNewConverter = convertToProto_new(changeProtoConverter, change);
assertThat(resultOfNewConverter).isEqualTo(resultOfOldConverter);
}
@Test
public void changeIndexFieldWithSubjectButNotOriginalSubjectIsBinaryCompatible() {
Change change =
new Change(
new Change.Key("change 1"),
new Change.Id(14),
new Account.Id(35),
new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
new Timestamp(987654L));
change.setCurrentPatchSet(new PatchSet.Id(new Change.Id(14), 23), "subject XYZ", null);
byte[] resultOfOldConverter = convertToProto_old(changeCodec, change);
byte[] resultOfNewConverter = convertToProto_new(changeProtoConverter, change);
assertThat(resultOfNewConverter).isEqualTo(resultOfOldConverter);
}
@Test
public void changeIndexFieldWithoutPatchSetIsBinaryCompatible() {
Change change =
new Change(
new Change.Key("change 1"),
new Change.Id(14),
new Account.Id(35),
new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
new Timestamp(987654L));
// O for patch set ID means that there isn't any current patch set.
change.setCurrentPatchSet(new PatchSet.Id(new Change.Id(14), 0), null, null);
byte[] resultOfOldConverter = convertToProto_old(changeCodec, change);
byte[] resultOfNewConverter = convertToProto_new(changeProtoConverter, change);
assertThat(resultOfNewConverter).isEqualTo(resultOfOldConverter);
}
// Copied from ChangeField.
private static <T> byte[] convertToProto_old(ProtobufCodec<T> codec, T object) {
return codec.encodeToByteArray(object);
}
// Copied from ChangeField.
private static <T> byte[] convertToProto_new(ProtoConverter<?, T> converter, T object) {
return Protos.toByteArray(converter.toProto(object));
}
}

View File

@@ -0,0 +1,67 @@
// Copyright (C) 2018 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.reviewdb.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.proto.reviewdb.Reviewdb;
import com.google.gerrit.proto.testing.SerializedClassSubject;
import com.google.gerrit.reviewdb.client.Change;
import com.google.protobuf.Parser;
import org.junit.Test;
public class ChangeKeyProtoConverterTest {
private final ChangeKeyProtoConverter changeKeyProtoConverter = ChangeKeyProtoConverter.INSTANCE;
@Test
public void allValuesConvertedToProto() {
Change.Key changeKey = new Change.Key("change-1");
Reviewdb.Change_Key proto = changeKeyProtoConverter.toProto(changeKey);
Reviewdb.Change_Key expectedProto = Reviewdb.Change_Key.newBuilder().setId("change-1").build();
assertThat(proto).isEqualTo(expectedProto);
}
@Test
public void allValuesConvertedToProtoAndBackAgain() {
Change.Key changeKey = new Change.Key("change-52");
Change.Key convertedChangeKey =
changeKeyProtoConverter.fromProto(changeKeyProtoConverter.toProto(changeKey));
assertThat(convertedChangeKey).isEqualTo(changeKey);
}
@Test
public void protoCanBeParsedFromBytes() throws Exception {
Reviewdb.Change_Key proto = Reviewdb.Change_Key.newBuilder().setId("change 36").build();
byte[] bytes = proto.toByteArray();
Parser<Reviewdb.Change_Key> parser = changeKeyProtoConverter.getParser();
Reviewdb.Change_Key parsedProto = parser.parseFrom(bytes);
assertThat(parsedProto).isEqualTo(proto);
}
/** See {@link SerializedClassSubject} for background and what to do if this test fails. */
@Test
public void fieldsExistAsExpected() {
assertThatSerializedClass(Change.Key.class).hasFields(ImmutableMap.of("id", String.class));
}
}

View File

@@ -0,0 +1,363 @@
// Copyright (C) 2018 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.reviewdb.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.proto.reviewdb.Reviewdb;
import com.google.gerrit.proto.testing.SerializedClassSubject;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.protobuf.Parser;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import org.junit.Test;
public class ChangeProtoConverterTest {
private final ChangeProtoConverter changeProtoConverter = ChangeProtoConverter.INSTANCE;
@Test
public void allValuesConvertedToProto() {
Change change =
new Change(
new Change.Key("change 1"),
new Change.Id(14),
new Account.Id(35),
new Branch.NameKey(new Project.NameKey("project 67"), "branch 74"),
new Timestamp(987654L));
change.setLastUpdatedOn(new Timestamp(1234567L));
change.setStatus(Change.Status.MERGED);
change.setCurrentPatchSet(
new PatchSet.Id(new Change.Id(14), 23), "subject XYZ", "original subject ABC");
change.setTopic("my topic");
change.setSubmissionId("submission ID 234");
change.setAssignee(new Account.Id(100001));
change.setPrivate(true);
change.setWorkInProgress(true);
change.setReviewStarted(true);
change.setRevertOf(new Change.Id(180));
Reviewdb.Change proto = changeProtoConverter.toProto(change);
Reviewdb.Change expectedProto =
Reviewdb.Change.newBuilder()
.setChangeId(Reviewdb.Change_Id.newBuilder().setId(14))
.setChangeKey(Reviewdb.Change_Key.newBuilder().setId("change 1"))
.setRowVersion(0)
.setCreatedOn(987654L)
.setLastUpdatedOn(1234567L)
.setOwnerAccountId(Reviewdb.Account_Id.newBuilder().setId(35))
.setDest(
Reviewdb.Branch_NameKey.newBuilder()
.setProjectName(Reviewdb.Project_NameKey.newBuilder().setName("project 67"))
.setBranchName("refs/heads/branch 74"))
.setStatus(Change.STATUS_MERGED)
.setCurrentPatchSetId(23)
.setSubject("subject XYZ")
.setTopic("my topic")
.setOriginalSubject("original subject ABC")
.setSubmissionId("submission ID 234")
.setAssignee(Reviewdb.Account_Id.newBuilder().setId(100001))
.setIsPrivate(true)
.setWorkInProgress(true)
.setReviewStarted(true)
.setRevertOf(Reviewdb.Change_Id.newBuilder().setId(180))
.build();
assertThat(proto).isEqualTo(expectedProto);
}
@Test
public void mandatoryValuesConvertedToProto() {
Change change =
new Change(
new Change.Key("change 1"),
new Change.Id(14),
new Account.Id(35),
new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
new Timestamp(987654L));
Reviewdb.Change proto = changeProtoConverter.toProto(change);
Reviewdb.Change expectedProto =
Reviewdb.Change.newBuilder()
.setChangeId(Reviewdb.Change_Id.newBuilder().setId(14))
.setChangeKey(Reviewdb.Change_Key.newBuilder().setId("change 1"))
.setCreatedOn(987654L)
// Defaults to createdOn if not set.
.setLastUpdatedOn(987654L)
.setOwnerAccountId(Reviewdb.Account_Id.newBuilder().setId(35))
.setDest(
Reviewdb.Branch_NameKey.newBuilder()
.setProjectName(Reviewdb.Project_NameKey.newBuilder().setName("project 67"))
.setBranchName("refs/heads/branch-74"))
// Default values which can't be unset.
.setCurrentPatchSetId(0)
.setRowVersion(0)
.setStatus(Change.STATUS_NEW)
.setIsPrivate(false)
.setWorkInProgress(false)
.setReviewStarted(false)
.build();
assertThat(proto).isEqualTo(expectedProto);
}
// This test documents a special behavior which is necessary to ensure binary compatibility.
@Test
public void currentPatchSetIsAlwaysSetWhenConvertedToProto() {
Change change =
new Change(
new Change.Key("change 1"),
new Change.Id(14),
new Account.Id(35),
new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
new Timestamp(987654L));
// O as ID actually means that no current patch set is present.
change.setCurrentPatchSet(new PatchSet.Id(new Change.Id(14), 0), null, null);
Reviewdb.Change proto = changeProtoConverter.toProto(change);
Reviewdb.Change expectedProto =
Reviewdb.Change.newBuilder()
.setChangeId(Reviewdb.Change_Id.newBuilder().setId(14))
.setChangeKey(Reviewdb.Change_Key.newBuilder().setId("change 1"))
.setCreatedOn(987654L)
// Defaults to createdOn if not set.
.setLastUpdatedOn(987654L)
.setOwnerAccountId(Reviewdb.Account_Id.newBuilder().setId(35))
.setDest(
Reviewdb.Branch_NameKey.newBuilder()
.setProjectName(Reviewdb.Project_NameKey.newBuilder().setName("project 67"))
.setBranchName("refs/heads/branch-74"))
.setCurrentPatchSetId(0)
// Default values which can't be unset.
.setRowVersion(0)
.setStatus(Change.STATUS_NEW)
.setIsPrivate(false)
.setWorkInProgress(false)
.setReviewStarted(false)
.build();
assertThat(proto).isEqualTo(expectedProto);
}
// This test documents a special behavior which is necessary to ensure binary compatibility.
@Test
public void originalSubjectIsNotAutomaticallySetToSubjectWhenConvertedToProto() {
Change change =
new Change(
new Change.Key("change 1"),
new Change.Id(14),
new Account.Id(35),
new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
new Timestamp(987654L));
change.setCurrentPatchSet(new PatchSet.Id(new Change.Id(14), 23), "subject ABC", null);
Reviewdb.Change proto = changeProtoConverter.toProto(change);
Reviewdb.Change expectedProto =
Reviewdb.Change.newBuilder()
.setChangeId(Reviewdb.Change_Id.newBuilder().setId(14))
.setChangeKey(Reviewdb.Change_Key.newBuilder().setId("change 1"))
.setCreatedOn(987654L)
// Defaults to createdOn if not set.
.setLastUpdatedOn(987654L)
.setOwnerAccountId(Reviewdb.Account_Id.newBuilder().setId(35))
.setDest(
Reviewdb.Branch_NameKey.newBuilder()
.setProjectName(Reviewdb.Project_NameKey.newBuilder().setName("project 67"))
.setBranchName("refs/heads/branch-74"))
.setCurrentPatchSetId(23)
.setSubject("subject ABC")
// Default values which can't be unset.
.setRowVersion(0)
.setStatus(Change.STATUS_NEW)
.setIsPrivate(false)
.setWorkInProgress(false)
.setReviewStarted(false)
.build();
assertThat(proto).isEqualTo(expectedProto);
}
@Test
public void allValuesConvertedToProtoAndBackAgain() {
Change change =
new Change(
new Change.Key("change 1"),
new Change.Id(14),
new Account.Id(35),
new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
new Timestamp(987654L));
change.setLastUpdatedOn(new Timestamp(1234567L));
change.setStatus(Change.Status.MERGED);
change.setCurrentPatchSet(
new PatchSet.Id(new Change.Id(14), 23), "subject XYZ", "original subject ABC");
change.setTopic("my topic");
change.setSubmissionId("submission ID 234");
change.setAssignee(new Account.Id(100001));
change.setPrivate(true);
change.setWorkInProgress(true);
change.setReviewStarted(true);
change.setRevertOf(new Change.Id(180));
Change convertedChange = changeProtoConverter.fromProto(changeProtoConverter.toProto(change));
assertEqualChange(convertedChange, change);
}
@Test
public void mandatoryValuesConvertedToProtoAndBackAgain() {
Change change =
new Change(
new Change.Key("change 1"),
new Change.Id(14),
new Account.Id(35),
new Branch.NameKey(new Project.NameKey("project 67"), "branch-74"),
new Timestamp(987654L));
Change convertedChange = changeProtoConverter.fromProto(changeProtoConverter.toProto(change));
assertEqualChange(convertedChange, change);
}
// We need this special test as some values are only optional in the protobuf definition but can
// never be unset in our entity object.
@Test
public void protoWithOnlyRequiredValuesCanBeConvertedBack() {
Reviewdb.Change proto =
Reviewdb.Change.newBuilder().setChangeId(Reviewdb.Change_Id.newBuilder().setId(14)).build();
Change change = changeProtoConverter.fromProto(proto);
assertThat(change.getChangeId()).isEqualTo(14);
// Values which can't be null according to ReviewDb's column definition but which are optional.
assertThat(change.getKey()).isNull();
assertThat(change.getOwner()).isNull();
assertThat(change.getDest()).isNull();
assertThat(change.getCreatedOn()).isEqualTo(new Timestamp(0));
assertThat(change.getLastUpdatedOn()).isEqualTo(new Timestamp(0));
assertThat(change.getSubject()).isNull();
assertThat(change.currentPatchSetId()).isNull();
// Default values for unset protobuf fields which can't be unset in the entity object.
assertThat(change.getRowVersion()).isEqualTo(0);
assertThat(change.getStatus()).isEqualTo(Change.Status.NEW);
assertThat(change.isPrivate()).isFalse();
assertThat(change.isWorkInProgress()).isFalse();
assertThat(change.hasReviewStarted()).isFalse();
}
@Test
public void unsetLastUpdatedOnIsAutomaticallySetToCreatedOnWhenConvertedBack() {
Reviewdb.Change proto =
Reviewdb.Change.newBuilder()
.setChangeId(Reviewdb.Change_Id.newBuilder().setId(14))
.setChangeKey(Reviewdb.Change_Key.newBuilder().setId("change 1"))
.setCreatedOn(987654L)
.setOwnerAccountId(Reviewdb.Account_Id.newBuilder().setId(35))
.setDest(
Reviewdb.Branch_NameKey.newBuilder()
.setProjectName(Reviewdb.Project_NameKey.newBuilder().setName("project 67"))
.setBranchName("branch 74"))
.build();
Change change = changeProtoConverter.fromProto(proto);
assertThat(change.getLastUpdatedOn()).isEqualTo(new Timestamp(987654L));
}
@Test
public void protoCanBeParsedFromBytes() throws Exception {
Reviewdb.Change proto =
Reviewdb.Change.newBuilder()
.setChangeId(Reviewdb.Change_Id.newBuilder().setId(14))
.setChangeKey(Reviewdb.Change_Key.newBuilder().setId("change 1"))
.setRowVersion(0)
.setCreatedOn(987654L)
.setLastUpdatedOn(1234567L)
.setOwnerAccountId(Reviewdb.Account_Id.newBuilder().setId(35))
.setDest(
Reviewdb.Branch_NameKey.newBuilder()
.setProjectName(Reviewdb.Project_NameKey.newBuilder().setName("project 67"))
.setBranchName("branch 74"))
.setStatus(Change.STATUS_MERGED)
.setCurrentPatchSetId(23)
.setSubject("subject XYZ")
.setTopic("my topic")
.setOriginalSubject("original subject ABC")
.setSubmissionId("submission ID 234")
.setAssignee(Reviewdb.Account_Id.newBuilder().setId(100001))
.setIsPrivate(true)
.setWorkInProgress(true)
.setReviewStarted(true)
.setRevertOf(Reviewdb.Change_Id.newBuilder().setId(180))
.build();
byte[] bytes = proto.toByteArray();
Parser<Reviewdb.Change> parser = changeProtoConverter.getParser();
Reviewdb.Change parsedProto = parser.parseFrom(bytes);
assertThat(parsedProto).isEqualTo(proto);
}
/** See {@link SerializedClassSubject} for background and what to do if this test fails. */
@Test
public void fieldsExistAsExpected() {
assertThatSerializedClass(Change.class)
.hasFields(
ImmutableMap.<String, Type>builder()
.put("changeId", Change.Id.class)
.put("changeKey", Change.Key.class)
.put("rowVersion", int.class)
.put("createdOn", Timestamp.class)
.put("lastUpdatedOn", Timestamp.class)
.put("owner", Account.Id.class)
.put("dest", Branch.NameKey.class)
.put("status", char.class)
.put("currentPatchSetId", int.class)
.put("subject", String.class)
.put("topic", String.class)
.put("originalSubject", String.class)
.put("submissionId", String.class)
.put("assignee", Account.Id.class)
.put("isPrivate", boolean.class)
.put("workInProgress", boolean.class)
.put("reviewStarted", boolean.class)
.put("revertOf", Change.Id.class)
.build());
}
// Unfortunately, Change doesn't implement equals(). Remove this method when we switch Change to
// an AutoValue.
private static void assertEqualChange(Change change, Change expectedChange) {
assertThat(change.getChangeId()).isEqualTo(expectedChange.getChangeId());
assertThat(change.getKey()).isEqualTo(expectedChange.getKey());
assertThat(change.getRowVersion()).isEqualTo(expectedChange.getRowVersion());
assertThat(change.getCreatedOn()).isEqualTo(expectedChange.getCreatedOn());
assertThat(change.getLastUpdatedOn()).isEqualTo(expectedChange.getLastUpdatedOn());
assertThat(change.getOwner()).isEqualTo(expectedChange.getOwner());
assertThat(change.getDest()).isEqualTo(expectedChange.getDest());
assertThat(change.getStatus()).isEqualTo(expectedChange.getStatus());
assertThat(change.currentPatchSetId()).isEqualTo(expectedChange.currentPatchSetId());
assertThat(change.getSubject()).isEqualTo(expectedChange.getSubject());
assertThat(change.getTopic()).isEqualTo(expectedChange.getTopic());
assertThat(change.getOriginalSubject()).isEqualTo(expectedChange.getOriginalSubject());
assertThat(change.getSubmissionId()).isEqualTo(expectedChange.getSubmissionId());
assertThat(change.getAssignee()).isEqualTo(expectedChange.getAssignee());
assertThat(change.isPrivate()).isEqualTo(expectedChange.isPrivate());
assertThat(change.isWorkInProgress()).isEqualTo(expectedChange.isWorkInProgress());
assertThat(change.hasReviewStarted()).isEqualTo(expectedChange.hasReviewStarted());
assertThat(change.getRevertOf()).isEqualTo(expectedChange.getRevertOf());
}
}

View File

@@ -0,0 +1,71 @@
// Copyright (C) 2018 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.reviewdb.converter;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.proto.reviewdb.Reviewdb;
import com.google.gerrit.proto.testing.SerializedClassSubject;
import com.google.gerrit.reviewdb.client.Project;
import com.google.protobuf.Parser;
import org.junit.Test;
public class ProjectNameKeyProtoConverterTest {
private final ProjectNameKeyProtoConverter projectNameKeyProtoConverter =
ProjectNameKeyProtoConverter.INSTANCE;
@Test
public void allValuesConvertedToProto() {
Project.NameKey nameKey = new Project.NameKey("project-72");
Reviewdb.Project_NameKey proto = projectNameKeyProtoConverter.toProto(nameKey);
Reviewdb.Project_NameKey expectedProto =
Reviewdb.Project_NameKey.newBuilder().setName("project-72").build();
assertThat(proto).isEqualTo(expectedProto);
}
@Test
public void allValuesConvertedToProtoAndBackAgain() {
Project.NameKey nameKey = new Project.NameKey("project-52");
Project.NameKey convertedNameKey =
projectNameKeyProtoConverter.fromProto(projectNameKeyProtoConverter.toProto(nameKey));
assertThat(convertedNameKey).isEqualTo(nameKey);
}
@Test
public void protoCanBeParsedFromBytes() throws Exception {
Reviewdb.Project_NameKey proto =
Reviewdb.Project_NameKey.newBuilder().setName("project 36").build();
byte[] bytes = proto.toByteArray();
Parser<Reviewdb.Project_NameKey> parser = projectNameKeyProtoConverter.getParser();
Reviewdb.Project_NameKey parsedProto = parser.parseFrom(bytes);
assertThat(parsedProto).isEqualTo(proto);
}
/** See {@link SerializedClassSubject} for background and what to do if this test fails. */
@Test
public void fieldsExistAsExpected() {
assertThatSerializedClass(Project.NameKey.class)
.hasFields(ImmutableMap.of("name", String.class));
}
}