Skip to content

Commit

Permalink
feat(globalpatches) better rbusted support
Browse files Browse the repository at this point in the history
- do not patch math.randomseed() in rbusted (we run in a timer contect,
so we naver have a chance to seed in init_worker, hence, we never have a
chance to seed at all if we ever need to)
- add a patch for Luassert's `assert` occasionaly annoying arithmetic error in case
of assertion failure
  • Loading branch information
thibaultcha committed Oct 3, 2016
1 parent 82d6715 commit db71aa5
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 41 deletions.
5 changes: 4 additions & 1 deletion bin/busted
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#!/usr/bin/env resty

require("kong.core.globalpatches")({cli = true})
require("kong.core.globalpatches")({
cli = true,
rbusted = true
})

-- force LuaSocket usage to resolve `/etc/hosts` until
-- supported by resty-cli.
Expand Down
111 changes: 71 additions & 40 deletions kong/core/globalpatches.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,82 @@ return function(options)
ngx.IS_CLI = true
end

do
local meta = require "kong.meta"
if options.rbusted then

_G._KONG = {
_NAME = meta._NAME,
_VERSION = meta._VERSION
}
end
do
-- patch luassert's 'assert' because very often we use the Lua idiom:
-- local res = assert(some_method())
-- in our tests.
-- luassert's 'assert' would error out in case the assertion fails, and
-- if 'some_method()' returns a third return value because we attempt to
-- perform arithmetic (+1) to the 'level' argument of 'assert'.
-- This error would often supersed the actual error (arg #2) and be painful
-- to debug.
local assert = require "luassert.assert"
local assert_mt = getmetatable(assert)
if assert_mt then
assert_mt.__call = function(self, bool, message, level, ...)
if not bool then
local lvl = 1
if type(level) == "number" then
lvl = level + 1
end
error(message or "assertion failed!", lvl)
end
return bool, message, level, ...
end
end
end

else

do
local meta = require "kong.meta"

do
local randomseed = math.randomseed
local seed

--- Seeds the random generator, use with care.
-- Once - properly - seeded, this method is replaced with a stub
-- one. This is to enforce best-practises for seeding in ngx_lua,
-- and prevents third-party modules from overriding our correct seed
-- (many modules make a wrong usage of `math.randomseed()` by calling
-- it multiple times or do not use unique seed for Nginx workers).
--
-- This patched method will create a unique seed per worker process,
-- using a combination of both time and the worker's pid.
-- luacheck: globals math
_G.math.randomseed = function()
if not seed then
-- If we're in runtime nginx, we have multiple workers so we _only_
-- accept seeding when in the 'init_worker' phase.
-- That is because that phase is the earliest one before the
-- workers have a chance to process business logic, and because
-- if we'd do that in the 'init' phase, the Lua VM is not forked
-- yet and all workers would end-up using the same seed.
if not options.cli and ngx.get_phase() ~= "init_worker" then
error("math.randomseed() must be called in init_worker", 2)
_G._KONG = {
_NAME = meta._NAME,
_VERSION = meta._VERSION
}
end

do
local randomseed = math.randomseed
local seed

--- Seeds the random generator, use with care.
-- Once - properly - seeded, this method is replaced with a stub
-- one. This is to enforce best-practises for seeding in ngx_lua,
-- and prevents third-party modules from overriding our correct seed
-- (many modules make a wrong usage of `math.randomseed()` by calling
-- it multiple times or do not use unique seed for Nginx workers).
--
-- This patched method will create a unique seed per worker process,
-- using a combination of both time and the worker's pid.
-- luacheck: globals math
_G.math.randomseed = function()
if not seed then
-- If we're in runtime nginx, we have multiple workers so we _only_
-- accept seeding when in the 'init_worker' phase.
-- That is because that phase is the earliest one before the
-- workers have a chance to process business logic, and because
-- if we'd do that in the 'init' phase, the Lua VM is not forked
-- yet and all workers would end-up using the same seed.
if not options.cli and ngx.get_phase() ~= "init_worker" then
error("math.randomseed() must be called in init_worker", 2)
end

seed = ngx.time() + ngx.worker.pid()
ngx.log(ngx.DEBUG, "random seed: ", seed, " for worker nb ", ngx.worker.id(),
" (pid: ", ngx.worker.pid(), ")")
randomseed(seed)
else
ngx.log(ngx.DEBUG, "attempt to seed random number generator, but ",
"already seeded with ", seed)
end

seed = ngx.time() + ngx.worker.pid()
ngx.log(ngx.DEBUG, "random seed: ", seed, " for worker nb ", ngx.worker.id(),
" (pid: ", ngx.worker.pid(), ")")
randomseed(seed)
else
ngx.log(ngx.DEBUG, "attempt to seed random number generator, but ",
"already seeded with ", seed)
return seed
end

return seed
end

end
end
11 changes: 11 additions & 0 deletions spec/01-unit/00-globalpatches_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
describe("globalpatches", function()
describe("assert", function()
it("does not errors zhen arg #3 is not a number", function()
-- if not patched, this would throw:
-- luassert/assert.lua:155: attempt to perform arithmetic on a string value
assert.error_matches(function()
assert(false, "some error", "some return value")
end, "some error", nil, true)
end)
end)
end)

0 comments on commit db71aa5

Please sign in to comment.