refactor: improve error traceback

This commit is contained in:
littensy 2026-04-11 13:06:53 -07:00
parent ee6237c4a2
commit eab89077f6
6 changed files with 285 additions and 56 deletions

View file

@ -408,17 +408,6 @@ test("should run cleanups outside any evaluation context", function()
assert(calls == 0, "should not run after b update")
end)
test.skip("infinite recursion should eventually throw", function()
local counter, setCounter = signal(0)
local success = pcall(function()
effect(function()
setCounter(counter() + 1)
end)
end)
assert(not success, "should have thrown")
end)
test("should not retrigger the effect if the cleanup modifies one of the dependencies", function()
local a, setA = signal(0)
local calls = {}

View file

@ -92,13 +92,4 @@ test("should dispose inner effects if created in an effect", function()
assert(triggers == 2, "ran after dispose")
end)
test("should disallow yielding", function()
local success = pcall(function()
effectScope(function()
coroutine.yield()
end)
end)
assert(not success, "should error on yield")
end)
return {}

View file

@ -0,0 +1,200 @@
--!optimize 1
local Charm = require("../src")
local test = require("../../../tests/test")
test("should disallow yielding", function()
local success = pcall(function()
Charm.effectScope(function()
coroutine.yield()
end)
end)
assert(not success, "should error on yield")
end)
test.describe("traceback", function()
local DEFAULT_ERROR = "Test error"
local function fail(message: string?)
error(message or DEFAULT_ERROR, 0)
end
local function indirectError(message: string?)
fail(message)
end
local function assertStackTrace(err: unknown, expectation: string?)
expectation = expectation or DEFAULT_ERROR
assert(type(err) == "string", "Expected error to be a string")
assert(string.find(err, expectation), `Expected error message to be "{expectation}", got: {err}`)
assert(string.find(err, "fail"), `Expected stack trace to contain fail, got: {err}`)
assert(string.find(err, "indirectError"), `Expected stack trace to contain indirectError, got: {err}`)
end
test("signal update", function()
local _, err = pcall(function()
local _, setSrc = Charm.signal(0)
setSrc(function()
indirectError()
return 1
end)
end)
assertStackTrace(err)
end)
test("atom update", function()
local _, err = pcall(function()
local src = Charm.atom(0)
src(function()
indirectError()
return 1
end)
end)
assertStackTrace(err)
end)
test("computed", function()
local _, err = pcall(function()
local src = Charm.computed(function()
indirectError()
end)
src()
end)
assertStackTrace(err)
end)
test("effect init", function()
local _, err = pcall(function()
Charm.effect(function()
indirectError()
end)
end)
assertStackTrace(err)
end)
test("effect run", function()
local _, err = pcall(function()
local src, setSrc = Charm.signal(0)
Charm.effect(function()
if src() == 1 then
indirectError()
end
end)
setSrc(1)
end)
assertStackTrace(err)
end)
test("effect cleanups", function()
local _, err = pcall(function()
local dispose = Charm.effect(function()
Charm.onCleanup(function()
indirectError("onCleanup")
end)
return function()
indirectError("returned cleanup")
end
end)
dispose()
end)
assertStackTrace(err, "onCleanup")
assertStackTrace(err, "returned cleanup")
end)
test("trigger", function()
local _, err = pcall(function()
Charm.trigger(function()
indirectError()
end)
end)
assertStackTrace(err)
end)
test("untracked", function()
local _, err = pcall(function()
Charm.effectScope(function()
Charm.untracked(function()
indirectError()
end)
end)
end)
assertStackTrace(err)
end)
test("batch", function()
local _, err = pcall(function()
Charm.batch(function()
indirectError()
end)
end)
assertStackTrace(err)
end)
test("subscribe getter", function()
local _, err = pcall(function()
Charm.subscribe(function()
indirectError()
end, function() end)
end)
assertStackTrace(err)
end)
test("subscribe callback", function()
local _, err = pcall(function()
local src, setSrc = Charm.signal(0)
Charm.subscribe(src, function()
indirectError()
end)
setSrc(1)
end)
assertStackTrace(err)
end)
test("listen getter", function()
local _, err = pcall(function()
Charm.listen(function()
indirectError()
end, function() end)
end)
assertStackTrace(err)
end)
test("listen callback", function()
local _, err = pcall(function()
local src, setSrc = Charm.signal(0)
Charm.listen(src, function()
indirectError()
end)
setSrc(1)
end)
assertStackTrace(err)
end)
test("mapped getter", function()
local _, err = pcall(function()
local src = Charm.mapped(function()
indirectError()
return { 1 }
end, function()
return 1
end)
src()
end)
assertStackTrace(err)
end)
test("mapped transform", function()
local _, err = pcall(function()
local src = Charm.mapped(function()
return { 1 }
end, function()
indirectError()
return 1
end)
src()
end)
assertStackTrace(err)
end)
end)
return {}

View file

@ -12,6 +12,10 @@ test.describe("effectScope", function()
require("@self/effectScope.test")
end)
test.describe("error", function()
require("@self/errors.test")
end)
test.describe("gc", function()
require("@self/gc.test")
end)

View file

@ -167,4 +167,32 @@ test("should allow disposal during removal", function()
assert(table.concat(edits) == "+1+2+3+4-1-2-3-4", `bad edits: {table.concat(edits)}`)
end)
test("should not hang on error after recursive update", function()
local items, setItems = signal({})
observe(items, function()
setItems({ {} })
error("test error")
end)
local success, err = pcall(function()
setItems({ {} })
end)
assert(not success, "should error")
assert(string.find(tostring(err), "test error"), `bad error: {err}`)
end)
test("should not hang on error after recursive init", function()
local items, setItems = signal({ {} })
local success, err = pcall(function()
observe(items, function()
setItems({ {} })
error("test error")
end)
end)
assert(not success, "should error")
assert(string.find(tostring(err), "test error"), `bad error: {err}`)
end)
return {}