From 606535244619e1733135a9a77e45a003d58f2dfa Mon Sep 17 00:00:00 2001 From: Patrick Brophy Date: Fri, 31 Oct 2025 14:05:58 -0500 Subject: [PATCH 1/3] Add a methods to insert lists to classads and add elements to existing lists in a classad --- classad/classad.go | 146 +++++++++++++++++++++++++++++++++++++++ classad/classad_test.go | 149 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+) diff --git a/classad/classad.go b/classad/classad.go index 48e89df..db1da42 100644 --- a/classad/classad.go +++ b/classad/classad.go @@ -273,6 +273,152 @@ func (c *ClassAd) InsertAttrClassAd(name string, value *ClassAd) { c.Insert(name, &ast.RecordLiteral{ClassAd: inner}) } +// InsertAttrList inserts an attribute with a list of expressions. +// This is the most general method for inserting list attributes. +// +// Example: +// +// ad := classad.New() +// elements := []ast.Expr{ +// &ast.StringLiteral{Value: "hello"}, +// &ast.IntegerLiteral{Value: 42}, +// &ast.BooleanLiteral{Value: true}, +// } +// ad.InsertAttrList("items", elements) +// // Result: items = {"hello", 42, true} +func (c *ClassAd) InsertAttrList(name string, elements []ast.Expr) { + c.Insert(name, &ast.ListLiteral{Elements: elements}) +} + +// InsertAttrListInt inserts an attribute with a list of integer values. +// +// Example: +// +// ad := classad.New() +// ad.InsertAttrListInt("numbers", []int64{1, 2, 3, 4, 5}) +// // Result: numbers = {1, 2, 3, 4, 5} +func (c *ClassAd) InsertAttrListInt(name string, values []int64) { + elements := make([]ast.Expr, len(values)) + for i, v := range values { + elements[i] = &ast.IntegerLiteral{Value: v} + } + c.InsertAttrList(name, elements) +} + +// InsertAttrListFloat inserts an attribute with a list of float values. +// +// Example: +// +// ad := classad.New() +// ad.InsertAttrListFloat("values", []float64{1.5, 2.7, 3.14}) +// // Result: values = {1.5, 2.7, 3.14} +func (c *ClassAd) InsertAttrListFloat(name string, values []float64) { + elements := make([]ast.Expr, len(values)) + for i, v := range values { + elements[i] = &ast.RealLiteral{Value: v} + } + c.InsertAttrList(name, elements) +} + +// InsertAttrListString inserts an attribute with a list of string values. +// +// Example: +// +// ad := classad.New() +// ad.InsertAttrListString("names", []string{"Alice", "Bob", "Charlie"}) +// // Result: names = {"Alice", "Bob", "Charlie"} +func (c *ClassAd) InsertAttrListString(name string, values []string) { + elements := make([]ast.Expr, len(values)) + for i, v := range values { + elements[i] = &ast.StringLiteral{Value: v} + } + c.InsertAttrList(name, elements) +} + +// InsertAttrListBool inserts an attribute with a list of boolean values. +// +// Example: +// +// ad := classad.New() +// ad.InsertAttrListBool("flags", []bool{true, false, true}) +// // Result: flags = {true, false, true} +func (c *ClassAd) InsertAttrListBool(name string, values []bool) { + elements := make([]ast.Expr, len(values)) + for i, v := range values { + elements[i] = &ast.BooleanLiteral{Value: v} + } + c.InsertAttrList(name, elements) +} + +// InsertAttrListClassAd inserts an attribute with a list of nested ClassAd values. +// Each ClassAd will be embedded as a record literal in the list. +// +// Example: +// +// ad1 := classad.New() +// ad1.InsertAttr("x", 1) +// ad2 := classad.New() +// ad2.InsertAttr("y", 2) +// resultAd := classad.New() +// resultAd.InsertAttrListClassAd("items", []*classad.ClassAd{ad1, ad2}) +// // Result: items = {[x = 1], [y = 2]} +func (c *ClassAd) InsertAttrListClassAd(name string, values []*ClassAd) { + if len(values) == 0 { + c.InsertAttrList(name, []ast.Expr{}) + return + } + + elements := make([]ast.Expr, len(values)) + for i, value := range values { + var inner *ast.ClassAd + if value != nil { + inner = value.ad + } + if inner == nil { + inner = &ast.ClassAd{Attributes: []*ast.AttributeAssignment{}} + } + elements[i] = &ast.RecordLiteral{ClassAd: inner} + } + c.InsertAttrList(name, elements) +} + +// InsertListElement inserts an element into a list attribute. +// If the attribute doesn't exist, it creates a new list with the element. +// If the attribute exists and is a list, it appends the element to the existing list. +// If the attribute exists but is not a list, it replaces it with a new list containing the element. +// +// Example: +// +// ad := classad.New() +// ad.InsertListElement("items", &ast.StringLiteral{Value: "first"}) +// ad.InsertListElement("items", &ast.StringLiteral{Value: "second"}) +// // Result: items = {"first", "second"} +func (c *ClassAd) InsertListElement(name string, element ast.Expr) { + if c.ad == nil { + c.ad = &ast.ClassAd{Attributes: []*ast.AttributeAssignment{}} + } + + // Check if attribute already exists + for i, attr := range c.ad.Attributes { + if attr.Name == name { + // If it's a list, append to it + if list, ok := attr.Value.(*ast.ListLiteral); ok { + list.Elements = append(list.Elements, element) + return + } + // Otherwise, replace with a new list containing the element + c.ad.Attributes[i].Value = &ast.ListLiteral{Elements: []ast.Expr{element}} + return + } + } + + // Add new list attribute + c.ad.Attributes = append(c.ad.Attributes, &ast.AttributeAssignment{ + Name: name, + Value: &ast.ListLiteral{Elements: []ast.Expr{element}}, + }) +} + // Lookup returns the unevaluated expression for an attribute. // Returns nil if the attribute doesn't exist. // This is useful for inspecting or copying expressions without evaluating them. diff --git a/classad/classad_test.go b/classad/classad_test.go index 67f6d1c..0ca6c9b 100644 --- a/classad/classad_test.go +++ b/classad/classad_test.go @@ -2,6 +2,8 @@ package classad import ( "testing" + + "github.com/PelicanPlatform/classad/ast" ) func TestNewClassAd(t *testing.T) { @@ -155,6 +157,153 @@ func TestInsertAttr(t *testing.T) { } } +func TestInsertListElement(t *testing.T) { + ad := New() + + // Test creating a new list + ad.InsertListElement("items", &ast.StringLiteral{Value: "first"}) + if ad.Size() != 1 { + t.Errorf("Expected size 1, got %d", ad.Size()) + } + + // Verify it's a list + expr, ok := ad.Lookup("items") + if !ok || expr == nil { + t.Fatal("Attribute 'items' not found") + } + if expr.String() != "{\"first\"}" { + t.Errorf("Expected list {\"first\"}, got %s", expr.String()) + } + + // Test appending to existing list + ad.InsertListElement("items", &ast.StringLiteral{Value: "second"}) + ad.InsertListElement("items", &ast.IntegerLiteral{Value: 42}) + + expr, ok = ad.Lookup("items") + if !ok || expr == nil { + t.Fatal("Attribute 'items' not found after append") + } + expected := "{\"first\", \"second\", 42}" + if expr.String() != expected { + t.Errorf("Expected list %s, got %s", expected, expr.String()) + } + + // Test that size didn't change (still one attribute) + if ad.Size() != 1 { + t.Errorf("Expected size 1, got %d", ad.Size()) + } + + // Test replacing non-list attribute with list + ad.InsertAttr("x", 100) + ad.InsertListElement("x", &ast.IntegerLiteral{Value: 200}) + + expr, ok = ad.Lookup("x") + if !ok || expr == nil { + t.Fatal("Attribute 'x' not found") + } + if expr.String() != "{200}" { + t.Errorf("Expected list {200}, got %s", expr.String()) + } +} + +func TestInsertAttrList(t *testing.T) { + ad := New() + + // Test InsertAttrList with mixed types + elements := []ast.Expr{ + &ast.StringLiteral{Value: "hello"}, + &ast.IntegerLiteral{Value: 42}, + &ast.BooleanLiteral{Value: true}, + &ast.RealLiteral{Value: 3.14}, + } + ad.InsertAttrList("mixed", elements) + + expr, ok := ad.Lookup("mixed") + if !ok || expr == nil { + t.Fatal("Attribute 'mixed' not found") + } + expected := "{\"hello\", 42, true, 3.14}" + if expr.String() != expected { + t.Errorf("Expected list %s, got %s", expected, expr.String()) + } + + // Test empty list + ad.InsertAttrList("empty", []ast.Expr{}) + expr, ok = ad.Lookup("empty") + if !ok || expr == nil { + t.Fatal("Attribute 'empty' not found") + } + if expr.String() != "{}" { + t.Errorf("Expected empty list {}, got %s", expr.String()) + } +} + +func TestInsertAttrListInt(t *testing.T) { + ad := New() + ad.InsertAttrListInt("numbers", []int64{1, 2, 3, 4, 5}) + + expr, ok := ad.Lookup("numbers") + if !ok || expr == nil { + t.Fatal("Attribute 'numbers' not found") + } + expected := "{1, 2, 3, 4, 5}" + if expr.String() != expected { + t.Errorf("Expected list %s, got %s", expected, expr.String()) + } + + // Test empty list + ad.InsertAttrListInt("emptyInts", []int64{}) + expr, ok = ad.Lookup("emptyInts") + if !ok || expr == nil { + t.Fatal("Attribute 'emptyInts' not found") + } + if expr.String() != "{}" { + t.Errorf("Expected empty list {}, got %s", expr.String()) + } +} + +func TestInsertAttrListFloat(t *testing.T) { + ad := New() + ad.InsertAttrListFloat("values", []float64{1.5, 2.7, 3.14}) + + expr, ok := ad.Lookup("values") + if !ok || expr == nil { + t.Fatal("Attribute 'values' not found") + } + expected := "{1.5, 2.7, 3.14}" + if expr.String() != expected { + t.Errorf("Expected list %s, got %s", expected, expr.String()) + } +} + +func TestInsertAttrListString(t *testing.T) { + ad := New() + ad.InsertAttrListString("names", []string{"Alice", "Bob", "Charlie"}) + + expr, ok := ad.Lookup("names") + if !ok || expr == nil { + t.Fatal("Attribute 'names' not found") + } + expected := "{\"Alice\", \"Bob\", \"Charlie\"}" + if expr.String() != expected { + t.Errorf("Expected list %s, got %s", expected, expr.String()) + } +} + +func TestInsertAttrListBool(t *testing.T) { + ad := New() + ad.InsertAttrListBool("flags", []bool{true, false, true}) + + expr, ok := ad.Lookup("flags") + if !ok || expr == nil { + t.Fatal("Attribute 'flags' not found") + } + expected := "{true, false, true}" + if expr.String() != expected { + t.Errorf("Expected list %s, got %s", expected, expr.String()) + } +} + func TestLookup(t *testing.T) { ad := New() ad.InsertAttr("x", 10) From 752768b05e9fccf765de5bfdc73333aa7a5bbbb1 Mon Sep 17 00:00:00 2001 From: Patrick Brophy Date: Fri, 31 Oct 2025 14:16:06 -0500 Subject: [PATCH 2/3] Apply suggestion from @Copilot Removed the inconsistent early return from InsertAttrListClassAd. It now follows the same pattern as the other InsertAttrList* methods, using make([]ast.Expr, len(values)) which correctly handles empty slices. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- classad/classad.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/classad/classad.go b/classad/classad.go index db1da42..0342e94 100644 --- a/classad/classad.go +++ b/classad/classad.go @@ -363,11 +363,6 @@ func (c *ClassAd) InsertAttrListBool(name string, values []bool) { // resultAd.InsertAttrListClassAd("items", []*classad.ClassAd{ad1, ad2}) // // Result: items = {[x = 1], [y = 2]} func (c *ClassAd) InsertAttrListClassAd(name string, values []*ClassAd) { - if len(values) == 0 { - c.InsertAttrList(name, []ast.Expr{}) - return - } - elements := make([]ast.Expr, len(values)) for i, value := range values { var inner *ast.ClassAd From abf36610e2e2e80e07bdfbddb2808e0d184b78c1 Mon Sep 17 00:00:00 2001 From: Patrick Brophy Date: Fri, 31 Oct 2025 15:55:39 -0500 Subject: [PATCH 3/3] Make generic InsertAttrList function and update tests --- classad/classad.go | 146 +++++++++++++++------------------------- classad/classad_test.go | 129 ++++++++++++++++++----------------- 2 files changed, 123 insertions(+), 152 deletions(-) diff --git a/classad/classad.go b/classad/classad.go index 0342e94..370ea15 100644 --- a/classad/classad.go +++ b/classad/classad.go @@ -273,108 +273,63 @@ func (c *ClassAd) InsertAttrClassAd(name string, value *ClassAd) { c.Insert(name, &ast.RecordLiteral{ClassAd: inner}) } -// InsertAttrList inserts an attribute with a list of expressions. -// This is the most general method for inserting list attributes. +// InsertAttrList inserts an attribute with a list of values using generics. +// Supported types are: int64, float64, string, bool, *ClassAd, and *Expr. // // Example: // // ad := classad.New() -// elements := []ast.Expr{ -// &ast.StringLiteral{Value: "hello"}, -// &ast.IntegerLiteral{Value: 42}, -// &ast.BooleanLiteral{Value: true}, -// } -// ad.InsertAttrList("items", elements) -// // Result: items = {"hello", 42, true} -func (c *ClassAd) InsertAttrList(name string, elements []ast.Expr) { - c.Insert(name, &ast.ListLiteral{Elements: elements}) -} - -// InsertAttrListInt inserts an attribute with a list of integer values. -// -// Example: -// -// ad := classad.New() -// ad.InsertAttrListInt("numbers", []int64{1, 2, 3, 4, 5}) -// // Result: numbers = {1, 2, 3, 4, 5} -func (c *ClassAd) InsertAttrListInt(name string, values []int64) { - elements := make([]ast.Expr, len(values)) - for i, v := range values { - elements[i] = &ast.IntegerLiteral{Value: v} - } - c.InsertAttrList(name, elements) -} - -// InsertAttrListFloat inserts an attribute with a list of float values. -// -// Example: -// -// ad := classad.New() -// ad.InsertAttrListFloat("values", []float64{1.5, 2.7, 3.14}) -// // Result: values = {1.5, 2.7, 3.14} -func (c *ClassAd) InsertAttrListFloat(name string, values []float64) { - elements := make([]ast.Expr, len(values)) - for i, v := range values { - elements[i] = &ast.RealLiteral{Value: v} - } - c.InsertAttrList(name, elements) -} - -// InsertAttrListString inserts an attribute with a list of string values. -// -// Example: +// InsertAttrList(ad, "numbers", []int64{1, 2, 3, 4, 5}) +// InsertAttrList(ad, "names", []string{"Alice", "Bob", "Charlie"}) +// InsertAttrList(ad, "flags", []bool{true, false, true}) +// InsertAttrList(ad, "values", []float64{1.5, 2.7, 3.14}) // -// ad := classad.New() -// ad.InsertAttrListString("names", []string{"Alice", "Bob", "Charlie"}) -// // Result: names = {"Alice", "Bob", "Charlie"} -func (c *ClassAd) InsertAttrListString(name string, values []string) { - elements := make([]ast.Expr, len(values)) - for i, v := range values { - elements[i] = &ast.StringLiteral{Value: v} - } - c.InsertAttrList(name, elements) -} - -// InsertAttrListBool inserts an attribute with a list of boolean values. -// -// Example: +// // Also works with ClassAds +// ad1, ad2 := classad.New(), classad.New() +// ad1.InsertAttr("x", 1) +// ad2.InsertAttr("y", 2) +// InsertAttrList(ad, "items", []*classad.ClassAd{ad1, ad2}) // -// ad := classad.New() -// ad.InsertAttrListBool("flags", []bool{true, false, true}) -// // Result: flags = {true, false, true} -func (c *ClassAd) InsertAttrListBool(name string, values []bool) { +// // And with expressions +// expr1, _ := classad.ParseExpr("\"hello\"") +// expr2, _ := classad.ParseExpr("42") +// InsertAttrList(ad, "mixed", []*classad.Expr{expr1, expr2}) +func InsertAttrList[T int64 | float64 | string | bool | *ClassAd | *Expr](c *ClassAd, name string, values []T) { elements := make([]ast.Expr, len(values)) for i, v := range values { - elements[i] = &ast.BooleanLiteral{Value: v} + elements[i] = valueToAstExpr(v) } - c.InsertAttrList(name, elements) + c.Insert(name, &ast.ListLiteral{Elements: elements}) } -// InsertAttrListClassAd inserts an attribute with a list of nested ClassAd values. -// Each ClassAd will be embedded as a record literal in the list. -// -// Example: -// -// ad1 := classad.New() -// ad1.InsertAttr("x", 1) -// ad2 := classad.New() -// ad2.InsertAttr("y", 2) -// resultAd := classad.New() -// resultAd.InsertAttrListClassAd("items", []*classad.ClassAd{ad1, ad2}) -// // Result: items = {[x = 1], [y = 2]} -func (c *ClassAd) InsertAttrListClassAd(name string, values []*ClassAd) { - elements := make([]ast.Expr, len(values)) - for i, value := range values { +// valueToAstExpr converts a value to an ast.Expr based on its type. +func valueToAstExpr[T int64 | float64 | string | bool | *ClassAd | *Expr](v T) ast.Expr { + switch val := any(v).(type) { + case int64: + return &ast.IntegerLiteral{Value: val} + case float64: + return &ast.RealLiteral{Value: val} + case string: + return &ast.StringLiteral{Value: val} + case bool: + return &ast.BooleanLiteral{Value: val} + case *ClassAd: var inner *ast.ClassAd - if value != nil { - inner = value.ad + if val != nil { + inner = val.ad } if inner == nil { inner = &ast.ClassAd{Attributes: []*ast.AttributeAssignment{}} } - elements[i] = &ast.RecordLiteral{ClassAd: inner} + return &ast.RecordLiteral{ClassAd: inner} + case *Expr: + if val == nil { + return &ast.UndefinedLiteral{} + } + return val.internal() + default: + return &ast.UndefinedLiteral{} } - c.InsertAttrList(name, elements) } // InsertListElement inserts an element into a list attribute. @@ -385,24 +340,33 @@ func (c *ClassAd) InsertAttrListClassAd(name string, values []*ClassAd) { // Example: // // ad := classad.New() -// ad.InsertListElement("items", &ast.StringLiteral{Value: "first"}) -// ad.InsertListElement("items", &ast.StringLiteral{Value: "second"}) +// expr1, _ := classad.ParseExpr("\"first\"") +// expr2, _ := classad.ParseExpr("\"second\"") +// ad.InsertListElement("items", expr1) +// ad.InsertListElement("items", expr2) // // Result: items = {"first", "second"} -func (c *ClassAd) InsertListElement(name string, element ast.Expr) { +func (c *ClassAd) InsertListElement(name string, element *Expr) { if c.ad == nil { c.ad = &ast.ClassAd{Attributes: []*ast.AttributeAssignment{}} } + var astExpr ast.Expr + if element == nil { + astExpr = &ast.UndefinedLiteral{} + } else { + astExpr = element.internal() + } + // Check if attribute already exists for i, attr := range c.ad.Attributes { if attr.Name == name { // If it's a list, append to it if list, ok := attr.Value.(*ast.ListLiteral); ok { - list.Elements = append(list.Elements, element) + list.Elements = append(list.Elements, astExpr) return } // Otherwise, replace with a new list containing the element - c.ad.Attributes[i].Value = &ast.ListLiteral{Elements: []ast.Expr{element}} + c.ad.Attributes[i].Value = &ast.ListLiteral{Elements: []ast.Expr{astExpr}} return } } @@ -410,7 +374,7 @@ func (c *ClassAd) InsertListElement(name string, element ast.Expr) { // Add new list attribute c.ad.Attributes = append(c.ad.Attributes, &ast.AttributeAssignment{ Name: name, - Value: &ast.ListLiteral{Elements: []ast.Expr{element}}, + Value: &ast.ListLiteral{Elements: []ast.Expr{astExpr}}, }) } diff --git a/classad/classad_test.go b/classad/classad_test.go index 0ca6c9b..aed195e 100644 --- a/classad/classad_test.go +++ b/classad/classad_test.go @@ -161,7 +161,8 @@ func TestInsertListElement(t *testing.T) { ad := New() // Test creating a new list - ad.InsertListElement("items", &ast.StringLiteral{Value: "first"}) + expr1 := &Expr{expr: &ast.StringLiteral{Value: "first"}} + ad.InsertListElement("items", expr1) if ad.Size() != 1 { t.Errorf("Expected size 1, got %d", ad.Size()) } @@ -176,8 +177,10 @@ func TestInsertListElement(t *testing.T) { } // Test appending to existing list - ad.InsertListElement("items", &ast.StringLiteral{Value: "second"}) - ad.InsertListElement("items", &ast.IntegerLiteral{Value: 42}) + expr2 := &Expr{expr: &ast.StringLiteral{Value: "second"}} + expr3 := &Expr{expr: &ast.IntegerLiteral{Value: 42}} + ad.InsertListElement("items", expr2) + ad.InsertListElement("items", expr3) expr, ok = ad.Lookup("items") if !ok || expr == nil { @@ -195,7 +198,8 @@ func TestInsertListElement(t *testing.T) { // Test replacing non-list attribute with list ad.InsertAttr("x", 100) - ad.InsertListElement("x", &ast.IntegerLiteral{Value: 200}) + expr4 := &Expr{expr: &ast.IntegerLiteral{Value: 200}} + ad.InsertListElement("x", expr4) expr, ok = ad.Lookup("x") if !ok || expr == nil { @@ -209,98 +213,101 @@ func TestInsertListElement(t *testing.T) { func TestInsertAttrList(t *testing.T) { ad := New() - // Test InsertAttrList with mixed types - elements := []ast.Expr{ - &ast.StringLiteral{Value: "hello"}, - &ast.IntegerLiteral{Value: 42}, - &ast.BooleanLiteral{Value: true}, - &ast.RealLiteral{Value: 3.14}, - } - ad.InsertAttrList("mixed", elements) - - expr, ok := ad.Lookup("mixed") + // Test with integers + InsertAttrList(ad, "numbers", []int64{1, 2, 3, 4, 5}) + expr, ok := ad.Lookup("numbers") if !ok || expr == nil { - t.Fatal("Attribute 'mixed' not found") + t.Fatal("Attribute 'numbers' not found") } - expected := "{\"hello\", 42, true, 3.14}" + expected := "{1, 2, 3, 4, 5}" if expr.String() != expected { t.Errorf("Expected list %s, got %s", expected, expr.String()) } - // Test empty list - ad.InsertAttrList("empty", []ast.Expr{}) - expr, ok = ad.Lookup("empty") + // Test with strings + InsertAttrList(ad, "names", []string{"Alice", "Bob", "Charlie"}) + expr, ok = ad.Lookup("names") if !ok || expr == nil { - t.Fatal("Attribute 'empty' not found") + t.Fatal("Attribute 'names' not found") } - if expr.String() != "{}" { - t.Errorf("Expected empty list {}, got %s", expr.String()) + expected = "{\"Alice\", \"Bob\", \"Charlie\"}" + if expr.String() != expected { + t.Errorf("Expected list %s, got %s", expected, expr.String()) } -} - -func TestInsertAttrListInt(t *testing.T) { - ad := New() - ad.InsertAttrListInt("numbers", []int64{1, 2, 3, 4, 5}) - expr, ok := ad.Lookup("numbers") + // Test with floats + InsertAttrList(ad, "values", []float64{1.5, 2.7, 3.14}) + expr, ok = ad.Lookup("values") if !ok || expr == nil { - t.Fatal("Attribute 'numbers' not found") + t.Fatal("Attribute 'values' not found") } - expected := "{1, 2, 3, 4, 5}" + expected = "{1.5, 2.7, 3.14}" if expr.String() != expected { t.Errorf("Expected list %s, got %s", expected, expr.String()) } - // Test empty list - ad.InsertAttrListInt("emptyInts", []int64{}) - expr, ok = ad.Lookup("emptyInts") + // Test with bools + InsertAttrList(ad, "flags", []bool{true, false, true}) + expr, ok = ad.Lookup("flags") if !ok || expr == nil { - t.Fatal("Attribute 'emptyInts' not found") + t.Fatal("Attribute 'flags' not found") } - if expr.String() != "{}" { - t.Errorf("Expected empty list {}, got %s", expr.String()) + expected = "{true, false, true}" + if expr.String() != expected { + t.Errorf("Expected list %s, got %s", expected, expr.String()) } -} -func TestInsertAttrListFloat(t *testing.T) { - ad := New() - ad.InsertAttrListFloat("values", []float64{1.5, 2.7, 3.14}) - - expr, ok := ad.Lookup("values") + // Test with ClassAds + ad1 := New() + ad1.InsertAttr("x", 1) + ad2 := New() + ad2.InsertAttr("y", 2) + ad3 := New() + ad3.InsertAttr("z", 3) + InsertAttrList(ad, "items", []*ClassAd{ad1, ad2, ad3}) + expr, ok = ad.Lookup("items") if !ok || expr == nil { - t.Fatal("Attribute 'values' not found") + t.Fatal("Attribute 'items' not found") } - expected := "{1.5, 2.7, 3.14}" + expected = "{[x = 1], [y = 2], [z = 3]}" if expr.String() != expected { t.Errorf("Expected list %s, got %s", expected, expr.String()) } -} -func TestInsertAttrListString(t *testing.T) { - ad := New() - ad.InsertAttrListString("names", []string{"Alice", "Bob", "Charlie"}) - - expr, ok := ad.Lookup("names") + // Test with Exprs + elements := []*Expr{ + {expr: &ast.StringLiteral{Value: "hello"}}, + {expr: &ast.IntegerLiteral{Value: 42}}, + {expr: &ast.BooleanLiteral{Value: true}}, + {expr: &ast.RealLiteral{Value: 3.14}}, + } + InsertAttrList(ad, "mixed", elements) + expr, ok = ad.Lookup("mixed") if !ok || expr == nil { - t.Fatal("Attribute 'names' not found") + t.Fatal("Attribute 'mixed' not found") } - expected := "{\"Alice\", \"Bob\", \"Charlie\"}" + expected = "{\"hello\", 42, true, 3.14}" if expr.String() != expected { t.Errorf("Expected list %s, got %s", expected, expr.String()) } -} -func TestInsertAttrListBool(t *testing.T) { - ad := New() - ad.InsertAttrListBool("flags", []bool{true, false, true}) + // Test empty lists + InsertAttrList(ad, "emptyInts", []int64{}) + expr, ok = ad.Lookup("emptyInts") + if !ok || expr == nil { + t.Fatal("Attribute 'emptyInts' not found") + } + if expr.String() != "{}" { + t.Errorf("Expected empty list {}, got %s", expr.String()) + } - expr, ok := ad.Lookup("flags") + InsertAttrList(ad, "emptyExprs", []*Expr{}) + expr, ok = ad.Lookup("emptyExprs") if !ok || expr == nil { - t.Fatal("Attribute 'flags' not found") + t.Fatal("Attribute 'emptyExprs' not found") } - expected := "{true, false, true}" - if expr.String() != expected { - t.Errorf("Expected list %s, got %s", expected, expr.String()) + if expr.String() != "{}" { + t.Errorf("Expected empty list {}, got %s", expr.String()) } }