Commit fccb146e authored by kenton@google.com's avatar kenton@google.com

Massive roll-up of changes. See CHANGES.txt.

parent d5cf7b55
2009-12-17 version 2.3.0:
General
* Parsers for repeated numeric fields now always accept both packed and
unpacked input. The [packed=true] option only affects serializers.
Therefore, it is possible to switch a field to packed format without
breaking backwards-compatibility -- as long as all parties are using
protobuf 2.3.0 or above, at least.
* The generic RPC service code generated by the C++, Java, and Python
generators can be disabled via file options:
option cc_generic_services = false;
option java_generic_services = false;
option py_generic_services = false;
This allows plugins to generate alternative code, possibly specific to some
particular RPC implementation.
protoc
* Now supports a plugin system for code generators. Plugins can generate
code for new languages or inject additional code into the output of other
code generators. Plugins are just binaries which accept a protocol buffer
on stdin and write a protocol buffer to stdout, so they may be written in
any language. See src/google/protobuf/compiler/plugin.proto.
* inf, -inf, and nan can now be used as default values for float and double
fields.
C++
* Various speed and code size optimizations.
* DynamicMessageFactory is now fully thread-safe.
* Message::Utf8DebugString() method is like DebugString() but avoids escaping
UTF-8 bytes.
* Compiled-in message types can now contain dynamic extensions, through use
of CodedInputStream::SetExtensionRegistry().
Java
* parseDelimitedFrom() and mergeDelimitedFrom() now detect EOF and return
false/null instead of throwing an exception.
* Fixed some initialization ordering bugs.
* Fixes for OpenJDK 7.
Python
* 10-25 times faster than 2.2.0, still pure-Python.
* Calling a mutating method on a sub-message always instantiates the message
in its parent even if the mutating method doesn't actually mutate anything
(e.g. parsing from an empty string).
* Expanded descriptors a bit.
2009-08-11 version 2.2.0:
C++
......
......@@ -114,18 +114,12 @@ EXTRA_DIST = \
python/google/protobuf/internal/generator_test.py \
python/google/protobuf/internal/containers.py \
python/google/protobuf/internal/decoder.py \
python/google/protobuf/internal/decoder_test.py \
python/google/protobuf/internal/descriptor_test.py \
python/google/protobuf/internal/encoder.py \
python/google/protobuf/internal/encoder_test.py \
python/google/protobuf/internal/input_stream.py \
python/google/protobuf/internal/input_stream_test.py \
python/google/protobuf/internal/message_listener.py \
python/google/protobuf/internal/message_test.py \
python/google/protobuf/internal/more_extensions.proto \
python/google/protobuf/internal/more_messages.proto \
python/google/protobuf/internal/output_stream.py \
python/google/protobuf/internal/output_stream_test.py \
python/google/protobuf/internal/reflection_test.py \
python/google/protobuf/internal/service_reflection_test.py \
python/google/protobuf/internal/test_util.py \
......
......@@ -4,6 +4,8 @@
# be included in the distribution. These files are not checked in because they
# are automatically generated.
set -e
# Check that we're being run from the right directory.
if test ! -f src/google/protobuf/stubs/common.h; then
cat >&2 << __EOF__
......@@ -13,6 +15,14 @@ __EOF__
exit 1
fi
# Check that gtest is present. Usually it is already there since the
# directory is set up as an SVN external.
if test ! -e gtest; then
echo "Google Test not present. Fetching gtest-1.3.0 from the web..."
curl http://googletest.googlecode.com/files/gtest-1.3.0.tar.bz2 | tar jx
mv gtest-1.3.0 gtest
fi
set -ex
# Temporary hack: Must change C runtime library to "multi-threaded DLL",
......
......@@ -27,5 +27,7 @@ __EOF__
fi
cd src
make $@ protoc && ./protoc --cpp_out=dllexport_decl=LIBPROTOBUF_EXPORT:. google/protobuf/descriptor.proto
make $@ protoc &&
./protoc --cpp_out=dllexport_decl=LIBPROTOBUF_EXPORT:. google/protobuf/descriptor.proto && \
./protoc --cpp_out=dllexport_decl=LIBPROTOC_EXPORT:. google/protobuf/compiler/plugin.proto
cd ..
......@@ -113,6 +113,7 @@
<arg value="../src/google/protobuf/unittest_import_lite.proto" />
<arg value="../src/google/protobuf/unittest_lite_imports_nonlite.proto" />
<arg value="../src/google/protobuf/unittest_enormous_descriptor.proto" />
<arg value="../src/google/protobuf/unittest_no_generic_services.proto" />
</exec>
</tasks>
<testSourceRoot>target/generated-test-sources</testSourceRoot>
......
......@@ -311,6 +311,12 @@ public abstract class AbstractMessage extends AbstractMessageLite
} else {
field = extension.descriptor;
defaultInstance = extension.defaultInstance;
if (defaultInstance == null &&
field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
throw new IllegalStateException(
"Message-typed extension lacked default instance: " +
field.getFullName());
}
}
} else {
field = null;
......@@ -319,15 +325,28 @@ public abstract class AbstractMessage extends AbstractMessageLite
field = type.findFieldByNumber(fieldNumber);
}
if (field == null || wireType !=
FieldSet.getWireFormatForFieldType(
boolean unknown = false;
boolean packed = false;
if (field == null) {
unknown = true; // Unknown field.
} else if (wireType == FieldSet.getWireFormatForFieldType(
field.getLiteType(),
false /* isPacked */)) {
packed = false;
} else if (field.isPackable() &&
wireType == FieldSet.getWireFormatForFieldType(
field.getLiteType(),
field.getOptions().getPacked())) {
// Unknown field or wrong wire type. Skip.
true /* isPacked */)) {
packed = true;
} else {
unknown = true; // Unknown wire type.
}
if (unknown) { // Unknown field or wrong wire type. Skip.
return unknownFields.mergeFieldFrom(tag, input);
}
if (field.getOptions().getPacked()) {
if (packed) {
final int length = input.readRawVarint32();
final int limit = input.pushLimit(length);
if (field.getLiteType() == WireFormat.FieldType.ENUM) {
......@@ -673,13 +692,13 @@ public abstract class AbstractMessage extends AbstractMessageLite
}
@Override
public BuilderType mergeDelimitedFrom(final InputStream input)
public boolean mergeDelimitedFrom(final InputStream input)
throws IOException {
return super.mergeDelimitedFrom(input);
}
@Override
public BuilderType mergeDelimitedFrom(
public boolean mergeDelimitedFrom(
final InputStream input,
final ExtensionRegistryLite extensionRegistry)
throws IOException {
......
......@@ -86,7 +86,7 @@ public abstract class AbstractMessageLite implements MessageLite {
CodedOutputStream.computeRawVarint32Size(serialized) + serialized);
final CodedOutputStream codedOutput =
CodedOutputStream.newInstance(output, bufferSize);
codedOutput.writeRawVarint32(getSerializedSize());
codedOutput.writeRawVarint32(serialized);
writeTo(codedOutput);
codedOutput.flush();
}
......@@ -105,13 +105,7 @@ public abstract class AbstractMessageLite implements MessageLite {
public BuilderType mergeFrom(final CodedInputStream input)
throws IOException {
// TODO(kenton): Don't use null here. Currently we have to because
// using ExtensionRegistry.getEmptyRegistry() would imply a dependency
// on ExtensionRegistry. However, AbstractMessage overrides this with
// a correct implementation, and lite messages don't yet support
// extensions, so it ends up not mattering for now. It will matter
// once lite messages support extensions.
return mergeFrom(input, null);
return mergeFrom(input, ExtensionRegistryLite.getEmptyRegistry());
}
// Re-defined here for return type covariance.
......@@ -275,20 +269,24 @@ public abstract class AbstractMessageLite implements MessageLite {
}
}
public BuilderType mergeDelimitedFrom(
public boolean mergeDelimitedFrom(
final InputStream input,
final ExtensionRegistryLite extensionRegistry)
throws IOException {
final int size = CodedInputStream.readRawVarint32(input);
final int firstByte = input.read();
if (firstByte == -1) {
return false;
}
final int size = CodedInputStream.readRawVarint32(firstByte, input);
final InputStream limitedInput = new LimitedInputStream(input, size);
return mergeFrom(limitedInput, extensionRegistry);
mergeFrom(limitedInput, extensionRegistry);
return true;
}
public BuilderType mergeDelimitedFrom(final InputStream input)
public boolean mergeDelimitedFrom(final InputStream input)
throws IOException {
final int size = CodedInputStream.readRawVarint32(input);
final InputStream limitedInput = new LimitedInputStream(input, size);
return mergeFrom(limitedInput);
return mergeDelimitedFrom(input,
ExtensionRegistryLite.getEmptyRegistry());
}
/**
......
......@@ -98,6 +98,24 @@ public final class ByteString {
return copyFrom(bytes, 0, bytes.length);
}
/**
* Copies {@code size} bytes from a {@code java.nio.ByteBuffer} into
* a {@code ByteString}.
*/
public static ByteString copyFrom(final ByteBuffer bytes, final int size) {
final byte[] copy = new byte[size];
bytes.get(copy);
return new ByteString(copy);
}
/**
* Copies the remaining bytes from a {@code java.nio.ByteBuffer} into
* a {@code ByteString}.
*/
public static ByteString copyFrom(final ByteBuffer bytes) {
return copyFrom(bytes, bytes.remaining());
}
/**
* Encodes {@code text} into a sequence of bytes using the named charset
* and returns the result as a {@code ByteString}.
......
......@@ -84,8 +84,9 @@ public final class CodedInputStream {
}
lastTag = readRawVarint32();
if (lastTag == 0) {
// If we actually read zero, that's not a valid tag.
if (WireFormat.getTagFieldNumber(lastTag) == 0) {
// If we actually read zero (or any tag number corresponding to field
// number zero), that's not a valid tag.
throw InvalidProtocolBufferException.invalidTag();
}
return lastTag;
......@@ -355,8 +356,26 @@ public final class CodedInputStream {
* CodedInputStream buffers its input.
*/
static int readRawVarint32(final InputStream input) throws IOException {
int result = 0;
int offset = 0;
final int firstByte = input.read();
if (firstByte == -1) {
throw InvalidProtocolBufferException.truncatedMessage();
}
return readRawVarint32(firstByte, input);
}
/**
* Like {@link #readRawVarint32(InputStream)}, but expects that the caller
* has already read one byte. This allows the caller to determine if EOF
* has been reached before attempting to read.
*/
static int readRawVarint32(final int firstByte,
final InputStream input) throws IOException {
if ((firstByte & 0x80) == 0) {
return firstByte;
}
int result = firstByte & 0x7f;
int offset = 7;
for (; offset < 32; offset += 7) {
final int b = input.read();
if (b == -1) {
......
......@@ -48,7 +48,7 @@ import java.io.UnsupportedEncodingException;
* (given a message object of the type) {@code message.getDescriptorForType()}.
*
* Descriptors are built from DescriptorProtos, as defined in
* {@code net/proto2/proto/descriptor.proto}.
* {@code google/protobuf/descriptor.proto}.
*
* @author kenton@google.com Kenton Varda
*/
......@@ -699,6 +699,11 @@ public final class Descriptors {
return getOptions().getPacked();
}
/** Can this field be packed? i.e. is it a repeated primitive field? */
public boolean isPackable() {
return isRepeated() && getLiteType().isPackable();
}
/** Returns true if the field had an explicitly-defined default value. */
public boolean hasDefaultValue() { return proto.hasDefaultValue(); }
......@@ -810,39 +815,34 @@ public final class Descriptors {
private Object defaultValue;
public enum Type {
DOUBLE (FieldDescriptorProto.Type.TYPE_DOUBLE , JavaType.DOUBLE ),
FLOAT (FieldDescriptorProto.Type.TYPE_FLOAT , JavaType.FLOAT ),
INT64 (FieldDescriptorProto.Type.TYPE_INT64 , JavaType.LONG ),
UINT64 (FieldDescriptorProto.Type.TYPE_UINT64 , JavaType.LONG ),
INT32 (FieldDescriptorProto.Type.TYPE_INT32 , JavaType.INT ),
FIXED64 (FieldDescriptorProto.Type.TYPE_FIXED64 , JavaType.LONG ),
FIXED32 (FieldDescriptorProto.Type.TYPE_FIXED32 , JavaType.INT ),
BOOL (FieldDescriptorProto.Type.TYPE_BOOL , JavaType.BOOLEAN ),
STRING (FieldDescriptorProto.Type.TYPE_STRING , JavaType.STRING ),
GROUP (FieldDescriptorProto.Type.TYPE_GROUP , JavaType.MESSAGE ),
MESSAGE (FieldDescriptorProto.Type.TYPE_MESSAGE , JavaType.MESSAGE ),
BYTES (FieldDescriptorProto.Type.TYPE_BYTES , JavaType.BYTE_STRING),
UINT32 (FieldDescriptorProto.Type.TYPE_UINT32 , JavaType.INT ),
ENUM (FieldDescriptorProto.Type.TYPE_ENUM , JavaType.ENUM ),
SFIXED32(FieldDescriptorProto.Type.TYPE_SFIXED32, JavaType.INT ),
SFIXED64(FieldDescriptorProto.Type.TYPE_SFIXED64, JavaType.LONG ),
SINT32 (FieldDescriptorProto.Type.TYPE_SINT32 , JavaType.INT ),
SINT64 (FieldDescriptorProto.Type.TYPE_SINT64 , JavaType.LONG );
Type(final FieldDescriptorProto.Type proto, final JavaType javaType) {
this.proto = proto;
DOUBLE (JavaType.DOUBLE ),
FLOAT (JavaType.FLOAT ),
INT64 (JavaType.LONG ),
UINT64 (JavaType.LONG ),
INT32 (JavaType.INT ),
FIXED64 (JavaType.LONG ),
FIXED32 (JavaType.INT ),
BOOL (JavaType.BOOLEAN ),
STRING (JavaType.STRING ),
GROUP (JavaType.MESSAGE ),
MESSAGE (JavaType.MESSAGE ),
BYTES (JavaType.BYTE_STRING),
UINT32 (JavaType.INT ),
ENUM (JavaType.ENUM ),
SFIXED32(JavaType.INT ),
SFIXED64(JavaType.LONG ),
SINT32 (JavaType.INT ),
SINT64 (JavaType.LONG );
Type(final JavaType javaType) {
this.javaType = javaType;
if (ordinal() != proto.getNumber() - 1) {
throw new RuntimeException(
"descriptor.proto changed but Desrciptors.java wasn't updated.");
}
}
private FieldDescriptorProto.Type proto;
private JavaType javaType;
public FieldDescriptorProto.Type toProto() { return proto; }
public FieldDescriptorProto.Type toProto() {
return FieldDescriptorProto.Type.valueOf(ordinal() + 1);
}
public JavaType getJavaType() { return javaType; }
public static Type valueOf(final FieldDescriptorProto.Type type) {
......@@ -902,17 +902,11 @@ public final class Descriptors {
}
// Only repeated primitive fields may be packed.
if (proto.getOptions().getPacked()) {
if (proto.getLabel() != FieldDescriptorProto.Label.LABEL_REPEATED ||
proto.getType() == FieldDescriptorProto.Type.TYPE_STRING ||
proto.getType() == FieldDescriptorProto.Type.TYPE_GROUP ||
proto.getType() == FieldDescriptorProto.Type.TYPE_MESSAGE ||
proto.getType() == FieldDescriptorProto.Type.TYPE_BYTES) {
if (proto.getOptions().getPacked() && !isPackable()) {
throw new DescriptorValidationException(this,
"[packed = true] can only be specified for repeated primitive " +
"fields.");
}
}
if (isExtension) {
if (!proto.hasExtendee()) {
......@@ -1030,10 +1024,26 @@ public final class Descriptors {
defaultValue = TextFormat.parseUInt64(proto.getDefaultValue());
break;
case FLOAT:
if (proto.getDefaultValue().equals("inf")) {
defaultValue = Float.POSITIVE_INFINITY;
} else if (proto.getDefaultValue().equals("-inf")) {
defaultValue = Float.NEGATIVE_INFINITY;
} else if (proto.getDefaultValue().equals("nan")) {
defaultValue = Float.NaN;
} else {
defaultValue = Float.valueOf(proto.getDefaultValue());
}
break;
case DOUBLE:
if (proto.getDefaultValue().equals("inf")) {
defaultValue = Double.POSITIVE_INFINITY;
} else if (proto.getDefaultValue().equals("-inf")) {
defaultValue = Double.NEGATIVE_INFINITY;
} else if (proto.getDefaultValue().equals("nan")) {
defaultValue = Double.NaN;
} else {
defaultValue = Double.valueOf(proto.getDefaultValue());
}
break;
case BOOL:
defaultValue = Boolean.valueOf(proto.getDefaultValue());
......@@ -1064,12 +1074,9 @@ public final class Descriptors {
"Message type had default value.");
}
} catch (NumberFormatException e) {
final DescriptorValidationException validationException =
new DescriptorValidationException(this,
throw new DescriptorValidationException(this,
"Could not parse default value: \"" +
proto.getDefaultValue() + '\"');
validationException.initCause(e);
throw validationException;
proto.getDefaultValue() + '\"', e);
}
} else {
// Determine the default default for this field.
......@@ -1536,14 +1543,7 @@ public final class Descriptors {
private DescriptorValidationException(
final GenericDescriptor problemDescriptor,
final String description) {
this(problemDescriptor, description, null);
}
private DescriptorValidationException(
final GenericDescriptor problemDescriptor,
final String description,
final Throwable cause) {
super(problemDescriptor.getFullName() + ": " + description, cause);
super(problemDescriptor.getFullName() + ": " + description);
// Note that problemDescriptor may be partially uninitialized, so we
// don't want to expose it directly to the user. So, we only provide
......@@ -1553,6 +1553,14 @@ public final class Descriptors {
this.description = description;
}
private DescriptorValidationException(
final GenericDescriptor problemDescriptor,
final String description,
final Throwable cause) {
this(problemDescriptor, description);
initCause(cause);
}
private DescriptorValidationException(
final FileDescriptor problemDescriptor,
final String description) {
......
......@@ -157,6 +157,11 @@ public final class ExtensionRegistry extends ExtensionRegistryLite {
public void add(final GeneratedMessage.GeneratedExtension<?, ?> extension) {
if (extension.getDescriptor().getJavaType() ==
FieldDescriptor.JavaType.MESSAGE) {
if (extension.getMessageDefaultInstance() == null) {
throw new IllegalStateException(
"Registered message-type extension had null default instance: " +
extension.getDescriptor().getFullName());
}
add(new ExtensionInfo(extension.getDescriptor(),
extension.getMessageDefaultInstance()));
} else {
......
......@@ -789,6 +789,10 @@ public abstract class GeneratedMessage extends AbstractMessage {
messageDefaultInstance =
(Message) invokeOrDie(getMethodOrDie(type, "getDefaultInstance"),
null);
if (messageDefaultInstance == null) {
throw new IllegalStateException(
type.getName() + ".getDefaultInstance() returned null.");
}
break;
case ENUM:
enumValueOf = getMethodOrDie(type, "valueOf",
......
......@@ -303,7 +303,7 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
final ExtensionRegistryLite extensionRegistry,
final int tag) throws IOException {
final FieldSet<ExtensionDescriptor> extensions =
internalGetResult().extensions;
((ExtendableMessage) internalGetResult()).extensions;
final int wireType = WireFormat.getTagWireType(tag);
final int fieldNumber = WireFormat.getTagFieldNumber(tag);
......@@ -312,15 +312,29 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
extensionRegistry.findLiteExtensionByNumber(
getDefaultInstanceForType(), fieldNumber);
if (extension == null || wireType !=
FieldSet.getWireFormatForFieldType(
boolean unknown = false;
boolean packed = false;
if (extension == null) {
unknown = true; // Unknown field.
} else if (wireType == FieldSet.getWireFormatForFieldType(
extension.descriptor.getLiteType(),
extension.descriptor.isPacked())) {
// Unknown field or wrong wire type. Skip.
false /* isPacked */)) {
packed = false; // Normal, unpacked value.
} else if (extension.descriptor.isRepeated &&
extension.descriptor.type.isPackable() &&
wireType == FieldSet.getWireFormatForFieldType(
extension.descriptor.getLiteType(),
true /* isPacked */)) {
packed = true; // Packed value.
} else {
unknown = true; // Wrong wire type.
}
if (unknown) { // Unknown field or wrong wire type. Skip.
return input.skipField(tag);
}
if (extension.descriptor.isPacked()) {
if (packed) {
final int length = input.readRawVarint32();
final int limit = input.pushLimit(length);
if (extension.descriptor.getLiteType() == WireFormat.FieldType.ENUM) {
......@@ -396,7 +410,8 @@ public abstract class GeneratedMessageLite extends AbstractMessageLite {
}
protected final void mergeExtensionFields(final MessageType other) {
internalGetResult().extensions.mergeFrom(other.extensions);
((ExtendableMessage) internalGetResult()).extensions.mergeFrom(
((ExtendableMessage) other).extensions);
}
}
......
......@@ -296,9 +296,9 @@ public interface Message extends MessageLite {
Builder mergeFrom(InputStream input,
ExtensionRegistryLite extensionRegistry)
throws IOException;
Builder mergeDelimitedFrom(InputStream input)
boolean mergeDelimitedFrom(InputStream input)
throws IOException;
Builder mergeDelimitedFrom(InputStream input,
boolean mergeDelimitedFrom(InputStream input,
ExtensionRegistryLite extensionRegistry)
throws IOException;
}
......
......@@ -317,14 +317,18 @@ public interface MessageLite {
* then the message data. Use
* {@link MessageLite#writeDelimitedTo(OutputStream)} to write messages in
* this format.
*
* @returns True if successful, or false if the stream is at EOF when the
* method starts. Any other error (including reaching EOF during
* parsing) will cause an exception to be thrown.
*/
Builder mergeDelimitedFrom(InputStream input)
boolean mergeDelimitedFrom(InputStream input)
throws IOException;
/**
* Like {@link #mergeDelimitedFrom(InputStream)} but supporting extensions.
*/
Builder mergeDelimitedFrom(InputStream input,
boolean mergeDelimitedFrom(InputStream input,
ExtensionRegistryLite extensionRegistry)
throws IOException;
}
......
......@@ -426,7 +426,7 @@ public final class TextFormat {
Pattern.compile("(\\s|(#.*$))++", Pattern.MULTILINE);
private static final Pattern TOKEN = Pattern.compile(
"[a-zA-Z_][0-9a-zA-Z_+-]*+|" + // an identifier
"[0-9+-][0-9a-zA-Z_.+-]*+|" + // a number
"[.]?[0-9+-][0-9a-zA-Z_.+-]*+|" + // a number
"\"([^\"\n\\\\]|\\\\.)*+(\"|\\\\?$)|" + // a double-quoted string
"\'([^\"\n\\\\]|\\\\.)*+(\'|\\\\?$)", // a single-quoted string
Pattern.MULTILINE);
......
......@@ -30,6 +30,8 @@
package com.google.protobuf;
import com.google.protobuf.AbstractMessageLite.Builder.LimitedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
......@@ -551,19 +553,23 @@ public final class UnknownFieldSet implements MessageLite {
return this;
}
public Builder mergeDelimitedFrom(InputStream input)
public boolean mergeDelimitedFrom(InputStream input)
throws IOException {
final int size = CodedInputStream.readRawVarint32(input);
final InputStream limitedInput =
new AbstractMessage.Builder.LimitedInputStream(input, size);
return mergeFrom(limitedInput, null);
final int firstByte = input.read();
if (firstByte == -1) {
return false;
}
final int size = CodedInputStream.readRawVarint32(firstByte, input);
final InputStream limitedInput = new LimitedInputStream(input, size);
mergeFrom(limitedInput);
return true;
}
public Builder mergeDelimitedFrom(
public boolean mergeDelimitedFrom(
InputStream input,
ExtensionRegistryLite extensionRegistry) throws IOException {
// UnknownFieldSet has no extensions.
return mergeFrom(input);
return mergeDelimitedFrom(input);
}
public Builder mergeFrom(
......
......@@ -113,10 +113,18 @@ public final class WireFormat {
FIXED64 (JavaType.LONG , WIRETYPE_FIXED64 ),
FIXED32 (JavaType.INT , WIRETYPE_FIXED32 ),
BOOL (JavaType.BOOLEAN , WIRETYPE_VARINT ),
STRING (JavaType.STRING , WIRETYPE_LENGTH_DELIMITED),
GROUP (JavaType.MESSAGE , WIRETYPE_START_GROUP ),
MESSAGE (JavaType.MESSAGE , WIRETYPE_LENGTH_DELIMITED),
BYTES (JavaType.BYTE_STRING, WIRETYPE_LENGTH_DELIMITED),
STRING (JavaType.STRING , WIRETYPE_LENGTH_DELIMITED) {
public boolean isPackable() { return false; }
},
GROUP (JavaType.MESSAGE , WIRETYPE_START_GROUP ) {
public boolean isPackable() { return false; }
},
MESSAGE (JavaType.MESSAGE , WIRETYPE_LENGTH_DELIMITED) {
public boolean isPackable() { return false; }
},
BYTES (JavaType.BYTE_STRING, WIRETYPE_LENGTH_DELIMITED) {
public boolean isPackable() { return false; }
},
UINT32 (JavaType.INT , WIRETYPE_VARINT ),
ENUM (JavaType.ENUM , WIRETYPE_VARINT ),
SFIXED32(JavaType.INT , WIRETYPE_FIXED32 ),
......@@ -134,6 +142,8 @@ public final class WireFormat {
public JavaType getJavaType() { return javaType; }
public int getWireType() { return wireType; }
public boolean isPackable() { return true; }
}
// Field numbers for feilds in MessageSet wire format.
......
......@@ -38,6 +38,7 @@ import protobuf_unittest.UnittestProto.TestAllTypes;
import protobuf_unittest.UnittestProto.TestPackedTypes;
import protobuf_unittest.UnittestProto.TestRequired;
import protobuf_unittest.UnittestProto.TestRequiredForeign;
import protobuf_unittest.UnittestProto.TestUnpackedTypes;
import junit.framework.TestCase;
......@@ -238,6 +239,43 @@ public class AbstractMessageTest extends TestCase {
TestUtil.assertPackedFieldsSet((TestPackedTypes) message.wrappedMessage);
}
public void testUnpackedSerialization() throws Exception {
Message abstractMessage =
new AbstractMessageWrapper(TestUtil.getUnpackedSet());
TestUtil.assertUnpackedFieldsSet(
TestUnpackedTypes.parseFrom(abstractMessage.toByteString()));
assertEquals(TestUtil.getUnpackedSet().toByteString(),
abstractMessage.toByteString());
}
public void testParsePackedToUnpacked() throws Exception {
AbstractMessageWrapper.Builder builder =
new AbstractMessageWrapper.Builder(TestUnpackedTypes.newBuilder());
AbstractMessageWrapper message =
builder.mergeFrom(TestUtil.getPackedSet().toByteString()).build();
TestUtil.assertUnpackedFieldsSet(
(TestUnpackedTypes) message.wrappedMessage);
}
public void testParseUnpackedToPacked() throws Exception {
AbstractMessageWrapper.Builder builder =
new AbstractMessageWrapper.Builder(TestPackedTypes.newBuilder());
AbstractMessageWrapper message =
builder.mergeFrom(TestUtil.getUnpackedSet().toByteString()).build();
TestUtil.assertPackedFieldsSet((TestPackedTypes) message.wrappedMessage);
}
public void testUnpackedParsing() throws Exception {
AbstractMessageWrapper.Builder builder =
new AbstractMessageWrapper.Builder(TestUnpackedTypes.newBuilder());
AbstractMessageWrapper message =
builder.mergeFrom(TestUtil.getUnpackedSet().toByteString()).build();
TestUtil.assertUnpackedFieldsSet(
(TestUnpackedTypes) message.wrappedMessage);
}
public void testOptimizedForSize() throws Exception {
// We're mostly only checking that this class was compiled successfully.
TestOptimizedForSize message =
......
......@@ -490,4 +490,18 @@ public class CodedInputStreamTest extends TestCase {
assertEquals(0, in.readTag());
assertEquals(5, in.getTotalBytesRead());
}
public void testInvalidTag() throws Exception {
// Any tag number which corresponds to field number zero is invalid and
// should throw InvalidProtocolBufferException.
for (int i = 0; i < 8; i++) {
try {
CodedInputStream.newInstance(bytes(i)).readTag();
fail("Should have thrown an exception.");
} catch (InvalidProtocolBufferException e) {
assertEquals(InvalidProtocolBufferException.invalidTag().getMessage(),
e.getMessage());
}
}
}
}
......@@ -30,6 +30,10 @@
package com.google.protobuf;
import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.Descriptors.DescriptorValidationException;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
......@@ -63,6 +67,22 @@ import java.util.Collections;
* @author kenton@google.com Kenton Varda
*/
public class DescriptorsTest extends TestCase {
// Regression test for bug where referencing a FieldDescriptor.Type value
// before a FieldDescriptorProto.Type value would yield a
// ExceptionInInitializerError.
private static final Object STATIC_INIT_TEST = FieldDescriptor.Type.BOOL;
public void testFieldTypeEnumMapping() throws Exception {
assertEquals(FieldDescriptor.Type.values().length,
FieldDescriptorProto.Type.values().length);
for (FieldDescriptor.Type type : FieldDescriptor.Type.values()) {
FieldDescriptorProto.Type protoType = type.toProto();
assertEquals("TYPE_" + type.name(), protoType.name());
assertEquals(type, FieldDescriptor.Type.valueOf(protoType));
}
}
public void testFileDescriptor() throws Exception {
FileDescriptor file = UnittestProto.getDescriptor();
......@@ -405,4 +425,35 @@ public class DescriptorsTest extends TestCase {
UnittestEnormousDescriptor.getDescriptor()
.toProto().getSerializedSize() > 65536);
}
/**
* Tests that the DescriptorValidationException works as intended.
*/
public void testDescriptorValidatorException() throws Exception {
FileDescriptorProto fileDescriptorProto = FileDescriptorProto.newBuilder()
.setName("foo.proto")
.addMessageType(DescriptorProto.newBuilder()
.setName("Foo")
.addField(FieldDescriptorProto.newBuilder()
.setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL)
.setType(FieldDescriptorProto.Type.TYPE_INT32)
.setName("foo")
.setNumber(1)
.setDefaultValue("invalid")
.build())
.build())
.build();
try {
Descriptors.FileDescriptor.buildFrom(fileDescriptorProto,
new FileDescriptor[0]);
fail("DescriptorValidationException expected");
} catch (DescriptorValidationException e) {
// Expected; check that the error message contains some useful hints
assertTrue(e.getMessage().indexOf("foo") != -1);
assertTrue(e.getMessage().indexOf("Foo") != -1);
assertTrue(e.getMessage().indexOf("invalid") != -1);
assertTrue(e.getCause() instanceof NumberFormatException);
assertTrue(e.getCause().getMessage().indexOf("invalid") != -1);
}
}
}
......@@ -39,6 +39,8 @@ import protobuf_unittest.UnittestProto.ForeignEnum;
import protobuf_unittest.UnittestProto.TestAllTypes;
import protobuf_unittest.UnittestProto.TestAllExtensions;
import protobuf_unittest.UnittestProto.TestExtremeDefaultValues;
import protobuf_unittest.UnittestProto.TestPackedTypes;
import protobuf_unittest.UnittestProto.TestUnpackedTypes;
import protobuf_unittest.MultipleFilesTestProto;
import protobuf_unittest.MessageWithNoOuter;
import protobuf_unittest.EnumWithNoOuter;
......@@ -303,8 +305,15 @@ public class GeneratedMessageTest extends TestCase {
TestUtil.assertClear(TestAllTypes.getDefaultInstance());
TestUtil.assertClear(TestAllTypes.newBuilder().build());
assertEquals("\u1234",
TestExtremeDefaultValues.getDefaultInstance().getUtf8String());
TestExtremeDefaultValues message =
TestExtremeDefaultValues.getDefaultInstance();
assertEquals("\u1234", message.getUtf8String());
assertEquals(Double.POSITIVE_INFINITY, message.getInfDouble());
assertEquals(Double.NEGATIVE_INFINITY, message.getNegInfDouble());
assertTrue(Double.isNaN(message.getNanDouble()));
assertEquals(Float.POSITIVE_INFINITY, message.getInfFloat());
assertEquals(Float.NEGATIVE_INFINITY, message.getNegInfFloat());
assertTrue(Float.isNaN(message.getNanFloat()));
}
public void testReflectionGetters() throws Exception {
......@@ -361,6 +370,20 @@ public class GeneratedMessageTest extends TestCase {
assertTrue(map.findValueByNumber(12345) == null);
}
public void testParsePackedToUnpacked() throws Exception {
TestUnpackedTypes.Builder builder = TestUnpackedTypes.newBuilder();
TestUnpackedTypes message =
builder.mergeFrom(TestUtil.getPackedSet().toByteString()).build();
TestUtil.assertUnpackedFieldsSet(message);
}
public void testParseUnpackedToPacked() throws Exception {
TestPackedTypes.Builder builder = TestPackedTypes.newBuilder();
TestPackedTypes message =
builder.mergeFrom(TestUtil.getUnpackedSet().toByteString()).build();
TestUtil.assertPackedFieldsSet(message);
}
// =================================================================
// Extensions.
......@@ -615,4 +638,12 @@ public class GeneratedMessageTest extends TestCase {
UnittestProto.REPEATED_NESTED_MESSAGE_EXTENSION_FIELD_NUMBER, 48);
assertEquals(UnittestProto.REPEATED_NESTED_ENUM_EXTENSION_FIELD_NUMBER, 51);
}
public void testRecursiveMessageDefaultInstance() throws Exception {
UnittestProto.TestRecursiveMessage message =
UnittestProto.TestRecursiveMessage.getDefaultInstance();
assertTrue(message != null);
assertTrue(message.getA() != null);
assertTrue(message.getA() == message);
}
}
......@@ -30,7 +30,9 @@
package com.google.protobuf;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Descriptors.MethodDescriptor;
import google.protobuf.no_generic_services_test.UnittestNoGenericServices;
import protobuf_unittest.MessageWithNoOuter;
import protobuf_unittest.ServiceWithNoOuter;
import protobuf_unittest.UnittestProto.TestAllTypes;
......@@ -44,6 +46,9 @@ import org.easymock.classextension.EasyMock;
import org.easymock.classextension.IMocksControl;
import org.easymock.IArgumentMatcher;
import java.util.HashSet;
import java.util.Set;
import junit.framework.TestCase;
/**
......@@ -220,6 +225,48 @@ public class ServiceTest extends TestCase {
control.verify();
}
public void testNoGenericServices() throws Exception {
// Non-services should be usable.
UnittestNoGenericServices.TestMessage message =
UnittestNoGenericServices.TestMessage.newBuilder()
.setA(123)
.setExtension(UnittestNoGenericServices.testExtension, 456)
.build();
assertEquals(123, message.getA());
assertEquals(1, UnittestNoGenericServices.TestEnum.FOO.getNumber());
// Build a list of the class names nested in UnittestNoGenericServices.
String outerName = "google.protobuf.no_generic_services_test." +
"UnittestNoGenericServices";
Class<?> outerClass = Class.forName(outerName);
Set<String> innerClassNames = new HashSet<String>();
for (Class<?> innerClass : outerClass.getClasses()) {
String fullName = innerClass.getName();
// Figure out the unqualified name of the inner class.
// Note: Surprisingly, the full name of an inner class will be separated
// from the outer class name by a '$' rather than a '.'. This is not
// mentioned in the documentation for java.lang.Class. I don't want to
// make assumptions, so I'm just going to accept any character as the
// separator.
assertTrue(fullName.startsWith(outerName));
innerClassNames.add(fullName.substring(outerName.length() + 1));
}
// No service class should have been generated.
assertTrue(innerClassNames.contains("TestMessage"));
assertTrue(innerClassNames.contains("TestEnum"));
assertFalse(innerClassNames.contains("TestService"));
// But descriptors are there.
FileDescriptor file = UnittestNoGenericServices.getDescriptor();
assertEquals(1, file.getServices().size());
assertEquals("TestService", file.getServices().get(0).getName());
assertEquals(1, file.getServices().get(0).getMethods().size());
assertEquals("Foo",
file.getServices().get(0).getMethods().get(0).getName());
}
// =================================================================
/**
......
......@@ -217,6 +217,7 @@ import protobuf_unittest.UnittestProto.TestAllExtensions;
import protobuf_unittest.UnittestProto.TestAllTypes;
import protobuf_unittest.UnittestProto.TestPackedExtensions;
import protobuf_unittest.UnittestProto.TestPackedTypes;
import protobuf_unittest.UnittestProto.TestUnpackedTypes;
import protobuf_unittest.UnittestProto.ForeignMessage;
import protobuf_unittest.UnittestProto.ForeignEnum;
import com.google.protobuf.test.UnittestImport.ImportMessage;
......@@ -289,6 +290,12 @@ class TestUtil {
return builder.build();
}
public static TestUnpackedTypes getUnpackedSet() {
TestUnpackedTypes.Builder builder = TestUnpackedTypes.newBuilder();
setUnpackedFields(builder);
return builder.build();
}
public static TestPackedExtensions getPackedExtensionsSet() {
TestPackedExtensions.Builder builder = TestPackedExtensions.newBuilder();
setPackedExtensions(builder);
......@@ -955,6 +962,42 @@ class TestUtil {
message.addPackedEnum (ForeignEnum.FOREIGN_BAZ);
}
/**
* Set every field of {@code message} to a unique value. Must correspond with
* the values applied by {@code setPackedFields}.
*/
public static void setUnpackedFields(TestUnpackedTypes.Builder message) {
message.addUnpackedInt32 (601);
message.addUnpackedInt64 (602);
message.addUnpackedUint32 (603);
message.addUnpackedUint64 (604);
message.addUnpackedSint32 (605);
message.addUnpackedSint64 (606);
message.addUnpackedFixed32 (607);
message.addUnpackedFixed64 (608);
message.addUnpackedSfixed32(609);
message.addUnpackedSfixed64(610);
message.addUnpackedFloat (611);
message.addUnpackedDouble (612);
message.addUnpackedBool (true);
message.addUnpackedEnum (ForeignEnum.FOREIGN_BAR);
// Add a second one of each field.
message.addUnpackedInt32 (701);
message.addUnpackedInt64 (702);
message.addUnpackedUint32 (703);
message.addUnpackedUint64 (704);
message.addUnpackedSint32 (705);
message.addUnpackedSint64 (706);
message.addUnpackedFixed32 (707);
message.addUnpackedFixed64 (708);
message.addUnpackedSfixed32(709);
message.addUnpackedSfixed64(710);
message.addUnpackedFloat (711);
message.addUnpackedDouble (712);
message.addUnpackedBool (false);
message.addUnpackedEnum (ForeignEnum.FOREIGN_BAZ);
}
/**
* Assert (using {@code junit.framework.Assert}} that all fields of
* {@code message} are set to the values assigned by {@code setPackedFields}.
......@@ -1004,6 +1047,55 @@ class TestUtil {
Assert.assertEquals(ForeignEnum.FOREIGN_BAZ, message.getPackedEnum(1));
}
/**
* Assert (using {@code junit.framework.Assert}} that all fields of
* {@code message} are set to the values assigned by {@code setUnpackedFields}.
*/
public static void assertUnpackedFieldsSet(TestUnpackedTypes message) {
Assert.assertEquals(2, message.getUnpackedInt32Count ());
Assert.assertEquals(2, message.getUnpackedInt64Count ());
Assert.assertEquals(2, message.getUnpackedUint32Count ());
Assert.assertEquals(2, message.getUnpackedUint64Count ());
Assert.assertEquals(2, message.getUnpackedSint32Count ());
Assert.assertEquals(2, message.getUnpackedSint64Count ());
Assert.assertEquals(2, message.getUnpackedFixed32Count ());
Assert.assertEquals(2, message.getUnpackedFixed64Count ());
Assert.assertEquals(2, message.getUnpackedSfixed32Count());
Assert.assertEquals(2, message.getUnpackedSfixed64Count());
Assert.assertEquals(2, message.getUnpackedFloatCount ());
Assert.assertEquals(2, message.getUnpackedDoubleCount ());
Assert.assertEquals(2, message.getUnpackedBoolCount ());
Assert.assertEquals(2, message.getUnpackedEnumCount ());
Assert.assertEquals(601 , message.getUnpackedInt32 (0));
Assert.assertEquals(602 , message.getUnpackedInt64 (0));
Assert.assertEquals(603 , message.getUnpackedUint32 (0));
Assert.assertEquals(604 , message.getUnpackedUint64 (0));
Assert.assertEquals(605 , message.getUnpackedSint32 (0));
Assert.assertEquals(606 , message.getUnpackedSint64 (0));
Assert.assertEquals(607 , message.getUnpackedFixed32 (0));
Assert.assertEquals(608 , message.getUnpackedFixed64 (0));
Assert.assertEquals(609 , message.getUnpackedSfixed32(0));
Assert.assertEquals(610 , message.getUnpackedSfixed64(0));
Assert.assertEquals(611 , message.getUnpackedFloat (0), 0.0);
Assert.assertEquals(612 , message.getUnpackedDouble (0), 0.0);
Assert.assertEquals(true , message.getUnpackedBool (0));
Assert.assertEquals(ForeignEnum.FOREIGN_BAR, message.getUnpackedEnum(0));
Assert.assertEquals(701 , message.getUnpackedInt32 (1));
Assert.assertEquals(702 , message.getUnpackedInt64 (1));
Assert.assertEquals(703 , message.getUnpackedUint32 (1));
Assert.assertEquals(704 , message.getUnpackedUint64 (1));
Assert.assertEquals(705 , message.getUnpackedSint32 (1));
Assert.assertEquals(706 , message.getUnpackedSint64 (1));
Assert.assertEquals(707 , message.getUnpackedFixed32 (1));
Assert.assertEquals(708 , message.getUnpackedFixed64 (1));
Assert.assertEquals(709 , message.getUnpackedSfixed32(1));
Assert.assertEquals(710 , message.getUnpackedSfixed64(1));
Assert.assertEquals(711 , message.getUnpackedFloat (1), 0.0);
Assert.assertEquals(712 , message.getUnpackedDouble (1), 0.0);
Assert.assertEquals(false, message.getUnpackedBool (1));
Assert.assertEquals(ForeignEnum.FOREIGN_BAZ, message.getUnpackedEnum(1));
}
// ===================================================================
// Like above, but for extensions
......
......@@ -68,7 +68,7 @@ public class TextFormatTest extends TestCase {
private static String allExtensionsSetText = TestUtil.readTextFromFile(
"text_format_unittest_extensions_data.txt");
private String exoticText =
private static String exoticText =
"repeated_int32: -1\n" +
"repeated_int32: -2147483648\n" +
"repeated_int64: -1\n" +
......@@ -80,7 +80,13 @@ public class TextFormatTest extends TestCase {
"repeated_double: 123.0\n" +
"repeated_double: 123.5\n" +
"repeated_double: 0.125\n" +
"repeated_double: .125\n" +
"repeated_double: -.125\n" +
"repeated_double: 1.23E17\n" +
"repeated_double: 1.23E+17\n" +
"repeated_double: -1.23e-17\n" +
"repeated_double: .23e+17\n" +
"repeated_double: -.23E17\n" +
"repeated_double: 1.235E22\n" +
"repeated_double: 1.235E-18\n" +
"repeated_double: 123.456789\n" +
......@@ -91,6 +97,10 @@ public class TextFormatTest extends TestCase {
"\\341\\210\\264\"\n" +
"repeated_bytes: \"\\000\\001\\a\\b\\f\\n\\r\\t\\v\\\\\\'\\\"\\376\"\n";
private static String canonicalExoticText =
exoticText.replace(": .", ": 0.").replace(": -.", ": -0.") // short-form double
.replace("23e", "23E").replace("E+", "E").replace("0.23E17", "2.3E16");
private String messageSetText =
"[protobuf_unittest.TestMessageSetExtension1] {\n" +
" i: 123\n" +
......@@ -231,7 +241,13 @@ public class TextFormatTest extends TestCase {
.addRepeatedDouble(123)
.addRepeatedDouble(123.5)
.addRepeatedDouble(0.125)
.addRepeatedDouble(.125)
.addRepeatedDouble(-.125)
.addRepeatedDouble(123e15)
.addRepeatedDouble(123e15)
.addRepeatedDouble(-1.23e-17)
.addRepeatedDouble(.23e17)
.addRepeatedDouble(-23e15)
.addRepeatedDouble(123.5e20)
.addRepeatedDouble(123.5e-20)
.addRepeatedDouble(123.456789)
......@@ -244,7 +260,7 @@ public class TextFormatTest extends TestCase {
.addRepeatedBytes(bytes("\0\001\007\b\f\n\r\t\013\\\'\"\u00fe"))
.build();
assertEquals(exoticText, message.toString());
assertEquals(canonicalExoticText, message.toString());
}
public void testPrintMessageSet() throws Exception {
......@@ -319,7 +335,7 @@ public class TextFormatTest extends TestCase {
// Too lazy to check things individually. Don't try to debug this
// if testPrintExotic() is failing.
assertEquals(exoticText, builder.build().toString());
assertEquals(canonicalExoticText, builder.build().toString());
}
public void testParseMessageSet() throws Exception {
......
......@@ -235,6 +235,9 @@ public class WireFormatTest extends TestCase {
TestUtil.assertPackedFieldsSet(TestPackedTypes.parseDelimitedFrom(input));
assertEquals(34, input.read());
assertEquals(-1, input.read());
// We're at EOF, so parsing again should return null.
assertTrue(TestAllTypes.parseDelimitedFrom(input) == null);
}
private void assertFieldsInOrder(ByteString data) throws Exception {
......
This diff is collapsed.
......@@ -54,8 +54,7 @@ class BaseContainer(object):
Args:
message_listener: A MessageListener implementation.
The RepeatedScalarFieldContainer will call this object's
TransitionToNonempty() method when it transitions from being empty to
being nonempty.
Modified() method when it is modified.
"""
self._message_listener = message_listener
self._values = []
......@@ -73,6 +72,9 @@ class BaseContainer(object):
# The concrete classes should define __eq__.
return not self == other
def __repr__(self):
return repr(self._values)
class RepeatedScalarFieldContainer(BaseContainer):
......@@ -86,8 +88,7 @@ class RepeatedScalarFieldContainer(BaseContainer):
Args:
message_listener: A MessageListener implementation.
The RepeatedScalarFieldContainer will call this object's
TransitionToNonempty() method when it transitions from being empty to
being nonempty.
Modified() method when it is modified.
type_checker: A type_checkers.ValueChecker instance to run on elements
inserted into this container.
"""
......@@ -96,44 +97,47 @@ class RepeatedScalarFieldContainer(BaseContainer):
def append(self, value):
"""Appends an item to the list. Similar to list.append()."""
self.insert(len(self._values), value)
self._type_checker.CheckValue(value)
self._values.append(value)
if not self._message_listener.dirty:
self._message_listener.Modified()
def insert(self, key, value):
"""Inserts the item at the specified position. Similar to list.insert()."""
self._type_checker.CheckValue(value)
self._values.insert(key, value)
self._message_listener.ByteSizeDirty()
if len(self._values) == 1:
self._message_listener.TransitionToNonempty()
if not self._message_listener.dirty:
self._message_listener.Modified()
def extend(self, elem_seq):
"""Extends by appending the given sequence. Similar to list.extend()."""
if not elem_seq:
return
orig_empty = len(self._values) == 0
new_values = []
for elem in elem_seq:
self._type_checker.CheckValue(elem)
new_values.append(elem)
self._values.extend(new_values)
self._message_listener.ByteSizeDirty()
if orig_empty:
self._message_listener.TransitionToNonempty()
self._message_listener.Modified()
def MergeFrom(self, other):
"""Appends the contents of another repeated field of the same type to this
one. We do not check the types of the individual fields.
"""
self._values.extend(other._values)
self._message_listener.Modified()
def remove(self, elem):
"""Removes an item from the list. Similar to list.remove()."""
self._values.remove(elem)
self._message_listener.ByteSizeDirty()
self._message_listener.Modified()
def __setitem__(self, key, value):
"""Sets the item on the specified position."""
# No need to call TransitionToNonempty(), since if we're able to
# set the element at this index, we were already nonempty before
# this method was called.
self._message_listener.ByteSizeDirty()
self._type_checker.CheckValue(value)
self._values[key] = value
self._message_listener.Modified()
def __getslice__(self, start, stop):
"""Retrieves the subset of items from between the specified indices."""
......@@ -146,17 +150,17 @@ class RepeatedScalarFieldContainer(BaseContainer):
self._type_checker.CheckValue(value)
new_values.append(value)
self._values[start:stop] = new_values
self._message_listener.ByteSizeDirty()
self._message_listener.Modified()
def __delitem__(self, key):
"""Deletes the item at the specified position."""
del self._values[key]
self._message_listener.ByteSizeDirty()
self._message_listener.Modified()
def __delslice__(self, start, stop):
"""Deletes the subset of items from between the specified indices."""
del self._values[start:stop]
self._message_listener.ByteSizeDirty()
self._message_listener.Modified()
def __eq__(self, other):
"""Compares the current instance with another one."""
......@@ -186,8 +190,7 @@ class RepeatedCompositeFieldContainer(BaseContainer):
Args:
message_listener: A MessageListener implementation.
The RepeatedCompositeFieldContainer will call this object's
TransitionToNonempty() method when it transitions from being empty to
being nonempty.
Modified() method when it is modified.
message_descriptor: A Descriptor instance describing the protocol type
that should be present in this container. We'll use the
_concrete_class field of this descriptor when the client calls add().
......@@ -199,10 +202,24 @@ class RepeatedCompositeFieldContainer(BaseContainer):
new_element = self._message_descriptor._concrete_class()
new_element._SetListener(self._message_listener)
self._values.append(new_element)
self._message_listener.ByteSizeDirty()
self._message_listener.TransitionToNonempty()
if not self._message_listener.dirty:
self._message_listener.Modified()
return new_element
def MergeFrom(self, other):
"""Appends the contents of another repeated field of the same type to this
one, copying each individual message.
"""
message_class = self._message_descriptor._concrete_class
listener = self._message_listener
values = self._values
for message in other._values:
new_element = message_class()
new_element._SetListener(listener)
new_element.MergeFrom(message)
values.append(new_element)
listener.Modified()
def __getslice__(self, start, stop):
"""Retrieves the subset of items from between the specified indices."""
return self._values[start:stop]
......@@ -210,12 +227,12 @@ class RepeatedCompositeFieldContainer(BaseContainer):
def __delitem__(self, key):
"""Deletes the item at the specified position."""
del self._values[key]
self._message_listener.ByteSizeDirty()
self._message_listener.Modified()
def __delslice__(self, start, stop):
"""Deletes the subset of items from between the specified indices."""
del self._values[start:stop]
self._message_listener.ByteSizeDirty()
self._message_listener.Modified()
def __eq__(self, other):
"""Compares the current instance with another one."""
......
This diff is collapsed.
This diff is collapsed.
......@@ -35,16 +35,30 @@
__author__ = 'robinson@google.com (Will Robinson)'
import unittest
from google.protobuf import unittest_import_pb2
from google.protobuf import unittest_pb2
from google.protobuf import descriptor_pb2
from google.protobuf import descriptor
from google.protobuf import text_format
TEST_EMPTY_MESSAGE_DESCRIPTOR_ASCII = """
name: 'TestEmptyMessage'
"""
class DescriptorTest(unittest.TestCase):
def setUp(self):
self.my_file = descriptor.FileDescriptor(
name='some/filename/some.proto',
package='protobuf_unittest'
)
self.my_enum = descriptor.EnumDescriptor(
name='ForeignEnum',
full_name='protobuf_unittest.ForeignEnum',
filename='ForeignEnum',
filename=None,
file=self.my_file,
values=[
descriptor.EnumValueDescriptor(name='FOREIGN_FOO', index=0, number=4),
descriptor.EnumValueDescriptor(name='FOREIGN_BAR', index=1, number=5),
......@@ -53,7 +67,8 @@ class DescriptorTest(unittest.TestCase):
self.my_message = descriptor.Descriptor(
name='NestedMessage',
full_name='protobuf_unittest.TestAllTypes.NestedMessage',
filename='some/filename/some.proto',
filename=None,
file=self.my_file,
containing_type=None,
fields=[
descriptor.FieldDescriptor(
......@@ -61,7 +76,7 @@ class DescriptorTest(unittest.TestCase):
full_name='protobuf_unittest.TestAllTypes.NestedMessage.bb',
index=0, number=1,
type=5, cpp_type=1, label=1,
default_value=0,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None),
],
......@@ -80,6 +95,7 @@ class DescriptorTest(unittest.TestCase):
self.my_service = descriptor.ServiceDescriptor(
name='TestServiceWithOptions',
full_name='protobuf_unittest.TestServiceWithOptions',
file=self.my_file,
index=0,
methods=[
self.my_method
......@@ -109,5 +125,210 @@ class DescriptorTest(unittest.TestCase):
self.assertEqual(self.my_service.GetOptions(),
descriptor_pb2.ServiceOptions())
def testFileDescriptorReferences(self):
self.assertEqual(self.my_enum.file, self.my_file)
self.assertEqual(self.my_message.file, self.my_file)
def testFileDescriptor(self):
self.assertEqual(self.my_file.name, 'some/filename/some.proto')
self.assertEqual(self.my_file.package, 'protobuf_unittest')
class DescriptorCopyToProtoTest(unittest.TestCase):
"""Tests for CopyTo functions of Descriptor."""
def _AssertProtoEqual(self, actual_proto, expected_class, expected_ascii):
expected_proto = expected_class()
text_format.Merge(expected_ascii, expected_proto)
self.assertEqual(
actual_proto, expected_proto,
'Not equal,\nActual:\n%s\nExpected:\n%s\n'
% (str(actual_proto), str(expected_proto)))
def _InternalTestCopyToProto(self, desc, expected_proto_class,
expected_proto_ascii):
actual = expected_proto_class()
desc.CopyToProto(actual)
self._AssertProtoEqual(
actual, expected_proto_class, expected_proto_ascii)
def testCopyToProto_EmptyMessage(self):
self._InternalTestCopyToProto(
unittest_pb2.TestEmptyMessage.DESCRIPTOR,
descriptor_pb2.DescriptorProto,
TEST_EMPTY_MESSAGE_DESCRIPTOR_ASCII)
def testCopyToProto_NestedMessage(self):
TEST_NESTED_MESSAGE_ASCII = """
name: 'NestedMessage'
field: <
name: 'bb'
number: 1
label: 1 # Optional
type: 5 # TYPE_INT32
>
"""
self._InternalTestCopyToProto(
unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR,
descriptor_pb2.DescriptorProto,
TEST_NESTED_MESSAGE_ASCII)
def testCopyToProto_ForeignNestedMessage(self):
TEST_FOREIGN_NESTED_ASCII = """
name: 'TestForeignNested'
field: <
name: 'foreign_nested'
number: 1
label: 1 # Optional
type: 11 # TYPE_MESSAGE
type_name: '.protobuf_unittest.TestAllTypes.NestedMessage'
>
"""
self._InternalTestCopyToProto(
unittest_pb2.TestForeignNested.DESCRIPTOR,
descriptor_pb2.DescriptorProto,
TEST_FOREIGN_NESTED_ASCII)
def testCopyToProto_ForeignEnum(self):
TEST_FOREIGN_ENUM_ASCII = """
name: 'ForeignEnum'
value: <
name: 'FOREIGN_FOO'
number: 4
>
value: <
name: 'FOREIGN_BAR'
number: 5
>
value: <
name: 'FOREIGN_BAZ'
number: 6
>
"""
self._InternalTestCopyToProto(
unittest_pb2._FOREIGNENUM,
descriptor_pb2.EnumDescriptorProto,
TEST_FOREIGN_ENUM_ASCII)
def testCopyToProto_Options(self):
TEST_DEPRECATED_FIELDS_ASCII = """
name: 'TestDeprecatedFields'
field: <
name: 'deprecated_int32'
number: 1
label: 1 # Optional
type: 5 # TYPE_INT32
options: <
deprecated: true
>
>
"""
self._InternalTestCopyToProto(
unittest_pb2.TestDeprecatedFields.DESCRIPTOR,
descriptor_pb2.DescriptorProto,
TEST_DEPRECATED_FIELDS_ASCII)
def testCopyToProto_AllExtensions(self):
TEST_EMPTY_MESSAGE_WITH_EXTENSIONS_ASCII = """
name: 'TestEmptyMessageWithExtensions'
extension_range: <
start: 1
end: 536870912
>
"""
self._InternalTestCopyToProto(
unittest_pb2.TestEmptyMessageWithExtensions.DESCRIPTOR,
descriptor_pb2.DescriptorProto,
TEST_EMPTY_MESSAGE_WITH_EXTENSIONS_ASCII)
def testCopyToProto_SeveralExtensions(self):
TEST_MESSAGE_WITH_SEVERAL_EXTENSIONS_ASCII = """
name: 'TestMultipleExtensionRanges'
extension_range: <
start: 42
end: 43
>
extension_range: <
start: 4143
end: 4244
>
extension_range: <
start: 65536
end: 536870912
>
"""
self._InternalTestCopyToProto(
unittest_pb2.TestMultipleExtensionRanges.DESCRIPTOR,
descriptor_pb2.DescriptorProto,
TEST_MESSAGE_WITH_SEVERAL_EXTENSIONS_ASCII)
def testCopyToProto_FileDescriptor(self):
UNITTEST_IMPORT_FILE_DESCRIPTOR_ASCII = ("""
name: 'google/protobuf/unittest_import.proto'
package: 'protobuf_unittest_import'
message_type: <
name: 'ImportMessage'
field: <
name: 'd'
number: 1
label: 1 # Optional
type: 5 # TYPE_INT32
>
>
""" +
"""enum_type: <
name: 'ImportEnum'
value: <
name: 'IMPORT_FOO'
number: 7
>
value: <
name: 'IMPORT_BAR'
number: 8
>
value: <
name: 'IMPORT_BAZ'
number: 9
>
>
options: <
java_package: 'com.google.protobuf.test'
optimize_for: 1 # SPEED
>
""")
self._InternalTestCopyToProto(
unittest_import_pb2.DESCRIPTOR,
descriptor_pb2.FileDescriptorProto,
UNITTEST_IMPORT_FILE_DESCRIPTOR_ASCII)
def testCopyToProto_ServiceDescriptor(self):
TEST_SERVICE_ASCII = """
name: 'TestService'
method: <
name: 'Foo'
input_type: '.protobuf_unittest.FooRequest'
output_type: '.protobuf_unittest.FooResponse'
>
method: <
name: 'Bar'
input_type: '.protobuf_unittest.BarRequest'
output_type: '.protobuf_unittest.BarResponse'
>
"""
self._InternalTestCopyToProto(
unittest_pb2.TestService.DESCRIPTOR,
descriptor_pb2.ServiceDescriptorProto,
TEST_SERVICE_ASCII)
if __name__ == '__main__':
unittest.main()
This diff is collapsed.
This diff is collapsed.
......@@ -35,15 +35,20 @@
# indirect testing of the protocol compiler output.
"""Unittest that directly tests the output of the pure-Python protocol
compiler. See //net/proto2/internal/reflection_test.py for a test which
compiler. See //google/protobuf/reflection_test.py for a test which
further ensures that we can use Python protocol message objects as we expect.
"""
__author__ = 'robinson@google.com (Will Robinson)'
import unittest
from google.protobuf import unittest_import_pb2
from google.protobuf import unittest_mset_pb2
from google.protobuf import unittest_pb2
from google.protobuf import unittest_no_generic_services_pb2
MAX_EXTENSION = 536870912
class GeneratorTest(unittest.TestCase):
......@@ -71,6 +76,31 @@ class GeneratorTest(unittest.TestCase):
self.assertEqual(3, proto.BAZ)
self.assertEqual(3, unittest_pb2.TestAllTypes.BAZ)
def testExtremeDefaultValues(self):
message = unittest_pb2.TestExtremeDefaultValues()
self.assertEquals(float('inf'), message.inf_double)
self.assertEquals(float('-inf'), message.neg_inf_double)
self.assert_(message.nan_double != message.nan_double)
self.assertEquals(float('inf'), message.inf_float)
self.assertEquals(float('-inf'), message.neg_inf_float)
self.assert_(message.nan_float != message.nan_float)
def testHasDefaultValues(self):
desc = unittest_pb2.TestAllTypes.DESCRIPTOR
expected_has_default_by_name = {
'optional_int32': False,
'repeated_int32': False,
'optional_nested_message': False,
'default_int32': True,
}
has_default_by_name = dict(
[(f.name, f.has_default_value)
for f in desc.fields
if f.name in expected_has_default_by_name])
self.assertEqual(expected_has_default_by_name, has_default_by_name)
def testContainingTypeBehaviorForExtensions(self):
self.assertEqual(unittest_pb2.optional_int32_extension.containing_type,
unittest_pb2.TestAllExtensions.DESCRIPTOR)
......@@ -95,6 +125,81 @@ class GeneratorTest(unittest.TestCase):
proto = unittest_mset_pb2.TestMessageSet()
self.assertTrue(proto.DESCRIPTOR.GetOptions().message_set_wire_format)
def testNestedTypes(self):
self.assertEquals(
set(unittest_pb2.TestAllTypes.DESCRIPTOR.nested_types),
set([
unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR,
unittest_pb2.TestAllTypes.OptionalGroup.DESCRIPTOR,
unittest_pb2.TestAllTypes.RepeatedGroup.DESCRIPTOR,
]))
self.assertEqual(unittest_pb2.TestEmptyMessage.DESCRIPTOR.nested_types, [])
self.assertEqual(
unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR.nested_types, [])
def testContainingType(self):
self.assertTrue(
unittest_pb2.TestEmptyMessage.DESCRIPTOR.containing_type is None)
self.assertTrue(
unittest_pb2.TestAllTypes.DESCRIPTOR.containing_type is None)
self.assertEqual(
unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR.containing_type,
unittest_pb2.TestAllTypes.DESCRIPTOR)
self.assertEqual(
unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR.containing_type,
unittest_pb2.TestAllTypes.DESCRIPTOR)
self.assertEqual(
unittest_pb2.TestAllTypes.RepeatedGroup.DESCRIPTOR.containing_type,
unittest_pb2.TestAllTypes.DESCRIPTOR)
def testContainingTypeInEnumDescriptor(self):
self.assertTrue(unittest_pb2._FOREIGNENUM.containing_type is None)
self.assertEqual(unittest_pb2._TESTALLTYPES_NESTEDENUM.containing_type,
unittest_pb2.TestAllTypes.DESCRIPTOR)
def testPackage(self):
self.assertEqual(
unittest_pb2.TestAllTypes.DESCRIPTOR.file.package,
'protobuf_unittest')
desc = unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR
self.assertEqual(desc.file.package, 'protobuf_unittest')
self.assertEqual(
unittest_import_pb2.ImportMessage.DESCRIPTOR.file.package,
'protobuf_unittest_import')
self.assertEqual(
unittest_pb2._FOREIGNENUM.file.package, 'protobuf_unittest')
self.assertEqual(
unittest_pb2._TESTALLTYPES_NESTEDENUM.file.package,
'protobuf_unittest')
self.assertEqual(
unittest_import_pb2._IMPORTENUM.file.package,
'protobuf_unittest_import')
def testExtensionRange(self):
self.assertEqual(
unittest_pb2.TestAllTypes.DESCRIPTOR.extension_ranges, [])
self.assertEqual(
unittest_pb2.TestAllExtensions.DESCRIPTOR.extension_ranges,
[(1, MAX_EXTENSION)])
self.assertEqual(
unittest_pb2.TestMultipleExtensionRanges.DESCRIPTOR.extension_ranges,
[(42, 43), (4143, 4244), (65536, MAX_EXTENSION)])
def testFileDescriptor(self):
self.assertEqual(unittest_pb2.DESCRIPTOR.name,
'google/protobuf/unittest.proto')
self.assertEqual(unittest_pb2.DESCRIPTOR.package, 'protobuf_unittest')
self.assertFalse(unittest_pb2.DESCRIPTOR.serialized_pb is None)
def testNoGenericServices(self):
# unittest_no_generic_services.proto should contain defs for everything
# except services.
self.assertTrue(hasattr(unittest_no_generic_services_pb2, "TestMessage"))
self.assertTrue(hasattr(unittest_no_generic_services_pb2, "FOO"))
self.assertTrue(hasattr(unittest_no_generic_services_pb2, "test_extension"))
self.assertFalse(hasattr(unittest_no_generic_services_pb2, "TestService"))
if __name__ == '__main__':
unittest.main()
This diff is collapsed.
This diff is collapsed.
......@@ -39,22 +39,34 @@ __author__ = 'robinson@google.com (Will Robinson)'
class MessageListener(object):
"""Listens for transitions to nonempty and for invalidations of cached
byte sizes. Meant to be registered via Message._SetListener().
"""
"""Listens for modifications made to a message. Meant to be registered via
Message._SetListener().
def TransitionToNonempty(self):
"""Called the *first* time that this message becomes nonempty.
Implementations are free (but not required) to call this method multiple
times after the message has become nonempty.
Attributes:
dirty: If True, then calling Modified() would be a no-op. This can be
used to avoid these calls entirely in the common case.
"""
raise NotImplementedError
def ByteSizeDirty(self):
"""Called *every* time the cached byte size value
for this object is invalidated (transitions from being
"clean" to "dirty").
def Modified(self):
"""Called every time the message is modified in such a way that the parent
message may need to be updated. This currently means either:
(a) The message was modified for the first time, so the parent message
should henceforth mark the message as present.
(b) The message's cached byte size became dirty -- i.e. the message was
modified for the first time after a previous call to ByteSize().
Therefore the parent should also mark its byte size as dirty.
Note that (a) implies (b), since new objects start out with a client cached
size (zero). However, we document (a) explicitly because it is important.
Modified() will *only* be called in response to one of these two events --
not every time the sub-message is modified.
Note that if the listener's |dirty| attribute is true, then calling
Modified at the moment would be a no-op, so it can be skipped. Performance-
sensitive callers should check this attribute directly before calling since
it will be true most of the time.
"""
raise NotImplementedError
......@@ -62,8 +74,5 @@ class NullMessageListener(object):
"""No-op MessageListener implementation."""
def TransitionToNonempty(self):
pass
def ByteSizeDirty(self):
def Modified(self):
pass
......@@ -30,7 +30,16 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Tests python protocol buffers against the golden message."""
"""Tests python protocol buffers against the golden message.
Note that the golden messages exercise every known field type, thus this
test ends up exercising and verifying nearly all of the parsing and
serialization code in the whole library.
TODO(kenton): Merge with wire_format_test? It doesn't make a whole lot of
sense to call this a test of the "message" module, which only declares an
abstract interface.
"""
__author__ = 'gps@google.com (Gregory P. Smith)'
......@@ -40,14 +49,41 @@ from google.protobuf import unittest_pb2
from google.protobuf.internal import test_util
class MessageTest(test_util.GoldenMessageTestCase):
class MessageTest(unittest.TestCase):
def testGoldenMessage(self):
golden_data = test_util.GoldenFile('golden_message').read()
golden_message = unittest_pb2.TestAllTypes()
golden_message.ParseFromString(golden_data)
self.ExpectAllFieldsSet(golden_message)
test_util.ExpectAllFieldsSet(self, golden_message)
self.assertTrue(golden_message.SerializeToString() == golden_data)
def testGoldenExtensions(self):
golden_data = test_util.GoldenFile('golden_message').read()
golden_message = unittest_pb2.TestAllExtensions()
golden_message.ParseFromString(golden_data)
all_set = unittest_pb2.TestAllExtensions()
test_util.SetAllExtensions(all_set)
self.assertEquals(all_set, golden_message)
self.assertTrue(golden_message.SerializeToString() == golden_data)
def testGoldenPackedMessage(self):
golden_data = test_util.GoldenFile('golden_packed_fields_message').read()
golden_message = unittest_pb2.TestPackedTypes()
golden_message.ParseFromString(golden_data)
all_set = unittest_pb2.TestPackedTypes()
test_util.SetAllPackedFields(all_set)
self.assertEquals(all_set, golden_message)
self.assertTrue(all_set.SerializeToString() == golden_data)
def testGoldenPackedExtensions(self):
golden_data = test_util.GoldenFile('golden_packed_fields_message').read()
golden_message = unittest_pb2.TestPackedExtensions()
golden_message.ParseFromString(golden_data)
all_set = unittest_pb2.TestPackedExtensions()
test_util.SetAllPackedExtensions(all_set)
self.assertEquals(all_set, golden_message)
self.assertTrue(all_set.SerializeToString() == golden_data)
if __name__ == '__main__':
unittest.main()
This diff is collapsed.
This diff is collapsed.
......@@ -43,7 +43,7 @@ from google.protobuf import unittest_pb2
from google.protobuf import unittest_mset_pb2
class TextFormatTest(test_util.GoldenMessageTestCase):
class TextFormatTest(unittest.TestCase):
def ReadGolden(self, golden_filename):
f = test_util.GoldenFile(golden_filename)
golden_lines = f.readlines()
......@@ -149,7 +149,7 @@ class TextFormatTest(test_util.GoldenMessageTestCase):
parsed_message = unittest_pb2.TestAllTypes()
text_format.Merge(ascii_text, parsed_message)
self.assertEqual(message, parsed_message)
self.ExpectAllFieldsSet(message)
test_util.ExpectAllFieldsSet(self, message)
def testMergeAllExtensions(self):
message = unittest_pb2.TestAllExtensions()
......@@ -212,12 +212,18 @@ class TextFormatTest(test_util.GoldenMessageTestCase):
text_format.Merge, text, message)
def testMergeBadExtension(self):
message = unittest_pb2.TestAllTypes()
message = unittest_pb2.TestAllExtensions()
text = '[unknown_extension]: 8\n'
self.assertRaisesWithMessage(
text_format.ParseError,
'1:2 : Extension "unknown_extension" not registered.',
text_format.Merge, text, message)
message = unittest_pb2.TestAllTypes()
self.assertRaisesWithMessage(
text_format.ParseError,
('1:2 : Message type "protobuf_unittest.TestAllTypes" does not have '
'extensions.'),
text_format.Merge, text, message)
def testMergeGroupNotClosed(self):
message = unittest_pb2.TestAllTypes()
......@@ -231,6 +237,19 @@ class TextFormatTest(test_util.GoldenMessageTestCase):
text_format.ParseError, '1:16 : Expected "}".',
text_format.Merge, text, message)
def testMergeEmptyGroup(self):
message = unittest_pb2.TestAllTypes()
text = 'OptionalGroup: {}'
text_format.Merge(text, message)
self.assertTrue(message.HasField('optionalgroup'))
message.Clear()
message = unittest_pb2.TestAllTypes()
text = 'OptionalGroup: <>'
text_format.Merge(text, message)
self.assertTrue(message.HasField('optionalgroup'))
def testMergeBadEnumValue(self):
message = unittest_pb2.TestAllTypes()
text = 'optional_nested_enum: BARR'
......
......@@ -99,7 +99,7 @@ class Message(object):
Args:
other_msg: Message to copy into the current one.
"""
if self == other_msg:
if self is other_msg:
return
self.Clear()
self.MergeFrom(other_msg)
......@@ -108,6 +108,15 @@ class Message(object):
"""Clears all data that was set in the message."""
raise NotImplementedError
def SetInParent(self):
"""Mark this as present in the parent.
This normally happens automatically when you assign a field of a
sub-message, but sometimes you want to make the sub-message
present while keeping it empty. If you find yourself using this,
you may want to reconsider your design."""
raise NotImplementedError
def IsInitialized(self):
"""Checks if the message is initialized.
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -103,6 +103,13 @@ class LIBPROTOC_EXPORT OutputDirectory {
// contain "." or ".." components.
virtual io::ZeroCopyOutputStream* Open(const string& filename) = 0;
// Creates a ZeroCopyOutputStream which will insert code into the given file
// at the given insertion point. See plugin.proto for more information on
// insertion points. The default implementation assert-fails -- it exists
// only for backwards-compatibility.
virtual io::ZeroCopyOutputStream* OpenForInsert(
const string& filename, const string& insertion_point);
private:
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(OutputDirectory);
};
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment