Files
MaxScale/server/core/test/testmodulecmd.cc
Markus Mäkelä b3c1e15f22 MXS-1220: Add output for module commands
The module commands can now produce JSON formatted output which is passed
to the caller. The output should conform to the JSON API as closely as
possible.

Currently, the REST API wraps all JSON produced by module commands inside
a meta-object of the following type:

  {
    "meta": <output of module command>
  }

This allows the output to be JSON API conformant without modifying the
modules and allows incremental updates to code.
2017-06-12 10:08:25 +03:00

445 lines
16 KiB
C++

/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2020-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
/**
* Test modulecmd.h functionality
*/
#include <maxscale/alloc.h>
#include <maxscale/dcb.h>
#include <maxscale/paths.h>
#include <maxscale/modulecmd.h>
#include <maxscale/session.h>
#include <maxscale/json_api.h>
#include "../maxscale/monitor.h"
#define TEST(a, b) do{if (!(a)){printf("%s:%d " b "\n", __FILE__, __LINE__);return 1;}}while(false)
static bool ok = false;
bool test_fn(const MODULECMD_ARG *arg, json_t** output)
{
ok = (arg->argc == 2 && strcmp(arg->argv[0].value.string, "Hello") == 0 &&
arg->argv[1].value.boolean);
return true;
}
int test_arguments()
{
const void *params1[] = {"Hello", "true"};
const void *params2[] = {"Hello", "1"};
const void *wrong_params1[] = {"Hi", "true"};
const void *wrong_params2[] = {"Hello", "false"};
const void *bad_params1[] = {"Hello", "World!"};
const void *bad_params2[] = {"Hello", NULL};
const void *bad_params3[] = {NULL, NULL};
const void *bad_params4[] = {NULL, "World!"};
const char *ns = "test_arguments";
const char *id = "test_arguments";
modulecmd_arg_type_t args1[] =
{
{MODULECMD_ARG_STRING, ""},
{MODULECMD_ARG_BOOLEAN, ""}
};
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
/**
* Test command creation
*/
TEST(modulecmd_find_command(ns, id) == NULL, "The registered command should not yet be found");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_register_command(ns, id, MODULECMD_TYPE_ACTIVE, test_fn, 2, args1),
"Registering a command should succeed");
TEST(!modulecmd_register_command(ns, id, MODULECMD_TYPE_ACTIVE, test_fn, 2, args1),
"Registering the command a second time should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
const MODULECMD *cmd = modulecmd_find_command(ns, id);
TEST(cmd, "The registered command should be found");
/**
* Test bad arguments
*/
TEST(modulecmd_arg_parse(cmd, 0, NULL) == NULL, "Passing no arguments should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_arg_parse(cmd, 1, params1) == NULL, "Passing one argument should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_arg_parse(cmd, 3, params1) == NULL, "Passing three arguments should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_arg_parse(cmd, 2, bad_params1) == NULL, "Passing bad arguments should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_arg_parse(cmd, 2, bad_params2) == NULL, "Passing bad arguments should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_arg_parse(cmd, 2, bad_params3) == NULL, "Passing bad arguments should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_arg_parse(cmd, 2, bad_params4) == NULL, "Passing bad arguments should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
/**
* Test valid arguments
*/
MODULECMD_ARG* alist = modulecmd_arg_parse(cmd, 2, params1);
TEST(alist, "Arguments should be parsed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, alist, NULL), "Module call should be successful");
TEST(ok, "Function should receive right parameters");
ok = false;
TEST(modulecmd_call_command(cmd, alist, NULL), "Second Module call should be successful");
TEST(ok, "Function should receive right parameters");
ok = false;
modulecmd_arg_free(alist);
TEST((alist = modulecmd_arg_parse(cmd, 2, params2)), "Arguments should be parsed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, alist, NULL), "Module call should be successful");
TEST(ok, "Function should receive right parameters");
modulecmd_arg_free(alist);
/**
* Test valid but wrong arguments
*/
TEST((alist = modulecmd_arg_parse(cmd, 2, wrong_params1)), "Arguments should be parsed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, alist, NULL), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(!ok, "Function should receive wrong parameters");
modulecmd_arg_free(alist);
TEST((alist = modulecmd_arg_parse(cmd, 2, wrong_params2)), "Arguments should be parsed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, alist, NULL), "Module call should be successful");
TEST(!ok, "Function should receive wrong parameters");
modulecmd_arg_free(alist);
return 0;
}
bool test_fn2(const MODULECMD_ARG *arg, json_t** output)
{
return true;
}
int test_optional_arguments()
{
const void *params1[] = {"Hello", "true"};
const void *params2[] = {NULL, "true"};
const void *params3[] = {"Hello", NULL};
const void *params4[] = {NULL, NULL};
const char *ns = "test_optional_arguments";
const char *id = "test_optional_arguments";
modulecmd_arg_type_t args1[] =
{
{MODULECMD_ARG_STRING | MODULECMD_ARG_OPTIONAL, ""},
{MODULECMD_ARG_BOOLEAN | MODULECMD_ARG_OPTIONAL, ""}
};
TEST(modulecmd_register_command(ns, id, MODULECMD_TYPE_ACTIVE, test_fn2, 2, args1),
"Registering a command should succeed");
const MODULECMD *cmd = modulecmd_find_command(ns, id);
TEST(cmd, "The registered command should be found");
MODULECMD_ARG *arg = modulecmd_arg_parse(cmd, 2, params1);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg, NULL), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
arg = modulecmd_arg_parse(cmd, 2, params2);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg, NULL), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
arg = modulecmd_arg_parse(cmd, 2, params3);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg, NULL), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
arg = modulecmd_arg_parse(cmd, 2, params4);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg, NULL), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
arg = modulecmd_arg_parse(cmd, 1, params1);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg, NULL), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
arg = modulecmd_arg_parse(cmd, 1, params2);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg, NULL), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
arg = modulecmd_arg_parse(cmd, 0, params1);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg, NULL), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
TEST(modulecmd_call_command(cmd, NULL, NULL), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
return 0;
}
bool test_fn3(const MODULECMD_ARG *arg, json_t** output)
{
modulecmd_set_error("Something went wrong!");
return false;
}
int test_module_errors()
{
const char *ns = "test_module_errors";
const char *id = "test_module_errors";
TEST(modulecmd_register_command(ns, id, MODULECMD_TYPE_ACTIVE, test_fn3, 0, NULL),
"Registering a command should succeed");
const MODULECMD *cmd = modulecmd_find_command(ns, id);
TEST(cmd, "The registered command should be found");
TEST(!modulecmd_call_command(cmd, NULL, NULL), "Module call should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
return 0;
}
bool test_fn_map(const MODULECMD_ARG *arg, json_t** output)
{
return true;
}
const char *map_dom = "test_map";
bool mapfn(const MODULECMD *cmd, void *data)
{
int *i = (int*)data;
(*i)++;
return true;
}
int test_map()
{
for (int i = 0; i < 10; i++)
{
char id[200];
sprintf(id, "test_map%d", i + 1);
TEST(modulecmd_register_command(map_dom, id, MODULECMD_TYPE_ACTIVE, test_fn_map, 0, NULL),
"Registering a command should succeed");
}
int n = 0;
TEST(modulecmd_foreach(NULL, NULL, mapfn, &n), "Mapping function should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(n >= 10, "Every registered command should be called");
n = 0;
TEST(modulecmd_foreach("test_map", NULL, mapfn, &n), "Mapping function should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(n == 10, "Every registered command should be called");
n = 0;
TEST(modulecmd_foreach(NULL, "test_map", mapfn, &n), "Mapping function should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(n == 10, "Every registered command should be called");
n = 0;
TEST(modulecmd_foreach("test_map", "test_map", mapfn, &n), "Mapping function should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(n == 10, "Every registered command should be called");
n = 0;
TEST(modulecmd_foreach("wrong domain", "test_map", mapfn, &n), "Mapping function should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(n == 0, "No registered command should be called");
n = 0;
TEST(modulecmd_foreach("test_map", "test_map[2-4]", mapfn, &n), "Mapping function should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(n == 3, "Three registered commands should be called");
n = 0;
TEST(!modulecmd_foreach("(", NULL, mapfn, &n), "Mapping function should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(n == 0, "No registered command should be called");
return 0;
}
static DCB my_dcb;
bool ptrfn(const MODULECMD_ARG *argv, json_t** output)
{
bool rval = false;
if (argv->argc == 1 && argv->argv[0].value.dcb == &my_dcb)
{
rval = true;
}
return rval;
}
int test_pointers()
{
const char *ns = "test_pointers";
const char *id = "test_pointers";
modulecmd_arg_type_t args[] =
{
{MODULECMD_ARG_DCB, ""}
};
TEST(modulecmd_register_command(ns, id, MODULECMD_TYPE_ACTIVE, ptrfn, 1, args),
"Registering a command should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
const MODULECMD *cmd = modulecmd_find_command(ns, id);
TEST(cmd, "The registered command should be found");
const void* params[] = {&my_dcb};
MODULECMD_ARG *arg = modulecmd_arg_parse(cmd, 1, params);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg, NULL), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
return 0;
}
bool monfn(const MODULECMD_ARG *arg, json_t** output)
{
return true;
}
int test_domain_matching()
{
const char *ns = "mysqlmon";
const char *id = "test_domain_matching";
modulecmd_arg_type_t args[] =
{
{MODULECMD_ARG_MONITOR | MODULECMD_ARG_NAME_MATCHES_DOMAIN, ""}
};
TEST(modulecmd_register_command(ns, id, MODULECMD_TYPE_ACTIVE, monfn, 1, args),
"Registering a command should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
const MODULECMD *cmd = modulecmd_find_command(ns, id);
TEST(cmd, "The registered command should be found");
/** Create a monitor */
char *libdir = MXS_STRDUP_A("../../modules/monitor/mysqlmon/");
set_libdir(libdir);
monitor_alloc((char*)ns, (char*)"mysqlmon");
const void* params[] = {ns};
MODULECMD_ARG *arg = modulecmd_arg_parse(cmd, 1, params);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg, NULL), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
return 0;
}
bool outputfn(const MODULECMD_ARG *arg, json_t** output)
{
json_t* obj = json_object();
json_object_set_new(obj, "hello", json_string("world"));
*output = obj;
return output != NULL;
}
int test_output()
{
const char *ns = "test_output";
const char *id = "test_output";
TEST(modulecmd_register_command(ns, id, MODULECMD_TYPE_ACTIVE, outputfn, 0, NULL),
"Registering a command should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
const MODULECMD *cmd = modulecmd_find_command(ns, id);
TEST(cmd, "The registered command should be found");
json_t* output = NULL;
TEST(modulecmd_call_command(cmd, NULL, &output), "Module call should be successful");
TEST(output, "Output should be non-NULL");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(json_is_string(mxs_json_pointer(output, "/hello")), "Value should be correct");
json_decref(output);
TEST(modulecmd_call_command(cmd, NULL, NULL), "Module call with NULL output should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
return 0;
}
int main(int argc, char **argv)
{
int rc = 0;
rc += test_arguments();
rc += test_optional_arguments();
rc += test_module_errors();
rc += test_map();
rc += test_pointers();
rc += test_domain_matching();
rc += test_output();
return rc;
}