From cc1e55c37cfb0b856e61394635162733033fa29b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Sep 2025 21:59:37 +0000 Subject: [PATCH] Add comprehensive tests for remaining core components - KV, CatchStack, LoopTable, and Collectors Co-authored-by: ziflex <1607148+ziflex@users.noreply.github.com> --- pkg/compiler/internal/core/collector_test.go | 239 +++++++++++++ pkg/compiler/internal/core/loops_test.go | 353 +++++++++++++++++++ pkg/compiler/internal/core/misc_test.go | 296 ++++++++++++++++ 3 files changed, 888 insertions(+) create mode 100644 pkg/compiler/internal/core/collector_test.go create mode 100644 pkg/compiler/internal/core/loops_test.go create mode 100644 pkg/compiler/internal/core/misc_test.go diff --git a/pkg/compiler/internal/core/collector_test.go b/pkg/compiler/internal/core/collector_test.go new file mode 100644 index 00000000..e65cba48 --- /dev/null +++ b/pkg/compiler/internal/core/collector_test.go @@ -0,0 +1,239 @@ +package core_test + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + + "github.com/MontFerret/ferret/pkg/compiler/internal/core" + "github.com/MontFerret/ferret/pkg/runtime" + "github.com/MontFerret/ferret/pkg/vm" +) + +func TestCollectSelector(t *testing.T) { + Convey("CollectSelector", t, func() { + Convey("NewCollectSelector", func() { + Convey("Should create a new collect selector", func() { + name := runtime.NewString("testField") + + selector := core.NewCollectSelector(name) + + So(selector, ShouldNotBeNil) + So(selector.Name(), ShouldEqual, name) + }) + + Convey("Should handle empty string", func() { + name := runtime.NewString("") + + selector := core.NewCollectSelector(name) + + So(selector, ShouldNotBeNil) + So(selector.Name(), ShouldEqual, name) + }) + + Convey("Should handle different string values", func() { + testCases := []string{ + "fieldName", + "field.nested", + "field[0]", + "complex.field[0].nested", + "123", + "field_name", + "FIELD_NAME", + } + + for _, testCase := range testCases { + name := runtime.NewString(testCase) + selector := core.NewCollectSelector(name) + + So(selector.Name(), ShouldEqual, name) + So(string(selector.Name()), ShouldEqual, testCase) + } + }) + }) + + Convey(".Name", func() { + Convey("Should return the selector name", func() { + name := runtime.NewString("fieldName") + selector := core.NewCollectSelector(name) + + retrievedName := selector.Name() + + So(retrievedName, ShouldEqual, name) + So(string(retrievedName), ShouldEqual, "fieldName") + }) + }) + }) +} + +func TestCollector(t *testing.T) { + Convey("Collector", t, func() { + Convey("NewCollector", func() { + Convey("Should create a new collector", func() { + dst := vm.Operand(1) + projection := core.NewCollectorGroupProjection("testGroup") + selectors := []*core.CollectSelector{ + core.NewCollectSelector(runtime.NewString("field1")), + core.NewCollectSelector(runtime.NewString("field2")), + } + aggregation := core.NewCollectorAggregation(vm.Operand(2), nil) + + collector := core.NewCollector( + core.CollectorTypeKeyGroup, + dst, + projection, + selectors, + aggregation, + ) + + So(collector, ShouldNotBeNil) + So(collector.Type(), ShouldEqual, core.CollectorTypeKeyGroup) + So(collector.Destination(), ShouldEqual, dst) + So(collector.Projection(), ShouldEqual, projection) + So(collector.GroupSelectors(), ShouldHaveLength, 2) + So(collector.Aggregation(), ShouldEqual, aggregation) + }) + + Convey("Should handle nil parameters", func() { + dst := vm.Operand(1) + + collector := core.NewCollector( + core.CollectorTypeCounter, + dst, + nil, + nil, + nil, + ) + + So(collector, ShouldNotBeNil) + So(collector.Type(), ShouldEqual, core.CollectorTypeCounter) + So(collector.Destination(), ShouldEqual, dst) + So(collector.Projection(), ShouldBeNil) + So(collector.GroupSelectors(), ShouldBeNil) + So(collector.Aggregation(), ShouldBeNil) + }) + + Convey("Should handle empty selectors", func() { + collector := core.NewCollector( + core.CollectorTypeKey, + vm.Operand(1), + nil, + []*core.CollectSelector{}, + nil, + ) + + So(collector.GroupSelectors(), ShouldHaveLength, 0) + }) + }) + + Convey("DetermineCollectorType", func() { + Convey("Should return correct type for different combinations", func() { + // Test all combinations + testCases := []struct { + withGrouping bool + withAggregation bool + withProjection bool + withCounter bool + expected core.CollectorType + }{ + {true, false, false, true, core.CollectorTypeKeyCounter}, + {true, false, false, false, core.CollectorTypeKeyGroup}, + {true, true, false, false, core.CollectorTypeKeyGroup}, + {false, true, false, false, core.CollectorTypeKeyGroup}, + {false, false, true, false, core.CollectorTypeCounter}, + {false, false, false, false, core.CollectorTypeCounter}, + {false, false, false, true, core.CollectorTypeCounter}, + } + + for _, tc := range testCases { + result := core.DetermineCollectorType( + tc.withGrouping, + tc.withAggregation, + tc.withProjection, + tc.withCounter, + ) + + So(result, ShouldEqual, tc.expected) + } + }) + + Convey("Should prioritize grouping over aggregation", func() { + result := core.DetermineCollectorType(true, true, false, false) + + So(result, ShouldEqual, core.CollectorTypeKeyGroup) + }) + + Convey("Should prioritize counter when grouping is present", func() { + result := core.DetermineCollectorType(true, false, false, true) + + So(result, ShouldEqual, core.CollectorTypeKeyCounter) + }) + }) + + Convey("Collector Types", func() { + Convey("Should have correct type constants", func() { + // Just verify the constants exist and have expected values + So(core.CollectorTypeCounter, ShouldEqual, 0) + So(core.CollectorTypeKey, ShouldEqual, 1) + So(core.CollectorTypeKeyCounter, ShouldEqual, 2) + So(core.CollectorTypeKeyGroup, ShouldEqual, 3) + }) + }) + + Convey("Integration", func() { + Convey("Should work with real selector data", func() { + // Create selectors + selectors := []*core.CollectSelector{ + core.NewCollectSelector(runtime.NewString("category")), + core.NewCollectSelector(runtime.NewString("status")), + core.NewCollectSelector(runtime.NewString("priority")), + } + + // Determine collector type + collectorType := core.DetermineCollectorType(true, false, true, false) + + // Create collector + collector := core.NewCollector( + collectorType, + vm.Operand(10), + nil, + selectors, + nil, + ) + + // Verify + So(collector.Type(), ShouldEqual, core.CollectorTypeKeyGroup) + So(collector.GroupSelectors(), ShouldHaveLength, 3) + + // Check selector names + So(string(collector.GroupSelectors()[0].Name()), ShouldEqual, "category") + So(string(collector.GroupSelectors()[1].Name()), ShouldEqual, "status") + So(string(collector.GroupSelectors()[2].Name()), ShouldEqual, "priority") + }) + + Convey("Should handle complex collector configurations", func() { + // Create multiple collectors with different types + collectors := []*core.Collector{ + core.NewCollector(core.CollectorTypeCounter, vm.Operand(1), nil, nil, nil), + core.NewCollector(core.CollectorTypeKey, vm.Operand(2), nil, []*core.CollectSelector{ + core.NewCollectSelector(runtime.NewString("key1")), + }, nil), + core.NewCollector(core.CollectorTypeKeyCounter, vm.Operand(3), nil, []*core.CollectSelector{ + core.NewCollectSelector(runtime.NewString("key2")), + core.NewCollectSelector(runtime.NewString("key3")), + }, nil), + } + + // Verify each collector + So(collectors[0].Type(), ShouldEqual, core.CollectorTypeCounter) + So(collectors[1].Type(), ShouldEqual, core.CollectorTypeKey) + So(collectors[2].Type(), ShouldEqual, core.CollectorTypeKeyCounter) + + // Verify destinations are different + So(collectors[0].Destination(), ShouldEqual, vm.Operand(1)) + So(collectors[1].Destination(), ShouldEqual, vm.Operand(2)) + So(collectors[2].Destination(), ShouldEqual, vm.Operand(3)) + }) + }) + }) +} \ No newline at end of file diff --git a/pkg/compiler/internal/core/loops_test.go b/pkg/compiler/internal/core/loops_test.go new file mode 100644 index 00000000..b6084d23 --- /dev/null +++ b/pkg/compiler/internal/core/loops_test.go @@ -0,0 +1,353 @@ +package core_test + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + + "github.com/MontFerret/ferret/pkg/compiler/internal/core" + "github.com/MontFerret/ferret/pkg/vm" +) + +func TestLoopTable(t *testing.T) { + Convey("LoopTable", t, func() { + Convey("NewLoopTable", func() { + Convey("Should create a new loop table", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + So(lt, ShouldNotBeNil) + So(lt.Depth(), ShouldEqual, 0) + So(lt.Current(), ShouldBeNil) + }) + }) + + Convey(".NewForInLoop", func() { + Convey("Should create ForIn loop with correct properties", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + loop := lt.NewForInLoop(core.NormalLoop, true) + + So(loop, ShouldNotBeNil) + So(loop.Kind, ShouldEqual, core.ForInLoop) + So(loop.Type, ShouldEqual, core.NormalLoop) + So(loop.Distinct, ShouldBeTrue) + So(loop.Allocate, ShouldBeTrue) + So(loop.Dst, ShouldNotEqual, vm.NoopOperand) + }) + + Convey("Should handle different loop types", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + normalLoop := lt.NewForInLoop(core.NormalLoop, false) + passThroughLoop := lt.NewForInLoop(core.PassThroughLoop, true) + temporalLoop := lt.NewForInLoop(core.TemporalLoop, false) + + So(normalLoop.Type, ShouldEqual, core.NormalLoop) + So(passThroughLoop.Type, ShouldEqual, core.PassThroughLoop) + So(temporalLoop.Type, ShouldEqual, core.TemporalLoop) + }) + }) + + Convey(".NewForWhileLoop", func() { + Convey("Should create ForWhile loop with correct properties", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + loop := lt.NewForWhileLoop(core.NormalLoop, false) + + So(loop, ShouldNotBeNil) + So(loop.Kind, ShouldEqual, core.ForWhileLoop) + So(loop.Type, ShouldEqual, core.NormalLoop) + So(loop.Distinct, ShouldBeFalse) + }) + }) + + Convey(".NewLoop", func() { + Convey("Should create loop with specified parameters", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + loop := lt.NewLoop(core.DoWhileLoop, core.NormalLoop, true) + + So(loop, ShouldNotBeNil) + So(loop.Kind, ShouldEqual, core.DoWhileLoop) + So(loop.Type, ShouldEqual, core.NormalLoop) + So(loop.Distinct, ShouldBeTrue) + So(loop.Allocate, ShouldBeTrue) + So(loop.Dst, ShouldNotEqual, vm.NoopOperand) + }) + + Convey("Should handle temporal loops differently", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + loop := lt.NewLoop(core.ForInLoop, core.TemporalLoop, false) + + So(loop.Type, ShouldEqual, core.TemporalLoop) + So(loop.Dst, ShouldEqual, vm.NoopOperand) // Temporal loops don't get result registers + }) + + Convey("Should handle nested PassThrough loops", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + // Create parent PassThrough loop + parentLoop := lt.NewLoop(core.ForInLoop, core.PassThroughLoop, false) + lt.Push(parentLoop) + + // Create child loop + childLoop := lt.NewLoop(core.ForInLoop, core.NormalLoop, false) + + So(childLoop.Allocate, ShouldBeFalse) // Should inherit from PassThrough parent + }) + }) + + Convey(".Push", func() { + Convey("Should add loop to stack", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + loop := lt.NewForInLoop(core.NormalLoop, false) + + lt.Push(loop) + + So(lt.Depth(), ShouldEqual, 1) + So(lt.Current(), ShouldEqual, loop) + }) + + Convey("Should handle multiple loops", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + loop1 := lt.NewForInLoop(core.NormalLoop, false) + loop2 := lt.NewForWhileLoop(core.NormalLoop, true) + + lt.Push(loop1) + lt.Push(loop2) + + So(lt.Depth(), ShouldEqual, 2) + So(lt.Current(), ShouldEqual, loop2) + }) + }) + + Convey(".Pop", func() { + Convey("Should remove and return top loop", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + loop1 := lt.NewForInLoop(core.NormalLoop, false) + loop2 := lt.NewForWhileLoop(core.NormalLoop, true) + + lt.Push(loop1) + lt.Push(loop2) + + popped := lt.Pop() + + So(popped, ShouldEqual, loop2) + So(lt.Depth(), ShouldEqual, 1) + So(lt.Current(), ShouldEqual, loop1) + }) + + Convey("Should handle empty stack", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + popped := lt.Pop() + + So(popped, ShouldBeNil) + So(lt.Depth(), ShouldEqual, 0) + }) + + Convey("Should handle popping until empty", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + loop := lt.NewForInLoop(core.NormalLoop, false) + lt.Push(loop) + + popped1 := lt.Pop() + popped2 := lt.Pop() + + So(popped1, ShouldEqual, loop) + So(popped2, ShouldBeNil) + So(lt.Depth(), ShouldEqual, 0) + }) + }) + + Convey(".FindParent", func() { + Convey("Should find parent loop that allocates", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + // Create a loop that allocates + allocatingLoop := lt.NewLoop(core.ForInLoop, core.NormalLoop, false) + allocatingLoop.Allocate = true + lt.Push(allocatingLoop) + + // Create a loop that doesn't allocate + nonAllocatingLoop := lt.NewLoop(core.ForInLoop, core.PassThroughLoop, false) + nonAllocatingLoop.Allocate = false + lt.Push(nonAllocatingLoop) + + // Find parent from position 1 (current position) + parent := lt.FindParent(1) + + So(parent, ShouldEqual, allocatingLoop) + }) + + Convey("Should return nil if no allocating parent found", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + // Create loops that don't allocate + loop1 := lt.NewLoop(core.ForInLoop, core.PassThroughLoop, false) + loop1.Allocate = false + loop2 := lt.NewLoop(core.ForInLoop, core.PassThroughLoop, false) + loop2.Allocate = false + + lt.Push(loop1) + lt.Push(loop2) + + parent := lt.FindParent(1) + + So(parent, ShouldBeNil) + }) + + Convey("Should handle invalid positions", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + parent := lt.FindParent(0) + So(parent, ShouldBeNil) + + parent2 := lt.FindParent(-1) + So(parent2, ShouldBeNil) + }) + }) + + Convey(".Current", func() { + Convey("Should return top of stack", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + loop := lt.NewForInLoop(core.NormalLoop, false) + lt.Push(loop) + + current := lt.Current() + + So(current, ShouldEqual, loop) + }) + + Convey("Should return nil for empty stack", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + current := lt.Current() + + So(current, ShouldBeNil) + }) + }) + + Convey(".Depth", func() { + Convey("Should return correct depth", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + So(lt.Depth(), ShouldEqual, 0) + + loop1 := lt.NewForInLoop(core.NormalLoop, false) + lt.Push(loop1) + So(lt.Depth(), ShouldEqual, 1) + + loop2 := lt.NewForWhileLoop(core.NormalLoop, true) + lt.Push(loop2) + So(lt.Depth(), ShouldEqual, 2) + + lt.Pop() + So(lt.Depth(), ShouldEqual, 1) + }) + }) + + Convey(".DebugView", func() { + Convey("Should return debug information", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + loop1 := lt.NewForInLoop(core.NormalLoop, false) + loop2 := lt.NewForWhileLoop(core.PassThroughLoop, true) + + lt.Push(loop1) + lt.Push(loop2) + + debug := lt.DebugView() + + So(debug, ShouldNotBeEmpty) + So(debug, ShouldContainSubstring, "Loop[0]") + So(debug, ShouldContainSubstring, "Loop[1]") + }) + + Convey("Should handle empty stack", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + debug := lt.DebugView() + + So(debug, ShouldEqual, "") + }) + }) + + Convey("Integration", func() { + Convey("Should handle complex loop nesting", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + // Create nested loop structure + outerLoop := lt.NewForInLoop(core.NormalLoop, false) + lt.Push(outerLoop) + + innerLoop1 := lt.NewForWhileLoop(core.PassThroughLoop, true) + lt.Push(innerLoop1) + + innerLoop2 := lt.NewLoop(core.DoWhileLoop, core.TemporalLoop, false) + lt.Push(innerLoop2) + + // Test operations + So(lt.Depth(), ShouldEqual, 3) + So(lt.Current(), ShouldEqual, innerLoop2) + + // Find parent + parent := lt.FindParent(2) // Should find first allocating loop + So(parent, ShouldNotBeNil) + + // Pop and verify + popped := lt.Pop() + So(popped, ShouldEqual, innerLoop2) + So(lt.Current(), ShouldEqual, innerLoop1) + + // Continue popping + lt.Pop() + lt.Pop() + + So(lt.Depth(), ShouldEqual, 0) + So(lt.Current(), ShouldBeNil) + }) + + Convey("Should create loops with proper inheritance", func() { + ra := core.NewRegisterAllocator() + lt := core.NewLoopTable(ra) + + // Create PassThrough parent + parentLoop := lt.NewLoop(core.ForInLoop, core.PassThroughLoop, false) + lt.Push(parentLoop) + + // Create child - should not allocate due to PassThrough parent + childLoop := lt.NewLoop(core.ForWhileLoop, core.NormalLoop, true) + + So(childLoop.Allocate, ShouldBeFalse) + So(childLoop.Dst, ShouldEqual, parentLoop.Dst) + }) + }) + }) +} \ No newline at end of file diff --git a/pkg/compiler/internal/core/misc_test.go b/pkg/compiler/internal/core/misc_test.go new file mode 100644 index 00000000..6af6cc7b --- /dev/null +++ b/pkg/compiler/internal/core/misc_test.go @@ -0,0 +1,296 @@ +package core_test + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + + "github.com/MontFerret/ferret/pkg/compiler/internal/core" + "github.com/MontFerret/ferret/pkg/vm" +) + +func TestKV(t *testing.T) { + Convey("KV", t, func() { + Convey("NewKV", func() { + Convey("Should create a new key-value pair", func() { + key := vm.Operand(1) + value := vm.Operand(2) + + kv := core.NewKV(key, value) + + So(kv, ShouldNotBeNil) + So(kv.Key, ShouldEqual, key) + So(kv.Value, ShouldEqual, value) + }) + + Convey("Should handle zero operands", func() { + key := vm.Operand(0) + value := vm.Operand(0) + + kv := core.NewKV(key, value) + + So(kv, ShouldNotBeNil) + So(kv.Key, ShouldEqual, key) + So(kv.Value, ShouldEqual, value) + }) + + Convey("Should handle large operand values", func() { + key := vm.Operand(999999) + value := vm.Operand(888888) + + kv := core.NewKV(key, value) + + So(kv, ShouldNotBeNil) + So(kv.Key, ShouldEqual, key) + So(kv.Value, ShouldEqual, value) + }) + }) + }) +} + +func TestCatchStack(t *testing.T) { + Convey("CatchStack", t, func() { + Convey("NewCatchStack", func() { + Convey("Should create a new empty catch stack", func() { + cs := core.NewCatchStack() + + So(cs, ShouldNotBeNil) + So(cs.Len(), ShouldEqual, 0) + So(cs.All(), ShouldHaveLength, 0) + }) + }) + + Convey(".Push", func() { + Convey("Should add catch entries", func() { + cs := core.NewCatchStack() + + cs.Push(10, 20, 30) + + So(cs.Len(), ShouldEqual, 1) + entries := cs.All() + So(entries, ShouldHaveLength, 1) + So(entries[0][0], ShouldEqual, 10) // start + So(entries[0][1], ShouldEqual, 20) // end + So(entries[0][2], ShouldEqual, 30) // jump + }) + + Convey("Should handle multiple entries", func() { + cs := core.NewCatchStack() + + cs.Push(10, 20, 30) + cs.Push(40, 50, 60) + cs.Push(70, 80, 90) + + So(cs.Len(), ShouldEqual, 3) + entries := cs.All() + So(entries, ShouldHaveLength, 3) + }) + + Convey("Should handle zero values", func() { + cs := core.NewCatchStack() + + cs.Push(0, 0, 0) + + So(cs.Len(), ShouldEqual, 1) + entries := cs.All() + So(entries[0][0], ShouldEqual, 0) + So(entries[0][1], ShouldEqual, 0) + So(entries[0][2], ShouldEqual, 0) + }) + }) + + Convey(".Pop", func() { + Convey("Should remove last entry", func() { + cs := core.NewCatchStack() + cs.Push(10, 20, 30) + cs.Push(40, 50, 60) + + cs.Pop() + + So(cs.Len(), ShouldEqual, 1) + entries := cs.All() + So(entries[0][0], ShouldEqual, 10) + So(entries[0][1], ShouldEqual, 20) + So(entries[0][2], ShouldEqual, 30) + }) + + Convey("Should handle empty stack", func() { + cs := core.NewCatchStack() + + // Should not panic + So(func() { cs.Pop() }, ShouldNotPanic) + So(cs.Len(), ShouldEqual, 0) + }) + + Convey("Should handle popping until empty", func() { + cs := core.NewCatchStack() + cs.Push(10, 20, 30) + cs.Push(40, 50, 60) + + cs.Pop() + cs.Pop() + + So(cs.Len(), ShouldEqual, 0) + + // Should not panic + So(func() { cs.Pop() }, ShouldNotPanic) + }) + }) + + Convey(".Find", func() { + Convey("Should find catch entry for position", func() { + cs := core.NewCatchStack() + cs.Push(10, 20, 30) + cs.Push(25, 35, 40) + + // Position within first range + catch, found := cs.Find(15) + So(found, ShouldBeTrue) + So(catch[0], ShouldEqual, 10) + So(catch[1], ShouldEqual, 20) + So(catch[2], ShouldEqual, 30) + + // Position within second range + catch2, found2 := cs.Find(30) + So(found2, ShouldBeTrue) + So(catch2[0], ShouldEqual, 25) + So(catch2[1], ShouldEqual, 35) + So(catch2[2], ShouldEqual, 40) + }) + + Convey("Should handle boundary conditions", func() { + cs := core.NewCatchStack() + cs.Push(10, 20, 30) + + // Start boundary + catch, found := cs.Find(10) + So(found, ShouldBeTrue) + So(catch[0], ShouldEqual, 10) + + // End boundary + catch2, found2 := cs.Find(20) + So(found2, ShouldBeTrue) + So(catch2[0], ShouldEqual, 10) + }) + + Convey("Should not find catch for position outside ranges", func() { + cs := core.NewCatchStack() + cs.Push(10, 20, 30) + + // Before range + _, found := cs.Find(5) + So(found, ShouldBeFalse) + + // After range + _, found2 := cs.Find(25) + So(found2, ShouldBeFalse) + }) + + Convey("Should handle empty stack", func() { + cs := core.NewCatchStack() + + _, found := cs.Find(10) + So(found, ShouldBeFalse) + }) + }) + + Convey(".Clear", func() { + Convey("Should remove all entries", func() { + cs := core.NewCatchStack() + cs.Push(10, 20, 30) + cs.Push(40, 50, 60) + cs.Push(70, 80, 90) + + cs.Clear() + + So(cs.Len(), ShouldEqual, 0) + So(cs.All(), ShouldHaveLength, 0) + }) + + Convey("Should handle empty stack", func() { + cs := core.NewCatchStack() + + So(func() { cs.Clear() }, ShouldNotPanic) + So(cs.Len(), ShouldEqual, 0) + }) + }) + + Convey(".Len", func() { + Convey("Should return correct length", func() { + cs := core.NewCatchStack() + + So(cs.Len(), ShouldEqual, 0) + + cs.Push(10, 20, 30) + So(cs.Len(), ShouldEqual, 1) + + cs.Push(40, 50, 60) + So(cs.Len(), ShouldEqual, 2) + + cs.Pop() + So(cs.Len(), ShouldEqual, 1) + }) + }) + + Convey(".All", func() { + Convey("Should return all entries", func() { + cs := core.NewCatchStack() + cs.Push(10, 20, 30) + cs.Push(40, 50, 60) + + all := cs.All() + + So(all, ShouldHaveLength, 2) + So(all[0][0], ShouldEqual, 10) + So(all[0][1], ShouldEqual, 20) + So(all[0][2], ShouldEqual, 30) + So(all[1][0], ShouldEqual, 40) + So(all[1][1], ShouldEqual, 50) + So(all[1][2], ShouldEqual, 60) + }) + + Convey("Should return empty slice for empty stack", func() { + cs := core.NewCatchStack() + + all := cs.All() + + So(all, ShouldHaveLength, 0) + }) + }) + + Convey("Integration", func() { + Convey("Should handle complex operations", func() { + cs := core.NewCatchStack() + + // Add multiple entries + cs.Push(0, 10, 100) + cs.Push(15, 25, 200) + cs.Push(30, 40, 300) + + // Test finding + catch, found := cs.Find(5) + So(found, ShouldBeTrue) + So(catch[2], ShouldEqual, 100) + + catch2, found2 := cs.Find(20) + So(found2, ShouldBeTrue) + So(catch2[2], ShouldEqual, 200) + + catch3, found3 := cs.Find(35) + So(found3, ShouldBeTrue) + So(catch3[2], ShouldEqual, 300) + + // Pop one + cs.Pop() + + // Should not find in removed range + _, found4 := cs.Find(35) + So(found4, ShouldBeFalse) + + // Should still find in remaining ranges + _, found5 := cs.Find(20) + So(found5, ShouldBeTrue) + }) + }) + }) +} \ No newline at end of file