@@ -44,6 +44,7 @@ List<DynamicTest> packageScriptsMatchReference() {
4444 add (tests , "preload" , PRELOAD );
4545 add (tests , "lua" , LUAMOD );
4646 add (tests , "err" , ERRORS );
47+ add (tests , "deep" , DEEP );
4748 return tests ;
4849 }
4950
@@ -287,4 +288,178 @@ private static void add(List<DynamicTest> tests, String tag, String[] srcs) {
287288 package.searchers = { function(n) error("searcher died") end }
288289 print(pcall(require, "fkpk_e5"))
289290 """ , };
291+
292+ /**
293+ * {@code require} / {@code package.searchpath} 的<b>深矩阵</b>(57 行):
294+ * 真在磁盘上造一批模块,然后走完整条搜索器链 —— 而不只是在内存里摆
295+ * {@code package.preload}。之前这一块只覆盖了「纯 Lua 那条路」的一半。
296+ * <p>
297+ * 覆盖到的形状:模块返回 nil / false / true / 数字 / 无返回 / 多值(只取第一)、
298+ * 模块抛错、模块语法错、模块里自己设 {@code package.loaded}、
299+ * 点号模块名换目录分隔符、重复 {@code require} 的缓存、循环 {@code require}
300+ * (参考给 {@code C stack overflow},不是哨兵)、
301+ * {@code package.loaded} 预置成值 / false / nil 的三种后果、
302+ * {@code package.preload} 给函数 / 返回 nil / 抛错 / 非函数、
303+ * {@code searchpath} 的 {@code sep} / {@code rep} 与缺参、
304+ * {@code package.searchers} 的个数与逐个调用、整条链被换掉。
305+ * <p>
306+ * 顺带钉住一件容易悄悄坏掉的事:<b>{@code package.path} 的默认值</b>必须与参考
307+ * 二进制编译时那套 {@code LUA_PATH_DEFAULT} 一致 —— 矩阵里每条
308+ * {@code module 'X' not found} 都把 {@code /usr/local/share/lua/5.4/...} 那
309+ * 一串<b>完整打印</b>出来,改一个字符就红。
310+ * <p>
311+ * 敏感性量过:把 {@code searchpath} 的默认 {@code sep} 从 {@code "."} 改成
312+ * {@code "-"},立刻红在 {@code sp_dots}。
313+ * <p>
314+ * 库目录用 {@code /tmp/fkreq} 并在开头 {@code rm -rf} 重建:两边跑在同一台
315+ * 机器上,绝对路径对双方都可见,也不依赖 cwd。
316+ */
317+ private static final String [] DEEP = {"""
318+ -- require / package.searchpath 深矩阵:真在磁盘上造模块,走完整条搜索器链
319+ local out = {}
320+ local function d(v)
321+ local t = type(v)
322+ if t == "nil" then return "nil" end
323+ if t == "boolean" then return tostring(v) end
324+ if t == "number" then return math.type(v) .. ":" .. tostring(v) end
325+ if t == "string" then return string.format("q(%d)[%s]", #v, (v:gsub("\\ n", "\\ \\ n"))) end
326+ return t
327+ end
328+ local function probe(name, f, ...)
329+ local r = table.pack(pcall(f, ...))
330+ local s = name .. " [" .. r.n .. "]"
331+ for i = 2, r.n do s = s .. " " .. d(r[i]) end
332+ if not r[1] then s = s .. " ERR " .. d(r[2]) end
333+ out[#out + 1] = s
334+ end
335+ local function say(s) out[#out + 1] = s end
336+
337+ local D = "/tmp/fkreq"
338+ local R = D .. "/repo"
339+ os.execute("rm -rf " .. D .. " && mkdir -p " .. R .. "/sub")
340+ local function put(path, body)
341+ local f = assert(io.open(path, "w"))
342+ f:write(body)
343+ f:close()
344+ end
345+
346+ put(R .. "/basic.lua", "return 'BASIC'\\ n")
347+ put(R .. "/nonil.lua", "-- 什么都不返回\\ n")
348+ put(R .. "/retnil.lua", "return nil\\ n")
349+ put(R .. "/retfalse.lua", "return false\\ n")
350+ put(R .. "/rettrue.lua", "return true\\ n")
351+ put(R .. "/retnum.lua", "return 42\\ n")
352+ put(R .. "/retmulti.lua", "return 1, 2, 3\\ n")
353+ put(R .. "/err.lua", "error('boom')\\ n")
354+ put(R .. "/syntax.lua", "this is not lua\\ n")
355+ put(R .. "/prints.lua", "print('SIDE'); return 'P'\\ n")
356+ put(R .. "/setsloaded.lua", "package.loaded['setsloaded'] = 'PRESET'; return 'FROMCHUNK'\\ n")
357+ put(R .. "/sub/inner.lua", "return 'INNER'\\ n")
358+ put(R .. "/counter.lua", "local n = (package.loaded['counter_n'] or 0) + 1\\ n"
359+ .. "package.loaded['counter_n'] = n\\ nreturn n\\ n")
360+ put(R .. "/circ.lua", "return require('circ')\\ n")
361+
362+ -- 把仓库路径前置到 package.path,避免依赖 cwd
363+ local oldpath = package.path
364+ package.path = R .. "/?.lua;" .. R .. "/?/init.lua;" .. oldpath
365+
366+ say("path_head " .. tostring(package.path:sub(1, #R + 6)))
367+
368+ probe("r_basic", require, "basic")
369+ probe("r_basic_twice", require, "basic") -- 缓存:第二次不再跑
370+ probe("r_prints", require, "prints") -- 副作用只发生一次
371+ probe("r_prints2", require, "prints")
372+ probe("r_nonil", require, "nonil") -- 无返回 → true
373+ probe("r_retnil", require, "retnil") -- 返回 nil → true
374+ probe("r_retfalse", require, "retfalse") -- 返回 false → false
375+ probe("r_rettrue", require, "rettrue")
376+ probe("r_retnum", require, "retnum")
377+ probe("r_retmulti", require, "retmulti") -- 只取第一个
378+ probe("r_err", require, "err")
379+ probe("r_syntax", require, "syntax")
380+ probe("r_setsloaded", require, "setsloaded") -- chunk 里先设了 loaded
381+ probe("r_setsloaded2", require, "setsloaded")
382+ probe("r_sub", require, "sub.inner") -- 点号换成目录分隔符
383+ probe("r_counter1", require, "counter")
384+ probe("r_counter2", require, "counter")
385+ probe("r_missing", require, "no_such_mod_xyz")
386+ probe("r_missing_dots", require, "a.b.c.nope")
387+ probe("r_circ", require, "circ") -- 循环 require 给的是哨兵
388+ probe("r_noname", require)
389+ probe("r_nilname", require, nil)
390+ probe("r_numname", require, 42)
391+ probe("r_emptyname", require, "")
392+ probe("r_boolname", require, true)
393+
394+ -- package.loaded 预置
395+ package.loaded["preset"] = "PRE"
396+ probe("l_preset", require, "preset")
397+ package.loaded["prefalse"] = false
398+ probe("l_prefalse", require, "prefalse")
399+ package.loaded["prenil"] = nil
400+ probe("l_prenil", require, "prenil")
401+
402+ -- package.preload
403+ package.preload["frompreload"] = function() return "FROM_PRELOAD" end
404+ probe("p_preload", require, "frompreload")
405+ package.preload["preloadnil"] = function() return nil end
406+ probe("p_preloadnil", require, "preloadnil")
407+ package.preload["preloaderr"] = function() error("pe") end
408+ probe("p_preloaderr", require, "preloaderr")
409+ probe("p_preload_extra", function()
410+ package.preload["multi"] = function(n) return "N=" .. tostring(n) end
411+ return require("multi")
412+ end)
413+ probe("p_preload_badtype", function()
414+ package.preload["notfn"] = 42
415+ return require("notfn")
416+ end)
417+
418+ -- searchpath
419+ probe("sp_basic", package.searchpath, "basic", R .. "/?.lua")
420+ probe("sp_missing", package.searchpath, "nope_xyz", R .. "/?.lua")
421+ probe("sp_multi", package.searchpath, "nope_xyz", R .. "/?.lua;" .. R .. "/?/init.lua")
422+ probe("sp_dots", package.searchpath, "sub.inner", R .. "/?.lua")
423+ probe("sp_sep", package.searchpath, "sub.inner", R .. "/?.lua", ".", "/")
424+ probe("sp_rep", package.searchpath, "subXinner", R .. "/?.lua", "X", "/")
425+ probe("sp_rep2", package.searchpath, "sub--inner", R .. "/?.lua", "--", "/")
426+ probe("sp_emptysep", package.searchpath, "basic", R .. "/?.lua", "", "")
427+ probe("sp_nopath", package.searchpath, "basic", "")
428+ probe("sp_nosep", package.searchpath, "basic", R .. "/?.lua")
429+ probe("sp_badname", package.searchpath, 42, R .. "/?.lua")
430+ probe("sp_badpath", package.searchpath, "basic", 42)
431+ probe("sp_noargs", package.searchpath)
432+ probe("sp_abs", package.searchpath, "basic", R .. "/?.lua;" .. R .. "/?.lua")
433+
434+ -- searchers 链本身
435+ probe("s_count", function() return #package.searchers end)
436+ probe("s_types", function()
437+ local r = {}
438+ for i, v in ipairs(package.searchers) do r[#r + 1] = type(v) end
439+ return table.concat(r, ",")
440+ end)
441+ probe("s_call1", function() return package.searchers[1]("basic") end)
442+ probe("s_call4", function() return package.searchers[4]("basic") end)
443+ probe("s_call_missing1", function() return package.searchers[1]("nope_xyz_1") end)
444+ probe("s_call_bad", function() return package.searchers[1](42) end)
445+ probe("s_replace", function()
446+ local old = package.searchers
447+ package.searchers = { function(n) return function() return "REPLACED" end end }
448+ local v = require("anything_xyz")
449+ package.searchers = old
450+ return v
451+ end)
452+ probe("s_empty", function()
453+ local old = package.searchers
454+ package.searchers = {}
455+ local ok, e = pcall(require, "anything_xyz")
456+ package.searchers = old
457+ return ok, e
458+ end)
459+
460+ package.path = oldpath
461+ os.execute("rm -rf " .. D)
462+ print(table.concat(out, "\\ n"))
463+ """ };
464+
290465}
0 commit comments