OpenResty + Luaで単体テストを書く際にハマったこと
これは何?
OpenResty環境で使用しているLua Scriptのシステムで単体テストを書きたいと思ったのですが,そこそこ詰まる部分があったので詰まった部分をまとめました。
環境
- OpenResty 1.21.4.1
- busted: Lua Scriptの単体テスト用フレームワーク
詳しくは自分のリポジトリ参照。
ハマりポイント①LuaJITとLuaのバージョンをあっていない際にライブラリがうまく動かないことがある
事象: bustedがうまく実行できない
/usr/local/openresty/luajit/bin/luajit: /usr/local/openresty/lua54/bin/busted:3: unexpected symbol near '-'
前提: LuaJITとは
LuaJIT is a Just-In-Time Compiler for the Lua programming language.
Homepage: http://luajit.org/luajit.html
LuaJIT is enabled by default since OpenResty 1.5.8.1. Please explicitly specify the --with-luajit option while configuring OpenResty older than 1.5.8.1. See Installation for details.
OpenResty公式ドキュメントLuaJIT
OpenRestyは1.5.8.1以降はLuaJITを使用しています。
LuaJITを使うことで通常のLuaよりも高速に動作します。
解決策: LuaRocksでベースとするLuaのバージョンをLuaJITにあわせる
- LuaRocksはLuaで使用するライブラリの管理ツール
- LuaRocksのインストールにはHereRocksを使うとLuaやLuaJITとセットでインストールできる
自分はもともと,Lua5.4を使用していましたが,LuaJITの公式ドキュメントによるとLuaJITはLua5.1をベースに作成されています。
このため,LuaRocksをLua5.1を使用するように変更しました。
以下はluajit 2.1.0-beta3にあわせてluarocksをインストールする例です
usr/local/openresty/luajit/bin/luajit -v
LuaJIT 2.1.0-beta3 -- Copyright (C) 2005-2022 Mike Pall. https://luajit.org/
hererocks luajit21 -j 2.1.0-beta3 -r latest
ライブラリによっては最新のLua5.4にあわせてインストールしても動くものもありそうだが,無難にLuaJITに合わせるべきと思われる。
LUA_PATH
のexportが必要
ハマりポイント②事象: module not foundエラーがでる
以下は自分のbusted
の実行例ですが,LUA_PATH
が通っていないと.soを探して見つからないエラーがでます。
<details><summary>エラー分の詳細</summary>
/usr/local/openresty/luajit21/bin/busted -p _test tests
Error → tests/auth_factory_test.lua @ 1
auth_factory.lua get_auth_instance
./src/auth_factory.lua:2: module 'basic_auth' not found:No LuaRocks module found for basic_auth
no field package.preload['basic_auth']
no file './src/basic_auth.lua'
no file './src/basic_auth/basic_auth.lua'
no file './src/basic_auth/init.lua'
no file '/usr/local/openresty/luajit21/share/lua/5.1/basic_auth.lua'
no file '/usr/local/openresty/luajit21/share/lua/5.1/basic_auth/init.lua'
no file '/root/.luarocks/share/lua/5.1/basic_auth.lua'
no file '/root/.luarocks/share/lua/5.1/basic_auth/init.lua'
no file './csrc/basic_auth.so'
no file './csrc/basic_auth/basic_auth.so'
no file '/usr/local/openresty/luajit21/lib/lua/5.1/basic_auth.so'
no file './basic_auth.so'
no file '/usr/local/openresty/luajit21/lib/lua/5.1/loadall.so'
no file '/root/.luarocks/lib/lua/5.1/basic_auth.so'
/usr/local/openresty/reverse_proxy/tests
</details>
LUA_PATH
のexport
解決策: LUA_PATH
の内容はnginx.confに記載のあるlua_package_path
をコピペしました。TODO: 必要最低限を探してもいいかも
export LUA_PATH="/usr/local/openresty/lualib/?.lua;/usr/local/openresty/luajit/libs/?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/jit/?.lua;/usr/local/openresty/reverse_proxy/src/?.lua;/usr/local/openresty/reverse_proxy/src/auth/?.lua;/usr/local/openresty/lualib/resty/?.lua;/usr/local/openresty/lualib/ngx/?.lua;/usr/local/openresty/?.lua;/usr/local/openresty/luajit21/share/lua/5.1/?.lua"
ハマりポイント③busted実行時にLuaJITに依存するライブラリが使えない
事象: ffi not foundエラーがでる
/usr/local/openresty/lua54/share/lua/5.4/resty/md5.lua:4: module 'ffi' not found:
No LuaRocks module found for ffi
no field package.preload['ffi']
no file './src/ffi.lua'
no file './src/ffi/ffi.lua'
no file './src/ffi/init.lua'
no file '/usr/local/openresty/lua54/share/lua/5.4/ffi.lua'
no file '/usr/local/openresty/lua54/share/lua/5.4/ffi/init.lua'
no file '/usr/local/openresty/lualib/ffi.lua'
no file '/usr/local/openresty/luajit/libs/ffi.lua'
no file '/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/jit/ffi.lua'
no file '/usr/local/openresty/reverse_proxy/src/ffi.lua'
no file '/usr/local/openresty/reverse_proxy/src/auth/ffi.lua'
no file '/usr/local/openresty/lualib/resty/ffi.lua'
no file '/usr/local/openresty/lualib/ngx/ffi.lua'
no file '/usr/local/openresty/ffi.lua'
no file '/root/.luarocks/share/lua/5.4/ffi.lua'
no file '/root/.luarocks/share/lua/5.4/ffi/init.lua'
no file './csrc/ffi.so'
no file './csrc/ffi/ffi.so'
no file '/usr/local/openresty/lua54/lib/lua/5.4/ffi.so'
no file '/usr/local/openresty/lua54/lib/lua/5.4/loadall.so'
no file './ffi.so'
no file '/root/.luarocks/lib/lua/5.4/ffi.so'
解決策: ffiを使用できるようにbustedの実行コマンドを変更する
一部のライブラリはLuaJitでのみサポートされているffi
に依存します。
調査したところ,以下のようにfflをインポートすれば解決できるようです。
以下は実行例です。
local ffi = require "ffi"
export LUA_PATH="/usr/local/openresty/lualib/?.lua;/usr/local/openresty/luajit/libs/?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/jit/?.lua;/usr/local/openresty/reverse_proxy/src/?.lua;/usr/local/openresty/reverse_proxy/src/auth/?.lua;/usr/local/openresty/lualib/resty/?.lua;/usr/local/openresty/lualib/ngx/?.lua;/usr/local/openresty/?.lua;/usr/local/openresty/luajit21/share/lua/5.1/?.lua"
pushd ../
# NOTE: ffiはluajitでのみしかサポートされていないため,https://github.com/lunarmodules/busted/issues/369 を参考に修正
/usr/local/openresty/luajit21/bin/busted --helper=/usr/local/openresty/reverse_proxy/tests/helper.lua -p _test tests
popd
ngx
変数周りのnilに対処が必要
ハマりポイント④OpenRestyが絡む部分で事象: nilエラーがでている
以下は実行例です。
/usr/local/openresty/reverse_proxy /usr/local/openresty/reverse_proxy/tests
✱
0 successes / 0 failures / 1 error / 0 pending : 0.001674 seconds
Error → tests/auth_factory_test.lua @ 1
auth_factory.lua get_auth_instance
...ocal/openresty/luajit21/share/lua/5.1/resty/template.lua:147: attempt to index field 'location' (a nil value)
/usr/local/openresty/reverse_proxy/tests
解決策: ngxをMockする
有志が作っていたfakengxというツールもあるようですが,現在はPublic Archiveになっているので,必要な変数のみを自前でモックすることにしました。
GitHub Copilot Chat/Edit等にエラー文をいれると勝手にMock内容を増やしてくれるで,いい時代になりましたね。
describe("auth_factory.lua get_auth_instance", function()
-- NOTE: ngxのモックが必要だったので雑に作成。ngxをモックする公式のライブラリはなさそう
_G.ngx = {
var = {},
log = function() end,
ERR = "ERR",
INFO = "INFO",
HTTP_INTERNAL_SERVER_ERROR = 500,
exit = function() end,
re = {match = function() return nil end},
location = {},
config = {prefix = function()
return "/usr/local/openresty/nginx/"
end},
get_phase = function() return "init" end,
socket = {
tcp = function()
return {
settimeout = function() end,
connect = function() return true end,
setkeepalive = function() end,
close = function() end,
send = function() return true end,
receive = function() return nil end
}
end
}
}
最後に: サンプル
被テストコード
パラメータによって必要な認証のインスタンスを返す関数です。
local _M = {}
local basic_auth = require "basic_auth"
local digest_auth = require "digest_auth"
local form_auth = require "form_auth"
local _M = {}
function _M.get_auth_instance(auth_type)
if auth_type == "basic" then
return require "basic_auth"
elseif auth_type == "digest" then
return require "digest_auth"
elseif auth_type == "form" then
return require "form_auth"
else
error("Invalid authentication type: " .. auth_type)
end
end
return _M
テストコード
describe("auth_factory.lua get_auth_instance", function()
-- NOTE: ngxのモックが必要だったので雑に作成。ngxをモックする公式のライブラリはなさそう
_G.ngx = {
var = {},
log = function() end,
ERR = "ERR",
INFO = "INFO",
HTTP_INTERNAL_SERVER_ERROR = 500,
exit = function() end,
re = {match = function() return nil end},
location = {},
config = {prefix = function()
return "/usr/local/openresty/nginx/"
end},
get_phase = function() return "init" end,
socket = {
tcp = function()
return {
settimeout = function() end,
connect = function() return true end,
setkeepalive = function() end,
close = function() end,
send = function() return true end,
receive = function() return nil end
}
end
}
}
local auth_factory = require "auth_factory"
it("should return basic auth instance", function()
local auth_instance = auth_factory.get_auth_instance("basic")
assert.is.equal(auth_instance, require "basic_auth")
end)
it("should return digest auth instance", function()
local auth_instance = auth_factory.get_auth_instance("digest")
assert.is.equal(auth_instance, require "digest_auth")
end)
it("should return form auth instance", function()
local auth_instance = auth_factory.get_auth_instance("form")
assert.is.equal(auth_instance, require "form_auth")
end)
it("should throw error for invalid auth type", function()
assert.has_error(function()
auth_factory.get_auth_instance("invalid")
end, "Invalid authentication type: invalid")
end)
end)
#!/bin/bash
# NOTE: nginx.confのlua_package_pathをコピペ LUA_PATHの設定が必要
export LUA_PATH="/usr/local/openresty/lualib/?.lua;/usr/local/openresty/luajit/libs/?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/jit/?.lua;/usr/local/openresty/reverse_proxy/src/?.lua;/usr/local/openresty/reverse_proxy/src/auth/?.lua;/usr/local/openresty/lualib/resty/?.lua;/usr/local/openresty/lualib/ngx/?.lua;/usr/local/openresty/?.lua;/usr/local/openresty/luajit21/share/lua/5.1/?.lua"
pushd ../
# NOTE: ffiはluajitでのみしかサポートされていないため,https://github.com/lunarmodules/busted/issues/369 を参考に修正
/usr/local/openresty/luajit21/bin/busted --helper=/usr/local/openresty/reverse_proxy/tests/helper.lua -p _test tests
popd
感想
- OpenRestyが絡むと,ngxをモックしないといけないのが辛い。testコードを書く工数があがりそうなら,結合テストにある程度寄せる判断をしても良いかもしれない。
- なるべくモックしなくても良いようにファイルをわける設計にすることでテストが書きやすくなりそう
Discussion