libcruft-util/test/json2/event.cpp

463 lines
12 KiB
C++

#include "json2/event.hpp"
#include "json2/except.hpp"
#include "tap.hpp"
#include <vector>
#include <cstring>
#include <functional>
///////////////////////////////////////////////////////////////////////////////
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::error> (
[&] () {
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<util::json2::parse_error> (
[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<util::json2::parse_error> (
[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<util::json2::event::type_t> 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<type_t> 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::parse_error> (
[&] () {
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<type_t> types;
std::vector<std::string> 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<type_t> types;
std::vector<std::string> 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::parse_error> (
[&] () {
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<util::json2::personality::jsonish> (
[] (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 ();
}