summaryrefslogtreecommitdiff
path: root/tests/json
diff options
context:
space:
mode:
authorChris Fallin <cfallin@google.com>2014-12-09 12:27:22 -0800
committerChris Fallin <cfallin@google.com>2014-12-09 13:23:58 -0800
commit8f8113b4fff748b57b0ff2f1a301e86b4703be84 (patch)
treece16586d7f0a8e5c87252727b4b3745d1a48a4eb /tests/json
parente257bd978d5e6278e7b188d543858852c0c4d856 (diff)
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).
Diffstat (limited to 'tests/json')
-rw-r--r--tests/json/test_json.cc244
1 files changed, 244 insertions, 0 deletions
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 <string>
+
+// 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<upb::FieldDef> 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<upb::SymbolTable> symtab) {
+ upb::Status st;
+
+ // Create SubMessage.
+ upb::reffed_ptr<upb::MessageDef> submsg(upb::MessageDef::New());
+ submsg->set_full_name("SubMessage", &st);
+ AddField(submsg.get(), 1, "foo", UPB_TYPE_INT32, false);
+
+ // Create MyEnum.
+ upb::reffed_ptr<upb::EnumDef> 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<upb::MessageDef> 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<std::string*>(_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<upb::SymbolTable> symtab(upb::SymbolTable::New());
+ const upb::MessageDef* md = BuildTestMessage(symtab.get());
+ upb::reffed_ptr<const upb::Handlers> 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;
+}
+}
generated by cgit on debian on lair
contact matthew@masot.net with questions or feedback