aboutsummaryrefslogtreecommitdiff
path: root/examples/redis-unstable/tests/unit/scripting.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'examples/redis-unstable/tests/unit/scripting.tcl')
-rw-r--r--examples/redis-unstable/tests/unit/scripting.tcl2688
1 files changed, 0 insertions, 2688 deletions
diff --git a/examples/redis-unstable/tests/unit/scripting.tcl b/examples/redis-unstable/tests/unit/scripting.tcl
deleted file mode 100644
index 911f114..0000000
--- a/examples/redis-unstable/tests/unit/scripting.tcl
+++ /dev/null
@@ -1,2688 +0,0 @@
1#
2# Copyright (c) 2009-Present, Redis Ltd.
3# All rights reserved.
4#
5# Copyright (c) 2024-present, Valkey contributors.
6# All rights reserved.
7#
8# Licensed under your choice of (a) the Redis Source Available License 2.0
9# (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
10# GNU Affero General Public License v3 (AGPLv3).
11#
12# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
13#
14
15foreach is_eval {0 1} {
16
17if {$is_eval == 1} {
18 proc run_script {args} {
19 r eval {*}$args
20 }
21 proc run_script_ro {args} {
22 r eval_ro {*}$args
23 }
24 proc run_script_on_connection {args} {
25 [lindex $args 0] eval {*}[lrange $args 1 end]
26 }
27 proc kill_script {args} {
28 r script kill
29 }
30} else {
31 proc run_script {args} {
32 r function load replace [format "#!lua name=test\nredis.register_function('test', function(KEYS, ARGV)\n %s \nend)" [lindex $args 0]]
33 if {[r readingraw] eq 1} {
34 # read name
35 assert_equal {test} [r read]
36 }
37 r fcall test {*}[lrange $args 1 end]
38 }
39 proc run_script_ro {args} {
40 r function load replace [format "#!lua name=test\nredis.register_function{function_name='test', callback=function(KEYS, ARGV)\n %s \nend, flags={'no-writes'}}" [lindex $args 0]]
41 if {[r readingraw] eq 1} {
42 # read name
43 assert_equal {test} [r read]
44 }
45 r fcall_ro test {*}[lrange $args 1 end]
46 }
47 proc run_script_on_connection {args} {
48 set rd [lindex $args 0]
49 $rd function load replace [format "#!lua name=test\nredis.register_function('test', function(KEYS, ARGV)\n %s \nend)" [lindex $args 1]]
50 # read name
51 $rd read
52 $rd fcall test {*}[lrange $args 2 end]
53 }
54 proc kill_script {args} {
55 r function kill
56 }
57}
58
59start_server {tags {"scripting"}} {
60
61 if {$is_eval eq 1} {
62 test {Script - disallow write on OOM} {
63 r config set maxmemory 1
64
65 catch {[r eval "redis.call('set', 'x', 1)" 0]} e
66 assert_match {*command not allowed when used memory*} $e
67
68 r config set maxmemory 0
69 } {OK} {needs:config-maxmemory}
70 } ;# is_eval
71
72 test {EVAL - Does Lua interpreter replies to our requests?} {
73 run_script {return 'hello'} 0
74 } {hello}
75
76 test {EVAL - Return _G} {
77 run_script {return _G} 0
78 } {}
79
80 test {EVAL - Return table with a metatable that raise error} {
81 run_script {local a = {}; setmetatable(a,{__index=function() foo() end}) return a} 0
82 } {}
83
84 test {EVAL - Return table with a metatable that call redis} {
85 run_script {local a = {}; setmetatable(a,{__index=function() redis.call('set', 'x', '1') end}) return a} 1 x
86 # make sure x was not set
87 r get x
88 } {}
89
90 test {EVAL - Lua integer -> Redis protocol type conversion} {
91 run_script {return 100.5} 0
92 } {100}
93
94 test {EVAL - Lua string -> Redis protocol type conversion} {
95 run_script {return 'hello world'} 0
96 } {hello world}
97
98 test {EVAL - Lua true boolean -> Redis protocol type conversion} {
99 run_script {return true} 0
100 } {1}
101
102 test {EVAL - Lua false boolean -> Redis protocol type conversion} {
103 run_script {return false} 0
104 } {}
105
106 test {EVAL - Lua status code reply -> Redis protocol type conversion} {
107 run_script {return {ok='fine'}} 0
108 } {fine}
109
110 test {EVAL - Lua error reply -> Redis protocol type conversion} {
111 catch {
112 run_script {return {err='ERR this is an error'}} 0
113 } e
114 set _ $e
115 } {ERR this is an error}
116
117 test {EVAL - Lua table -> Redis protocol type conversion} {
118 run_script {return {1,2,3,'ciao',{1,2}}} 0
119 } {1 2 3 ciao {1 2}}
120
121 test {EVAL - Are the KEYS and ARGV arrays populated correctly?} {
122 run_script {return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}} 2 a{t} b{t} c{t} d{t}
123 } {a{t} b{t} c{t} d{t}}
124
125 test {EVAL - is Lua able to call Redis API?} {
126 r set mykey myval
127 run_script {return redis.call('get',KEYS[1])} 1 mykey
128 } {myval}
129
130 if {$is_eval eq 1} {
131 # eval sha is only relevant for is_eval Lua
132 test {EVALSHA - Can we call a SHA1 if already defined?} {
133 r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey
134 } {myval}
135
136 test {EVALSHA_RO - Can we call a SHA1 if already defined?} {
137 r evalsha_ro fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey
138 } {myval}
139
140 test {EVALSHA - Can we call a SHA1 in uppercase?} {
141 r evalsha FD758D1589D044DD850A6F05D52F2EEFD27F033F 1 mykey
142 } {myval}
143
144 test {EVALSHA - Do we get an error on invalid SHA1?} {
145 catch {r evalsha NotValidShaSUM 0} e
146 set _ $e
147 } {NOSCRIPT*}
148
149 test {EVALSHA - Do we get an error on non defined SHA1?} {
150 catch {r evalsha ffd632c7d33e571e9f24556ebed26c3479a87130 0} e
151 set _ $e
152 } {NOSCRIPT*}
153 } ;# is_eval
154
155 test {EVAL - Redis integer -> Lua type conversion} {
156 r set x 0
157 run_script {
158 local foo = redis.pcall('incr',KEYS[1])
159 return {type(foo),foo}
160 } 1 x
161 } {number 1}
162
163 test {EVAL - Lua number -> Redis integer conversion} {
164 r del hash
165 run_script {
166 local foo = redis.pcall('hincrby','hash','field',200000000)
167 return {type(foo),foo}
168 } 0
169 } {number 200000000}
170
171 test {EVAL - Redis bulk -> Lua type conversion} {
172 r set mykey myval
173 run_script {
174 local foo = redis.pcall('get',KEYS[1])
175 return {type(foo),foo}
176 } 1 mykey
177 } {string myval}
178
179 test {EVAL - Redis multi bulk -> Lua type conversion} {
180 r del mylist
181 r rpush mylist a
182 r rpush mylist b
183 r rpush mylist c
184 run_script {
185 local foo = redis.pcall('lrange',KEYS[1],0,-1)
186 return {type(foo),foo[1],foo[2],foo[3],# foo}
187 } 1 mylist
188 } {table a b c 3}
189
190 test {EVAL - Redis status reply -> Lua type conversion} {
191 run_script {
192 local foo = redis.pcall('set',KEYS[1],'myval')
193 return {type(foo),foo['ok']}
194 } 1 mykey
195 } {table OK}
196
197 test {EVAL - Redis error reply -> Lua type conversion} {
198 r set mykey myval
199 run_script {
200 local foo = redis.pcall('incr',KEYS[1])
201 return {type(foo),foo['err']}
202 } 1 mykey
203 } {table {ERR value is not an integer or out of range}}
204
205 test {EVAL - Redis nil bulk reply -> Lua type conversion} {
206 r del mykey
207 run_script {
208 local foo = redis.pcall('get',KEYS[1])
209 return {type(foo),foo == false}
210 } 1 mykey
211 } {boolean 1}
212
213 test {EVAL - Is the Lua client using the currently selected DB?} {
214 r set mykey "this is DB 9"
215 r select 10
216 r set mykey "this is DB 10"
217 run_script {return redis.pcall('get',KEYS[1])} 1 mykey
218 } {this is DB 10} {singledb:skip}
219
220 test {EVAL - SELECT inside Lua should not affect the caller} {
221 # here we DB 10 is selected
222 r set mykey "original value"
223 run_script {return redis.pcall('select','9')} 0
224 set res [r get mykey]
225 r select 9
226 set res
227 } {original value} {singledb:skip}
228
229 if 0 {
230 test {EVAL - Script can't run more than configured time limit} {
231 r config set lua-time-limit 1
232 catch {
233 run_script {
234 local i = 0
235 while true do i=i+1 end
236 } 0
237 } e
238 set _ $e
239 } {*execution time*}
240 }
241
242 test {EVAL - Scripts do not block on blpop command} {
243 r lpush l 1
244 r lpop l
245 run_script {return redis.pcall('blpop','l',0)} 1 l
246 } {}
247
248 test {EVAL - Scripts do not block on brpop command} {
249 r lpush l 1
250 r lpop l
251 run_script {return redis.pcall('brpop','l',0)} 1 l
252 } {}
253
254 test {EVAL - Scripts do not block on brpoplpush command} {
255 r lpush empty_list1{t} 1
256 r lpop empty_list1{t}
257 run_script {return redis.pcall('brpoplpush','empty_list1{t}', 'empty_list2{t}',0)} 2 empty_list1{t} empty_list2{t}
258 } {}
259
260 test {EVAL - Scripts do not block on blmove command} {
261 r lpush empty_list1{t} 1
262 r lpop empty_list1{t}
263 run_script {return redis.pcall('blmove','empty_list1{t}', 'empty_list2{t}', 'LEFT', 'LEFT', 0)} 2 empty_list1{t} empty_list2{t}
264 } {}
265
266 test {EVAL - Scripts do not block on bzpopmin command} {
267 r zadd empty_zset 10 foo
268 r zmpop 1 empty_zset MIN
269 run_script {return redis.pcall('bzpopmin','empty_zset', 0)} 1 empty_zset
270 } {}
271
272 test {EVAL - Scripts do not block on bzpopmax command} {
273 r zadd empty_zset 10 foo
274 r zmpop 1 empty_zset MIN
275 run_script {return redis.pcall('bzpopmax','empty_zset', 0)} 1 empty_zset
276 } {}
277
278 test {EVAL - Scripts do not block on wait} {
279 run_script {return redis.pcall('wait','1','0')} 0
280 } {0}
281
282 test {EVAL - Scripts do not block on waitaof} {
283 r config set appendonly no
284 run_script {return redis.pcall('waitaof','0','1','0')} 0
285 } {0 0}
286
287 test {EVAL - Scripts do not block on XREAD with BLOCK option} {
288 r del s
289 r xgroup create s g $ MKSTREAM
290 set res [run_script {return redis.pcall('xread','STREAMS','s','$')} 1 s]
291 assert {$res eq {}}
292 run_script {return redis.pcall('xread','BLOCK',0,'STREAMS','s','$')} 1 s
293 } {}
294
295 test {EVAL - Scripts do not block on XREADGROUP with BLOCK option} {
296 set res [run_script {return redis.pcall('xreadgroup','group','g','c','STREAMS','s','>')} 1 s]
297 assert {$res eq {}}
298 run_script {return redis.pcall('xreadgroup','group','g','c','BLOCK',0,'STREAMS','s','>')} 1 s
299 } {}
300
301 test {EVAL - Scripts do not block on XREAD with BLOCK option -- non empty stream} {
302 r XADD s * a 1
303 set res [run_script {return redis.pcall('xread','BLOCK',0,'STREAMS','s','$')} 1 s]
304 assert {$res eq {}}
305
306 set res [run_script {return redis.pcall('xread','BLOCK',0,'STREAMS','s','0-0')} 1 s]
307 assert {[lrange [lindex $res 0 1 0 1] 0 1] eq {a 1}}
308 }
309
310 test {EVAL - Scripts do not block on XREADGROUP with BLOCK option -- non empty stream} {
311 r XADD s * b 2
312 set res [
313 run_script {return redis.pcall('xreadgroup','group','g','c','BLOCK',0,'STREAMS','s','>')} 1 s
314 ]
315 assert {[llength [lindex $res 0 1]] == 2}
316 lindex $res 0 1 0 1
317 } {a 1}
318
319 test {EVAL - Scripts can run non-deterministic commands} {
320 set e {}
321 catch {
322 run_script {redis.pcall('randomkey'); return redis.pcall('set','x','ciao')} 1 x
323 } e
324 set e
325 } {*OK*}
326
327 test {EVAL - No arguments to redis.call/pcall is considered an error} {
328 set e {}
329 catch {run_script {return redis.call()} 0} e
330 set e
331 } {*one argument*}
332
333 test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} {
334 set e {}
335 catch {
336 run_script "redis.call('nosuchcommand')" 0
337 } e
338 set e
339 } {*Unknown Redis*}
340
341 test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} {
342 set e {}
343 catch {
344 run_script "redis.call('get','a','b','c')" 0
345 } e
346 set e
347 } {*number of args*}
348
349 test {EVAL - redis.call variant raises a Lua error on Redis cmd error (1)} {
350 set e {}
351 r set foo bar
352 catch {
353 run_script {redis.call('lpush',KEYS[1],'val')} 1 foo
354 } e
355 set e
356 } {*against a key*}
357
358 test {EVAL - Test table unpack with invalid indexes} {
359 catch {run_script { return {unpack({1,2,3}, -2, 2147483647)} } 0} e
360 assert_match {*too many results to unpack*} $e
361 catch {run_script { return {unpack({1,2,3}, 0, 2147483647)} } 0} e
362 assert_match {*too many results to unpack*} $e
363 catch {run_script { return {unpack({1,2,3}, -2147483648, -2)} } 0} e
364 assert_match {*too many results to unpack*} $e
365 set res [run_script { return {unpack({1,2,3}, -1, -2)} } 0]
366 assert_match {} $res
367 set res [run_script { return {unpack({1,2,3}, 1, -1)} } 0]
368 assert_match {} $res
369
370 # unpack with range -1 to 5, verify nil indexes
371 set res [run_script {
372 local function unpack_to_list(t, i, j)
373 local n, v = select('#', unpack(t, i, j)), {unpack(t, i, j)}
374 for i = 1, n do v[i] = v[i] or '_NIL_' end
375 v.n = n
376 return v
377 end
378
379 return unpack_to_list({1,2,3}, -1, 5)
380 } 0]
381 assert_match {_NIL_ _NIL_ 1 2 3 _NIL_ _NIL_} $res
382
383 # unpack with negative range, verify nil indexes
384 set res [run_script {
385 local function unpack_to_list(t, i, j)
386 local n, v = select('#', unpack(t, i, j)), {unpack(t, i, j)}
387 for i = 1, n do v[i] = v[i] or '_NIL_' end
388 v.n = n
389 return v
390 end
391
392 return unpack_to_list({1,2,3}, -2147483648, -2147483646)
393 } 0]
394 assert_match {_NIL_ _NIL_ _NIL_} $res
395 } {}
396
397 test {EVAL - JSON numeric decoding} {
398 # We must return the table as a string because otherwise
399 # Redis converts floats to ints and we get 0 and 1023 instead
400 # of 0.0003 and 1023.2 as the parsed output.
401 run_script {return
402 table.concat(
403 cjson.decode(
404 "[0.0, -5e3, -1, 0.3e-3, 1023.2, 0e10]"), " ")
405 } 0
406 } {0 -5000 -1 0.0003 1023.2 0}
407
408 test {EVAL - JSON string decoding} {
409 run_script {local decoded = cjson.decode('{"keya": "a", "keyb": "b"}')
410 return {decoded.keya, decoded.keyb}
411 } 0
412 } {a b}
413
414 test {EVAL - JSON empty array decoding} {
415 # Default behavior
416 assert_equal "{}" [run_script {
417 return cjson.encode(cjson.decode('[]'))
418 } 0]
419 assert_equal "{}" [run_script {
420 cjson.decode_array_with_array_mt(false)
421 return cjson.encode(cjson.decode('[]'))
422 } 0]
423 assert_equal "{\"item\":{}}" [run_script {
424 cjson.decode_array_with_array_mt(false)
425 return cjson.encode(cjson.decode('{"item": []}'))
426 } 0]
427
428 # With array metatable
429 assert_equal "\[\]" [run_script {
430 cjson.decode_array_with_array_mt(true)
431 return cjson.encode(cjson.decode('[]'))
432 } 0]
433 assert_equal "{\"item\":\[\]}" [run_script {
434 cjson.decode_array_with_array_mt(true)
435 return cjson.encode(cjson.decode('{"item": []}'))
436 } 0]
437 }
438
439 test {EVAL - JSON empty array decoding after element removal} {
440 # Default: emptied array becomes object
441 assert_equal "{}" [run_script {
442 cjson.decode_array_with_array_mt(false)
443 local t = cjson.decode('[1, 2]')
444 -- emptying the array
445 t[1] = nil
446 t[2] = nil
447 return cjson.encode(t)
448 } 0]
449
450 # With array metatable: emptied array stays array
451 assert_equal "\[\]" [run_script {
452 cjson.decode_array_with_array_mt(true)
453 local t = cjson.decode('[1, 2]')
454 -- emptying the array
455 t[1] = nil
456 t[2] = nil
457 return cjson.encode(t)
458 } 0]
459 }
460
461 test {EVAL - cjson array metatable modification should be readonly} {
462 catch {
463 run_script {
464 cjson.decode_array_with_array_mt(true)
465 local t = cjson.decode('[]')
466 getmetatable(t).__is_cjson_array = function() return 1 end
467 return cjson.encode(t)
468 } 0
469 } e
470 set _ $e
471 } {*Attempt to modify a readonly table*}
472
473 test {EVAL - JSON smoke test} {
474 run_script {
475 local some_map = {
476 s1="Some string",
477 n1=100,
478 a1={"Some","String","Array"},
479 nil1=nil,
480 b1=true,
481 b2=false}
482 local encoded = cjson.encode(some_map)
483 local decoded = cjson.decode(encoded)
484 assert(table.concat(some_map) == table.concat(decoded))
485
486 cjson.encode_keep_buffer(false)
487 encoded = cjson.encode(some_map)
488 decoded = cjson.decode(encoded)
489 assert(table.concat(some_map) == table.concat(decoded))
490
491 -- Table with numeric keys
492 local table1 = {one="one", [1]="one"}
493 encoded = cjson.encode(table1)
494 decoded = cjson.decode(encoded)
495 assert(decoded["one"] == table1["one"])
496 assert(decoded["1"] == table1[1])
497
498 -- Array
499 local array1 = {[1]="one", [2]="two"}
500 encoded = cjson.encode(array1)
501 decoded = cjson.decode(encoded)
502 assert(table.concat(array1) == table.concat(decoded))
503
504 -- Invalid keys
505 local invalid_map = {}
506 invalid_map[false] = "false"
507 local ok, encoded = pcall(cjson.encode, invalid_map)
508 assert(ok == false)
509
510 -- Max depth
511 cjson.encode_max_depth(1)
512 ok, encoded = pcall(cjson.encode, some_map)
513 assert(ok == false)
514
515 cjson.decode_max_depth(1)
516 ok, decoded = pcall(cjson.decode, '{"obj": {"array": [1,2,3,4]}}')
517 assert(ok == false)
518
519 -- Invalid numbers
520 ok, encoded = pcall(cjson.encode, {num1=0/0})
521 assert(ok == false)
522 cjson.encode_invalid_numbers(true)
523 ok, encoded = pcall(cjson.encode, {num1=0/0})
524 assert(ok == true)
525
526 -- Restore defaults
527 cjson.decode_max_depth(1000)
528 cjson.encode_max_depth(1000)
529 cjson.encode_invalid_numbers(false)
530 } 0
531 }
532
533 test {EVAL - cmsgpack can pack double?} {
534 run_script {local encoded = cmsgpack.pack(0.1)
535 local h = ""
536 for i = 1, #encoded do
537 h = h .. string.format("%02x",string.byte(encoded,i))
538 end
539 return h
540 } 0
541 } {cb3fb999999999999a}
542
543 test {EVAL - cmsgpack can pack negative int64?} {
544 run_script {local encoded = cmsgpack.pack(-1099511627776)
545 local h = ""
546 for i = 1, #encoded do
547 h = h .. string.format("%02x",string.byte(encoded,i))
548 end
549 return h
550 } 0
551 } {d3ffffff0000000000}
552
553 test {EVAL - cmsgpack pack/unpack smoke test} {
554 run_script {
555 local str_lt_32 = string.rep("x", 30)
556 local str_lt_255 = string.rep("x", 250)
557 local str_lt_65535 = string.rep("x", 65530)
558 local str_long = string.rep("x", 100000)
559 local array_lt_15 = {1, 2, 3, 4, 5}
560 local array_lt_65535 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}
561 local array_big = {}
562 for i=1, 100000 do
563 array_big[i] = i
564 end
565 local map_lt_15 = {a=1, b=2}
566 local map_big = {}
567 for i=1, 100000 do
568 map_big[tostring(i)] = i
569 end
570 local some_map = {
571 s1=str_lt_32,
572 s2=str_lt_255,
573 s3=str_lt_65535,
574 s4=str_long,
575 d1=0.1,
576 i1=1,
577 i2=250,
578 i3=65530,
579 i4=100000,
580 i5=2^40,
581 i6=-1,
582 i7=-120,
583 i8=-32000,
584 i9=-100000,
585 i10=-3147483648,
586 a1=array_lt_15,
587 a2=array_lt_65535,
588 a3=array_big,
589 m1=map_lt_15,
590 m2=map_big,
591 b1=false,
592 b2=true,
593 n=nil
594 }
595 local encoded = cmsgpack.pack(some_map)
596 local decoded = cmsgpack.unpack(encoded)
597 assert(table.concat(some_map) == table.concat(decoded))
598 local offset, decoded_one = cmsgpack.unpack_one(encoded, 0)
599 assert(table.concat(some_map) == table.concat(decoded_one))
600 assert(offset == -1)
601
602 local encoded_multiple = cmsgpack.pack(str_lt_32, str_lt_255, str_lt_65535, str_long)
603 local offset, obj = cmsgpack.unpack_limit(encoded_multiple, 1, 0)
604 assert(obj == str_lt_32)
605 offset, obj = cmsgpack.unpack_limit(encoded_multiple, 1, offset)
606 assert(obj == str_lt_255)
607 offset, obj = cmsgpack.unpack_limit(encoded_multiple, 1, offset)
608 assert(obj == str_lt_65535)
609 offset, obj = cmsgpack.unpack_limit(encoded_multiple, 1, offset)
610 assert(obj == str_long)
611 assert(offset == -1)
612 } 0
613 }
614
615 test {EVAL - cmsgpack can pack and unpack circular references?} {
616 run_script {local a = {x=nil,y=5}
617 local b = {x=a}
618 a['x'] = b
619 local encoded = cmsgpack.pack(a)
620 local h = ""
621 -- cmsgpack encodes to a depth of 16, but can't encode
622 -- references, so the encoded object has a deep copy recursive
623 -- depth of 16.
624 for i = 1, #encoded do
625 h = h .. string.format("%02x",string.byte(encoded,i))
626 end
627 -- when unpacked, re.x.x != re because the unpack creates
628 -- individual tables down to a depth of 16.
629 -- (that's why the encoded output is so large)
630 local re = cmsgpack.unpack(encoded)
631 assert(re)
632 assert(re.x)
633 assert(re.x.x.y == re.y)
634 assert(re.x.x.x.x.y == re.y)
635 assert(re.x.x.x.x.x.x.y == re.y)
636 assert(re.x.x.x.x.x.x.x.x.x.x.y == re.y)
637 -- maximum working depth:
638 assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.y == re.y)
639 -- now the last x would be b above and has no y
640 assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x)
641 -- so, the final x.x is at the depth limit and was assigned nil
642 assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x == nil)
643 return {h, re.x.x.x.x.x.x.x.x.y == re.y, re.y == 5}
644 } 0
645 } {82a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a178c0 1 1}
646
647 test {EVAL - Numerical sanity check from bitop} {
648 run_script {assert(0x7fffffff == 2147483647, "broken hex literals");
649 assert(0xffffffff == -1 or 0xffffffff == 2^32-1,
650 "broken hex literals");
651 assert(tostring(-1) == "-1", "broken tostring()");
652 assert(tostring(0xffffffff) == "-1" or
653 tostring(0xffffffff) == "4294967295",
654 "broken tostring()")
655 } 0
656 } {}
657
658 test {EVAL - Verify minimal bitop functionality} {
659 run_script {assert(bit.tobit(1) == 1);
660 assert(bit.band(1) == 1);
661 assert(bit.bxor(1,2) == 3);
662 assert(bit.bor(1,2,4,8,16,32,64,128) == 255)
663 } 0
664 } {}
665
666 test {EVAL - Able to parse trailing comments} {
667 run_script {return 'hello' --trailing comment} 0
668 } {hello}
669
670 test {EVAL_RO - Successful case} {
671 r set foo bar
672 assert_equal bar [run_script_ro {return redis.call('get', KEYS[1]);} 1 foo]
673 }
674
675 test {EVAL_RO - Cannot run write commands} {
676 r set foo bar
677 catch {run_script_ro {redis.call('del', KEYS[1]);} 1 foo} e
678 set e
679 } {ERR Write commands are not allowed from read-only scripts*}
680
681 if {$is_eval eq 1} {
682 # script command is only relevant for is_eval Lua
683 test {SCRIPTING FLUSH - is able to clear the scripts cache?} {
684 r set mykey myval
685
686 r script load {return redis.call('get',KEYS[1])}
687 set v [r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey]
688 assert_equal $v myval
689 r script flush
690 assert_error {NOSCRIPT*} {r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey}
691
692 r eval {return redis.call('get',KEYS[1])} 1 mykey
693 set v [r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey]
694 assert_equal $v myval
695 r script flush
696 assert_error {NOSCRIPT*} {r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey}
697 }
698
699 test {SCRIPTING FLUSH ASYNC} {
700 for {set j 0} {$j < 100} {incr j} {
701 r script load "return $j"
702 }
703 assert { [string match "*number_of_cached_scripts:100*" [r info Memory]] }
704 r script flush async
705 assert { [string match "*number_of_cached_scripts:0*" [r info Memory]] }
706 }
707
708 test {SCRIPT EXISTS - can detect already defined scripts?} {
709 r eval "return 1+1" 0
710 r script exists a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bd9 a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bda
711 } {1 0}
712
713 test {SCRIPT LOAD - is able to register scripts in the scripting cache} {
714 list \
715 [r script load "return 'loaded'"] \
716 [r evalsha b534286061d4b9e4026607613b95c06c06015ae8 0]
717 } {b534286061d4b9e4026607613b95c06c06015ae8 loaded}
718
719 test "SORT is normally not alpha re-ordered for the scripting engine" {
720 r del myset
721 r sadd myset 1 2 3 4 10
722 r eval {return redis.call('sort',KEYS[1],'desc')} 1 myset
723 } {10 4 3 2 1} {cluster:skip}
724
725 test "SORT BY <constant> output gets ordered for scripting" {
726 r del myset
727 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
728 r eval {return redis.call('sort',KEYS[1],'by','_')} 1 myset
729 } {a aa aaa azz b c d e f g h i l m n o p q r s t u v z} {cluster:skip}
730
731 test "SORT BY <constant> with GET gets ordered for scripting" {
732 r del myset
733 r sadd myset a b c
734 r eval {return redis.call('sort',KEYS[1],'by','_','get','#','get','_:*')} 1 myset
735 } {a {} b {} c {}} {cluster:skip}
736 } ;# is_eval
737
738 test "redis.sha1hex() implementation" {
739 list [run_script {return redis.sha1hex('')} 0] \
740 [run_script {return redis.sha1hex('Pizza & Mandolino')} 0]
741 } {da39a3ee5e6b4b0d3255bfef95601890afd80709 74822d82031af7493c20eefa13bd07ec4fada82f}
742
743 test "Measures elapsed time os.clock()" {
744 set escaped [run_script {
745 local start = os.clock()
746 while os.clock() - start < 1 do end
747 return {double = os.clock() - start}
748 } 0]
749 assert_morethan_equal $escaped 1 ;# 1 second
750 }
751
752 test "Prohibit dangerous lua methods in sandbox" {
753 assert_equal "" [run_script {
754 local allowed_methods = {"clock"}
755 -- Find a value from a tuple and return the position.
756 local indexOf = function(tuple, value)
757 for i, v in ipairs(tuple) do
758 if v == value then return i end
759 end
760 return nil
761 end
762 -- Check for disallowed methods and verify all allowed methods exist.
763 -- If an allowed method is found, it's removed from 'allowed_methods'.
764 -- If 'allowed_methods' is empty at the end, all allowed methods were found.
765 for key, value in pairs(os) do
766 local index = indexOf(allowed_methods, key)
767 if index == nil or type(value) ~= "function" then
768 return "Disallowed "..type(value)..":"..key
769 end
770 table.remove(allowed_methods, index)
771 end
772 if #allowed_methods ~= 0 then
773 return "Expected method not found: "..table.concat(allowed_methods, ",")
774 end
775 return ""
776 } 0]
777 }
778
779 test "Verify execution of prohibit dangerous Lua methods will fail" {
780 assert_error {ERR *attempt to call field 'execute'*} {run_script {os.execute()} 0}
781 assert_error {ERR *attempt to call field 'exit'*} {run_script {os.exit()} 0}
782 assert_error {ERR *attempt to call field 'getenv'*} {run_script {os.getenv()} 0}
783 assert_error {ERR *attempt to call field 'remove'*} {run_script {os.remove()} 0}
784 assert_error {ERR *attempt to call field 'rename'*} {run_script {os.rename()} 0}
785 assert_error {ERR *attempt to call field 'setlocale'*} {run_script {os.setlocale()} 0}
786 assert_error {ERR *attempt to call field 'tmpname'*} {run_script {os.tmpname()} 0}
787 }
788
789 test {Globals protection reading an undeclared global variable} {
790 catch {run_script {return a} 0} e
791 set e
792 } {ERR *attempted to access * global*}
793
794 test {Globals protection setting an undeclared global*} {
795 catch {run_script {a=10} 0} e
796 set e
797 } {ERR *Attempt to modify a readonly table*}
798
799 test {lua bit.tohex bug} {
800 set res [run_script {return bit.tohex(65535, -2147483648)} 0]
801 r ping
802 set res
803 } {0000FFFF}
804
805 test {Test an example script DECR_IF_GT} {
806 set decr_if_gt {
807 local current
808
809 current = redis.call('get',KEYS[1])
810 if not current then return nil end
811 if current > ARGV[1] then
812 return redis.call('decr',KEYS[1])
813 else
814 return redis.call('get',KEYS[1])
815 end
816 }
817 r set foo 5
818 set res {}
819 lappend res [run_script $decr_if_gt 1 foo 2]
820 lappend res [run_script $decr_if_gt 1 foo 2]
821 lappend res [run_script $decr_if_gt 1 foo 2]
822 lappend res [run_script $decr_if_gt 1 foo 2]
823 lappend res [run_script $decr_if_gt 1 foo 2]
824 set res
825 } {4 3 2 2 2}
826
827 if {$is_eval eq 1} {
828 # random handling is only relevant for is_eval Lua
829 test {random numbers are random now} {
830 set rand1 [r eval {return tostring(math.random())} 0]
831 wait_for_condition 100 1 {
832 $rand1 ne [r eval {return tostring(math.random())} 0]
833 } else {
834 fail "random numbers should be random, now it's fixed value"
835 }
836 }
837
838 test {Scripting engine PRNG can be seeded correctly} {
839 set rand1 [r eval {
840 math.randomseed(ARGV[1]); return tostring(math.random())
841 } 0 10]
842 set rand2 [r eval {
843 math.randomseed(ARGV[1]); return tostring(math.random())
844 } 0 10]
845 set rand3 [r eval {
846 math.randomseed(ARGV[1]); return tostring(math.random())
847 } 0 20]
848 assert_equal $rand1 $rand2
849 assert {$rand2 ne $rand3}
850 }
851 } ;# is_eval
852
853 test {EVAL does not leak in the Lua stack} {
854 r script flush ;# reset Lua VM
855 r set x 0
856 # Use a non blocking client to speedup the loop.
857 set rd [redis_deferring_client]
858 for {set j 0} {$j < 10000} {incr j} {
859 run_script_on_connection $rd {return redis.call("incr",KEYS[1])} 1 x
860 }
861 for {set j 0} {$j < 10000} {incr j} {
862 $rd read
863 }
864 assert {[s used_memory_lua] < 1024*100}
865 $rd close
866 r get x
867 } {10000}
868
869 if {$is_eval eq 1} {
870 test {SPOP: We can call scripts rewriting client->argv from Lua} {
871 set repl [attach_to_replication_stream]
872 #this sadd operation is for external-cluster test. If myset doesn't exist, 'del myset' won't get propagated.
873 r sadd myset ppp
874 r del myset
875 r sadd myset a b c
876 assert {[r eval {return redis.call('spop', 'myset')} 0] ne {}}
877 assert {[r eval {return redis.call('spop', 'myset', 1)} 0] ne {}}
878 assert {[r eval {return redis.call('spop', KEYS[1])} 1 myset] ne {}}
879 # this one below should not be replicated
880 assert {[r eval {return redis.call('spop', KEYS[1])} 1 myset] eq {}}
881 r set trailingkey 1
882 assert_replication_stream $repl {
883 {select *}
884 {sadd *}
885 {del *}
886 {sadd *}
887 {srem myset *}
888 {srem myset *}
889 {srem myset *}
890 {set *}
891 }
892 close_replication_stream $repl
893 } {} {needs:repl}
894
895 test {MGET: mget shouldn't be propagated in Lua} {
896 set repl [attach_to_replication_stream]
897 r mset a{t} 1 b{t} 2 c{t} 3 d{t} 4
898 #read-only, won't be replicated
899 assert {[r eval {return redis.call('mget', 'a{t}', 'b{t}', 'c{t}', 'd{t}')} 0] eq {1 2 3 4}}
900 r set trailingkey 2
901 assert_replication_stream $repl {
902 {select *}
903 {mset *}
904 {set *}
905 }
906 close_replication_stream $repl
907 } {} {needs:repl}
908
909 test {EXPIRE: We can call scripts rewriting client->argv from Lua} {
910 set repl [attach_to_replication_stream]
911 r set expirekey 1
912 #should be replicated as EXPIREAT
913 assert {[r eval {return redis.call('expire', KEYS[1], ARGV[1])} 1 expirekey 3] eq 1}
914
915 assert_replication_stream $repl {
916 {select *}
917 {set *}
918 {pexpireat expirekey *}
919 }
920 close_replication_stream $repl
921 } {} {needs:repl}
922
923 test {INCRBYFLOAT: We can call scripts expanding client->argv from Lua} {
924 # coverage for scripts calling commands that expand the argv array
925 # an attempt to add coverage for a possible bug in luaArgsToRedisArgv
926 # this test needs a fresh server so that lua_argv_size is 0.
927 # glibc realloc can return the same pointer even when the size changes
928 # still this test isn't able to trigger the issue, but we keep it anyway.
929 start_server {tags {"scripting"}} {
930 set repl [attach_to_replication_stream]
931 # a command with 5 argsument
932 r eval {redis.call('hmget', KEYS[1], 1, 2, 3)} 1 key
933 # then a command with 3 that is replicated as one with 4
934 r eval {redis.call('incrbyfloat', KEYS[1], 1)} 1 key
935 # then a command with 4 args
936 r eval {redis.call('set', KEYS[1], '1', 'KEEPTTL')} 1 key
937
938 assert_replication_stream $repl {
939 {select *}
940 {set key 1 KEEPTTL}
941 {set key 1 KEEPTTL}
942 }
943 close_replication_stream $repl
944 }
945 } {} {needs:repl}
946
947 } ;# is_eval
948
949 test {Call Redis command with many args from Lua (issue #1764)} {
950 run_script {
951 local i
952 local x={}
953 redis.call('del','mylist')
954 for i=1,100 do
955 table.insert(x,i)
956 end
957 redis.call('rpush','mylist',unpack(x))
958 return redis.call('lrange','mylist',0,-1)
959 } 1 mylist
960 } {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100}
961
962 test {Number conversion precision test (issue #1118)} {
963 run_script {
964 local value = 9007199254740991
965 redis.call("set","foo",value)
966 return redis.call("get","foo")
967 } 1 foo
968 } {9007199254740991}
969
970 test {String containing number precision test (regression of issue #1118)} {
971 run_script {
972 redis.call("set", "key", "12039611435714932082")
973 return redis.call("get", "key")
974 } 1 key
975 } {12039611435714932082}
976
977 test {Verify negative arg count is error instead of crash (issue #1842)} {
978 catch { run_script { return "hello" } -12 } e
979 set e
980 } {ERR Number of keys can't be negative}
981
982 test {Scripts can handle commands with incorrect arity} {
983 assert_error "ERR Wrong number of args calling Redis command from script*" {run_script "redis.call('set','invalid')" 0}
984 assert_error "ERR Wrong number of args calling Redis command from script*" {run_script "redis.call('incr')" 0}
985 }
986
987 test {Correct handling of reused argv (issue #1939)} {
988 run_script {
989 for i = 0, 10 do
990 redis.call('SET', 'a{t}', '1')
991 redis.call('MGET', 'a{t}', 'b{t}', 'c{t}')
992 redis.call('EXPIRE', 'a{t}', 0)
993 redis.call('GET', 'a{t}')
994 redis.call('MGET', 'a{t}', 'b{t}', 'c{t}')
995 end
996 } 3 a{t} b{t} c{t}
997 }
998
999 test {Functions in the Redis namespace are able to report errors} {
1000 catch {
1001 run_script {
1002 redis.sha1hex()
1003 } 0
1004 } e
1005 set e
1006 } {*wrong number*}
1007
1008 test {CLUSTER RESET can not be invoke from within a script} {
1009 catch {
1010 run_script {
1011 redis.call('cluster', 'reset', 'hard')
1012 } 0
1013 } e
1014 set _ $e
1015 } {*command is not allowed*}
1016
1017 test {Script with RESP3 map} {
1018 set expected_dict [dict create field value]
1019 set expected_list [list field value]
1020
1021 # Sanity test for RESP3 without scripts
1022 r HELLO 3
1023 r hset hash field value
1024 set res [r hgetall hash]
1025 assert_equal $res $expected_dict
1026
1027 # Test RESP3 client with script in both RESP2 and RESP3 modes
1028 set res [run_script {redis.setresp(3); return redis.call('hgetall', KEYS[1])} 1 hash]
1029 assert_equal $res $expected_dict
1030 set res [run_script {redis.setresp(2); return redis.call('hgetall', KEYS[1])} 1 hash]
1031 assert_equal $res $expected_list
1032
1033 # Test RESP2 client with script in both RESP2 and RESP3 modes
1034 r HELLO 2
1035 set res [run_script {redis.setresp(3); return redis.call('hgetall', KEYS[1])} 1 hash]
1036 assert_equal $res $expected_list
1037 set res [run_script {redis.setresp(2); return redis.call('hgetall', KEYS[1])} 1 hash]
1038 assert_equal $res $expected_list
1039 } {} {resp3}
1040
1041 if {!$::log_req_res} { # this test creates a huge nested array which python can't handle (RecursionError: maximum recursion depth exceeded in comparison)
1042 test {Script return recursive object} {
1043 r readraw 1
1044 set res [run_script {local a = {}; local b = {a}; a[1] = b; return a} 0]
1045 # drain the response
1046 while {true} {
1047 if {$res == "-ERR reached lua stack limit"} {
1048 break
1049 }
1050 assert_equal $res "*1"
1051 set res [r read]
1052 }
1053 r readraw 0
1054 # make sure the connection is still valid
1055 assert_equal [r ping] {PONG}
1056 }
1057 }
1058
1059 test {Script check unpack with massive arguments} {
1060 run_script {
1061 local a = {}
1062 for i=1,7999 do
1063 a[i] = 1
1064 end
1065 return redis.call("lpush", "l", unpack(a))
1066 } 1 l
1067 } {7999}
1068
1069 test "Script read key with expiration set" {
1070 r SET key value EX 10
1071 assert_equal [run_script {
1072 if redis.call("EXISTS", "key") then
1073 return redis.call("GET", "key")
1074 else
1075 return redis.call("EXISTS", "key")
1076 end
1077 } 1 key] "value"
1078 }
1079
1080 test "Script del key with expiration set" {
1081 r SET key value EX 10
1082 assert_equal [run_script {
1083 redis.call("DEL", "key")
1084 return redis.call("EXISTS", "key")
1085 } 1 key] 0
1086 }
1087
1088 test "Script ACL check" {
1089 r acl setuser bob on {>123} {+@scripting} {+set} {~x*}
1090 assert_equal [r auth bob 123] {OK}
1091
1092 # Check permission granted
1093 assert_equal [run_script {
1094 return redis.acl_check_cmd('set','xx',1)
1095 } 1 xx] 1
1096
1097 # Check permission denied unauthorised command
1098 assert_equal [run_script {
1099 return redis.acl_check_cmd('hset','xx','f',1)
1100 } 1 xx] {}
1101
1102 # Check permission denied unauthorised key
1103 # Note: we don't pass the "yy" key as an argument to the script so key acl checks won't block the script
1104 assert_equal [run_script {
1105 return redis.acl_check_cmd('set','yy',1)
1106 } 0] {}
1107
1108 # Check error due to invalid command
1109 assert_error {ERR *Invalid command passed to redis.acl_check_cmd()*} {run_script {
1110 return redis.acl_check_cmd('invalid-cmd','arg')
1111 } 0}
1112 }
1113
1114 test "Binary code loading failed" {
1115 assert_error {ERR *attempt to call a nil value*} {run_script {
1116 return loadstring(string.dump(function() return 1 end))()
1117 } 0}
1118 }
1119
1120 test "Try trick global protection 1" {
1121 catch {
1122 run_script {
1123 setmetatable(_G, {})
1124 } 0
1125 } e
1126 set _ $e
1127 } {*Attempt to modify a readonly table*}
1128
1129 test "Try trick global protection 2" {
1130 catch {
1131 run_script {
1132 local g = getmetatable(_G)
1133 g.__index = {}
1134 } 0
1135 } e
1136 set _ $e
1137 } {*Attempt to modify a readonly table*}
1138
1139 test "Try trick global protection 3" {
1140 catch {
1141 run_script {
1142 redis = function() return 1 end
1143 } 0
1144 } e
1145 set _ $e
1146 } {*Attempt to modify a readonly table*}
1147
1148 test "Try trick global protection 4" {
1149 catch {
1150 run_script {
1151 _G = {}
1152 } 0
1153 } e
1154 set _ $e
1155 } {*Attempt to modify a readonly table*}
1156
1157 test "Try trick readonly table on redis table" {
1158 catch {
1159 run_script {
1160 redis.call = function() return 1 end
1161 } 0
1162 } e
1163 set _ $e
1164 } {*Attempt to modify a readonly table*}
1165
1166 test "Try trick readonly table on json table" {
1167 catch {
1168 run_script {
1169 cjson.encode = function() return 1 end
1170 } 0
1171 } e
1172 set _ $e
1173 } {*Attempt to modify a readonly table*}
1174
1175 test "Try trick readonly table on cmsgpack table" {
1176 catch {
1177 run_script {
1178 cmsgpack.pack = function() return 1 end
1179 } 0
1180 } e
1181 set _ $e
1182 } {*Attempt to modify a readonly table*}
1183
1184 test "Try trick readonly table on bit table" {
1185 catch {
1186 run_script {
1187 bit.lshift = function() return 1 end
1188 } 0
1189 } e
1190 set _ $e
1191 } {*Attempt to modify a readonly table*}
1192
1193 test "Try trick readonly table on basic types metatable" {
1194 # Run the following scripts for basic types. Either getmetatable()
1195 # should return nil or the metatable must be readonly.
1196 set scripts {
1197 {getmetatable(nil).__index = function() return 1 end}
1198 {getmetatable('').__index = function() return 1 end}
1199 {getmetatable(123.222).__index = function() return 1 end}
1200 {getmetatable(true).__index = function() return 1 end}
1201 {getmetatable(function() return 1 end).__index = function() return 1 end}
1202 {getmetatable(coroutine.create(function() return 1 end)).__index = function() return 1 end}
1203 }
1204
1205 foreach code $scripts {
1206 catch {run_script $code 0} e
1207 assert {
1208 [string match "*attempt to index a nil value script*" $e] ||
1209 [string match "*Attempt to modify a readonly table*" $e]
1210 }
1211 }
1212 }
1213
1214 test "Test loadfile are not available" {
1215 catch {
1216 run_script {
1217 loadfile('some file')
1218 } 0
1219 } e
1220 set _ $e
1221 } {*Script attempted to access nonexistent global variable 'loadfile'*}
1222
1223 test "Test dofile are not available" {
1224 catch {
1225 run_script {
1226 dofile('some file')
1227 } 0
1228 } e
1229 set _ $e
1230 } {*Script attempted to access nonexistent global variable 'dofile'*}
1231
1232 test "Test print are not available" {
1233 catch {
1234 run_script {
1235 print('some data')
1236 } 0
1237 } e
1238 set _ $e
1239 } {*Script attempted to access nonexistent global variable 'print'*}
1240}
1241
1242# start a new server to test the large-memory tests
1243start_server {tags {"scripting external:skip large-memory"}} {
1244 test {EVAL - JSON string encoding a string larger than 2GB} {
1245 run_script {
1246 local s = string.rep("a", 1024 * 1024 * 1024)
1247 return #cjson.encode(s..s..s)
1248 } 0
1249 } {3221225474} ;# length includes two double quotes at both ends
1250
1251 test {EVAL - Test long escape sequences for strings} {
1252 run_script {
1253 -- Generate 1gb '==...==' separator
1254 local s = string.rep('=', 1024 * 1024)
1255 local t = {} for i=1,1024 do t[i] = s end
1256 local sep = table.concat(t)
1257 collectgarbage('collect')
1258
1259 local code = table.concat({'return [',sep,'[x]',sep,']'})
1260 collectgarbage('collect')
1261
1262 -- Load the code and run it. Script will return the string length.
1263 -- Escape sequence: [=....=[ to ]=...=] will be ignored
1264 -- Actual string is a single character: 'x'. Script will return 1
1265 local func = loadstring(code)
1266 return #func()
1267 } 0
1268 } {1}
1269
1270 test {EVAL - Lua can parse string with too many new lines} {
1271 # Create a long string consisting only of newline characters. When Lua
1272 # fails to parse a string, it typically includes a snippet like
1273 # "... near ..." in the error message to indicate the last recognizable
1274 # token. In this test, since the input contains only newlines, there
1275 # should be no identifiable token, so the error message should contain
1276 # only the actual error, without a near clause.
1277
1278 run_script {
1279 local s = string.rep('\n', 1024 * 1024)
1280 local t = {} for i=1,2048 do t[#t+1] = s end
1281 local lines = table.concat(t)
1282 local fn, err = loadstring(lines)
1283 return err
1284 } 0
1285 } {*chunk has too many lines}
1286}
1287
1288# Start a new server to test lua-enable-deprecated-api config
1289foreach enabled {no yes} {
1290start_server [subst {tags {"scripting external:skip"} overrides {lua-enable-deprecated-api $enabled}}] {
1291 test "Test setfenv availability lua-enable-deprecated-api=$enabled" {
1292 catch {
1293 run_script {
1294 local f = function() return 1 end
1295 setfenv(f, {})
1296 return 0
1297 } 0
1298 } e
1299 if {$enabled} {
1300 assert_equal $e 0
1301 } else {
1302 assert_match {*Script attempted to access nonexistent global variable 'setfenv'*} $e
1303 }
1304 }
1305
1306 test "Test getfenv availability lua-enable-deprecated-api=$enabled" {
1307 catch {
1308 run_script {
1309 local f = function() return 1 end
1310 getfenv(f)
1311 return 0
1312 } 0
1313 } e
1314 if {$enabled} {
1315 assert_equal $e 0
1316 } else {
1317 assert_match {*Script attempted to access nonexistent global variable 'getfenv'*} $e
1318 }
1319 }
1320
1321 test "Test newproxy availability lua-enable-deprecated-api=$enabled" {
1322 catch {
1323 run_script {
1324 getmetatable(newproxy(true)).__gc = function() return 1 end
1325 return 0
1326 } 0
1327 } e
1328 if {$enabled} {
1329 assert_equal $e 0
1330 } else {
1331 assert_match {*Script attempted to access nonexistent global variable 'newproxy'*} $e
1332 }
1333 }
1334}
1335}
1336
1337# Start a new server since the last test in this stanza will kill the
1338# instance at all.
1339start_server {tags {"scripting"}} {
1340 test {Timedout read-only scripts can be killed by SCRIPT KILL} {
1341 set rd [redis_deferring_client]
1342 r config set lua-time-limit 10
1343 run_script_on_connection $rd {while true do end} 0
1344 after 200
1345 catch {r ping} e
1346 assert_match {BUSY*} $e
1347 kill_script
1348 after 200 ; # Give some time to Lua to call the hook again...
1349 assert_equal [r ping] "PONG"
1350 $rd close
1351 }
1352
1353 test {Timedout read-only scripts can be killed by SCRIPT KILL even when use pcall} {
1354 set rd [redis_deferring_client]
1355 r config set lua-time-limit 10
1356 run_script_on_connection $rd {local f = function() while 1 do redis.call('ping') end end while 1 do pcall(f) end} 0
1357
1358 wait_for_condition 50 100 {
1359 [catch {r ping} e] == 1
1360 } else {
1361 fail "Can't wait for script to start running"
1362 }
1363 catch {r ping} e
1364 assert_match {BUSY*} $e
1365
1366 kill_script
1367
1368 wait_for_condition 50 100 {
1369 [catch {r ping} e] == 0
1370 } else {
1371 fail "Can't wait for script to be killed"
1372 }
1373 assert_equal [r ping] "PONG"
1374
1375 catch {$rd read} res
1376 $rd close
1377
1378 assert_match {*killed by user*} $res
1379 }
1380
1381 test {Timedout script does not cause a false dead client} {
1382 set rd [redis_deferring_client]
1383 r config set lua-time-limit 10
1384
1385 # senging (in a pipeline):
1386 # 1. eval "while 1 do redis.call('ping') end" 0
1387 # 2. ping
1388 if {$is_eval == 1} {
1389 set buf "*3\r\n\$4\r\neval\r\n\$33\r\nwhile 1 do redis.call('ping') end\r\n\$1\r\n0\r\n"
1390 append buf "*1\r\n\$4\r\nping\r\n"
1391 } else {
1392 set buf "*4\r\n\$8\r\nfunction\r\n\$4\r\nload\r\n\$7\r\nreplace\r\n\$97\r\n#!lua name=test\nredis.register_function('test', function() while 1 do redis.call('ping') end end)\r\n"
1393 append buf "*3\r\n\$5\r\nfcall\r\n\$4\r\ntest\r\n\$1\r\n0\r\n"
1394 append buf "*1\r\n\$4\r\nping\r\n"
1395 }
1396 $rd write $buf
1397 $rd flush
1398
1399 wait_for_condition 50 100 {
1400 [catch {r ping} e] == 1
1401 } else {
1402 fail "Can't wait for script to start running"
1403 }
1404 catch {r ping} e
1405 assert_match {BUSY*} $e
1406
1407 kill_script
1408 wait_for_condition 50 100 {
1409 [catch {r ping} e] == 0
1410 } else {
1411 fail "Can't wait for script to be killed"
1412 }
1413 assert_equal [r ping] "PONG"
1414
1415 if {$is_eval == 0} {
1416 # read the function name
1417 assert_match {test} [$rd read]
1418 }
1419
1420 catch {$rd read} res
1421 assert_match {*killed by user*} $res
1422
1423 set res [$rd read]
1424 assert_match {*PONG*} $res
1425
1426 $rd close
1427 }
1428
1429 test {Timedout script link is still usable after Lua returns} {
1430 r config set lua-time-limit 10
1431 run_script {for i=1,100000 do redis.call('ping') end return 'ok'} 0
1432 r ping
1433 } {PONG}
1434
1435 test {Timedout scripts and unblocked command} {
1436 # make sure a command that's allowed during BUSY doesn't trigger an unblocked command
1437
1438 # enable AOF to also expose an assertion if the bug would happen
1439 r flushall
1440 r config set appendonly yes
1441
1442 # create clients, and set one to block waiting for key 'x'
1443 set rd [redis_deferring_client]
1444 set rd2 [redis_deferring_client]
1445 set r3 [redis_client]
1446 $rd2 blpop x 0
1447 wait_for_blocked_clients_count 1
1448
1449 # hack: allow the script to use client list command so that we can control when it aborts
1450 r DEBUG set-disable-deny-scripts 1
1451 r config set lua-time-limit 10
1452 run_script_on_connection $rd {
1453 local clients
1454 redis.call('lpush',KEYS[1],'y');
1455 while true do
1456 clients = redis.call('client','list')
1457 if string.find(clients, 'abortscript') ~= nil then break end
1458 end
1459 redis.call('lpush',KEYS[1],'z');
1460 return clients
1461 } 1 x
1462
1463 # wait for the script to be busy
1464 after 200
1465 catch {r ping} e
1466 assert_match {BUSY*} $e
1467
1468 # run cause the script to abort, and run a command that could have processed
1469 # unblocked clients (due to a bug)
1470 $r3 hello 2 setname abortscript
1471
1472 # make sure the script completed before the pop was processed
1473 assert_equal [$rd2 read] {x z}
1474 assert_match {*abortscript*} [$rd read]
1475
1476 $rd close
1477 $rd2 close
1478 $r3 close
1479 r DEBUG set-disable-deny-scripts 0
1480 } {OK} {external:skip needs:debug}
1481
1482 test {Timedout scripts that modified data can't be killed by SCRIPT KILL} {
1483 set rd [redis_deferring_client]
1484 r config set lua-time-limit 10
1485 run_script_on_connection $rd {redis.call('set',KEYS[1],'y'); while true do end} 1 x
1486 after 200
1487 catch {r ping} e
1488 assert_match {BUSY*} $e
1489 catch {kill_script} e
1490 assert_match {UNKILLABLE*} $e
1491 catch {r ping} e
1492 assert_match {BUSY*} $e
1493 } {} {external:skip}
1494
1495 # Note: keep this test at the end of this server stanza because it
1496 # kills the server.
1497 test {SHUTDOWN NOSAVE can kill a timedout script anyway} {
1498 # The server should be still unresponding to normal commands.
1499 catch {r ping} e
1500 assert_match {BUSY*} $e
1501 catch {r shutdown nosave}
1502 # Make sure the server was killed
1503 catch {set rd [redis_deferring_client]} e
1504 assert_match {*connection refused*} $e
1505 } {} {external:skip}
1506}
1507
1508 start_server {tags {"scripting repl needs:debug external:skip"}} {
1509 start_server {} {
1510 test "Before the replica connects we issue two EVAL commands" {
1511 # One with an error, but still executing a command.
1512 # SHA is: 67164fc43fa971f76fd1aaeeaf60c1c178d25876
1513 catch {
1514 run_script {redis.call('incr',KEYS[1]); redis.call('nonexisting')} 1 x
1515 }
1516 # One command is correct:
1517 # SHA is: 6f5ade10a69975e903c6d07b10ea44c6382381a5
1518 run_script {return redis.call('incr',KEYS[1])} 1 x
1519 } {2}
1520
1521 test "Connect a replica to the master instance" {
1522 r -1 slaveof [srv 0 host] [srv 0 port]
1523 wait_for_condition 50 100 {
1524 [s -1 role] eq {slave} &&
1525 [string match {*master_link_status:up*} [r -1 info replication]]
1526 } else {
1527 fail "Can't turn the instance into a replica"
1528 }
1529 }
1530
1531 if {$is_eval eq 1} {
1532 test "Now use EVALSHA against the master, with both SHAs" {
1533 # The server should replicate successful and unsuccessful
1534 # commands as EVAL instead of EVALSHA.
1535 catch {
1536 r evalsha 67164fc43fa971f76fd1aaeeaf60c1c178d25876 1 x
1537 }
1538 r evalsha 6f5ade10a69975e903c6d07b10ea44c6382381a5 1 x
1539 } {4}
1540
1541 test "'x' should be '4' for EVALSHA being replicated by effects" {
1542 wait_for_condition 50 100 {
1543 [r -1 get x] eq {4}
1544 } else {
1545 fail "Expected 4 in x, but value is '[r -1 get x]'"
1546 }
1547 }
1548 } ;# is_eval
1549
1550 test "Replication of script multiple pushes to list with BLPOP" {
1551 set rd [redis_deferring_client]
1552 $rd brpop a 0
1553 run_script {
1554 redis.call("lpush",KEYS[1],"1");
1555 redis.call("lpush",KEYS[1],"2");
1556 } 1 a
1557 set res [$rd read]
1558 $rd close
1559 wait_for_condition 50 100 {
1560 [r -1 lrange a 0 -1] eq [r lrange a 0 -1]
1561 } else {
1562 fail "Expected list 'a' in replica and master to be the same, but they are respectively '[r -1 lrange a 0 -1]' and '[r lrange a 0 -1]'"
1563 }
1564 set res
1565 } {a 1}
1566
1567 if {$is_eval eq 1} {
1568 test "EVALSHA replication when first call is readonly" {
1569 r del x
1570 r eval {if tonumber(ARGV[1]) > 0 then redis.call('incr', KEYS[1]) end} 1 x 0
1571 r evalsha 6e0e2745aa546d0b50b801a20983b70710aef3ce 1 x 0
1572 r evalsha 6e0e2745aa546d0b50b801a20983b70710aef3ce 1 x 1
1573 wait_for_condition 50 100 {
1574 [r -1 get x] eq {1}
1575 } else {
1576 fail "Expected 1 in x, but value is '[r -1 get x]'"
1577 }
1578 }
1579 } ;# is_eval
1580
1581 test "Lua scripts using SELECT are replicated correctly" {
1582 run_script {
1583 redis.call("set","foo1","bar1")
1584 redis.call("select","10")
1585 redis.call("incr","x")
1586 redis.call("select","11")
1587 redis.call("incr","z")
1588 } 3 foo1 x z
1589 run_script {
1590 redis.call("set","foo1","bar1")
1591 redis.call("select","10")
1592 redis.call("incr","x")
1593 redis.call("select","11")
1594 redis.call("incr","z")
1595 } 3 foo1 x z
1596 wait_for_condition 50 100 {
1597 [debug_digest -1] eq [debug_digest]
1598 } else {
1599 fail "Master-Replica desync after Lua script using SELECT."
1600 }
1601 } {} {singledb:skip}
1602 }
1603 }
1604
1605start_server {tags {"scripting repl external:skip"}} {
1606 start_server {overrides {appendonly yes aof-use-rdb-preamble no}} {
1607 test "Connect a replica to the master instance" {
1608 r -1 slaveof [srv 0 host] [srv 0 port]
1609 wait_for_condition 50 100 {
1610 [s -1 role] eq {slave} &&
1611 [string match {*master_link_status:up*} [r -1 info replication]]
1612 } else {
1613 fail "Can't turn the instance into a replica"
1614 }
1615 }
1616
1617 # replicate_commands is the default on Redis Function
1618 test "Redis.replicate_commands() can be issued anywhere now" {
1619 r eval {
1620 redis.call('set','foo','bar');
1621 return redis.replicate_commands();
1622 } 0
1623 } {1}
1624
1625 test "Redis.set_repl() can be issued before replicate_commands() now" {
1626 catch {
1627 r eval {
1628 redis.set_repl(redis.REPL_ALL);
1629 } 0
1630 } e
1631 set e
1632 } {}
1633
1634 test "Redis.set_repl() don't accept invalid values" {
1635 catch {
1636 run_script {
1637 redis.set_repl(12345);
1638 } 0
1639 } e
1640 set e
1641 } {*Invalid*flags*}
1642
1643 test "Test selective replication of certain Redis commands from Lua" {
1644 r del a b c d
1645 run_script {
1646 redis.call('set','a','1');
1647 redis.set_repl(redis.REPL_NONE);
1648 redis.call('set','b','2');
1649 redis.set_repl(redis.REPL_AOF);
1650 redis.call('set','c','3');
1651 redis.set_repl(redis.REPL_ALL);
1652 redis.call('set','d','4');
1653 } 4 a b c d
1654
1655 wait_for_condition 50 100 {
1656 [r -1 mget a b c d] eq {1 {} {} 4}
1657 } else {
1658 fail "Only a and d should be replicated to replica"
1659 }
1660
1661 # Master should have everything right now
1662 assert {[r mget a b c d] eq {1 2 3 4}}
1663
1664 # After an AOF reload only a, c and d should exist
1665 r debug loadaof
1666
1667 assert {[r mget a b c d] eq {1 {} 3 4}}
1668 }
1669
1670 test "PRNG is seeded randomly for command replication" {
1671 if {$is_eval eq 1} {
1672 # on is_eval Lua we need to call redis.replicate_commands() to get real randomization
1673 set a [
1674 run_script {
1675 redis.replicate_commands()
1676 return math.random()*100000;
1677 } 0
1678 ]
1679 set b [
1680 run_script {
1681 redis.replicate_commands()
1682 return math.random()*100000;
1683 } 0
1684 ]
1685 } else {
1686 set a [
1687 run_script {
1688 return math.random()*100000;
1689 } 0
1690 ]
1691 set b [
1692 run_script {
1693 return math.random()*100000;
1694 } 0
1695 ]
1696 }
1697 assert {$a ne $b}
1698 }
1699
1700 test "Using side effects is not a problem with command replication" {
1701 run_script {
1702 redis.call('set','time',redis.call('time')[1])
1703 } 0
1704
1705 assert {[r get time] ne {}}
1706
1707 wait_for_condition 50 100 {
1708 [r get time] eq [r -1 get time]
1709 } else {
1710 fail "Time key does not match between master and replica"
1711 }
1712 }
1713 }
1714}
1715
1716if {$is_eval eq 1} {
1717start_server {tags {"scripting external:skip"}} {
1718 r script debug sync
1719 r eval {return 'hello'} 0
1720 r eval {return 'hello'} 0
1721}
1722
1723start_server {tags {"scripting needs:debug external:skip"}} {
1724 test {Test scripting debug protocol parsing} {
1725 r script debug sync
1726 r eval {return 'hello'} 0
1727 catch {r 'hello\0world'} e
1728 assert_match {*Unknown Redis Lua debugger command*} $e
1729 catch {r 'hello\0'} e
1730 assert_match {*Unknown Redis Lua debugger command*} $e
1731 catch {r '\0hello'} e
1732 assert_match {*Unknown Redis Lua debugger command*} $e
1733 catch {r '\0hello\0'} e
1734 assert_match {*Unknown Redis Lua debugger command*} $e
1735 }
1736
1737 test {Test scripting debug lua stack overflow} {
1738 r script debug sync
1739 r eval {return 'hello'} 0
1740 set cmd "*101\r\n\$5\r\nredis\r\n"
1741 append cmd [string repeat "\$4\r\ntest\r\n" 100]
1742 r write $cmd
1743 r flush
1744 set ret [r read]
1745 assert_match {*Unknown Redis command called from script*} $ret
1746 # make sure the server is still ok
1747 reconnect
1748 assert_equal [r ping] {PONG}
1749 }
1750}
1751
1752start_server {tags {"scripting external:skip"}} {
1753 test {Lua scripts eviction does not generate many scripts} {
1754 r script flush
1755 r config resetstat
1756
1757 # "return 1" sha is: e0e1f9fabfc9d4800c877a703b823ac0578ff8db
1758 # "return 500" sha is: 98fe65896b61b785c5ed328a5a0a1421f4f1490c
1759 for {set j 1} {$j <= 250} {incr j} {
1760 r eval "return $j" 0
1761 }
1762 for {set j 251} {$j <= 500} {incr j} {
1763 r eval_ro "return $j" 0
1764 }
1765 assert_equal [s number_of_cached_scripts] 500
1766 assert_equal 1 [r evalsha e0e1f9fabfc9d4800c877a703b823ac0578ff8db 0]
1767 assert_equal 1 [r evalsha_ro e0e1f9fabfc9d4800c877a703b823ac0578ff8db 0]
1768 assert_equal 500 [r evalsha 98fe65896b61b785c5ed328a5a0a1421f4f1490c 0]
1769 assert_equal 500 [r evalsha_ro 98fe65896b61b785c5ed328a5a0a1421f4f1490c 0]
1770
1771 # Scripts between "return 1" and "return 500" are evicted
1772 for {set j 501} {$j <= 750} {incr j} {
1773 r eval "return $j" 0
1774 }
1775 for {set j 751} {$j <= 1000} {incr j} {
1776 r eval "return $j" 0
1777 }
1778 assert_error {NOSCRIPT*} {r evalsha e0e1f9fabfc9d4800c877a703b823ac0578ff8db 0}
1779 assert_error {NOSCRIPT*} {r evalsha_ro e0e1f9fabfc9d4800c877a703b823ac0578ff8db 0}
1780 assert_error {NOSCRIPT*} {r evalsha 98fe65896b61b785c5ed328a5a0a1421f4f1490c 0}
1781 assert_error {NOSCRIPT*} {r evalsha_ro 98fe65896b61b785c5ed328a5a0a1421f4f1490c 0}
1782
1783 assert_equal [s evicted_scripts] 500
1784 assert_equal [s number_of_cached_scripts] 500
1785 }
1786
1787 test {Lua scripts eviction is plain LRU} {
1788 r script flush
1789 r config resetstat
1790
1791 # "return 1" sha is: e0e1f9fabfc9d4800c877a703b823ac0578ff8db
1792 # "return 2" sha is: 7f923f79fe76194c868d7e1d0820de36700eb649
1793 # "return 3" sha is: 09d3822de862f46d784e6a36848b4f0736dda47a
1794 # "return 500" sha is: 98fe65896b61b785c5ed328a5a0a1421f4f1490c
1795 # "return 1000" sha is: 94f1a7bc9f985a1a1d5a826a85579137d9d840c8
1796 for {set j 1} {$j <= 500} {incr j} {
1797 r eval "return $j" 0
1798 }
1799
1800 # Call "return 1" to move it to the tail.
1801 r eval "return 1" 0
1802 # Call "return 2" to move it to the tail.
1803 r evalsha 7f923f79fe76194c868d7e1d0820de36700eb649 0
1804 # Create a new script, "return 3" will be evicted.
1805 r eval "return 1000" 0
1806 # "return 1" is ok since it was moved to tail.
1807 assert_equal 1 [r evalsha e0e1f9fabfc9d4800c877a703b823ac0578ff8db 0]
1808 # "return 2" is ok since it was moved to tail.
1809 assert_equal 1 [r evalsha e0e1f9fabfc9d4800c877a703b823ac0578ff8db 0]
1810 # "return 3" was evicted.
1811 assert_error {NOSCRIPT*} {r evalsha 09d3822de862f46d784e6a36848b4f0736dda47a 0}
1812 # Others are ok.
1813 assert_equal 500 [r evalsha 98fe65896b61b785c5ed328a5a0a1421f4f1490c 0]
1814 assert_equal 1000 [r evalsha 94f1a7bc9f985a1a1d5a826a85579137d9d840c8 0]
1815
1816 assert_equal [s evicted_scripts] 1
1817 assert_equal [s number_of_cached_scripts] 500
1818 }
1819
1820 test {Lua scripts eviction does not affect script load} {
1821 r script flush
1822 r config resetstat
1823
1824 set num [randomRange 500 1000]
1825 for {set j 1} {$j <= $num} {incr j} {
1826 r script load "return $j"
1827 r eval "return 'str_$j'" 0
1828 }
1829 set evicted [s evicted_scripts]
1830 set cached [s number_of_cached_scripts]
1831 # evicted = num eval scripts - 500 eval scripts
1832 assert_equal $evicted [expr $num-500]
1833 # cached = num load scripts + 500 eval scripts
1834 assert_equal $cached [expr $num+500]
1835 }
1836}
1837
1838} ;# is_eval
1839
1840start_server {tags {"scripting needs:debug"}} {
1841 r debug set-disable-deny-scripts 1
1842
1843 for {set i 2} {$i <= 3} {incr i} {
1844 for {set client_proto 2} {$client_proto <= 3} {incr client_proto} {
1845 if {[lsearch $::denytags "resp3"] >= 0} {
1846 if {$client_proto == 3} {continue}
1847 } elseif {$::force_resp3} {
1848 if {$client_proto == 2} {continue}
1849 }
1850 r hello $client_proto
1851 set extra "RESP$i/$client_proto"
1852 r readraw 1
1853
1854 test "test $extra big number protocol parsing" {
1855 set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'bignum')" 0]
1856 if {$client_proto == 2 || $i == 2} {
1857 # if either Lua or the client is RESP2 the reply will be RESP2
1858 assert_equal $ret {$37}
1859 assert_equal [r read] {1234567999999999999999999999999999999}
1860 } else {
1861 assert_equal $ret {(1234567999999999999999999999999999999}
1862 }
1863 }
1864
1865 test "test $extra malformed big number protocol parsing" {
1866 set ret [run_script "return {big_number='123\\r\\n123'}" 0]
1867 if {$client_proto == 2} {
1868 # if either Lua or the client is RESP2 the reply will be RESP2
1869 assert_equal $ret {$8}
1870 assert_equal [r read] {123 123}
1871 } else {
1872 assert_equal $ret {(123 123}
1873 }
1874 }
1875
1876 test "test $extra map protocol parsing" {
1877 set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'map')" 0]
1878 if {$client_proto == 2 || $i == 2} {
1879 # if either Lua or the client is RESP2 the reply will be RESP2
1880 assert_equal $ret {*6}
1881 } else {
1882 assert_equal $ret {%3}
1883 }
1884 for {set j 0} {$j < 6} {incr j} {
1885 r read
1886 }
1887 }
1888
1889 test "test $extra set protocol parsing" {
1890 set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'set')" 0]
1891 if {$client_proto == 2 || $i == 2} {
1892 # if either Lua or the client is RESP2 the reply will be RESP2
1893 assert_equal $ret {*3}
1894 } else {
1895 assert_equal $ret {~3}
1896 }
1897 for {set j 0} {$j < 3} {incr j} {
1898 r read
1899 }
1900 }
1901
1902 test "test $extra double protocol parsing" {
1903 set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'double')" 0]
1904 if {$client_proto == 2 || $i == 2} {
1905 # if either Lua or the client is RESP2 the reply will be RESP2
1906 assert_equal $ret {$5}
1907 assert_equal [r read] {3.141}
1908 } else {
1909 assert_equal $ret {,3.141}
1910 }
1911 }
1912
1913 test "test $extra null protocol parsing" {
1914 set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'null')" 0]
1915 if {$client_proto == 2} {
1916 # null is a special case in which a Lua client format does not effect the reply to the client
1917 assert_equal $ret {$-1}
1918 } else {
1919 assert_equal $ret {_}
1920 }
1921 } {}
1922
1923 test "test $extra verbatim protocol parsing" {
1924 set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'verbatim')" 0]
1925 if {$client_proto == 2 || $i == 2} {
1926 # if either Lua or the client is RESP2 the reply will be RESP2
1927 assert_equal $ret {$25}
1928 assert_equal [r read] {This is a verbatim}
1929 assert_equal [r read] {string}
1930 } else {
1931 assert_equal $ret {=29}
1932 assert_equal [r read] {txt:This is a verbatim}
1933 assert_equal [r read] {string}
1934 }
1935 }
1936
1937 test "test $extra true protocol parsing" {
1938 set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'true')" 0]
1939 if {$client_proto == 2 || $i == 2} {
1940 # if either Lua or the client is RESP2 the reply will be RESP2
1941 assert_equal $ret {:1}
1942 } else {
1943 assert_equal $ret {#t}
1944 }
1945 }
1946
1947 test "test $extra false protocol parsing" {
1948 set ret [run_script "redis.setresp($i);return redis.call('debug', 'protocol', 'false')" 0]
1949 if {$client_proto == 2 || $i == 2} {
1950 # if either Lua or the client is RESP2 the reply will be RESP2
1951 assert_equal $ret {:0}
1952 } else {
1953 assert_equal $ret {#f}
1954 }
1955 }
1956
1957 r readraw 0
1958 r hello 2
1959 }
1960 }
1961
1962 # attribute is not relevant to test with resp2
1963 test {test resp3 attribute protocol parsing} {
1964 # attributes are not (yet) expose to the script
1965 # So here we just check the parser handles them and they are ignored.
1966 run_script "redis.setresp(3);return redis.call('debug', 'protocol', 'attrib')" 0
1967 } {Some real reply following the attribute}
1968
1969 test "Script block the time during execution" {
1970 assert_equal [run_script {
1971 redis.call("SET", "key", "value", "PX", "1")
1972 redis.call("DEBUG", "SLEEP", 0.01)
1973 return redis.call("EXISTS", "key")
1974 } 1 key] 1
1975
1976 assert_equal 0 [r EXISTS key]
1977 }
1978
1979 test "Script delete the expired key" {
1980 r DEBUG set-active-expire 0
1981 r SET key value PX 1
1982 after 2
1983
1984 # use DEBUG OBJECT to make sure it doesn't error (means the key still exists)
1985 r DEBUG OBJECT key
1986
1987 assert_equal [run_script {return redis.call('EXISTS', 'key')} 1 key] 0
1988 assert_equal 0 [r EXISTS key]
1989 r DEBUG set-active-expire 1
1990 }
1991
1992 test "TIME command using cached time" {
1993 set res [run_script {
1994 local result1 = {redis.call("TIME")}
1995 redis.call("DEBUG", "SLEEP", 0.01)
1996 local result2 = {redis.call("TIME")}
1997 return {result1, result2}
1998 } 0]
1999 assert_equal [lindex $res 0] [lindex $res 1]
2000 }
2001
2002 test "Script block the time in some expiration related commands" {
2003 # The test uses different commands to set the "same" expiration time for different keys,
2004 # and interspersed with "DEBUG SLEEP", to verify that time is frozen in script.
2005 # The commands involved are [P]TTL / SET EX[PX] / [P]EXPIRE / GETEX / [P]SETEX / [P]EXPIRETIME
2006 set res [run_script {
2007 redis.call("SET", "key1{t}", "value", "EX", 1)
2008 redis.call("DEBUG", "SLEEP", 0.01)
2009
2010 redis.call("SET", "key2{t}", "value", "PX", 1000)
2011 redis.call("DEBUG", "SLEEP", 0.01)
2012
2013 redis.call("SET", "key3{t}", "value")
2014 redis.call("EXPIRE", "key3{t}", 1)
2015 redis.call("DEBUG", "SLEEP", 0.01)
2016
2017 redis.call("SET", "key4{t}", "value")
2018 redis.call("PEXPIRE", "key4{t}", 1000)
2019 redis.call("DEBUG", "SLEEP", 0.01)
2020
2021 redis.call("SETEX", "key5{t}", 1, "value")
2022 redis.call("DEBUG", "SLEEP", 0.01)
2023
2024 redis.call("PSETEX", "key6{t}", 1000, "value")
2025 redis.call("DEBUG", "SLEEP", 0.01)
2026
2027 redis.call("SET", "key7{t}", "value")
2028 redis.call("GETEX", "key7{t}", "EX", 1)
2029 redis.call("DEBUG", "SLEEP", 0.01)
2030
2031 redis.call("SET", "key8{t}", "value")
2032 redis.call("GETEX", "key8{t}", "PX", 1000)
2033 redis.call("DEBUG", "SLEEP", 0.01)
2034
2035 local ttl_results = {redis.call("TTL", "key1{t}"),
2036 redis.call("TTL", "key2{t}"),
2037 redis.call("TTL", "key3{t}"),
2038 redis.call("TTL", "key4{t}"),
2039 redis.call("TTL", "key5{t}"),
2040 redis.call("TTL", "key6{t}"),
2041 redis.call("TTL", "key7{t}"),
2042 redis.call("TTL", "key8{t}")}
2043
2044 local pttl_results = {redis.call("PTTL", "key1{t}"),
2045 redis.call("PTTL", "key2{t}"),
2046 redis.call("PTTL", "key3{t}"),
2047 redis.call("PTTL", "key4{t}"),
2048 redis.call("PTTL", "key5{t}"),
2049 redis.call("PTTL", "key6{t}"),
2050 redis.call("PTTL", "key7{t}"),
2051 redis.call("PTTL", "key8{t}")}
2052
2053 local expiretime_results = {redis.call("EXPIRETIME", "key1{t}"),
2054 redis.call("EXPIRETIME", "key2{t}"),
2055 redis.call("EXPIRETIME", "key3{t}"),
2056 redis.call("EXPIRETIME", "key4{t}"),
2057 redis.call("EXPIRETIME", "key5{t}"),
2058 redis.call("EXPIRETIME", "key6{t}"),
2059 redis.call("EXPIRETIME", "key7{t}"),
2060 redis.call("EXPIRETIME", "key8{t}")}
2061
2062 local pexpiretime_results = {redis.call("PEXPIRETIME", "key1{t}"),
2063 redis.call("PEXPIRETIME", "key2{t}"),
2064 redis.call("PEXPIRETIME", "key3{t}"),
2065 redis.call("PEXPIRETIME", "key4{t}"),
2066 redis.call("PEXPIRETIME", "key5{t}"),
2067 redis.call("PEXPIRETIME", "key6{t}"),
2068 redis.call("PEXPIRETIME", "key7{t}"),
2069 redis.call("PEXPIRETIME", "key8{t}")}
2070
2071 return {ttl_results, pttl_results, expiretime_results, pexpiretime_results}
2072 } 8 key1{t} key2{t} key3{t} key4{t} key5{t} key6{t} key7{t} key8{t}]
2073
2074 # The elements in each list are equal.
2075 assert_equal 1 [llength [lsort -unique [lindex $res 0]]]
2076 assert_equal 1 [llength [lsort -unique [lindex $res 1]]]
2077 assert_equal 1 [llength [lsort -unique [lindex $res 2]]]
2078 assert_equal 1 [llength [lsort -unique [lindex $res 3]]]
2079
2080 # Then we check that the expiration time is set successfully.
2081 assert_morethan [lindex $res 0] 0
2082 assert_morethan [lindex $res 1] 0
2083 assert_morethan [lindex $res 2] 0
2084 assert_morethan [lindex $res 3] 0
2085 }
2086
2087 test "RESTORE expired keys with expiration time" {
2088 set res [run_script {
2089 redis.call("SET", "key1{t}", "value")
2090 local encoded = redis.call("DUMP", "key1{t}")
2091
2092 redis.call("RESTORE", "key2{t}", 1, encoded, "REPLACE")
2093 redis.call("DEBUG", "SLEEP", 0.01)
2094 redis.call("RESTORE", "key3{t}", 1, encoded, "REPLACE")
2095
2096 return {redis.call("PEXPIRETIME", "key2{t}"), redis.call("PEXPIRETIME", "key3{t}")}
2097 } 3 key1{t} key2{t} key3{t}]
2098
2099 # Can get the expiration time and they are all equal.
2100 assert_morethan [lindex $res 0] 0
2101 assert_equal [lindex $res 0] [lindex $res 1]
2102 }
2103
2104 r debug set-disable-deny-scripts 0
2105}
2106
2107start_server {tags {"scripting"}} {
2108 test "Test script flush will not leak memory - script:$is_eval" {
2109 r flushall
2110 r script flush
2111 r function flush
2112
2113 # This is a best-effort test to check we don't leak some resources on
2114 # script flush and function flush commands. For lua vm, we create a
2115 # jemalloc thread cache. On each script flush command, thread cache is
2116 # destroyed and we create a new one. In this test, running script flush
2117 # many times to verify there is no increase in the memory usage while
2118 # re-creating some of the resources for lua vm.
2119 set used_memory [s used_memory]
2120 set allocator_allocated [s allocator_allocated]
2121
2122 r multi
2123 for {set j 1} {$j <= 500} {incr j} {
2124 if {$is_eval} {
2125 r SCRIPT FLUSH
2126 } else {
2127 r FUNCTION FLUSH
2128 }
2129 }
2130 r exec
2131
2132 # Verify used memory is not (much) higher.
2133 assert_lessthan [s used_memory] [expr $used_memory*1.5]
2134 assert_lessthan [s allocator_allocated] [expr $allocator_allocated*1.5]
2135 }
2136
2137 test "Verify Lua performs GC correctly after script loading" {
2138 set dummy_script "--[string repeat x 10]\nreturn "
2139 set n 50000
2140 for {set i 0} {$i < $n} {incr i} {
2141 set script "$dummy_script[format "%06d" $i]"
2142 if {$is_eval} {
2143 r script load $script
2144 } else {
2145 r function load "#!lua name=test$i\nredis.register_function('test$i', function(KEYS, ARGV)\n $script \nend)"
2146 }
2147 }
2148
2149 if {$is_eval} {
2150 assert_lessthan [s used_memory_lua] 17500000
2151 } else {
2152 assert_lessthan [s used_memory_vm_functions] 14500000
2153 }
2154 } {} {debug_defrag:skip}
2155}
2156} ;# foreach is_eval
2157
2158
2159# Scripting "shebang" notation tests
2160start_server {tags {"scripting"}} {
2161 test "Shebang support for lua engine" {
2162 catch {
2163 r eval {#!not-lua
2164 return 1
2165 } 0
2166 } e
2167 assert_match {*Unexpected engine in script shebang*} $e
2168
2169 assert_equal [r eval {#!lua
2170 return 1
2171 } 0] 1
2172 }
2173
2174 test "Unknown shebang option" {
2175 catch {
2176 r eval {#!lua badger=data
2177 return 1
2178 } 0
2179 } e
2180 assert_match {*Unknown lua shebang option*} $e
2181 }
2182
2183 test "Unknown shebang flag" {
2184 catch {
2185 r eval {#!lua flags=allow-oom,what?
2186 return 1
2187 } 0
2188 } e
2189 assert_match {*Unexpected flag in script shebang*} $e
2190 }
2191
2192 test "allow-oom shebang flag" {
2193 r set x 123
2194
2195 r config set maxmemory 1
2196
2197 # Fail to execute deny-oom command in OOM condition (backwards compatibility mode without flags)
2198 assert_error {OOM command not allowed when used memory > 'maxmemory'*} {
2199 r eval {
2200 redis.call('set','x',1)
2201 return 1
2202 } 1 x
2203 }
2204 # Can execute non deny-oom commands in OOM condition (backwards compatibility mode without flags)
2205 assert_equal [
2206 r eval {
2207 return redis.call('get','x')
2208 } 1 x
2209 ] {123}
2210
2211 # Fail to execute regardless of script content when we use default flags in OOM condition
2212 assert_error {OOM *} {
2213 r eval {#!lua flags=
2214 return 1
2215 } 0
2216 }
2217
2218 # Script with allow-oom can write despite being in OOM state
2219 assert_equal [
2220 r eval {#!lua flags=allow-oom
2221 redis.call('set','x',1)
2222 return 1
2223 } 1 x
2224 ] 1
2225
2226 # read-only scripts implies allow-oom
2227 assert_equal [
2228 r eval {#!lua flags=no-writes
2229 redis.call('get','x')
2230 return 1
2231 } 0
2232 ] 1
2233 assert_equal [
2234 r eval_ro {#!lua flags=no-writes
2235 redis.call('get','x')
2236 return 1
2237 } 1 x
2238 ] 1
2239
2240 # Script with no shebang can read in OOM state
2241 assert_equal [
2242 r eval {
2243 redis.call('get','x')
2244 return 1
2245 } 1 x
2246 ] 1
2247
2248 # Script with no shebang can read in OOM state (eval_ro variant)
2249 assert_equal [
2250 r eval_ro {
2251 redis.call('get','x')
2252 return 1
2253 } 1 x
2254 ] 1
2255
2256 r config set maxmemory 0
2257 } {OK} {needs:config-maxmemory}
2258
2259 test "no-writes shebang flag" {
2260 assert_error {ERR Write commands are not allowed from read-only scripts*} {
2261 r eval {#!lua flags=no-writes
2262 redis.call('set','x',1)
2263 return 1
2264 } 1 x
2265 }
2266 }
2267
2268 start_server {tags {"external:skip"}} {
2269 r -1 set x "some value"
2270 test "no-writes shebang flag on replica" {
2271 r replicaof [srv -1 host] [srv -1 port]
2272 wait_for_condition 50 100 {
2273 [s role] eq {slave} &&
2274 [string match {*master_link_status:up*} [r info replication]]
2275 } else {
2276 fail "Can't turn the instance into a replica"
2277 }
2278
2279 assert_equal [
2280 r eval {#!lua flags=no-writes
2281 return redis.call('get','x')
2282 } 1 x
2283 ] "some value"
2284
2285 assert_error {READONLY You can't write against a read only replica.} {
2286 r eval {#!lua
2287 return redis.call('get','x')
2288 } 1 x
2289 }
2290
2291 # test no-write inside multi-exec
2292 r multi
2293 r eval {#!lua flags=no-writes
2294 redis.call('get','x')
2295 return 1
2296 } 1 x
2297 assert_equal [r exec] 1
2298
2299 # test no shebang without write inside multi-exec
2300 r multi
2301 r eval {
2302 redis.call('get','x')
2303 return 1
2304 } 1 x
2305 assert_equal [r exec] 1
2306
2307 # temporarily set the server to master, so it doesn't block the queuing
2308 # and we can test the evaluation of the flags on exec
2309 r replicaof no one
2310 set rr [redis_client]
2311 set rr2 [redis_client]
2312 $rr multi
2313 $rr2 multi
2314
2315 # test write inside multi-exec
2316 # we don't need to do any actual write
2317 $rr eval {#!lua
2318 return 1
2319 } 0
2320
2321 # test no shebang with write inside multi-exec
2322 $rr2 eval {
2323 redis.call('set','x',1)
2324 return 1
2325 } 1 x
2326
2327 r replicaof [srv -1 host] [srv -1 port]
2328
2329 # To avoid -LOADING reply, wait until replica syncs with master.
2330 wait_for_condition 50 100 {
2331 [s master_link_status] eq {up}
2332 } else {
2333 fail "Replica did not sync in time."
2334 }
2335
2336 assert_error {EXECABORT Transaction discarded because of: READONLY *} {$rr exec}
2337 assert_error {READONLY You can't write against a read only replica. script: *} {$rr2 exec}
2338 $rr close
2339 $rr2 close
2340 }
2341 }
2342
2343 test "not enough good replicas" {
2344 r set x "some value"
2345 r config set min-replicas-to-write 1
2346
2347 assert_equal [
2348 r eval {#!lua flags=no-writes
2349 return redis.call('get','x')
2350 } 1 x
2351 ] "some value"
2352
2353 assert_equal [
2354 r eval {
2355 return redis.call('get','x')
2356 } 1 x
2357 ] "some value"
2358
2359 assert_error {NOREPLICAS *} {
2360 r eval {#!lua
2361 return redis.call('get','x')
2362 } 1 x
2363 }
2364
2365 assert_error {NOREPLICAS *} {
2366 r eval {
2367 return redis.call('set','x', 1)
2368 } 1 x
2369 }
2370
2371 r config set min-replicas-to-write 0
2372 }
2373
2374 test "not enough good replicas state change during long script" {
2375 r set x "pre-script value"
2376 r config set min-replicas-to-write 1
2377 r config set lua-time-limit 10
2378 start_server {tags {"external:skip"}} {
2379 # add a replica and wait for the master to recognize it's online
2380 r slaveof [srv -1 host] [srv -1 port]
2381 wait_replica_online [srv -1 client]
2382
2383 # run a slow script that does one write, then waits for INFO to indicate
2384 # that the replica dropped, and then runs another write
2385 set rd [redis_deferring_client -1]
2386 $rd eval {
2387 redis.call('set','x',"script value")
2388 while true do
2389 local info = redis.call('info','replication')
2390 if (string.match(info, "connected_slaves:0")) then
2391 redis.call('set','x',info)
2392 break
2393 end
2394 end
2395 return 1
2396 } 1 x
2397
2398 # wait for the script to time out and yield
2399 wait_for_condition 100 100 {
2400 [catch {r -1 ping} e] == 1
2401 } else {
2402 fail "Can't wait for script to start running"
2403 }
2404 catch {r -1 ping} e
2405 assert_match {BUSY*} $e
2406
2407 # cause the replica to disconnect (triggering the busy script to exit)
2408 r slaveof no one
2409
2410 # make sure the script was able to write after the replica dropped
2411 assert_equal [$rd read] 1
2412 assert_match {*connected_slaves:0*} [r -1 get x]
2413
2414 $rd close
2415 }
2416 r config set min-replicas-to-write 0
2417 r config set lua-time-limit 5000
2418 } {OK} {external:skip needs:repl}
2419
2420 test "allow-stale shebang flag" {
2421 r config set replica-serve-stale-data no
2422 r replicaof 127.0.0.1 1
2423
2424 assert_error {MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'.} {
2425 r eval {
2426 return redis.call('get','x')
2427 } 1 x
2428 }
2429
2430 assert_error {MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'.} {
2431 r eval {#!lua flags=no-writes
2432 return 1
2433 } 0
2434 }
2435
2436 assert_equal [
2437 r eval {#!lua flags=allow-stale,no-writes
2438 return 1
2439 } 0
2440 ] 1
2441
2442
2443 assert_error {*Can not execute the command on a stale replica*} {
2444 r eval {#!lua flags=allow-stale,no-writes
2445 return redis.call('get','x')
2446 } 1 x
2447 }
2448
2449 assert_match {foobar} [
2450 r eval {#!lua flags=allow-stale,no-writes
2451 return redis.call('echo','foobar')
2452 } 0
2453 ]
2454
2455 # Test again with EVALSHA
2456 set sha [
2457 r script load {#!lua flags=allow-stale,no-writes
2458 return redis.call('echo','foobar')
2459 }
2460 ]
2461 assert_match {foobar} [r evalsha $sha 0]
2462
2463 r replicaof no one
2464 r config set replica-serve-stale-data yes
2465 set _ {}
2466 } {} {external:skip}
2467
2468 test "reject script do not cause a Lua stack leak" {
2469 r config set maxmemory 1
2470 for {set i 0} {$i < 50} {incr i} {
2471 assert_error {OOM *} {r eval {#!lua
2472 return 1
2473 } 0}
2474 }
2475 r config set maxmemory 0
2476 assert_equal [r eval {#!lua
2477 return 1
2478 } 0] 1
2479 }
2480}
2481
2482# Additional eval only tests
2483start_server {tags {"scripting"}} {
2484 test "Consistent eval error reporting" {
2485 r config resetstat
2486 r config set maxmemory 1
2487 # Script aborted due to Redis state (OOM) should report script execution error with detailed internal error
2488 assert_error {OOM command not allowed when used memory > 'maxmemory'*} {
2489 r eval {return redis.call('set','x','y')} 1 x
2490 }
2491 assert_equal [errorrstat OOM r] {count=1}
2492 assert_equal [s total_error_replies] {1}
2493 assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r]
2494 assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval r]
2495
2496 # redis.pcall() failure due to Redis state (OOM) returns lua error table with Redis error message without '-' prefix
2497 r config resetstat
2498 assert_equal [
2499 r eval {
2500 local t = redis.pcall('set','x','y')
2501 if t['err'] == "OOM command not allowed when used memory > 'maxmemory'." then
2502 return 1
2503 else
2504 return 0
2505 end
2506 } 1 x
2507 ] 1
2508 # error stats were not incremented
2509 assert_equal [errorrstat ERR r] {}
2510 assert_equal [errorrstat OOM r] {count=1}
2511 assert_equal [s total_error_replies] {1}
2512 assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r]
2513 assert_match {calls=1*rejected_calls=0,failed_calls=0*} [cmdrstat eval r]
2514
2515 # Returning an error object from lua is handled as a valid RESP error result.
2516 r config resetstat
2517 assert_error {OOM command not allowed when used memory > 'maxmemory'.} {
2518 r eval { return redis.pcall('set','x','y') } 1 x
2519 }
2520 assert_equal [errorrstat ERR r] {}
2521 assert_equal [errorrstat OOM r] {count=1}
2522 assert_equal [s total_error_replies] {1}
2523 assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r]
2524 assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval r]
2525
2526 r config set maxmemory 0
2527 r config resetstat
2528 # Script aborted due to error result of Redis command
2529 assert_error {ERR DB index is out of range*} {
2530 r eval {return redis.call('select',99)} 0
2531 }
2532 assert_equal [errorrstat ERR r] {count=1}
2533 assert_equal [s total_error_replies] {1}
2534 assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat select r]
2535 assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval r]
2536
2537 # redis.pcall() failure due to error in Redis command returns lua error table with redis error message without '-' prefix
2538 r config resetstat
2539 assert_equal [
2540 r eval {
2541 local t = redis.pcall('select',99)
2542 if t['err'] == "ERR DB index is out of range" then
2543 return 1
2544 else
2545 return 0
2546 end
2547 } 0
2548 ] 1
2549 assert_equal [errorrstat ERR r] {count=1} ;
2550 assert_equal [s total_error_replies] {1}
2551 assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat select r]
2552 assert_match {calls=1*rejected_calls=0,failed_calls=0*} [cmdrstat eval r]
2553
2554 # Script aborted due to scripting specific error state (write cmd with eval_ro) should report script execution error with detailed internal error
2555 r config resetstat
2556 assert_error {ERR Write commands are not allowed from read-only scripts*} {
2557 r eval_ro {return redis.call('set','x','y')} 1 x
2558 }
2559 assert_equal [errorrstat ERR r] {count=1}
2560 assert_equal [s total_error_replies] {1}
2561 assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r]
2562 assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval_ro r]
2563
2564 # redis.pcall() failure due to scripting specific error state (write cmd with eval_ro) returns lua error table with Redis error message without '-' prefix
2565 r config resetstat
2566 assert_equal [
2567 r eval_ro {
2568 local t = redis.pcall('set','x','y')
2569 if t['err'] == "ERR Write commands are not allowed from read-only scripts." then
2570 return 1
2571 else
2572 return 0
2573 end
2574 } 1 x
2575 ] 1
2576 assert_equal [errorrstat ERR r] {count=1}
2577 assert_equal [s total_error_replies] {1}
2578 assert_match {calls=0*rejected_calls=1,failed_calls=0*} [cmdrstat set r]
2579 assert_match {calls=1*rejected_calls=0,failed_calls=0*} [cmdrstat eval_ro r]
2580
2581 r config resetstat
2582 # make sure geoadd will failed
2583 r set Sicily 1
2584 assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} {
2585 r eval {return redis.call('GEOADD', 'Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania')} 1 x
2586 }
2587 assert_equal [errorrstat WRONGTYPE r] {count=1}
2588 assert_equal [s total_error_replies] {1}
2589 assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat geoadd r]
2590 assert_match {calls=1*rejected_calls=0,failed_calls=1*} [cmdrstat eval r]
2591 } {} {cluster:skip}
2592
2593 test "LUA redis.error_reply API" {
2594 r config resetstat
2595 assert_error {MY_ERR_CODE custom msg} {
2596 r eval {return redis.error_reply("MY_ERR_CODE custom msg")} 0
2597 }
2598 assert_equal [errorrstat MY_ERR_CODE r] {count=1}
2599 }
2600
2601 test "LUA redis.error_reply API with empty string" {
2602 r config resetstat
2603 assert_error {ERR} {
2604 r eval {return redis.error_reply("")} 0
2605 }
2606 assert_equal [errorrstat ERR r] {count=1}
2607 }
2608
2609 test "LUA redis.status_reply API" {
2610 r config resetstat
2611 r readraw 1
2612 assert_equal [
2613 r eval {return redis.status_reply("MY_OK_CODE custom msg")} 0
2614 ] {+MY_OK_CODE custom msg}
2615 r readraw 0
2616 assert_equal [errorrstat MY_ERR_CODE r] {} ;# error stats were not incremented
2617 }
2618
2619 test "LUA test pcall" {
2620 assert_equal [
2621 r eval {local status, res = pcall(function() return 1 end); return 'status: ' .. tostring(status) .. ' result: ' .. res} 0
2622 ] {status: true result: 1}
2623 }
2624
2625 test "LUA test pcall with error" {
2626 assert_match {status: false result:*Script attempted to access nonexistent global variable 'foo'} [
2627 r eval {local status, res = pcall(function() return foo end); return 'status: ' .. tostring(status) .. ' result: ' .. res} 0
2628 ]
2629 }
2630
2631 test "LUA test pcall with non string/integer arg" {
2632 assert_error "ERR Lua redis lib command arguments must be strings or integers*" {
2633 r eval {
2634 local x={}
2635 return redis.call("ping", x)
2636 } 0
2637 }
2638 # run another command, to make sure the cached argv array survived
2639 assert_equal [
2640 r eval {
2641 return redis.call("ping", "asdf")
2642 } 0
2643 ] {asdf}
2644 }
2645
2646 test "LUA test trim string as expected" {
2647 # this test may fail if we use different memory allocator than jemalloc, as libc for example may keep the old size on realloc.
2648 if {[string match {*jemalloc*} [s mem_allocator]]} {
2649 # test that when using LUA cache mechanism, if there is free space in the argv array, the string is trimmed.
2650 r set foo [string repeat "a" 45]
2651 set expected_memory [r memory usage foo]
2652
2653 # Jemalloc will allocate for the requested 63 bytes, 80 bytes.
2654 # We can't test for larger sizes because LUA_CMD_OBJCACHE_MAX_LEN is 64.
2655 # This value will be recycled to be used in the next argument.
2656 # We use SETNX to avoid saving the string which will prevent us to reuse it in the next command.
2657 r eval {
2658 return redis.call("SETNX", "foo", string.rep("a", 63))
2659 } 0
2660
2661 # Jemalloc will allocate for the request 45 bytes, 56 bytes.
2662 # we can't test for smaller sizes because OBJ_ENCODING_EMBSTR_SIZE_LIMIT is 44 where no trim is done.
2663 r eval {
2664 return redis.call("SET", "foo", string.rep("a", 45))
2665 } 0
2666
2667 # Assert the string has been trimmed and the 80 bytes from the previous alloc were not kept.
2668 assert { [r memory usage foo] <= $expected_memory};
2669 }
2670 }
2671
2672 test {EVAL - explicit error() call handling} {
2673 # error("simple string error")
2674 assert_error {ERR user_script:1: simple string error script: *} {
2675 r eval "error('simple string error')" 0
2676 }
2677
2678 # error({"err": "ERR table error"})
2679 assert_error {ERR table error script: *} {
2680 r eval "error({err='ERR table error'})" 0
2681 }
2682
2683 # error({})
2684 assert_error {ERR unknown error script: *} {
2685 r eval "error({})" 0
2686 }
2687 }
2688}