set testmodule [file normalize tests/modules/helloscripting.so] set HELLO_PROGRAM "#!hello name=mylib\nRFUNCTION foo\nARGS 0\nRETURN\nFUNCTION bar\nCONSTI 432\nRETURN" start_server {tags {"modules"}} { r module load $testmodule r function load $HELLO_PROGRAM test {Load script with invalid library name} { assert_error {ERR Library names can only contain letters, numbers, or underscores(_) and must be at least one character long} {r function load "#!hello name=my-lib\nFUNCTION foo\nARGS 0\nRETURN"} } test {Load script with existing library} { assert_error {ERR Library 'mylib' already exists} {r function load $HELLO_PROGRAM} } test {Load script with invalid engine} { assert_error {ERR Engine 'wasm' not found} {r function load "#!wasm name=mylib2\nFUNCTION foo\nARGS 0\nRETURN"} } test {Load script with no functions} { assert_error {ERR No functions registered} {r function load "#!hello name=mylib2\n"} } test {Load script with duplicate function} { assert_error {ERR Function foo already exists} {r function load "#!hello name=mylib2\nFUNCTION foo\nARGS 0\nRETURN"} } test {Load script with no metadata header} { assert_error {ERR Missing library metadata} {r function load "FUNCTION foo\nARGS 0\nRETURN"} } test {Load script with header without lib name} { assert_error {ERR Library name was not given} {r function load "#!hello \n"} } test {Load script with header with unknown param} { assert_error {ERR Invalid metadata value given: nme=mylib} {r function load "#!hello nme=mylib\n"} } test {Load script with header with lib name passed twice} { assert_error {ERR Invalid metadata value, name argument was given multiple times} {r function load "#!hello name=mylib2 name=mylib3\n"} } test {Load script with invalid function name} { assert_error {ERR Function names can only contain letters, numbers, or underscores(_) and must be at least one character long} {r function load "#!hello name=mylib2\nFUNCTION foo-bar\nARGS 0\nRETURN"} } test {Load script with duplicate function} { assert_error {ERR Function already exists in the library} {r function load "#!hello name=mylib2\nFUNCTION foo\nARGS 0\nRETURN\nFUNCTION foo\nARGS 0\nRETURN"} } test {Load script with syntax error} { assert_error {ERR Failed to parse instruction: 'SEND'} {r function load replace "#!hello name=mylib3\nFUNCTION foo\nARGS 0\nSEND"} } test {Call scripting engine function: calling foo works} { r fcall foo 0 134 } {134} test {Call scripting engine function: calling bar works} { r fcall bar 0 } {432} test {Call server command from script} { set result [r eval {#!hello FUNCTION callcmd CONSTS x ARGS 0 CONSTI 2 CALL SET RETURN } 0 43] assert_equal $result "OK" assert_equal [r GET x] 43 set result [r eval {#!hello FUNCTION callcmd CONSTS hello CONSTI 1 CALL PING RETURN } 0] assert_equal $result "hello" set result [r eval {#!hello FUNCTION callcmd CONSTI 0 CALL PING RETURN } 0] assert_equal $result "PONG" } test {Call server NOSCRIPT command} { assert_error {ERR command 'acl|cat' is not allowed on script mode} { r eval {#!hello FUNCTION callcmd CONSTS CAT CONSTI 1 CALL ACL RETURN } 0 } assert_error {ERR This Valkey command is not allowed from script*} { r eval {#!lua return server.call('ACL', 'CAT') } 0 } r debug set-disable-deny-scripts 1 set result [r eval {#!hello FUNCTION callcmd CONSTS CAT CONSTI 1 CALL ACL RETURN } 0] assert_equal $result "OK" r eval {#!lua return server.call('ACL', 'CAT') } 0 r debug set-disable-deny-scripts 0 } test {Call server command without permission} { r acl setuser default -set r ACL LOG RESET assert_error {NOPERM User default has no permissions *} {r set x 5} assert_error {NOPERM User default has no permissions *} { r eval {#!hello FUNCTION callcmd CONSTS x ARGS 0 CONSTI 2 CALL SET RETURN } 0 43 } assert_error {ERR ACL failure in script*} { r eval {#!lua return server.call('SET', 'x', 5) } 0 } # verify ACL LOG entries set entries [r ACL LOG] assert_equal [llength $entries] 3 set entry [lindex $entries 0] assert_equal [dict get $entry username] {default} assert_equal [dict get $entry context] {lua} assert_equal [dict get $entry object] {set} assert_equal [dict get $entry reason] {command} assert_match {*cmd=eval*} [dict get $entry client-info] set entry [lindex $entries 1] assert_equal [dict get $entry username] {default} assert_equal [dict get $entry context] {script} assert_equal [dict get $entry object] {set} assert_equal [dict get $entry reason] {command} assert_match {*cmd=eval*} [dict get $entry client-info] set entry [lindex $entries 2] assert_equal [dict get $entry username] {default} assert_equal [dict get $entry context] {toplevel} assert_equal [dict get $entry object] {set} assert_equal [dict get $entry reason] {command} assert_match {*cmd=set*} [dict get $entry client-info] r acl setuser default +set } test {Call server write command in RO script} { assert_error {ERR Write commands are not allowed*} { r eval {#!lua flags=no-writes return server.call('SET', 'x', 5) } 0 } assert_error {ERR Write commands are not allowed*} { r eval {#!hello flags=no-writes FUNCTION callcmd CONSTS x CONSTI 43 CONSTI 2 CALL SET RETURN } 0 } r function load {#!hello name=errlib RFUNCTION callcmd CONSTS x CONSTI 43 CONSTI 2 CALL SET RETURN } assert_error {ERR Write commands are not allowed*} {r fcall callcmd 0} } test {Call server command when OOM} { r config set maxmemory 1 assert_error {*command not allowed when used memory*} { r eval {#!lua return server.call('set', 'x', 1) } 0 } set res [r eval {#!lua flags=allow-oom return server.call('set', 'x', 1) } 0] assert_equal $res "OK" assert_error {*command not allowed when used memory*} { r eval {#!hello FUNCTION callcmd CONSTS x CONSTI 43 CONSTI 2 CALL SET RETURN } 0 } set res [r eval {#!hello flags=allow-oom FUNCTION callcmd CONSTS x CONSTI 43 CONSTI 2 CALL SET RETURN } 0] assert_equal $res "OK" r config set maxmemory 0 } test {Ensure errors from commands called from script is counted only once} { r lpush l 1 assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} { r eval {#!hello FUNCTION callcmd CONSTS l CONSTI 1 CALL GET RETURN } 0 } set errorstats [r info Errorstats] regexp {errorstat_WRONGTYPE:count=([0-9]+)} $errorstats -> wrongtype_errors assert_equal $wrongtype_errors 1 } test {Call server command that returns NULL values} { r lpush s a set result [r eval {#!hello FUNCTION callcmd CONSTS s CONSTI 1 CONSTI 2 CALL BLPOP RETURN } 0] assert_equal $result "OK" set result [r eval {#!hello FUNCTION callcmd CONSTS s CONSTI 2 CONSTI 2 CALL BLPOP RETURN } 0] assert_equal $result "(null array)" set result [r eval {#!hello FUNCTION callcmd CONSTS f CONSTI 1 CALL GET RETURN } 0] assert_equal $result "(null string)" } test {Replace function library and call functions} { set result [r function load replace "#!hello name=mylib\nFUNCTION foo\nARGS 0\nRETURN\nFUNCTION bar\nCONSTI 500\nRETURN"] assert_equal $result "mylib" set result [r fcall foo 0 132] assert_equal $result 132 set result [r fcall bar 0] assert_equal $result 500 } test {List scripting engine functions} { r function flush sync r function load replace "#!hello name=mylib\nFUNCTION foobar\nARGS 0\nRETURN" r function list } {{library_name mylib engine HELLO functions {{name foobar description {} flags {}}}}} test {Load a second library and call a function} { r function load "#!hello name=mylib2\nFUNCTION getarg\nARGS 0\nRETURN" set result [r fcall getarg 0 456] assert_equal $result 456 } test {Delete all libraries and functions} { set result [r function flush] assert_equal $result {OK} r function list } {} test {Test the deletion of a single library} { r function load $HELLO_PROGRAM r function load "#!hello name=mylib2\nFUNCTION getarg\nARGS 0\nRETURN" set result [r function delete mylib] assert_equal $result {OK} set result [r fcall getarg 0 446] assert_equal $result 446 } test {Test dump and restore function library} { r function load $HELLO_PROGRAM set result [r fcall bar 0] assert_equal $result 432 set dump [r function dump] set result [r function flush] assert_equal $result {OK} set result [r function restore $dump] assert_equal $result {OK} set result [r fcall getarg 0 436] assert_equal $result 436 set result [r fcall bar 0] assert_equal $result 432 } test {Test function kill} { set rd [valkey_deferring_client] r config set busy-reply-threshold 10 r function load REPLACE "#!hello name=mylib\nFUNCTION wait\nARGS 0\nSLEEP\nARGS 0\nRETURN" $rd fcall wait 0 100 after 1000 catch {r ping} e assert_match {BUSY*} $e assert_match {running_script {name wait command {fcall wait 0 100} duration_ms *} engines {*}} [r FUNCTION STATS] r function kill after 1000 ; assert_equal [r ping] "PONG" assert_error {ERR Script killed by user with FUNCTION KILL*} {$rd read} $rd ping assert_equal [$rd read] "PONG" $rd close } test {Test eval execution} { set result [r eval "#!hello\nFUNCTION foo\nARGS 0\nRETURN" 0 145] assert_equal $result 145 } test {Test evalsha execution} { set sha [r script load "#!hello\nFUNCTION foo\nARGS 0\nRETURN"] set result [r evalsha $sha 0 167] assert_equal $result 167 } test {Test script exists} { set sha [r script load "#!hello\nFUNCTION foo\nARGS 0\nRETURN"] set result [r script exists $sha] assert_equal $result 1 } test {Test script flush sync} { set sha [r script load "#!hello\nFUNCTION foo\nARGS 0\nRETURN"] set result [r script exists $sha] assert_equal $result 1 r script flush SYNC set result [r script exists $sha] assert_equal $result 0 } test {Test script flush async} { set sha [r script load "#!hello\nFUNCTION foo\nARGS 0\nRETURN"] set result [r script exists $sha] assert_equal $result 1 r script flush ASYNC set result [r script exists $sha] assert_equal $result 0 } test {Test HELLO debugger} { r script debug sync hello set ret [r eval "#!hello\nFUNCTION foo\nARGS 0\nRETURN" 0 167] assert_equal {{>>> 0: ARGS 0}} $ret set cmd "*1\r\n\$4\r\nstep\r\n" r write $cmd r flush set ret [r read] assert_equal {{>>> 1: RETURN}} $ret set cmd "*1\r\n\$5\r\nstack\r\n" r write $cmd r flush set ret [r read] assert_equal {{Stack contents:} {top -> [0] 167}} $ret set cmd "*1\r\n\$1\r\nc\r\n" r write $cmd r flush set ret [r read] assert_equal {} $ret r script debug off reconnect assert_equal [r ping] {PONG} } test {Test INFO scriptingengines section} { # Get the scripting engines info section set info [r info scriptingengines] # Verify the section header exists assert_match "*# Scripting Engines*" $info # Verify we have exactly 2 engines (LUA + HELLO) assert_match "*engines_count:*" $info regexp {engines_count:([0-9]+)} $info -> engines_count assert_equal $engines_count 2 # Verify memory fields exist and are non-negative numbers assert_match "*engines_total_used_memory:*" $info assert_match "*engines_total_memory_overhead:*" $info regexp {engines_total_used_memory:([0-9]+)} $info -> total_memory regexp {engines_total_memory_overhead:([0-9]+)} $info -> total_overhead assert {$total_memory >= 0} assert {$total_overhead >= 0} # Verify individual engine information exists assert_match "*engine_0:*" $info assert_match "*engine_1:*" $info # Check that engines have proper format including abi_version assert_match "*engine_*:name=*,module=*,abi_version=*,used_memory=*,memory_overhead=*" $info # Verify both LUA and HELLO engines are present assert_match "*name=LUA*" $info assert_match "*name=HELLO*" $info # Verify LUA is built-in and HELLO is from module assert_match "*name=LUA,module=lua*" $info assert_match "*name=HELLO,module=helloengine*" $info } test {Unload scripting engine module} { set result [r module unload helloengine] assert_equal $result "OK" } test {Load scripting engine in version before function env reset} { r module load $testmodule 2 r function load $HELLO_PROGRAM set result [r fcall foo 0 123] assert_equal $result 123 set result [r function flush async] assert_equal $result {OK} assert_error {ERR Function not found} {r fcall foo 0 123} set result [r module unload helloengine] assert_equal $result "OK" } test {Load scripting engine in version before debugger support} { r module load $testmodule 3 r function load $HELLO_PROGRAM set result [r fcall foo 0 123] assert_equal $result 123 assert_error {ERR The scripting engine 'HELLO' does not support interactive script debugging} {r script debug sync hello} set result [r module unload helloengine] assert_equal $result "OK" } }