Merge "SafeProtoConverterTest: test proto defaults"
diff --git a/java/com/google/gerrit/entities/converter/SafeProtoConverter.java b/java/com/google/gerrit/entities/converter/SafeProtoConverter.java
index bab7bb2..f4a66a0 100644
--- a/java/com/google/gerrit/entities/converter/SafeProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/SafeProtoConverter.java
@@ -2,7 +2,7 @@
import com.google.errorprone.annotations.Immutable;
import com.google.gerrit.common.ConvertibleToProto;
-import com.google.protobuf.MessageLite;
+import com.google.protobuf.Message;
/**
* An extension to {@link ProtoConverter} that enforces the Entity class and the Proto class to stay
@@ -21,7 +21,7 @@
* setters, there is no need to explicitly test your safe converter.
*/
@Immutable
-public interface SafeProtoConverter<P extends MessageLite, C> extends ProtoConverter<P, C> {
+public interface SafeProtoConverter<P extends Message, C> extends ProtoConverter<P, C> {
Class<P> getProtoClass();
diff --git a/javatests/com/google/gerrit/entities/converter/SafeProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/SafeProtoConverterTest.java
index 42a993d..e892d42 100644
--- a/javatests/com/google/gerrit/entities/converter/SafeProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/SafeProtoConverterTest.java
@@ -2,6 +2,7 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Primitives;
@@ -10,6 +11,8 @@
import com.google.common.testing.ArbitraryInstances;
import com.google.gerrit.common.ConvertibleToProto;
import com.google.gerrit.common.Nullable;
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.Message;
import com.google.protobuf.MessageLite;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -26,7 +29,6 @@
import java.util.Optional;
import javax.annotation.Nonnull;
import org.apache.commons.lang3.builder.EqualsBuilder;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -36,7 +38,7 @@
@RunWith(Parameterized.class)
public class SafeProtoConverterTest {
@Parameter(0)
- public SafeProtoConverter<MessageLite, Object> converter;
+ public SafeProtoConverter<Message, Object> converter;
@Parameter(1)
public String converterName;
@@ -49,7 +51,7 @@
.filter(SafeProtoConverter.class::isAssignableFrom)
.filter(clz -> !SafeProtoConverter.class.equals(clz))
.filter(Class::isEnum)
- .map(clz -> (SafeProtoConverter<MessageLite, Object>) clz.getEnumConstants()[0])
+ .map(clz -> (SafeProtoConverter<Message, Object>) clz.getEnumConstants()[0])
.map(clz -> new Object[] {clz, clz.getClass().getSimpleName()})
.collect(toImmutableList());
}
@@ -104,8 +106,16 @@
* update the corresponding Java class accordingly.
*/
@Test
- @Ignore("TODO(b/335372403) - implement")
- public void protoDefaultsKeptOnDoubleConversion() {}
+ public void protoDefaultsKeptOnDoubleConversion() {
+ Message defaultInstance = getProtoDefaultInstance(converter.getProtoClass());
+ Message preFilled = explicitlyFillProtoDefaults(defaultInstance);
+ Message resFromDefault =
+ converter.toProto(converter.fromProto(converter.getProtoClass().cast(preFilled)));
+ Message resFromPrefilled =
+ converter.toProto(converter.fromProto(converter.getProtoClass().cast(preFilled)));
+ assertThat(resFromDefault).isEqualTo(preFilled);
+ assertThat(resFromPrefilled).isEqualTo(preFilled);
+ }
@Nullable
private static Object buildObjectWithFullFieldsOrThrow(Class<?> clz) throws Exception {
@@ -207,4 +217,32 @@
|| String.class.isAssignableFrom(c)
|| Timestamp.class.isAssignableFrom(c);
}
+
+ /**
+ * Returns the default instance for the given MessageLite class, if it has the {@code
+ * getDefaultInstance} static method.
+ *
+ * @param type the protobuf message class
+ * @throws IllegalArgumentException if the given class doesn't have the static {@code
+ * getDefaultInstance} method
+ */
+ public static <T extends MessageLite> T getProtoDefaultInstance(Class<T> type) {
+ try {
+ return type.cast(type.getMethod("getDefaultInstance").invoke(null));
+ } catch (ReflectiveOperationException | ClassCastException e) {
+ throw new IllegalStateException("Cannot get default instance for " + type, e);
+ }
+ }
+
+ private static Message explicitlyFillProtoDefaults(Message defaultInstance) {
+ Message.Builder res = defaultInstance.toBuilder();
+ for (FieldDescriptor f : defaultInstance.getDescriptorForType().getFields()) {
+ if (f.getType().equals(FieldDescriptor.Type.MESSAGE)) {
+ res.setField(f, explicitlyFillProtoDefaults((Message) defaultInstance.getField(f)));
+ } else {
+ res.setField(f, defaultInstance.getField(f));
+ }
+ }
+ return res.build();
+ }
}
diff --git a/proto/entities.proto b/proto/entities.proto
index be04e97..0f577a4 100644
--- a/proto/entities.proto
+++ b/proto/entities.proto
@@ -277,7 +277,7 @@
// Next ID: 2
message ObjectId {
// Hex string representation of the ID.
- optional string name = 1;
+ optional string name = 1 [default="0000000000000000000000000000000000000000"];
}
// Serialized form of a continuation token used for pagination.
@@ -453,11 +453,11 @@
optional Side side = 2 [default = REVISION];
message Range {
// 1-based
- optional int32 start_line = 1;
+ optional int32 start_line = 1 [default = 1];
// 0-based
optional int32 start_char = 2;
// 1-based
- optional int32 end_line = 3;
+ optional int32 end_line = 3 [default = 1];
// 0-based
optional int32 end_char = 4;
}
@@ -466,7 +466,7 @@
// number is identical to the range's end line.
optional Range position_range = 3;
// 1-based
- optional int32 line_number = 4;
+ optional int32 line_number = 4 [default = 1];
}
// If not set, the comment is on the patchset level.
@@ -488,4 +488,4 @@
optional fixed64 written_on_millis = 11;
// Required.
optional string server_id = 12;
-}
\ No newline at end of file
+}