Skip to content

Commit

Permalink
[configuration] extend template test package, incl. utility and inven…
Browse files Browse the repository at this point in the history
…tory functions
  • Loading branch information
knopers8 committed Feb 24, 2025
1 parent 6861011 commit 5edcea4
Show file tree
Hide file tree
Showing 9 changed files with 425 additions and 10 deletions.
3 changes: 3 additions & 0 deletions common/gera/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ type Map[K comparable, V any] interface {
Set(key K, value V) bool
Del(key K) bool

// Flattened should return a flat representation of the tree in the root direction,
// where children kv pairs overwrite parent kv pairs.
Flattened() (map[K]V, error)
// FlattenedParent should return a flat representation of the tree in the root direction,
FlattenedParent() (map[K]V, error)
WrappedAndFlattened(m Map[K, V]) (map[K]V, error)

Expand Down
2 changes: 1 addition & 1 deletion configuration/template/dplutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func extractConfigURIs(dplCommand string) (uris []string) {
// Match any consul/apricot URI
// it would be the easiest to use a backreference in the regex, but regexp does not support those:
// (['"]?)((consul-json|apricot)://[^ |\n]*)(\1)
re := regexp.MustCompile(`['"]?(consul-json|apricot)://[^ |\n]*`)
re := regexp.MustCompile(`['"]?(consul-json|apricot)://[^ |\n]+`)
matches := re.FindAllStringSubmatch(dplCommand, nMaxExpectedQcPayloads)

for _, match := range matches {
Expand Down
8 changes: 8 additions & 0 deletions configuration/template/dplutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ var _ = Describe("DPL utilities", func() {
Expect(uris).To(HaveLen(0))
})
})
When("URI is not complete", func() {
BeforeEach(func() {
uris = extractConfigURIs("myexe --config apricot://")
})
It("should return an empty slice", func() {
Expect(uris).To(HaveLen(0))
})
})
When("the URI is the last argument", func() {
BeforeEach(func() {
uris = extractConfigURIs("myexe --config apricot://host.cern.ch:12345/components/qc/ANY/any/ctp-raw")
Expand Down
19 changes: 19 additions & 0 deletions configuration/template/fieldwrappers.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ func WrapGeneric(getterF GetterFunc, setterF SetterFunc) Field {
}
}

// WrapMapItems creates a slice of Fields from a map of string key-value pairs.
// It wraps each map item in a GenericWrapper, allowing for dynamic access and
// modification of the original map's values through the Fields interface.
//
// Parameters:
// - items: A map[string]string to be wrapped
//
// Returns:
// - Fields: A slice of Field interfaces, each corresponding to a map item
func WrapMapItems(items map[string]string) Fields {
fields := make(Fields, 0)
for k := range items {
Expand All @@ -83,6 +92,16 @@ func WrapMapItems(items map[string]string) Fields {
return fields
}

// WrapSliceItems creates a slice of Fields from a slice of strings.
// It wraps each string item in a GenericWrapper, allowing for dynamic
// access and modification of the original slice's elements through
// the Fields interface.
//
// Parameters:
// - items: A []string to be wrapped
//
// Returns:
// - Fields: A slice of Field interfaces, each corresponding to a slice item
func WrapSliceItems(items []string) Fields {
fields := make(Fields, 0)
for i := range items {
Expand Down
78 changes: 78 additions & 0 deletions configuration/template/fieldwrappers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package template_test

import (
"github.com/AliceO2Group/Control/configuration/template"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Field Wrappers", func() {
Describe("PointerWrapper", func() {
It("should get and set values correctly", func() {
value := "test"
pw := template.WrapPointer(&value)

Expect(pw.Get()).To(Equal("test"))

pw.Set("new value")
Expect(value).To(Equal("new value"))
})
})

Describe("GenericWrapper", func() {
It("should get and set values correctly", func() {
value := "test"
gw := template.WrapGeneric(
func() string { return value },
func(v string) { value = v },
)

Expect(gw.Get()).To(Equal("test"))

gw.Set("new value")
Expect(value).To(Equal("new value"))
})
})

Describe("WrapMapItems", func() {
It("should wrap map items correctly", func() {
items := map[string]string{
"key1": "value1",
"key2": "value2",
}
fields := template.WrapMapItems(items)

Expect(fields).To(HaveLen(2))

for _, field := range fields {
initialValue := field.Get()
field.Set("new " + initialValue)
}

expectedItems := map[string]string{
"key1": "new value1",
"key2": "new value2",
}

Expect(items).To(Equal(expectedItems))
})
})

Describe("WrapSliceItems", func() {
It("should wrap slice items correctly", func() {
items := []string{"item1", "item2", "item3"}
fields := template.WrapSliceItems(items)

Expect(fields).To(HaveLen(3))

for i, field := range fields {
Expect(field.Get()).To(Equal(items[i]))
field.Set("new " + items[i])
}

expectedItems := []string{"new item1", "new item2", "new item3"}

Expect(items).To(Equal(expectedItems))
})
})
})
216 changes: 216 additions & 0 deletions configuration/template/stack_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package template_test

import (
"encoding/json"
"github.com/AliceO2Group/Control/apricot/local"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"strings"

"github.com/AliceO2Group/Control/configuration/template"
)

var _ = Describe("Template function stack", func() {

Describe("utility functions", func() {
var utilFuncMap map[string]interface{}

BeforeEach(func() {
varStack := map[string]string{
"test_var": "test_value",
"prefix_var": "prefixed_value",
"fallback_var": "fallback_value",
"prefixed_none_fallback_var": "none",
}
utilFuncMap = template.MakeUtilFuncMap(varStack)
})

Context("strings functions", func() {
It("should validate inputs and convert string to int", func() {
atoiFunc := utilFuncMap["Atoi"].(func(string) int)

result := atoiFunc("123")
Expect(result).To(Equal(123))
// we only produce an error log if unexpected input appears
result = atoiFunc("abc")
Expect(result).To(Equal(0))
result = atoiFunc("")
Expect(result).To(Equal(0))
})

It("should validate inputs and convert int to string", func() {
itoaFunc := utilFuncMap["Itoa"].(func(int) string)

Expect(itoaFunc(123)).To(Equal("123"))
Expect(itoaFunc(0)).To(Equal("0"))
Expect(itoaFunc(-456)).To(Equal("-456"))
})

It("should trim quotes", func() {
trimQuotesFunc := utilFuncMap["TrimQuotes"].(func(string) string)

Expect(trimQuotesFunc("\"test\"")).To(Equal("test"))
// Fixme: should it support also single quotes?
//Expect(trimQuotesFunc("'test'")).To(Equal("test"))

Expect(trimQuotesFunc("test")).To(Equal("test"))
Expect(trimQuotesFunc("")).To(Equal(""))
Expect(trimQuotesFunc("\"test")).To(Equal("test"))
Expect(trimQuotesFunc("test\"")).To(Equal("test"))
})

It("should trim spaces", func() {
trimSpaceFunc := utilFuncMap["TrimSpace"].(func(string) string)

Expect(trimSpaceFunc(" test ")).To(Equal("test"))
Expect(trimSpaceFunc("test")).To(Equal("test"))
Expect(trimSpaceFunc("test test ")).To(Equal("test test"))
})

It("should convert to upper case", func() {
toUpperFunc := utilFuncMap["ToUpper"].(func(string) string)
Expect(toUpperFunc("test")).To(Equal("TEST"))
Expect(toUpperFunc("Test")).To(Equal("TEST"))
Expect(toUpperFunc("1")).To(Equal("1"))
})

It("should convert to lower case", func() {
toLowerFunc := utilFuncMap["ToLower"].(func(string) string)
Expect(toLowerFunc("TEST")).To(Equal("test"))
Expect(toLowerFunc("Test")).To(Equal("test"))
Expect(toLowerFunc("1")).To(Equal("1"))
})

It("should check if a string is truthy", func() {
isTruthyFunc := utilFuncMap["IsTruthy"].(func(string) bool)

Expect(isTruthyFunc("true")).To(BeTrue())
Expect(isTruthyFunc("TRUE")).To(BeTrue())
Expect(isTruthyFunc("yes")).To(BeTrue())
Expect(isTruthyFunc("y")).To(BeTrue())
Expect(isTruthyFunc("1")).To(BeTrue())
Expect(isTruthyFunc("on")).To(BeTrue())
Expect(isTruthyFunc("ok")).To(BeTrue())

Expect(isTruthyFunc("false")).To(BeFalse())
Expect(isTruthyFunc("")).To(BeFalse())
Expect(isTruthyFunc("0")).To(BeFalse())
Expect(isTruthyFunc("truthy")).To(BeFalse())
})

It("should check if a string is falsy", func() {
isFalsyFunc := utilFuncMap["IsFalsy"].(func(string) bool)
Expect(isFalsyFunc("false")).To(BeTrue())
Expect(isFalsyFunc("FALSE")).To(BeTrue())
Expect(isFalsyFunc("no")).To(BeTrue())
Expect(isFalsyFunc("n")).To(BeTrue())
Expect(isFalsyFunc("0")).To(BeTrue())
Expect(isFalsyFunc("off")).To(BeTrue())
Expect(isFalsyFunc("none")).To(BeTrue())
Expect(isFalsyFunc("")).To(BeTrue())

Expect(isFalsyFunc("true")).To(BeFalse())
Expect(isFalsyFunc("1")).To(BeFalse())
})
})

Context("json functions", func() {
It("should unmarshal JSON", func() {
unmarshalFunc := utilFuncMap["json"].(map[string]interface{})["Unmarshal"].(func(string) interface{})
result := unmarshalFunc(`{"key": "value"}`)
Expect(result).To(HaveKeyWithValue("key", "value"))
})

It("should marshal to JSON", func() {
marshalFunc := utilFuncMap["json"].(map[string]interface{})["Marshal"].(func(interface{}) string)
input := map[string]string{"key": "value"}
result := marshalFunc(input)
Expect(result).To(MatchJSON(`{"key": "value"}`))
})
})

Context("uid functions", func() {
It("should generate a new UID", func() {
newFunc := utilFuncMap["uid"].(map[string]interface{})["New"].(func() string)
uid1 := newFunc()
uid2 := newFunc()
Expect(uid1).NotTo(Equal(uid2))
})
})

Context("util functions", func() {
It("should handle prefixed override", func() {
prefixedOverrideFunc := utilFuncMap["util"].(map[string]interface{})["PrefixedOverride"].(func(string, string) string)
Expect(prefixedOverrideFunc("var", "prefix")).To(Equal("prefixed_value"))
Expect(prefixedOverrideFunc("fallback_var", "nonexistent_prefix")).To(Equal("fallback_value"))
Expect(prefixedOverrideFunc("fallback_var", "prefixed_none")).To(Equal("fallback_value"))
Expect(prefixedOverrideFunc("nonexistent_fallback_var", "prefix")).To(Equal(""))
})

It("should handle nullable strings", func() {
nullableFunc := utilFuncMap["util"].(map[string]interface{})["Nullable"].(func(*string) string)
var nilString *string
Expect(nullableFunc(nilString)).To(Equal(""))

nonNilString := "test"
Expect(nullableFunc(&nonNilString)).To(Equal("test"))
})

It("should check if suffix is in range", func() {
suffixInRangeFunc := utilFuncMap["util"].(map[string]interface{})["SuffixInRange"].(func(string, string, string, string) string)
Expect(suffixInRangeFunc("test5", "test", "1", "10")).To(Equal("true"))
Expect(suffixInRangeFunc("test15", "test", "1", "10")).To(Equal("false"))
// no prefix
Expect(suffixInRangeFunc("other5", "test", "1", "10")).To(Equal("false"))
// no suffix
Expect(suffixInRangeFunc("test", "test", "1", "10")).To(Equal("false"))
// range arguments are not numbers
Expect(suffixInRangeFunc("test", "test", "a", "10")).To(Equal("false"))
Expect(suffixInRangeFunc("test", "test", "1", "b")).To(Equal("false"))
})
})
})

Describe("inventory functions", func() {
var (
svc *local.Service
configAccessObject map[string]interface{}
err error
)
BeforeEach(func() {
svc, err = local.NewService("file://" + *tmpDir + "/" + serviceConfigFile)
Expect(err).NotTo(HaveOccurred())

varStack := map[string]string{}
configAccessObject = template.MakeConfigAccessObject(svc, varStack)
})

It("should get detector for host", func() {
getDetectorFunc := configAccessObject["inventory"].(map[string]interface{})["DetectorForHost"].(func(string) string)
Expect(getDetectorFunc("flp001")).To(Equal("ABC"))
Expect(getDetectorFunc("NOPE")).To(ContainSubstring("error"))
})
It("should get detectors for a list of hosts", func() {
getDetectorsFunc := configAccessObject["inventory"].(map[string]interface{})["DetectorsForHosts"].(func(string) string)
Expect(getDetectorsFunc("[ \"flp001\", \"flp002\" ]")).To(Equal("[\"ABC\",\"DEF\"]"))
Expect(getDetectorsFunc("[ \"flp001\", \"NOPE\" ]")).To(ContainSubstring("error"))
Expect(getDetectorsFunc("[ \"NOPE\" ]")).To(ContainSubstring("error"))
Expect(getDetectorsFunc("flp001")).To(ContainSubstring("error"))
})
It("should get CRU cards for host", func() {
getCruCardsFunc := configAccessObject["inventory"].(map[string]interface{})["CRUCardsForHost"].(func(string) string)
var result []string
err := json.Unmarshal([]byte(getCruCardsFunc("flp001")), &result)
Expect(err).NotTo(HaveOccurred())
Expect(result).To(ConsistOf("0228", "0229"))
Expect(getCruCardsFunc("NOPE")).To(ContainSubstring("error"))
})
It("should get endpoints for CRU card", func() {
getEndpointsFunc := configAccessObject["inventory"].(map[string]interface{})["EndpointsForCRUCard"].(func(string, string) string)
endpoints := strings.Split(getEndpointsFunc("flp001", "0228"), " ")
Expect(endpoints).To(ConsistOf("0", "1"))
// fixme: probably incorrect behaviour, but I don't want to risk breaking something
Expect(getEndpointsFunc("flp001", "NOPE")).To(BeEmpty())
})
})
})
Loading

0 comments on commit 5edcea4

Please sign in to comment.