-- Critical Failure Tests for FlexLove -- These tests are designed to find ACTUAL BUGS: -- 1. Memory leaks / garbage creation without cleanup -- 2. Layout calculation bugs causing incorrect positioning -- 3. Unsafe input access (nil dereference, division by zero, etc.) package.path = package.path .. ";./?.lua;./modules/?.lua" require("testing.loveStub") local luaunit = require("testing.luaunit") local FlexLove = require("FlexLove") TestCriticalFailures = {} function TestCriticalFailures:setUp() collectgarbage("collect") FlexLove.destroy() FlexLove.setMode("retained") end function TestCriticalFailures:tearDown() FlexLove.destroy() collectgarbage("collect") end -- ============================================================ -- MEMORY LEAK TESTS - Find garbage that's not cleaned up -- ============================================================ -- Test: Canvas objects should be cleaned up on resize function TestCriticalFailures:test_canvas_cleanup_on_resize() FlexLove.init() -- Create initial canvases FlexLove.draw(function() end) local canvas1 = FlexLove._gameCanvas -- Resize should invalidate old canvases FlexLove.resize() -- Draw again to create new canvases FlexLove.draw(function() end) local canvas2 = FlexLove._gameCanvas -- Old canvas should be replaced luaunit.assertNotEquals(canvas1, canvas2) -- Check canvas is actually nil after resize (before draw) FlexLove.resize() luaunit.assertNil(FlexLove._gameCanvas) end -- Test: Elements should be cleaned up from topElements on destroy function TestCriticalFailures:test_element_cleanup_from_top_elements() local element1 = FlexLove.new({ width = 100, height = 100 }) local element2 = FlexLove.new({ width = 100, height = 100 }) luaunit.assertEquals(#FlexLove.topElements, 2) element1:destroy() luaunit.assertEquals(#FlexLove.topElements, 1) element2:destroy() luaunit.assertEquals(#FlexLove.topElements, 0) end -- Test: Child elements should be destroyed when parent is destroyed function TestCriticalFailures:test_child_cleanup_on_parent_destroy() local parent = FlexLove.new({ width = 200, height = 200 }) local child = FlexLove.new({ width = 50, height = 50, parent = parent }) luaunit.assertEquals(#parent.children, 1) -- Destroy parent should also clear children parent:destroy() -- Child should have no parent reference (potential memory leak if not cleared) luaunit.assertNil(child.parent) luaunit.assertEquals(#parent.children, 0) end -- Test: Event handlers should be cleared on destroy (closure leak) function TestCriticalFailures:test_event_handler_cleanup() local captured_data = { large_array = {} } for i = 1, 1000 do captured_data.large_array[i] = i end local element = FlexLove.new({ width = 100, height = 100, onEvent = function(el, event) -- This closure captures captured_data print(captured_data.large_array[1]) end, }) element:destroy() -- onEvent should be nil after destroy (prevent closure leak) luaunit.assertNil(element.onEvent) end -- Test: Immediate mode state should not grow unbounded function TestCriticalFailures:test_immediate_mode_state_cleanup() FlexLove.setMode("immediate") FlexLove.init({ stateRetentionFrames = 2 }) -- Create elements for multiple frames for frame = 1, 10 do FlexLove.beginFrame() FlexLove.new({ id = "element_" .. frame, width = 100, height = 100 }) FlexLove.endFrame() end -- State count should be limited by stateRetentionFrames local stateCount = FlexLove.getStateCount() -- Should be much less than 10 due to cleanup luaunit.assertTrue(stateCount < 10, "State count: " .. stateCount .. " (should be cleaned up)") end -- ============================================================ -- LAYOUT CALCULATION BUGS - Find incorrect positioning -- ============================================================ -- Test: Flex layout with overflow should not position children outside container function TestCriticalFailures:test_flex_overflow_positioning() local parent = FlexLove.new({ width = 100, height = 100, positioning = "flex", flexDirection = "horizontal", flexWrap = "nowrap", }) -- Add children that exceed parent width local child1 = FlexLove.new({ width = 80, height = 50, parent = parent }) local child2 = FlexLove.new({ width = 80, height = 50, parent = parent }) -- Children should be positioned, even if they overflow -- Check that x positions are at least valid numbers luaunit.assertNotNil(child1.x) luaunit.assertNotNil(child2.x) luaunit.assertTrue(child1.x >= 0) luaunit.assertTrue(child2.x > child1.x) end -- Test: Percentage width with zero parent width (division by zero) function TestCriticalFailures:test_percentage_width_zero_parent() local parent = FlexLove.new({ width = 0, height = 100 }) -- This should not crash (division by zero in percentage calculation) local success, child = pcall(function() return FlexLove.new({ width = "50%", height = 50, parent = parent }) end) luaunit.assertTrue(success, "Should not crash with zero parent width") if success then -- Width should be 0 or handled gracefully luaunit.assertTrue(child.width >= 0) end end -- Test: Auto-sizing with circular dependency function TestCriticalFailures:test_autosizing_circular_dependency() FlexLove.init() FlexLove.init() -- Parent auto-sizes to child, child uses percentage of parent local parent = FlexLove.new({ height = 100 }) -- No width = auto -- Child width is percentage of parent, but parent width depends on child local success, child = pcall(function() return FlexLove.new({ width = "50%", height = 50, parent = parent }) end) luaunit.assertTrue(success, "Should not crash with circular sizing") -- Check that we don't get NaN or negative values if success then luaunit.assertFalse(parent.width ~= parent.width, "Parent width should not be NaN") luaunit.assertFalse(child.width ~= child.width, "Child width should not be NaN") luaunit.assertTrue(parent.width >= 0, "Parent width should be non-negative") luaunit.assertTrue(child.width >= 0, "Child width should be non-negative") end end -- Test: Negative padding should not cause negative content dimensions function TestCriticalFailures:test_negative_padding_content_dimensions() local element = FlexLove.new({ width = 100, height = 100, padding = { top = -50, left = -50, right = -50, bottom = -50 }, }) -- Content width/height should never be negative luaunit.assertTrue(element.width >= 0, "Content width should be non-negative: " .. element.width) luaunit.assertTrue(element.height >= 0, "Content height should be non-negative: " .. element.height) end -- Test: Grid layout with zero rows/columns (division by zero) function TestCriticalFailures:test_grid_zero_dimensions() local parent = FlexLove.new({ width = 300, height = 200, positioning = "grid", gridRows = 0, gridColumns = 0, }) -- This should not crash when adding children local success = pcall(function() FlexLove.new({ width = 50, height = 50, parent = parent }) end) luaunit.assertTrue(success, "Should not crash with zero grid dimensions") end -- ============================================================ -- UNSAFE INPUT ACCESS - Find nil dereference and type errors -- ============================================================ -- Test: setText with number should not crash (type coercion) function TestCriticalFailures:test_set_text_with_number() local element = FlexLove.new({ width = 100, height = 100, text = "initial" }) -- Many Lua APIs expect string but get number local success = pcall(function() element:setText(12345) end) luaunit.assertTrue(success, "Should handle number text gracefully") end -- Test: Image path with special characters should not crash file system function TestCriticalFailures:test_image_path_special_characters() local success = pcall(function() FlexLove.new({ width = 100, height = 100, imagePath = "../../../etc/passwd", -- Path traversal attempt }) end) luaunit.assertTrue(success, "Should handle malicious paths gracefully") end -- Test: onEvent callback that errors should not crash the system function TestCriticalFailures:test_on_event_error_handling() local element = FlexLove.new({ width = 100, height = 100, onEvent = function(el, event) error("Intentional error in callback") end, }) -- Simulate mouse click local success = pcall(function() local InputEvent = require("modules.InputEvent") local event = InputEvent.new({ type = "pressed", button = 1 }) if element.onEvent then element.onEvent(element, event) end end) -- Should error (no protection), but shouldn't leave system in bad state luaunit.assertFalse(success) -- Element should still be valid luaunit.assertNotNil(element) end -- Test: Text with null bytes should not cause buffer issues function TestCriticalFailures:test_text_with_null_bytes() local success = pcall(function() FlexLove.new({ width = 200, height = 100, text = "Hello\0World\0\0\0", }) end) luaunit.assertTrue(success, "Should handle null bytes in text") end -- Test: Extremely deep nesting should not cause stack overflow function TestCriticalFailures:test_extreme_nesting_stack_overflow() local parent = FlexLove.new({ width = 500, height = 500 }) local current = parent -- Try to create 1000 levels of nesting local success = pcall(function() for i = 1, 1000 do local child = FlexLove.new({ width = 10, height = 10, parent = current, }) current = child end end) -- This might fail due to legitimate recursion limits -- But it should fail gracefully, not segfault if not success then print("Deep nesting failed (expected for extreme depth)") end luaunit.assertTrue(true) -- If we get here, no segfault end -- Test: Gap with NaN value function TestCriticalFailures:test_gap_nan_value() local success = pcall(function() FlexLove.new({ width = 300, height = 200, positioning = "flex", gap = 0 / 0, -- NaN }) end) -- NaN in calculations can propagate and cause issues luaunit.assertTrue(success, "Should handle NaN gap") end -- Test: Border-box model with huge padding function TestCriticalFailures:test_huge_padding_overflow() local element = FlexLove.new({ width = 100, height = 100, padding = { top = 1000000, left = 1000000, right = 1000000, bottom = 1000000 }, }) -- Content dimensions should be clamped to 0, not underflow luaunit.assertTrue(element.width >= 0, "Width should not underflow: " .. element.width) luaunit.assertTrue(element.height >= 0, "Height should not underflow: " .. element.height) end -- Test: Scroll position race condition in immediate mode function TestCriticalFailures:test_scroll_position_race_immediate_mode() FlexLove.setMode("immediate") -- Create scrollable element FlexLove.beginFrame() local element = FlexLove.new({ id = "scroll_test", width = 200, height = 200, overflow = "scroll", }) FlexLove.endFrame() -- Set scroll position element:setScrollPosition(50, 50) -- Create same element next frame (state should restore) FlexLove.beginFrame() local element2 = FlexLove.new({ id = "scroll_test", width = 200, height = 200, overflow = "scroll", }) FlexLove.endFrame() -- Scroll position should persist (or at least not crash) local scrollX, scrollY = element2:getScrollPosition() luaunit.assertNotNil(scrollX) luaunit.assertNotNil(scrollY) end -- Test: Theme with missing required properties function TestCriticalFailures:test_theme_missing_properties() local Theme = require("modules.Theme") -- Create theme with minimal properties local success = pcall(function() local theme = Theme.new({ name = "broken", -- Missing components table }) FlexLove.init({ theme = theme }) end) -- Should handle gracefully or error clearly luaunit.assertTrue(true) -- If we get here, no segfault end -- Test: Blur with zero or negative quality function TestCriticalFailures:test_blur_invalid_quality() local success = pcall(function() FlexLove.new({ width = 100, height = 100, contentBlur = { intensity = 50, quality = 0 }, }) end) luaunit.assertTrue(success, "Should handle zero blur quality") success = pcall(function() FlexLove.new({ width = 100, height = 100, contentBlur = { intensity = 50, quality = -5 }, }) end) luaunit.assertTrue(success, "Should handle negative blur quality") end -- ============================================================ -- NIL DEREFERENCE BUGS - Target specific LSP warnings -- ============================================================ -- Test: 9-patch padding with corrupted theme state (Element.lua:752-755) function TestCriticalFailures:test_ninepatch_padding_nil_dereference() local Theme = require("modules.Theme") -- Create a theme with 9-patch data local theme = Theme.new({ name = "test_theme", components = { container = { ninePatch = { imagePath = "themes/metal.lua", -- Invalid path to trigger edge case contentPadding = { top = 10, left = 10, right = 10, bottom = 10 } } } } }) FlexLove.init({ theme = theme }) -- Try to create element that uses 9-patch padding -- If ninePatchContentPadding becomes nil but use9PatchPadding is true, this will crash local success, err = pcall(function() return FlexLove.new({ width = 100, height = 100, component = "container", -- No explicit padding, should use 9-patch padding }) end) if not success then print("ERROR: " .. tostring(err)) end luaunit.assertTrue(success, "Should handle 9-patch padding gracefully") end -- Test: Theme with malformed 9-patch data function TestCriticalFailures:test_malformed_ninepatch_data() local Theme = require("modules.Theme") -- Create theme with incomplete 9-patch data local success = pcall(function() local theme = Theme.new({ name = "broken_nine_patch", components = { container = { ninePatch = { -- Missing imagePath contentPadding = { top = 10, left = 10 }, -- Incomplete padding } } } }) FlexLove.init({ theme = theme }) FlexLove.new({ width = 100, height = 100, component = "container", }) end) -- Should either succeed or fail with clear error (not nil dereference) luaunit.assertTrue(true) -- If we get here, no segfault end -- ============================================================ -- INTEGRATION TESTS - Combine features in unexpected ways -- ============================================================ -- Test: Scrollable element with overflow content + immediate mode + state restoration function TestCriticalFailures:test_scroll_overflow_immediate_mode_integration() FlexLove.setMode("immediate") for frame = 1, 3 do FlexLove.beginFrame() local scrollContainer = FlexLove.new({ id = "scroll_container", width = 200, height = 150, overflow = "scroll", positioning = "flex", flexDirection = "vertical", }) -- Add children that exceed container height for i = 1, 10 do FlexLove.new({ id = "child_" .. i, width = 180, height = 50, parent = scrollContainer, }) end FlexLove.endFrame() -- Scroll on second frame if frame == 2 then scrollContainer:setScrollPosition(0, 100) end -- Check scroll position restored on third frame if frame == 3 then local scrollX, scrollY = scrollContainer:getScrollPosition() luaunit.assertNotNil(scrollX, "Scroll X should be preserved") luaunit.assertNotNil(scrollY, "Scroll Y should be preserved") end end end -- Test: Grid layout with auto-sized children and percentage gaps function TestCriticalFailures:test_grid_autosized_children_percentage_gap() local grid = FlexLove.new({ width = 300, height = 300, positioning = "grid", gridRows = 3, gridColumns = 3, gap = "5%", -- Percentage gap }) -- Add auto-sized children (no explicit dimensions) for i = 1, 9 do local child = FlexLove.new({ parent = grid, text = "Cell " .. i, -- Auto-sizing based on text }) -- Verify child dimensions are valid luaunit.assertNotNil(child.width) luaunit.assertNotNil(child.height) luaunit.assertTrue(child.width >= 0) luaunit.assertTrue(child.height >= 0) end end -- Test: Nested flex containers with conflicting alignment function TestCriticalFailures:test_nested_flex_conflicting_alignment() local outer = FlexLove.new({ width = 400, height = 400, positioning = "flex", flexDirection = "vertical", alignItems = "stretch", justifyContent = "center", }) local middle = FlexLove.new({ parent = outer, height = 200, -- Auto width (should stretch) positioning = "flex", flexDirection = "horizontal", alignItems = "flex-end", justifyContent = "space-between", }) local inner1 = FlexLove.new({ parent = middle, width = 50, -- Auto height text = "A", }) local inner2 = FlexLove.new({ parent = middle, width = 50, height = 100, text = "B", }) -- Verify all elements have valid dimensions and positions luaunit.assertTrue(outer.width > 0) luaunit.assertTrue(middle.width > 0) luaunit.assertTrue(inner1.height > 0) luaunit.assertNotNil(inner1.x) luaunit.assertNotNil(inner2.x) end -- Test: Element with multiple conflicting size sources function TestCriticalFailures:test_conflicting_size_sources() -- Element with explicit size + auto-sizing content + parent constraints local parent = FlexLove.new({ width = 200, height = 200, positioning = "flex", alignItems = "stretch", }) local success = pcall(function() FlexLove.new({ parent = parent, width = 300, -- Exceeds parent width height = "50%", -- Percentage height text = "Very long text that should cause auto-sizing", padding = { top = 50, left = 50, right = 50, bottom = 50 }, }) end) luaunit.assertTrue(success, "Should handle conflicting size sources") end -- Test: Image element with resize during load function TestCriticalFailures:test_image_resize_during_load() local element = FlexLove.new({ width = 100, height = 100, imagePath = "nonexistent.png", -- Won't load, but should handle gracefully }) -- Simulate resize while "loading" FlexLove.resize(1920, 1080) -- Element should still be valid luaunit.assertNotNil(element.width) luaunit.assertNotNil(element.height) luaunit.assertTrue(element.width > 0) luaunit.assertTrue(element.height > 0) end -- Test: Rapid theme switching with active elements function TestCriticalFailures:test_rapid_theme_switching() local Theme = require("modules.Theme") local theme1 = Theme.new({ name = "theme1", components = {} }) local theme2 = Theme.new({ name = "theme2", components = {} }) FlexLove.init({ theme = theme1 }) -- Create elements with theme1 local element1 = FlexLove.new({ width = 100, height = 100 }) local element2 = FlexLove.new({ width = 100, height = 100 }) -- Switch theme FlexLove.destroy() FlexLove.init({ theme = theme2 }) -- Old elements should be invalidated (accessing them might crash) local success = pcall(function() element1:setText("test") end) -- It's OK if this fails (element destroyed), but shouldn't segfault luaunit.assertTrue(true) end -- Test: Update properties during layout calculation function TestCriticalFailures:test_update_during_layout() local parent = FlexLove.new({ width = 300, height = 300, positioning = "flex", }) local child = FlexLove.new({ width = 100, height = 100, parent = parent, }) -- Modify child properties immediately after creation (during layout) child:setText("Modified during layout") child.backgroundColor = { r = 1, g = 0, b = 0, a = 1 } -- Trigger another layout parent:resize(400, 400) -- Everything should still be valid luaunit.assertNotNil(child.text) luaunit.assertEquals(child.text, "Modified during layout") end -- ============================================================ -- STATE CORRUPTION SCENARIOS -- ============================================================ -- Test: Destroy element with active event listeners function TestCriticalFailures:test_destroy_with_active_listeners() local eventFired = false local element = FlexLove.new({ width = 100, height = 100, onEvent = function(el, event) eventFired = true end, }) -- Simulate an event via InputEvent local InputEvent = require("modules.InputEvent") local event = InputEvent.new({ type = "pressed", button = 1, x = 50, y = 50 }) if element.onEvent and element:contains(50, 50) then element.onEvent(element, event) end luaunit.assertTrue(eventFired, "Event should fire before destroy") -- Destroy element element:destroy() -- onEvent should be nil after destroy luaunit.assertNil(element.onEvent, "onEvent should be cleared after destroy") end -- Test: Double destroy should be safe function TestCriticalFailures:test_double_destroy_safety() local element = FlexLove.new({ width = 100, height = 100 }) element:destroy() -- Second destroy should be safe (idempotent) local success = pcall(function() element:destroy() end) luaunit.assertTrue(success, "Double destroy should be safe") end -- Test: Circular parent-child reference (should never happen, but test safety) function TestCriticalFailures:test_circular_parent_child_reference() local parent = FlexLove.new({ width = 200, height = 200 }) local child = FlexLove.new({ width = 100, height = 100, parent = parent }) -- Try to create circular reference (should be prevented) local success = pcall(function() parent.parent = child -- This should never be allowed parent:layoutChildren() -- This would cause infinite recursion end) -- Even if we set circular reference, layout should not crash luaunit.assertTrue(true) -- If we get here, no stack overflow end -- Test: Modify children array during iteration function TestCriticalFailures:test_modify_children_during_iteration() local parent = FlexLove.new({ width = 300, height = 300, positioning = "flex", }) -- Add several children local children = {} for i = 1, 5 do children[i] = FlexLove.new({ width = 50, height = 50, parent = parent, }) end -- Remove child during layout (simulates user code modifying structure) local success = pcall(function() -- Trigger layout parent:layoutChildren() -- Remove a child (modifies children array) children[3]:destroy() -- Trigger layout again parent:layoutChildren() end) luaunit.assertTrue(success, "Should handle children modification during layout") end if not _G.RUNNING_ALL_TESTS then os.exit(luaunit.LuaUnit.run()) end