#include "json2/event.hpp" #include "json2/except.hpp" #include "tap.hpp" #include #include #include /////////////////////////////////////////////////////////////////////////////// void test_numbers (util::TAP::logger &tap) { static const struct { const char *data; bool good; const char *message; } TESTS[] = { { "1", true, "single digit" }, { "01", false, "leading zero" }, { "-1", true, "leading minus" }, { "+1", false, "leading plus" }, { "1.", false, "truncated fraction" }, { "1.0", true, "fraction" }, { "1.0e", false, "truncated exponential" }, { "1.0e1", true, "lower exponential" }, { "1.0E1", true, "upper exponential" }, { "1.0e+1", true, "positive exponential" }, { "1.0e+", false, "truncated positive exponential" }, { "1.0e-1", true, "negative exponential" }, { "1.0e-", false, "truncated negative exponential" }, }; for (const auto &t: TESTS) { auto first = t.data; auto last = first + strlen (first); if (!t.good) { tap.expect_throw ( [&] () { util::json2::event::parse ([] (auto) {}, first, last); }, "number, %s", t.message ); } else { util::json2::event::type_t type; auto end = util::json2::event::parse ( [&type] (auto p) { type = p.type (); }, first, last ); tap.expect ( last == end && type == util::json2::event::type_t::NUMBER, "number, %s", t.message ); } } } /////////////////////////////////////////////////////////////////////////////// void test_literals (util::TAP::logger &tap) { static const struct { const char *data; bool good; util::json2::event::type_t type; const char *message; } TESTS[] = { { "true", true, util::json2::event::type_t::BOOLEAN, "lower true" }, { "TRUE", false, util::json2::event::type_t::BOOLEAN, "upper true" }, { "truE", false, util::json2::event::type_t::BOOLEAN, "mixed true" }, { "tru", false, util::json2::event::type_t::BOOLEAN, "truncated true" }, { "false", true, util::json2::event::type_t::BOOLEAN, "lower false" }, { "FALSE", false, util::json2::event::type_t::BOOLEAN, "upper false" }, { "falSe", false, util::json2::event::type_t::BOOLEAN, "mixed false" }, { "fals", false, util::json2::event::type_t::BOOLEAN, "truncated false" }, { "null", true, util::json2::event::type_t::NONE, "lower null" }, { "NULL", false, util::json2::event::type_t::NONE, "upper null" }, { "nUll", false, util::json2::event::type_t::NONE, "mixed null" }, { "nul", false, util::json2::event::type_t::NONE, "truncated null" }, }; for (const auto &t: TESTS) { auto first = t.data; auto last = first + strlen (first); if (t.good) { util::json2::event::type_t type; util::json2::event::parse ([&type] (auto p) { type = p.type (); }, first, last); tap.expect_eq (type, t.type, "literal, %s", t.message); } else { tap.expect_throw ( [first,last] () { util::json2::event::parse ([] (auto) {}, first, last); }, "literal, %s", t.message ); } } } /////////////////////////////////////////////////////////////////////////////// void test_strings (util::TAP::logger &tap) { static const struct { const char *data; bool good; const char *message; } TESTS[] = { { "\"abc\"", true, "abc" }, { "\"\"", true, "empty" }, { "\"", false, "unbalanced quote" }, { "\"\\n\"", true, "newline escape" }, { "\"\\a\"", true, "valid unnecessary escape" }, { "\"\\uABCD\"", true, "upper unicode hex escape" }, { "\"\\uabcd\"", true, "lower unicode hex escape" }, { "\"\\uab\"", false, "truncated unicode hex escape" }, { "\"\\uabxy\"", false, "invalid unicode hex escape" }, }; for (const auto &t: TESTS) { auto first = t.data; auto last = first + strlen (first); if (t.good) { util::json2::event::type_t type; util::json2::event::parse ([&type] (auto p) { type = p.type (); }, first, last); tap.expect_eq (type, util::json2::event::type_t::STRING, "string, %s", t.message); } else { tap.expect_throw ( [first,last] () { util::json2::event::parse ([] (auto) {}, first, last); }, "string, %s", t.message ); } } }; /////////////////////////////////////////////////////////////////////////////// void test_arrays (util::TAP::logger &tap) { using util::json2::event::type_t; static const struct { const char *data; bool good; std::vector types; const char *message; } TESTS[] = { { "[]", true, { type_t::ARRAY_BEGIN, type_t::ARRAY_END }, "empty" }, { "[1]", true, { type_t::ARRAY_BEGIN, type_t::NUMBER, type_t::ARRAY_END }, "single number" }, { "[1true]", false, { type_t::ARRAY_BEGIN, type_t::NUMBER }, "contatenated number/bool" }, { "[1,2]", true, { type_t::ARRAY_BEGIN, type_t::NUMBER, type_t::NUMBER, type_t::ARRAY_END }, "two numbers" }, { "[1,]", false, { type_t::ARRAY_BEGIN, type_t::NUMBER }, "single trailing comma" }, { "[1,2,]", false, { type_t::ARRAY_BEGIN, type_t::NUMBER, type_t::NUMBER }, "double trailing comma" }, { "[,]", false, { type_t::ARRAY_BEGIN }, "only comma" }, { "[", false, { type_t::ARRAY_BEGIN }, "missing terminator" }, { "[[]]", true, { type_t::ARRAY_BEGIN, type_t::ARRAY_BEGIN, type_t::ARRAY_END, type_t::ARRAY_END }, "nested array" }, { "[[]", false, { type_t::ARRAY_BEGIN, type_t::ARRAY_END }, "unbalanced nested array" }, }; for (const auto &t: TESTS) { auto first = t.data; auto last = first + strlen (first); if (t.good) { std::vector types; util::json2::event::parse ([&types] (auto p) { types.push_back (p.type ()); }, first, last); tap.expect_eq (types, t.types, "array, %s", t.message); } else { tap.expect_throw ( [&] () { util::json2::event::parse ([] (auto) { }, first, last); }, "array, %s", t.message ); } } }; /////////////////////////////////////////////////////////////////////////////// void test_objects (util::TAP::logger &tap) { using util::json2::event::type_t; static const struct { const char *data; bool good; std::vector types; std::vector strings; const char *message; } TESTS[] = { { "{}", true, { type_t::OBJECT_BEGIN, type_t::OBJECT_END }, {}, "empty" }, { "{", false, { type_t::OBJECT_BEGIN }, {}, "missing terminator" }, { R"json({"a":1})json", true, { type_t::OBJECT_BEGIN, type_t::STRING, type_t::NUMBER, type_t::OBJECT_END }, { "\"a\"" }, "empty" }, { "{1:1}", false, { type_t::OBJECT_BEGIN }, {}, "integer key" }, { "{:1}", false, { type_t::OBJECT_BEGIN }, {}, "no key" }, { R"json({"a":})json", false, { type_t::OBJECT_BEGIN, type_t::STRING }, {}, "no value" }, { R"json({"a":[]})json", true, { type_t::OBJECT_BEGIN, type_t::STRING, type_t::ARRAY_BEGIN, type_t::ARRAY_END, type_t::OBJECT_END }, { "\"a\"" }, "array value" }, { R"json({ "a": { "b": null } })json", true, { type_t::OBJECT_BEGIN, type_t::STRING, type_t::OBJECT_BEGIN, type_t::STRING, type_t::NONE, type_t::OBJECT_END, type_t::OBJECT_END }, { "\"a\"", "\"b\"" }, "recursive object" } }; for (const auto &t: TESTS) { auto first = t.data; auto last = first + strlen (first); if (t.good) { std::vector types; std::vector strings; util::json2::event::parse ([&] (auto p) { types.push_back (p.type ()); if (p.type () == type_t::STRING) strings.push_back (std::string {p.first, p.last}); }, first, last); tap.expect (types == t.types && strings == t.strings, "object, %s", t.message); } else { tap.expect_throw ( [&] () { util::json2::event::parse ([] (auto) {}, first, last); }, "object, %s", t.message ); } } }; /////////////////////////////////////////////////////////////////////////////// void test_jsonish (util::TAP::logger &tap) { static const struct { const char *data; const char *message; } TESTS[] = { //{ "{}", "empty object" }, //{ "0xdeadbeef", "hex literal" }, //{ "0b11011100", "binary literal" }, //{ "0666", "octal literal" }, //{ "0.", "float without frac" }, //{ "+0.", "float with leading +" }, //{ "-0.", "float with leading -" }, //{ "string", "bare literal string" }, //{ "{foo: 1}", "bare string key" }, //{ "{foo: bar}", "bare string key and value" }, //{ "{foo: bar,}", "trailing object comma" }, //{ "[1,]", "trailing array comma" }, //{ "1 #comment", "trailing comment" }, { "#comment\n1", "leading comment" }, }; for (const auto &t: TESTS) { bool success = true; try { util::json2::event::parse ( [] (auto) {}, t.data, t.data + strlen (t.data) ); } catch (const util::json2::error&) { success = false; } tap.expect (success, "jsonish, %s", t.message); } }; /////////////////////////////////////////////////////////////////////////////// int main (void) { util::TAP::logger tap; test_numbers (tap); test_literals (tap); test_strings (tap); test_arrays (tap); test_objects (tap); test_jsonish (tap); return tap.status (); }