Remove unused test files for lua-resty-mlcache

This commit is contained in:
Théophile Diot 2025-01-16 10:26:03 +01:00
parent 597aa7d2e3
commit d3a25aca34
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
16 changed files with 0 additions and 10024 deletions

View file

@ -1,603 +0,0 @@
# vim:set ts=4 sts=4 sw=4 et ft=:
use strict;
use lib '.';
use t::TestMLCache;
plan tests => repeat_each() * blocks() * 5;
run_tests();
__DATA__
=== TEST 1: new() ensures shm exists
--- config
location /t {
content_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
local ipc, err = mlcache_ipc.new("foo")
ngx.say(err)
}
}
--- response_body
no such lua_shared_dict: foo
--- no_error_log
[error]
[crit]
[alert]
=== TEST 2: broadcast() sends an event through shm
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "received event from my_channel: ", data)
end)
}
--- config
location /t {
content_by_lua_block {
assert(ipc:broadcast("my_channel", "hello world"))
assert(ipc:poll())
}
}
--- ignore_response_body
--- error_log
received event from my_channel: hello world
--- no_error_log
[error]
[crit]
[alert]
=== TEST 3: broadcast() runs event callback in protected mode
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
ipc:subscribe("my_channel", function(data)
error("my callback had an error")
end)
}
--- config
location /t {
content_by_lua_block {
assert(ipc:broadcast("my_channel", "hello world"))
assert(ipc:poll())
}
}
--- ignore_response_body
--- error_log eval
qr/\[error\] .*? \[ipc\] callback for channel 'my_channel' threw a Lua error: init_worker_by_lua(.*?)?:\d: my callback had an error/
--- no_error_log
lua entry thread aborted: runtime error
[crit]
[alert]
=== TEST 4: poll() catches invalid timeout arg
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
}
--- config
location /t {
content_by_lua_block {
local ok, err = pcall(ipc.poll, ipc, false)
if not ok then
ngx.say(err)
end
}
}
--- response_body
timeout must be a number
--- no_error_log
[error]
[crit]
[alert]
=== TEST 5: poll() catches up with all events
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "received event from my_channel: ", data)
end)
}
--- config
location /t {
content_by_lua_block {
assert(ipc:broadcast("my_channel", "msg 1"))
assert(ipc:broadcast("my_channel", "msg 2"))
assert(ipc:broadcast("my_channel", "msg 3"))
assert(ipc:poll())
}
}
--- ignore_response_body
--- error_log
received event from my_channel: msg 1
received event from my_channel: msg 2
received event from my_channel: msg 3
--- no_error_log
[error]
=== TEST 6: poll() resumes to current idx if events were previously evicted
This ensures new workers spawned during a master process' lifecycle do not
attempt to replay all events from index 0.
https://github.com/thibaultcha/lua-resty-mlcache/issues/87
https://github.com/thibaultcha/lua-resty-mlcache/issues/93
--- http_config
lua_shared_dict ipc_shm 32k;
init_by_lua_block {
require "resty.core"
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "my_channel event: ", data)
end)
for i = 1, 32 do
-- fill shm, simulating busy workers
-- this must trigger eviction for this test to succeed
assert(ipc:broadcast("my_channel", string.rep(".", 2^10)))
end
}
--- config
location /t {
content_by_lua_block {
ngx.say("ipc.idx: ", ipc.idx)
assert(ipc:broadcast("my_channel", "first broadcast"))
assert(ipc:broadcast("my_channel", "second broadcast"))
-- first poll without new() to simulate new worker
assert(ipc:poll())
-- ipc.idx set to shm_idx-1 ("second broadcast")
ngx.say("ipc.idx: ", ipc.idx)
}
}
--- response_body
ipc.idx: 0
ipc.idx: 34
--- error_log
my_channel event: second broadcast
--- no_error_log
my_channel event: first broadcast
[error]
=== TEST 7: poll() does not execute events from self (same pid)
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm"))
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "received event from my_channel: ", data)
end)
}
--- config
location /t {
content_by_lua_block {
assert(ipc:broadcast("my_channel", "hello world"))
assert(ipc:poll())
}
}
--- ignore_response_body
--- no_error_log
received event from my_channel: hello world
[error]
[crit]
[alert]
=== TEST 8: poll() runs all registered callbacks for a channel
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "callback 1 from my_channel: ", data)
end)
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "callback 2 from my_channel: ", data)
end)
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "callback 3 from my_channel: ", data)
end)
}
--- config
location /t {
content_by_lua_block {
assert(ipc:broadcast("my_channel", "hello world"))
assert(ipc:poll())
}
}
--- ignore_response_body
--- error_log
callback 1 from my_channel: hello world
callback 2 from my_channel: hello world
callback 3 from my_channel: hello world
--- no_error_log
[error]
=== TEST 9: poll() exits when no event to poll
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "callback from my_channel: ", data)
end)
}
--- config
location /t {
content_by_lua_block {
assert(ipc:poll())
}
}
--- ignore_response_body
--- no_error_log
callback from my_channel: hello world
[error]
[crit]
[alert]
=== TEST 10: poll() runs all callbacks from all channels
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "callback 1 from my_channel: ", data)
end)
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "callback 2 from my_channel: ", data)
end)
ipc:subscribe("other_channel", function(data)
ngx.log(ngx.NOTICE, "callback 1 from other_channel: ", data)
end)
ipc:subscribe("other_channel", function(data)
ngx.log(ngx.NOTICE, "callback 2 from other_channel: ", data)
end)
}
--- config
location /t {
content_by_lua_block {
assert(ipc:broadcast("my_channel", "hello world"))
assert(ipc:broadcast("other_channel", "hello ipc"))
assert(ipc:broadcast("other_channel", "hello ipc 2"))
assert(ipc:poll())
}
}
--- ignore_response_body
--- grep_error_log eval: qr/callback \d+ from [^,]*/
--- grep_error_log_out
callback 1 from my_channel: hello world
callback 2 from my_channel: hello world
callback 1 from other_channel: hello ipc
callback 2 from other_channel: hello ipc
callback 1 from other_channel: hello ipc 2
callback 2 from other_channel: hello ipc 2
--- no_error_log
[error]
[crit]
[alert]
=== TEST 11: poll() catches tampered shm (by third-party users)
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
}
--- config
location /t {
content_by_lua_block {
assert(ipc:broadcast("my_channel", "msg 1"))
assert(ngx.shared.ipc_shm:set("lua-resty-ipc:index", false))
local ok, err = ipc:poll()
if not ok then
ngx.say(err)
end
}
}
--- response_body
index is not a number, shm tampered with
--- no_error_log
[error]
[crit]
[alert]
=== TEST 12: poll() retries getting an event until timeout
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
}
--- config
location /t {
content_by_lua_block {
assert(ipc:broadcast("my_channel", "msg 1"))
ngx.shared.ipc_shm:delete(1)
ngx.shared.ipc_shm:flush_expired()
local ok, err = ipc:poll()
if not ok then
ngx.log(ngx.ERR, "could not poll: ", err)
end
}
}
--- ignore_response_body
--- grep_error_log eval: qr/((\[error\] .*?)|(\[ipc\] no event data at index '\d+', retrying .*?))[^,]*/
--- grep_error_log_out eval
qr/\[ipc\] no event data at index '1', retrying in: 0\.001s
\[ipc\] no event data at index '1', retrying in: 0\.002s
\[ipc\] no event data at index '1', retrying in: 0\.004s
\[ipc\] no event data at index '1', retrying in: 0\.008s
\[ipc\] no event data at index '1', retrying in: 0\.016s
\[ipc\] no event data at index '1', retrying in: 0\.032s
\[ipc\] no event data at index '1', retrying in: 0\.064s
\[ipc\] no event data at index '1', retrying in: 0\.128s
\[ipc\] no event data at index '1', retrying in: 0\.045s
\[error\] .*? could not poll: timeout/
--- no_error_log
[warn]
[crit]
[alert]
=== TEST 13: poll() reaches custom timeout
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
}
--- config
location /t {
content_by_lua_block {
assert(ipc:broadcast("my_channel", "msg 1"))
ngx.shared.ipc_shm:delete(1)
ngx.shared.ipc_shm:flush_expired()
local ok, err = ipc:poll(0.01)
if not ok then
ngx.log(ngx.ERR, "could not poll: ", err)
end
}
}
--- ignore_response_body
--- grep_error_log eval: qr/((\[error\] .*?)|(\[ipc\] no event data at index '\d+', retrying .*?))[^,]*/
--- grep_error_log_out eval
qr/\[ipc\] no event data at index '1', retrying in: 0\.001s
\[ipc\] no event data at index '1', retrying in: 0\.002s
\[ipc\] no event data at index '1', retrying in: 0\.004s
\[ipc\] no event data at index '1', retrying in: 0\.003s
\[error\] .*? could not poll: timeout/
--- no_error_log
[warn]
[crit]
[alert]
=== TEST 14: poll() logs errors and continue if event has been tampered with
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "callback from my_channel: ", data)
end)
}
--- config
location /t {
content_by_lua_block {
assert(ipc:broadcast("my_channel", "msg 1"))
assert(ipc:broadcast("my_channel", "msg 2"))
assert(ngx.shared.ipc_shm:set(1, false))
assert(ipc:poll())
}
}
--- ignore_response_body
--- error_log eval
[
qr/\[error\] .*? \[ipc\] event at index '1' is not a string, shm tampered with/,
qr/\[notice\] .*? callback from my_channel: msg 2/,
]
--- no_error_log
[warn]
[crit]
=== TEST 15: poll() is safe to be called in contexts that don't support ngx.sleep()
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "callback from my_channel: ", data)
end)
}
--- config
location /t {
return 200;
log_by_lua_block {
assert(ipc:broadcast("my_channel", "msg 1"))
ngx.shared.ipc_shm:delete(1)
ngx.shared.ipc_shm:flush_expired()
local ok, err = ipc:poll()
if not ok then
ngx.log(ngx.ERR, "could not poll: ", err)
end
}
}
--- ignore_response_body
--- error_log eval
[
qr/\[info\] .*? \[ipc\] no event data at index '1', retrying in: 0\.001s/,
qr/\[warn\] .*? \[ipc\] could not sleep before retry: API disabled in the context of log_by_lua/,
qr/\[error\] .*? could not poll: timeout/,
]
--- no_error_log
[crit]
=== TEST 16: poll() guards self.idx from growing beyond the current shm idx
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "callback from my_channel: ", data)
end)
}
--- config
location /t {
content_by_lua_block {
assert(ipc:broadcast("other_channel", ""))
assert(ipc:poll())
assert(ipc:broadcast("my_channel", "fist broadcast"))
assert(ipc:broadcast("other_channel", ""))
assert(ipc:broadcast("my_channel", "second broadcast"))
-- shm idx is 5, let's mess with the instance's idx
ipc.idx = 10
assert(ipc:poll())
-- we may have skipped the above events, but we are able to resume polling
assert(ipc:broadcast("other_channel", ""))
assert(ipc:broadcast("my_channel", "third broadcast"))
assert(ipc:poll())
}
}
--- ignore_response_body
--- error_log
callback from my_channel: third broadcast
--- no_error_log
callback from my_channel: first broadcast
callback from my_channel: second broadcast
[error]
=== TEST 17: poll() JITs
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "callback from my_channel: ", data)
end)
}
--- config
location /t {
content_by_lua_block {
for i = 1, 10e3 do
assert(ipc:poll())
end
}
}
--- ignore_response_body
--- error_log eval
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):2 loop\]/
--- no_error_log
[warn]
[error]
[crit]
=== TEST 18: broadcast() JITs
--- http_config
init_worker_by_lua_block {
local mlcache_ipc = require "resty.mlcache.ipc"
ipc = assert(mlcache_ipc.new("ipc_shm", true))
ipc:subscribe("my_channel", function(data)
ngx.log(ngx.NOTICE, "callback from my_channel: ", data)
end)
}
--- config
location /t {
content_by_lua_block {
for i = 1, 10e3 do
assert(ipc:broadcast("my_channel", "hello world"))
end
}
}
--- ignore_response_body
--- error_log eval
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):2 loop\]/
--- no_error_log
[warn]
[error]
[crit]

