local describe, it, expect = lust.describe, lust.it, lust.expect
local compile_loop = require("compile_loop")
local h = compile_loop._test_helpers()
local function make_state(refresh_mode)
local s = compile_loop._test_make_mf_state()
if refresh_mode then
s.file_digest_refresh = refresh_mode
end
return s
end
local function fake_cache(mtime_val)
return {
digest = "digest content",
line_index = "L1-10: section",
mtime = mtime_val,
cached_at = os.time(),
}
end
local function write_temp(content)
local path = os.tmpname()
local f = io.open(path, "w")
if not f then error("cannot create temp file: " .. path) end
f:write(content)
f:close()
return path
end
describe("compile_loop ST2 cache lifecycle", function()
it("cache hit (auto, mtime match, within TTL): distill not called", function()
local call_count = 0
compile_loop._test_set_distill_subloop(function(path, content, mf_state, conf) call_count = call_count + 1
return "digest", "L1-1: stub", nil
end)
local state = make_state("auto")
local fake_mtime = 12345
state.file_digest["/tmp/fake.lua"] = fake_cache(fake_mtime)
local cached = state.file_digest["/tmp/fake.lua"]
local result = h.should_use_cache(cached, fake_mtime, "auto")
expect(result).to.equal(true)
expect(call_count).to.equal(0)
compile_loop._test_reset_distill_subloop()
end)
it("cache miss (new path): distill called and cache written", function()
local call_count = 0
compile_loop._test_set_distill_subloop(function(path, content, mf_state, conf) call_count = call_count + 1
return "digest-for-" .. path, "L1-5: line index", nil
end)
local big_content = string.rep("x", 10001)
local path = write_temp(big_content)
local state = make_state("auto")
local target_set = { [path] = true }
expect(state.file_digest[path]).to.equal(nil)
local result = h.read_file_tool_handler(path, target_set, state, {})
expect(result.ok).to.equal(true)
expect(call_count).to.equal(1)
expect(state.file_digest[path]).to_not.equal(nil)
expect(state.file_digest[path].digest).to.equal("digest-for-" .. path)
compile_loop._test_reset_distill_subloop()
os.remove(path)
end)
it("refresh=always: cache not used even when mtime matches", function()
local fake_mtime = 99999
local cached = fake_cache(fake_mtime)
local result = h.should_use_cache(cached, fake_mtime, "always")
expect(result).to.equal(false)
end)
it("refresh=files: mtime match → cache hit; mtime mismatch → cache miss", function()
local fake_mtime = 55555
local cached = fake_cache(fake_mtime)
local hit = h.should_use_cache(cached, fake_mtime, "files")
local miss = h.should_use_cache(cached, fake_mtime + 1, "files")
expect(hit).to.equal(true)
expect(miss).to.equal(false)
end)
it("refresh=manual: cache used regardless of mtime change", function()
local cached = fake_cache(11111)
local result = h.should_use_cache(cached, 99999, "manual")
expect(result).to.equal(true)
end)
it("per-iter rebuild: mf_state.file_digest is read-only (not cleared)", function()
local state = make_state("auto")
state.file_digest["/a/b.lua"] = {
digest = "pre-existing digest",
line_index = "L1-20: functions",
mtime = 42,
cached_at = os.time(),
}
state.iter = state.iter + 1
state.last_err = nil
state.sr_digest_prev = nil
expect(state.file_digest["/a/b.lua"]).to_not.equal(nil)
expect(state.file_digest["/a/b.lua"].digest).to.equal("pre-existing digest")
end)
it("read_file_range: size>THRESHOLD file returns verbatim lines (no distill)", function()
local lines = {}
for i = 1, 300 do
lines[i] = "line " .. i .. ": " .. string.rep("a", 40)
end
local big_content = table.concat(lines, "\n")
expect(#big_content > 10000).to.equal(true)
local path = write_temp(big_content)
local target_set = { [path] = true }
local call_count = 0
compile_loop._test_set_distill_subloop(function(...) call_count = call_count + 1
return "should not be called", "L1-?: stub", nil
end)
local result = h.read_file_range_tool_handler(path, 5, 10, target_set)
expect(result.ok).to.equal(true)
expect(result.content).to.equal(table.concat({ lines[5], lines[6], lines[7], lines[8], lines[9], lines[10] }, "\n"))
expect(call_count).to.equal(0)
compile_loop._test_reset_distill_subloop()
os.remove(path)
end)
it("read_file_range: range exceeding max returns {ok=false}", function()
local fake_path = "/some/path.lua"
local target_set = { [fake_path] = true }
local result = h.read_file_range_tool_handler(fake_path, 1, 501, target_set)
expect(result.ok).to.equal(false)
expect(type(result.error)).to.equal("string")
end)
it("read_file_range: invalid range (line_start > line_end) returns {ok=false}", function()
local fake_path = "/some/path.lua"
local target_set = { [fake_path] = true }
local result = h.read_file_range_tool_handler(fake_path, 10, 5, target_set)
expect(result.ok).to.equal(false)
end)
it("read_file_range: path not in allowlist returns {ok=false}", function()
local result = h.read_file_range_tool_handler("/not/allowed.lua", 1, 5, {})
expect(result.ok).to.equal(false)
expect(result.error:find("not in target_files allowlist")).to_not.equal(nil)
end)
end)