blob: 69c5795c1f3cae7449837fcafca329becd67a5f9 [file] [log] [blame]
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.google.protobuf;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import protobuf_unittest.UnittestOptimizeFor;
import protobuf_unittest.UnittestOptimizeFor.TestOptimizedForSize;
import protobuf_unittest.UnittestOptimizeFor.TestRequiredOptimizedForSize;
import protobuf_unittest.UnittestProto;
import protobuf_unittest.UnittestProto.ForeignMessage;
import protobuf_unittest.UnittestProto.TestAllTypes;
import protobuf_unittest.UnittestProto.TestEmptyMessage;
import protobuf_unittest.UnittestProto.TestMergeException;
import protobuf_unittest.UnittestProto.TestParsingMerge;
import protobuf_unittest.UnittestProto.TestRequired;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit test for {@link Parser}. */
@RunWith(JUnit4.class)
public class ParserTest {
@Test
public void testGeneratedMessageParserSingleton() throws Exception {
for (int i = 0; i < 10; i++) {
assertThat(TestUtil.getAllSet().getParserForType()).isEqualTo(TestAllTypes.parser());
}
}
private void assertRoundTripEquals(MessageLite message, ExtensionRegistryLite registry)
throws Exception {
final byte[] data = message.toByteArray();
final int offset = 20;
final int length = data.length;
final int padding = 30;
Parser<? extends MessageLite> parser = message.getParserForType();
assertMessageEquals(message, parser.parseFrom(data, registry));
assertMessageEquals(
message,
parser.parseFrom(generatePaddingArray(data, offset, padding), offset, length, registry));
assertMessageEquals(message, parser.parseFrom(message.toByteString(), registry));
assertMessageEquals(message, parser.parseFrom(new ByteArrayInputStream(data), registry));
assertMessageEquals(message, parser.parseFrom(CodedInputStream.newInstance(data), registry));
assertMessageEquals(
message, parser.parseFrom(message.toByteString().asReadOnlyByteBuffer(), registry));
}
@SuppressWarnings("unchecked")
private void assertRoundTripEquals(MessageLite message) throws Exception {
final byte[] data = message.toByteArray();
final int offset = 20;
final int length = data.length;
final int padding = 30;
Parser<MessageLite> parser = (Parser<MessageLite>) message.getParserForType();
assertMessageEquals(message, parser.parseFrom(data));
assertMessageEquals(
message, parser.parseFrom(generatePaddingArray(data, offset, padding), offset, length));
assertMessageEquals(message, parser.parseFrom(message.toByteString()));
assertMessageEquals(message, parser.parseFrom(new ByteArrayInputStream(data)));
assertMessageEquals(message, parser.parseFrom(CodedInputStream.newInstance(data)));
assertMessageEquals(message, parser.parseFrom(message.toByteString().asReadOnlyByteBuffer()));
}
private void assertMessageEquals(MessageLite expected, MessageLite actual) throws Exception {
if (expected instanceof Message) {
assertThat(actual).isEqualTo(expected);
} else {
assertThat(actual.toByteString()).isEqualTo(expected.toByteString());
}
}
private byte[] generatePaddingArray(byte[] data, int offset, int padding) {
byte[] result = new byte[offset + data.length + padding];
System.arraycopy(data, 0, result, offset, data.length);
return result;
}
@Test
public void testNormalMessage() throws Exception {
assertRoundTripEquals(TestUtil.getAllSet());
}
@Test
public void testParsePartial() throws Exception {
assertParsePartial(TestRequired.parser(), TestRequired.newBuilder().setA(1).buildPartial());
}
private <T extends MessageLite> void assertParsePartial(Parser<T> parser, T partialMessage)
throws Exception {
final String errorString = "Should throw exceptions when the parsed message isn't initialized.";
// parsePartialFrom should pass.
byte[] data = partialMessage.toByteArray();
assertThat(parser.parsePartialFrom(data)).isEqualTo(partialMessage);
assertThat(parser.parsePartialFrom(partialMessage.toByteString())).isEqualTo(partialMessage);
assertThat(parser.parsePartialFrom(new ByteArrayInputStream(data))).isEqualTo(partialMessage);
assertThat(parser.parsePartialFrom(CodedInputStream.newInstance(data)))
.isEqualTo(partialMessage);
// parseFrom(ByteArray)
try {
parser.parseFrom(partialMessage.toByteArray());
assertWithMessage(errorString).fail();
} catch (InvalidProtocolBufferException e) {
// pass.
}
// parseFrom(ByteString)
try {
parser.parseFrom(partialMessage.toByteString());
assertWithMessage(errorString).fail();
} catch (InvalidProtocolBufferException e) {
// pass.
}
// parseFrom(InputStream)
try {
parser.parseFrom(new ByteArrayInputStream(partialMessage.toByteArray()));
assertWithMessage(errorString).fail();
} catch (IOException e) {
// pass.
}
// parseFrom(CodedInputStream)
try {
parser.parseFrom(CodedInputStream.newInstance(partialMessage.toByteArray()));
assertWithMessage(errorString).fail();
} catch (IOException e) {
// pass.
}
}
@Test
public void testParseExtensions() throws Exception {
assertRoundTripEquals(TestUtil.getAllExtensionsSet(), TestUtil.getExtensionRegistry());
}
@Test
public void testParsePacked() throws Exception {
assertRoundTripEquals(TestUtil.getPackedSet());
assertRoundTripEquals(TestUtil.getPackedExtensionsSet(), TestUtil.getExtensionRegistry());
}
@Test
public void testParseDelimitedTo() throws Exception {
// Write normal Message.
TestAllTypes normalMessage = TestUtil.getAllSet();
ByteArrayOutputStream output = new ByteArrayOutputStream();
normalMessage.writeDelimitedTo(output);
normalMessage.writeDelimitedTo(output);
InputStream input = new ByteArrayInputStream(output.toByteArray());
assertMessageEquals(normalMessage, normalMessage.getParserForType().parseDelimitedFrom(input));
assertMessageEquals(normalMessage, normalMessage.getParserForType().parseDelimitedFrom(input));
}
@Test
public void testParseUnknownFields() throws Exception {
// All fields will be treated as unknown fields in emptyMessage.
TestEmptyMessage emptyMessage = TestEmptyMessage.parseFrom(TestUtil.getAllSet().toByteString());
assertThat(emptyMessage.toByteString()).isEqualTo(TestUtil.getAllSet().toByteString());
}
@Test
public void testOptimizeForSize() throws Exception {
TestOptimizedForSize.Builder builder = TestOptimizedForSize.newBuilder();
builder.setI(12).setMsg(ForeignMessage.newBuilder().setC(34).build());
builder.setExtension(TestOptimizedForSize.testExtension, 56);
builder.setExtension(
TestOptimizedForSize.testExtension2,
TestRequiredOptimizedForSize.newBuilder().setX(78).build());
TestOptimizedForSize message = builder.build();
ExtensionRegistry registry = ExtensionRegistry.newInstance();
UnittestOptimizeFor.registerAllExtensions(registry);
assertRoundTripEquals(message, registry);
}
/** Helper method for {@link #testParsingMerge()}. */
private void assertMessageMerged(TestAllTypes allTypes) throws Exception {
assertThat(allTypes.getOptionalInt32()).isEqualTo(3);
assertThat(allTypes.getOptionalInt64()).isEqualTo(2);
assertThat(allTypes.getOptionalString()).isEqualTo("hello");
}
@Test
public void testParsingMerge() throws Exception {
// Build messages.
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
TestAllTypes msg1 = builder.setOptionalInt32(1).build();
builder.clear();
TestAllTypes msg2 = builder.setOptionalInt64(2).build();
builder.clear();
TestAllTypes msg3 = builder.setOptionalInt32(3).setOptionalString("hello").build();
// Build groups.
TestParsingMerge.RepeatedFieldsGenerator.Group1 optionalG1 =
TestParsingMerge.RepeatedFieldsGenerator.Group1.newBuilder().setField1(msg1).build();
TestParsingMerge.RepeatedFieldsGenerator.Group1 optionalG2 =
TestParsingMerge.RepeatedFieldsGenerator.Group1.newBuilder().setField1(msg2).build();
TestParsingMerge.RepeatedFieldsGenerator.Group1 optionalG3 =
TestParsingMerge.RepeatedFieldsGenerator.Group1.newBuilder().setField1(msg3).build();
TestParsingMerge.RepeatedFieldsGenerator.Group2 repeatedG1 =
TestParsingMerge.RepeatedFieldsGenerator.Group2.newBuilder().setField1(msg1).build();
TestParsingMerge.RepeatedFieldsGenerator.Group2 repeatedG2 =
TestParsingMerge.RepeatedFieldsGenerator.Group2.newBuilder().setField1(msg2).build();
TestParsingMerge.RepeatedFieldsGenerator.Group2 repeatedG3 =
TestParsingMerge.RepeatedFieldsGenerator.Group2.newBuilder().setField1(msg3).build();
// Assign and serialize RepeatedFieldsGenerator.
ByteString data =
TestParsingMerge.RepeatedFieldsGenerator.newBuilder()
.addField1(msg1)
.addField1(msg2)
.addField1(msg3)
.addField2(msg1)
.addField2(msg2)
.addField2(msg3)
.addField3(msg1)
.addField3(msg2)
.addField3(msg3)
.addGroup1(optionalG1)
.addGroup1(optionalG2)
.addGroup1(optionalG3)
.addGroup2(repeatedG1)
.addGroup2(repeatedG2)
.addGroup2(repeatedG3)
.addExt1(msg1)
.addExt1(msg2)
.addExt1(msg3)
.addExt2(msg1)
.addExt2(msg2)
.addExt2(msg3)
.build()
.toByteString();
// Parse TestParsingMerge.
ExtensionRegistry registry = ExtensionRegistry.newInstance();
UnittestProto.registerAllExtensions(registry);
TestParsingMerge parsingMerge = TestParsingMerge.parseFrom(data, registry);
// Required and optional fields should be merged.
assertMessageMerged(parsingMerge.getRequiredAllTypes());
assertMessageMerged(parsingMerge.getOptionalAllTypes());
assertMessageMerged(parsingMerge.getOptionalGroup().getOptionalGroupAllTypes());
assertMessageMerged(parsingMerge.getExtension(TestParsingMerge.optionalExt));
// Repeated fields should not be merged.
assertThat(parsingMerge.getRepeatedAllTypesCount()).isEqualTo(3);
assertThat(parsingMerge.getRepeatedGroupCount()).isEqualTo(3);
assertThat(parsingMerge.getExtensionCount(TestParsingMerge.repeatedExt)).isEqualTo(3);
}
@Test
public void testExceptionWhenMergingExtendedMessagesMissingRequiredFields() {
// create a TestMergeException message (missing required fields) that looks like
// all_extensions {
// [TestRequired.single] {
// }
// }
TestMergeException.Builder message = TestMergeException.newBuilder();
message
.getAllExtensionsBuilder()
.setExtension(TestRequired.single, TestRequired.newBuilder().buildPartial());
ByteString byteString = message.buildPartial().toByteString();
// duplicate the bytestring to make the `all_extensions` field repeat twice, so that it will
// need merging when parsing back
ByteString duplicatedByteString = byteString.concat(byteString);
byte[] bytes = duplicatedByteString.toByteArray();
ExtensionRegistry registry = ExtensionRegistry.newInstance();
UnittestProto.registerAllExtensions(registry);
// `parseFrom` should throw InvalidProtocolBufferException, not UninitializedMessageException,
// for each of the 5 possible input types:
// parseFrom(ByteString)
try {
TestMergeException.parseFrom(duplicatedByteString, registry);
assertWithMessage("Expected InvalidProtocolBufferException").fail();
} catch (Exception e) {
assertThat(e.getClass()).isEqualTo(InvalidProtocolBufferException.class);
}
// parseFrom(ByteArray)
try {
TestMergeException.parseFrom(bytes, registry);
assertWithMessage("Expected InvalidProtocolBufferException").fail();
} catch (Exception e) {
assertThat(e.getClass()).isEqualTo(InvalidProtocolBufferException.class);
}
// parseFrom(InputStream)
try {
TestMergeException.parseFrom(new ByteArrayInputStream(bytes), registry);
assertWithMessage("Expected InvalidProtocolBufferException").fail();
} catch (Exception e) {
assertThat(e.getClass()).isEqualTo(InvalidProtocolBufferException.class);
}
// parseFrom(CodedInputStream)
try {
TestMergeException.parseFrom(CodedInputStream.newInstance(bytes), registry);
assertWithMessage("Expected InvalidProtocolBufferException").fail();
} catch (Exception e) {
assertThat(e.getClass()).isEqualTo(InvalidProtocolBufferException.class);
}
// parseFrom(ByteBuffer)
try {
TestMergeException.parseFrom(duplicatedByteString.asReadOnlyByteBuffer(), registry);
assertWithMessage("Expected InvalidProtocolBufferException").fail();
} catch (Exception e) {
assertThat(e.getClass()).isEqualTo(InvalidProtocolBufferException.class);
}
}
@Test
public void testParseDelimitedFrom_firstByteInterrupted_preservesCause() {
try {
TestAllTypes.parseDelimitedFrom(
new InputStream() {
@Override
public int read() throws IOException {
throw new InterruptedIOException();
}
});
assertWithMessage("Expected InterruptedIOException").fail();
} catch (Exception e) {
assertThat(e.getClass()).isEqualTo(InterruptedIOException.class);
}
}
@Test
public void testParseDelimitedFrom_secondByteInterrupted_preservesCause() {
try {
TestAllTypes.parseDelimitedFrom(
new InputStream() {
private int i;
@Override
public int read() throws IOException {
switch (i++) {
case 0:
return 1;
case 1:
throw new InterruptedIOException();
default:
throw new AssertionError();
}
}
});
assertWithMessage("Expected InterruptedIOException").fail();
} catch (Exception e) {
assertThat(e.getClass()).isEqualTo(InterruptedIOException.class);
}
}
}