From 8f8113b4fff748b57b0ff2f1a301e86b4703be84 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Tue, 9 Dec 2014 12:27:22 -0800 Subject: JSON test, symbolic enum names in JSON, and a few improvements. - Added a JSON test that round-trips (parses then re-serializes) several test messages, ensuring that the re-serialized form matches the original exactly. - Added support for printing and parsing symbolic enum names (rather than integer values) in JSON. - Updated JSON printer to properly handle string fields that come in multiple pieces. ('bytes' fields still do not support this, and this work is more challenging because it requires making the base64 encoder resumable. Base64 encoding is not separable at an input-byte granularity, unlike string escaping.) - Fixed a < vs. <= bug in UTF-8 encoding generation (oops). --- tests/json/test_json.cc | 244 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 tests/json/test_json.cc (limited to 'tests/json') diff --git a/tests/json/test_json.cc b/tests/json/test_json.cc new file mode 100644 index 0000000..1444081 --- /dev/null +++ b/tests/json/test_json.cc @@ -0,0 +1,244 @@ +/* + * upb - a minimalist implementation of protocol buffers. + * + * Copyright (c) 2014 Google Inc. See LICENSE for details. + * + * A set of tests for JSON parsing and serialization. + */ + +#include "tests/upb_test.h" +#include "upb/handlers.h" +#include "upb/symtab.h" +#include "upb/json/printer.h" +#include "upb/json/parser.h" +#include "upb/upb.h" + +#include + +// Macros for readability in test case list: allows us to give TEST("...") / +// EXPECT("...") pairs. +#define TEST(x) x +#define EXPECT_SAME NULL +#define EXPECT(x) x +#define TEST_SENTINEL { NULL, NULL } + +struct TestCase { + const char* input; + const char* expected; +}; + +static TestCase kTestRoundtripMessages[] = { + // Test most fields here. + { + TEST("{\"optional_int32\":-42,\"optional_string\":\"Test\\u0001Message\"," + "\"optional_msg\":{\"foo\":42}," + "\"optional_bool\":true,\"repeated_msg\":[{\"foo\":1}," + "{\"foo\":2}]}"), + EXPECT_SAME + }, + // Test special escapes in strings. + { + TEST("{\"repeated_string\":[\"\\b\",\"\\r\",\"\\n\",\"\\f\",\"\\t\"," + "\"\uFFFF\"]}"), + EXPECT_SAME + }, + // Test enum symbolic names. + { + // The common case: parse and print the symbolic name. + TEST("{\"optional_enum\":\"A\"}"), + EXPECT_SAME + }, + { + // Unknown enum value: will be printed as an integer. + TEST("{\"optional_enum\":42}"), + EXPECT_SAME + }, + { + // Known enum value: we're happy to parse an integer but we will re-emit the + // symbolic name. + TEST("{\"optional_enum\":1}"), + EXPECT("{\"optional_enum\":\"B\"}") + }, + // UTF-8 tests: escapes -> literal UTF8 in output. + { + // Note double escape on \uXXXX: we want the escape to be processed by the + // JSON parser, not by the C++ compiler! + TEST("{\"optional_string\":\"\\u007F\"}"), + EXPECT("{\"optional_string\":\"\x7F\"}") + }, + { + TEST("{\"optional_string\":\"\\u0080\"}"), + EXPECT("{\"optional_string\":\"\xC2\x80\"}") + }, + { + TEST("{\"optional_string\":\"\\u07FF\"}"), + EXPECT("{\"optional_string\":\"\xDF\xBF\"}") + }, + { + TEST("{\"optional_string\":\"\\u0800\"}"), + EXPECT("{\"optional_string\":\"\xE0\xA0\x80\"}") + }, + { + TEST("{\"optional_string\":\"\\uFFFF\"}"), + EXPECT("{\"optional_string\":\"\xEF\xBF\xBF\"}") + }, + TEST_SENTINEL +}; + +static void AddField(upb::MessageDef* message, + int number, + const char* name, + upb_fieldtype_t type, + bool is_repeated, + const upb::Def* subdef = NULL) { + upb::reffed_ptr field(upb::FieldDef::New()); + upb::Status st; + field->set_name(name, &st); + field->set_type(type); + field->set_label(is_repeated ? UPB_LABEL_REPEATED : UPB_LABEL_OPTIONAL); + field->set_number(number, &st); + if (subdef) { + field->set_subdef(subdef, &st); + } + message->AddField(field, &st); +} + +static const upb::MessageDef* BuildTestMessage( + upb::reffed_ptr symtab) { + upb::Status st; + + // Create SubMessage. + upb::reffed_ptr submsg(upb::MessageDef::New()); + submsg->set_full_name("SubMessage", &st); + AddField(submsg.get(), 1, "foo", UPB_TYPE_INT32, false); + + // Create MyEnum. + upb::reffed_ptr myenum(upb::EnumDef::New()); + myenum->set_full_name("MyEnum", &st); + myenum->AddValue("A", 0, &st); + myenum->AddValue("B", 1, &st); + myenum->AddValue("C", 2, &st); + + // Create TestMessage. + upb::reffed_ptr md(upb::MessageDef::New()); + md->set_full_name("TestMessage", &st); + + AddField(md.get(), 1, "optional_int32", UPB_TYPE_INT32, false); + AddField(md.get(), 2, "optional_int64", UPB_TYPE_INT64, false); + AddField(md.get(), 3, "optional_uint32", UPB_TYPE_UINT32, false); + AddField(md.get(), 4, "optional_uint64", UPB_TYPE_UINT64, false); + AddField(md.get(), 5, "optional_string", UPB_TYPE_STRING, false); + AddField(md.get(), 6, "optional_bytes", UPB_TYPE_BYTES, false); + AddField(md.get(), 7, "optional_bool" , UPB_TYPE_BOOL, false); + AddField(md.get(), 8, "optional_msg" , UPB_TYPE_MESSAGE, false, + upb::upcast(submsg.get())); + AddField(md.get(), 9, "optional_enum", UPB_TYPE_ENUM, false, + upb::upcast(myenum.get())); + + AddField(md.get(), 11, "repeated_int32", UPB_TYPE_INT32, true); + AddField(md.get(), 12, "repeated_int64", UPB_TYPE_INT64, true); + AddField(md.get(), 13, "repeated_uint32", UPB_TYPE_UINT32, true); + AddField(md.get(), 14, "repeated_uint64", UPB_TYPE_UINT64, true); + AddField(md.get(), 15, "repeated_string", UPB_TYPE_STRING, true); + AddField(md.get(), 16, "repeated_bytes", UPB_TYPE_BYTES, true); + AddField(md.get(), 17, "repeated_bool" , UPB_TYPE_BOOL, true); + AddField(md.get(), 18, "repeated_msg" , UPB_TYPE_MESSAGE, true, + upb::upcast(submsg.get())); + AddField(md.get(), 19, "optional_enum", UPB_TYPE_ENUM, true, + upb::upcast(myenum.get())); + + // Add both to our symtab. + upb::Def* defs[3] = { + upb::upcast(submsg.ReleaseTo(&defs)), + upb::upcast(myenum.ReleaseTo(&defs)), + upb::upcast(md.ReleaseTo(&defs)), + }; + symtab->Add(defs, 3, &defs, &st); + + // Return TestMessage. + return symtab->LookupMessage("TestMessage"); +} + +class StringSink { + public: + StringSink() { + upb_byteshandler_init(&byteshandler_); + upb_byteshandler_setstring(&byteshandler_, &str_handler, NULL); + upb_bytessink_reset(&bytessink_, &byteshandler_, &s_); + } + ~StringSink() { } + + upb_bytessink* Sink() { return &bytessink_; } + + const std::string& Data() { return s_; } + + private: + + static size_t str_handler(void* _closure, const void* hd, + const char* data, size_t len, + const upb_bufhandle* handle) { + UPB_UNUSED(hd); + UPB_UNUSED(handle); + std::string* s = static_cast(_closure); + std::string appended(data, len); + s->append(data, len); + return len; + } + + upb_byteshandler byteshandler_; + upb_bytessink bytessink_; + std::string s_; +}; + +// Starts with a message in JSON format, parses and directly serializes again, +// and compares the result. +void test_json_roundtrip() { + upb::reffed_ptr symtab(upb::SymbolTable::New()); + const upb::MessageDef* md = BuildTestMessage(symtab.get()); + upb::reffed_ptr serialize_handlers( + upb::json::Printer::NewHandlers(md)); + + for (const TestCase* test_case = kTestRoundtripMessages; + test_case->input != NULL; test_case++) { + + const char *json_src = test_case->input; + const char *json_expected = test_case->expected; + if (json_expected == EXPECT_SAME) { + json_expected = json_src; + } + + upb::Status st; + upb::json::Parser parser(&st); + upb::json::Printer printer(serialize_handlers.get()); + StringSink data_sink; + + parser.ResetOutput(printer.input()); + printer.ResetOutput(data_sink.Sink()); + + bool ok = upb::BufferSource::PutBuffer(json_src, strlen(json_src), + parser.input()); + if (!ok) { + fprintf(stderr, "upb parse error: %s\n", st.error_message()); + } + ASSERT(ok); + + if (memcmp(json_expected, + data_sink.Data().data(), + data_sink.Data().size())) { + fprintf(stderr, + "JSON parse/serialize roundtrip result differs:\n" + "Original:\n%s\nParsed/Serialized:\n%s\n", + json_src, data_sink.Data().c_str()); + abort(); + } + } +} + +extern "C" { +int run_tests(int argc, char *argv[]) { + UPB_UNUSED(argc); + UPB_UNUSED(argv); + test_json_roundtrip(); + return 0; +} +} -- cgit v1.2.3