mirror of https://github.com/redis/redis.git
				
				
				
			
		
			
				
	
	
		
			1228 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			Tcl
		
	
	
	
			
		
		
	
	
			1228 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			Tcl
		
	
	
	
| proc get_function_code {args} {
 | |
|     return [format "#!%s name=%s\nredis.register_function('%s', function(KEYS, ARGV)\n %s \nend)" [lindex $args 0] [lindex $args 1] [lindex $args 2] [lindex $args 3]]
 | |
| }
 | |
| 
 | |
| proc get_no_writes_function_code {args} {
 | |
|     return [format "#!%s name=%s\nredis.register_function{function_name='%s', callback=function(KEYS, ARGV)\n %s \nend, flags={'no-writes'}}" [lindex $args 0] [lindex $args 1] [lindex $args 2] [lindex $args 3]]
 | |
| }
 | |
| 
 | |
| start_server {tags {"scripting"}} {
 | |
|     test {FUNCTION - Basic usage} {
 | |
|         r function load [get_function_code LUA test test {return 'hello'}]
 | |
|         r fcall test 0
 | |
|     } {hello}
 | |
| 
 | |
|     test {FUNCTION - Load with unknown argument} {
 | |
|         catch {
 | |
|             r function load foo bar [get_function_code LUA test test {return 'hello'}]
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*Unknown option given*}
 | |
| 
 | |
|     test {FUNCTION - Create an already exiting library raise error} {
 | |
|         catch {
 | |
|             r function load [get_function_code LUA test test {return 'hello1'}]
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*already exists*}
 | |
| 
 | |
|     test {FUNCTION - Create an already exiting library raise error (case insensitive)} {
 | |
|         catch {
 | |
|             r function load [get_function_code LUA test test {return 'hello1'}]
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*already exists*}
 | |
| 
 | |
|     test {FUNCTION - Create a library with wrong name format} {
 | |
|         catch {
 | |
|             r function load [get_function_code LUA {bad\0foramat} test {return 'hello1'}]
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*Library names can only contain letters and numbers*}
 | |
| 
 | |
|     test {FUNCTION - Create library with unexisting engine} {
 | |
|         catch {
 | |
|             r function load [get_function_code bad_engine test test {return 'hello1'}]
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*Engine 'bad_engine' not found*}
 | |
| 
 | |
|     test {FUNCTION - Test uncompiled script} {
 | |
|         catch {
 | |
|             r function load replace [get_function_code LUA test test {bad script}]
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*Error compiling function*}
 | |
| 
 | |
|     test {FUNCTION - test replace argument} {
 | |
|         r function load REPLACE [get_function_code LUA test test {return 'hello1'}]
 | |
|         r fcall test 0
 | |
|     } {hello1}
 | |
| 
 | |
|     test {FUNCTION - test function case insensitive} {
 | |
|         r fcall TEST 0
 | |
|     } {hello1}
 | |
| 
 | |
|     test {FUNCTION - test replace argument with failure keeps old libraries} {
 | |
|          catch {r function create LUA test REPLACE {error}}
 | |
|         r fcall test 0
 | |
|     } {hello1}
 | |
| 
 | |
|     test {FUNCTION - test function delete} {
 | |
|         r function delete test
 | |
|         catch {
 | |
|             r fcall test 0
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*Function not found*}
 | |
| 
 | |
|     test {FUNCTION - test fcall bad arguments} {
 | |
|         r function load [get_function_code LUA test test {return 'hello'}]
 | |
|         catch {
 | |
|             r fcall test bad_arg
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*Bad number of keys provided*}
 | |
| 
 | |
|     test {FUNCTION - test fcall bad number of keys arguments} {
 | |
|         catch {
 | |
|             r fcall test 10 key1
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*Number of keys can't be greater than number of args*}
 | |
| 
 | |
|     test {FUNCTION - test fcall negative number of keys} {
 | |
|         catch {
 | |
|             r fcall test -1 key1
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*Number of keys can't be negative*}
 | |
| 
 | |
|     test {FUNCTION - test delete on not exiting library} {
 | |
|         catch {
 | |
|             r function delete test1
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*Library not found*}
 | |
| 
 | |
|     test {FUNCTION - test function kill when function is not running} {
 | |
|         catch {
 | |
|             r function kill
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*No scripts in execution*}
 | |
| 
 | |
|     test {FUNCTION - test wrong subcommand} {
 | |
|         catch {
 | |
|             r function bad_subcommand
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*unknown subcommand*}
 | |
| 
 | |
|     test {FUNCTION - test loading from rdb} {
 | |
|         r debug reload
 | |
|         r fcall test 0
 | |
|     } {hello} {needs:debug}
 | |
| 
 | |
|     test {FUNCTION - test debug reload different options} {
 | |
|         catch {r debug reload noflush} e
 | |
|         assert_match "*Error trying to load the RDB*" $e
 | |
|         r debug reload noflush merge
 | |
|         r function list
 | |
|     } {{library_name test engine LUA functions {{name test description {} flags {}}}}} {needs:debug}
 | |
| 
 | |
|     test {FUNCTION - test debug reload with nosave and noflush} {
 | |
|         r function delete test
 | |
|         r set x 1
 | |
|         r function load [get_function_code LUA test1 test1 {return 'hello'}]
 | |
|         r debug reload
 | |
|         r function load [get_function_code LUA test2 test2 {return 'hello'}]
 | |
|         r debug reload nosave noflush merge
 | |
|         assert_equal [r fcall test1 0] {hello}
 | |
|         assert_equal [r fcall test2 0] {hello}
 | |
|     } {} {needs:debug}
 | |
| 
 | |
|     test {FUNCTION - test flushall and flushdb do not clean functions} {
 | |
|         r function flush
 | |
|         r function load REPLACE [get_function_code lua test test {return redis.call('set', 'x', '1')}]
 | |
|         r flushall
 | |
|         r flushdb
 | |
|         r function list
 | |
|     } {{library_name test engine LUA functions {{name test description {} flags {}}}}}
 | |
| 
 | |
|     test {FUNCTION - test function dump and restore} {
 | |
|         r function flush
 | |
|         r function load [get_function_code lua test test {return 'hello'}]
 | |
|         set e [r function dump]
 | |
|         r function delete test
 | |
|         assert_match {} [r function list]
 | |
|         r function restore $e
 | |
|         r function list
 | |
|     } {{library_name test engine LUA functions {{name test description {} flags {}}}}}
 | |
| 
 | |
|     test {FUNCTION - test function dump and restore with flush argument} {
 | |
|         set e [r function dump]
 | |
|         r function flush
 | |
|         assert_match {} [r function list]
 | |
|         r function restore $e FLUSH
 | |
|         r function list
 | |
|     } {{library_name test engine LUA functions {{name test description {} flags {}}}}}
 | |
| 
 | |
|     test {FUNCTION - test function dump and restore with append argument} {
 | |
|         set e [r function dump]
 | |
|         r function flush
 | |
|         assert_match {} [r function list]
 | |
|         r function load [get_function_code lua test test {return 'hello1'}]
 | |
|         catch {r function restore $e APPEND} err
 | |
|         assert_match {*already exists*} $err
 | |
|         r function flush
 | |
|         r function load [get_function_code lua test1 test1 {return 'hello1'}]
 | |
|         r function restore $e APPEND
 | |
|         assert_match {hello} [r fcall test 0]
 | |
|         assert_match {hello1} [r fcall test1 0]
 | |
|     }
 | |
| 
 | |
|     test {FUNCTION - test function dump and restore with replace argument} {
 | |
|         r function flush
 | |
|         r function load [get_function_code LUA test test {return 'hello'}]
 | |
|         set e [r function dump]
 | |
|         r function flush
 | |
|         assert_match {} [r function list]
 | |
|         r function load [get_function_code lua test test {return 'hello1'}]
 | |
|         assert_match {hello1} [r fcall test 0]
 | |
|         r function restore $e REPLACE
 | |
|         assert_match {hello} [r fcall test 0]
 | |
|     }
 | |
| 
 | |
|     test {FUNCTION - test function restore with bad payload do not drop existing functions} {
 | |
|         r function flush
 | |
|         r function load [get_function_code LUA test test {return 'hello'}]
 | |
|         catch {r function restore bad_payload} e
 | |
|         assert_match {*payload version or checksum are wrong*} $e
 | |
|         r function list
 | |
|     } {{library_name test engine LUA functions {{name test description {} flags {}}}}}
 | |
| 
 | |
|     test {FUNCTION - test function restore with wrong number of arguments} {
 | |
|         catch {r function restore arg1 args2 arg3} e
 | |
|         set _ $e
 | |
|     } {*unknown subcommand or wrong number of arguments for 'restore'. Try FUNCTION HELP.}
 | |
| 
 | |
|     test {FUNCTION - test fcall_ro with write command} {
 | |
|         r function load REPLACE [get_no_writes_function_code lua test test {return redis.call('set', 'x', '1')}]
 | |
|         catch { r fcall_ro test 0 } e
 | |
|         set _ $e
 | |
|     } {*Write commands are not allowed from read-only scripts*}
 | |
| 
 | |
|     test {FUNCTION - test fcall_ro with read only commands} {
 | |
|         r function load REPLACE [get_no_writes_function_code lua test test {return redis.call('get', 'x')}]
 | |
|         r set x 1
 | |
|         r fcall_ro test 0
 | |
|     } {1}
 | |
| 
 | |
|     test {FUNCTION - test keys and argv} {
 | |
|         r function load REPLACE [get_function_code lua test test {return redis.call('set', KEYS[1], ARGV[1])}]
 | |
|         r fcall test 1 x foo
 | |
|         r get x
 | |
|     } {foo}
 | |
| 
 | |
|     test {FUNCTION - test command get keys on fcall} {
 | |
|         r COMMAND GETKEYS fcall test 1 x foo
 | |
|     } {x}
 | |
| 
 | |
|     test {FUNCTION - test command get keys on fcall_ro} {
 | |
|         r COMMAND GETKEYS fcall_ro test 1 x foo
 | |
|     } {x}
 | |
| 
 | |
|     test {FUNCTION - test function kill} {
 | |
|         set rd [redis_deferring_client]
 | |
|         r config set busy-reply-threshold 10
 | |
|         r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}]
 | |
|         $rd fcall test 0
 | |
|         after 200
 | |
|         catch {r ping} e
 | |
|         assert_match {BUSY*} $e
 | |
|         assert_match {running_script {name test command {fcall test 0} duration_ms *} engines {*}} [r FUNCTION STATS]
 | |
|         r function kill
 | |
|         after 200 ; # Give some time to Lua to call the hook again...
 | |
|         assert_equal [r ping] "PONG"
 | |
|     }
 | |
| 
 | |
|     test {FUNCTION - test script kill not working on function} {
 | |
|         set rd [redis_deferring_client]
 | |
|         r config set busy-reply-threshold 10
 | |
|         r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}]
 | |
|         $rd fcall test 0
 | |
|         after 200
 | |
|         catch {r ping} e
 | |
|         assert_match {BUSY*} $e
 | |
|         catch {r script kill} e
 | |
|         assert_match {BUSY*} $e
 | |
|         r function kill
 | |
|         after 200 ; # Give some time to Lua to call the hook again...
 | |
|         assert_equal [r ping] "PONG"
 | |
|     }
 | |
| 
 | |
|     test {FUNCTION - test function kill not working on eval} {
 | |
|         set rd [redis_deferring_client]
 | |
|         r config set busy-reply-threshold 10
 | |
|         $rd eval {local a = 1 while true do a = a + 1 end} 0
 | |
|         after 200
 | |
|         catch {r ping} e
 | |
|         assert_match {BUSY*} $e
 | |
|         catch {r function kill} e
 | |
|         assert_match {BUSY*} $e
 | |
|         r script kill
 | |
|         after 200 ; # Give some time to Lua to call the hook again...
 | |
|         assert_equal [r ping] "PONG"
 | |
|     }
 | |
| 
 | |
|     test {FUNCTION - test function flush} {
 | |
|         r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}]
 | |
|         assert_match {{library_name test engine LUA functions {{name test description {} flags {}}}}} [r function list]
 | |
|         r function flush
 | |
|         assert_match {} [r function list]
 | |
| 
 | |
|         r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}]
 | |
|         assert_match {{library_name test engine LUA functions {{name test description {} flags {}}}}} [r function list]
 | |
|         r function flush async
 | |
|         assert_match {} [r function list]
 | |
| 
 | |
|         r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}]
 | |
|         assert_match {{library_name test engine LUA functions {{name test description {} flags {}}}}} [r function list]
 | |
|         r function flush sync
 | |
|         assert_match {} [r function list]
 | |
|     }
 | |
| 
 | |
|     test {FUNCTION - test function wrong argument} {
 | |
|         catch {r function flush bad_arg} e
 | |
|         assert_match {*only supports SYNC|ASYNC*} $e
 | |
| 
 | |
|         catch {r function flush sync extra_arg} e
 | |
|         assert_match {*unknown subcommand or wrong number of arguments for 'flush'. Try FUNCTION HELP.} $e
 | |
|     }
 | |
| }
 | |
| 
 | |
| start_server {tags {"scripting repl external:skip"}} {
 | |
|     start_server {} {
 | |
|         test "Connect a replica to the master instance" {
 | |
|             r -1 slaveof [srv 0 host] [srv 0 port]
 | |
|             wait_for_condition 150 100 {
 | |
|                 [s -1 role] eq {slave} &&
 | |
|                 [string match {*master_link_status:up*} [r -1 info replication]]
 | |
|             } else {
 | |
|                 fail "Can't turn the instance into a replica"
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         test {FUNCTION - creation is replicated to replica} {
 | |
|             r function load [get_no_writes_function_code LUA test test {return 'hello'}]
 | |
|             wait_for_condition 150 100 {    
 | |
|                 [r -1 function list] eq {{library_name test engine LUA functions {{name test description {} flags no-writes}}}}
 | |
|             } else {
 | |
|                 fail "Failed waiting for function to replicate to replica"
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         test {FUNCTION - call on replica} {
 | |
|             r -1 fcall test 0
 | |
|         } {hello}
 | |
| 
 | |
|         test {FUNCTION - restore is replicated to replica} {
 | |
|             set e [r function dump]
 | |
| 
 | |
|             r function delete test
 | |
|             wait_for_condition 150 100 {
 | |
|                 [r -1 function list] eq {}
 | |
|             } else {
 | |
|                 fail "Failed waiting for function to replicate to replica"
 | |
|             }
 | |
| 
 | |
|             assert_equal [r function restore $e] {OK}
 | |
| 
 | |
|             wait_for_condition 150 100 {
 | |
|                 [r -1 function list] eq {{library_name test engine LUA functions {{name test description {} flags no-writes}}}}
 | |
|             } else {
 | |
|                 fail "Failed waiting for function to replicate to replica"
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         test {FUNCTION - delete is replicated to replica} {
 | |
|             r function delete test
 | |
|             wait_for_condition 150 100 {
 | |
|                 [r -1 function list] eq {}
 | |
|             } else {
 | |
|                 fail "Failed waiting for function to replicate to replica"
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         test {FUNCTION - flush is replicated to replica} {
 | |
|             r function load [get_function_code LUA test test {return 'hello'}]
 | |
|             wait_for_condition 150 100 {
 | |
|                 [r -1 function list] eq {{library_name test engine LUA functions {{name test description {} flags {}}}}}
 | |
|             } else {
 | |
|                 fail "Failed waiting for function to replicate to replica"
 | |
|             }
 | |
|             r function flush
 | |
|             wait_for_condition 150 100 {
 | |
|                 [r -1 function list] eq {}
 | |
|             } else {
 | |
|                 fail "Failed waiting for function to replicate to replica"
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         test "Disconnecting the replica from master instance" {
 | |
|             r -1 slaveof no one
 | |
|             # creating a function after disconnect to make sure function
 | |
|             # is replicated on rdb phase
 | |
|             r function load [get_no_writes_function_code LUA test test {return 'hello'}]
 | |
| 
 | |
|             # reconnect the replica
 | |
|             r -1 slaveof [srv 0 host] [srv 0 port]
 | |
|             wait_for_condition 150 100 {
 | |
|                 [s -1 role] eq {slave} &&
 | |
|                 [string match {*master_link_status:up*} [r -1 info replication]]
 | |
|             } else {
 | |
|                 fail "Can't turn the instance into a replica"
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         test "FUNCTION - test replication to replica on rdb phase" {
 | |
|             r -1 fcall test 0
 | |
|         } {hello}
 | |
| 
 | |
|         test "FUNCTION - test replication to replica on rdb phase info command" {
 | |
|             r -1 function list
 | |
|         } {{library_name test engine LUA functions {{name test description {} flags no-writes}}}}
 | |
| 
 | |
|         test "FUNCTION - create on read only replica" {
 | |
|             catch {
 | |
|                 r -1 function load [get_function_code LUA test test {return 'hello'}]
 | |
|             } e
 | |
|             set _ $e
 | |
|         } {*can't write against a read only replica*}
 | |
| 
 | |
|         test "FUNCTION - delete on read only replica" {
 | |
|             catch {
 | |
|                 r -1 function delete test
 | |
|             } e
 | |
|             set _ $e
 | |
|         } {*can't write against a read only replica*}
 | |
| 
 | |
|         test "FUNCTION - function effect is replicated to replica" {
 | |
|             r function load REPLACE [get_function_code LUA test test {return redis.call('set', 'x', '1')}]
 | |
|             r fcall test 0
 | |
|             assert {[r get x] eq {1}}
 | |
|             wait_for_condition 150 100 {
 | |
|                 [r -1 get x] eq {1}
 | |
|             } else {
 | |
|                 fail "Failed waiting function effect to be replicated to replica"
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         test "FUNCTION - modify key space of read only replica" {
 | |
|             catch {
 | |
|                 r -1 fcall test 0
 | |
|             } e
 | |
|             set _ $e
 | |
|         } {*Can not run script with write flag on readonly replica*}
 | |
|     }
 | |
| }
 | |
| 
 | |
| test {FUNCTION can processes create, delete and flush commands in AOF when doing "debug loadaof" in read-only slaves} {
 | |
|     start_server {} {
 | |
|         r config set appendonly yes
 | |
|         waitForBgrewriteaof r
 | |
|         r FUNCTION LOAD "#!lua name=test\nredis.register_function('test', function() return 'hello' end)"
 | |
|         r config set slave-read-only yes
 | |
|         r slaveof 127.0.0.1 0
 | |
|         r debug loadaof
 | |
|         r slaveof no one
 | |
|         assert_equal [r function list] {{library_name test engine LUA functions {{name test description {} flags {}}}}}
 | |
| 
 | |
|         r FUNCTION DELETE test
 | |
| 
 | |
|         r slaveof 127.0.0.1 0
 | |
|         r debug loadaof
 | |
|         r slaveof no one
 | |
|         assert_equal [r function list] {}
 | |
| 
 | |
|         r FUNCTION LOAD "#!lua name=test\nredis.register_function('test', function() return 'hello' end)"
 | |
|         r FUNCTION FLUSH
 | |
| 
 | |
|         r slaveof 127.0.0.1 0
 | |
|         r debug loadaof
 | |
|         r slaveof no one
 | |
|         assert_equal [r function list] {}
 | |
|     }
 | |
| } {} {needs:debug external:skip}
 | |
| 
 | |
| start_server {tags {"scripting"}} {
 | |
|     test {LIBRARIES - test shared function can access default globals} {
 | |
|         r function load {#!lua name=lib1
 | |
|             local function ping()
 | |
|                 return redis.call('ping')
 | |
|             end
 | |
|             redis.register_function(
 | |
|                 'f1',
 | |
|                 function(keys, args)
 | |
|                     return ping()
 | |
|                 end
 | |
|             )
 | |
|         }
 | |
|         r fcall f1 0
 | |
|     } {PONG}
 | |
| 
 | |
|     test {LIBRARIES - usage and code sharing} {
 | |
|         r function load REPLACE {#!lua name=lib1
 | |
|             local function add1(a)
 | |
|                 return a + 1
 | |
|             end
 | |
|             redis.register_function(
 | |
|                 'f1',
 | |
|                 function(keys, args)
 | |
|                     return add1(1)
 | |
|                 end
 | |
|             )
 | |
|             redis.register_function(
 | |
|                 'f2',
 | |
|                 function(keys, args)
 | |
|                     return add1(2)
 | |
|                 end
 | |
|             )
 | |
|         }
 | |
|         assert_equal [r fcall f1 0] {2}
 | |
|         assert_equal [r fcall f2 0] {3}
 | |
|         r function list
 | |
|     } {{library_name lib1 engine LUA functions {*}}}
 | |
| 
 | |
|     test {LIBRARIES - test registration failure revert the entire load} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib1
 | |
|                 local function add1(a)
 | |
|                     return a + 2
 | |
|                 end
 | |
|                 redis.register_function(
 | |
|                     'f1',
 | |
|                     function(keys, args)
 | |
|                         return add1(1)
 | |
|                     end
 | |
|                 )
 | |
|                 redis.register_function(
 | |
|                     'f2',
 | |
|                     'not a function'
 | |
|                 )
 | |
|             }
 | |
|         } e
 | |
|         assert_match {*second argument to redis.register_function must be a function*} $e
 | |
|         assert_equal [r fcall f1 0] {2}
 | |
|         assert_equal [r fcall f2 0] {3}
 | |
|     }
 | |
| 
 | |
|     test {LIBRARIES - test registration function name collision} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib2
 | |
|                 redis.register_function(
 | |
|                     'f1',
 | |
|                     function(keys, args)
 | |
|                         return 1
 | |
|                     end
 | |
|                 )
 | |
|             }
 | |
|         } e
 | |
|         assert_match {*Function f1 already exists*} $e
 | |
|         assert_equal [r fcall f1 0] {2}
 | |
|         assert_equal [r fcall f2 0] {3}
 | |
|     }
 | |
| 
 | |
|     test {LIBRARIES - test registration function name collision on same library} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib2
 | |
|                 redis.register_function(
 | |
|                     'f1',
 | |
|                     function(keys, args)
 | |
|                         return 1
 | |
|                     end
 | |
|                 )
 | |
|                 redis.register_function(
 | |
|                     'f1',
 | |
|                     function(keys, args)
 | |
|                         return 1
 | |
|                     end
 | |
|                 )
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*Function already exists in the library*}
 | |
| 
 | |
|     test {LIBRARIES - test registration with no argument} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib2
 | |
|                 redis.register_function()
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*wrong number of arguments to redis.register_function*}
 | |
| 
 | |
|     test {LIBRARIES - test registration with only name} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib2
 | |
|                 redis.register_function('f1')
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*calling redis.register_function with a single argument is only applicable to Lua table*}
 | |
| 
 | |
|     test {LIBRARIES - test registration with to many arguments} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib2
 | |
|                 redis.register_function('f1', function() return 1 end, {}, 'description', 'extra arg')
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*wrong number of arguments to redis.register_function*}
 | |
| 
 | |
|     test {LIBRARIES - test registration with no string name} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib2
 | |
|                 redis.register_function(nil, function() return 1 end)
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*first argument to redis.register_function must be a string*}
 | |
| 
 | |
|     test {LIBRARIES - test registration with wrong name format} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib2
 | |
|                 redis.register_function('test\0test', function() return 1 end)
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*Function names can only contain letters and numbers and must be at least one character long*}
 | |
| 
 | |
|     test {LIBRARIES - test registration with empty name} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib2
 | |
|                 redis.register_function('', function() return 1 end)
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*Function names can only contain letters and numbers and must be at least one character long*}
 | |
| 
 | |
|     test {LIBRARIES - math.random from function load} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib2
 | |
|                 return math.random()
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*attempted to access nonexistent global variable 'math'*}
 | |
| 
 | |
|     test {LIBRARIES - redis.call from function load} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib2
 | |
|                 return redis.call('ping')
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*attempted to access nonexistent global variable 'call'*}
 | |
| 
 | |
|     test {LIBRARIES - redis.setresp from function load} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib2
 | |
|                 return redis.setresp(3)
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*attempted to access nonexistent global variable 'setresp'*}
 | |
| 
 | |
|     test {LIBRARIES - redis.set_repl from function load} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib2
 | |
|                 return redis.set_repl(redis.REPL_NONE)
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*attempted to access nonexistent global variable 'set_repl'*}
 | |
| 
 | |
|     test {LIBRARIES - malicious access test} {
 | |
|         # the 'library' API is not exposed inside a
 | |
|         # function context and the 'redis' API is not
 | |
|         # expose on the library registration context.
 | |
|         # But a malicious user might find a way to hack it
 | |
|         # (as demonstrated in this test). This is why we
 | |
|         # have another level of protection on the C
 | |
|         # code itself and we want to test it and verify
 | |
|         # that it works properly.
 | |
|         r function load replace {#!lua name=lib1
 | |
|             local lib = redis
 | |
|             lib.register_function('f1', function ()
 | |
|                 lib.redis = redis
 | |
|                 lib.math = math
 | |
|                 return {ok='OK'}
 | |
|             end)
 | |
| 
 | |
|             lib.register_function('f2', function ()
 | |
|                 lib.register_function('f1', function ()
 | |
|                     lib.redis = redis
 | |
|                     lib.math = math
 | |
|                     return {ok='OK'}
 | |
|                 end)
 | |
|             end)
 | |
|         }
 | |
|         catch {[r fcall f1 0]} e
 | |
|         assert_match {*Attempt to modify a readonly table*} $e
 | |
| 
 | |
|         catch {[r function load {#!lua name=lib2
 | |
|             redis.math.random()
 | |
|         }]} e
 | |
|         assert_match {*Script attempted to access nonexistent global variable 'math'*} $e
 | |
| 
 | |
|         catch {[r function load {#!lua name=lib2
 | |
|             redis.redis.call('ping')
 | |
|         }]} e
 | |
|         assert_match {*Script attempted to access nonexistent global variable 'redis'*} $e
 | |
| 
 | |
|         catch {[r fcall f2 0]} e
 | |
|         assert_match {*can only be called on FUNCTION LOAD command*} $e
 | |
|     }
 | |
| 
 | |
|     test {LIBRARIES - delete removed all functions on library} {
 | |
|         r function delete lib1
 | |
|         r function list
 | |
|     } {}
 | |
| 
 | |
|     test {LIBRARIES - register function inside a function} {
 | |
|         r function load {#!lua name=lib
 | |
|             redis.register_function(
 | |
|                 'f1',
 | |
|                 function(keys, args)
 | |
|                     redis.register_function(
 | |
|                         'f2',
 | |
|                         function(key, args)
 | |
|                             return 2
 | |
|                         end
 | |
|                     )
 | |
|                     return 1
 | |
|                 end
 | |
|             )
 | |
|         }
 | |
|         catch {r fcall f1 0} e
 | |
|         set _ $e
 | |
|     } {*attempt to call field 'register_function' (a nil value)*}
 | |
| 
 | |
|     test {LIBRARIES - register library with no functions} {
 | |
|         r function flush
 | |
|         catch {
 | |
|             r function load {#!lua name=lib
 | |
|                 return 1
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*No functions registered*}
 | |
| 
 | |
|     test {LIBRARIES - load timeout} {
 | |
|         catch {
 | |
|             r function load {#!lua name=lib
 | |
|                 local a = 1
 | |
|                 while 1 do a = a + 1 end
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*FUNCTION LOAD timeout*}
 | |
| 
 | |
|     test {LIBRARIES - verify global protection on the load run} {
 | |
|         catch {
 | |
|             r function load {#!lua name=lib
 | |
|                 a = 1
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*Attempt to modify a readonly table*}
 | |
| 
 | |
|     test {LIBRARIES - named arguments} {
 | |
|         r function load {#!lua name=lib
 | |
|             redis.register_function{
 | |
|                 function_name='f1',
 | |
|                 callback=function()
 | |
|                     return 'hello'
 | |
|                 end,
 | |
|                 description='some desc'
 | |
|             }
 | |
|         }
 | |
|         r function list
 | |
|     } {{library_name lib engine LUA functions {{name f1 description {some desc} flags {}}}}}
 | |
| 
 | |
|     test {LIBRARIES - named arguments, bad function name} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib
 | |
|                 redis.register_function{
 | |
|                     function_name=function() return 1 end,
 | |
|                     callback=function()
 | |
|                         return 'hello'
 | |
|                     end,
 | |
|                     description='some desc'
 | |
|                 }
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*function_name argument given to redis.register_function must be a string*}
 | |
| 
 | |
|     test {LIBRARIES - named arguments, bad callback type} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib
 | |
|                 redis.register_function{
 | |
|                     function_name='f1',
 | |
|                     callback='bad',
 | |
|                     description='some desc'
 | |
|                 }
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*callback argument given to redis.register_function must be a function*}
 | |
| 
 | |
|     test {LIBRARIES - named arguments, bad description} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib
 | |
|                 redis.register_function{
 | |
|                     function_name='f1',
 | |
|                     callback=function()
 | |
|                         return 'hello'
 | |
|                     end,
 | |
|                     description=function() return 1 end
 | |
|                 }
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*description argument given to redis.register_function must be a string*}
 | |
| 
 | |
|     test {LIBRARIES - named arguments, unknown argument} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib
 | |
|                 redis.register_function{
 | |
|                     function_name='f1',
 | |
|                     callback=function()
 | |
|                         return 'hello'
 | |
|                     end,
 | |
|                     description='desc',
 | |
|                     some_unknown='unknown'
 | |
|                 }
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*unknown argument given to redis.register_function*}
 | |
| 
 | |
|     test {LIBRARIES - named arguments, missing function name} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib
 | |
|                 redis.register_function{
 | |
|                     callback=function()
 | |
|                         return 'hello'
 | |
|                     end,
 | |
|                     description='desc'
 | |
|                 }
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*redis.register_function must get a function name argument*}
 | |
| 
 | |
|     test {LIBRARIES - named arguments, missing callback} {
 | |
|         catch {
 | |
|             r function load replace {#!lua name=lib
 | |
|                 redis.register_function{
 | |
|                     function_name='f1',
 | |
|                     description='desc'
 | |
|                 }
 | |
|             }
 | |
|         } e
 | |
|         set _ $e
 | |
|     } {*redis.register_function must get a callback argument*}
 | |
| 
 | |
|     test {FUNCTION - test function restore with function name collision} {
 | |
|         r function flush
 | |
|         r function load {#!lua name=lib1
 | |
|             local function add1(a)
 | |
|                 return a + 1
 | |
|             end
 | |
|             redis.register_function(
 | |
|                 'f1',
 | |
|                 function(keys, args)
 | |
|                     return add1(1)
 | |
|                 end
 | |
|             )
 | |
|             redis.register_function(
 | |
|                 'f2',
 | |
|                 function(keys, args)
 | |
|                     return add1(2)
 | |
|                 end
 | |
|             )
 | |
|             redis.register_function(
 | |
|                 'f3',
 | |
|                 function(keys, args)
 | |
|                     return add1(3)
 | |
|                 end
 | |
|             )
 | |
|         }
 | |
|         set e [r function dump]
 | |
|         r function flush
 | |
| 
 | |
|         # load a library with different name but with the same function name
 | |
|         r function load {#!lua name=lib1
 | |
|             redis.register_function(
 | |
|                 'f6',
 | |
|                 function(keys, args)
 | |
|                     return 7
 | |
|                 end
 | |
|             )
 | |
|         }
 | |
|         r function load {#!lua name=lib2
 | |
|             local function add1(a)
 | |
|                 return a + 1
 | |
|             end
 | |
|             redis.register_function(
 | |
|                 'f4',
 | |
|                 function(keys, args)
 | |
|                     return add1(4)
 | |
|                 end
 | |
|             )
 | |
|             redis.register_function(
 | |
|                 'f5',
 | |
|                 function(keys, args)
 | |
|                     return add1(5)
 | |
|                 end
 | |
|             )
 | |
|             redis.register_function(
 | |
|                 'f3',
 | |
|                 function(keys, args)
 | |
|                     return add1(3)
 | |
|                 end
 | |
|             )
 | |
|         }
 | |
| 
 | |
|         catch {r function restore $e} error
 | |
|         assert_match {*Library lib1 already exists*} $error
 | |
|         assert_equal [r fcall f3 0] {4}
 | |
|         assert_equal [r fcall f4 0] {5}
 | |
|         assert_equal [r fcall f5 0] {6}
 | |
|         assert_equal [r fcall f6 0] {7}
 | |
| 
 | |
|         catch {r function restore $e replace} error
 | |
|         assert_match {*Function f3 already exists*} $error
 | |
|         assert_equal [r fcall f3 0] {4}
 | |
|         assert_equal [r fcall f4 0] {5}
 | |
|         assert_equal [r fcall f5 0] {6}
 | |
|         assert_equal [r fcall f6 0] {7}
 | |
|     }
 | |
| 
 | |
|     test {FUNCTION - test function list with code} {
 | |
|         r function flush
 | |
|         r function load {#!lua name=library1
 | |
|             redis.register_function('f6', function(keys, args) return 7 end)
 | |
|         }
 | |
|         r function list withcode
 | |
|     } {{library_name library1 engine LUA functions {{name f6 description {} flags {}}} library_code {*redis.register_function('f6', function(keys, args) return 7 end)*}}}
 | |
| 
 | |
|     test {FUNCTION - test function list with pattern} {
 | |
|         r function load {#!lua name=lib1
 | |
|             redis.register_function('f7', function(keys, args) return 7 end)
 | |
|         }
 | |
|         r function list libraryname library*
 | |
|     } {{library_name library1 engine LUA functions {{name f6 description {} flags {}}}}}
 | |
| 
 | |
|     test {FUNCTION - test function list wrong argument} {
 | |
|         catch {r function list bad_argument} e
 | |
|         set _ $e
 | |
|     } {*Unknown argument bad_argument*}
 | |
| 
 | |
|     test {FUNCTION - test function list with bad argument to library name} {
 | |
|         catch {r function list libraryname} e
 | |
|         set _ $e
 | |
|     } {*library name argument was not given*}
 | |
| 
 | |
|     test {FUNCTION - test function list withcode multiple times} {
 | |
|         catch {r function list withcode withcode} e
 | |
|         set _ $e
 | |
|     } {*Unknown argument withcode*}
 | |
| 
 | |
|     test {FUNCTION - test function list libraryname multiple times} {
 | |
|         catch {r function list withcode libraryname foo libraryname foo} e
 | |
|         set _ $e
 | |
|     } {*Unknown argument libraryname*}
 | |
| 
 | |
|     test {FUNCTION - verify OOM on function load and function restore} {
 | |
|         r function flush
 | |
|         r function load replace {#!lua name=test
 | |
|             redis.register_function('f1', function() return 1 end)
 | |
|         }
 | |
|         set payload [r function dump]
 | |
|         r config set maxmemory 1
 | |
| 
 | |
|         r function flush
 | |
|         catch {r function load replace {#!lua name=test
 | |
|             redis.register_function('f1', function() return 1 end)
 | |
|         }} e
 | |
|         assert_match {*command not allowed when used memory*} $e
 | |
| 
 | |
|         r function flush
 | |
|         catch {r function restore $payload} e
 | |
|         assert_match {*command not allowed when used memory*} $e
 | |
| 
 | |
|         r config set maxmemory 0
 | |
|     } {OK} {needs:config-maxmemory}
 | |
| 
 | |
|     test {FUNCTION - verify allow-omm allows running any command} {
 | |
|         r FUNCTION load replace {#!lua name=f1
 | |
|             redis.register_function{
 | |
|                 function_name='f1',
 | |
|                 callback=function() return redis.call('set', 'x', '1') end,
 | |
|                 flags={'allow-oom'}
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         r config set maxmemory 1
 | |
| 
 | |
|         assert_match {OK} [r fcall f1 1 x]
 | |
|         assert_match {1} [r get x]
 | |
| 
 | |
|         r config set maxmemory 0
 | |
|     } {OK} {needs:config-maxmemory}
 | |
| }
 | |
| 
 | |
| start_server {tags {"scripting"}} {
 | |
|     test {FUNCTION - wrong flags type named arguments} {
 | |
|         catch {r function load replace {#!lua name=test
 | |
|             redis.register_function{
 | |
|                 function_name = 'f1',
 | |
|                 callback = function() return 1 end,
 | |
|                 flags = 'bad flags type'
 | |
|             }
 | |
|         }} e
 | |
|         set _ $e
 | |
|     } {*flags argument to redis.register_function must be a table representing function flags*}
 | |
| 
 | |
|     test {FUNCTION - wrong flag type} {
 | |
|         catch {r function load replace {#!lua name=test
 | |
|             redis.register_function{
 | |
|                 function_name = 'f1',
 | |
|                 callback = function() return 1 end,
 | |
|                 flags = {function() return 1 end}
 | |
|             }
 | |
|         }} e
 | |
|         set _ $e
 | |
|     } {*unknown flag given*}
 | |
| 
 | |
|     test {FUNCTION - unknown flag} {
 | |
|         catch {r function load replace {#!lua name=test
 | |
|             redis.register_function{
 | |
|                 function_name = 'f1',
 | |
|                 callback = function() return 1 end,
 | |
|                 flags = {'unknown'}
 | |
|             }
 | |
|         }} e
 | |
|         set _ $e
 | |
|     } {*unknown flag given*}
 | |
| 
 | |
|     test {FUNCTION - write script on fcall_ro} {
 | |
|         r function load replace {#!lua name=test
 | |
|             redis.register_function{
 | |
|                 function_name = 'f1',
 | |
|                 callback = function() return redis.call('set', 'x', 1) end
 | |
|             }
 | |
|         }
 | |
|         catch {r fcall_ro f1 0} e
 | |
|         set _ $e
 | |
|     } {*Can not execute a script with write flag using \*_ro command*}
 | |
| 
 | |
|     test {FUNCTION - write script with no-writes flag} {
 | |
|         r function load replace {#!lua name=test
 | |
|             redis.register_function{
 | |
|                 function_name = 'f1',
 | |
|                 callback = function() return redis.call('set', 'x', 1) end,
 | |
|                 flags = {'no-writes'}
 | |
|             }
 | |
|         }
 | |
|         catch {r fcall f1 0} e
 | |
|         set _ $e
 | |
|     } {*Write commands are not allowed from read-only scripts*}
 | |
| 
 | |
|     test {FUNCTION - deny oom} {
 | |
|         r FUNCTION load replace {#!lua name=test
 | |
|             redis.register_function('f1', function() return redis.call('set', 'x', '1') end) 
 | |
|         }
 | |
| 
 | |
|         r config set maxmemory 1
 | |
| 
 | |
|         catch {[r fcall f1 1 k]} e
 | |
|         assert_match {*can not run it when used memory > 'maxmemory'*} $e
 | |
| 
 | |
|         r config set maxmemory 0
 | |
|     } {OK} {needs:config-maxmemory}
 | |
| 
 | |
|     test {FUNCTION - deny oom on no-writes function} {
 | |
|         r FUNCTION load replace {#!lua name=test
 | |
|             redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-writes'}}
 | |
|         }
 | |
| 
 | |
|         r config set maxmemory 1
 | |
| 
 | |
|         catch {r fcall f1 1 k} e
 | |
|         assert_match {*can not run it when used memory > 'maxmemory'*} $e
 | |
| 
 | |
|         catch {r fcall_ro f1 1 k} e
 | |
|         assert_match {*can not run it when used memory > 'maxmemory'*} $e
 | |
| 
 | |
|         r config set maxmemory 0
 | |
|     } {OK} {needs:config-maxmemory}
 | |
| 
 | |
|     test {FUNCTION - allow stale} {
 | |
|         r FUNCTION load replace {#!lua name=test
 | |
|             redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-writes'}}
 | |
|             redis.register_function{function_name='f2', callback=function() return 'hello' end, flags={'allow-stale', 'no-writes'}}
 | |
|             redis.register_function{function_name='f3', callback=function() return redis.call('get', 'x') end, flags={'allow-stale', 'no-writes'}}
 | |
|             redis.register_function{function_name='f4', callback=function() return redis.call('info', 'server') end, flags={'allow-stale', 'no-writes'}}
 | |
|         }
 | |
|         
 | |
|         r config set replica-serve-stale-data no
 | |
|         r replicaof 127.0.0.1 1
 | |
| 
 | |
|         catch {[r fcall f1 0]} e
 | |
|         assert_match {*'allow-stale' flag is not set on the script*} $e
 | |
| 
 | |
|         assert_equal {hello} [r fcall f2 0]
 | |
| 
 | |
|         catch {[r fcall f3 0]} e
 | |
|         assert_match {*Can not execute the command on a stale replica*} $e
 | |
| 
 | |
|         assert_match {*redis_version*} [r fcall f4 0]
 | |
| 
 | |
|         r replicaof no one
 | |
|         r config set replica-serve-stale-data yes
 | |
|         set _ {}
 | |
|     } {} {external:skip}
 | |
| 
 | |
|     test {FUNCTION - redis version api} {
 | |
|         r FUNCTION load replace {#!lua name=test
 | |
|             local version = redis.REDIS_VERSION_NUM
 | |
| 
 | |
|             redis.register_function{function_name='get_version_v1', callback=function()
 | |
|               return string.format('%s.%s.%s',
 | |
|                                     bit.band(bit.rshift(version, 16), 0x000000ff),
 | |
|                                     bit.band(bit.rshift(version, 8), 0x000000ff),
 | |
|                                     bit.band(version, 0x000000ff))
 | |
|             end}
 | |
|             redis.register_function{function_name='get_version_v2', callback=function() return redis.REDIS_VERSION end}
 | |
|         }
 | |
| 
 | |
|         catch {[r fcall f1 0]} e
 | |
|         assert_equal  [r fcall get_version_v1 0] [r fcall get_version_v2 0]
 | |
|     }
 | |
| 
 | |
|     test {FUNCTION - function stats} {
 | |
|         r FUNCTION FLUSH
 | |
| 
 | |
|         r FUNCTION load {#!lua name=test1
 | |
|             redis.register_function('f1', function() return 1 end)
 | |
|             redis.register_function('f2', function() return 1 end)
 | |
|         }
 | |
| 
 | |
|         r FUNCTION load {#!lua name=test2
 | |
|             redis.register_function('f3', function() return 1 end)
 | |
|         }
 | |
| 
 | |
|         r function stats
 | |
|     } {running_script {} engines {LUA {libraries_count 2 functions_count 3}}}
 | |
| 
 | |
|     test {FUNCTION - function stats reloaded correctly from rdb} {
 | |
|         r debug reload
 | |
|         r function stats
 | |
|     } {running_script {} engines {LUA {libraries_count 2 functions_count 3}}} {needs:debug}
 | |
| 
 | |
|     test {FUNCTION - function stats delete library} {
 | |
|         r function delete test1
 | |
|         r function stats
 | |
|     } {running_script {} engines {LUA {libraries_count 1 functions_count 1}}}
 | |
| 
 | |
|     test {FUNCTION - test function stats on loading failure} {
 | |
|         r FUNCTION FLUSH
 | |
| 
 | |
|         r FUNCTION load {#!lua name=test1
 | |
|             redis.register_function('f1', function() return 1 end)
 | |
|             redis.register_function('f2', function() return 1 end)
 | |
|         }
 | |
| 
 | |
|         catch {r FUNCTION load {#!lua name=test1
 | |
|             redis.register_function('f3', function() return 1 end)
 | |
|         }} e
 | |
|         assert_match "*Library 'test1' already exists*" $e
 | |
|         
 | |
| 
 | |
|         r function stats
 | |
|     } {running_script {} engines {LUA {libraries_count 1 functions_count 2}}}
 | |
| 
 | |
|     test {FUNCTION - function stats cleaned after flush} {
 | |
|         r function flush
 | |
|         r function stats
 | |
|     } {running_script {} engines {LUA {libraries_count 0 functions_count 0}}}
 | |
| 
 | |
|     test {FUNCTION - function test empty engine} {
 | |
|          catch {r function load replace {#! name=test
 | |
|             redis.register_function('foo', function() return 1 end)
 | |
|         }} e
 | |
|         set _ $e
 | |
|     } {ERR Engine '' not found}
 | |
| 
 | |
|     test {FUNCTION - function test unknown metadata value} {
 | |
|          catch {r function load replace {#!lua name=test foo=bar
 | |
|             redis.register_function('foo', function() return 1 end)
 | |
|         }} e
 | |
|         set _ $e
 | |
|     } {ERR Invalid metadata value given: foo=bar}
 | |
| 
 | |
|     test {FUNCTION - function test no name} {
 | |
|          catch {r function load replace {#!lua
 | |
|             redis.register_function('foo', function() return 1 end)
 | |
|         }} e
 | |
|         set _ $e
 | |
|     } {ERR Library name was not given}
 | |
| 
 | |
|     test {FUNCTION - function test multiple names} {
 | |
|          catch {r function load replace {#!lua name=foo name=bar
 | |
|             redis.register_function('foo', function() return 1 end)
 | |
|         }} e
 | |
|         set _ $e
 | |
|     } {ERR Invalid metadata value, name argument was given multiple times}
 | |
| 
 | |
|     test {FUNCTION - function test name with quotes} {
 | |
|         r function load replace {#!lua name="foo"
 | |
|             redis.register_function('foo', function() return 1 end)
 | |
|         }
 | |
|     } {foo}
 | |
| 
 | |
|     test {FUNCTION - trick global protection 1} {
 | |
|         r FUNCTION FLUSH
 | |
| 
 | |
|         r FUNCTION load {#!lua name=test1
 | |
|             redis.register_function('f1', function() 
 | |
|                 mt = getmetatable(_G)
 | |
|                 original_globals = mt.__index
 | |
|                 original_globals['redis'] = function() return 1 end
 | |
|             end)
 | |
|         }
 | |
| 
 | |
|         catch {[r fcall f1 0]} e
 | |
|         set _ $e
 | |
|     } {*Attempt to modify a readonly table*}
 | |
| 
 | |
|     test {FUNCTION - test getmetatable on script load} {
 | |
|         r FUNCTION FLUSH
 | |
| 
 | |
|         catch {
 | |
|             r FUNCTION load {#!lua name=test1
 | |
|                 mt = getmetatable(_G)
 | |
|             }
 | |
|         } e
 | |
| 
 | |
|         set _ $e
 | |
|     } {*Script attempted to access nonexistent global variable 'getmetatable'*}
 | |
| 
 | |
| }
 |