blob: 596d113a9240bb38f445398c76cd7939f8c7d3b8 [file] [log] [blame]
Austin Schuh40c16522018-10-28 20:27:54 -07001import com.google.protobuf.ByteString;
2import com.google.protobuf.AbstractMessage;
3import com.google.protobuf.Parser;
4import com.google.protobuf.CodedInputStream;
Brian Silverman9c614bc2016-02-15 20:20:02 -05005import com.google.protobuf.conformance.Conformance;
Austin Schuh40c16522018-10-28 20:27:54 -07006import com.google.protobuf.InvalidProtocolBufferException;
7import com.google.protobuf_test_messages.proto3.TestMessagesProto3;
8import com.google.protobuf_test_messages.proto3.TestMessagesProto3.TestAllTypesProto3;
9import com.google.protobuf_test_messages.proto2.TestMessagesProto2;
10import com.google.protobuf_test_messages.proto2.TestMessagesProto2.TestAllTypesProto2;
11import com.google.protobuf.ExtensionRegistry;
Brian Silverman9c614bc2016-02-15 20:20:02 -050012import com.google.protobuf.util.JsonFormat;
13import com.google.protobuf.util.JsonFormat.TypeRegistry;
Austin Schuh40c16522018-10-28 20:27:54 -070014import java.nio.ByteBuffer;
15import java.util.ArrayList;
Brian Silverman9c614bc2016-02-15 20:20:02 -050016
17class ConformanceJava {
18 private int testCount = 0;
19 private TypeRegistry typeRegistry;
20
21 private boolean readFromStdin(byte[] buf, int len) throws Exception {
22 int ofs = 0;
23 while (len > 0) {
24 int read = System.in.read(buf, ofs, len);
25 if (read == -1) {
26 return false; // EOF
27 }
28 ofs += read;
29 len -= read;
30 }
31
32 return true;
33 }
34
35 private void writeToStdout(byte[] buf) throws Exception {
36 System.out.write(buf);
37 }
38
39 // Returns -1 on EOF (the actual values will always be positive).
40 private int readLittleEndianIntFromStdin() throws Exception {
41 byte[] buf = new byte[4];
42 if (!readFromStdin(buf, 4)) {
43 return -1;
44 }
45 return (buf[0] & 0xff)
46 | ((buf[1] & 0xff) << 8)
47 | ((buf[2] & 0xff) << 16)
48 | ((buf[3] & 0xff) << 24);
49 }
50
51 private void writeLittleEndianIntToStdout(int val) throws Exception {
52 byte[] buf = new byte[4];
53 buf[0] = (byte)val;
54 buf[1] = (byte)(val >> 8);
55 buf[2] = (byte)(val >> 16);
56 buf[3] = (byte)(val >> 24);
57 writeToStdout(buf);
58 }
Austin Schuh40c16522018-10-28 20:27:54 -070059
60 private enum BinaryDecoderType {
61 BTYE_STRING_DECODER,
62 BYTE_ARRAY_DECODER,
63 ARRAY_BYTE_BUFFER_DECODER,
64 READONLY_ARRAY_BYTE_BUFFER_DECODER,
65 DIRECT_BYTE_BUFFER_DECODER,
66 READONLY_DIRECT_BYTE_BUFFER_DECODER,
67 INPUT_STREAM_DECODER;
68 }
69
70 private static class BinaryDecoder <MessageType extends AbstractMessage> {
71 public MessageType decode (ByteString bytes, BinaryDecoderType type,
72 Parser <MessageType> parser, ExtensionRegistry extensions)
73 throws InvalidProtocolBufferException {
74 switch (type) {
75 case BTYE_STRING_DECODER:
76 return parser.parseFrom(bytes, extensions);
77 case BYTE_ARRAY_DECODER:
78 return parser.parseFrom(bytes.toByteArray(), extensions);
79 case ARRAY_BYTE_BUFFER_DECODER: {
80 ByteBuffer buffer = ByteBuffer.allocate(bytes.size());
81 bytes.copyTo(buffer);
82 buffer.flip();
83 try {
84 return parser.parseFrom(CodedInputStream.newInstance(buffer), extensions);
85 } catch (InvalidProtocolBufferException e) {
86 throw e;
87 }
88 }
89 case READONLY_ARRAY_BYTE_BUFFER_DECODER: {
90 try {
91 return parser.parseFrom(
92 CodedInputStream.newInstance(bytes.asReadOnlyByteBuffer()), extensions);
93 } catch (InvalidProtocolBufferException e) {
94 throw e;
95 }
96 }
97 case DIRECT_BYTE_BUFFER_DECODER: {
98 ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size());
99 bytes.copyTo(buffer);
100 buffer.flip();
101 try {
102 return parser.parseFrom(CodedInputStream.newInstance(buffer), extensions);
103 } catch (InvalidProtocolBufferException e) {
104 throw e;
105 }
106 }
107 case READONLY_DIRECT_BYTE_BUFFER_DECODER: {
108 ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size());
109 bytes.copyTo(buffer);
110 buffer.flip();
111 try {
112 return parser.parseFrom(
113 CodedInputStream.newInstance(buffer.asReadOnlyBuffer()), extensions);
114 } catch (InvalidProtocolBufferException e) {
115 throw e;
116 }
117 }
118 case INPUT_STREAM_DECODER: {
119 try {
120 return parser.parseFrom(bytes.newInput(), extensions);
121 } catch (InvalidProtocolBufferException e) {
122 throw e;
123 }
124 }
125 default :
126 return null;
127 }
128 }
129 }
130
131 private <MessageType extends AbstractMessage> MessageType parseBinary(
132 ByteString bytes, Parser <MessageType> parser, ExtensionRegistry extensions)
133 throws InvalidProtocolBufferException {
134 ArrayList <MessageType> messages = new ArrayList <MessageType> ();
135 ArrayList <InvalidProtocolBufferException> exceptions =
136 new ArrayList <InvalidProtocolBufferException>();
137
138 for (int i = 0; i < BinaryDecoderType.values().length; i++) {
139 messages.add(null);
140 exceptions.add(null);
141 }
142 BinaryDecoder <MessageType> decoder = new BinaryDecoder <MessageType> ();
143
144 boolean hasMessage = false;
145 boolean hasException = false;
146 for (int i = 0; i < BinaryDecoderType.values().length; ++i) {
147 try {
148 //= BinaryDecoderType.values()[i].parseProto3(bytes);
149 messages.set(i, decoder.decode(bytes, BinaryDecoderType.values()[i], parser, extensions));
150 hasMessage = true;
151 } catch (InvalidProtocolBufferException e) {
152 exceptions.set(i, e);
153 hasException = true;
154 }
155 }
156
157 if (hasMessage && hasException) {
158 StringBuilder sb =
159 new StringBuilder("Binary decoders disagreed on whether the payload was valid.\n");
160 for (int i = 0; i < BinaryDecoderType.values().length; ++i) {
161 sb.append(BinaryDecoderType.values()[i].name());
162 if (messages.get(i) != null) {
163 sb.append(" accepted the payload.\n");
164 } else {
165 sb.append(" rejected the payload.\n");
166 }
167 }
168 throw new RuntimeException(sb.toString());
169 }
170
171 if (hasException) {
172 // We do not check if exceptions are equal. Different implementations may return different
173 // exception messages. Throw an arbitrary one out instead.
174 throw exceptions.get(0);
175 }
176
177 // Fast path comparing all the messages with the first message, assuming equality being
178 // symmetric and transitive.
179 boolean allEqual = true;
180 for (int i = 1; i < messages.size(); ++i) {
181 if (!messages.get(0).equals(messages.get(i))) {
182 allEqual = false;
183 break;
184 }
185 }
186
187 // Slow path: compare and find out all unequal pairs.
188 if (!allEqual) {
189 StringBuilder sb = new StringBuilder();
190 for (int i = 0; i < messages.size() - 1; ++i) {
191 for (int j = i + 1; j < messages.size(); ++j) {
192 if (!messages.get(i).equals(messages.get(j))) {
193 sb.append(BinaryDecoderType.values()[i].name())
194 .append(" and ")
195 .append(BinaryDecoderType.values()[j].name())
196 .append(" parsed the payload differently.\n");
197 }
198 }
199 }
200 throw new RuntimeException(sb.toString());
201 }
202
203 return messages.get(0);
204 }
Brian Silverman9c614bc2016-02-15 20:20:02 -0500205
206 private Conformance.ConformanceResponse doTest(Conformance.ConformanceRequest request) {
Austin Schuh40c16522018-10-28 20:27:54 -0700207 com.google.protobuf.AbstractMessage testMessage;
208 boolean isProto3 = request.getMessageType().equals("protobuf_test_messages.proto3.TestAllTypesProto3");
209 boolean isProto2 = request.getMessageType().equals("protobuf_test_messages.proto2.TestAllTypesProto2");
Brian Silverman9c614bc2016-02-15 20:20:02 -0500210
211 switch (request.getPayloadCase()) {
212 case PROTOBUF_PAYLOAD: {
Austin Schuh40c16522018-10-28 20:27:54 -0700213 if (isProto3) {
214 try {
215 ExtensionRegistry extensions = ExtensionRegistry.newInstance();
216 TestMessagesProto3.registerAllExtensions(extensions);
217 testMessage = parseBinary(request.getProtobufPayload(), TestAllTypesProto3.parser(), extensions);
218 } catch (InvalidProtocolBufferException e) {
219 return Conformance.ConformanceResponse.newBuilder().setParseError(e.getMessage()).build();
220 }
221 } else if (isProto2) {
222 try {
223 ExtensionRegistry extensions = ExtensionRegistry.newInstance();
224 TestMessagesProto2.registerAllExtensions(extensions);
225 testMessage = parseBinary(request.getProtobufPayload(), TestAllTypesProto2.parser(), extensions);
226 } catch (InvalidProtocolBufferException e) {
227 return Conformance.ConformanceResponse.newBuilder().setParseError(e.getMessage()).build();
228 }
229 } else {
230 throw new RuntimeException("Protobuf request doesn't have specific payload type.");
Brian Silverman9c614bc2016-02-15 20:20:02 -0500231 }
232 break;
233 }
234 case JSON_PAYLOAD: {
235 try {
Austin Schuh40c16522018-10-28 20:27:54 -0700236 TestMessagesProto3.TestAllTypesProto3.Builder builder =
237 TestMessagesProto3.TestAllTypesProto3.newBuilder();
Brian Silverman9c614bc2016-02-15 20:20:02 -0500238 JsonFormat.parser().usingTypeRegistry(typeRegistry)
239 .merge(request.getJsonPayload(), builder);
240 testMessage = builder.build();
241 } catch (InvalidProtocolBufferException e) {
242 return Conformance.ConformanceResponse.newBuilder().setParseError(e.getMessage()).build();
243 }
244 break;
245 }
246 case PAYLOAD_NOT_SET: {
247 throw new RuntimeException("Request didn't have payload.");
248 }
249
250 default: {
251 throw new RuntimeException("Unexpected payload case.");
252 }
253 }
254
255 switch (request.getRequestedOutputFormat()) {
256 case UNSPECIFIED:
257 throw new RuntimeException("Unspecified output format.");
258
Austin Schuh40c16522018-10-28 20:27:54 -0700259 case PROTOBUF: {
260 ByteString MessageString = testMessage.toByteString();
261 return Conformance.ConformanceResponse.newBuilder().setProtobufPayload(MessageString).build();
262 }
Brian Silverman9c614bc2016-02-15 20:20:02 -0500263
264 case JSON:
265 try {
266 return Conformance.ConformanceResponse.newBuilder().setJsonPayload(
267 JsonFormat.printer().usingTypeRegistry(typeRegistry).print(testMessage)).build();
268 } catch (InvalidProtocolBufferException | IllegalArgumentException e) {
269 return Conformance.ConformanceResponse.newBuilder().setSerializeError(
270 e.getMessage()).build();
271 }
272
273 default: {
274 throw new RuntimeException("Unexpected request output.");
275 }
276 }
277 }
278
279 private boolean doTestIo() throws Exception {
280 int bytes = readLittleEndianIntFromStdin();
281
282 if (bytes == -1) {
283 return false; // EOF
284 }
285
286 byte[] serializedInput = new byte[bytes];
287
288 if (!readFromStdin(serializedInput, bytes)) {
289 throw new RuntimeException("Unexpected EOF from test program.");
290 }
291
292 Conformance.ConformanceRequest request =
293 Conformance.ConformanceRequest.parseFrom(serializedInput);
294 Conformance.ConformanceResponse response = doTest(request);
295 byte[] serializedOutput = response.toByteArray();
296
297 writeLittleEndianIntToStdout(serializedOutput.length);
298 writeToStdout(serializedOutput);
299
300 return true;
301 }
302
303 public void run() throws Exception {
304 typeRegistry = TypeRegistry.newBuilder().add(
Austin Schuh40c16522018-10-28 20:27:54 -0700305 TestMessagesProto3.TestAllTypesProto3.getDescriptor()).build();
Brian Silverman9c614bc2016-02-15 20:20:02 -0500306 while (doTestIo()) {
Austin Schuh40c16522018-10-28 20:27:54 -0700307 this.testCount++;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500308 }
309
310 System.err.println("ConformanceJava: received EOF from test runner after " +
311 this.testCount + " tests");
312 }
313
314 public static void main(String[] args) throws Exception {
315 new ConformanceJava().run();
316 }
317}