From b19827e932f91834cf584775292b74f1614391fa Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Wed, 19 Nov 2025 11:15:28 +0900 Subject: [PATCH 1/4] 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 4ef30e506e71..626f47007af8 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 fe3b0aba6d99e9ff387c91216fec48ffacb76133 Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Thu, 20 Nov 2025 17:19:11 +0900 Subject: [PATCH 2/4] 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 74b88ccbbaf077b09f5c558a7f37d56e5df015c4 Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Thu, 20 Nov 2025 17:25:54 +0900 Subject: [PATCH 3/4] fix: eclint --- t/APISIX.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/APISIX.pm b/t/APISIX.pm index 626f47007af8..6210449aa3d9 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 9c80b2c760656ba73f24fc3a92fcbffd97c2d964 Mon Sep 17 00:00:00 2001 From: "inkyu.bae" Date: Thu, 20 Nov 2025 17:29:01 +0900 Subject: [PATCH 4/4] 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);