xref: /redis-3.2.3/tests/unit/scripting.tcl (revision 6972f18c)
1start_server {tags {"scripting"}} {
2    test {EVAL - Does Lua interpreter replies to our requests?} {
3        r eval {return 'hello'} 0
4    } {hello}
5
6    test {EVAL - Lua integer -> Redis protocol type conversion} {
7        r eval {return 100.5} 0
8    } {100}
9
10    test {EVAL - Lua string -> Redis protocol type conversion} {
11        r eval {return 'hello world'} 0
12    } {hello world}
13
14    test {EVAL - Lua true boolean -> Redis protocol type conversion} {
15        r eval {return true} 0
16    } {1}
17
18    test {EVAL - Lua false boolean -> Redis protocol type conversion} {
19        r eval {return false} 0
20    } {}
21
22    test {EVAL - Lua status code reply -> Redis protocol type conversion} {
23        r eval {return {ok='fine'}} 0
24    } {fine}
25
26    test {EVAL - Lua error reply -> Redis protocol type conversion} {
27        catch {
28            r eval {return {err='this is an error'}} 0
29        } e
30        set _ $e
31    } {this is an error}
32
33    test {EVAL - Lua table -> Redis protocol type conversion} {
34        r eval {return {1,2,3,'ciao',{1,2}}} 0
35    } {1 2 3 ciao {1 2}}
36
37    test {EVAL - Are the KEYS and ARGV arrays populated correctly?} {
38        r eval {return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}} 2 a b c d
39    } {a b c d}
40
41    test {EVAL - is Lua able to call Redis API?} {
42        r set mykey myval
43        r eval {return redis.call('get','mykey')} 0
44    } {myval}
45
46    test {EVALSHA - Can we call a SHA1 if already defined?} {
47        r evalsha 9bd632c7d33e571e9f24556ebed26c3479a87129 0
48    } {myval}
49
50    test {EVALSHA - Can we call a SHA1 in uppercase?} {
51        r evalsha 9BD632C7D33E571E9F24556EBED26C3479A87129 0
52    } {myval}
53
54    test {EVALSHA - Do we get an error on invalid SHA1?} {
55        catch {r evalsha NotValidShaSUM 0} e
56        set _ $e
57    } {NOSCRIPT*}
58
59    test {EVALSHA - Do we get an error on non defined SHA1?} {
60        catch {r evalsha ffd632c7d33e571e9f24556ebed26c3479a87130 0} e
61        set _ $e
62    } {NOSCRIPT*}
63
64    test {EVAL - Redis integer -> Lua type conversion} {
65        r eval {
66            local foo = redis.pcall('incr','x')
67            return {type(foo),foo}
68        } 0
69    } {number 1}
70
71    test {EVAL - Redis bulk -> Lua type conversion} {
72        r set mykey myval
73        r eval {
74            local foo = redis.pcall('get','mykey')
75            return {type(foo),foo}
76        } 0
77    } {string myval}
78
79    test {EVAL - Redis multi bulk -> Lua type conversion} {
80        r del mylist
81        r rpush mylist a
82        r rpush mylist b
83        r rpush mylist c
84        r eval {
85            local foo = redis.pcall('lrange','mylist',0,-1)
86            return {type(foo),foo[1],foo[2],foo[3],# foo}
87        } 0
88    } {table a b c 3}
89
90    test {EVAL - Redis status reply -> Lua type conversion} {
91        r eval {
92            local foo = redis.pcall('set','mykey','myval')
93            return {type(foo),foo['ok']}
94        } 0
95    } {table OK}
96
97    test {EVAL - Redis error reply -> Lua type conversion} {
98        r set mykey myval
99        r eval {
100            local foo = redis.pcall('incr','mykey')
101            return {type(foo),foo['err']}
102        } 0
103    } {table {ERR value is not an integer or out of range}}
104
105    test {EVAL - Redis nil bulk reply -> Lua type conversion} {
106        r del mykey
107        r eval {
108            local foo = redis.pcall('get','mykey')
109            return {type(foo),foo == false}
110        } 0
111    } {boolean 1}
112
113    test {EVAL - Is Lua affecting the currently selected DB?} {
114        r set mykey "this is DB 9"
115        r select 10
116        r set mykey "this is DB 10"
117        r eval {return redis.pcall('get','mykey')} 0
118    } {this is DB 10}
119
120    test {EVAL - Is Lua seleced DB retained?} {
121        r eval {return redis.pcall('select','9')} 0
122        r get mykey
123    } {this is DB 9}
124
125    if 0 {
126        test {EVAL - Script can't run more than configured time limit} {
127            r config set lua-time-limit 1
128            catch {
129                r eval {
130                    local i = 0
131                    while true do i=i+1 end
132                } 0
133            } e
134            set _ $e
135        } {*execution time*}
136    }
137
138    test {EVAL - Scripts can't run certain commands} {
139        set e {}
140        catch {r eval {return redis.pcall('spop','x')} 0} e
141        set e
142    } {*not allowed*}
143
144    test {EVAL - Scripts can't run certain commands} {
145        set e {}
146        catch {
147            r eval "redis.pcall('randomkey'); return redis.pcall('set','x','ciao')" 0
148        } e
149        set e
150    } {*not allowed after*}
151
152    test {EVAL - No arguments to redis.call/pcall is considered an error} {
153        set e {}
154        catch {r eval {return redis.call()} 0} e
155        set e
156    } {*one argument*}
157
158    test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} {
159        set e {}
160        catch {
161            r eval "redis.call('nosuchcommand')" 0
162        } e
163        set e
164    } {*Unknown Redis*}
165
166    test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} {
167        set e {}
168        catch {
169            r eval "redis.call('get','a','b','c')" 0
170        } e
171        set e
172    } {*number of args*}
173
174    test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} {
175        set e {}
176        r set foo bar
177        catch {
178            r eval "redis.call('lpush','foo','val')" 0
179        } e
180        set e
181    } {*against a key*}
182
183    test {SCRIPTING FLUSH - is able to clear the scripts cache?} {
184        r set mykey myval
185        set v [r evalsha 9bd632c7d33e571e9f24556ebed26c3479a87129 0]
186        assert_equal $v myval
187        set e ""
188        r script flush
189        catch {r evalsha 9bd632c7d33e571e9f24556ebed26c3479a87129 0} e
190        set e
191    } {NOSCRIPT*}
192
193    test {SCRIPT EXISTS - can detect already defined scripts?} {
194        r eval "return 1+1" 0
195        r script exists a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bd9 a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bda
196    } {1 0}
197
198    test {SCRIPT LOAD - is able to register scripts in the scripting cache} {
199        list \
200            [r script load "return 'loaded'"] \
201            [r evalsha b534286061d4b9e4026607613b95c06c06015ae8 0]
202    } {b534286061d4b9e4026607613b95c06c06015ae8 loaded}
203
204    test "In the context of Lua the output of random commands gets ordered" {
205        r del myset
206        r sadd myset a b c d e f g h i l m n o p q r s t u v z aa aaa azz
207        r eval {return redis.call('smembers','myset')} 0
208    } {a aa aaa azz b c d e f g h i l m n o p q r s t u v z}
209
210    test "SORT is normally not alpha re-ordered for the scripting engine" {
211        r del myset
212        r sadd myset 1 2 3 4 10
213        r eval {return redis.call('sort','myset','desc')} 0
214    } {10 4 3 2 1}
215
216    test "SORT BY <constant> output gets ordered for scripting" {
217        r del myset
218        r sadd myset a b c d e f g h i l m n o p q r s t u v z aa aaa azz
219        r eval {return redis.call('sort','myset','by','_')} 0
220    } {a aa aaa azz b c d e f g h i l m n o p q r s t u v z}
221
222    test "SORT BY <constant> with GET gets ordered for scripting" {
223        r del myset
224        r sadd myset a b c
225        r eval {return redis.call('sort','myset','by','_','get','#','get','_:*')} 0
226    } {a {} b {} c {}}
227
228    test "redis.sha1hex() implementation" {
229        list [r eval {return redis.sha1hex('')} 0] \
230             [r eval {return redis.sha1hex('Pizza & Mandolino')} 0]
231    } {da39a3ee5e6b4b0d3255bfef95601890afd80709 74822d82031af7493c20eefa13bd07ec4fada82f}
232
233    test {Globals protection reading an undeclared global variable} {
234        catch {r eval {return a} 0} e
235        set e
236    } {*ERR*attempted to access unexisting global*}
237
238    test {Globals protection setting an undeclared global*} {
239        catch {r eval {a=10} 0} e
240        set e
241    } {*ERR*attempted to create global*}
242
243    test {Test an example script DECR_IF_GT} {
244        set decr_if_gt {
245            local current
246
247            current = redis.call('get',KEYS[1])
248            if not current then return nil end
249            if current > ARGV[1] then
250                return redis.call('decr',KEYS[1])
251            else
252                return redis.call('get',KEYS[1])
253            end
254        }
255        r set foo 5
256        set res {}
257        lappend res [r eval $decr_if_gt 1 foo 2]
258        lappend res [r eval $decr_if_gt 1 foo 2]
259        lappend res [r eval $decr_if_gt 1 foo 2]
260        lappend res [r eval $decr_if_gt 1 foo 2]
261        lappend res [r eval $decr_if_gt 1 foo 2]
262        set res
263    } {4 3 2 2 2}
264
265    test {Scripting engine resets PRNG at every script execution} {
266        set rand1 [r eval {return tostring(math.random())} 0]
267        set rand2 [r eval {return tostring(math.random())} 0]
268        assert_equal $rand1 $rand2
269    }
270
271    test {Scripting engine PRNG can be seeded correctly} {
272        set rand1 [r eval {
273            math.randomseed(ARGV[1]); return tostring(math.random())
274        } 0 10]
275        set rand2 [r eval {
276            math.randomseed(ARGV[1]); return tostring(math.random())
277        } 0 10]
278        set rand3 [r eval {
279            math.randomseed(ARGV[1]); return tostring(math.random())
280        } 0 20]
281        assert_equal $rand1 $rand2
282        assert {$rand2 ne $rand3}
283    }
284
285    test {EVAL does not leak in the Lua stack} {
286        r set x 0
287        # Use a non blocking client to speedup the loop.
288        set rd [redis_deferring_client]
289        for {set j 0} {$j < 10000} {incr j} {
290            $rd eval {return redis.call("incr",KEYS[1])} 1 x
291        }
292        for {set j 0} {$j < 10000} {incr j} {
293            $rd read
294        }
295        assert {[s used_memory_lua] < 1024*100}
296        $rd close
297        r get x
298    } {10000}
299
300    test {EVAL processes writes from AOF in read-only slaves} {
301        r flushall
302        r config set appendonly yes
303        r eval {redis.call("set","foo","100")} 0
304        r eval {redis.call("incr","foo")} 0
305        r eval {redis.call("incr","foo")} 0
306        wait_for_condition 50 100 {
307            [s aof_rewrite_in_progress] == 0
308        } else {
309            fail "AOF rewrite can't complete after CONFIG SET appendonly yes."
310        }
311        r config set slave-read-only yes
312        r slaveof 127.0.0.1 0
313        r debug loadaof
314        r get foo
315    } {102}
316}
317
318# Start a new server since the last test in this stanza will kill the
319# instance at all.
320start_server {tags {"scripting"}} {
321    test {Timedout read-only scripts can be killed by SCRIPT KILL} {
322        set rd [redis_deferring_client]
323        r config set lua-time-limit 10
324        $rd eval {while true do end} 0
325        after 200
326        catch {r ping} e
327        assert_match {BUSY*} $e
328        r script kill
329        assert_equal [r ping] "PONG"
330    }
331
332    test {Timedout script link is still usable after Lua returns} {
333        r config set lua-time-limit 10
334        r eval {for i=1,100000 do redis.call('ping') end return 'ok'} 0
335        r ping
336    } {PONG}
337
338    test {Timedout scripts that modified data can't be killed by SCRIPT KILL} {
339        set rd [redis_deferring_client]
340        r config set lua-time-limit 10
341        $rd eval {redis.call('set','x','y'); while true do end} 0
342        after 200
343        catch {r ping} e
344        assert_match {BUSY*} $e
345        catch {r script kill} e
346        assert_match {UNKILLABLE*} $e
347        catch {r ping} e
348        assert_match {BUSY*} $e
349    }
350
351    # Note: keep this test at the end of this server stanza because it
352    # kills the server.
353    test {SHUTDOWN NOSAVE can kill a timedout script anyway} {
354        # The server sould be still unresponding to normal commands.
355        catch {r ping} e
356        assert_match {BUSY*} $e
357        catch {r shutdown nosave}
358        # Make sure the server was killed
359        catch {set rd [redis_deferring_client]} e
360        assert_match {*connection refused*} $e
361    }
362}
363
364start_server {tags {"scripting repl"}} {
365    start_server {} {
366        test {Before the slave connects we issue two EVAL commands} {
367            # One with an error, but still executing a command.
368            # SHA is: 6e8bd6bdccbe78899e3cc06b31b6dbf4324c2e56
369            catch {
370                r eval {redis.call('incr','x'); redis.call('nonexisting')} 0
371            }
372            # One command is correct:
373            # SHA is: ae3477e27be955de7e1bc9adfdca626b478d3cb2
374            r eval {return redis.call('incr','x')} 0
375        } {2}
376
377        test {Connect a slave to the main instance} {
378            r -1 slaveof [srv 0 host] [srv 0 port]
379            wait_for_condition 50 100 {
380                [s -1 role] eq {slave} &&
381                [string match {*master_link_status:up*} [r -1 info replication]]
382            } else {
383                fail "Can't turn the instance into a slave"
384            }
385        }
386
387        test {Now use EVALSHA against the master, with both SHAs} {
388            # The server should replicate successful and unsuccessful
389            # commands as EVAL instead of EVALSHA.
390            catch {
391                r evalsha 6e8bd6bdccbe78899e3cc06b31b6dbf4324c2e56 0
392            }
393            r evalsha ae3477e27be955de7e1bc9adfdca626b478d3cb2 0
394        } {4}
395
396        test {If EVALSHA was replicated as EVAL, 'x' should be '4'} {
397            wait_for_condition 50 100 {
398                [r -1 get x] eq {4}
399            } else {
400                fail "Expected 4 in x, but value is '[r -1 get x]'"
401            }
402        }
403
404        test {Replication of script multiple pushes to list with BLPOP} {
405            set rd [redis_deferring_client]
406            $rd brpop a 0
407            r eval {
408                redis.call("lpush","a","1");
409                redis.call("lpush","a","2");
410            } 0
411            set res [$rd read]
412            $rd close
413            wait_for_condition 50 100 {
414                [r -1 lrange a 0 -1] eq [r lrange a 0 -1]
415            } else {
416                fail "Expected list 'a' in slave and master to be the same, but they are respectively '[r -1 lrange a 0 -1]' and '[r lrange a 0 -1]'"
417            }
418            set res
419        } {a 1}
420
421        test {EVALSHA replication when first call is readonly} {
422            r del x
423            r eval {if tonumber(KEYS[1]) > 0 then redis.call('incr', 'x') end} 1 0
424            r evalsha 38fe3ddf5284a1d48f37f824b4c4e826879f3cb9 1 0
425            r evalsha 38fe3ddf5284a1d48f37f824b4c4e826879f3cb9 1 1
426            wait_for_condition 50 100 {
427                [r -1 get x] eq {1}
428            } else {
429                fail "Expected 1 in x, but value is '[r -1 get x]'"
430            }
431        }
432    }
433}
434