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:
239
pkg/compiler/internal/core/collector_test.go
Normal file
239
pkg/compiler/internal/core/collector_test.go
Normal 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))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
353
pkg/compiler/internal/core/loops_test.go
Normal file
353
pkg/compiler/internal/core/loops_test.go
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
296
pkg/compiler/internal/core/misc_test.go
Normal file
296
pkg/compiler/internal/core/misc_test.go
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user