#include "config.h" #include "../json_tok.c" #include "../param.c" #include #include #include #include #include #include #include #include char *fail_msg = NULL; bool failed = false; static bool check_fail(void) { if (!failed) return false; failed = false; return true; } struct command *cmd; struct command_result { }; static struct command_result cmd_failed; struct command_result *command_fail(struct command *cmd, errcode_t code, const char *fmt, ...) { failed = true; va_list ap; va_start(ap, fmt); fail_msg = tal_vfmt(cmd, fmt, ap); va_end(ap); return &cmd_failed; } /* AUTOGENERATED MOCKS START */ /* Generated stub for json_to_channel_id */ bool json_to_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct channel_id *cid UNNEEDED) { fprintf(stderr, "json_to_channel_id called!\n"); abort(); } /* Generated stub for json_to_node_id */ bool json_to_node_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct node_id *id UNNEEDED) { fprintf(stderr, "json_to_node_id called!\n"); abort(); } /* Generated stub for json_to_pubkey */ bool json_to_pubkey(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct pubkey *pubkey UNNEEDED) { fprintf(stderr, "json_to_pubkey called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ /* We do this lightningd-style: */ enum command_mode { CMD_NORMAL, CMD_USAGE, CMD_CHECK }; struct command { enum command_mode mode; const char *usage; }; void command_set_usage(struct command *cmd, const char *usage) { cmd->usage = usage; } bool command_usage_only(const struct command *cmd) { return cmd->mode == CMD_USAGE; } bool command_check_only(const struct command *cmd) { return cmd->mode == CMD_CHECK; } struct json { jsmntok_t *toks; char *buffer; }; static void convert_quotes(char *first) { while (*first != '\0') { if (*first == '\'') *first = '"'; first++; } } static struct json *json_parse(const tal_t * ctx, const char *str) { struct json *j = tal(ctx, struct json); j->buffer = tal_strdup(j, str); convert_quotes(j->buffer); j->toks = tal_arr(j, jsmntok_t, 50); assert(j->toks); jsmn_parser parser; again: jsmn_init(&parser); int ret = jsmn_parse(&parser, j->buffer, strlen(j->buffer), j->toks, tal_count(j->toks)); if (ret == JSMN_ERROR_NOMEM) { tal_resize(&j->toks, tal_count(j->toks) * 2); goto again; } if (ret <= 0) { assert(0); } return j; } static void zero_params(void) { struct json *j = json_parse(cmd, "{}"); assert(param(cmd, j->buffer, j->toks, NULL)); j = json_parse(cmd, "[]"); assert(param(cmd, j->buffer, j->toks, NULL)); } struct sanity { char *str; bool failed; int ival; u64 fpval; /* floating-point, multiplied by 1000000 */ char *fail_str; }; struct sanity buffers[] = { // pass {"['42', '3.15']", false, 42, 3150000, NULL}, {"{ 'u64' : '42', 'fp' : '3.15' }", false, 42, 3150000, NULL}, // fail {"{'u64':'42', 'fp':'3.15', 'extra':'stuff'}", true, 0, 0, "unknown parameter"}, {"['42', '3.15', 'stuff']", true, 0, 0, "too many"}, {"['42', '3.15', 'null']", true, 0, 0, "too many"}, // not enough {"{'u64':'42'}", true, 0, 0, "missing required"}, {"['42']", true, 0, 0, "missing required"}, // fail wrong type {"{'u64':'hello', 'fp':'3.15'}", true, 0, 0, "be an unsigned 64"}, {"['3.15', '3.15', 'stuff']", true, 0, 0, "integer"}, }; static void stest(const struct json *j, struct sanity *b) { u64 *ival; u64 *fpval; if (!param(cmd, j->buffer, j->toks, p_req("u64", param_u64, &ival), p_req("fp", param_millionths, &fpval), NULL)) { assert(check_fail()); assert(b->failed == true); if (!strstr(fail_msg, b->fail_str)) { printf("%s != %s\n", fail_msg, b->fail_str); assert(false); } } else { assert(!check_fail()); assert(b->failed == false); assert(*ival == 42); assert(*fpval > 3149900 && b->fpval < 3150100); } } static void sanity(void) { for (int i = 0; i < ARRAY_SIZE(buffers); ++i) { struct json *j = json_parse(cmd, buffers[i].str); assert(j->toks->type == JSMN_OBJECT || j->toks->type == JSMN_ARRAY); stest(j, &buffers[i]); } } /* * Make sure toks are passed through correctly, and also make sure * optional missing toks are set to NULL. */ static void tok_tok(void) { { unsigned int n; const jsmntok_t *tok = NULL; struct json *j = json_parse(cmd, "{ 'satoshi': '546' }"); assert(param(cmd, j->buffer, j->toks, p_req("satoshi", param_tok, &tok), NULL)); assert(tok); assert(json_to_number(j->buffer, tok, &n)); assert(n == 546); } // again with missing optional parameter { /* make sure it is *not* NULL */ const jsmntok_t *tok = (const jsmntok_t *) 65535; struct json *j = json_parse(cmd, "{}"); assert(param(cmd, j->buffer, j->toks, p_opt("satoshi", param_tok, &tok), NULL)); /* make sure it *is* NULL */ assert(tok == NULL); } } /* check for valid but duplicate json name-value pairs */ static void dup_names(void) { struct json *j = json_parse(cmd, "{ 'u64' : '42', 'u64' : '43', 'fp' : '3.15' }"); u64 *i; u64 *fp; assert(!param(cmd, j->buffer, j->toks, p_req("u64", param_u64, &i), p_req("fp", param_millionths, &fp), NULL)); } static void null_params(void) { uint64_t **intptrs = tal_arr(cmd, uint64_t *, 7); /* no null params */ struct json *j = json_parse(cmd, "[ '10', '11', '12', '13', '14', '15', '16']"); assert(param(cmd, j->buffer, j->toks, p_req("0", param_u64, &intptrs[0]), p_req("1", param_u64, &intptrs[1]), p_req("2", param_u64, &intptrs[2]), p_req("3", param_u64, &intptrs[3]), p_opt_def("4", param_u64, &intptrs[4], 999), p_opt("5", param_u64, &intptrs[5]), p_opt("6", param_u64, &intptrs[6]), NULL)); for (int i = 0; i < tal_count(intptrs); ++i) { assert(intptrs[i]); assert(*intptrs[i] == i + 10); } /* missing at end */ j = json_parse(cmd, "[ '10', '11', '12', '13', '14']"); assert(param(cmd, j->buffer, j->toks, p_req("0", param_u64, &intptrs[0]), p_req("1", param_u64, &intptrs[1]), p_req("2", param_u64, &intptrs[2]), p_req("3", param_u64, &intptrs[3]), p_opt("4", param_u64, &intptrs[4]), p_opt("5", param_u64, &intptrs[5]), p_opt_def("6", param_u64, &intptrs[6], 888), NULL)); assert(*intptrs[0] == 10); assert(*intptrs[1] == 11); assert(*intptrs[2] == 12); assert(*intptrs[3] == 13); assert(*intptrs[4] == 14); assert(!intptrs[5]); assert(*intptrs[6] == 888); } static void no_params(void) { struct json *j = json_parse(cmd, "[]"); assert(param(cmd, j->buffer, j->toks, NULL)); j = json_parse(cmd, "[ 'unexpected' ]"); assert(!param(cmd, j->buffer, j->toks, NULL)); } #if DEVELOPER /* * Check to make sure there are no programming mistakes. */ static void bad_programmer(void) { u64 *ival; u64 *ival2; u64 *fpval; struct json *j = json_parse(cmd, "[ '25', '546', '26' ]"); /* check for repeated names */ assert(!param(cmd, j->buffer, j->toks, p_req("repeat", param_u64, &ival), p_req("fp", param_millionths, &fpval), p_req("repeat", param_u64, &ival2), NULL)); assert(check_fail()); assert(strstr(fail_msg, "developer error")); assert(!param(cmd, j->buffer, j->toks, p_req("repeat", param_u64, &ival), p_req("fp", param_millionths, &fpval), p_req("repeat", param_u64, &ival), NULL)); assert(check_fail()); assert(strstr(fail_msg, "developer error")); assert(!param(cmd, j->buffer, j->toks, p_req("u64", param_u64, &ival), p_req("repeat", param_millionths, &fpval), p_req("repeat", param_millionths, &fpval), NULL)); assert(check_fail()); assert(strstr(fail_msg, "developer error")); /* check for repeated arguments */ assert(!param(cmd, j->buffer, j->toks, p_req("u64", param_u64, &ival), p_req("repeated-arg", param_u64, &ival), NULL)); assert(check_fail()); assert(strstr(fail_msg, "developer error")); assert(!param(cmd, j->buffer, j->toks, p_req("u64", (param_cbx) NULL, NULL), NULL)); assert(check_fail()); assert(strstr(fail_msg, "developer error")); /* Add required param after optional */ j = json_parse(cmd, "[ '25', '546', '26', '1.1' ]"); unsigned int *msatoshi; u64 *riskfactor_millionths; assert(!param( cmd, j->buffer, j->toks, p_req("u64", param_u64, &ival), p_req("fp", param_millionths, &fpval), p_opt_def("msatoshi", param_number, &msatoshi, 100), p_req("riskfactor", param_millionths, &riskfactor_millionths), NULL)); assert(*msatoshi); assert(*msatoshi == 100); assert(check_fail()); assert(strstr(fail_msg, "developer error")); } #endif static void add_members(struct param **params, char **obj, char **arr, unsigned int **ints) { for (int i = 0; i < tal_count(ints); ++i) { const char *name = tal_fmt(*params, "%i", i); if (i != 0) { tal_append_fmt(obj, ", "); tal_append_fmt(arr, ", "); } tal_append_fmt(obj, "\"%i\" : %i", i, i); tal_append_fmt(arr, "%i", i); param_add(params, name, true, typesafe_cb_preargs(struct command_result *, void **, param_number, &ints[i], struct command *, const char *, const char *, const jsmntok_t *), &ints[i]); } } /* * A roundabout way of initializing an array of ints to: * ints[0] = 0, ints[1] = 1, ... ints[499] = 499 */ static void five_hundred_params(void) { struct param *params = tal_arr(NULL, struct param, 0); unsigned int **ints = tal_arr(params, unsigned int*, 500); char *obj = tal_fmt(params, "{ "); char *arr = tal_fmt(params, "[ "); add_members(¶ms, &obj, &arr, ints); tal_append_fmt(&obj, "}"); tal_append_fmt(&arr, "]"); /* first test object version */ struct json *j = json_parse(params, obj); assert(param_arr(cmd, j->buffer, j->toks, params, false) == NULL); for (int i = 0; i < tal_count(ints); ++i) { assert(ints[i]); assert(*ints[i] == i); *ints[i] = 65535; } /* now test array */ j = json_parse(params, arr); assert(param_arr(cmd, j->buffer, j->toks, params, false) == NULL); for (int i = 0; i < tal_count(ints); ++i) { assert(*ints[i] == i); } tal_free(params); } static void sendpay(void) { struct json *j = json_parse(cmd, "[ 'A', '123', 'hello there' '547']"); const jsmntok_t *routetok, *note; u64 *msatoshi; unsigned *cltv; if (!param(cmd, j->buffer, j->toks, p_req("route", param_tok, &routetok), p_req("cltv", param_number, &cltv), p_opt("note", param_tok, ¬e), p_opt("msatoshi", param_u64, &msatoshi), NULL)) assert(false); assert(note); assert(!strncmp("hello there", j->buffer + note->start, note->end - note->start)); assert(msatoshi); assert(*msatoshi == 547); } static void sendpay_nulltok(void) { struct json *j = json_parse(cmd, "[ 'A', '123']"); const jsmntok_t *routetok, *note = (void *) 65535; u64 *msatoshi; unsigned *cltv; if (!param(cmd, j->buffer, j->toks, p_req("route", param_tok, &routetok), p_req("cltv", param_number, &cltv), p_opt("note", param_tok, ¬e), p_opt("msatoshi", param_u64, &msatoshi), NULL)) assert(false); assert(note == NULL); assert(msatoshi == NULL); } static void advanced(void) { { struct json *j = json_parse(cmd, "[ 'lightning', 24, 'tok', 543 ]"); struct json_escape *label; u64 *msat; u64 *msat_opt1, *msat_opt2; const jsmntok_t *tok; assert(param(cmd, j->buffer, j->toks, p_req("description", param_label, &label), p_req("msat", param_u64, &msat), p_req("tok", param_tok, &tok), p_opt("msat_opt1", param_u64, &msat_opt1), p_opt("msat_opt2", param_u64, &msat_opt2), NULL)); assert(label != NULL); assert(streq(label->s, "lightning")); assert(*msat == 24); assert(tok); assert(msat_opt1); assert(*msat_opt1 == 543); assert(msat_opt2 == NULL); } { struct json *j = json_parse(cmd, "[ 3, 'foo' ]"); struct json_escape *label, *foo; assert(param(cmd, j->buffer, j->toks, p_req("label", param_label, &label), p_opt("foo", param_label, &foo), NULL)); assert(streq(label->s, "3")); assert(streq(foo->s, "foo")); } { u64 *msat; u64 *msat2; struct json *j = json_parse(cmd, "[ 3 ]"); assert(param(cmd, j->buffer, j->toks, p_opt_def("msat", param_u64, &msat, 23), p_opt_def("msat2", param_u64, &msat2, 53), NULL)); assert(*msat == 3); assert(msat2); assert(*msat2 == 53); } } static void advanced_fail(void) { { struct json *j = json_parse(cmd, "[ 'anyx' ]"); u64 *msat; assert(!param(cmd, j->buffer, j->toks, p_req("msat", param_u64, &msat), NULL)); assert(check_fail()); assert(strstr(fail_msg, "'msat' should be an unsigned 64 bit integer, not 'anyx'")); } } #define test_cb(cb, T, json_, value, pass) \ { \ struct json *j = json_parse(cmd, json_); \ T *v; \ struct command_result *ret = cb(cmd, "name", j->buffer, j->toks + 1, &v); \ assert((ret == NULL) == pass); \ if (ret == NULL) { \ assert(v); \ assert(*v == value); \ } \ } static void param_tests(void) { test_cb(param_bool, bool, "[ true ]", true, true); test_cb(param_bool, bool, "[ false ]", false, true); test_cb(param_bool, bool, "[ tru ]", false, false); test_cb(param_bool, bool, "[ 1 ]", false, false); test_cb(param_millionths, u64, "[ -0.01 ]", 0, false); test_cb(param_millionths, u64, "[ 0.00 ]", 0, true); test_cb(param_millionths, u64, "[ 1 ]", 1000000, true); test_cb(param_millionths, u64, "[ 1.1 ]", 1100000, true); test_cb(param_millionths, u64, "[ 1.01 ]", 1010000, true); test_cb(param_millionths, u64, "[ 99.99 ]", 99990000, true); test_cb(param_millionths, u64, "[ 100.0 ]", 100000000, true); test_cb(param_millionths, u64, "[ 100.001 ]", 100001000, true); test_cb(param_millionths, u64, "[ 1000 ]", 1000000000, true); test_cb(param_millionths, u64, "[ 'wow' ]", 0, false); } static void test_invoice(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { u64 *msatoshi_val; struct json_escape *label_val; const char *desc_val; u64 *expiry; const jsmntok_t *fallbacks; const jsmntok_t *preimagetok; assert(cmd->mode == CMD_USAGE); if (!param(cmd, buffer, params, p_req("msatoshi", param_u64, &msatoshi_val), p_req("label", param_label, &label_val), p_req("description", param_escaped_string, &desc_val), p_opt("expiry", param_u64, &expiry), p_opt("fallbacks", param_array, &fallbacks), p_opt("preimage", param_tok, &preimagetok), NULL)) return; /* should not be here since we are in the mode of CMD_USAGE * and it always returns false. */ abort(); } static void usage(void) { cmd->mode = CMD_USAGE; test_invoice(cmd, NULL, NULL, NULL); assert(streq(cmd->usage, "msatoshi label description " "[expiry] [fallbacks] [preimage]")); cmd->mode = CMD_NORMAL; } int main(void) { setup_locale(); setup_tmpctx(); cmd = tal(tmpctx, struct command); cmd->mode = CMD_NORMAL; fail_msg = tal_arr(cmd, char, 10000); zero_params(); sanity(); tok_tok(); null_params(); no_params(); #if DEVELOPER bad_programmer(); #endif dup_names(); five_hundred_params(); sendpay(); sendpay_nulltok(); advanced(); advanced_fail(); param_tests(); usage(); tal_free(tmpctx); printf("run-params ok\n"); }