View file

@ -1,533 +0,0 @@
# vim:set ts=4 sts=4 sw=4 et ft=:
use strict;
use lib '.';
use t::TestMLCache;
repeat_each(2);
plan tests => repeat_each() * blocks() * 3;
run_tests();
__DATA__
=== TEST 1: module has version number
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
ngx.say(mlcache._VERSION)
}
}
--- response_body_like
\d+\.\d+\.\d+
--- no_error_log
[error]
=== TEST 2: new() validates name
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = pcall(mlcache.new)
if not ok then
ngx.say(err)
end
}
}
--- response_body
name must be a string
--- no_error_log
[error]
=== TEST 3: new() validates shm
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = pcall(mlcache.new, "name")
if not ok then
ngx.say(err)
end
}
}
--- response_body
shm must be a string
--- no_error_log
[error]
=== TEST 4: new() validates opts
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = pcall(mlcache.new, "name", "cache_shm", "foo")
if not ok then
ngx.say(err)
end
}
}
--- response_body
opts must be a table
--- no_error_log
[error]
=== TEST 5: new() ensures shm exists
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("name", "foo")
if not cache then
ngx.say(err)
end
}
}
--- response_body
no such lua_shared_dict: foo
--- no_error_log
[error]
=== TEST 6: new() supports ipc_shm option and validates it
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = pcall(mlcache.new, "name", "cache_shm", { ipc_shm = 1 })
if not ok then
ngx.say(err)
end
}
}
--- response_body
opts.ipc_shm must be a string
--- no_error_log
[error]
=== TEST 7: new() supports opts.ipc_shm and ensures it exists
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("name", "cache_shm", { ipc_shm = "ipc" })
if not cache then
ngx.log(ngx.ERR, err)
end
}
}
--- ignore_response_body
--- error_log eval
qr/\[error\] .*? no such lua_shared_dict: ipc/
--- no_error_log
[crit]
=== TEST 8: new() supports ipc options and validates it
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = pcall(mlcache.new, "name", "cache_shm", { ipc = false })
if not ok then
ngx.say(err)
end
}
}
--- response_body
opts.ipc must be a table
--- no_error_log
[error]
=== TEST 9: new() prevents both opts.ipc_shm and opts.ipc to be given
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = pcall(mlcache.new, "name", "cache_shm", {
ipc_shm = "ipc",
ipc = {}
})
if not ok then
ngx.say(err)
end
}
}
--- response_body
cannot specify both of opts.ipc_shm and opts.ipc
--- no_error_log
[error]
=== TEST 10: new() validates ipc.register_listeners + ipc.broadcast + ipc.poll (type: custom)
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local args = {
"register_listeners",
"broadcast",
"poll",
}
for _, arg in ipairs(args) do
local ipc_opts = {
register_listeners = function() end,
broadcast = function() end,
poll = function() end,
}
ipc_opts[arg] = false
local ok, err = pcall(mlcache.new, "name", "cache_shm", {
ipc = ipc_opts,
})
if not ok then
ngx.say(err)
end
end
}
}
--- response_body
opts.ipc.register_listeners must be a function
opts.ipc.broadcast must be a function
opts.ipc.poll must be a function
--- no_error_log
[error]
=== TEST 11: new() ipc.register_listeners can return nil + err (type: custom)
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("name", "cache_shm", {
ipc = {
register_listeners = function()
return nil, "something happened"
end,
broadcast = function() end,
poll = function() end,
}
})
if not cache then
ngx.say(err)
end
}
}
--- response_body_like
failed to initialize custom IPC \(opts\.ipc\.register_listeners returned an error\): something happened
--- no_error_log
[error]
=== TEST 12: new() calls ipc.register_listeners with events array (type: custom)
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("name", "cache_shm", {
ipc = {
register_listeners = function(events)
local res = {}
for ev_name, ev in pairs(events) do
table.insert(res, string.format("%s | channel: %s | handler: %s",
ev_name, ev.channel, type(ev.handler)))
end
table.sort(res)
for i = 1, #res do
ngx.say(res[i])
end
end,
broadcast = function() end,
poll = function() end,
}
})
if not cache then
ngx.say(err)
end
}
}
--- response_body
invalidation | channel: mlcache:invalidations:name | handler: function
purge | channel: mlcache:purge:name | handler: function
--- no_error_log
[error]
=== TEST 13: new() ipc.poll is optional (some IPC libraries might not need it
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("name", "cache_shm", {
ipc = {
register_listeners = function() end,
broadcast = function() end,
poll = nil
}
})
if not cache then
ngx.say(err)
end
ngx.say("ok")
}
}
--- response_body
ok
--- no_error_log
[error]
=== TEST 14: new() validates opts.lru_size
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = pcall(mlcache.new, "name", "cache_shm", {
lru_size = "",
})
if not ok then
ngx.log(ngx.ERR, err)
end
}
}
--- response_body
--- error_log
opts.lru_size must be a number
=== TEST 15: new() validates opts.ttl
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = pcall(mlcache.new, "name", "cache_shm", {
ttl = ""
})
if not ok then
ngx.say(err)
end
local ok, err = pcall(mlcache.new, "name", "cache_shm", {
ttl = -1
})
if not ok then
ngx.say(err)
end
}
}
--- response_body
opts.ttl must be a number
opts.ttl must be >= 0
--- no_error_log
[error]
=== TEST 16: new() validates opts.neg_ttl
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = pcall(mlcache.new, "name", "cache_shm", {
neg_ttl = ""
})
if not ok then
ngx.say(err)
end
local ok, err = pcall(mlcache.new, "name", "cache_shm", {
neg_ttl = -1
})
if not ok then
ngx.say(err)
end
}
}
--- response_body
opts.neg_ttl must be a number
opts.neg_ttl must be >= 0
--- no_error_log
[error]
=== TEST 17: new() validates opts.resty_lock_opts
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = pcall(mlcache.new, "name", "cache_shm", {
resty_lock_opts = false,
})
if not ok then
ngx.say(err)
end
}
}
--- response_body
opts.resty_lock_opts must be a table
--- no_error_log
[error]
=== TEST 18: new() validates opts.shm_set_tries
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local values = {
false,
-1,
0,
}
for _, v in ipairs(values) do
local ok, err = pcall(mlcache.new, "name", "cache_shm", {
shm_set_tries = v,
})
if not ok then
ngx.say(err)
end
end
}
}
--- response_body
opts.shm_set_tries must be a number
opts.shm_set_tries must be >= 1
opts.shm_set_tries must be >= 1
--- no_error_log
[error]
=== TEST 19: new() validates opts.shm_miss
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = pcall(mlcache.new, "name", "cache_shm", {
shm_miss = false,
})
if not ok then
ngx.say(err)
end
}
}
--- response_body
opts.shm_miss must be a string
--- no_error_log
[error]
=== TEST 20: new() ensures opts.shm_miss exists
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = mlcache.new("name", "cache_shm", {
shm_miss = "foo",
})
if not ok then
ngx.say(err)
end
}
}
--- response_body
no such lua_shared_dict for opts.shm_miss: foo
--- no_error_log
[error]
=== TEST 21: new() creates an mlcache object with default attributes
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("name", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
end
ngx.say(type(cache))
ngx.say(type(cache.ttl))
ngx.say(type(cache.neg_ttl))
}
}
--- response_body
table
number
number
--- no_error_log
[error]
=== TEST 22: new() accepts user-provided LRU instances via opts.lru
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local pureffi_lrucache = require "resty.lrucache.pureffi"
local my_lru = pureffi_lrucache.new(100)
local cache = assert(mlcache.new("name", "cache_shm", { lru = my_lru }))
ngx.say("lru is user-provided: ", cache.lru == my_lru)
}
}
--- response_body
lru is user-provided: true
--- no_error_log
[error]

File diff suppressed because it is too large Load diff

View file

