mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 22:11:38 +00:00
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.
This commit is contained in:
parent
180e1f9078
commit
01110fc916
20 changed files with 8211 additions and 15 deletions
8
plugin/src/chaos/init.lua
Normal file
8
plugin/src/chaos/init.lua
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
--[[
|
||||
Chaos engineering tests module.
|
||||
|
||||
This module contains stress tests that simulate random operations,
|
||||
rapid changes, and edge conditions to uncover stability issues.
|
||||
]]
|
||||
|
||||
return {}
|
||||
462
plugin/src/chaos/stressPatterns.spec.lua
Normal file
462
plugin/src/chaos/stressPatterns.spec.lua
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
--[[
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue