| // Protocol Buffers - Google's data interchange format | |
| // Copyright 2023 Google LLC. All rights reserved. | |
| // | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file or at | |
| // https://developers.google.com/open-source/licenses/bsd | |
| /* This is a upb implementation of the upb conformance tests, see: | |
| * https://github.com/google/protobuf/tree/master/conformance | |
| */ | |
| #include <errno.h> | |
| #include <stdarg.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include "conformance/conformance.upb.h" | |
| #include "conformance/conformance.upbdefs.h" | |
| #include "editions/golden/test_messages_proto2_editions.upbdefs.h" | |
| #include "editions/golden/test_messages_proto3_editions.upbdefs.h" | |
| #include "google/protobuf/test_messages_proto2.upbdefs.h" | |
| #include "google/protobuf/test_messages_proto3.upbdefs.h" | |
| #include "upb/base/upcast.h" | |
| #include "upb/json/decode.h" | |
| #include "upb/json/encode.h" | |
| #include "upb/reflection/message.h" | |
| #include "upb/text/encode.h" | |
| #include "upb/wire/decode.h" | |
| #include "upb/wire/encode.h" | |
| // Must be last. | |
| #include "upb/port/def.inc" | |
| int test_count = 0; | |
| bool verbose = false; /* Set to true to get req/resp printed on stderr. */ | |
| bool CheckedRead(int fd, void* buf, size_t len) { | |
| size_t ofs = 0; | |
| while (len > 0) { | |
| ssize_t bytes_read = read(fd, (char*)buf + ofs, len); | |
| if (bytes_read == 0) return false; | |
| if (bytes_read < 0) { | |
| perror("reading from test runner"); | |
| exit(1); | |
| } | |
| len -= bytes_read; | |
| ofs += bytes_read; | |
| } | |
| return true; | |
| } | |
| void CheckedWrite(int fd, const void* buf, size_t len) { | |
| if ((size_t)write(fd, buf, len) != len) { | |
| perror("writing to test runner"); | |
| exit(1); | |
| } | |
| } | |
| typedef struct { | |
| const conformance_ConformanceRequest* request; | |
| conformance_ConformanceResponse* response; | |
| upb_Arena* arena; | |
| const upb_DefPool* symtab; | |
| } ctx; | |
| bool parse_proto(upb_Message* msg, const upb_MessageDef* m, const ctx* c) { | |
| upb_StringView proto = | |
| conformance_ConformanceRequest_protobuf_payload(c->request); | |
| if (upb_Decode(proto.data, proto.size, msg, upb_MessageDef_MiniTable(m), NULL, | |
| 0, c->arena) == kUpb_DecodeStatus_Ok) { | |
| return true; | |
| } else { | |
| static const char msg[] = "Parse error"; | |
| conformance_ConformanceResponse_set_parse_error( | |
| c->response, upb_StringView_FromString(msg)); | |
| return false; | |
| } | |
| } | |
| void serialize_proto(const upb_Message* msg, const upb_MessageDef* m, | |
| const ctx* c) { | |
| size_t len; | |
| char* data; | |
| upb_EncodeStatus status = | |
| upb_Encode(msg, upb_MessageDef_MiniTable(m), 0, c->arena, &data, &len); | |
| if (status == kUpb_EncodeStatus_Ok) { | |
| conformance_ConformanceResponse_set_protobuf_payload( | |
| c->response, upb_StringView_FromDataAndSize(data, len)); | |
| } else { | |
| static const char msg[] = "Error serializing."; | |
| conformance_ConformanceResponse_set_serialize_error( | |
| c->response, upb_StringView_FromString(msg)); | |
| } | |
| } | |
| void serialize_text(const upb_Message* msg, const upb_MessageDef* m, | |
| const ctx* c) { | |
| size_t len; | |
| size_t len2; | |
| int opts = 0; | |
| char* data; | |
| if (!conformance_ConformanceRequest_print_unknown_fields(c->request)) { | |
| opts |= UPB_TXTENC_SKIPUNKNOWN; | |
| } | |
| len = upb_TextEncode(msg, m, c->symtab, opts, NULL, 0); | |
| data = upb_Arena_Malloc(c->arena, len + 1); | |
| len2 = upb_TextEncode(msg, m, c->symtab, opts, data, len + 1); | |
| UPB_ASSERT(len == len2); | |
| conformance_ConformanceResponse_set_text_payload( | |
| c->response, upb_StringView_FromDataAndSize(data, len)); | |
| } | |
| bool parse_json(upb_Message* msg, const upb_MessageDef* m, const ctx* c) { | |
| upb_StringView json = conformance_ConformanceRequest_json_payload(c->request); | |
| upb_Status status; | |
| int opts = 0; | |
| if (conformance_ConformanceRequest_test_category(c->request) == | |
| conformance_JSON_IGNORE_UNKNOWN_PARSING_TEST) { | |
| opts |= upb_JsonDecode_IgnoreUnknown; | |
| } | |
| upb_Status_Clear(&status); | |
| if (upb_JsonDecode(json.data, json.size, msg, m, c->symtab, opts, c->arena, | |
| &status)) { | |
| return true; | |
| } else { | |
| const char* inerr = upb_Status_ErrorMessage(&status); | |
| size_t len = strlen(inerr); | |
| char* err = upb_Arena_Malloc(c->arena, len + 1); | |
| memcpy(err, inerr, strlen(inerr)); | |
| err[len] = '\0'; | |
| conformance_ConformanceResponse_set_parse_error( | |
| c->response, upb_StringView_FromString(err)); | |
| return false; | |
| } | |
| } | |
| void serialize_json(const upb_Message* msg, const upb_MessageDef* m, | |
| const ctx* c) { | |
| size_t len; | |
| size_t len2; | |
| int opts = 0; | |
| char* data; | |
| upb_Status status; | |
| upb_Status_Clear(&status); | |
| len = upb_JsonEncode(msg, m, c->symtab, opts, NULL, 0, &status); | |
| if (len == (size_t)-1) { | |
| const char* inerr = upb_Status_ErrorMessage(&status); | |
| size_t len = strlen(inerr); | |
| char* err = upb_Arena_Malloc(c->arena, len + 1); | |
| memcpy(err, inerr, strlen(inerr)); | |
| err[len] = '\0'; | |
| conformance_ConformanceResponse_set_serialize_error( | |
| c->response, upb_StringView_FromString(err)); | |
| return; | |
| } | |
| data = upb_Arena_Malloc(c->arena, len + 1); | |
| len2 = upb_JsonEncode(msg, m, c->symtab, opts, data, len + 1, &status); | |
| UPB_ASSERT(len == len2); | |
| conformance_ConformanceResponse_set_json_payload( | |
| c->response, upb_StringView_FromDataAndSize(data, len)); | |
| } | |
| bool parse_input(upb_Message* msg, const upb_MessageDef* m, const ctx* c) { | |
| switch (conformance_ConformanceRequest_payload_case(c->request)) { | |
| case conformance_ConformanceRequest_payload_protobuf_payload: | |
| return parse_proto(msg, m, c); | |
| case conformance_ConformanceRequest_payload_json_payload: | |
| return parse_json(msg, m, c); | |
| case conformance_ConformanceRequest_payload_NOT_SET: | |
| fprintf(stderr, "conformance_upb: Request didn't have payload.\n"); | |
| return false; | |
| default: { | |
| static const char msg[] = "Unsupported input format."; | |
| conformance_ConformanceResponse_set_skipped( | |
| c->response, upb_StringView_FromString(msg)); | |
| return false; | |
| } | |
| } | |
| } | |
| void write_output(const upb_Message* msg, const upb_MessageDef* m, | |
| const ctx* c) { | |
| switch (conformance_ConformanceRequest_requested_output_format(c->request)) { | |
| case conformance_UNSPECIFIED: | |
| fprintf(stderr, "conformance_upb: Unspecified output format.\n"); | |
| exit(1); | |
| case conformance_PROTOBUF: | |
| serialize_proto(msg, m, c); | |
| break; | |
| case conformance_TEXT_FORMAT: | |
| serialize_text(msg, m, c); | |
| break; | |
| case conformance_JSON: | |
| serialize_json(msg, m, c); | |
| break; | |
| default: { | |
| static const char msg[] = "Unsupported output format."; | |
| conformance_ConformanceResponse_set_skipped( | |
| c->response, upb_StringView_FromString(msg)); | |
| break; | |
| } | |
| } | |
| } | |
| void DoTest(const ctx* c) { | |
| upb_Message* msg; | |
| upb_StringView name = conformance_ConformanceRequest_message_type(c->request); | |
| const upb_MessageDef* m = | |
| upb_DefPool_FindMessageByNameWithSize(c->symtab, name.data, name.size); | |
| #if 0 | |
| // Handy code for limiting conformance tests to a single input payload. | |
| // This is a hack since the conformance runner doesn't give an easy way to | |
| // specify what test should be run. | |
| const char skip[] = "\343>\010\301\002\344>\230?\001\230?\002\230?\003"; | |
| upb_StringView skip_str = upb_StringView_FromDataAndSize(skip, sizeof(skip) - 1); | |
| upb_StringView pb_payload = | |
| conformance_ConformanceRequest_protobuf_payload(c->request); | |
| if (!upb_StringView_IsEqual(pb_payload, skip_str)) m = NULL; | |
| #endif | |
| if (!m) { | |
| static const char msg[] = "Unknown message type."; | |
| conformance_ConformanceResponse_set_skipped(c->response, | |
| upb_StringView_FromString(msg)); | |
| return; | |
| } | |
| msg = upb_Message_New(upb_MessageDef_MiniTable(m), c->arena); | |
| if (parse_input(msg, m, c)) { | |
| write_output(msg, m, c); | |
| } | |
| } | |
| void debug_print(const char* label, const upb_Message* msg, | |
| const upb_MessageDef* m, const ctx* c) { | |
| char buf[512]; | |
| upb_TextEncode(msg, m, c->symtab, UPB_TXTENC_SINGLELINE, buf, sizeof(buf)); | |
| fprintf(stderr, "%s: %s\n", label, buf); | |
| } | |
| bool DoTestIo(upb_DefPool* symtab) { | |
| upb_Status status; | |
| char* input; | |
| char* output; | |
| uint32_t input_size; | |
| size_t output_size; | |
| ctx c; | |
| if (!CheckedRead(STDIN_FILENO, &input_size, sizeof(uint32_t))) { | |
| /* EOF. */ | |
| return false; | |
| } | |
| c.symtab = symtab; | |
| c.arena = upb_Arena_New(); | |
| input = upb_Arena_Malloc(c.arena, input_size); | |
| if (!CheckedRead(STDIN_FILENO, input, input_size)) { | |
| fprintf(stderr, "conformance_upb: unexpected EOF on stdin.\n"); | |
| exit(1); | |
| } | |
| c.request = conformance_ConformanceRequest_parse(input, input_size, c.arena); | |
| c.response = conformance_ConformanceResponse_new(c.arena); | |
| if (c.request) { | |
| DoTest(&c); | |
| } else { | |
| fprintf(stderr, "conformance_upb: parse of ConformanceRequest failed: %s\n", | |
| upb_Status_ErrorMessage(&status)); | |
| } | |
| output = conformance_ConformanceResponse_serialize(c.response, c.arena, | |
| &output_size); | |
| uint32_t network_out = (uint32_t)output_size; | |
| CheckedWrite(STDOUT_FILENO, &network_out, sizeof(uint32_t)); | |
| CheckedWrite(STDOUT_FILENO, output, output_size); | |
| test_count++; | |
| if (verbose) { | |
| debug_print("Request", UPB_UPCAST(c.request), | |
| conformance_ConformanceRequest_getmsgdef(symtab), &c); | |
| debug_print("Response", UPB_UPCAST(c.response), | |
| conformance_ConformanceResponse_getmsgdef(symtab), &c); | |
| fprintf(stderr, "\n"); | |
| } | |
| upb_Arena_Free(c.arena); | |
| return true; | |
| } | |
| int main(void) { | |
| upb_DefPool* symtab = upb_DefPool_New(); | |
| #ifdef REBUILD_MINITABLES | |
| _upb_DefPool_LoadDefInitEx( | |
| symtab, &google_protobuf_test_messages_proto2_proto_upbdefinit, true); | |
| _upb_DefPool_LoadDefInitEx( | |
| symtab, &google_protobuf_test_messages_proto3_proto_upbdefinit, true); | |
| _upb_DefPool_LoadDefInitEx( | |
| symtab, | |
| &editions_golden_test_messages_proto2_editions_proto_upbdefinit, | |
| true); | |
| _upb_DefPool_LoadDefInitEx( | |
| symtab, | |
| &editions_golden_test_messages_proto3_editions_proto_upbdefinit, | |
| true); | |
| #else | |
| protobuf_test_messages_proto2_TestAllTypesProto2_getmsgdef(symtab); | |
| protobuf_test_messages_editions_proto2_TestAllTypesProto2_getmsgdef(symtab); | |
| protobuf_test_messages_proto3_TestAllTypesProto3_getmsgdef(symtab); | |
| protobuf_test_messages_editions_proto3_TestAllTypesProto3_getmsgdef(symtab); | |
| #endif | |
| while (1) { | |
| if (!DoTestIo(symtab)) { | |
| fprintf(stderr, | |
| "conformance_upb: received EOF from test runner " | |
| "after %d tests, exiting\n", | |
| test_count); | |
| upb_DefPool_Free(symtab); | |
| return 0; | |
| } | |
| } | |
| } |