]> git.saurik.com Git - redis.git/blob - tests/unit/scripting.tcl
SDIFF fuzz test added.
[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 - 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
286 # Start a new server since the last test in this stanza will kill the
287 # instance at all.
288 start_server {tags {"scripting"}} {
289 test {Timedout read-only scripts can be killed by SCRIPT KILL} {
290 set rd [redis_deferring_client]
291 r config set lua-time-limit 10
292 $rd eval {while true do end} 0
293 after 200
294 catch {r ping} e
295 assert_match {BUSY*} $e
296 r script kill
297 assert_equal [r ping] "PONG"
298 }
299
300 test {Timedout scripts that modified data can't be killed by SCRIPT KILL} {
301 set rd [redis_deferring_client]
302 r config set lua-time-limit 10
303 $rd eval {redis.call('set','x','y'); while true do end} 0
304 after 200
305 catch {r ping} e
306 assert_match {BUSY*} $e
307 catch {r script kill} e
308 assert_match {UNKILLABLE*} $e
309 catch {r ping} e
310 assert_match {BUSY*} $e
311 }
312
313 test {SHUTDOWN NOSAVE can kill a timedout script anyway} {
314 # The server sould be still unresponding to normal commands.
315 catch {r ping} e
316 assert_match {BUSY*} $e
317 catch {r shutdown nosave}
318 # Make sure the server was killed
319 catch {set rd [redis_deferring_client]} e
320 assert_match {*connection refused*} $e
321 }
322 }
323
324 start_server {tags {"scripting repl"}} {
325 start_server {} {
326 test {Before the slave connects we issue an EVAL command} {
327 r eval {return redis.call('incr','x')} 0
328 } {1}
329
330 test {Connect a slave to the main instance} {
331 r -1 slaveof [srv 0 host] [srv 0 port]
332 wait_for_condition 50 100 {
333 [s -1 role] eq {slave} &&
334 [string match {*master_link_status:up*} [r -1 info replication]]
335 } else {
336 fail "Can't turn the instance into a slave"
337 }
338 }
339
340 test {Now use EVALSHA against the master} {
341 r evalsha ae3477e27be955de7e1bc9adfdca626b478d3cb2 0
342 } {2}
343
344 test {If EVALSHA was replicated as EVAL the slave should be ok} {
345 wait_for_condition 50 100 {
346 [r -1 get x] eq {2}
347 } else {
348 fail "Expected 2 in x, but value is '[r -1 get x]'"
349 }
350 }
351
352 test {Replication of script multiple pushes to list with BLPOP} {
353 set rd [redis_deferring_client]
354 $rd brpop a 0
355 r eval {
356 redis.call("lpush","a","1");
357 redis.call("lpush","a","2");
358 } 0
359 set res [$rd read]
360 $rd close
361 wait_for_condition 50 100 {
362 [r -1 lrange a 0 -1] eq [r lrange a 0 -1]
363 } else {
364 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]'"
365 }
366 set res
367 } {a 1}
368 }
369 }