rojo/plugin/src/chaos/stressPatterns.spec.lua
Joe 01110fc916 Complete comprehensive plugin tests and add new stress test files
- Marked all todos in the comprehensive_plugin_tests plan as completed, indicating that all planned tests have been implemented.
- Introduced new stress test files for InstanceMap, PatchSet, and encoding functionalities, covering various scenarios including large batch operations, conflict resolution, and deep/wide hierarchies.
- Enhanced the testing framework to ensure thorough validation of plugin functionalities and edge cases.
2026-02-01 22:05:27 -06:00

462 lines
11 KiB
Lua

--[[
Chaos engineering stress tests.
Tests random operations, rapid changes, and edge conditions
to uncover potential race conditions and stability issues.
]]
return function()
local InstanceMap = require(script.Parent.Parent.InstanceMap)
local PatchSet = require(script.Parent.Parent.PatchSet)
local applyPatch = require(script.Parent.Parent.Reconciler.applyPatch)
local diff = require(script.Parent.Parent.Reconciler.diff)
local testUtils = require(script.Parent.Parent.testUtils)
local LargeTreeGenerator = testUtils.LargeTreeGenerator
local PatchGenerator = testUtils.PatchGenerator
local HttpService = game:GetService("HttpService")
-- Test container for cleanup
local container
beforeEach(function()
container = Instance.new("Folder")
container.Name = "ChaosTestContainer"
container.Parent = game:GetService("Workspace")
end)
afterEach(function()
if container then
container:Destroy()
container = nil
end
end)
local function generateId()
return HttpService:GenerateGUID(false)
end
describe("rapid add/remove cycles", function()
it("should handle rapid sequential add/remove", function()
local instanceMap = InstanceMap.new()
local root = Instance.new("Folder")
root.Name = "Root"
root.Parent = container
local rootId = generateId()
instanceMap:insert(rootId, root)
for i = 1, 50 do
-- Add
local addPatch = PatchGenerator.createAdditionsPatch({
count = 5,
parentId = rootId,
})
local unapplied1 = applyPatch(instanceMap, addPatch)
expect(PatchSet.isEmpty(unapplied1)).to.equal(true)
-- Get IDs of added instances
local idsToRemove = {}
for id in pairs(addPatch.added) do
table.insert(idsToRemove, id)
end
-- Remove
local removePatch = PatchGenerator.createRemovalsPatch({ ids = idsToRemove })
local unapplied2 = applyPatch(instanceMap, removePatch)
expect(PatchSet.isEmpty(unapplied2)).to.equal(true)
end
-- Root should have no children
expect(#root:GetChildren()).to.equal(0)
instanceMap:stop()
end)
it("should handle interleaved add/remove", function()
local instanceMap = InstanceMap.new()
local root = Instance.new("Folder")
root.Name = "Root"
root.Parent = container
local rootId = generateId()
instanceMap:insert(rootId, root)
local allIds = {}
for i = 1, 30 do
-- Add some
local addPatch = PatchGenerator.createAdditionsPatch({
count = 3,
parentId = rootId,
})
applyPatch(instanceMap, addPatch)
for id in pairs(addPatch.added) do
table.insert(allIds, id)
end
-- Remove some (if we have any)
if #allIds > 5 then
local toRemove = {}
for j = 1, 2 do
if #allIds > 0 then
table.insert(toRemove, table.remove(allIds, 1))
end
end
if #toRemove > 0 then
local removePatch = PatchGenerator.createRemovalsPatch({ ids = toRemove })
applyPatch(instanceMap, removePatch)
end
end
end
instanceMap:stop()
end)
end)
describe("property flapping", function()
it("should handle rapid property changes", function()
local instanceMap = InstanceMap.new()
local sv = Instance.new("StringValue")
sv.Name = "Flapping"
sv.Value = "Initial"
sv.Parent = container
local id = generateId()
instanceMap:insert(id, sv)
for i = 1, 100 do
local patch = PatchSet.newEmpty()
table.insert(patch.updated, {
id = id,
changedProperties = {
Value = { String = "Value_" .. tostring(i) },
},
})
applyPatch(instanceMap, patch)
end
expect(sv.Value).to.equal("Value_100")
instanceMap:stop()
end)
it("should handle rapid name changes", function()
local instanceMap = InstanceMap.new()
local folder = Instance.new("Folder")
folder.Name = "Initial"
folder.Parent = container
local id = generateId()
instanceMap:insert(id, folder)
for i = 1, 50 do
local patch = PatchSet.newEmpty()
table.insert(patch.updated, {
id = id,
changedName = "Name_" .. tostring(i),
changedProperties = {},
})
applyPatch(instanceMap, patch)
end
expect(folder.Name).to.equal("Name_50")
instanceMap:stop()
end)
end)
describe("tree mutations", function()
it("should handle adding to different parts of tree", function()
local instanceMap = InstanceMap.new()
-- Create initial tree
local root = Instance.new("Folder")
root.Name = "Root"
root.Parent = container
local rootId = generateId()
instanceMap:insert(rootId, root)
local branchIds = {}
for i = 1, 5 do
local branch = Instance.new("Folder")
branch.Name = "Branch_" .. tostring(i)
branch.Parent = root
local branchId = generateId()
instanceMap:insert(branchId, branch)
table.insert(branchIds, branchId)
end
-- Add children to random branches
for _ = 1, 50 do
local parentId = branchIds[math.random(1, #branchIds)]
local patch = PatchGenerator.createAdditionsPatch({
count = 1,
parentId = parentId,
})
applyPatch(instanceMap, patch)
end
-- Total should be root + 5 branches + 50 new = 56
expect(instanceMap:size()).to.equal(56)
instanceMap:stop()
end)
it("should handle moving instances between parents", function()
local instanceMap = InstanceMap.new()
local root = Instance.new("Folder")
root.Name = "Root"
root.Parent = container
local rootId = generateId()
instanceMap:insert(rootId, root)
local parent1 = Instance.new("Folder")
parent1.Name = "Parent1"
parent1.Parent = root
local parent2 = Instance.new("Folder")
parent2.Name = "Parent2"
parent2.Parent = root
local child = Instance.new("Folder")
child.Name = "Child"
child.Parent = parent1
local parent1Id = generateId()
local parent2Id = generateId()
local childId = generateId()
instanceMap:insert(parent1Id, parent1)
instanceMap:insert(parent2Id, parent2)
instanceMap:insert(childId, child)
-- Move child back and forth
for _ = 1, 20 do
child.Parent = parent2
child.Parent = parent1
end
-- Child should still be tracked
expect(instanceMap.fromIds[childId]).to.equal(child)
instanceMap:stop()
end)
end)
describe("mixed operation stress", function()
it("should handle random mix of operations", function()
local instanceMap = InstanceMap.new()
local root = Instance.new("Folder")
root.Name = "Root"
root.Parent = container
local rootId = generateId()
instanceMap:insert(rootId, root)
local knownIds = { rootId }
for _ = 1, 100 do
local operation = math.random(1, 3)
if operation == 1 and #knownIds < 500 then
-- Add
local parentId = knownIds[math.random(1, #knownIds)]
local patch = PatchGenerator.createAdditionsPatch({
count = 1,
parentId = parentId,
})
applyPatch(instanceMap, patch)
for id in pairs(patch.added) do
table.insert(knownIds, id)
end
elseif operation == 2 and #knownIds > 1 then
-- Remove (not root)
local indexToRemove = math.random(2, #knownIds)
local idToRemove = knownIds[indexToRemove]
table.remove(knownIds, indexToRemove)
local patch = PatchGenerator.createRemovalsPatch({ ids = { idToRemove } })
applyPatch(instanceMap, patch)
elseif operation == 3 and #knownIds > 0 then
-- Update
local idToUpdate = knownIds[math.random(1, #knownIds)]
local patch = PatchSet.newEmpty()
table.insert(patch.updated, {
id = idToUpdate,
changedName = "Updated_" .. tostring(os.clock()),
changedProperties = {},
})
applyPatch(instanceMap, patch)
end
end
instanceMap:stop()
end)
end)
describe("diff consistency", function()
it("should produce consistent diffs", function()
local instanceMap = InstanceMap.new()
local root = Instance.new("Folder")
root.Name = "Root"
root.Parent = container
local child1 = Instance.new("Folder")
child1.Name = "Child1"
child1.Parent = root
local child2 = Instance.new("Folder")
child2.Name = "Child2"
child2.Parent = root
local rootId = generateId()
local child1Id = generateId()
local child2Id = generateId()
instanceMap:insert(rootId, root)
instanceMap:insert(child1Id, child1)
instanceMap:insert(child2Id, child2)
local virtualInstances = {
[rootId] = {
Id = rootId,
ClassName = "Folder",
Name = "Root",
Parent = nil,
Properties = {},
Children = { child1Id, child2Id },
},
[child1Id] = {
Id = child1Id,
ClassName = "Folder",
Name = "Child1",
Parent = rootId,
Properties = {},
Children = {},
},
[child2Id] = {
Id = child2Id,
ClassName = "Folder",
Name = "Child2",
Parent = rootId,
Properties = {},
Children = {},
},
}
-- Diff multiple times, should be consistent
for _ = 1, 10 do
local ok, patch = diff(instanceMap, virtualInstances, rootId)
expect(ok).to.equal(true)
expect(PatchSet.isEmpty(patch)).to.equal(true)
end
instanceMap:stop()
end)
end)
describe("large batch operations", function()
it("should handle large batch addition and removal", function()
local instanceMap = InstanceMap.new()
local root = Instance.new("Folder")
root.Name = "Root"
root.Parent = container
local rootId = generateId()
instanceMap:insert(rootId, root)
-- Add 200 instances
local addPatch = PatchGenerator.createAdditionsPatch({
count = 200,
parentId = rootId,
})
local startTime = os.clock()
applyPatch(instanceMap, addPatch)
local addTime = os.clock() - startTime
expect(#root:GetChildren()).to.equal(200)
expect(addTime < 5).to.equal(true)
-- Remove all
local idsToRemove = {}
for id in pairs(addPatch.added) do
table.insert(idsToRemove, id)
end
local removePatch = PatchGenerator.createRemovalsPatch({ ids = idsToRemove })
startTime = os.clock()
applyPatch(instanceMap, removePatch)
local removeTime = os.clock() - startTime
expect(#root:GetChildren()).to.equal(0)
expect(removeTime < 5).to.equal(true)
instanceMap:stop()
end)
end)
describe("edge conditions", function()
it("should handle empty patches gracefully", function()
local instanceMap = InstanceMap.new()
local root = Instance.new("Folder")
root.Name = "Root"
root.Parent = container
local rootId = generateId()
instanceMap:insert(rootId, root)
-- Apply many empty patches
for _ = 1, 100 do
local patch = PatchSet.newEmpty()
local unapplied = applyPatch(instanceMap, patch)
expect(PatchSet.isEmpty(unapplied)).to.equal(true)
end
instanceMap:stop()
end)
it("should handle patches with invalid IDs gracefully", function()
local instanceMap = InstanceMap.new()
local root = Instance.new("Folder")
root.Name = "Root"
root.Parent = container
local rootId = generateId()
instanceMap:insert(rootId, root)
-- Patch with updates to non-existent IDs
local patch = PatchSet.newEmpty()
for _ = 1, 10 do
table.insert(patch.updated, {
id = generateId(), -- Non-existent
changedName = "NewName",
changedProperties = {},
})
end
local unapplied = applyPatch(instanceMap, patch)
-- All updates should be unapplied
expect(#unapplied.updated).to.equal(10)
instanceMap:stop()
end)
end)
end