[Tarantool-patches] [PATCH 4/4] test: add a basic unit test for serializer helpers
Alexander Turenko
alexander.turenko at tarantool.org
Wed Jun 23 22:12:42 MSK 2021
The main reason of writting this test is to learn how those serializer
helpers work. It also may be a good base for future test cases.
Part of #3228
---
test/unit/CMakeLists.txt | 3 +
test/unit/serializer.c | 444 ++++++++++++++++++++++++++++++++++++
test/unit/serializer.result | 135 +++++++++++
3 files changed, 582 insertions(+)
create mode 100644 test/unit/serializer.c
create mode 100644 test/unit/serializer.result
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 8df77bac3..5bb7cd6e7 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -275,3 +275,6 @@ add_executable(popen-child popen-child.c)
add_executable(popen.test popen.c core_test_utils.c)
target_link_libraries(popen.test misc unit core)
+
+add_executable(serializer.test serializer.c)
+target_link_libraries(serializer.test unit box ${LUAJIT_LIBRARIES})
diff --git a/test/unit/serializer.c b/test/unit/serializer.c
new file mode 100644
index 000000000..bca239fa8
--- /dev/null
+++ b/test/unit/serializer.c
@@ -0,0 +1,444 @@
+#include <lua.h> /* lua_*() */
+#include <lauxlib.h> /* luaL_*() */
+#include <lualib.h> /* luaL_openlibs() */
+#include "unit.h" /* plan, header, footer, is, ok */
+#include "lua/serializer.h" /* functions to test */
+#include "mp_extension_types.h" /* enum mp_extension_type */
+
+static int
+check_luaL_field(const struct luaL_field *field, const struct luaL_field *exp,
+ const char *description)
+{
+ plan(4);
+ header();
+
+ is(field->type, exp->type, "%s: type", description);
+
+ /* More types may be added on demand. */
+ switch (exp->type) {
+ case MP_STR: {
+ /*
+ * Don't compare string values for equality: check
+ * whether actual result contains the expected
+ * pattern at beginning. It is just to simplify
+ * writting of test cases.
+ */
+ int rc = strstr(field->sval.data, exp->sval.data) ==
+ field->sval.data;
+ ok(rc, "%s: sval.data", description);
+ /* Don't compare 'sval.len'. */
+ ok(true, "# skip; %s: Don't compare 'exp_type'", description);
+ ok(true, "# skip; %s: Don't compare 'compact'", description);
+ break;
+ }
+ case MP_ARRAY:
+ case MP_MAP:
+ is(field->size, exp->size, "%s: size", description);
+ ok(true, "# skip; %s: Don't compare 'exp_type'", description);
+ is(field->compact, exp->compact, "%s: compact", description);
+ break;
+ case MP_EXT:
+ ok(true, "# skip; %s: Don't check MP_EXT data", description);
+ is(field->ext_type, exp->ext_type, "%s: ext_type", description);
+ ok(true, "# skip; %s: Don't compare 'compact'", description);
+ break;
+ default:
+ assert(false);
+ }
+
+ footer();
+ return check_plan();
+}
+
+static int
+test_luaL_field_basic(struct lua_State *L)
+{
+ struct {
+ /* A string to output with a test case. */
+ const char *description;
+ /* A code to push a Lua object to inspect. */
+ const char *src;
+ /*
+ * Whether to call and verify luaL_checkfield()
+ * instead of luaL_tofield() (the default is
+ * luaL_tofield()).
+ */
+ bool invoke_checkfield;
+ /*
+ * Expected <struct luaL_field> after call to
+ * luaL_tofield() / luaL_checkfield().
+ *
+ * For MP_STR: interpret .sval.data as beginning
+ * of the actual string (for testing purposes:
+ * tostring() representation of userdata and cdata
+ * contains an address at the end). Don't compare
+ * .sval.len.
+ */
+ struct luaL_field exp_field;
+ /*
+ * A code to check the resulting value on the Lua
+ * stack after luaL_tofield() / luaL_checkfield().
+ *
+ * 'res' is the object to verify.
+ *
+ * 'src' contains the source object.
+ *
+ * Use 'cmp(a, b)' for a deep comparison.
+ */
+ const char *check_lua;
+ } cases[] = {
+ {
+ .description = "table as array",
+ .src = "return {1, 2, 3}",
+ .exp_field = {
+ .size = 3,
+ .type = MP_ARRAY,
+ .compact = false,
+ },
+ .check_lua = "return res == src",
+ },
+ {
+ .description = "table as map",
+ .src = "return {foo = 'bar'}",
+ .exp_field = {
+ .size = 1,
+ .type = MP_MAP,
+ .compact = false,
+ },
+ .check_lua = "return res == src",
+ },
+ {
+ .description = "table with __serialize = 'map'",
+ .src = "return setmetatable({1, 2, 3}, {"
+ " __serialize = 'map'"
+ "})",
+ .exp_field = {
+ .size = 3,
+ .type = MP_MAP,
+ .compact = true,
+ },
+ .check_lua = "return res == src",
+ },
+ {
+ .description = "table with __serialize function",
+ .src = "return setmetatable({foo = 'bar'}, {"
+ " __serialize = function(self)"
+ " return {1, 2, 3}"
+ " end"
+ "})",
+ .exp_field = {
+ .size = 3,
+ .type = MP_ARRAY,
+ .compact = false,
+ },
+ .check_lua = "return cmp(res, {1, 2, 3})",
+ },
+ {
+ .description = "unknown userdata",
+ .src = "return newproxy()",
+ .exp_field = {
+ .type = MP_EXT,
+ .ext_type = MP_UNKNOWN_EXTENSION,
+ },
+ .check_lua = "return type(res) == 'userdata' and"
+ " res == src",
+ },
+ {
+ .description = "unknown userdata (checkfield)",
+ .src = "return newproxy()",
+ .invoke_checkfield = true,
+ .exp_field = {
+ .sval = {
+ .data = "userdata: 0x",
+ },
+ .type = MP_STR,
+ },
+ .check_lua = "return type(res) == 'string' and"
+ " res:match('^userdata: ')",
+ },
+ {
+ .description = "userdata with __serialize function",
+ .src = "do"
+ " local ud = newproxy(true)"
+ " local mt = getmetatable(ud)"
+ " mt.__serialize = function(self)"
+ " return {1, 2, 3}"
+ " end"
+ /*
+ * See the comment in the next test
+ * case.
+ */
+ " mt.__index = mt"
+ /* Keep the trailing whitespace. */
+ " return ud "
+ "end",
+ .exp_field = {
+ .type = MP_EXT,
+ .ext_type = MP_UNKNOWN_EXTENSION,
+ },
+ .check_lua = "return type(res) == 'userdata' and"
+ " res == src",
+ },
+ {
+ .description = "userdata with __serialize function "
+ "(checkfield)",
+ .src = "do"
+ " local ud = newproxy(true)"
+ " local mt = getmetatable(ud)"
+ " mt.__serialize = function(self)"
+ " return {1, 2, 3}"
+ " end"
+ /*
+ * Surprise! __serialize should be
+ * accessible via lua_gettable(),
+ * otherwise our serializer will
+ * not recognize it. So we should
+ * set __index to the metatable
+ * itself.
+ *
+ * luaL_register_type() does it.
+ * However, personally, I don't see
+ * a strict reason for that and for
+ * me it looks hacky. It would be
+ * better to teach the serializer
+ * to inspect the metatable
+ * directly.
+ */
+ " mt.__index = mt"
+ /* Keep the trailing whitespace. */
+ " return ud "
+ "end",
+ .invoke_checkfield = true,
+ .exp_field = {
+ .size = 3,
+ .type = MP_ARRAY,
+ },
+ .check_lua = "return cmp(res, {1, 2, 3})",
+ },
+ {
+ .description = "unknown cdata",
+ .src = "do"
+ " local ffi = require('ffi')"
+ " ffi.cdef([["
+ " struct foo {"
+ " int x;"
+ " };"
+ " ]])"
+ /* Keep the trailing whitespace. */
+ " return ffi.new('struct foo', {x = 42}) "
+ "end",
+ .exp_field = {
+ .type = MP_EXT,
+ .ext_type = MP_UNKNOWN_EXTENSION,
+ },
+ .check_lua = "do"
+ " local ffi = require('ffi')"
+ " return type(res) == 'cdata' and"
+ " ffi.istype('struct foo', res) and"
+ " res == src and"
+ /* Keep the trailing whitespace. */
+ " res.x == 42 "
+ "end",
+ },
+ {
+ .description = "unknown cdata (checkfield)",
+ .src = "do"
+ " local ffi = require('ffi')"
+ /*
+ * ffi.cdef() is in the previous
+ * test case.
+ */
+ /* Keep the trailing whitespace. */
+ " return ffi.new('struct foo', {x = 42}) "
+ "end",
+ .invoke_checkfield = true,
+ .exp_field = {
+ .sval = {
+ .data = "cdata<struct foo>: 0x"
+ },
+ .type = MP_STR,
+ },
+ .check_lua = "return type(res) == 'string' and"
+ " res:match('^cdata<struct foo>: ')",
+ },
+ {
+ .description = "cdata with __serialize",
+ .src = "do"
+ " local ffi = require('ffi')"
+ " local mt = {"
+ " __serialize = function(self)"
+ " return {1, 2, 3}"
+ " end"
+ " }"
+ /*
+ * See the comment for the userdata
+ * test case above.
+ */
+ " mt.__index = mt"
+ /*
+ * ffi.cdef() is in one of previous
+ * test cases.
+ */
+ " ffi.metatype('struct foo', mt)"
+ /* Keep the trailing whitespace. */
+ " return ffi.new('struct foo', {x = 42}) "
+ "end",
+ .exp_field = {
+ .type = MP_EXT,
+ .ext_type = MP_UNKNOWN_EXTENSION,
+ },
+ .check_lua = "do"
+ " local ffi = require('ffi')"
+ " return type(res) == 'cdata' and"
+ " ffi.istype('struct foo', res) and"
+ " res == src and"
+ /* Keep the trailing whitespace. */
+ " res.x == 42 "
+ "end",
+ },
+ {
+ .description = "cdata with __serialize (checkfield)",
+ .src = "do"
+ " local ffi = require('ffi')"
+ /*
+ * ffi.cdef() and ffi.metatype()
+ * are in previous test cases.
+ */
+ /* Keep the trailing whitespace. */
+ " return ffi.new('struct foo', {x = 42}) "
+ "end",
+ .invoke_checkfield = true,
+ .exp_field = {
+ .size = 3,
+ .type = MP_ARRAY,
+ },
+ .check_lua = "return cmp(res, {1, 2, 3})",
+ },
+ };
+
+ int planned = (int) (sizeof(cases) / sizeof(cases[0]));
+ plan(4 * planned);
+ header();
+
+ /*
+ * Initialize serializer with almost default options.
+ *
+ * Set 'has_compact' to test it (otherwise
+ * <struct luaL_field>.compact will not be set).
+ *
+ * Set 'encode_use_tostring' just to don't introduce
+ * complex code with catching a Lua error from a C
+ * function.
+ */
+ struct luaL_serializer cfg;
+ luaL_serializer_create(&cfg);
+ cfg.has_compact = true;
+ cfg.encode_use_tostring = true;
+
+ /* Add cmp function for check_lua. */
+ luaL_loadstring(L,
+ "do"
+ " local cmp"
+ ""
+ " cmp = function(a, b)"
+ " if type(a) ~= type(b) then"
+ " return false"
+ " end"
+ ""
+ " if type(a) == 'table' then"
+ " for k, v in pairs(a) do"
+ " if not cmp(v, b[k]) then"
+ " return false"
+ " end"
+ " end"
+ " for k, v in pairs(b) do"
+ " if not cmp(v, a[k]) then"
+ " return false"
+ " end"
+ " end"
+ " return true"
+ " end"
+ ""
+ " return a == b"
+ " end"
+ ""
+ /* Caution: keep the trailing whitespace. */
+ " return cmp "
+ "end");
+ lua_call(L, 0, 1);
+ lua_setglobal(L, "cmp");
+
+ for (int i = 0; i < planned; ++i) {
+ int initial_top = lua_gettop(L);
+
+ const char *description = cases[i].description;
+
+ /* Push a Lua object to the Lua stack. */
+ luaL_loadstring(L, cases[i].src);
+ lua_call(L, 0, 1);
+
+ /* Set _G.src. */
+ lua_pushvalue(L, -1);
+ lua_setglobal(L, "src");
+
+ /* Call luaL_tofield() / luaL_checkfield(). */
+ int top = lua_gettop(L);
+ struct luaL_field field;
+ if (cases[i].invoke_checkfield) {
+ luaL_checkfield(L, &cfg, -1, &field);
+ ok(true, "# skip; %s: luaL_checkfield() has no retval",
+ description);
+ } else {
+ int rc = luaL_tofield(L, &cfg, NULL, -1, &field);
+ is(rc, 0, "%s: luaL_tofield retval", description);
+ }
+
+ /* Check stack size. */
+ is(lua_gettop(L) - top, 0, "%s: Lua stack size", description);
+
+ /*
+ * Set _G.res. The object is placed to the same
+ * index as the source object: the top item in our
+ * case.
+ */
+ lua_pushvalue(L, -1);
+ lua_setglobal(L, "res");
+
+ /* Check resulting Lua object. */
+ luaL_loadstring(L, cases[i].check_lua);
+ lua_call(L, 0, 1);
+ is(lua_toboolean(L, -1), 1, "%s: Lua result", description);
+ lua_pop(L, 1);
+
+ /* Check luaL_field content. */
+ check_luaL_field(&field, &cases[i].exp_field, description);
+
+ /* Unset _G.src and _G.res. */
+ lua_pushnil(L);
+ lua_setglobal(L, "src");
+ lua_pushnil(L);
+ lua_setglobal(L, "res");
+
+ /* Clean up the Lua stack. */
+ lua_pop(L, 1);
+ assert(lua_gettop(L) == initial_top);
+ }
+
+ /* Unset _G.cmp. */
+ lua_pushnil(L);
+ lua_setglobal(L, "cmp");
+
+ footer();
+ return check_plan();
+}
+
+int
+main()
+{
+ struct lua_State *L = luaL_newstate();
+ luaL_openlibs(L);
+
+ tarantool_lua_serializer_init(L);
+
+ return test_luaL_field_basic(L);
+}
diff --git a/test/unit/serializer.result b/test/unit/serializer.result
new file mode 100644
index 000000000..74479ac49
--- /dev/null
+++ b/test/unit/serializer.result
@@ -0,0 +1,135 @@
+1..48
+ *** test_luaL_field_basic ***
+ok 1 - table as array: luaL_tofield retval
+ok 2 - table as array: Lua stack size
+ok 3 - table as array: Lua result
+ 1..4
+ *** check_luaL_field ***
+ ok 1 - table as array: type
+ ok 2 - table as array: size
+ ok 3 - # skip; table as array: Don't compare 'exp_type'
+ ok 4 - table as array: compact
+ *** check_luaL_field: done ***
+ok 4 - subtests
+ok 5 - table as map: luaL_tofield retval
+ok 6 - table as map: Lua stack size
+ok 7 - table as map: Lua result
+ 1..4
+ *** check_luaL_field ***
+ ok 1 - table as map: type
+ ok 2 - table as map: size
+ ok 3 - # skip; table as map: Don't compare 'exp_type'
+ ok 4 - table as map: compact
+ *** check_luaL_field: done ***
+ok 8 - subtests
+ok 9 - table with __serialize = 'map': luaL_tofield retval
+ok 10 - table with __serialize = 'map': Lua stack size
+ok 11 - table with __serialize = 'map': Lua result
+ 1..4
+ *** check_luaL_field ***
+ ok 1 - table with __serialize = 'map': type
+ ok 2 - table with __serialize = 'map': size
+ ok 3 - # skip; table with __serialize = 'map': Don't compare 'exp_type'
+ ok 4 - table with __serialize = 'map': compact
+ *** check_luaL_field: done ***
+ok 12 - subtests
+ok 13 - table with __serialize function: luaL_tofield retval
+ok 14 - table with __serialize function: Lua stack size
+ok 15 - table with __serialize function: Lua result
+ 1..4
+ *** check_luaL_field ***
+ ok 1 - table with __serialize function: type
+ ok 2 - table with __serialize function: size
+ ok 3 - # skip; table with __serialize function: Don't compare 'exp_type'
+ ok 4 - table with __serialize function: compact
+ *** check_luaL_field: done ***
+ok 16 - subtests
+ok 17 - unknown userdata: luaL_tofield retval
+ok 18 - unknown userdata: Lua stack size
+ok 19 - unknown userdata: Lua result
+ 1..4
+ *** check_luaL_field ***
+ ok 1 - unknown userdata: type
+ ok 2 - # skip; unknown userdata: Don't check MP_EXT data
+ ok 3 - unknown userdata: ext_type
+ ok 4 - # skip; unknown userdata: Don't compare 'compact'
+ *** check_luaL_field: done ***
+ok 20 - subtests
+ok 21 - # skip; unknown userdata (checkfield): luaL_checkfield() has no retval
+ok 22 - unknown userdata (checkfield): Lua stack size
+ok 23 - unknown userdata (checkfield): Lua result
+ 1..4
+ *** check_luaL_field ***
+ ok 1 - unknown userdata (checkfield): type
+ ok 2 - unknown userdata (checkfield): sval.data
+ ok 3 - # skip; unknown userdata (checkfield): Don't compare 'exp_type'
+ ok 4 - # skip; unknown userdata (checkfield): Don't compare 'compact'
+ *** check_luaL_field: done ***
+ok 24 - subtests
+ok 25 - userdata with __serialize function: luaL_tofield retval
+ok 26 - userdata with __serialize function: Lua stack size
+ok 27 - userdata with __serialize function: Lua result
+ 1..4
+ *** check_luaL_field ***
+ ok 1 - userdata with __serialize function: type
+ ok 2 - # skip; userdata with __serialize function: Don't check MP_EXT data
+ ok 3 - userdata with __serialize function: ext_type
+ ok 4 - # skip; userdata with __serialize function: Don't compare 'compact'
+ *** check_luaL_field: done ***
+ok 28 - subtests
+ok 29 - # skip; userdata with __serialize function (checkfield): luaL_checkfield() has no retval
+ok 30 - userdata with __serialize function (checkfield): Lua stack size
+ok 31 - userdata with __serialize function (checkfield): Lua result
+ 1..4
+ *** check_luaL_field ***
+ ok 1 - userdata with __serialize function (checkfield): type
+ ok 2 - userdata with __serialize function (checkfield): size
+ ok 3 - # skip; userdata with __serialize function (checkfield): Don't compare 'exp_type'
+ ok 4 - userdata with __serialize function (checkfield): compact
+ *** check_luaL_field: done ***
+ok 32 - subtests
+ok 33 - unknown cdata: luaL_tofield retval
+ok 34 - unknown cdata: Lua stack size
+ok 35 - unknown cdata: Lua result
+ 1..4
+ *** check_luaL_field ***
+ ok 1 - unknown cdata: type
+ ok 2 - # skip; unknown cdata: Don't check MP_EXT data
+ ok 3 - unknown cdata: ext_type
+ ok 4 - # skip; unknown cdata: Don't compare 'compact'
+ *** check_luaL_field: done ***
+ok 36 - subtests
+ok 37 - # skip; unknown cdata (checkfield): luaL_checkfield() has no retval
+ok 38 - unknown cdata (checkfield): Lua stack size
+ok 39 - unknown cdata (checkfield): Lua result
+ 1..4
+ *** check_luaL_field ***
+ ok 1 - unknown cdata (checkfield): type
+ ok 2 - unknown cdata (checkfield): sval.data
+ ok 3 - # skip; unknown cdata (checkfield): Don't compare 'exp_type'
+ ok 4 - # skip; unknown cdata (checkfield): Don't compare 'compact'
+ *** check_luaL_field: done ***
+ok 40 - subtests
+ok 41 - cdata with __serialize: luaL_tofield retval
+ok 42 - cdata with __serialize: Lua stack size
+ok 43 - cdata with __serialize: Lua result
+ 1..4
+ *** check_luaL_field ***
+ ok 1 - cdata with __serialize: type
+ ok 2 - # skip; cdata with __serialize: Don't check MP_EXT data
+ ok 3 - cdata with __serialize: ext_type
+ ok 4 - # skip; cdata with __serialize: Don't compare 'compact'
+ *** check_luaL_field: done ***
+ok 44 - subtests
+ok 45 - # skip; cdata with __serialize (checkfield): luaL_checkfield() has no retval
+ok 46 - cdata with __serialize (checkfield): Lua stack size
+ok 47 - cdata with __serialize (checkfield): Lua result
+ 1..4
+ *** check_luaL_field ***
+ ok 1 - cdata with __serialize (checkfield): type
+ ok 2 - cdata with __serialize (checkfield): size
+ ok 3 - # skip; cdata with __serialize (checkfield): Don't compare 'exp_type'
+ ok 4 - cdata with __serialize (checkfield): compact
+ *** check_luaL_field: done ***
+ok 48 - subtests
+ *** test_luaL_field_basic: done ***
--
2.31.1
More information about the Tarantool-patches
mailing list