@ -1,611 +0,0 @@
# vim:set ts=4 sts=4 sw=4 et ft=:
use strict;
use lib '.';
use t::TestMLCache;
workers(2);
#repeat_each(2);
plan tests => repeat_each() * (blocks() * 4);
run_tests();
__DATA__
=== TEST 1: peek() validates key
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local ok, err = pcall(cache.peek, cache)
if not ok then
ngx.say(err)
end
}
}
--- response_body
key must be a string
--- no_error_log
[error]
[crit]
=== TEST 2: peek() returns nil if a key has never been fetched before
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local ttl, err = cache:peek("my_key")
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("ttl: ", ttl)
}
}
--- response_body
ttl: nil
--- no_error_log
[error]
[crit]
=== TEST 3: peek() returns the remaining ttl if a key has been fetched before
--- main_config
timer_resolution 10ms;
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb()
return nil
end
local val, err = cache:get("my_key", { neg_ttl = 19 }, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
local ttl, err = cache:peek("my_key")
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("ttl: ", math.ceil(ttl))
ngx.sleep(1)
local ttl, err = cache:peek("my_key")
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("ttl: ", math.ceil(ttl))
}
}
--- response_body
ttl: 19
ttl: 18
--- no_error_log
[error]
[crit]
=== TEST 4: peek() returns a 0 remaining_ttl if the ttl was 0
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return nil
end
local val, err = cache:get("my_key", { neg_ttl = 0 }, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.sleep(1)
local ttl = assert(cache:peek("my_key"))
ngx.say("ttl: ", math.ceil(ttl))
ngx.sleep(1)
local ttl = assert(cache:peek("my_key"))
ngx.say("ttl: ", math.ceil(ttl))
}
}
--- response_body
ttl: 0
ttl: 0
--- no_error_log
[error]
[crit]
=== TEST 5: peek() returns remaining ttl if shm_miss is specified
--- main_config
timer_resolution 10ms;
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
shm_miss = "cache_shm_miss",
}))
local function cb()
return nil
end
local val, err = cache:get("my_key", { neg_ttl = 19 }, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
local ttl, err = cache:peek("my_key")
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("ttl: ", math.ceil(ttl))
ngx.sleep(1)
local ttl, err = cache:peek("my_key")
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("ttl: ", math.ceil(ttl))
}
}
--- response_body
ttl: 19
ttl: 18
--- no_error_log
[error]
[crit]
=== TEST 6: peek() returns the value if a key has been fetched before
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb_number()
return 123
end
local function cb_nil()
return nil
end
local val, err = cache:get("my_key", nil, cb_number)
if err then
ngx.log(ngx.ERR, err)
return
end
local val, err = cache:get("my_nil_key", nil, cb_nil)
if err then
ngx.log(ngx.ERR, err)
return
end
local ttl, err, val = cache:peek("my_key")
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("ttl: ", math.ceil(ttl), " val: ", val)
local ttl, err, val = cache:peek("my_nil_key")
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("ttl: ", math.ceil(ttl), " nil_val: ", val)
}
}
--- response_body_like
ttl: \d* val: 123
ttl: \d* nil_val: nil
--- no_error_log
[error]
[crit]
=== TEST 7: peek() returns the value if shm_miss is specified
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
shm_miss = "cache_shm_miss",
}))
local function cb_nil()
return nil
end
local val, err = cache:get("my_nil_key", nil, cb_nil)
if err then
ngx.log(ngx.ERR, err)
return
end
local ttl, err, val = cache:peek("my_nil_key")
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("ttl: ", math.ceil(ttl), " nil_val: ", val)
}
}
--- response_body_like
ttl: \d* nil_val: nil
--- no_error_log
[error]
[crit]
=== TEST 8: peek() JITs on hit
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return 123456
end
local val = assert(cache:get("key", nil, cb))
ngx.say("val: ", val)
for i = 1, 10e3 do
assert(cache:peek("key"))
end
}
}
--- response_body
val: 123456
--- error_log eval
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):13 loop\]/
--- no_error_log
[error]
=== TEST 9: peek() JITs on miss
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
for i = 1, 10e3 do
local ttl, err, val = cache:peek("key")
assert(err == nil)
assert(ttl == nil)
assert(val == nil)
end
}
}
--- ignore_response_body
--- error_log eval
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/
--- no_error_log
[error]
[crit]
=== TEST 10: peek() returns nil if a value expired
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
assert(cache:get("my_key", { ttl = 0.3 }, function()
return 123
end))
ngx.sleep(0.3)
local ttl, err, data, stale = cache:peek("my_key")
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("ttl: ", ttl)
ngx.say("data: ", data)
ngx.say("stale: ", stale)
}
}
--- response_body
ttl: nil
data: nil
stale: nil
--- no_error_log
[error]
[crit]
=== TEST 11: peek() returns nil if a value expired in 'shm_miss'
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
shm_miss = "cache_shm_miss"
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local data, err = cache:get("my_key", { neg_ttl = 0.3 }, function()
return nil
end)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.sleep(0.3)
local ttl, err, data, stale = cache:peek("my_key")
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("ttl: ", ttl)
ngx.say("data: ", data)
ngx.say("stale: ", stale)
}
}
--- response_body
ttl: nil
data: nil
stale: nil
--- no_error_log
[error]
[crit]
=== TEST 12: peek() accepts stale arg and returns stale values
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
assert(cache:get("my_key", { ttl = 0.3 }, function()
return 123
end))
ngx.sleep(0.3)
local ttl, err, data, stale = cache:peek("my_key", true)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("ttl: ", ttl)
ngx.say("data: ", data)
ngx.say("stale: ", stale)
}
}
--- response_body_like chomp
ttl: -0\.\d+
data: 123
stale: true
--- no_error_log
[error]
[crit]
=== TEST 13: peek() accepts stale arg and returns stale values from 'shm_miss'
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
shm_miss = "cache_shm_miss"
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local data, err = cache:get("my_key", { neg_ttl = 0.3 }, function()
return nil
end)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.sleep(0.3)
local ttl, err, data, stale = cache:peek("my_key", true)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("ttl: ", ttl)
ngx.say("data: ", data)
ngx.say("stale: ", stale)
}
}
--- response_body_like chomp
ttl: -0\.\d+
data: nil
stale: true
--- no_error_log
[error]
[crit]
=== TEST 14: peek() does not evict stale items from L2 shm
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ttl = 0.3,
}))
local data, err = cache:get("key", nil, function()
return 123
end)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.sleep(0.3)
for i = 1, 3 do
remaining_ttl, err, data = cache:peek("key", true)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("remaining_ttl: ", remaining_ttl)
ngx.say("data: ", data)
end
}
}
--- response_body_like chomp
remaining_ttl: -\d\.\d+
data: 123
remaining_ttl: -\d\.\d+
data: 123
remaining_ttl: -\d\.\d+
data: 123
--- no_error_log
[error]
[crit]
=== TEST 15: peek() does not evict stale negative data from L2 shm_miss
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
neg_ttl = 0.3,
shm_miss = "cache_shm_miss",
}))
local data, err = cache:get("key", nil, function()
return nil
end)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.sleep(0.3)
for i = 1, 3 do
remaining_ttl, err, data = cache:peek("key", true)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("remaining_ttl: ", remaining_ttl)
ngx.say("data: ", data)
end
}
}
--- response_body_like chomp
remaining_ttl: -\d\.\d+
data: nil
remaining_ttl: -\d\.\d+
data: nil
remaining_ttl: -\d\.\d+
data: nil
--- no_error_log
[error]
[crit]

View file

@ -1,81 +0,0 @@
# vim:set ts=4 sts=4 sw=4 et ft=:
use strict;
use lib '.';
use t::TestMLCache;
workers(2);
#repeat_each(2);
plan tests => repeat_each() * blocks() * 3;
run_tests();
__DATA__
=== TEST 1: update() errors if no ipc
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local ok, err = pcall(cache.update, cache, "foo")
ngx.say(err)
}
}
--- response_body
no polling configured, specify opts.ipc_shm or opts.ipc.poll
--- no_error_log
[error]
=== TEST 2: update() calls ipc poll() with timeout arg
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc = {
register_listeners = function() end,
broadcast = function() end,
poll = function(...)
ngx.say("called poll() with args: ", ...)
return true
end,
}
}))
assert(cache:update(3.5, "not me"))
}
}
--- response_body
called poll() with args: 3.5
--- no_error_log
[error]
=== TEST 3: update() JITs when no events to catch up
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
for i = 1, 10e3 do
assert(cache:update())
end
}
}
--- ignore_response_body
--- error_log eval
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):8 loop\]/
--- no_error_log
[error]

View file

