diff --git a/src/script_lua.c b/src/script_lua.c index 2873159d4..82591d3fc 100644 --- a/src/script_lua.c +++ b/src/script_lua.c @@ -432,7 +432,7 @@ static void redisProtocolToLuaType_Double(void *ctx, double d, const char *proto * table is never a valid reply by proper commands, since the returned * tables are otherwise always indexed by integers, never by strings. */ void luaPushError(lua_State *lua, char *error) { - lua_Debug dbg; + sds msg; /* If debugging is active and in step mode, log errors resulting from * Redis commands. */ @@ -443,15 +443,21 @@ void luaPushError(lua_State *lua, char *error) { lua_newtable(lua); lua_pushstring(lua,"err"); - /* Attempt to figure out where this function was called, if possible */ - if(lua_getstack(lua, 1, &dbg) && lua_getinfo(lua, "nSl", &dbg)) { - sds msg = sdscatprintf(sdsempty(), "%s: %d: %s", - dbg.source, dbg.currentline, error); - lua_pushstring(lua, msg); - sdsfree(msg); - } else { - lua_pushstring(lua, error); - } + /* There are two possible formats for the received `error` string: + * 1) "-CODE msg": in this case we remove the leading '-' since we don't store it as part of the lua error format. + * 2) "msg": in this case we prepend a generic 'ERR' code since all error statuses need some error code. + * We support format (1) so this function can reuse the error messages used in other places in redis. + * We support format (2) so it'll be easy to pass descriptive errors to this function without worrying about format. + */ + if (error[0] == '-') + msg = sdsnew(error+1); + else + msg = sdscatprintf(sdsempty(), "ERR %s", error); + /* Trim newline at end of string. If we reuse the ready-made Redis error objects (case 1 above) then we might + * have a newline that needs to be trimmed. In any case the lua Redis error table shouldn't end with a newline. */ + msg = sdstrim(msg, "\r\n"); + lua_pushstring(lua, msg); + sdsfree(msg); lua_settable(lua,-3); } diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl index 93db6d071..cfbe60faf 100644 --- a/tests/unit/scripting.tcl +++ b/tests/unit/scripting.tcl @@ -1400,3 +1400,75 @@ start_server {tags {"scripting"}} { set _ {} } {} {external:skip} } + +# Additional eval only tests +start_server {tags {"scripting"}} { + test "Consistent eval error reporting" { + r config set maxmemory 1 + # Script aborted due to Redis state (OOM) should report script execution error with detailed internal error + assert_error {ERR Error running script (call to *): @user_script:*: OOM command not allowed when used memory > 'maxmemory'.} { + r eval {return redis.call('set','x','y')} 1 x + } + # redis.pcall() failure due to Redis state (OOM) returns lua error table with Redis error message without '-' prefix + assert_equal [ + r eval { + local t = redis.pcall('set','x','y') + if t['err'] == "OOM command not allowed when used memory > 'maxmemory'." then + return 1 + else + return 0 + end + } 1 x + ] 1 + # Returning an error object from lua is handled as a valid RESP error result. + assert_error {OOM command not allowed when used memory > 'maxmemory'.} { + r eval { return redis.pcall('set','x','y') } 1 x + } + r config set maxmemory 0 + # Script aborted due to error result of Redis command + assert_error {ERR Error running script (call to *): @user_script:*: ERR DB index is out of range} { + r eval {return redis.call('select',99)} 0 + } + # redis.pcall() failure due to error in Redis command returns lua error table with redis error message without '-' prefix + assert_equal [ + r eval { + local t = redis.pcall('select',99) + if t['err'] == "ERR DB index is out of range" then + return 1 + else + return 0 + end + } 0 + ] 1 + # Script aborted due to scripting specific error state (write cmd with eval_ro) should report script execution error with detailed internal error + assert_error {ERR Error running script (call to *): @user_script:*: ERR Write commands are not allowed from read-only scripts.} { + r eval_ro {return redis.call('set','x','y')} 1 x + } + # redis.pcall() failure due to scripting specific error state (write cmd with eval_ro) returns lua error table with Redis error message without '-' prefix + assert_equal [ + r eval_ro { + local t = redis.pcall('set','x','y') + if t['err'] == "ERR Write commands are not allowed from read-only scripts." then + return 1 + else + return 0 + end + } 1 x + ] 1 + } {} {cluster:skip} + + test "LUA redis.error_reply API" { + assert_error {MY_ERR_CODE custom msg} { + r eval {return redis.error_reply("MY_ERR_CODE custom msg")} 0 + } + } + + test "LUA redis.status_reply API" { + r readraw 1 + assert_equal [ + r eval {return redis.status_reply("MY_OK_CODE custom msg")} 0 + ] {+MY_OK_CODE custom msg} + r readraw 0 + } +} +