protobuf/conformance/ConformanceJava.java
2016-11-17 16:59:59 -08:00

315 lines
10 KiB
Java

import com.google.protobuf.ByteString;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.conformance.Conformance;
import com.google.protobuf.util.JsonFormat.TypeRegistry;
import com.google.protobuf.util.JsonFormat;
import java.io.IOException;
import java.nio.ByteBuffer;
class ConformanceJava {
private int testCount = 0;
private TypeRegistry typeRegistry;
private boolean readFromStdin(byte[] buf, int len) throws Exception {
int ofs = 0;
while (len > 0) {
int read = System.in.read(buf, ofs, len);
if (read == -1) {
return false; // EOF
}
ofs += read;
len -= read;
}
return true;
}
private void writeToStdout(byte[] buf) throws Exception {
System.out.write(buf);
}
// Returns -1 on EOF (the actual values will always be positive).
private int readLittleEndianIntFromStdin() throws Exception {
byte[] buf = new byte[4];
if (!readFromStdin(buf, 4)) {
return -1;
}
return (buf[0] & 0xff)
| ((buf[1] & 0xff) << 8)
| ((buf[2] & 0xff) << 16)
| ((buf[3] & 0xff) << 24);
}
private void writeLittleEndianIntToStdout(int val) throws Exception {
byte[] buf = new byte[4];
buf[0] = (byte)val;
buf[1] = (byte)(val >> 8);
buf[2] = (byte)(val >> 16);
buf[3] = (byte)(val >> 24);
writeToStdout(buf);
}
private enum BinaryDecoder {
BYTE_STRING_DECODER() {
@Override
public Conformance.TestAllTypes parse(ByteString bytes)
throws InvalidProtocolBufferException {
return Conformance.TestAllTypes.parseFrom(bytes);
}
},
BYTE_ARRAY_DECODER() {
@Override
public Conformance.TestAllTypes parse(ByteString bytes)
throws InvalidProtocolBufferException {
return Conformance.TestAllTypes.parseFrom(bytes.toByteArray());
}
},
ARRAY_BYTE_BUFFER_DECODER() {
@Override
public Conformance.TestAllTypes parse(ByteString bytes)
throws InvalidProtocolBufferException {
ByteBuffer buffer = ByteBuffer.allocate(bytes.size());
bytes.copyTo(buffer);
buffer.flip();
try {
return Conformance.TestAllTypes.parseFrom(CodedInputStream.newInstance(buffer));
} catch (InvalidProtocolBufferException e) {
throw e;
} catch (IOException e) {
throw new RuntimeException(
"ByteString based ByteBuffer should not throw IOException.", e);
}
}
},
READONLY_ARRAY_BYTE_BUFFER_DECODER() {
@Override
public Conformance.TestAllTypes parse(ByteString bytes)
throws InvalidProtocolBufferException {
try {
return Conformance.TestAllTypes.parseFrom(
CodedInputStream.newInstance(bytes.asReadOnlyByteBuffer()));
} catch (InvalidProtocolBufferException e) {
throw e;
} catch (IOException e) {
throw new RuntimeException(
"ByteString based ByteBuffer should not throw IOException.", e);
}
}
},
DIRECT_BYTE_BUFFER_DECODER() {
@Override
public Conformance.TestAllTypes parse(ByteString bytes)
throws InvalidProtocolBufferException {
ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size());
bytes.copyTo(buffer);
buffer.flip();
try {
return Conformance.TestAllTypes.parseFrom(CodedInputStream.newInstance(buffer));
} catch (InvalidProtocolBufferException e) {
throw e;
} catch (IOException e) {
throw new RuntimeException(
"ByteString based ByteBuffer should not throw IOException.", e);
}
}
},
READONLY_DIRECT_BYTE_BUFFER_DECODER() {
@Override
public Conformance.TestAllTypes parse(ByteString bytes)
throws InvalidProtocolBufferException {
ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size());
bytes.copyTo(buffer);
buffer.flip();
try {
return Conformance.TestAllTypes.parseFrom(
CodedInputStream.newInstance(buffer.asReadOnlyBuffer()));
} catch (InvalidProtocolBufferException e) {
throw e;
} catch (IOException e) {
throw new RuntimeException(
"ByteString based ByteBuffer should not throw IOException.", e);
}
}
},
INPUT_STREAM_DECODER() {
@Override
public Conformance.TestAllTypes parse(ByteString bytes)
throws InvalidProtocolBufferException {
try {
return Conformance.TestAllTypes.parseFrom(bytes.newInput());
} catch (InvalidProtocolBufferException e) {
throw e;
} catch (IOException e) {
throw new RuntimeException(
"ByteString based InputStream should not throw IOException.", e);
}
}
};
public abstract Conformance.TestAllTypes parse(ByteString bytes)
throws InvalidProtocolBufferException;
}
private Conformance.TestAllTypes parseBinary(ByteString bytes)
throws InvalidProtocolBufferException {
Conformance.TestAllTypes[] messages =
new Conformance.TestAllTypes[BinaryDecoder.values().length];
InvalidProtocolBufferException[] exceptions =
new InvalidProtocolBufferException[BinaryDecoder.values().length];
boolean hasMessage = false;
boolean hasException = false;
for (int i = 0; i < BinaryDecoder.values().length; ++i) {
try {
messages[i] = BinaryDecoder.values()[i].parse(bytes);
hasMessage = true;
} catch (InvalidProtocolBufferException e) {
exceptions[i] = e;
hasException = true;
}
}
if (hasMessage && hasException) {
StringBuilder sb =
new StringBuilder("Binary decoders disagreed on whether the payload was valid.\n");
for (int i = 0; i < BinaryDecoder.values().length; ++i) {
sb.append(BinaryDecoder.values()[i].name());
if (messages[i] != null) {
sb.append(" accepted the payload.\n");
} else {
sb.append(" rejected the payload.\n");
}
}
throw new RuntimeException(sb.toString());
}
if (hasException) {
// We do not check if exceptions are equal. Different implementations may return different
// exception messages. Throw an arbitrary one out instead.
throw exceptions[0];
}
// Fast path comparing all the messages with the first message, assuming equality being
// symmetric and transitive.
boolean allEqual = true;
for (int i = 1; i < messages.length; ++i) {
if (!messages[0].equals(messages[i])) {
allEqual = false;
break;
}
}
// Slow path: compare and find out all unequal pairs.
if (!allEqual) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < messages.length - 1; ++i) {
for (int j = i + 1; j < messages.length; ++j) {
if (!messages[i].equals(messages[j])) {
sb.append(BinaryDecoder.values()[i].name())
.append(" and ")
.append(BinaryDecoder.values()[j].name())
.append(" parsed the payload differently.\n");
}
}
}
throw new RuntimeException(sb.toString());
}
return messages[0];
}
private Conformance.ConformanceResponse doTest(Conformance.ConformanceRequest request) {
Conformance.TestAllTypes testMessage;
switch (request.getPayloadCase()) {
case PROTOBUF_PAYLOAD: {
try {
testMessage = parseBinary(request.getProtobufPayload());
} catch (InvalidProtocolBufferException e) {
return Conformance.ConformanceResponse.newBuilder().setParseError(e.getMessage()).build();
}
break;
}
case JSON_PAYLOAD: {
try {
Conformance.TestAllTypes.Builder builder = Conformance.TestAllTypes.newBuilder();
JsonFormat.parser().usingTypeRegistry(typeRegistry)
.merge(request.getJsonPayload(), builder);
testMessage = builder.build();
} catch (InvalidProtocolBufferException e) {
return Conformance.ConformanceResponse.newBuilder().setParseError(e.getMessage()).build();
}
break;
}
case PAYLOAD_NOT_SET: {
throw new RuntimeException("Request didn't have payload.");
}
default: {
throw new RuntimeException("Unexpected payload case.");
}
}
switch (request.getRequestedOutputFormat()) {
case UNSPECIFIED:
throw new RuntimeException("Unspecified output format.");
case PROTOBUF:
return Conformance.ConformanceResponse.newBuilder().setProtobufPayload(testMessage.toByteString()).build();
case JSON:
try {
return Conformance.ConformanceResponse.newBuilder().setJsonPayload(
JsonFormat.printer().usingTypeRegistry(typeRegistry).print(testMessage)).build();
} catch (InvalidProtocolBufferException | IllegalArgumentException e) {
return Conformance.ConformanceResponse.newBuilder().setSerializeError(
e.getMessage()).build();
}
default: {
throw new RuntimeException("Unexpected request output.");
}
}
}
private boolean doTestIo() throws Exception {
int bytes = readLittleEndianIntFromStdin();
if (bytes == -1) {
return false; // EOF
}
byte[] serializedInput = new byte[bytes];
if (!readFromStdin(serializedInput, bytes)) {
throw new RuntimeException("Unexpected EOF from test program.");
}
Conformance.ConformanceRequest request =
Conformance.ConformanceRequest.parseFrom(serializedInput);
Conformance.ConformanceResponse response = doTest(request);
byte[] serializedOutput = response.toByteArray();
writeLittleEndianIntToStdout(serializedOutput.length);
writeToStdout(serializedOutput);
return true;
}
public void run() throws Exception {
typeRegistry = TypeRegistry.newBuilder().add(
Conformance.TestAllTypes.getDescriptor()).build();
while (doTestIo()) {
this.testCount++;
}
System.err.println("ConformanceJava: received EOF from test runner after " +
this.testCount + " tests");
}
public static void main(String[] args) throws Exception {
new ConformanceJava().run();
}
}