@ -1,588 +0,0 @@
# vim:set ts=4 sts=4 sw=4 et ft=:
use strict;
use lib '.';
use t::TestMLCache;
#repeat_each(2);
plan tests => repeat_each() * blocks() * 4;
run_tests();
__DATA__
=== TEST 1: set() errors if no ipc
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local ok, err = pcall(cache.set, cache, "foo")
ngx.say(err)
}
}
--- response_body
no ipc to propagate update, specify opts.ipc_shm or opts.ipc
--- no_error_log
[error]
[crit]
=== TEST 2: set() validates key
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
local ok, err = pcall(cache.set, cache)
if not ok then
ngx.say(err)
end
}
}
--- response_body
key must be a string
--- no_error_log
[error]
[crit]
=== TEST 3: set() puts a value directly in shm
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
-- setting a value in shm
assert(cache:set("my_key", nil, 123))
-- declaring a callback that MUST NOT be called
local function cb()
ngx.log(ngx.ERR, "callback was called but should not have")
end
-- try to get()
local value = assert(cache:get("my_key", nil, cb))
ngx.say("value from get(): ", value)
-- value MUST BE in lru
local value_lru = cache.lru:get("my_key")
ngx.say("cache lru value after get(): ", value_lru)
}
}
--- response_body
value from get(): 123
cache lru value after get(): 123
--- no_error_log
[error]
[crit]
=== TEST 4: set() puts a negative hit directly in shm_miss if specified
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
shm_miss = "cache_shm_miss",
}))
-- setting a value in shm
assert(cache:set("my_key", nil, nil))
-- declaring a callback that MUST NOT be called
local function cb()
ngx.log(ngx.ERR, "callback was called but should not have")
end
-- try to get()
local value, err = cache:get("my_key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("value from get(): ", value)
}
}
--- response_body
value from get(): nil
--- no_error_log
[error]
[crit]
=== TEST 5: set() puts a value directly in its own LRU
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
-- setting a value in shm
assert(cache:set("my_key", nil, 123))
-- value MUST BE be in lru
local value_lru = cache.lru:get("my_key")
ngx.say("cache lru value after set(): ", value_lru)
}
}
--- response_body
cache lru value after set(): 123
--- no_error_log
[error]
[crit]
=== TEST 6: set() respects 'ttl' for non-nil values
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
-- setting a non-nil value in shm
assert(cache:set("my_key", {
ttl = 0.2,
neg_ttl = 1,
}, 123))
-- declaring a callback that logs accesses
local function cb()
ngx.say("callback called")
return 123
end
-- try to get() (callback MUST NOT be called)
ngx.say("calling get()")
local value = assert(cache:get("my_key", nil, cb))
ngx.say("value from get(): ", value)
-- wait until expiry
ngx.say("waiting until expiry...")
ngx.sleep(0.3)
ngx.say("waited 0.3s")
-- try to get() (callback MUST be called)
ngx.say("calling get()")
local value = assert(cache:get("my_key", nil, cb))
ngx.say("value from get(): ", value)
}
}
--- response_body
calling get()
value from get(): 123
waiting until expiry...
waited 0.3s
calling get()
callback called
value from get(): 123
--- no_error_log
[error]
[crit]
=== TEST 7: set() respects 'neg_ttl' for nil values
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
-- setting a nil value in shm
assert(cache:set("my_key", {
ttl = 1,
neg_ttl = 0.2,
}, nil))
-- declaring a callback that logs accesses
local function cb()
ngx.say("callback called")
return nil
end
-- try to get() (callback MUST NOT be called)
ngx.say("calling get()")
local value, err = cache:get("my_key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
end
ngx.say("value from get(): ", value)
-- wait until expiry
ngx.say("waiting until expiry...")
ngx.sleep(0.3)
ngx.say("waited 0.3s")
-- try to get() (callback MUST be called)
ngx.say("calling get()")
local value, err = cache:get("my_key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
end
ngx.say("value from get(): ", value)
}
}
--- response_body
calling get()
value from get(): nil
waiting until expiry...
waited 0.3s
calling get()
callback called
value from get(): nil
--- no_error_log
[error]
[crit]
=== TEST 8: set() respects 'set_shm_tries'
--- config
location /t {
content_by_lua_block {
local dict = ngx.shared.cache_shm
dict:flush_all()
dict:flush_expired()
local mlcache = require "resty.mlcache"
-- fill up shm
local idx = 0
while true do
local ok, err, forcible = dict:set(idx, string.rep("a", 2^2))
if not ok then
ngx.log(ngx.ERR, err)
return
end
if forcible then
break
end
idx = idx + 1
end
-- shm:set() will evict up to 30 items when the shm is full
-- now, trigger a hit with a larger value which should trigger LRU
-- eviction and force the slab allocator to free pages
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
local data, err = cache:set("key", {
shm_set_tries = 5,
}, string.rep("a", 2^12))
if err then
ngx.log(ngx.ERR, err)
return
end
-- from shm
cache.lru:delete("key")
local cb_called
local function cb()
cb_called = true
end
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("type of data in shm: ", type(data))
ngx.say("callback was called: ", cb_called ~= nil)
}
}
--- response_body
type of data in shm: string
callback was called: false
--- no_error_log
[warn]
[error]
=== TEST 9: set() with shm_miss can set a nil where a value was
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
shm_miss = "cache_shm_miss",
}))
local function cb()
return 123
end
-- install a non-nil value in the cache
local value, err = cache:get("my_key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("initial value from get(): ", value)
-- override that value with a negative hit that
-- must go in the shm_miss (and the shm value must be
-- erased)
assert(cache:set("my_key", nil, nil))
-- and remove it from the LRU
cache.lru:delete("my_key")
-- ok, now we should be getting nil from the cache
local value, err = cache:get("my_key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("value from get() after set(): ", value)
}
}
--- response_body
initial value from get(): 123
value from get() after set(): nil
--- no_error_log
[error]
[crit]
=== TEST 10: set() with shm_miss can set a value where a nil was
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
shm_miss = "cache_shm_miss",
}))
local function cb()
return nil
end
-- install a non-nil value in the cache
local value, err = cache:get("my_key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("initial value from get(): ", value)
-- override that value with a negative hit that
-- must go in the shm_miss (and the shm value must be
-- erased)
assert(cache:set("my_key", nil, 123))
-- and remove it from the LRU
cache.lru:delete("my_key")
-- ok, now we should be getting nil from the cache
local value, err = cache:get("my_key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("value from get() after set(): ", value)
}
}
--- response_body
initial value from get(): nil
value from get() after set(): 123
--- no_error_log
[error]
[crit]
=== TEST 11: set() returns 'no memory' errors upon fragmentation in the shm
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
-- fill shm
local idx = 0
while true do
local ok, err, forcible = ngx.shared.cache_shm:set(idx, true)
if not ok then
ngx.log(ngx.ERR, err)
return
end
if forcible then
break
end
idx = idx + 1
end
-- set large value
local ok, err = cache:set("my_key", { shm_set_tries = 1 }, string.rep("a", 2^10))
ngx.say(ok)
ngx.say(err)
}
}
--- response_body
nil
could not write to lua_shared_dict 'cache_shm': no memory
--- no_error_log
[warn]
[error]
=== TEST 12: set() does not set LRU upon shm insertion error
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
-- fill shm
local idx = 0
while true do
local ok, err, forcible = ngx.shared.cache_shm:set(idx, true)
if not ok then
ngx.log(ngx.ERR, err)
return
end
if forcible then
break
end
idx = idx + 1
end
-- set large value
local ok = cache:set("my_key", { shm_set_tries = 1 }, string.rep("a", 2^10))
assert(ok == nil)
local data = cache.lru:get("my_key")
ngx.say(data)
}
}
--- response_body
nil
--- no_error_log
[error]
[crit]
=== TEST 13: set() calls broadcast() with invalidated key
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc = {
register_listeners = function() end,
broadcast = function(channel, data, ...)
ngx.say("channel: ", channel)
ngx.say("data: ", data)
ngx.say("other args:", ...)
return true
end,
poll = function() end,
}
}))
assert(cache:set("my_key", nil, nil))
}
}
--- response_body
channel: mlcache:invalidations:my_mlcache
data: my_key
other args:
--- no_error_log
[error]
[crit]

View file

@ -1,228 +0,0 @@
# vim:set ts=4 sts=4 sw=4 et ft=:
use strict;
use lib '.';
use t::TestMLCache;
workers(2);
#repeat_each(2);
plan tests => repeat_each() * blocks() * 3;
run_tests();
__DATA__
=== TEST 1: delete() errors if no ipc
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local ok, err = pcall(cache.delete, cache, "foo")
ngx.say(err)
}
}
--- response_body
no ipc to propagate deletion, specify opts.ipc_shm or opts.ipc
--- no_error_log
[error]
=== TEST 2: delete() validates key
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
local ok, err = pcall(cache.delete, cache, 123)
ngx.say(err)
}
}
--- response_body
key must be a string
--- no_error_log
[error]
=== TEST 3: delete() removes a cached value from LRU + shm
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
local value = 123
local function cb()
ngx.say("in callback")
return value
end
-- set a value (callback call)
local data = assert(cache:get("key", nil, cb))
ngx.say("from callback: ", data)
-- get a value (no callback call)
data = assert(cache:get("key", nil, cb))
ngx.say("from LRU: ", data)
-- test if value is set from shm (safer to check due to the key)
local v = ngx.shared.cache_shm:get(cache.name .. "key")
ngx.say("shm has value before delete: ", v ~= nil)
-- delete the value
assert(cache:delete("key"))
local v = ngx.shared.cache_shm:get(cache.name .. "key")
ngx.say("shm has value after delete: ", v ~= nil)
-- ensure LRU was also deleted
v = cache.lru:get("key")
ngx.say("from LRU: ", v)
-- start over from callback again
value = 456
data = assert(cache:get("key", nil, cb))
ngx.say("from callback: ", data)
}
}
--- response_body
in callback
from callback: 123
from LRU: 123
shm has value before delete: true
shm has value after delete: false
from LRU: nil
in callback
from callback: 456
--- no_error_log
[error]
=== TEST 4: delete() removes a cached nil from shm_miss if specified
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
shm_miss = "cache_shm_miss",
}))
local value = nil
local function cb()
ngx.say("in callback")
return value
end
-- set a value (callback call)
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from callback: ", data)
-- get a value (no callback call)
data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from LRU: ", data)
-- test if value is set from shm (safer to check due to the key)
local v = ngx.shared.cache_shm_miss:get(cache.name .. "key")
ngx.say("shm_miss has value before delete: ", v ~= nil)
-- delete the value
assert(cache:delete("key"))
local v = ngx.shared.cache_shm_miss:get(cache.name .. "key")
ngx.say("shm_miss has value after delete: ", v ~= nil)
-- ensure LRU was also deleted
v = cache.lru:get("key")
ngx.say("from LRU: ", v)
-- start over from callback again
value = 456
data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from callback again: ", data)
}
}
--- response_body
in callback
from callback: nil
from LRU: nil
shm_miss has value before delete: true
shm_miss has value after delete: false
from LRU: nil
in callback
from callback again: 456
--- no_error_log
[error]
=== TEST 5: delete() calls broadcast with invalidated key
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc = {
register_listeners = function() end,
broadcast = function(channel, data, ...)
ngx.say("channel: ", channel)
ngx.say("data: ", data)
ngx.say("other args:", ...)
return true
end,
poll = function() end,
}
}))
assert(cache:delete("my_key"))
}
}
--- response_body
channel: mlcache:invalidations:my_mlcache
data: my_key
other args:
--- no_error_log
[error]

