]> git.saurik.com Git - redis.git/blob - tests/unit/scripting.tcl
Differentiate SCRIPT KILL error replies.
[redis.git] / tests / unit / scripting.tcl
1 start_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 ARGS 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 - Do we get an error on invalid SHA1?} {
51 catch {r evalsha NotValidShaSUM 0} e
52 set _ $e
53 } {NOSCRIPT*}
54
55 test {EVALSHA - Do we get an error on non defined SHA1?} {
56 catch {r evalsha ffd632c7d33e571e9f24556ebed26c3479a87130 0} e
57 set _ $e
58 } {NOSCRIPT*}
59
60 test {EVAL - Redis integer -> Lua type conversion} {
61 r eval {
62 local foo = redis.pcall('incr','x')
63 return {type(foo),foo}
64 } 0
65 } {number 1}
66
67 test {EVAL - Redis bulk -> Lua type conversion} {
68 r set mykey myval
69 r eval {
70 local foo = redis.pcall('get','mykey')
71 return {type(foo),foo}
72 } 0
73 } {string myval}
74
75 test {EVAL - Redis multi bulk -> Lua type conversion} {
76 r del mylist
77 r rpush mylist a
78 r rpush mylist b
79 r rpush mylist c
80 r eval {
81 local foo = redis.pcall('lrange','mylist',0,-1)
82 return {type(foo),foo[1],foo[2],foo[3],# foo}
83 } 0
84 } {table a b c 3}
85
86 test {EVAL - Redis status reply -> Lua type conversion} {
87 r eval {
88 local foo = redis.pcall('set','mykey','myval')
89 return {type(foo),foo['ok']}
90 } 0
91 } {table OK}
92
93 test {EVAL - Redis error reply -> Lua type conversion} {
94 r set mykey myval
95 r eval {
96 local foo = redis.pcall('incr','mykey')
97 return {type(foo),foo['err']}
98 } 0
99 } {table {ERR value is not an integer or out of range}}
100
101 test {EVAL - Redis nil bulk reply -> Lua type conversion} {
102 r del mykey
103 r eval {
104 local foo = redis.pcall('get','mykey')
105 return {type(foo),foo == false}
106 } 0
107 } {boolean 1}
108
109 test {EVAL - Is Lua affecting the currently selected DB?} {
110 r set mykey "this is DB 9"
111 r select 10
112 r set mykey "this is DB 10"
113 r eval {return redis.pcall('get','mykey')} 0
114 } {this is DB 10}
115
116 test {EVAL - Is Lua seleced DB retained?} {
117 r eval {return redis.pcall('select','9')} 0
118 r get mykey
119 } {this is DB 9}
120
121 if 0 {
122 test {EVAL - Script can't run more than configured time limit} {
123 r config set lua-time-limit 1
124 catch {
125 r eval {
126 local i = 0
127 while true do i=i+1 end
128 } 0
129 } e
130 set _ $e
131 } {*execution time*}
132 }
133
134 test {EVAL - Scripts can't run certain commands} {
135 set e {}
136 catch {r eval {return redis.pcall('spop','x')} 0} e
137 set e
138 } {*not allowed*}
139
140 test {EVAL - Scripts can't run certain commands} {
141 set e {}
142 catch {
143 r eval "redis.pcall('randomkey'); return redis.pcall('set','x','ciao')" 0
144 } e
145 set e
146 } {*not allowed after*}
147
148 test {EVAL - No arguments to redis.call/pcall is considered an error} {
149 set e {}
150 catch {r eval {return redis.call()} 0} e
151 set e
152 } {*one argument*}
153
154 test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} {
155 set e {}
156 catch {
157 r eval "redis.call('nosuchcommand')" 0
158 } e
159 set e
160 } {*Unknown Redis*}
161
162 test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} {
163 set e {}
164 catch {
165 r eval "redis.call('get','a','b','c')" 0
166 } e
167 set e
168 } {*number of args*}
169
170 test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} {
171 set e {}
172 r set foo bar
173 catch {
174 r eval "redis.call('lpush','foo','val')" 0
175 } e
176 set e
177 } {*against a key*}
178
179 test {SCRIPTING FLUSH - is able to clear the scripts cache?} {
180 r set mykey myval
181 set v [r evalsha 9bd632c7d33e571e9f24556ebed26c3479a87129 0]
182 assert_equal $v myval
183 set e ""
184 r script flush
185 catch {r evalsha 9bd632c7d33e571e9f24556ebed26c3479a87129 0} e
186 set e
187 } {NOSCRIPT*}
188
189 test {SCRIPT EXISTS - can detect already defined scripts?} {
190 r eval "return 1+1" 0
191 r script exists a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bd9 a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bda
192 } {1 0}
193
194 test {SCRIPT LOAD - is able to register scripts in the scripting cache} {
195 list \
196 [r script load "return 'loaded'"] \
197 [r evalsha b534286061d4b9e4026607613b95c06c06015ae8 0]
198 } {b534286061d4b9e4026607613b95c06c06015ae8 loaded}
199
200 test "In the context of Lua the output of random commands gets ordered" {
201 r del myset
202 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
203 r eval {return redis.call('smembers','myset')} 0
204 } {a aa aaa azz b c d e f g h i l m n o p q r s t u v z}
205
206 test "SORT is normally not alpha re-ordered for the scripting engine" {
207 r del myset
208 r sadd myset 1 2 3 4 10
209 r eval {return redis.call('sort','myset','desc')} 0
210 } {10 4 3 2 1}
211
212 test "SORT BY <constant> output gets ordered for scripting" {
213 r del myset
214 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
215 r eval {return redis.call('sort','myset','by','_')} 0
216 } {a aa aaa azz b c d e f g h i l m n o p q r s t u v z}
217
218 test "SORT BY <constant> with GET gets ordered for scripting" {
219 r del myset
220 r sadd myset a b c
221 r eval {return redis.call('sort','myset','by','_','get','#','get','_:*')} 0
222 } {a {} b {} c {}}
223
224 test "redis.sha1hex() implementation" {
225 list [r eval {return redis.sha1hex('')} 0] \
226 [r eval {return redis.sha1hex('Pizza & Mandolino')} 0]
227 } {da39a3ee5e6b4b0d3255bfef95601890afd80709 74822d82031af7493c20eefa13bd07ec4fada82f}
228
229 test {Globals protection reading an undeclared global variable} {
230 catch {r eval {return a} 0} e
231 set e
232 } {*ERR*attempted to access unexisting global*}
233
234 test {Globals protection setting an undeclared global*} {
235 catch {r eval {a=10} 0} e
236 set e
237 } {*ERR*attempted to create global*}
238
239 test {Test an example script DECR_IF_GT} {
240 set decr_if_gt {
241 local current
242
243 current = redis.call('get',KEYS[1])
244 if not current then return nil end
245 if current > ARGV[1] then
246 return redis.call('decr',KEYS[1])
247 else
248 return redis.call('get',KEYS[1])
249 end
250 }
251 r set foo 5
252 set res {}
253 lappend res [r eval $decr_if_gt 1 foo 2]
254 lappend res [r eval $decr_if_gt 1 foo 2]
255 lappend res [r eval $decr_if_gt 1 foo 2]
256 lappend res [r eval $decr_if_gt 1 foo 2]
257 lappend res [r eval $decr_if_gt 1 foo 2]
258 set res
259 } {4 3 2 2 2}
260
261 test {Scripting engine resets PRNG at every script execution} {
262 set rand1 [r eval {return tostring(math.random())} 0]
263 set rand2 [r eval {return tostring(math.random())} 0]
264 assert_equal $rand1 $rand2
265 }
266
267 test {Scripting engine PRNG can be seeded correctly} {
268 set rand1 [r eval {
269 math.randomseed(ARGV[1]); return tostring(math.random())
270 } 0 10]
271 set rand2 [r eval {
272 math.randomseed(ARGV[1]); return tostring(math.random())
273 } 0 10]
274 set rand3 [r eval {
275 math.randomseed(ARGV[1]); return tostring(math.random())
276 } 0 20]
277 assert_equal $rand1 $rand2
278 assert {$rand2 ne $rand3}
279 }
280 }
281
282 # Start a new server since the last test in this stanza will kill the
283 # instance at all.
284 start_server {tags {"scripting"}} {
285 test {Timedout read-only scripts can be killed by SCRIPT KILL} {
286 set rd [redis_deferring_client]
287 r config set lua-time-limit 10
288 $rd eval {while true do end} 0
289 after 200
290 catch {r ping} e
291 assert_match {BUSY*} $e
292 r script kill
293 assert_equal [r ping] "PONG"
294 }
295
296 test {Timedout scripts that modified data can't be killed by SCRIPT KILL} {
297 set rd [redis_deferring_client]
298 r config set lua-time-limit 10
299 $rd eval {redis.call('set','x','y'); while true do end} 0
300 after 200
301 catch {r ping} e
302 assert_match {BUSY*} $e
303 catch {r script kill} e
304 assert_match {UNKILLABLE*} $e
305 catch {r ping} e
306 assert_match {BUSY*} $e
307 }
308
309 test {SHUTDOWN NOSAVE can kill a timedout script anyway} {
310 # The server sould be still unresponding to normal commands.
311 catch {r ping} e
312 assert_match {BUSY*} $e
313 catch {r shutdown nosave}
314 # Make sure the server was killed
315 catch {set rd [redis_deferring_client]} e
316 assert_match {*connection refused*} $e
317 }
318 }
319
320 start_server {tags {"scripting repl"}} {
321 start_server {} {
322 test {Before the slave connects we issue an EVAL command} {
323 r eval {return redis.call('incr','x')} 0
324 } {1}
325
326 test {Connect a slave to the main instance} {
327 r -1 slaveof [srv 0 host] [srv 0 port]
328 wait_for_condition 50 100 {
329 [s -1 role] eq {slave} &&
330 [string match {*master_link_status:up*} [r -1 info replication]]
331 } else {
332 fail "Can't turn the instance into a slave"
333 }
334 }
335
336 test {Now use EVALSHA against the master} {
337 r evalsha ae3477e27be955de7e1bc9adfdca626b478d3cb2 0
338 } {2}
339
340 test {If EVALSHA was replicated as EVAL the slave should be ok} {
341 wait_for_condition 50 100 {
342 [r -1 get x] eq {2}
343 } else {
344 fail "Expected 2 in x, but value is '[r -1 get x]'"
345 }
346 }
347
348 test {Replication of script multiple pushes to list with BLPOP} {
349 set rd [redis_deferring_client]
350 $rd brpop a 0
351 r eval {
352 redis.call("lpush","a","1");
353 redis.call("lpush","a","2");
354 } 0
355 set res [$rd read]
356 $rd close
357 wait_for_condition 50 100 {
358 [r -1 lrange a 0 -1] eq [r lrange a 0 -1]
359 } else {
360 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]'"
361 }
362 set res
363 } {a 1}
364 }
365 }