a6fa4e7c7c
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
157 lines
3.7 KiB
Go
157 lines
3.7 KiB
Go
package controllogic
|
|
|
|
import (
|
|
"math"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestEvalExpr(t *testing.T) {
|
|
resolve := func(ds, name string) Value {
|
|
switch {
|
|
case ds == "stub" && name == "x":
|
|
return 10
|
|
case ds == "local" && name == "y":
|
|
return 3
|
|
case ds == "sys" && name == "dt":
|
|
return 0.5
|
|
}
|
|
return math.NaN()
|
|
}
|
|
cases := []struct {
|
|
expr string
|
|
want float64
|
|
}{
|
|
{"1 + 2 * 3", 7},
|
|
{"(1 + 2) * 3", 9},
|
|
{"10 % 3", 1},
|
|
{"{stub:x} + y", 13},
|
|
{"{stub:x} > 5 ? 1 : 0", 1},
|
|
{"min(4, 2, 8)", 2},
|
|
{"max(4, 2, 8)", 8},
|
|
{"abs(-5)", 5},
|
|
{"sqrt(16)", 4},
|
|
{"2 < 3 && 3 < 4", 1},
|
|
{"!0", 1},
|
|
{"-{stub:x}", -10},
|
|
{"{sys:dt} * 2", 1},
|
|
{"true + false", 1},
|
|
}
|
|
for _, c := range cases {
|
|
got := EvalExpr(c.expr, resolve)
|
|
if math.Abs(got-c.want) > 1e-9 {
|
|
t.Errorf("EvalExpr(%q) = %v, want %v", c.expr, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEvalExprErrors(t *testing.T) {
|
|
r := func(ds, name string) Value { return 0 }
|
|
for _, bad := range []string{"1 +", "(1", "1 2", "{unterminated"} {
|
|
if v := EvalExpr(bad, r); !math.IsNaN(v) {
|
|
t.Errorf("EvalExpr(%q) = %v, want NaN", bad, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCollectRefs(t *testing.T) {
|
|
refs := CollectRefs("{stub:x} + y + {epics:MY:PV} * z")
|
|
got := map[string]bool{}
|
|
for _, r := range refs {
|
|
got[r.DS+":"+r.Name] = true
|
|
}
|
|
for _, want := range []string{"stub:x", "local:y", "epics:MY:PV", "local:z"} {
|
|
if !got[want] {
|
|
t.Errorf("CollectRefs missing %q (got %v)", want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCheckExpr(t *testing.T) {
|
|
if msg := CheckExpr("1 + 2"); msg != "" {
|
|
t.Errorf("CheckExpr valid returned %q", msg)
|
|
}
|
|
if msg := CheckExpr("1 +"); msg == "" {
|
|
t.Errorf("CheckExpr invalid returned empty")
|
|
}
|
|
if msg := CheckExpr(""); msg != "" {
|
|
t.Errorf("CheckExpr empty returned %q", msg)
|
|
}
|
|
}
|
|
|
|
func TestEpicsRefSplitFirstColon(t *testing.T) {
|
|
refs := CollectRefs("{epics:SR:BPM:01:X}")
|
|
if len(refs) != 1 || refs[0].DS != "epics" || refs[0].Name != "SR:BPM:01:X" {
|
|
t.Errorf("got %+v, want epics / SR:BPM:01:X", refs)
|
|
}
|
|
}
|
|
|
|
// ── New tests for value-polymorphic evaluator ─────────────────────────────────
|
|
|
|
func numResolver(vals map[string]Value) Resolver {
|
|
return func(ds, name string) Value {
|
|
if v, ok := vals[ds+":"+name]; ok {
|
|
return v
|
|
}
|
|
return math.NaN()
|
|
}
|
|
}
|
|
|
|
func TestEvalValueScalar(t *testing.T) {
|
|
R := numResolver(nil)
|
|
if got := EvalExpr("2 + 3 * 4", R); got != 14 {
|
|
t.Fatalf("scalar = %v", got)
|
|
}
|
|
if !EvalBool("1 < 2 && 3 >= 3", R) {
|
|
t.Fatal("bool expr should be true")
|
|
}
|
|
}
|
|
|
|
func TestEvalValueArrayLiteralAndIndex(t *testing.T) {
|
|
R := numResolver(nil)
|
|
got := EvalValue("[1, 2, 3]", R)
|
|
if !reflect.DeepEqual(got, []Value{1.0, 2.0, 3.0}) {
|
|
t.Fatalf("array literal = %#v", got)
|
|
}
|
|
if v := EvalExpr("[10,20,30][-1]", R); v != 30 {
|
|
t.Fatalf("index -1 = %v", v)
|
|
}
|
|
}
|
|
|
|
func TestEvalArrayFuncs(t *testing.T) {
|
|
R := numResolver(map[string]Value{"local:buf": []Value{3.0, 1.0, 2.0}})
|
|
if v := EvalExpr("len(buf)", R); v != 3 {
|
|
t.Fatalf("len = %v", v)
|
|
}
|
|
if v := EvalExpr("sum(buf)", R); v != 6 {
|
|
t.Fatalf("sum = %v", v)
|
|
}
|
|
if v := EvalExpr("max(buf)", R); v != 3 {
|
|
t.Fatalf("max(array) = %v", v)
|
|
}
|
|
if v := EvalExpr("max(1, 9, 4)", R); v != 9 {
|
|
t.Fatalf("max(scalars) = %v", v)
|
|
}
|
|
got := EvalValue("push(buf, 7)", R)
|
|
if !reflect.DeepEqual(got, []Value{3.0, 1.0, 2.0, 7.0}) {
|
|
t.Fatalf("push = %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestEvalExprArrayYieldsNaN(t *testing.T) {
|
|
if v := EvalExpr("[1,2]", numResolver(nil)); !math.IsNaN(v) {
|
|
t.Fatalf("array via EvalExpr should be NaN, got %v", v)
|
|
}
|
|
}
|
|
|
|
func TestCollectRefsArray(t *testing.T) {
|
|
refs := CollectRefs("[{ds:a}, b[0]] ")
|
|
keys := map[string]bool{}
|
|
for _, r := range refs {
|
|
keys[r.DS+":"+r.Name] = true
|
|
}
|
|
if !keys["ds:a"] || !keys["local:b"] {
|
|
t.Fatalf("refs = %#v", refs)
|
|
}
|
|
}
|