View file

@ -1,811 +0,0 @@
# vim:set ts=4 sts=4 sw=4 et ft=:
use strict;
use lib '.';
use t::TestMLCache;
workers(2);
#repeat_each(2);
plan tests => repeat_each() * blocks() * 3;
run_tests();
__DATA__
=== TEST 1: l1_serializer is validated by the constructor
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = pcall(mlcache.new, "my_mlcache", "cache_shm", {
l1_serializer = false,
})
if not ok then
ngx.say(err)
end
}
}
--- response_body
opts.l1_serializer must be a function
--- no_error_log
[error]
=== TEST 2: l1_serializer is called on L1+L2 cache misses
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(s)
return string.format("transform(%q)", s)
end,
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local data, err = cache:get("key", nil, function() return "foo" end)
if not data then
ngx.log(ngx.ERR, err)
return
end
ngx.say(data)
}
}
--- response_body
transform("foo")
--- no_error_log
[error]
=== TEST 3: get() JITs when hit of scalar value coming from shm with l1_serializer
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(i)
return i + 2
end,
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb_number()
return 123456
end
for i = 1, 10e2 do
local data = assert(cache:get("number", nil, cb_number))
assert(data == 123458)
cache.lru:delete("number")
end
}
}
--- ignore_response_body
--- error_log eval
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):18 loop\]/
--- no_error_log
[error]
=== TEST 4: l1_serializer is not called on L1 hits
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local calls = 0
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(s)
calls = calls + 1
return string.format("transform(%q)", s)
end,
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
for i = 1, 3 do
local data, err = cache:get("key", nil, function() return "foo" end)
if not data then
ngx.log(ngx.ERR, err)
return
end
ngx.say(data)
end
ngx.say("calls: ", calls)
}
}
--- response_body
transform("foo")
transform("foo")
transform("foo")
calls: 1
--- no_error_log
[error]
=== TEST 5: l1_serializer is called on each L2 hit
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local calls = 0
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(s)
calls = calls + 1
return string.format("transform(%q)", s)
end,
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
for i = 1, 3 do
local data, err = cache:get("key", nil, function() return "foo" end)
if not data then
ngx.log(ngx.ERR, err)
return
end
ngx.say(data)
cache.lru:delete("key")
end
ngx.say("calls: ", calls)
}
}
--- response_body
transform("foo")
transform("foo")
transform("foo")
calls: 3
--- no_error_log
[error]
=== TEST 6: l1_serializer is called on boolean false hits
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(s)
return string.format("transform_boolean(%q)", s)
end,
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb()
return false
end
local data, err = cache:get("key", nil, cb)
if not data then
ngx.log(ngx.ERR, err)
return
end
ngx.say(data)
}
}
--- response_body
transform_boolean("false")
--- no_error_log
[error]
=== TEST 7: l1_serializer is called on lock timeout
--- config
location /t {
content_by_lua_block {
-- insert 2 dummy values to ensure that lock acquisition (which
-- uses shm:set) will _not_ evict out stale cached value
ngx.shared.cache_shm:set(1, true, 0.2)
ngx.shared.cache_shm:set(2, true, 0.2)
local mlcache = require "resty.mlcache"
local cache_1 = assert(mlcache.new("my_mlcache", "cache_shm", {
ttl = 0.3,
resurrect_ttl = 0.3,
l1_serializer = function(s)
return "from cache_1"
end,
}))
local cache_2 = assert(mlcache.new("my_mlcache", "cache_shm", {
ttl = 0.3,
resurrect_ttl = 0.3,
l1_serializer = function(s)
return "from cache_2"
end,
resty_lock_opts = {
timeout = 0.2
}
}))
local function cb(delay, return_val)
if delay then
ngx.sleep(delay)
end
return return_val or 123
end
-- cache in shm
local data, err, hit_lvl = cache_1:get("my_key", nil, cb)
assert(data == "from cache_1")
assert(err == nil)
assert(hit_lvl == 3)
-- make shm + LRU expire
ngx.sleep(0.3)
local t1 = ngx.thread.spawn(function()
-- trigger L3 callback again, but slow to return this time
cache_1:get("my_key", nil, cb, 0.3, 456)
end)
local t2 = ngx.thread.spawn(function()
-- make this mlcache wait on other's callback, and timeout
local data, err, hit_lvl = cache_2:get("my_key", nil, cb)
ngx.say("data: ", data)
ngx.say("err: ", err)
ngx.say("hit_lvl: ", hit_lvl)
end)
assert(ngx.thread.wait(t1))
assert(ngx.thread.wait(t2))
ngx.say()
ngx.say("-> subsequent get()")
data, err, hit_lvl = cache_2:get("my_key", nil, cb, nil, 123)
ngx.say("data: ", data)
ngx.say("err: ", err)
ngx.say("hit_lvl: ", hit_lvl) -- should be 1 since LRU instances are shared by mlcache namespace, and t1 finished
}
}
--- response_body
data: from cache_2
err: nil
hit_lvl: 4
-> subsequent get()
data: from cache_1
err: nil
hit_lvl: 1
--- error_log eval
qr/\[warn\] .*? could not acquire callback lock: timeout/
=== TEST 8: l1_serializer is called when value has < 1ms remaining_ttl
--- config
location /t {
content_by_lua_block {
local forced_now = ngx.now()
ngx.now = function()
return forced_now
end
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ttl = 0.2,
l1_serializer = function(s)
return "override"
end,
}))
local function cb(v)
return v or 42
end
local data, err = cache:get("key", nil, cb)
assert(data == "override", err or "invalid data value: " .. data)
-- drop L1 cache value
cache.lru:delete("key")
-- advance 0.2 second in the future, and simulate another :get()
-- call; the L2 shm entry will still be alive (as its clock is
-- not faked), but mlcache will compute a remaining_ttl of 0;
-- In such cases, we should _not_ cache the value indefinitely in
-- the L1 LRU cache.
forced_now = forced_now + 0.2
local data, err, hit_lvl = cache:get("key", nil, cb)
assert(data == "override", err or "invalid data value: " .. data)
ngx.say("+0.200s hit_lvl: ", hit_lvl)
-- the value is not cached in LRU (too short ttl anyway)
data, err, hit_lvl = cache:get("key", nil, cb)
assert(data == "override", err or "invalid data value: " .. data)
ngx.say("+0.200s hit_lvl: ", hit_lvl)
-- make it expire in shm (real wait)
ngx.sleep(0.201)
data, err, hit_lvl = cache:get("key", nil, cb, 91)
assert(data == "override", err or "invalid data value: " .. data)
ngx.say("+0.201s hit_lvl: ", hit_lvl)
}
}
--- response_body
+0.200s hit_lvl: 2
+0.200s hit_lvl: 2
+0.201s hit_lvl: 3
--- no_error_log
[error]
=== TEST 9: l1_serializer is called in protected mode (L2 miss)
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(s)
error("cannot transform")
end,
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local data, err = cache:get("key", nil, function() return "foo" end)
if not data then
ngx.say(err)
end
ngx.say(data)
}
}
--- response_body_like
l1_serializer threw an error: .*?: cannot transform
--- no_error_log
[error]
=== TEST 10: l1_serializer is called in protected mode (L2 hit)
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local called = false
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(s)
if called then error("cannot transform") end
called = true
return string.format("transform(%q)", s)
end,
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
assert(cache:get("key", nil, function() return "foo" end))
cache.lru:delete("key")
local data, err = cache:get("key", nil, function() return "foo" end)
if not data then
ngx.say(err)
end
ngx.say(data)
}
}
--- response_body_like
l1_serializer threw an error: .*?: cannot transform
--- no_error_log
[error]
=== TEST 11: l1_serializer is not called for L2+L3 misses (no record)
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local called = false
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(s)
called = true
return string.format("transform(%s)", s)
end,
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local data, err = cache:get("key", nil, function() return nil end)
if data ~= nil then
ngx.log(ngx.ERR, "got a value for a L3 miss: ", tostring(data))
return
elseif err ~= nil then
ngx.log(ngx.ERR, "got an error for a L3 miss: ", tostring(err))
return
end
-- our L3 returned nil, we do not call the l1_serializer and
-- we store the LRU nil sentinel value
ngx.say("l1_serializer called for L3 miss: ", called)
-- delete from LRU, and try from L2 again
cache.lru:delete("key")
local data, err = cache:get("key", nil, function() error("not supposed to call") end)
if data ~= nil then
ngx.log(ngx.ERR, "got a value for a L3 miss: ", tostring(data))
return
elseif err ~= nil then
ngx.log(ngx.ERR, "got an error for a L3 miss: ", tostring(err))
return
end
ngx.say("l1_serializer called for L2 negative hit: ", called)
}
}
--- response_body
l1_serializer called for L3 miss: false
l1_serializer called for L2 negative hit: false
--- no_error_log
[error]
=== TEST 12: l1_serializer is not supposed to return a nil value
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(s)
return nil
end,
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local ok, err = cache:get("key", nil, function() return "foo" end)
assert(not ok, "get() should not return successfully")
ngx.say(err)
}
}
--- response_body_like
l1_serializer returned a nil value
--- no_error_log
[error]
=== TEST 13: l1_serializer can return nil + error
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(s)
return nil, "l1_serializer: cannot transform"
end,
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local data, err = cache:get("key", nil, function() return "foo" end)
if not data then
ngx.say(err)
end
ngx.say("data: ", data)
}
}
--- response_body
l1_serializer: cannot transform
data: nil
--- no_error_log
[error]
=== TEST 14: l1_serializer can be given as a get() argument
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local data, err = cache:get("key", {
l1_serializer = function(s)
return string.format("transform(%q)", s)
end
}, function() return "foo" end)
if not data then
ngx.log(ngx.ERR, err)
return
end
ngx.say(data)
}
}
--- response_body
transform("foo")
--- no_error_log
[error]
=== TEST 15: l1_serializer as get() argument has precedence over the constructor one
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(s)
return string.format("constructor(%q)", s)
end
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local data, err = cache:get("key1", {
l1_serializer = function(s)
return string.format("get_argument(%q)", s)
end
}, function() return "foo" end)
if not data then
ngx.log(ngx.ERR, err)
return
end
ngx.say(data)
local data, err = cache:get("key2", nil, function() return "bar" end)
if not data then
ngx.log(ngx.ERR, err)
return
end
ngx.say(data)
}
}
--- response_body
get_argument("foo")
constructor("bar")
--- no_error_log
[error]
=== TEST 16: get() validates l1_serializer is a function
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local ok, err = pcall(cache.get, cache, "key", {
l1_serializer = false,
}, function() return "foo" end)
if not data then
ngx.say(err)
end
}
}
--- response_body
opts.l1_serializer must be a function
--- no_error_log
[error]
=== TEST 17: set() calls l1_serializer
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
l1_serializer = function(s)
return string.format("transform(%q)", s)
end
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local ok, err = cache:set("key", nil, "value")
if not ok then
ngx.log(ngx.ERR, err)
return
end
local value, err = cache:get("key", nil, error)
if not value then
ngx.log(ngx.ERR, err)
return
end
ngx.say(value)
}
}
--- response_body
transform("value")
--- no_error_log
[error]
=== TEST 18: set() calls l1_serializer for boolean false values
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
l1_serializer = function(s)
return string.format("transform_boolean(%q)", s)
end
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local ok, err = cache:set("key", nil, false)
if not ok then
ngx.log(ngx.ERR, err)
return
end
local value, err = cache:get("key", nil, error)
if not value then
ngx.log(ngx.ERR, err)
return
end
ngx.say(value)
}
}
--- response_body
transform_boolean("false")
--- no_error_log
[error]
=== TEST 19: l1_serializer as set() argument has precedence over the constructor one
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
l1_serializer = function(s)
return string.format("constructor(%q)", s)
end
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local ok, err = cache:set("key", {
l1_serializer = function(s)
return string.format("set_argument(%q)", s)
end
}, "value")
if not ok then
ngx.log(ngx.ERR, err)
return
end
local value, err = cache:get("key", nil, error)
if not value then
ngx.log(ngx.ERR, err)
return
end
ngx.say(value)
}
}
--- response_body
set_argument("value")
--- no_error_log
[error]
=== TEST 20: set() validates l1_serializer is a function
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local ok, err = pcall(cache.set, cache, "key", {
l1_serializer = true
}, "value")
if not data then
ngx.say(err)
end
}
}
--- response_body
opts.l1_serializer must be a function
--- no_error_log
[error]

