Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions classad/api_math_coverage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package classad

import (
"testing"

"github.com/PelicanPlatform/classad/ast"
)

func TestClassAdAPIAndMathEvaluation(t *testing.T) {
ad := New()
ad.InsertAttr("i", 2)
ad.InsertAttrFloat("f", 2.5)
ad.InsertAttrString("s", "hi")
ad.InsertAttrBool("b", true)
InsertAttrList(ad, "lst", []int64{1, 2, 3})
child := New()
child.InsertAttr("c", 5)
ad.InsertAttrClassAd("child", child)

if ad.Size() != 6 {
t.Fatalf("expected 6 attributes, got %d", ad.Size())
}

if v := GetOr[int64](ad, "missing", 9); v != 9 {
t.Fatalf("GetOr default expected 9, got %d", v)
}
if v := GetOr[int64](ad, "i", 0); v != 2 {
t.Fatalf("GetOr existing expected 2, got %d", v)
}

if _, ok := ad.Lookup("s"); !ok {
t.Fatalf("expected to find attribute s")
}
if str, ok := ad.EvaluateAttrString("s"); !ok || str != "hi" {
t.Fatalf("expected EvaluateAttrString hi, got %q ok=%v", str, ok)
}
if b, ok := ad.EvaluateAttrBool("b"); !ok || !b {
t.Fatalf("expected EvaluateAttrBool true, got %v ok=%v", b, ok)
}
if num, ok := ad.EvaluateAttrNumber("f"); !ok || num != 2.5 {
t.Fatalf("expected EvaluateAttrNumber 2.5, got %v ok=%v", num, ok)
}

expr := &ast.BinaryOp{
Op: "+",
Left: &ast.AttributeReference{Name: "i"},
Right: &ast.AttributeReference{Name: "f"},
}
res := ad.EvaluateExpr(expr)
if !res.IsReal() {
t.Fatalf("expected real result from i+f")
}
if val, _ := res.RealValue(); val != 4.5 {
t.Fatalf("expected 4.5, got %f", val)
}

expr2, err := ParseExpr("MY.i + TARGET.j + round(MY.f)")
if err != nil {
t.Fatalf("ParseExpr failed: %v", err)
}
target := New()
target.InsertAttr("j", 3)
res2 := ad.EvaluateExprWithTarget(expr2, target)
if !res2.IsInteger() {
t.Fatalf("expected integer from EvaluateExprWithTarget")
}
if val, _ := res2.IntValue(); val != 8 {
t.Fatalf("expected 8 from MY/TARGET math, got %d", val)
}

bin := ad.evaluateBinaryOp("-", NewIntValue(10), NewRealValue(3.5))
if !bin.IsReal() {
t.Fatalf("binary op should yield real")
}
if val, _ := bin.RealValue(); val != 6.5 {
t.Fatalf("expected 6.5, got %f", val)
}

unary := ad.evaluateUnaryOp("-", NewRealValue(1.25))
if val, _ := unary.RealValue(); val != -1.25 {
t.Fatalf("expected -1.25 from unary op, got %f", val)
}

if !ad.Delete("s") {
t.Fatalf("expected to delete attribute s")
}
ad.Clear()
if ad.Size() != 0 {
t.Fatalf("expected clear to remove all attributes")
}
if len(ad.GetAttributes()) != 0 {
t.Fatalf("expected no attribute names after clear")
}
}
14 changes: 14 additions & 0 deletions classad/classad.go
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,20 @@ func (c *ClassAd) valueToExpr(val Value) ast.Expr {
if boolVal, err := val.BoolValue(); err == nil {
return &ast.BooleanLiteral{Value: boolVal}
}
case ListValue:
list, err := val.ListValue()
if err == nil {
elements := make([]ast.Expr, 0, len(list))
for _, item := range list {
elements = append(elements, c.valueToExpr(item))
}
return &ast.ListLiteral{Elements: elements}
}
case ClassAdValue:
adVal, err := val.ClassAdValue()
if err == nil && adVal != nil {
return &ast.RecordLiteral{ClassAd: adVal.ad}
}
case UndefinedValue:
return &ast.UndefinedLiteral{}
case ErrorValue:
Expand Down
78 changes: 78 additions & 0 deletions classad/comparison_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package classad

import "testing"

func TestEvaluatorComparisonOperators(t *testing.T) {
cases := []struct {
expr string
expectErr bool
expectVal bool
}{
{"3 > 2", false, true},
{"2 <= 2", false, true},
{"1 >= 2", false, false},
{"\"b\" <= \"a\"", false, false},
{"\"b\" >= \"a\"", false, true},
{"true > false", true, false},
{"1 >= \"a\"", true, false},
{"\"c\" > \"b\"", false, true},
}

for _, tc := range cases {
val := evalBuiltin(t, tc.expr)
if tc.expectErr {
if !val.IsError() {
t.Fatalf("expected error for %s", tc.expr)
}
continue
}

if val.IsError() || val.IsUndefined() {
t.Fatalf("unexpected non-boolean result for %s: %v", tc.expr, val.Type())
}
b, _ := val.BoolValue()
if b != tc.expectVal {
t.Fatalf("unexpected value for %s: got %v want %v", tc.expr, b, tc.expectVal)
}
}
}

func TestEvaluatorEqualityUndefined(t *testing.T) {
val := evalBuiltin(t, `1 == undefined`)
if !val.IsUndefined() {
t.Fatalf("expected undefined result when comparing to undefined, got %v", val.Type())
}
}

func TestEvaluatorEqualityNonScalar(t *testing.T) {
val := evalBuiltin(t, `{1,2} == {1,2}`)
if !val.IsError() {
t.Fatalf("expected error when comparing non-scalar values, got %v", val.Type())
}
}

func TestEvaluatorEqualityVariants(t *testing.T) {
if v := evalBuiltin(t, `undefined == undefined`); !v.IsBool() {
t.Fatalf("expected bool for undefined==undefined")
} else if b, _ := v.BoolValue(); !b {
t.Fatalf("expected undefined==undefined to be true")
}

if v := evalBuiltin(t, `true == true`); !v.IsBool() {
t.Fatalf("expected bool for bool equality")
} else if b, _ := v.BoolValue(); !b {
t.Fatalf("expected true==true")
}

if v := evalBuiltin(t, `1 == 1.0000000001`); !v.IsBool() {
t.Fatalf("expected bool for numeric near-equality")
} else if b, _ := v.BoolValue(); !b {
t.Fatalf("expected near-equal numeric values to compare true")
}

if v := evalBuiltin(t, `"a" == 1`); !v.IsBool() {
t.Fatalf("expected bool for type-mismatch equality")
} else if b, _ := v.BoolValue(); b {
t.Fatalf("expected string vs int equality to be false")
}
}
67 changes: 67 additions & 0 deletions classad/coverage_misc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package classad

import (
"testing"

"github.com/PelicanPlatform/classad/ast"
)

func TestClassAdStringEmpty(t *testing.T) {
var ad ClassAd
if ad.String() != "[]" {
t.Fatalf("expected empty ClassAd string to be [] but got %q", ad.String())
}
}

func TestExprStringUndefined(t *testing.T) {
var expr Expr
if expr.String() != "undefined" {
t.Fatalf("expected undefined Expr string but got %q", expr.String())
}
}

func TestInsertExprNilNoop(t *testing.T) {
ad := &ClassAd{}
ad.InsertExpr("ignored", nil)
if ad.Size() != 0 {
t.Fatalf("expected insert of nil Expr to be ignored; size=%d", ad.Size())
}
}

func TestEvaluateExprWithTargetNil(t *testing.T) {
ad := &ClassAd{}
result := ad.EvaluateExprWithTarget(nil, &ClassAd{})
if !result.IsUndefined() {
t.Fatalf("expected undefined when evaluating nil expr, got %v", result)
}
}

func TestFlattenBinaryWithMissingRef(t *testing.T) {
ad := &ClassAd{}
ad.InsertAttr("A", 2)

expr, err := ParseExpr("A + B")
if err != nil {
t.Fatalf("failed to parse expression: %v", err)
}

flattened := ad.Flatten(expr)
if flattened == nil {
t.Fatalf("expected flattened expression, got nil")
}

bin, ok := flattened.internal().(*ast.BinaryOp)
if !ok {
t.Fatalf("expected binary op, got %T", flattened.internal())
}

left, ok := bin.Left.(*ast.IntegerLiteral)
if !ok || left.Value != 2 {
t.Fatalf("expected left literal 2, got %T with value %v", bin.Left, bin.Left)
}

right, ok := bin.Right.(*ast.AttributeReference)
if !ok || right.Name != "B" {
t.Fatalf("expected right attribute reference B, got %T with name %v", bin.Right, right.Name)
}
}
23 changes: 23 additions & 0 deletions classad/eval_with_target_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package classad

import "testing"

func TestEvaluateExprWithTargetCustom(t *testing.T) {
scope := New()
scope.InsertAttr("A", 1)
target := New()
target.InsertAttr("B", 5)

expr, err := ParseExpr("A + TARGET.B")
if err != nil {
t.Fatalf("parse expr failed: %v", err)
}

val := scope.EvaluateExprWithTarget(expr, target)
if val.IsError() || val.IsUndefined() {
t.Fatalf("expected defined value, got %v", val.Type())
}
if num, _ := val.IntValue(); num != 6 {
t.Fatalf("unexpected evaluation result: %d", num)
}
}
Loading
Loading