ConformanceJava.java 11 KB
Newer Older
1
import com.google.protobuf.ByteString;
2 3
import com.google.protobuf.AbstractMessage;
import com.google.protobuf.Parser;
4
import com.google.protobuf.CodedInputStream;
5
import com.google.protobuf.conformance.Conformance;
6 7
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf_test_messages.proto3.TestMessagesProto3;
8
import com.google.protobuf_test_messages.proto3.TestMessagesProto3.TestAllTypesProto3;
Yilun Chong's avatar
Yilun Chong committed
9
import com.google.protobuf_test_messages.proto2.TestMessagesProto2;
10 11
import com.google.protobuf_test_messages.proto2.TestMessagesProto2.TestAllTypesProto2;
import com.google.protobuf.ExtensionRegistry;
12
import com.google.protobuf.util.JsonFormat;
13
import com.google.protobuf.util.JsonFormat.TypeRegistry;
14
import java.nio.ByteBuffer;
15
import java.util.ArrayList;
16 17 18

class ConformanceJava {
  private int testCount = 0;
19
  private TypeRegistry typeRegistry;
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

  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;
    }
45 46 47 48
    return (buf[0] & 0xff)
        | ((buf[1] & 0xff) << 8)
        | ((buf[2] & 0xff) << 16)
        | ((buf[3] & 0xff) << 24);
49 50 51 52 53 54 55 56 57 58
  }

  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);
  }
59 60 61 62 63 64 65 66 67
  
  private enum BinaryDecoderType {
    BTYE_STRING_DECODER,
    BYTE_ARRAY_DECODER,
    ARRAY_BYTE_BUFFER_DECODER,
    READONLY_ARRAY_BYTE_BUFFER_DECODER,
    DIRECT_BYTE_BUFFER_DECODER,
    READONLY_DIRECT_BYTE_BUFFER_DECODER,
    INPUT_STREAM_DECODER;
68 69
  }

70 71 72
  private static class BinaryDecoder <MessageType extends AbstractMessage> {
    public MessageType decode (ByteString bytes, BinaryDecoderType type, 
        Parser <MessageType> parser, ExtensionRegistry extensions)
73
      throws InvalidProtocolBufferException {
74 75 76 77 78 79 80 81 82 83 84 85 86 87
      switch (type) {
        case BTYE_STRING_DECODER: 
          return parser.parseFrom(bytes, extensions);
        case BYTE_ARRAY_DECODER:
          return parser.parseFrom(bytes.toByteArray(), extensions);
        case ARRAY_BYTE_BUFFER_DECODER: {
          ByteBuffer buffer = ByteBuffer.allocate(bytes.size());
          bytes.copyTo(buffer);
          buffer.flip();
          try {
            return parser.parseFrom(CodedInputStream.newInstance(buffer), extensions);
          } catch (InvalidProtocolBufferException e) {
            throw e;
          }
88
        }
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
        case READONLY_ARRAY_BYTE_BUFFER_DECODER: {
          try {
            return parser.parseFrom(
                CodedInputStream.newInstance(bytes.asReadOnlyByteBuffer()), extensions);
          } catch (InvalidProtocolBufferException e) {
            throw e;
          }
        } 
        case DIRECT_BYTE_BUFFER_DECODER: {
          ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size());
          bytes.copyTo(buffer);
          buffer.flip();
          try {
            return parser.parseFrom(CodedInputStream.newInstance(buffer), extensions);
          } catch (InvalidProtocolBufferException e) {
            throw e;
          }
        }
        case READONLY_DIRECT_BYTE_BUFFER_DECODER: {
          ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.size());
          bytes.copyTo(buffer);
          buffer.flip();
          try {
            return parser.parseFrom(
                CodedInputStream.newInstance(buffer.asReadOnlyBuffer()), extensions);
          } catch (InvalidProtocolBufferException e) {
            throw e;
          }
        }
        case INPUT_STREAM_DECODER: {
          try {
            return parser.parseFrom(bytes.newInput(), extensions);
          } catch (InvalidProtocolBufferException e) {
            throw e;
123 124
          }
        }
125 126
        default :
          return null;
127 128 129
      }
    }
  }
130 131 132

  private <MessageType extends AbstractMessage> MessageType parseBinary(
      ByteString bytes, Parser <MessageType> parser, ExtensionRegistry extensions)