View file

@ -1,362 +0,0 @@
# vim:set ts=4 sts=4 sw=4 et ft=:
use strict;
use lib '.';
use t::TestMLCache;
#repeat_each(2);
plan tests => repeat_each() * blocks() * 3;
run_tests();
__DATA__
=== TEST 1: purge() errors if no ipc
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local ok, err = pcall(cache.purge, cache)
ngx.say(err)
}
}
--- response_body
no ipc to propagate purge, specify opts.ipc_shm or opts.ipc
--- no_error_log
[error]
=== TEST 2: purge() deletes all items from L1 + L2 (sanity 1/2)
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
-- populate mlcache
for i = 1, 100 do
assert(cache:get(tostring(i), nil, function() return i end))
end
-- purge
assert(cache:purge())
for i = 1, 100 do
local value, err = cache:get(tostring(i), nil, function() return nil end)
if err then
ngx.log(ngx.ERR, err)
return
end
if value ~= nil then
ngx.say("key ", i, " had: ", value)
end
end
ngx.say("ok")
}
}
--- response_body
ok
--- no_error_log
[error]
=== TEST 3: purge() deletes all items from L1 (sanity 2/2)
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
-- populate mlcache
for i = 1, 100 do
assert(cache:get(tostring(i), nil, function() return i end))
end
-- purge
assert(cache:purge())
for i = 1, 100 do
local value = cache.lru:get(tostring(i))
if value ~= nil then
ngx.say("key ", i, " had: ", value)
end
end
ngx.say("ok")
}
}
--- response_body
ok
--- no_error_log
[error]
=== TEST 4: purge() deletes all items from L1 with a custom LRU
--- skip_eval: 3: t::TestMLCache::skip_openresty('<', '1.13.6.2')
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local lrucache = require "resty.lrucache"
local lru = lrucache.new(100)
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
lru = lru,
}))
-- populate mlcache
for i = 1, 100 do
assert(cache:get(tostring(i), nil, function() return i end))
end
-- purge
assert(cache:purge())
for i = 1, 100 do
local value = cache.lru:get(tostring(i))
if value ~= nil then
ngx.say("key ", i, " had: ", value)
end
end
ngx.say("ok")
ngx.say("lru instance is the same one: ", lru == cache.lru)
}
}
--- response_body
ok
lru instance is the same one: true
--- no_error_log
[error]
=== TEST 5: purge() is prevented if custom LRU does not support flush_all()
--- skip_eval: 3: t::TestMLCache::skip_openresty('>', '1.13.6.1')
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local lrucache = require "resty.lrucache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
lru = lrucache.new(10),
}))
local pok, perr = pcall(cache.purge, cache)
if not pok then
ngx.say(perr)
return
end
ngx.say("ok")
}
}
--- response_body
cannot purge when using custom LRU cache with OpenResty < 1.13.6.2
--- no_error_log
[error]
=== TEST 6: purge() deletes all items from shm_miss is specified
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
shm_miss = "cache_shm_miss",
}))
-- populate mlcache
for i = 1, 100 do
local _, err = cache:get(tostring(i), nil, function() return nil end)
if err then
ngx.log(ngx.ERR, err)
return
end
end
-- purge
assert(cache:purge())
local called = 0
for i = 1, 100 do
local value, err = cache:get(tostring(i), nil, function() return i end)
if value ~= i then
ngx.say("key ", i, " had: ", value)
end
end
ngx.say("ok")
}
}
--- response_body
ok
--- no_error_log
[error]
=== TEST 7: purge() does not call shm:flush_expired() by default
--- config
location /t {
content_by_lua_block {
do
local cache_shm = ngx.shared.cache_shm
local mt = getmetatable(cache_shm)
local orig_cache_shm_flush_expired = mt.flush_expired
mt.flush_expired = function(self, ...)
ngx.say("flush_expired called with 'max_count'")
return orig_cache_shm_flush_expired(self, ...)
end
end
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
assert(cache:purge())
}
}
--- response_body_unlike
flush_expired called with 'max_count'
--- no_error_log
[error]
=== TEST 8: purge() calls shm:flush_expired() if argument specified
--- config
location /t {
content_by_lua_block {
do
local cache_shm = ngx.shared.cache_shm
local mt = getmetatable(cache_shm)
local orig_cache_shm_flush_expired = mt.flush_expired
mt.flush_expired = function(self, ...)
local arg = { ... }
local n = arg[1]
ngx.say("flush_expired called with 'max_count': ", n)
return orig_cache_shm_flush_expired(self, ...)
end
end
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
}))
assert(cache:purge(true))
}
}
--- response_body
flush_expired called with 'max_count': nil
--- no_error_log
[error]
=== TEST 9: purge() calls shm:flush_expired() if shm_miss is specified
--- config
location /t {
content_by_lua_block {
do
local cache_shm = ngx.shared.cache_shm
local mt = getmetatable(cache_shm)
local orig_cache_shm_flush_expired = mt.flush_expired
mt.flush_expired = function(self, ...)
local arg = { ... }
local n = arg[1]
ngx.say("flush_expired called with 'max_count': ", n)
return orig_cache_shm_flush_expired(self, ...)
end
end
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
shm_miss = "cache_shm_miss",
}))
assert(cache:purge(true))
}
}
--- response_body
flush_expired called with 'max_count': nil
flush_expired called with 'max_count': nil
--- no_error_log
[error]
=== TEST 10: purge() calls broadcast() on purge channel
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc = {
register_listeners = function() end,
broadcast = function(channel, data, ...)
ngx.say("channel: ", channel)
ngx.say("data:", data)
ngx.say("other args:", ...)
return true
end,
poll = function() end,
}
}))
assert(cache:purge())
}
}
--- response_body
channel: mlcache:purge:my_mlcache
data:
other args:
--- no_error_log
[error]

View file

