test: simplify gc testing

This commit is contained in:
Richard 2024-04-20 20:47:25 -07:00
parent 8ca299f671
commit 0c68ff760f
6 changed files with 111 additions and 49 deletions

View file

@ -1,4 +1,5 @@
local atom = require(script.Parent.Parent.atom) local atom = require(script.Parent.Parent.atom)
local derive = require(script.Parent.Parent.derive)
local effect = require(script.Parent.Parent.effect) local effect = require(script.Parent.Parent.effect)
local store = require(script.Parent.Parent.store) local store = require(script.Parent.Parent.store)
local subscribe = require(script.Parent.Parent.subscribe) local subscribe = require(script.Parent.Parent.subscribe)
@ -35,4 +36,26 @@ return function()
end) end)
expect(value).to.equal(1) expect(value).to.equal(1)
end) end)
it("batches derived atoms", function()
local source = atom(1)
local updates = 0
local computed = 0
local double = derive(function()
computed += 1
return source() * 2
end)
computed = 0 -- derived atoms will run once initially
subscribe(double, function()
updates += 1
end)
store.batch(function()
source(2)
source(3)
source(4)
end)
expect(updates).to.equal(1)
expect(computed).to.equal(1)
expect(double()).to.equal(8)
end)
end end

View file

@ -5,6 +5,8 @@ local Package = game:GetService("ReplicatedStorage").Packages.charm
local atom = require(Package.atom) local atom = require(Package.atom)
local derive = require(Package.derive) local derive = require(Package.derive)
local effect = require(Package.effect)
local subscribe = require(Package.subscribe)
local function parameterGenerator() local function parameterGenerator()
return atom({ count = 0 }) return atom({ count = 0 })
@ -26,14 +28,13 @@ local benchmarks = {
end end
end, end,
derived_memo = function(_, source: any) derived_nested = function(_, source: any)
derive(function() local double = derive(function()
return { count = 2 * source().count } return { count = 2 * source().count }
end, { end)
equals = function(previous, current) derive(function()
return previous.count == current.count return { count = 2 * double().count }
end, end)
})
for _ = 1, 500 do for _ = 1, 500 do
source({ count = math.random(1, 2) }) source({ count = math.random(1, 2) })
end end

View file

@ -1,6 +1,8 @@
local atom = require(script.Parent.Parent.atom) local atom = require(script.Parent.Parent.atom)
local derive = require(script.Parent.Parent.derive) local derive = require(script.Parent.Parent.derive)
local store = require(script.Parent.Parent.store) local store = require(script.Parent.Parent.store)
local collect = require(script.Parent.Parent.utils.collect)
local count = require(script.Parent.Parent.utils.count)
return function() return function()
it("memoizes the result", function() it("memoizes the result", function()
@ -35,34 +37,34 @@ return function()
expect(quadruple()).to.equal(8) expect(quadruple()).to.equal(8)
end) end)
it("garbage collects", function() it("collects derived atoms", function()
-- large values coerce the garbage collector to run local source = atom(0)
local source = atom(string.rep("x", 1e5))
local max = 0
local function listenersDecreased() derive(function()
local count = 0 return source()
for _ in next, store.listeners[source] do end)
count += 1
end
max = math.max(max, count)
return count < max
end
for index = 1, 500 do collect()
derive(function()
return string.upper(source()) assert(count(store.listeners[source]) == 0, "derived atom did not gc")
end)
it("collects nested derived atoms", function()
local source = atom(0)
do
-- Declare this in another scope to ensure no references are kept
local derived = derive(function()
return source()
end)
derive(function()
return derived() * 2
end) end)
if index % 50 == 0 then
-- derived atoms should be collected when memory is stressed
source(source() .. "x")
task.wait()
end
if listenersDecreased() then
return
end
end end
error("None of the derived atoms were collected") collect()
assert(count(store.listeners[source]) == 0, "nested derived atom did not gc")
end) end)
end end

View file

@ -1,6 +1,9 @@
local atom = require(script.Parent.Parent.atom) local atom = require(script.Parent.Parent.atom)
local derive = require(script.Parent.Parent.derive) local derive = require(script.Parent.Parent.derive)
local store = require(script.Parent.Parent.store)
local subscribe = require(script.Parent.Parent.subscribe) local subscribe = require(script.Parent.Parent.subscribe)
local collect = require(script.Parent.Parent.utils.collect)
local count = require(script.Parent.Parent.utils.count)
return function() return function()
it("runs on state change", function() it("runs on state change", function()
@ -27,25 +30,41 @@ return function()
local source = atom(1) local source = atom(1)
local updates = 0 local updates = 0
for _ = 1, 1000 do -- Create a new scope to encapsulate variables with a limited lifetime, like
-- The idea here is to test whether subscribe() prevents every derived -- 'unsubscribe' and 'derived', to ensure they are garbage collected.
-- atom in a chain from being garbage collected. If any are GCed, then --
-- the subscription will stop updating and the test will fail. -- The idea here is to test whether subscribe() prevents every derived
local derived = derive(function() -- atom in a chain from being garbage collected. If any are GCed, then
return source() -- the subscription will stop updating and the test will fail.
end) do
subscribe( local unsubscribe
derive(function()
return derived() * 2 do
end), local derived = derive(function()
function() return source()
updates += 1 end)
end
) unsubscribe = subscribe(
derive(function()
return derived() * 2
end),
function()
updates += 1
end
)
end
collect()
source(source() + 1)
assert(updates == 1, "subscription stopped updating")
assert(count(store.listeners[source]) == 1, "derived atom was garbage collected")
unsubscribe()
end end
source(source() + 1) collect()
task.wait()
expect(updates).to.equal(1000) assert(count(store.listeners[source]) == 0, "derived atom did not collect")
end) end)
end end

8
src/utils/collect.luau Normal file
View file

@ -0,0 +1,8 @@
local function collect()
for _ = 1, 1e4 do
local _ = table.create(1e3)
end
task.wait()
end
return collect

9
src/utils/count.luau Normal file
View file

@ -0,0 +1,9 @@
local function count(object: any): number
local count = 0
for _ in next, object do
count += 1
end
return count
end
return count