blob: 100bec4b54f09f0ce09274cdbd242ee1bab0848a [file] [log] [blame]
Rafi Kamal58d44202019-11-11 17:06:56 -08001// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc. All rights reserved.
3// https://developers.google.com/protocol-buffers/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9// * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11// * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15// * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
Yilun Chongfcb92682017-06-29 14:40:47 -070031import com.google.protobuf.AbstractMessage;
Yilun Chongcb95a7f2019-01-11 11:40:52 -080032import com.google.protobuf.ByteString;
Adam Cozzette5a76e632016-11-17 16:48:38 -080033import com.google.protobuf.CodedInputStream;
Yilun Chongfcb92682017-06-29 14:40:47 -070034import com.google.protobuf.ExtensionRegistry;
Yilun Chongcb95a7f2019-01-11 11:40:52 -080035import com.google.protobuf.InvalidProtocolBufferException;
36import com.google.protobuf.Parser;
37import com.google.protobuf.TextFormat;
38import com.google.protobuf.conformance.Conformance;
Adam Cozzette5a76e632016-11-17 16:48:38 -080039import com.google.protobuf.util.JsonFormat;
Joshua Habermanf1ce60e2016-12-03 11:51:25 -050040import com.google.protobuf.util.JsonFormat.TypeRegistry;
Yilun Chongcb95a7f2019-01-11 11:40:52 -080041import com.google.protobuf_test_messages.proto2.TestMessagesProto2;
42import com.google.protobuf_test_messages.proto2.TestMessagesProto2.TestAllTypesProto2;
43import com.google.protobuf_test_messages.proto3.TestMessagesProto3;
44import com.google.protobuf_test_messages.proto3.TestMessagesProto3.TestAllTypesProto3;
Adam Cozzette5a76e632016-11-17 16:48:38 -080045import java.nio.ByteBuffer;
Yilun Chongfcb92682017-06-29 14:40:47 -070046import java.util.ArrayList;
Josh Haberman420f9382015-04-16 12:50:39 -070047
48class ConformanceJava {
49 private int testCount = 0;
Feng Xiaoe841bac2015-12-11 17:09:20 -080050 private TypeRegistry typeRegistry;
Josh Haberman420f9382015-04-16 12:50:39 -070051
52 private boolean readFromStdin(byte[] buf, int len) throws Exception {
53 int ofs = 0;
54 while (len > 0) {
55 int read = System.in.read(buf, ofs, len);
56 if (read == -1) {
Joshua Habermance560632021-04-15 15:53:17 -070057 return false; // EOF
Josh Haberman420f9382015-04-16 12:50:39 -070058 }
59 ofs += read;
60 len -= read;
61 }
62
63 return true;
64 }
65
66 private void writeToStdout(byte[] buf) throws Exception {
67 System.out.write(buf);
68 }
69
70 // Returns -1 on EOF (the actual values will always be positive).
71 private int readLittleEndianIntFromStdin() throws Exception {
72 byte[] buf = new byte[4];
73 if (!readFromStdin(buf, 4)) {
74 return -1;
75 }
Feng Xiaoe841bac2015-12-11 17:09:20 -080076 return (buf[0] & 0xff)
77 | ((buf[1] & 0xff) << 8)
78 | ((buf[2] & 0xff) << 16)
79 | ((buf[3] & 0xff) << 24);
Josh Haberman420f9382015-04-16 12:50:39 -070080 }
81
82 private void writeLittleEndianIntToStdout(int val) throws Exception {
83 byte[] buf = new byte[4];
Joshua Habermance560632021-04-15 15:53:17 -070084 buf[0] = (byte) val;
85 buf[1] = (byte) (val >> 8);
86 buf[2] = (byte) (val >> 16);
87 buf[3] = (byte) (val >> 24);
Josh Haberman420f9382015-04-16 12:50:39 -070088 writeToStdout(buf);
89 }
Xiang Daie4794102019-02-21 11:28:50 +080090
Yilun Chongfcb92682017-06-29 14:40:47 -070091 private enum BinaryDecoderType {
92 BTYE_STRING_DECODER,
93 BYTE_ARRAY_DECODER,
94 ARRAY_BYTE_BUFFER_DECODER,
95 READONLY_ARRAY_BYTE_BUFFER_DECODER,
96 DIRECT_BYTE_BUFFER_DECODER,
97 READONLY_DIRECT_BYTE_BUFFER_DECODER,
98 INPUT_STREAM_DECODER;
99 }
100
Joshua Habermance560632021-04-15 15:53:17 -0700101 private static class BinaryDecoder<T extends AbstractMessage> {
102 public T decode(
103 ByteString bytes, BinaryDecoderType type, Parser<T> parser, ExtensionRegistry extensions)
104 throws InvalidProtocolBufferException {
Yilun Chongfcb92682017-06-29 14:40:47 -0700105 switch (type) {
Xiang Daie4794102019-02-21 11:28:50 +0800106 case BTYE_STRING_DECODER:
Yilun Chongfcb92682017-06-29 14:40:47 -0700107 case BYTE_ARRAY_DECODER:
Joshua Habermance560632021-04-15 15:53:17 -0700108 return parser.parseFrom(bytes, extensions);
109 case ARRAY_BYTE_BUFFER_DECODER:
110 {
111 ByteBuffer buffer = ByteBuffer.allocate(bytes.size());
112 bytes.copyTo(buffer);
113 buffer.flip();
Yilun Chongfcb92682017-06-29 14:40:47 -0700114 return parser.parseFrom(CodedInputStream.newInstance(buffer), extensions);
Yilun Chongfcb92682017-06-29 14:40:47 -0700115 }
Joshua Habermance560632021-04-15 15:53:17 -0700116 case READONLY_ARRAY_BYTE_BUFFER_DECODER:
117 {
Yilun Chongfcb92682017-06-29 14:40:47 -0700118 return parser.parseFrom(
119 CodedInputStream.newInstance(bytes.asReadOnlyByteBuffer()), extensions);
Yilun Chongfcb92682017-06-29 14:40:47 -0700120 }
Joshua Habermance560632021-04-15 15:53:17 -0700121 case DIRECT_BYTE_BUFFER_DECODER:
122 {
123 ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size());
124 bytes.copyTo(buffer);
125 buffer.flip();
Yilun Chongfcb92682017-06-29 14:40:47 -0700126 return parser.parseFrom(CodedInputStream.newInstance(buffer), extensions);
Yilun Chongfcb92682017-06-29 14:40:47 -0700127 }
Joshua Habermance560632021-04-15 15:53:17 -0700128 case READONLY_DIRECT_BYTE_BUFFER_DECODER:
129 {
130 ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size());
131 bytes.copyTo(buffer);
132 buffer.flip();
Yilun Chongfcb92682017-06-29 14:40:47 -0700133 return parser.parseFrom(
134 CodedInputStream.newInstance(buffer.asReadOnlyBuffer()), extensions);
Yilun Chongfcb92682017-06-29 14:40:47 -0700135 }
Joshua Habermance560632021-04-15 15:53:17 -0700136 case INPUT_STREAM_DECODER:
137 {
Yilun Chongfcb92682017-06-29 14:40:47 -0700138 return parser.parseFrom(bytes.newInput(), extensions);
Yilun Chongfcb92682017-06-29 14:40:47 -0700139 }
Yilun Chongfcb92682017-06-29 14:40:47 -0700140 }
Joshua Habermance560632021-04-15 15:53:17 -0700141 return null;
Yilun Chongfcb92682017-06-29 14:40:47 -0700142 }
143 }
144
Joshua Habermance560632021-04-15 15:53:17 -0700145 private <T extends AbstractMessage> T parseBinary(
146 ByteString bytes, Parser<T> parser, ExtensionRegistry extensions)
Yilun Chongfcb92682017-06-29 14:40:47 -0700147 throws InvalidProtocolBufferException {
Joshua Habermance560632021-04-15 15:53:17 -0700148 ArrayList<T> messages = new ArrayList<>();
149 ArrayList<InvalidProtocolBufferException> exceptions = new ArrayList<>();
Xiang Daie4794102019-02-21 11:28:50 +0800150
Yilun Chongfcb92682017-06-29 14:40:47 -0700151 for (int i = 0; i < BinaryDecoderType.values().length; i++) {
152 messages.add(null);
153 exceptions.add(null);
154 }
Joshua Habermance560632021-04-15 15:53:17 -0700155 if (messages.isEmpty()) {
156 throw new RuntimeException("binary decoder types missing");
157 }
158
159 BinaryDecoder<T> decoder = new BinaryDecoder<>();
Yilun Chong696cf772017-06-28 11:32:01 -0700160
161 boolean hasMessage = false;
162 boolean hasException = false;
Yilun Chongfcb92682017-06-29 14:40:47 -0700163 for (int i = 0; i < BinaryDecoderType.values().length; ++i) {
Yilun Chong696cf772017-06-28 11:32:01 -0700164 try {
Joshua Habermance560632021-04-15 15:53:17 -0700165 // = BinaryDecoderType.values()[i].parseProto3(bytes);
Yilun Chongfcb92682017-06-29 14:40:47 -0700166 messages.set(i, decoder.decode(bytes, BinaryDecoderType.values()[i], parser, extensions));
Adam Cozzette5a76e632016-11-17 16:48:38 -0800167 hasMessage = true;
168 } catch (InvalidProtocolBufferException e) {
Yilun Chongfcb92682017-06-29 14:40:47 -0700169 exceptions.set(i, e);
Adam Cozzette5a76e632016-11-17 16:48:38 -0800170 hasException = true;
171 }
172 }
173
174 if (hasMessage && hasException) {
175 StringBuilder sb =
176 new StringBuilder("Binary decoders disagreed on whether the payload was valid.\n");
Yilun Chongfcb92682017-06-29 14:40:47 -0700177 for (int i = 0; i < BinaryDecoderType.values().length; ++i) {
178 sb.append(BinaryDecoderType.values()[i].name());
179 if (messages.get(i) != null) {
Adam Cozzette5a76e632016-11-17 16:48:38 -0800180 sb.append(" accepted the payload.\n");
181 } else {
182 sb.append(" rejected the payload.\n");
183 }
184 }
185 throw new RuntimeException(sb.toString());
186 }
187
188 if (hasException) {
189 // We do not check if exceptions are equal. Different implementations may return different
190 // exception messages. Throw an arbitrary one out instead.
Joshua Habermance560632021-04-15 15:53:17 -0700191 InvalidProtocolBufferException exception = null;
192 for (InvalidProtocolBufferException e : exceptions) {
193 if (exception != null) {
194 exception.addSuppressed(e);
195 } else {
196 exception = e;
197 }
198 }
199 throw exception;
Adam Cozzette5a76e632016-11-17 16:48:38 -0800200 }
201
202 // Fast path comparing all the messages with the first message, assuming equality being
203 // symmetric and transitive.
204 boolean allEqual = true;
Yilun Chongfcb92682017-06-29 14:40:47 -0700205 for (int i = 1; i < messages.size(); ++i) {
206 if (!messages.get(0).equals(messages.get(i))) {
Adam Cozzette5a76e632016-11-17 16:48:38 -0800207 allEqual = false;
208 break;
209 }
210 }
211
212 // Slow path: compare and find out all unequal pairs.
213 if (!allEqual) {
214 StringBuilder sb = new StringBuilder();
Yilun Chongfcb92682017-06-29 14:40:47 -0700215 for (int i = 0; i < messages.size() - 1; ++i) {
216 for (int j = i + 1; j < messages.size(); ++j) {
217 if (!messages.get(i).equals(messages.get(j))) {
218 sb.append(BinaryDecoderType.values()[i].name())
Adam Cozzette5a76e632016-11-17 16:48:38 -0800219 .append(" and ")
Yilun Chongfcb92682017-06-29 14:40:47 -0700220 .append(BinaryDecoderType.values()[j].name())
Adam Cozzette5a76e632016-11-17 16:48:38 -0800221 .append(" parsed the payload differently.\n");
222 }
223 }
224 }
225 throw new RuntimeException(sb.toString());
226 }
227
Yilun Chongfcb92682017-06-29 14:40:47 -0700228 return messages.get(0);
Adam Cozzette5a76e632016-11-17 16:48:38 -0800229 }
230
Josh Haberman420f9382015-04-16 12:50:39 -0700231 private Conformance.ConformanceResponse doTest(Conformance.ConformanceRequest request) {
Yilun Chong696cf772017-06-28 11:32:01 -0700232 com.google.protobuf.AbstractMessage testMessage;
Joshua Haberman6ed73832020-05-13 13:46:15 -0700233 boolean isProto3 =
234 request.getMessageType().equals("protobuf_test_messages.proto3.TestAllTypesProto3");
235 boolean isProto2 =
236 request.getMessageType().equals("protobuf_test_messages.proto2.TestAllTypesProto2");
Josh Haberman420f9382015-04-16 12:50:39 -0700237
238 switch (request.getPayloadCase()) {
Joshua Habermance560632021-04-15 15:53:17 -0700239 case PROTOBUF_PAYLOAD:
240 {
Joshua Haberman6ed73832020-05-13 13:46:15 -0700241 if (isProto3) {
Joshua Habermance560632021-04-15 15:53:17 -0700242 try {
243 ExtensionRegistry extensions = ExtensionRegistry.newInstance();
244 TestMessagesProto3.registerAllExtensions(extensions);
245 testMessage =
246 parseBinary(
247 request.getProtobufPayload(), TestAllTypesProto3.parser(), extensions);
248 } catch (InvalidProtocolBufferException e) {
249 return Conformance.ConformanceResponse.newBuilder()
250 .setParseError(e.getMessage())
251 .build();
252 }
Joshua Haberman6ed73832020-05-13 13:46:15 -0700253 } else if (isProto2) {
Joshua Habermance560632021-04-15 15:53:17 -0700254 try {
255 ExtensionRegistry extensions = ExtensionRegistry.newInstance();
256 TestMessagesProto2.registerAllExtensions(extensions);
257 testMessage =
258 parseBinary(
259 request.getProtobufPayload(), TestAllTypesProto2.parser(), extensions);
260 } catch (InvalidProtocolBufferException e) {
261 return Conformance.ConformanceResponse.newBuilder()
262 .setParseError(e.getMessage())
263 .build();
264 }
Joshua Haberman6ed73832020-05-13 13:46:15 -0700265 } else {
266 throw new RuntimeException("Protobuf request doesn't have specific payload type.");
267 }
Joshua Habermance560632021-04-15 15:53:17 -0700268 break;
Feng Xiaoe841bac2015-12-11 17:09:20 -0800269 }
Joshua Habermance560632021-04-15 15:53:17 -0700270 case JSON_PAYLOAD:
271 {
Yilun Chongd8c25012019-02-22 18:13:33 +0800272 try {
Joshua Habermance560632021-04-15 15:53:17 -0700273 JsonFormat.Parser parser = JsonFormat.parser().usingTypeRegistry(typeRegistry);
274 if (request.getTestCategory()
275 == Conformance.TestCategory.JSON_IGNORE_UNKNOWN_PARSING_TEST) {
276 parser = parser.ignoringUnknownFields();
277 }
278 if (isProto3) {
279 TestMessagesProto3.TestAllTypesProto3.Builder builder =
280 TestMessagesProto3.TestAllTypesProto3.newBuilder();
281 parser.merge(request.getJsonPayload(), builder);
282 testMessage = builder.build();
283 } else if (isProto2) {
284 TestMessagesProto2.TestAllTypesProto2.Builder builder =
285 TestMessagesProto2.TestAllTypesProto2.newBuilder();
286 parser.merge(request.getJsonPayload(), builder);
287 testMessage = builder.build();
288 } else {
289 throw new RuntimeException("Protobuf request doesn't have specific payload type.");
290 }
291 } catch (InvalidProtocolBufferException e) {
292 return Conformance.ConformanceResponse.newBuilder()
293 .setParseError(e.getMessage())
294 .build();
295 }
296 break;
297 }
298 case TEXT_PAYLOAD:
299 {
300 if (isProto3) {
301 try {
302 TestMessagesProto3.TestAllTypesProto3.Builder builder =
303 TestMessagesProto3.TestAllTypesProto3.newBuilder();
304 TextFormat.merge(request.getTextPayload(), builder);
305 testMessage = builder.build();
306 } catch (TextFormat.ParseException e) {
Yilun Chongd8c25012019-02-22 18:13:33 +0800307 return Conformance.ConformanceResponse.newBuilder()
308 .setParseError(e.getMessage())
309 .build();
Joshua Habermance560632021-04-15 15:53:17 -0700310 }
311 } else if (isProto2) {
312 try {
313 TestMessagesProto2.TestAllTypesProto2.Builder builder =
314 TestMessagesProto2.TestAllTypesProto2.newBuilder();
315 TextFormat.merge(request.getTextPayload(), builder);
316 testMessage = builder.build();
317 } catch (TextFormat.ParseException e) {
Yilun Chongd8c25012019-02-22 18:13:33 +0800318 return Conformance.ConformanceResponse.newBuilder()
319 .setParseError(e.getMessage())
320 .build();
Joshua Habermance560632021-04-15 15:53:17 -0700321 }
322 } else {
323 throw new RuntimeException("Protobuf request doesn't have specific payload type.");
Yilun Chongd8c25012019-02-22 18:13:33 +0800324 }
Joshua Habermance560632021-04-15 15:53:17 -0700325 break;
Yilun Chongcb95a7f2019-01-11 11:40:52 -0800326 }
Joshua Habermance560632021-04-15 15:53:17 -0700327 case PAYLOAD_NOT_SET:
328 {
329 throw new RuntimeException("Request didn't have payload.");
330 }
Josh Haberman420f9382015-04-16 12:50:39 -0700331
Joshua Habermance560632021-04-15 15:53:17 -0700332 default:
333 {
334 throw new RuntimeException("Unexpected payload case.");
335 }
Josh Haberman420f9382015-04-16 12:50:39 -0700336 }
337
Josh Habermanb0500b32015-07-10 16:36:59 -0700338 switch (request.getRequestedOutputFormat()) {
Josh Haberman420f9382015-04-16 12:50:39 -0700339 case UNSPECIFIED:
340 throw new RuntimeException("Unspecified output format.");
341
Joshua Habermance560632021-04-15 15:53:17 -0700342 case PROTOBUF:
343 {
344 ByteString messageString = testMessage.toByteString();
345 return Conformance.ConformanceResponse.newBuilder()
346 .setProtobufPayload(messageString)
347 .build();
348 }
Josh Haberman420f9382015-04-16 12:50:39 -0700349
350 case JSON:
Feng Xiaoe841bac2015-12-11 17:09:20 -0800351 try {
Joshua Habermance560632021-04-15 15:53:17 -0700352 return Conformance.ConformanceResponse.newBuilder()
353 .setJsonPayload(
354 JsonFormat.printer().usingTypeRegistry(typeRegistry).print(testMessage))
355 .build();
Feng Xiaoe841bac2015-12-11 17:09:20 -0800356 } catch (InvalidProtocolBufferException | IllegalArgumentException e) {
Joshua Habermance560632021-04-15 15:53:17 -0700357 return Conformance.ConformanceResponse.newBuilder()
358 .setSerializeError(e.getMessage())
359 .build();
Feng Xiaoe841bac2015-12-11 17:09:20 -0800360 }
Josh Haberman420f9382015-04-16 12:50:39 -0700361
Yilun Chongcb95a7f2019-01-11 11:40:52 -0800362 case TEXT_FORMAT:
Joshua Habermance560632021-04-15 15:53:17 -0700363 return Conformance.ConformanceResponse.newBuilder()
364 .setTextPayload(TextFormat.printToString(testMessage))
365 .build();
Yilun Chongcb95a7f2019-01-11 11:40:52 -0800366
Joshua Habermance560632021-04-15 15:53:17 -0700367 default:
368 {
369 throw new RuntimeException("Unexpected request output.");
370 }
Josh Haberman420f9382015-04-16 12:50:39 -0700371 }
372 }
373
374 private boolean doTestIo() throws Exception {
375 int bytes = readLittleEndianIntFromStdin();
376
377 if (bytes == -1) {
Joshua Habermance560632021-04-15 15:53:17 -0700378 return false; // EOF
Josh Haberman420f9382015-04-16 12:50:39 -0700379 }
380
381 byte[] serializedInput = new byte[bytes];
382
383 if (!readFromStdin(serializedInput, bytes)) {
384 throw new RuntimeException("Unexpected EOF from test program.");
385 }
386
387 Conformance.ConformanceRequest request =
388 Conformance.ConformanceRequest.parseFrom(serializedInput);
389 Conformance.ConformanceResponse response = doTest(request);
390 byte[] serializedOutput = response.toByteArray();
391
392 writeLittleEndianIntToStdout(serializedOutput.length);
393 writeToStdout(serializedOutput);
394
395 return true;
396 }
397
398 public void run() throws Exception {
Joshua Habermance560632021-04-15 15:53:17 -0700399 typeRegistry =
400 TypeRegistry.newBuilder()
401 .add(TestMessagesProto3.TestAllTypesProto3.getDescriptor())
402 .build();
Josh Haberman420f9382015-04-16 12:50:39 -0700403 while (doTestIo()) {
Jisi Liu3b3c8ab2016-03-30 11:39:59 -0700404 this.testCount++;
Josh Haberman420f9382015-04-16 12:50:39 -0700405 }
406
Joshua Habermance560632021-04-15 15:53:17 -0700407 System.err.println(
408 "ConformanceJava: received EOF from test runner after " + this.testCount + " tests");
Josh Haberman420f9382015-04-16 12:50:39 -0700409 }
410
411 public static void main(String[] args) throws Exception {
412 new ConformanceJava().run();
413 }
414}