core-lightning/common/test/run-json.c
Rusty Russell 3ed03f9c8f common: make json_parse_input API retry friendly.
The jsmn parser is a beautiful piece of code.  In particular, you can parse
part of a string, then continue where you left off.

We don't take advantage of this, however, meaning for large JSON objects
we parse them multiple times before finally having enough to complete.

Expose the parser state and tokens through the API, so the caller can pass
them in repeatedly.  For the moment, every caller is allocates each time
(except the unit tests).

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2020-08-21 09:52:33 +09:30

274 lines
7.6 KiB
C

#include "../json_helpers.c"
#include <assert.h>
#include <ccan/mem/mem.h>
#include <common/json.h>
#include <common/utils.h>
#include <inttypes.h>
#include <stdio.h>
#include <wire/wire.h>
/* AUTOGENERATED MOCKS START */
/* Generated stub for node_id_from_hexstr */
bool node_id_from_hexstr(const char *str UNNEEDED, size_t slen UNNEEDED, struct node_id *id UNNEEDED)
{ fprintf(stderr, "node_id_from_hexstr called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */
// issue #577
static void do_json_tok_bitcoin_amount(const char* val, uint64_t expected)
{
uint64_t amount;
jsmntok_t tok;
tok.start = 0;
tok.end = strlen(val);
fprintf(stderr, "do_json_tok_bitcoin_amount(\"%s\", %"PRIu64"): ", val, expected);
assert(json_to_bitcoin_amount(val, &tok, &amount) == true);
assert(amount == expected);
fprintf(stderr, "ok\n");
}
static int test_json_tok_bitcoin_amount(void)
{
do_json_tok_bitcoin_amount("0.00000001", 1);
do_json_tok_bitcoin_amount("0.00000007", 7);
do_json_tok_bitcoin_amount("0.00000008", 8);
do_json_tok_bitcoin_amount("0.00000010", 10);
do_json_tok_bitcoin_amount("0.12345678", 12345678);
do_json_tok_bitcoin_amount("0.01234567", 1234567);
do_json_tok_bitcoin_amount("123.45678900", 12345678900);
return 0;
}
static void do_json_tok_millionths(const char *val, bool ok, uint64_t expected)
{
uint64_t amount;
jsmntok_t tok;
tok.start = 0;
tok.end = strlen(val);
assert(json_to_millionths(val, &tok, &amount) == ok);
if (ok)
assert(amount == expected);
}
static int test_json_tok_millionths(void)
{
do_json_tok_millionths("", false, 0);
do_json_tok_millionths("0..0", false, 0);
do_json_tok_millionths("0.0.", false, 0);
do_json_tok_millionths(".", false, 0);
do_json_tok_millionths("..", false, 0);
do_json_tok_millionths("0", true, 0);
do_json_tok_millionths(".0", true, 0);
do_json_tok_millionths("0.", true, 0);
do_json_tok_millionths("100", true, 100 * 1000000);
do_json_tok_millionths("100.0", true, 100 * 1000000);
do_json_tok_millionths("100.", true, 100 * 1000000);
do_json_tok_millionths("100.000001", true, 100 * 1000000 + 1);
do_json_tok_millionths("100.0000001", true, 100 * 1000000);
do_json_tok_millionths(".000009", true, 9);
do_json_tok_millionths(".0000099", true, 9);
do_json_tok_millionths("18446744073709.551615", true,
18446744073709551615ULL);
do_json_tok_millionths("18446744073709.551616", false, 0);
do_json_tok_millionths("18446744073709.551625", false, 0);
do_json_tok_millionths("18446744073709.551715", false, 0);
do_json_tok_millionths("18446744073709.552615", false, 0);
do_json_tok_millionths("18446744073709.561615", false, 0);
do_json_tok_millionths("18446744073709.651615", false, 0);
do_json_tok_millionths("18446744073710.551615", false, 0);
do_json_tok_millionths("18446744073809.551615", false, 0);
do_json_tok_millionths("18446744074709.551615", false, 0);
do_json_tok_millionths("18446744083709.551615", false, 0);
do_json_tok_millionths("18446744173709.551615", false, 0);
do_json_tok_millionths("18446745073709.551615", false, 0);
do_json_tok_millionths("18446754073709.551615", false, 0);
do_json_tok_millionths("18446844073709.551615", false, 0);
do_json_tok_millionths("18447744073709.551615", false, 0);
do_json_tok_millionths("18456744073709.551615", false, 0);
do_json_tok_millionths("18546744073709.551615", false, 0);
do_json_tok_millionths("19446744073709.551615", false, 0);
do_json_tok_millionths("28446744073709.551615", false, 0);
return 0;
}
static void test_json_tok_size(void)
{
jsmntok_t *toks;
char *buf;
bool ok, complete;
jsmn_parser parser;
buf = "[\"e1\", [\"e2\", \"e3\"]]";
toks = toks_alloc(tmpctx);
jsmn_init(&parser);
ok = json_parse_input(&parser, &toks, buf, strlen(buf), &complete);
assert(ok);
assert(complete);
/* size only counts *direct* children */
assert(toks[0].size == 2);
assert(toks[2].size == 2);
buf = "[[\"e1\", \"e2\"], \"e3\"]";
toks_reset(toks);
jsmn_init(&parser);
ok = json_parse_input(&parser, &toks, buf, strlen(buf), &complete);
assert(ok);
assert(complete);
/* size only counts *direct* children */
assert(toks[0].size == 2);
assert(toks[1].size == 2);
buf = "{\"e1\" : {\"e2\": 2, \"e3\": 3}}";
toks_reset(toks);
jsmn_init(&parser);
ok = json_parse_input(&parser, &toks, buf, strlen(buf), &complete);
assert(ok);
assert(complete);
/* size only counts *direct* children */
assert(toks[0].size == 1);
assert(toks[2].size == 2);
buf = "{\"e1\" : {\"e2\": 2, \"e3\": 3}, \"e4\" : {\"e5\": 5, \"e6\": 6}}";
toks_reset(toks);
jsmn_init(&parser);
ok = json_parse_input(&parser, &toks, buf, strlen(buf), &complete);
assert(ok);
assert(complete);
/* size only counts *direct* children */
assert(toks[0].size == 2);
assert(toks[2].size == 2);
assert(toks[8].size == 2);
/* This should *not* parse! (used to give toks[0]->size == 3!) */
buf = "{ \"\" \"\" \"\" }";
toks_reset(toks);
jsmn_init(&parser);
ok = json_parse_input(&parser, &toks, buf, strlen(buf), &complete);
assert(!ok);
/* This should *not* parse! (used to give toks[0]->size == 2!) */
buf = "{ 'satoshi', '546' }";
toks = json_parse_simple(tmpctx, buf, strlen(buf));
assert(!toks);
}
static void test_json_delve(void)
{
const jsmntok_t *toks, *t;
char *buf;
buf = "{\"1\":\"one\", \"2\":\"two\", \"3\":[\"three\", {\"deeper\": 17}]}";
toks = json_parse_simple(tmpctx, buf, strlen(buf));
assert(toks);
assert(toks->size == 3);
t = json_delve(buf, toks, ".1");
assert(t);
assert(t->type == JSMN_STRING);
assert(json_tok_streq(buf, t, "one"));
assert(t == toks+2);
t = json_delve(buf, toks, ".2");
assert(t);
assert(t->type == JSMN_STRING);
assert(json_tok_streq(buf, t, "two"));
assert(t == toks+4);
t = json_delve(buf, toks, ".3");
assert(t);
assert(t->type == JSMN_ARRAY);
assert(t == toks+6);
assert(t->size == 2);
t = json_delve(buf, toks, ".3[0]");
assert(t);
assert(t->type == JSMN_STRING);
assert(json_tok_streq(buf, t, "three"));
assert(t == toks+7);
t = json_delve(buf, toks, ".3[1]");
assert(t);
assert(t->type == JSMN_OBJECT);
assert(t == toks+8);
assert(t->size == 1);
t = json_delve(buf, toks, ".3[1].deeper");
assert(t);
assert(t->type == JSMN_PRIMITIVE);
assert(memeq(buf + t->start, t->end - t->start, "17", strlen("17")));
assert(t == toks+10);
t = json_delve(buf, toks, ".4");
assert(!t);
t = json_delve(buf, toks, "[0]");
assert(!t);
t = json_delve(buf, toks, ".deeper");
assert(!t);
t = json_delve(buf, toks, ".3[2]");
assert(!t);
t = json_delve(buf, toks, ".3[2].deeper");
assert(!t);
t = json_delve(buf, toks, ".3[0].deeper");
assert(!t);
t = json_delve(buf, toks, ".3[1].deeper[0]");
assert(!t);
t = json_delve(buf, toks, ".3[1][0]");
assert(!t);
/* Now a real example. */
buf = "{\n"
" \"jsonrpc\": \"2.0\", \n"
" \"method\": \"init\", \n"
" \"id\": 1, \n"
" \"params\": {\n"
" \"options\": {\n"
" }, \n"
" \"configuration\": {\n"
" \"lightning-dir\": \"/tmp/ltests-n2hyd543/test_pay_plugin_1/lightning-2/\", \n"
" \"rpc-file\": \"lightning-rpc\"\n"
" }\n"
" }\n"
"}\n"
"\n";
toks = json_parse_simple(tmpctx, buf, strlen(buf));
assert(toks);
assert(toks->size == 4);
t = json_delve(buf, toks, ".rpcfile");
assert(!t);
t = json_delve(buf, toks, ".configuration.rpc-file");
assert(!t);
t = json_delve(buf, toks, ".params.configuration");
assert(t);
assert(t->size == 2);
t = json_delve(buf, toks, ".params.configuration.rpc-file");
assert(t);
assert(t->type == JSMN_STRING);
assert(json_tok_streq(buf, t, "lightning-rpc"));
}
int main(void)
{
setup_locale();
setup_tmpctx();
test_json_tok_size();
test_json_tok_bitcoin_amount();
test_json_tok_millionths();
test_json_delve();
assert(!taken_any());
take_cleanup();
tal_free(tmpctx);
}