@ -1,344 +0,0 @@
# vim:set ts=4 sts=4 sw=4 et ft=:
use strict;
use lib '.';
use t::TestMLCache;
repeat_each(2);
plan tests => repeat_each() * blocks() * 3;
run_tests();
__DATA__
=== TEST 1: multiple instances with the same name have same lua-resty-lru instance
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache_1 = assert(mlcache.new("my_mlcache", "cache_shm"))
local cache_2 = assert(mlcache.new("my_mlcache", "cache_shm"))
ngx.say("lua-resty-lru instances are the same: ",
cache_1.lru == cache_2.lru)
}
}
--- response_body
lua-resty-lru instances are the same: true
--- no_error_log
[error]
=== TEST 2: multiple instances with different names have different lua-resty-lru instances
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache_1 = assert(mlcache.new("my_mlcache_1", "cache_shm"))
local cache_2 = assert(mlcache.new("my_mlcache_2", "cache_shm"))
ngx.say("lua-resty-lru instances are the same: ",
cache_1.lru == cache_2.lru)
}
}
--- response_body
lua-resty-lru instances are the same: false
--- no_error_log
[error]
=== TEST 3: garbage-collected instances also GC their lru instance
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
collectgarbage("collect")
local cache_1 = assert(mlcache.new("my_mlcache", "cache_shm"))
local cache_2 = assert(mlcache.new("my_mlcache", "cache_shm"))
-- cache something in cache_1's LRU
cache_1.lru:set("key", 123)
-- GC cache_1 (the LRU should survive because it is shared with cache_2)
cache_1 = nil
collectgarbage("collect")
-- prove LRU survived
ngx.say((cache_2.lru:get("key")))
-- GC cache_2 (and the LRU this time, since no more references)
cache_2 = nil
collectgarbage("collect")
-- re-create the caches and a new LRU
cache_1 = assert(mlcache.new("my_mlcache", "cache_shm"))
cache_2 = assert(mlcache.new("my_mlcache", "cache_shm"))
-- this is a new LRU, it has nothing in it
ngx.say((cache_2.lru:get("key")))
}
}
--- response_body
123
nil
--- no_error_log
[error]
=== TEST 4: multiple instances with different names get() of the same key are isolated
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
-- create 2 mlcache
local cache_1 = assert(mlcache.new("my_mlcache_1", "cache_shm"))
local cache_2 = assert(mlcache.new("my_mlcache_2", "cache_shm"))
-- set a value in both mlcaches
local data_1 = assert(cache_1:get("my_key", nil, function() return "value A" end))
local data_2 = assert(cache_2:get("my_key", nil, function() return "value B" end))
-- get values from LRU
local lru_1_value = cache_1.lru:get("my_key")
local lru_2_value = cache_2.lru:get("my_key")
ngx.say("cache_1 lru has: ", lru_1_value)
ngx.say("cache_2 lru has: ", lru_2_value)
-- delete values from LRU
cache_1.lru:delete("my_key")
cache_2.lru:delete("my_key")
-- get values from shm
local shm_1_value = assert(cache_1:get("my_key", nil, function() end))
local shm_2_value = assert(cache_2:get("my_key", nil, function() end))
ngx.say("cache_1 shm has: ", shm_1_value)
ngx.say("cache_2 shm has: ", shm_2_value)
}
}
--- response_body
cache_1 lru has: value A
cache_2 lru has: value B
cache_1 shm has: value A
cache_2 shm has: value B
--- no_error_log
[error]
=== TEST 5: multiple instances with different names delete() of the same key are isolated
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
-- create 2 mlcache
local cache_1 = assert(mlcache.new("my_mlcache_1", "cache_shm", {
ipc_shm = "ipc_shm",
}))
local cache_2 = assert(mlcache.new("my_mlcache_2", "cache_shm", {
ipc_shm = "ipc_shm",
}))
-- set 2 values in both mlcaches
local data_1 = assert(cache_1:get("my_key", nil, function() return "value A" end))
local data_2 = assert(cache_2:get("my_key", nil, function() return "value B" end))
-- test if value is set from shm (safer to check due to the key)
local shm_v = ngx.shared.cache_shm:get(cache_1.name .. "my_key")
ngx.say("cache_1 shm has a value: ", shm_v ~= nil)
-- delete value from mlcache 1
ngx.say("delete from cache_1")
assert(cache_1:delete("my_key"))
-- ensure cache 1 key is deleted from LRU
local lru_v = cache_1.lru:get("my_key")
ngx.say("cache_1 lru has: ", lru_v)
-- ensure cache 1 key is deleted from shm
local shm_v = ngx.shared.cache_shm:get(cache_1.name .. "my_key")
ngx.say("cache_1 shm has: ", shm_v)
-- ensure cache 2 still has its value
local shm_v_2 = ngx.shared.cache_shm:get(cache_2.name .. "my_key")
ngx.say("cache_2 shm has a value: ", shm_v_2 ~= nil)
local lru_v_2 = cache_2.lru:get("my_key")
ngx.say("cache_2 lru has: ", lru_v_2)
}
}
--- response_body
cache_1 shm has a value: true
delete from cache_1
cache_1 lru has: nil
cache_1 shm has: nil
cache_2 shm has a value: true
cache_2 lru has: value B
--- no_error_log
[error]
=== TEST 6: multiple instances with different names peek() of the same key are isolated
--- config
location /t {
content_by_lua_block {
-- must reset the shm so that when repeated, this tests doesn't
-- return unpredictible TTLs (0.9xxxs)
ngx.shared.cache_shm:flush_all()
ngx.shared.cache_shm:flush_expired()
local mlcache = require "resty.mlcache"
-- create 2 mlcaches
local cache_1 = assert(mlcache.new("my_mlcache_1", "cache_shm", {
ipc_shm = "ipc_shm",
}))
local cache_2 = assert(mlcache.new("my_mlcache_2", "cache_shm", {
ipc_shm = "ipc_shm",
}))
-- reset LRUs so repeated tests allow the below get() to set the
-- value in the shm
cache_1.lru:delete("my_key")
cache_2.lru:delete("my_key")
-- set a value in both mlcaches
local data_1 = assert(cache_1:get("my_key", { ttl = 1 }, function() return "value A" end))
local data_2 = assert(cache_2:get("my_key", { ttl = 2 }, function() return "value B" end))
-- peek cache 1
local ttl, err, val = assert(cache_1:peek("my_key"))
ngx.say("cache_1 ttl: ", ttl)
ngx.say("cache_1 value: ", val)
-- peek cache 2
local ttl, err, val = assert(cache_2:peek("my_key"))
ngx.say("cache_2 ttl: ", ttl)
ngx.say("cache_2 value: ", val)
}
}
--- response_body
cache_1 ttl: 1
cache_1 value: value A
cache_2 ttl: 2
cache_2 value: value B
--- no_error_log
[error]
=== TEST 7: non-namespaced instances use different delete() broadcast channel
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
-- create 2 mlcaches
local cache_1 = assert(mlcache.new("my_mlcache_1", "cache_shm", {
ipc = {
register_listeners = function() end,
broadcast = function(channel)
ngx.say("cache_1 channel: ", channel)
return true
end,
poll = function() end,
}
}))
local cache_2 = assert(mlcache.new("my_mlcache_2", "cache_shm", {
ipc = {
register_listeners = function() end,
broadcast = function(channel)
ngx.say("cache_2 channel: ", channel)
return true
end,
poll = function() end,
}
}))
assert(cache_1:delete("my_key"))
assert(cache_2:delete("my_key"))
}
}
--- response_body
cache_1 channel: mlcache:invalidations:my_mlcache_1
cache_2 channel: mlcache:invalidations:my_mlcache_2
--- no_error_log
[error]
=== TEST 8: non-namespaced instances use different purge() broadcast channel
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
-- create 2 mlcaches
local cache_1 = assert(mlcache.new("my_mlcache_1", "cache_shm", {
ipc = {
register_listeners = function() end,
broadcast = function(channel)
ngx.say("cache_1 channel: ", channel)
return true
end,
poll = function() end,
}
}))
local cache_2 = assert(mlcache.new("my_mlcache_2", "cache_shm", {
ipc = {
register_listeners = function() end,
broadcast = function(channel)
ngx.say("cache_2 channel: ", channel)
return true
end,
poll = function() end,
}
}))
assert(cache_1:purge())
assert(cache_2:purge())
}
}
--- response_body
cache_1 channel: mlcache:purge:my_mlcache_1
cache_2 channel: mlcache:purge:my_mlcache_2
--- no_error_log
[error]

View file

@ -1,275 +0,0 @@
# vim:set ts=4 sts=4 sw=4 et ft=:
use strict;
use lib '.';
use t::TestMLCache;
workers(2);
#repeat_each(2);
plan tests => repeat_each() * blocks() * 4;
run_tests();
__DATA__
=== TEST 1: update() with ipc_shm catches up with invalidation events
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
debug = true -- allows same worker to receive its own published events
}))
cache.ipc:subscribe(cache.events.invalidation.channel, function(data)
ngx.log(ngx.NOTICE, "received event from invalidations: ", data)
end)
assert(cache:delete("my_key"))
assert(cache:update())
}
}
--- ignore_response_body
--- error_log
received event from invalidations: my_key
--- no_error_log
[error]
[crit]
=== TEST 2: update() with ipc_shm timeouts when waiting for too long
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
debug = true -- allows same worker to receive its own published events
}))
cache.ipc:subscribe(cache.events.invalidation.channel, function(data)
ngx.log(ngx.NOTICE, "received event from invalidations: ", data)
end)
assert(cache:delete("my_key"))
assert(cache:delete("my_other_key"))
ngx.shared.ipc_shm:delete(2)
local ok, err = cache:update(0.1)
if not ok then
ngx.say(err)
end
}
}
--- response_body
could not poll ipc events: timeout
--- error_log
received event from invalidations: my_key
--- no_error_log
[error]
received event from invalidations: my_other
=== TEST 3: update() with ipc_shm JITs when no events to catch up
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ipc_shm = "ipc_shm",
debug = true -- allows same worker to receive its own published events
}))
for i = 1, 10e3 do
assert(cache:update())
end
}
}
--- ignore_response_body
--- error_log eval
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/
--- no_error_log
[error]
[crit]
=== TEST 4: set() with ipc_shm invalidates other workers' LRU cache
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local opts = {
ipc_shm = "ipc_shm",
debug = true -- allows same worker to receive its own published events
}
local cache = assert(mlcache.new("namespace", "cache_shm", opts))
local cache_clone = assert(mlcache.new("namespace", "cache_shm", opts))
do
local lru_delete = cache.lru.delete
cache.lru.delete = function(self, key)
ngx.say("called lru:delete() with key: ", key)
return lru_delete(self, key)
end
end
assert(cache:set("my_key", nil, nil))
ngx.say("calling update on cache")
assert(cache:update())
ngx.say("calling update on cache_clone")
assert(cache_clone:update())
}
}
--- response_body
calling update on cache
called lru:delete() with key: my_key
calling update on cache_clone
called lru:delete() with key: my_key
--- no_error_log
[error]
[crit]
=== TEST 5: delete() with ipc_shm invalidates other workers' LRU cache
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local opts = {
ipc_shm = "ipc_shm",
debug = true -- allows same worker to receive its own published events
}
local cache = assert(mlcache.new("namespace", "cache_shm", opts))
local cache_clone = assert(mlcache.new("namespace", "cache_shm", opts))
do
local lru_delete = cache.lru.delete
cache.lru.delete = function(self, key)
ngx.say("called lru:delete() with key: ", key)
return lru_delete(self, key)
end
end
assert(cache:delete("my_key"))
ngx.say("calling update on cache")
assert(cache:update())
ngx.say("calling update on cache_clone")
assert(cache_clone:update())
}
}
--- response_body
called lru:delete() with key: my_key
calling update on cache
called lru:delete() with key: my_key
calling update on cache_clone
called lru:delete() with key: my_key
--- no_error_log
[error]
[crit]
=== TEST 6: purge() with mlcache_shm invalidates other workers' LRU cache (OpenResty < 1.13.6.2)
--- skip_eval: 3: t::TestMLCache::skip_openresty('>=', '1.13.6.2')
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local opts = {
ipc_shm = "ipc_shm",
debug = true -- allows same worker to receive its own published events
}
local cache = assert(mlcache.new("namespace", "cache_shm", opts))
local cache_clone = assert(mlcache.new("namespace", "cache_shm", opts))
local lru = cache.lru
local lru_clone = cache_clone.lru
assert(cache:purge())
-- cache.lru should be different now
ngx.say("cache has new lru: ", cache.lru ~= lru)
ngx.say("cache_clone still has same lru: ", cache_clone.lru == lru_clone)
ngx.say("calling update on cache_clone")
assert(cache_clone:update())
-- cache.lru should be different now
ngx.say("cache_clone has new lru: ", cache_clone.lru ~= lru_clone)
}
}
--- response_body
cache has new lru: true
cache_clone still has same lru: true
calling update on cache_clone
cache_clone has new lru: true
--- no_error_log
[error]
[crit]
=== TEST 7: purge() with mlcache_shm invalidates other workers' LRU cache (OpenResty >= 1.13.6.2)
--- skip_eval: 3: t::TestMLCache::skip_openresty('<', '1.13.6.2')
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local opts = {
ipc_shm = "ipc_shm",
debug = true -- allows same worker to receive its own published events
}
local cache = assert(mlcache.new("namespace", "cache_shm", opts))
local cache_clone = assert(mlcache.new("namespace", "cache_shm", opts))
local lru = cache.lru
ngx.say("both instances use the same lru: ", cache.lru == cache_clone.lru)
do
local lru_flush_all = lru.flush_all
cache.lru.flush_all = function(self)
ngx.say("called lru:flush_all()")
return lru_flush_all(self)
end
end
assert(cache:purge())
ngx.say("calling update on cache_clone")
assert(cache_clone:update())
ngx.say("both instances use the same lru: ", cache.lru == cache_clone.lru)
ngx.say("lru didn't change after purge: ", cache.lru == lru)
}
}
--- response_body
both instances use the same lru: true
called lru:flush_all()
calling update on cache_clone
called lru:flush_all()
both instances use the same lru: true
lru didn't change after purge: true
--- no_error_log
[error]
[crit]

