[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