diff --git a/pkg/lua/contrib/lua_loadlib.c b/pkg/lua/contrib/lua_loadlib.c index cedc2d1938..b746044a52 100644 --- a/pkg/lua/contrib/lua_loadlib.c +++ b/pkg/lua/contrib/lua_loadlib.c @@ -42,12 +42,17 @@ /* ======================== 'searchers' functions =========================== */ +/* A null address for table_len means the weak symbol was not overridden */ +#define _SEARCH_BUILTINS(table, len, sname) \ + ((&(len) == NULL)? NULL : BINSEARCH_STR_P((table), (len), name, (sname), \ + LUAR_MAX_MODULE_NAME)) + static int _ll_searcher_builtin_lua(lua_State *L, const char *name) { const struct lua_riot_builtin_lua *lmodule = - BINSEARCH_STR_P(lua_riot_builtin_lua_table, - lua_riot_builtin_lua_table_len, - name, name, LUAR_MAX_MODULE_NAME); + _SEARCH_BUILTINS(lua_riot_builtin_lua_table, + lua_riot_builtin_lua_table_len, + name); if (lmodule != NULL) { int load_result = luaL_loadbuffer(L, (const char *)lmodule->code, @@ -79,8 +84,8 @@ static int searcher_builtin_lua(lua_State *L) case LUA_OK: return 2; /* there are two elements in the stack */ case LUAR_MODULE_NOTFOUND: - return luaL_error(L, "Module '%s' not found in Lua-builtins", - lua_tostring(L, 1)); + lua_pushliteral(L, "\n\tModule not found in Lua-builtins"); + return 1; default: return luaL_error(L, "error loading module '%s' from Lua-builtins: \n%s", lua_tostring(L, 1), lua_tostring(L, 2)); @@ -90,9 +95,9 @@ static int searcher_builtin_lua(lua_State *L) static int _ll_searcher_builtin_c(lua_State *L, const char *name) { const struct lua_riot_builtin_c *cmodule = - BINSEARCH_STR_P(lua_riot_builtin_c_table, - lua_riot_builtin_c_table_len, - name, name, LUAR_MAX_MODULE_NAME); + _SEARCH_BUILTINS(lua_riot_builtin_c_table, + lua_riot_builtin_c_table_len, + name); if (cmodule != NULL) { lua_pushcfunction(L, cmodule->luaopen); @@ -119,8 +124,8 @@ static int searcher_builtin_c(lua_State *L) return 2; } else { - return luaL_error(L, "Module '%s' not found in C-builtins", - lua_tostring(L, 1)); + lua_pushliteral(L, "\n\tModule not found in C-builtins"); + return 1; } } diff --git a/tests/lua_loader/Makefile b/tests/lua_loader/Makefile new file mode 100644 index 0000000000..8c1b66f7b6 --- /dev/null +++ b/tests/lua_loader/Makefile @@ -0,0 +1,16 @@ +include ../Makefile.tests_common + +USEPKG += lua + +BOARD_WHITELIST += native samr21-xpro + +ifneq ($(BOARD),native) + # This stack size is large enough to run Lua print() functions of + # various lengths. Other functions untested. + CFLAGS += -DTHREAD_STACKSIZE_MAIN='(THREAD_STACKSIZE_DEFAULT+2048)' +endif + +include $(RIOTBASE)/Makefile.include + +test: + tests/01-run.py diff --git a/tests/lua_loader/README b/tests/lua_loader/README new file mode 100644 index 0000000000..3996871263 --- /dev/null +++ b/tests/lua_loader/README @@ -0,0 +1,8 @@ +Test modified LUA module loader +=============================== + +This test defines a bunch of dummy modules, both in +pure lua and in C. The way modules are declares is not "nice" +in the sense that it is not the way it would be done in a big +application. The goal is to test the module loader code, and not +the build tooling. diff --git a/tests/lua_loader/cmodules.c b/tests/lua_loader/cmodules.c new file mode 100644 index 0000000000..7088d22a53 --- /dev/null +++ b/tests/lua_loader/cmodules.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @brief Define a couple of dummy lua extension modules + * + * @author Juan Carrano + * + * Normally one would not define more than one module in a single file, but + * these modules are exacly the same: a single table with an integer value. + * + * @} + */ + +#define LUA_LIB + +#include "lprefix.h" + +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" + +static const luaL_Reg funcs[] = { + /* placeholder */ + { "X", NULL }, + { NULL, NULL } +}; + +static int _luaopen_X(lua_State *L, const char *xstring) +{ + luaL_newlib(L, funcs); + + lua_pushstring(L, xstring); + lua_setfield(L, -2, "X"); + + return 1; +} + +int _luaopen_hello(lua_State *L) +{ + return _luaopen_X(L, "E se deixa no céu,"); +} + +int _luaopen_world(lua_State *L) +{ + return _luaopen_X(L, "como esquecida"); +} diff --git a/tests/lua_loader/main.c b/tests/lua_loader/main.c new file mode 100644 index 0000000000..5330daf330 --- /dev/null +++ b/tests/lua_loader/main.c @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2019 Freie Universität Berlin. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @brief Test custom lua loader ("require" module) + * + * @author Juan Carrano + * + * This application defines some pure-lua and some C-extension modules. + * First it tests running a module as a script (lua_riot_do_module). + * + * Then it goes into a loop reading single lines of input from stdin and + * executing them as lua source. The test script can use that to test module + * loading. + * + * @} + */ + +#include +#include +#include + +#include "lua_run.h" +#include "lua_builtin.h" + +static const uint8_t pure_module_1[] = "\n\ +return {a='Quando uma lua'}\n\ +"; + +static const uint8_t pure_module_2[] = "\n\ +return {a='chega de repente'}\n\ +"; + +static const uint8_t test_script[] = "\n\ +print'I am a module, hi!'\n\ +"; + +/* The -1 is because the final '\0' is not part of the source code that should + * be read by lua. + */ +const struct lua_riot_builtin_lua _lua_riot_builtin_lua_table[] = { + { "m1", pure_module_1, sizeof(pure_module_1) - 1 }, + { "m2", pure_module_2, sizeof(pure_module_2) - 1 }, + { "test", test_script, sizeof(test_script) - 1 } +}; + +extern int _luaopen_hello(lua_State *L); +extern int _luaopen_world(lua_State *L); + +const struct lua_riot_builtin_c _lua_riot_builtin_c_table[] = { + { "c1", _luaopen_hello }, + { "c2", _luaopen_world } +}; + +const struct lua_riot_builtin_lua *const lua_riot_builtin_lua_table = _lua_riot_builtin_lua_table; +const struct lua_riot_builtin_c *const lua_riot_builtin_c_table = _lua_riot_builtin_c_table; + +const size_t lua_riot_builtin_lua_table_len = + sizeof(_lua_riot_builtin_lua_table) / sizeof(*_lua_riot_builtin_lua_table); +const size_t lua_riot_builtin_c_table_len = + sizeof(_lua_riot_builtin_c_table) / sizeof(*_lua_riot_builtin_c_table); + +#define LUA_MEM_SIZE (11000) +static char lua_mem[LUA_MEM_SIZE] __attribute__ ((aligned(__BIGGEST_ALIGNMENT__))); + +#define LINEBUF_SZ (32) +static char linebuf[LINEBUF_SZ]; + +int main(void) +{ + int status; + + status = lua_riot_do_module("test", lua_mem, LUA_MEM_SIZE, + LUAR_LOAD_BASE, NULL); + + assert(status == LUAR_EXIT); + + while (fgets(linebuf, LINEBUF_SZ, stdin) != NULL) { + int status; + size_t linelen = strlen(linebuf); + + if (!linelen) { + continue; + } + else if (linebuf[linelen - 1] != '\n') { + puts("[ERROR] could not read a complete line"); + break; + } + + status = lua_riot_do_buffer((unsigned char *)linebuf, linelen, + lua_mem, LUA_MEM_SIZE, + LUAR_LOAD_BASE | LUAR_LOAD_PACKAGE, NULL); + assert(status == LUAR_EXIT); + } + + return 0; +} diff --git a/tests/lua_loader/tests/01-run.py b/tests/lua_loader/tests/01-run.py new file mode 100755 index 0000000000..44b7e104c3 --- /dev/null +++ b/tests/lua_loader/tests/01-run.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2019 Freie Universität Berlin +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +# Tell the lua interpreter running in riot to load some modules and print +# the value of a variable inside that module. + +import os +import sys + +MODULE_QUERIES = [ + ("m1", "a", "Quando uma lua"), + ("m2", "a", "chega de repente"), + ("c1", "X", "E se deixa no céu,"), + ("c2", "X", "como esquecida"), +] + + +def test(child): + # check startup message + child.expect_exact('I am a module, hi!') + + # loop other defined commands and expected output + for mod, attr, val in MODULE_QUERIES: + child.sendline('print((require"{}").{})'.format(mod, attr)) + child.expect_exact(val) + + +if __name__ == "__main__": + sys.path.append(os.path.join(os.environ['RIOTTOOLS'], 'testrunner')) + from testrunner import run + sys.exit(run(test))