View file

@ -1,80 +0,0 @@
# vim:set ts=4 sts=4 sw=4 et ft=:
use strict;
use lib '.';
use t::TestMLCache;
plan tests => repeat_each() * blocks() * 3;
run_tests();
__DATA__
=== TEST 1: new() validates opts.shm_locks
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = pcall(mlcache.new, "name", "cache_shm", {
shm_locks = false,
})
if not ok then
ngx.say(err)
end
}
}
--- response_body
opts.shm_locks must be a string
--- no_error_log
[error]
=== TEST 2: new() ensures opts.shm_locks exists
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local ok, err = mlcache.new("name", "cache_shm", {
shm_locks = "foo",
})
if not ok then
ngx.say(err)
end
}
}
--- response_body
no such lua_shared_dict for opts.shm_locks: foo
--- no_error_log
[error]
=== TEST 3: get() stores resty-locks in opts.shm_locks if specified
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("name", "cache_shm", {
shm_locks = "locks_shm",
}))
local function cb()
local keys = ngx.shared.locks_shm:get_keys()
for i, key in ipairs(keys) do
ngx.say(i, ": ", key)
end
return 123
end
cache:get("key", nil, cb)
}
}
--- response_body
1: lua-resty-mlcache:lock:namekey
--- no_error_log
[error]

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,178 +0,0 @@
# vim:set ts=4 sts=4 sw=4 et ft=:
use strict;
use lib '.';
use t::TestMLCache;
workers(2);
#repeat_each(2);
plan tests => repeat_each() * blocks() * 3;
run_tests();
__DATA__
=== TEST 1: new_bulk() creates a bulk
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local bulk = mlcache.new_bulk()
ngx.say("type: ", type(bulk))
ngx.say("size: ", #bulk)
ngx.say("bulk.n: ", bulk.n)
}
}
--- response_body
type: table
size: 0
bulk.n: 0
--- no_error_log
[error]
=== TEST 2: new_bulk() creates a bulk with narr in arg #1
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local bulk = mlcache.new_bulk(3)
ngx.say("type: ", type(bulk))
ngx.say("size: ", #bulk)
ngx.say("bulk.n: ", bulk.n)
}
}
--- response_body
type: table
size: 0
bulk.n: 0
--- no_error_log
[error]
=== TEST 3: bulk:add() adds bulk operations
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local function cb() end
local bulk = mlcache.new_bulk(3)
for i = 1, 3 do
bulk:add("key_" .. i, nil, cb, i)
end
for i = 1, 3*4, 4 do
ngx.say(tostring(bulk[i]), " ",
tostring(bulk[i + 1]), " ",
tostring(bulk[i + 2]), " ",
tostring(bulk[i + 3]))
end
ngx.say("bulk.n: ", bulk.n)
}
}
--- response_body_like
key_1 nil function: 0x[0-9a-fA-F]+ 1
key_2 nil function: 0x[0-9a-fA-F]+ 2
key_3 nil function: 0x[0-9a-fA-F]+ 3
bulk\.n: 3
--- no_error_log
[error]
=== TEST 4: bulk:add() can be given to get_bulk()
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb(i) return i end
local bulk = mlcache.new_bulk(3)
for i = 1, 3 do
bulk:add("key_" .. i, nil, cb, i)
end
local res, err = cache:get_bulk(bulk)
if not res then
ngx.log(ngx.ERR, err)
return
end
for i = 1, res.n, 3 do
ngx.say(tostring(res[i]), " ",
tostring(res[i + 1]), " ",
tostring(res[i + 2]))
end
}
}
--- response_body
1 nil 3
2 nil 3
3 nil 3
--- no_error_log
[error]
=== TEST 5: each_bulk_res() iterates over get_bulk() results
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local res, err = cache:get_bulk {
"key_a", nil, function() return 1 end, nil,
"key_b", nil, function() return 2 end, nil,
"key_c", nil, function() return 3 end, nil,
n = 3,
}
if not res then
ngx.log(ngx.ERR, err)
return
end
for i, data, err, hit_lvl in mlcache.each_bulk_res(res) do
ngx.say(i, " ", data, " ", err, " ", hit_lvl)
end
}
}
--- response_body
1 1 nil 3
2 2 nil 3
3 3 nil 3
--- no_error_log
[error]
=== TEST 6: each_bulk_res() throws an error on unrocognized res
--- config
location /t {
content_by_lua_block {
local mlcache = require "resty.mlcache"
local pok, perr = pcall(mlcache.each_bulk_res, {})
if not pok then
ngx.say(perr)
end
}
}
--- response_body
res must have res.n field; is this a get_bulk() result?
--- no_error_log
[error]

View file

@ -1,103 +0,0 @@
package t::TestMLCache;
use strict;
use Test::Nginx::Socket::Lua -Base;
use Cwd qw(cwd);
our $pwd = cwd();
our @EXPORT = qw(
$pwd
skip_openresty
);
my $PackagePath = qq{lua_package_path "$pwd/lib/?.lua;;";};
my $HttpConfig = qq{
lua_shared_dict cache_shm 1m;
lua_shared_dict cache_shm_miss 1m;
lua_shared_dict locks_shm 1m;
lua_shared_dict ipc_shm 1m;
init_by_lua_block {
-- local verbose = true
local verbose = false
local outfile = "$Test::Nginx::Util::ErrLogFile"
-- local outfile = "/tmp/v.log"
if verbose then
local dump = require "jit.dump"
dump.on(nil, outfile)
else
local v = require "jit.v"
v.on(outfile)
end
require "resty.core"
-- jit.opt.start("hotloop=1")
-- jit.opt.start("loopunroll=1000000")
-- jit.off()
}
};
add_block_preprocessor(sub {
my $block = shift;
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
my $http_config = $block->http_config || '';
$http_config .= $PackagePath;
if ($http_config !~ m/init_by_lua_block/) {
$http_config .= $HttpConfig;
}
$block->set_value("http_config", $http_config);
});
sub get_openresty_canon_version (@) {
sprintf "%d.%03d%03d%03d", $_[0], $_[1], $_[2], $_[3];
}
sub get_openresty_version () {
my $NginxBinary = $ENV{TEST_NGINX_BINARY} || 'nginx';
my $out = `$NginxBinary -V 2>&1`;
if (!defined $out || $? != 0) {
bail_out("Failed to get the version of the OpenResty in PATH");
die;
}
if ($out =~ m{openresty[^/]*/(\d+)\.(\d+)\.(\d+)\.(\d+)}s) {
return get_openresty_canon_version($1, $2, $3, $4);
}
if ($out =~ m{nginx[^/]*/(\d+)\.(\d+)\.(\d+)}s) {
return;
}
bail_out("Failed to parse the output of \"nginx -V\": $out\n");
die;
}
sub skip_openresty ($$) {
my ($op, $ver) = @_;
my $OpenrestyVersion = get_openresty_version();
if ($ver =~ m{(\d+)\.(\d+)\.(\d+)\.(\d+)}s) {
$ver = get_openresty_canon_version($1, $2, $3, $4);
} else {
bail_out("Invalid skip_openresty() arg: $ver");
die;
}
if (defined $OpenrestyVersion and eval "$OpenrestyVersion $op $ver") {
return 1;
}
}
no_long_string();
1;