Files
uopi/internal/controllogic/engine_test.go
T

169 lines
5.1 KiB
Go

package controllogic
import (
"context"
"reflect"
"testing"
)
// runFlowOnce compiles g, wires a minimal engine/context onto the compiled graph
// (enough for the run/follow path and the lock-free emitDebug guard), then drives
// the flow from triggerID synchronously and returns the compiled graph so callers
// can inspect locals.
func runFlowOnce(t *testing.T, g Graph, triggerID string) *compiledGraph {
t.Helper()
cg := compile(g)
cg.engine = &Engine{}
cg.genCtx = context.Background()
R := func(ds, name string) Value {
switch ds {
case "local":
return cg.getLocal(name)
default:
return 0.0
}
}
ctx := &runCtx{fired: triggerID, resolve: R}
cg.follow(triggerID, "out", ctx)
return cg
}
func TestArrayPushNode(t *testing.T) {
g := Graph{
ID: "g", Name: "n",
StateVars: []StateVar{{Name: "buf", Type: "array", Initial: "[1]", Sizing: "dynamic"}},
Nodes: []Node{
{ID: "t", Kind: "trigger.timer", Params: map[string]string{"interval": "1000"}},
{ID: "p", Kind: "action.array.push", Params: map[string]string{"array": "buf", "expr": "5"}},
},
Wires: []Wire{{From: "t", To: "p"}},
}
cg := runFlowOnce(t, g, "t")
if got := cg.getLocal("buf"); !reflect.DeepEqual(got, []Value{1.0, 5.0}) {
t.Fatalf("after push buf = %#v", got)
}
}
func TestArrayClearNode(t *testing.T) {
g := Graph{
ID: "g", Name: "n",
StateVars: []StateVar{{Name: "buf", Type: "array", Initial: "[1,2,3]", Sizing: "fixed", Capacity: 3}},
Nodes: []Node{
{ID: "t", Kind: "trigger.timer", Params: map[string]string{"interval": "1000"}},
{ID: "c", Kind: "action.array.clear", Params: map[string]string{"array": "buf"}},
},
Wires: []Wire{{From: "t", To: "c"}},
}
cg := runFlowOnce(t, g, "t")
if got := cg.getLocal("buf"); !reflect.DeepEqual(got, []Value{0.0, 0.0, 0.0}) {
t.Fatalf("after clear buf = %#v", got)
}
}
func TestArraySetNode(t *testing.T) {
g := Graph{
ID: "g", Name: "n",
StateVars: []StateVar{{Name: "buf", Type: "array", Initial: "[0,0,0]", Sizing: "dynamic"}},
Nodes: []Node{
{ID: "t", Kind: "trigger.timer", Params: map[string]string{"interval": "1000"}},
{ID: "s", Kind: "action.array.set", Params: map[string]string{"array": "buf", "index": "1", "expr": "9"}},
},
Wires: []Wire{{From: "t", To: "s"}},
}
cg := runFlowOnce(t, g, "t")
if got := cg.getLocal("buf"); !reflect.DeepEqual(got, []Value{0.0, 9.0, 0.0}) {
t.Fatalf("after set buf = %#v", got)
}
}
func TestArrayRemoveNode(t *testing.T) {
g := Graph{
ID: "g", Name: "n",
StateVars: []StateVar{{Name: "buf", Type: "array", Initial: "[10,20,30]", Sizing: "dynamic"}},
Nodes: []Node{
{ID: "t", Kind: "trigger.timer", Params: map[string]string{"interval": "1000"}},
{ID: "r", Kind: "action.array.remove", Params: map[string]string{"array": "buf", "index": "-1"}},
},
Wires: []Wire{{From: "t", To: "r"}},
}
cg := runFlowOnce(t, g, "t")
if got := cg.getLocal("buf"); !reflect.DeepEqual(got, []Value{10.0, 20.0}) {
t.Fatalf("after remove buf = %#v", got)
}
}
func TestArrayPopNode(t *testing.T) {
g := Graph{
ID: "g", Name: "n",
StateVars: []StateVar{{Name: "buf", Type: "array", Initial: "[1,2,3]", Sizing: "dynamic"}},
Nodes: []Node{
{ID: "t", Kind: "trigger.timer", Params: map[string]string{"interval": "1000"}},
{ID: "p", Kind: "action.array.pop", Params: map[string]string{"array": "buf"}},
},
Wires: []Wire{{From: "t", To: "p"}},
}
cg := runFlowOnce(t, g, "t")
if got := cg.getLocal("buf"); !reflect.DeepEqual(got, []Value{1.0, 2.0}) {
t.Fatalf("after pop buf = %#v", got)
}
}
func TestCompileInitsLocalsFromDecls(t *testing.T) {
g := Graph{
ID: "g", Name: "n",
StateVars: []StateVar{
{Name: "count", Type: "number", Initial: "7"},
{Name: "flag", Type: "bool", Initial: "true"},
{Name: "buf", Type: "array", Initial: "[1,2,3]", Sizing: "capped", Capacity: 4},
},
}
cg := compile(g)
if got := cg.getLocal("count"); got != Value(7.0) {
t.Fatalf("count = %#v", got)
}
if got := cg.getLocal("flag"); got != Value(1.0) {
t.Fatalf("flag = %#v", got)
}
if got := cg.getLocal("buf"); !reflect.DeepEqual(got, []Value{1.0, 2.0, 3.0}) {
t.Fatalf("buf = %#v", got)
}
}
func TestSetLocalAppliesSizing(t *testing.T) {
g := Graph{ID: "g", Name: "n", StateVars: []StateVar{
{Name: "buf", Type: "array", Initial: "[]", Sizing: "capped", Capacity: 2},
}}
cg := compile(g)
cg.setLocal("buf", []Value{1.0, 2.0, 3.0, 4.0})
if got := cg.getLocal("buf"); !reflect.DeepEqual(got, []Value{3.0, 4.0}) {
t.Fatalf("sized buf = %#v", got)
}
}
func TestResolverReturnsLocalValue(t *testing.T) {
g := Graph{ID: "g", Name: "n", StateVars: []StateVar{
{Name: "buf", Type: "array", Initial: "[10,20]", Sizing: "dynamic"},
}}
cg := compile(g)
R := func(ds, name string) Value {
if ds == "local" {
return cg.getLocal(name)
}
return 0.0
}
if v := EvalExpr("buf[1]", R); v != 20 {
t.Fatalf("buf[1] = %v", v)
}
if v := EvalExpr("sum(buf)", R); v != 30 {
t.Fatalf("sum(buf) = %v", v)
}
}
func TestEmitDebugAcceptsArray(t *testing.T) {
g := Graph{ID: "g", Name: "n"}
cg := compile(g)
cg.engine = &Engine{} // no observer installed; emitDebug must not panic
cg.emitDebug("x", []Value{1.0, 2.0}, true)
cg.emitDebug("x", 3.0, true)
}