1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-09-16 09:06:36 +02:00

Add comprehensive tests for remaining core components - KV, CatchStack, LoopTable, and Collectors

Co-authored-by: ziflex <1607148+ziflex@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-09-03 21:59:37 +00:00
parent 81551f98d4
commit cc1e55c37c
3 changed files with 888 additions and 0 deletions

View File

@@ -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))
})
})
})
}

View File

@@ -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)
})
})
})
}

View File

@@ -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)
})
})
})
}