From b77991c30b8c3e3bf883c2880c118aded5fd4e04 Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Wed, 19 Nov 2025 11:15:28 +0900 Subject: [PATCH 01/10] fix: support extra_lua_path in test framework Add support for setting custom Lua paths in test files through two complementary methods: 1. Block definitions (preferred): --- extra_lua_path: /custom/path/?.lua --- extra_lua_cpath: /custom/path/?.so 2. YAML configuration parsing: --- extra_yaml_config apisix: extra_lua_path: "/custom/path/?.lua" The implementation prepends custom paths to lua_package_path and lua_package_cpath, aligning test behavior with APISIX runtime where extra_lua_path allows loading custom plugins from specified directories. Block definitions take precedence when both methods are used, ensuring explicit test configuration overrides YAML settings. Added comprehensive test suite (t/admin/extra-lua-path.t) covering: - Path addition via block definitions - YAML configuration parsing - Simultaneous lua_path and lua_cpath configuration - Correct path prepending behavior - Precedence rules between methods This enables testing custom plugins in custom directories without modifying core APISIX paths. Fixes #12389 --- docs/en/latest/internal/testing-framework.md | 21 +++ t/APISIX.pm | 29 ++- t/admin/extra-lua-path.t | 186 +++++++++++++++++++ 3 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 t/admin/extra-lua-path.t diff --git a/docs/en/latest/internal/testing-framework.md b/docs/en/latest/internal/testing-framework.md index 7fcdf01e4d37..6a826caf655e 100644 --- a/docs/en/latest/internal/testing-framework.md +++ b/docs/en/latest/internal/testing-framework.md @@ -111,6 +111,27 @@ GET /index.html no valid upstream node ``` +## Custom Lua Paths + +To test custom plugins in custom directories, you can specify custom Lua paths: + +**Using block definitions:** + +``` +--- extra_lua_path: /custom/path/?.lua +--- extra_lua_cpath: /custom/path/?.so +``` + +**Using YAML config:** + +``` +--- extra_yaml_config +apisix: + extra_lua_path: "/custom/path/?.lua" +``` + +Block definitions take precedence over YAML config when both are provided. + ## Preparing the upstream To test the code, we need to provide a mock upstream. diff --git a/t/APISIX.pm b/t/APISIX.pm index d80e4162a430..8d187ba2bbd7 100644 --- a/t/APISIX.pm +++ b/t/APISIX.pm @@ -271,9 +271,34 @@ deployment: _EOC_ } + my $extra_lua_path = ""; + my $extra_lua_cpath = ""; + + # Method 1: Block definition (preferred) + if ($block->extra_lua_path) { + $extra_lua_path = $block->extra_lua_path . ";"; + } + if ($block->extra_lua_cpath) { + $extra_lua_cpath = $block->extra_lua_cpath . ";"; + } + + # Method 2: Extract from extra_yaml_config if block definition not provided + if (!$extra_lua_path && $block->extra_yaml_config) { + my $extra_yaml = $block->extra_yaml_config; + if ($extra_yaml =~ m/^\s*extra_lua_path:\s*["\']?([^"\'\n]+)["\']?/m) { + $extra_lua_path = $1 . ";"; + } + } + if (!$extra_lua_cpath && $block->extra_yaml_config) { + my $extra_yaml = $block->extra_yaml_config; + if ($extra_yaml =~ m/^\s*extra_lua_cpath:\s*["\']?([^"\'\n]+)["\']?/m) { + $extra_lua_cpath = $1 . ";"; + } + } + my $lua_deps_path = $block->lua_deps_path // <<_EOC_; - lua_package_path "$apisix_home/?.lua;$apisix_home/?/init.lua;$apisix_home/deps/share/lua/5.1/?/init.lua;$apisix_home/deps/share/lua/5.1/?.lua;$apisix_home/apisix/?.lua;$apisix_home/t/?.lua;$apisix_home/t/xrpc/?.lua;$apisix_home/t/xrpc/?/init.lua;;"; - lua_package_cpath "$apisix_home/?.so;$apisix_home/deps/lib/lua/5.1/?.so;$apisix_home/deps/lib64/lua/5.1/?.so;;"; + lua_package_path "$extra_lua_path$apisix_home/?.lua;$apisix_home/?/init.lua;$apisix_home/deps/share/lua/5.1/?/init.lua;$apisix_home/deps/share/lua/5.1/?.lua;$apisix_home/apisix/?.lua;$apisix_home/t/?.lua;$apisix_home/t/xrpc/?.lua;$apisix_home/t/xrpc/?/init.lua;;"; + lua_package_cpath "$extra_lua_cpath$apisix_home/?.so;$apisix_home/deps/lib/lua/5.1/?.so;$apisix_home/deps/lib64/lua/5.1/?.so;;"; _EOC_ my $main_config = $block->main_config // <<_EOC_; diff --git a/t/admin/extra-lua-path.t b/t/admin/extra-lua-path.t new file mode 100644 index 000000000000..9d95d6935c03 --- /dev/null +++ b/t/admin/extra-lua-path.t @@ -0,0 +1,186 @@ +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); + +run_tests; + +__DATA__ + +=== TEST 1: Check extra_lua_path via block definition +Verify that extra_lua_path block definition adds path to lua_package_path +--- extra_lua_path: /test/custom/path/?.lua +--- config + location /t { + content_by_lua_block { + local path = package.path + if string.find(path, "/test/custom/path/?.lua", 1, true) then + ngx.say("FOUND: extra_lua_path is in package.path") + else + ngx.say("NOT FOUND: extra_lua_path is missing") + ngx.say("package.path: ", path) + end + } + } +--- request +GET /t +--- response_body +FOUND: extra_lua_path is in package.path + + + +=== TEST 2: Check extra_lua_cpath via block definition +Verify that extra_lua_cpath block definition adds path to lua_package_cpath +--- extra_lua_cpath: /test/custom/path/?.so +--- config + location /t { + content_by_lua_block { + local cpath = package.cpath + if string.find(cpath, "/test/custom/path/?.so", 1, true) then + ngx.say("FOUND: extra_lua_cpath is in package.cpath") + else + ngx.say("NOT FOUND: extra_lua_cpath is missing") + ngx.say("package.cpath: ", cpath) + end + } + } +--- request +GET /t +--- response_body +FOUND: extra_lua_cpath is in package.cpath + + + +=== TEST 3: Check extra_lua_path from extra_yaml_config +Verify that extra_lua_path is parsed from extra_yaml_config +--- extra_yaml_config +apisix: + extra_lua_path: "/yaml/custom/path/?.lua" +--- config + location /t { + content_by_lua_block { + local path = package.path + if string.find(path, "/yaml/custom/path/?.lua", 1, true) then + ngx.say("FOUND: extra_lua_path from yaml config is in package.path") + else + ngx.say("NOT FOUND: extra_lua_path from yaml config is missing") + ngx.say("package.path: ", path) + end + } + } +--- request +GET /t +--- response_body +FOUND: extra_lua_path from yaml config is in package.path + + + +=== TEST 4: Check extra_lua_cpath from extra_yaml_config +Verify that extra_lua_cpath is parsed from extra_yaml_config +--- extra_yaml_config +apisix: + extra_lua_cpath: "/yaml/custom/path/?.so" +--- config + location /t { + content_by_lua_block { + local cpath = package.cpath + if string.find(cpath, "/yaml/custom/path/?.so", 1, true) then + ngx.say("FOUND: extra_lua_cpath from yaml config is in package.cpath") + else + ngx.say("NOT FOUND: extra_lua_cpath from yaml config is missing") + ngx.say("package.cpath: ", cpath) + end + } + } +--- request +GET /t +--- response_body +FOUND: extra_lua_cpath from yaml config is in package.cpath + + + +=== TEST 5: Check both extra_lua_path and extra_lua_cpath +Verify that both paths can be set simultaneously +--- extra_lua_path: /test/lua/?.lua +--- extra_lua_cpath: /test/so/?.so +--- config + location /t { + content_by_lua_block { + local path = package.path + local cpath = package.cpath + local lua_found = string.find(path, "/test/lua/?.lua", 1, true) + local so_found = string.find(cpath, "/test/so/?.so", 1, true) + + if lua_found and so_found then + ngx.say("FOUND: both extra_lua_path and extra_lua_cpath") + else + ngx.say("NOT FOUND") + ngx.say("lua_path found: ", lua_found and "yes" or "no") + ngx.say("so_path found: ", so_found and "yes" or "no") + end + } + } +--- request +GET /t +--- response_body +FOUND: both extra_lua_path and extra_lua_cpath + + + +=== TEST 6: Check path is prepended (comes first) +Verify that extra_lua_path is at the beginning of package.path +--- extra_lua_path: /first/path/?.lua +--- config + location /t { + content_by_lua_block { + local path = package.path + -- Check if custom path appears before apisix_home + local custom_pos = string.find(path, "/first/path/?.lua", 1, true) + local apisix_pos = string.find(path, "/apisix/?.lua", 1, true) + + if custom_pos and apisix_pos and custom_pos < apisix_pos then + ngx.say("SUCCESS: extra_lua_path is prepended correctly") + else + ngx.say("FAIL: extra_lua_path is not at the beginning") + ngx.say("custom_pos: ", custom_pos or "nil") + ngx.say("apisix_pos: ", apisix_pos or "nil") + end + } + } +--- request +GET /t +--- response_body +SUCCESS: extra_lua_path is prepended correctly + + + +=== TEST 7: Block definition takes precedence over yaml_config +Verify that block definition is used when both are provided +--- extra_lua_path: /block/path/?.lua +--- extra_yaml_config +apisix: + extra_lua_path: "/yaml/path/?.lua" +--- config + location /t { + content_by_lua_block { + local path = package.path + local block_found = string.find(path, "/block/path/?.lua", 1, true) + local yaml_found = string.find(path, "/yaml/path/?.lua", 1, true) + + if block_found and not yaml_found then + ngx.say("SUCCESS: block definition takes precedence") + elseif yaml_found and not block_found then + ngx.say("FAIL: yaml config was used instead of block") + elseif block_found and yaml_found then + ngx.say("UNEXPECTED: both paths found") + else + ngx.say("FAIL: neither path found") + end + } + } +--- request +GET /t +--- response_body +SUCCESS: block definition takes precedence + From 15847d2885dd7ba58b1c1866afc13deb55a67c80 Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Thu, 20 Nov 2025 17:19:11 +0900 Subject: [PATCH 02/10] fix: lint error --- t/admin/extra-lua-path.t | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/t/admin/extra-lua-path.t b/t/admin/extra-lua-path.t index 9d95d6935c03..f91136eef042 100644 --- a/t/admin/extra-lua-path.t +++ b/t/admin/extra-lua-path.t @@ -111,7 +111,7 @@ Verify that both paths can be set simultaneously local cpath = package.cpath local lua_found = string.find(path, "/test/lua/?.lua", 1, true) local so_found = string.find(cpath, "/test/so/?.so", 1, true) - + if lua_found and so_found then ngx.say("FOUND: both extra_lua_path and extra_lua_cpath") else @@ -138,7 +138,7 @@ Verify that extra_lua_path is at the beginning of package.path -- Check if custom path appears before apisix_home local custom_pos = string.find(path, "/first/path/?.lua", 1, true) local apisix_pos = string.find(path, "/apisix/?.lua", 1, true) - + if custom_pos and apisix_pos and custom_pos < apisix_pos then ngx.say("SUCCESS: extra_lua_path is prepended correctly") else @@ -167,7 +167,7 @@ apisix: local path = package.path local block_found = string.find(path, "/block/path/?.lua", 1, true) local yaml_found = string.find(path, "/yaml/path/?.lua", 1, true) - + if block_found and not yaml_found then ngx.say("SUCCESS: block definition takes precedence") elseif yaml_found and not block_found then @@ -183,4 +183,3 @@ apisix: GET /t --- response_body SUCCESS: block definition takes precedence - From 22ebcf895bc09b0506ec252b3b241e1d2a00b9c7 Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Thu, 20 Nov 2025 17:25:54 +0900 Subject: [PATCH 03/10] fix: eclint --- t/APISIX.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/APISIX.pm b/t/APISIX.pm index 8d187ba2bbd7..ec1119c9ab04 100644 --- a/t/APISIX.pm +++ b/t/APISIX.pm @@ -273,7 +273,7 @@ _EOC_ my $extra_lua_path = ""; my $extra_lua_cpath = ""; - + # Method 1: Block definition (preferred) if ($block->extra_lua_path) { $extra_lua_path = $block->extra_lua_path . ";"; @@ -281,7 +281,7 @@ _EOC_ if ($block->extra_lua_cpath) { $extra_lua_cpath = $block->extra_lua_cpath . ";"; } - + # Method 2: Extract from extra_yaml_config if block definition not provided if (!$extra_lua_path && $block->extra_yaml_config) { my $extra_yaml = $block->extra_yaml_config; From 1dea1603aeabbe121158b5e4fad7216a146181f7 Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Thu, 20 Nov 2025 17:29:01 +0900 Subject: [PATCH 04/10] fix: add apache license --- t/admin/extra-lua-path.t | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/t/admin/extra-lua-path.t b/t/admin/extra-lua-path.t index f91136eef042..fbf545a8ce1e 100644 --- a/t/admin/extra-lua-path.t +++ b/t/admin/extra-lua-path.t @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + use t::APISIX 'no_plan'; repeat_each(1); From 0059726d89d633f4133aaf54dc48184d03a6d9d0 Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Sat, 3 Jan 2026 13:14:19 +0900 Subject: [PATCH 05/10] test: add real custom plugin loading test case for extra_lua_path --- t/admin/extra-lua-path.t | 65 +++++++++++++++++++ .../apisix/plugins/test-extra-path.lua | 56 ++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 t/plugin/custom-plugins/apisix/plugins/test-extra-path.lua diff --git a/t/admin/extra-lua-path.t b/t/admin/extra-lua-path.t index fbf545a8ce1e..4d2ed2a967ee 100644 --- a/t/admin/extra-lua-path.t +++ b/t/admin/extra-lua-path.t @@ -200,3 +200,68 @@ apisix: GET /t --- response_body SUCCESS: block definition takes precedence + + + +=== TEST 8: Load and execute custom plugin via extra_lua_path +Verify that a real custom plugin can be loaded and executed using extra_lua_path +--- extra_lua_path: t/plugin/custom-plugins/?.lua +--- extra_yaml_config +plugins: + - test-extra-path +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + + -- Create a route with the custom plugin + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/hello", + "plugins": { + "test-extra-path": { + "header_name": "X-Custom-Plugin", + "header_value": "successfully-loaded" + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say("failed to create route: ", body) + return + end + + ngx.sleep(0.5) + + -- Test the route to verify plugin execution + local http = require("resty.http") + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:" .. ngx.var.server_port .. "/hello") + + if not res then + ngx.say("request failed: ", err) + return + end + + -- Verify the custom plugin set the header + local header_value = res.headers["X-Custom-Plugin"] + if header_value == "successfully-loaded" then + ngx.say("custom plugin loaded and executed successfully") + else + ngx.say("custom plugin header not found or incorrect: ", tostring(header_value)) + end + } + } +--- request +GET /t +--- response_body +custom plugin loaded and executed successfully diff --git a/t/plugin/custom-plugins/apisix/plugins/test-extra-path.lua b/t/plugin/custom-plugins/apisix/plugins/test-extra-path.lua new file mode 100644 index 000000000000..f49052813f5b --- /dev/null +++ b/t/plugin/custom-plugins/apisix/plugins/test-extra-path.lua @@ -0,0 +1,56 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +local core = require("apisix.core") +local ngx = ngx + +local plugin_name = "test-extra-path" + +local schema = { + type = "object", + properties = { + header_name = { + type = "string", + default = "X-Test-Extra-Path" + }, + header_value = { + type = "string", + default = "plugin-loaded" + } + } +} + +local _M = { + version = 0.1, + priority = 1000, + name = plugin_name, + schema = schema, +} + + +function _M.check_schema(conf) + return core.schema.check(schema, conf) +end + + +function _M.header_filter(conf, ctx) + local header_name = conf.header_name or "X-Test-Extra-Path" + local header_value = conf.header_value or "plugin-loaded" + ngx.header[header_name] = header_value +end + + +return _M From a2152781032d024cfdfedea276a3c6917730f693 Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Sat, 3 Jan 2026 13:19:12 +0900 Subject: [PATCH 06/10] refactor: simplify custom plugin directory structure --- t/plugin/custom-plugins/{apisix/plugins => }/test-extra-path.lua | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename t/plugin/custom-plugins/{apisix/plugins => }/test-extra-path.lua (100%) diff --git a/t/plugin/custom-plugins/apisix/plugins/test-extra-path.lua b/t/plugin/custom-plugins/test-extra-path.lua similarity index 100% rename from t/plugin/custom-plugins/apisix/plugins/test-extra-path.lua rename to t/plugin/custom-plugins/test-extra-path.lua From 10e81dd125bdcd31c95c3ffced84fef11f0f4771 Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Sun, 4 Jan 2026 18:27:52 +0900 Subject: [PATCH 07/10] fix: correct custom plugin directory structure for extra_lua_path --- t/plugin/custom-plugins/{ => apisix/plugins}/test-extra-path.lua | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename t/plugin/custom-plugins/{ => apisix/plugins}/test-extra-path.lua (100%) diff --git a/t/plugin/custom-plugins/test-extra-path.lua b/t/plugin/custom-plugins/apisix/plugins/test-extra-path.lua similarity index 100% rename from t/plugin/custom-plugins/test-extra-path.lua rename to t/plugin/custom-plugins/apisix/plugins/test-extra-path.lua From 87c3142f706e0b2030556b6573cbf86f18734250 Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Thu, 29 Jan 2026 17:42:03 +0900 Subject: [PATCH 08/10] test: update extra lua path in extra-lua-path.t --- t/admin/extra-lua-path.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/admin/extra-lua-path.t b/t/admin/extra-lua-path.t index 4d2ed2a967ee..36cfce144c86 100644 --- a/t/admin/extra-lua-path.t +++ b/t/admin/extra-lua-path.t @@ -205,7 +205,7 @@ SUCCESS: block definition takes precedence === TEST 8: Load and execute custom plugin via extra_lua_path Verify that a real custom plugin can be loaded and executed using extra_lua_path ---- extra_lua_path: t/plugin/custom-plugins/?.lua +--- extra_lua_path: t/plugin/custom-plugins/?/init.lua;t/plugin/custom-plugins/?.lua --- extra_yaml_config plugins: - test-extra-path From 180866b23ef8e1f3923b13d004bd887507bb4bfc Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Thu, 29 Jan 2026 17:53:11 +0900 Subject: [PATCH 09/10] test: fix test conflict --- t/admin/extra-lua-path.t | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/t/admin/extra-lua-path.t b/t/admin/extra-lua-path.t index 36cfce144c86..a5ed4a239bfa 100644 --- a/t/admin/extra-lua-path.t +++ b/t/admin/extra-lua-path.t @@ -205,10 +205,7 @@ SUCCESS: block definition takes precedence === TEST 8: Load and execute custom plugin via extra_lua_path Verify that a real custom plugin can be loaded and executed using extra_lua_path ---- extra_lua_path: t/plugin/custom-plugins/?/init.lua;t/plugin/custom-plugins/?.lua ---- extra_yaml_config -plugins: - - test-extra-path +--- extra_lua_path: t/plugin/custom-plugins/?.lua --- config location /t { content_by_lua_block { From 3ed05ea28b29ebd60f580f1ae2efbe97b80d6b6d Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Thu, 5 Feb 2026 11:59:01 +0900 Subject: [PATCH 10/10] test: add test-extra-path plugin into test plugins config --- t/admin/extra-lua-path.t | 3 +++ 1 file changed, 3 insertions(+) diff --git a/t/admin/extra-lua-path.t b/t/admin/extra-lua-path.t index a5ed4a239bfa..4d2ed2a967ee 100644 --- a/t/admin/extra-lua-path.t +++ b/t/admin/extra-lua-path.t @@ -206,6 +206,9 @@ SUCCESS: block definition takes precedence === TEST 8: Load and execute custom plugin via extra_lua_path Verify that a real custom plugin can be loaded and executed using extra_lua_path --- extra_lua_path: t/plugin/custom-plugins/?.lua +--- extra_yaml_config +plugins: + - test-extra-path --- config location /t { content_by_lua_block {