Yilun Chong's avatar
Yilun Chong committed
133
      throws InvalidProtocolBufferException {
134 135 136 137 138 139 140 141 142
    ArrayList <MessageType> messages = new ArrayList <MessageType> ();
    ArrayList <InvalidProtocolBufferException> exceptions =
        new ArrayList <InvalidProtocolBufferException>();
    
    for (int i = 0; i < BinaryDecoderType.values().length; i++) {
      messages.add(null);
      exceptions.add(null);
    }
    BinaryDecoder <MessageType> decoder = new BinaryDecoder <MessageType> ();
Yilun Chong's avatar
Yilun Chong committed
143 144 145

    boolean hasMessage = false;
    boolean hasException = false;
146
    for (int i = 0; i < BinaryDecoderType.values().length; ++i) {
Yilun Chong's avatar
Yilun Chong committed
147
      try {
148 149
        //= BinaryDecoderType.values()[i].parseProto3(bytes);
        messages.set(i, decoder.decode(bytes, BinaryDecoderType.values()[i], parser, extensions));
Yilun Chong's avatar
Yilun Chong committed
150 151
        hasMessage = true;
      } catch (InvalidProtocolBufferException e) {
152
        exceptions.set(i, e);
Yilun Chong's avatar
Yilun Chong committed
153 154 155 156 157 158 159
        hasException = true;
      }
    }

    if (hasMessage && hasException) {
      StringBuilder sb =
          new StringBuilder("Binary decoders disagreed on whether the payload was valid.\n");
160 161 162
      for (int i = 0; i < BinaryDecoderType.values().length; ++i) {
        sb.append(BinaryDecoderType.values()[i].name());
        if (messages.get(i) != null) {
Yilun Chong's avatar
Yilun Chong committed
163 164 165 166 167 168 169 170 171 172 173
          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.
174
      throw exceptions.get(0);
Yilun Chong's avatar
Yilun Chong committed
175 176 177 178 179
    }

    // Fast path comparing all the messages with the first message, assuming equality being
    // symmetric and transitive.
    boolean allEqual = true;
180 181
    for (int i = 1; i < messages.size(); ++i) {
      if (!messages.get(0).equals(messages.get(i))) {
Yilun Chong's avatar
Yilun Chong committed
182 183 184 185 186 187 188 189
        allEqual = false;
        break;
      }
    }

    // Slow path: compare and find out all unequal pairs.
    if (!allEqual) {
      StringBuilder sb = new StringBuilder();
190 191 192 193
      for (int i = 0; i < messages.size() - 1; ++i) {
        for (int j = i + 1; j < messages.size(); ++j) {
          if (!messages.get(i).equals(messages.get(j))) {
            sb.append(BinaryDecoderType.values()[i].name())
Yilun Chong's avatar
Yilun Chong committed
194
                .append(" and ")
195
                .append(BinaryDecoderType.values()[j].name())
Yilun Chong's avatar
Yilun Chong committed
196 197 198 199 200 201 202
                .append(" parsed the payload differently.\n");
          }
        }
      }
      throw new RuntimeException(sb.toString());
    }

203
    return messages.get(0);
Yilun Chong's avatar
Yilun Chong committed
204 205
  }

206
  private Conformance.ConformanceResponse doTest(Conformance.ConformanceRequest request) {
Yilun Chong's avatar
Yilun Chong committed
207
    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");
210 211 212

    switch (request.getPayloadCase()) {
      case PROTOBUF_PAYLOAD: {
Yilun Chong's avatar
Yilun Chong committed
213 214
        if (isProto3) {
          try {
215 216
            ExtensionRegistry extensions = ExtensionRegistry.newInstance();
            TestMessagesProto3.registerAllExtensions(extensions);
217
            testMessage = parseBinary(request.getProtobufPayload(), TestAllTypesProto3.parser(), extensions);
Yilun Chong's avatar
Yilun Chong committed
218 219 220
          } catch (InvalidProtocolBufferException e) {
            return Conformance.ConformanceResponse.newBuilder().setParseError(e.getMessage()).build();
          }
221
        } else if (isProto2) {
Yilun Chong's avatar
Yilun Chong committed
222
          try {
223 224 225
            ExtensionRegistry extensions = ExtensionRegistry.newInstance();
            TestMessagesProto2.registerAllExtensions(extensions);
            testMessage = parseBinary(request.getProtobufPayload(), TestAllTypesProto2.parser(), extensions);
Yilun Chong's avatar
Yilun Chong committed
226 227 228 229 230
          } catch (InvalidProtocolBufferException e) {
            return Conformance.ConformanceResponse.newBuilder().setParseError(e.getMessage()).build();
          }
        } else {
          throw new RuntimeException("Protobuf request doesn't have specific payload type.");
231 232 233 234
        }
        break;
      }
      case JSON_PAYLOAD: {
235
        try {
236 237
          TestMessagesProto3.TestAllTypesProto3.Builder builder = 
              TestMessagesProto3.TestAllTypesProto3.newBuilder();
238 239 240 241 242 243 244
          JsonFormat.parser().usingTypeRegistry(typeRegistry)
              .merge(request.getJsonPayload(), builder);
          testMessage = builder.build();
        } catch (InvalidProtocolBufferException e) {
          return Conformance.ConformanceResponse.newBuilder().setParseError(e.getMessage()).build();
        }
        break;
245 246 247 248 249 250 251 252 253 254
      }
      case PAYLOAD_NOT_SET: {
        throw new RuntimeException("Request didn't have payload.");
      }

      default: {
        throw new RuntimeException("Unexpected payload case.");
      }
    }

255
    switch (request.getRequestedOutputFormat()) {
256 257 258
      case UNSPECIFIED:
        throw new RuntimeException("Unspecified output format.");

Yilun Chong's avatar
Yilun Chong committed
259 260 261 262
      case PROTOBUF: {
        ByteString MessageString = testMessage.toByteString(); 
        return Conformance.ConformanceResponse.newBuilder().setProtobufPayload(MessageString).build();
      }
263 264

      case JSON:
265 266 267 268 269 270 271
        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();
        }
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303

      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 {
304
    typeRegistry = TypeRegistry.newBuilder().add(
305
        TestMessagesProto3.TestAllTypesProto3.getDescriptor()).build();
306
    while (doTestIo()) {
307
      this.testCount++;
308 309 310 311 312 313 314 315 316 317
    }

    System.err.println("ConformanceJava: received EOF from test runner after " +
        this.testCount + " tests");
  }

  public static void main(String[] args) throws Exception {
    new ConformanceJava().run();
  }
}