diff options
44 files changed, 814 insertions, 495 deletions
@@ -5,7 +5,7 @@ import ( "text/template" "github.com/hairyhenderson/gomplate/v3/data" - "github.com/hairyhenderson/gomplate/v3/funcs" + "github.com/hairyhenderson/gomplate/v3/funcs" //nolint:staticcheck "github.com/hairyhenderson/gomplate/v3/internal/config" ) diff --git a/funcs/aws.go b/funcs/aws.go index 8f5f9380..bc4a573c 100644 --- a/funcs/aws.go +++ b/funcs/aws.go @@ -8,22 +8,15 @@ import ( "github.com/hairyhenderson/gomplate/v3/conv" ) -var ( - af *Funcs - afInit sync.Once -) - // AWSNS - the aws namespace +// Deprecated: don't use +//nolint:golint func AWSNS() *Funcs { - afInit.Do(func() { - af = &Funcs{ - awsopts: aws.GetClientOptions(), - } - }) - return af + return &Funcs{} } // AWSFuncs - +// Deprecated: use CreateAWSFuncs instead func AWSFuncs(f map[string]interface{}) { f2 := CreateAWSFuncs(context.Background()) for k, v := range f2 { @@ -34,10 +27,13 @@ func AWSFuncs(f map[string]interface{}) { // CreateAWSFuncs - func CreateAWSFuncs(ctx context.Context) map[string]interface{} { f := map[string]interface{}{} - ns := AWSNS() - ns.ctx = ctx - f["aws"] = AWSNS + ns := &Funcs{ + ctx: ctx, + awsopts: aws.GetClientOptions(), + } + + f["aws"] = func() interface{} { return ns } // global aliases - for backwards compatibility f["ec2meta"] = ns.EC2Meta diff --git a/funcs/aws_test.go b/funcs/aws_test.go index 22b6da65..79324f70 100644 --- a/funcs/aws_test.go +++ b/funcs/aws_test.go @@ -1,16 +1,27 @@ package funcs import ( + "context" + "strconv" "testing" "github.com/hairyhenderson/gomplate/v3/aws" "github.com/stretchr/testify/assert" ) -func TestNSIsIdempotent(t *testing.T) { - left := AWSNS() - right := AWSNS() - assert.True(t, left == right) +func TestCreateAWSFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateAWSFuncs(ctx) + actual := fmap["aws"].(func() interface{}) + + assert.Same(t, ctx, actual().(*Funcs).ctx) + }) + } } func TestAWSFuncs(t *testing.T) { diff --git a/funcs/base64.go b/funcs/base64.go index 0b913008..17cdf5ae 100644 --- a/funcs/base64.go +++ b/funcs/base64.go @@ -2,24 +2,19 @@ package funcs import ( "context" - "sync" "github.com/hairyhenderson/gomplate/v3/base64" "github.com/hairyhenderson/gomplate/v3/conv" ) -var ( - bf *Base64Funcs - bfInit sync.Once -) - // Base64NS - the base64 namespace +// Deprecated: don't use func Base64NS() *Base64Funcs { - bfInit.Do(func() { bf = &Base64Funcs{} }) - return bf + return &Base64Funcs{} } // AddBase64Funcs - +// Deprecated: use CreateBase64Funcs instead func AddBase64Funcs(f map[string]interface{}) { for k, v := range CreateBase64Funcs(context.Background()) { f[k] = v @@ -28,9 +23,12 @@ func AddBase64Funcs(f map[string]interface{}) { // CreateBase64Funcs - func CreateBase64Funcs(ctx context.Context) map[string]interface{} { - ns := Base64NS() - ns.ctx = ctx - return map[string]interface{}{"base64": Base64NS} + f := map[string]interface{}{} + + ns := &Base64Funcs{ctx} + f["base64"] = func() interface{} { return ns } + + return f } // Base64Funcs - @@ -39,19 +37,19 @@ type Base64Funcs struct { } // Encode - -func (f *Base64Funcs) Encode(in interface{}) (string, error) { +func (Base64Funcs) Encode(in interface{}) (string, error) { b := toBytes(in) return base64.Encode(b) } // Decode - -func (f *Base64Funcs) Decode(in interface{}) (string, error) { +func (Base64Funcs) Decode(in interface{}) (string, error) { out, err := base64.Decode(conv.ToString(in)) return string(out), err } // DecodeBytes - -func (f *Base64Funcs) DecodeBytes(in interface{}) ([]byte, error) { +func (Base64Funcs) DecodeBytes(in interface{}) ([]byte, error) { out, err := base64.Decode(conv.ToString(in)) return out, err } diff --git a/funcs/base64_test.go b/funcs/base64_test.go index 6cfe2b9e..4c611fee 100644 --- a/funcs/base64_test.go +++ b/funcs/base64_test.go @@ -2,11 +2,28 @@ package funcs import ( "bytes" + "context" + "strconv" "testing" "github.com/stretchr/testify/assert" ) +func TestCreateBase64Funcs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateBase64Funcs(ctx) + actual := fmap["base64"].(func() interface{}) + + assert.Same(t, ctx, actual().(*Base64Funcs).ctx) + }) + } +} + func TestBase64Encode(t *testing.T) { bf := &Base64Funcs{} assert.Equal(t, "Zm9vYmFy", must(bf.Encode("foobar"))) diff --git a/funcs/coll.go b/funcs/coll.go index ae36fbd7..8c6e9f47 100644 --- a/funcs/coll.go +++ b/funcs/coll.go @@ -2,7 +2,6 @@ package funcs import ( "context" - "sync" "github.com/hairyhenderson/gomplate/v3/conv" @@ -10,18 +9,14 @@ import ( "github.com/pkg/errors" ) -var ( - collNS *CollFuncs - collNSInit sync.Once -) - // CollNS - +// Deprecated: don't use func CollNS() *CollFuncs { - collNSInit.Do(func() { collNS = &CollFuncs{} }) - return collNS + return &CollFuncs{} } // AddCollFuncs - +// Deprecated: use CreateCollFuncs instead func AddCollFuncs(f map[string]interface{}) { for k, v := range CreateCollFuncs(context.Background()) { f[k] = v @@ -30,24 +25,24 @@ func AddCollFuncs(f map[string]interface{}) { // CreateCollFuncs - func CreateCollFuncs(ctx context.Context) map[string]interface{} { - ns := CollNS() - ns.ctx = ctx f := map[string]interface{}{} - f["coll"] = CollNS - - f["has"] = CollNS().Has - f["slice"] = CollNS().Slice - f["dict"] = CollNS().Dict - f["keys"] = CollNS().Keys - f["values"] = CollNS().Values - f["append"] = CollNS().Append - f["prepend"] = CollNS().Prepend - f["uniq"] = CollNS().Uniq - f["reverse"] = CollNS().Reverse - f["merge"] = CollNS().Merge - f["sort"] = CollNS().Sort - f["jsonpath"] = CollNS().JSONPath - f["flatten"] = CollNS().Flatten + + ns := &CollFuncs{ctx} + f["coll"] = func() interface{} { return ns } + + f["has"] = ns.Has + f["slice"] = ns.Slice + f["dict"] = ns.Dict + f["keys"] = ns.Keys + f["values"] = ns.Values + f["append"] = ns.Append + f["prepend"] = ns.Prepend + f["uniq"] = ns.Uniq + f["reverse"] = ns.Reverse + f["merge"] = ns.Merge + f["sort"] = ns.Sort + f["jsonpath"] = ns.JSONPath + f["flatten"] = ns.Flatten return f } @@ -57,57 +52,57 @@ type CollFuncs struct { } // Slice - -func (f *CollFuncs) Slice(args ...interface{}) []interface{} { +func (CollFuncs) Slice(args ...interface{}) []interface{} { return coll.Slice(args...) } // Has - -func (f *CollFuncs) Has(in interface{}, key string) bool { +func (CollFuncs) Has(in interface{}, key string) bool { return coll.Has(in, key) } // Dict - -func (f *CollFuncs) Dict(in ...interface{}) (map[string]interface{}, error) { +func (CollFuncs) Dict(in ...interface{}) (map[string]interface{}, error) { return coll.Dict(in...) } // Keys - -func (f *CollFuncs) Keys(in ...map[string]interface{}) ([]string, error) { +func (CollFuncs) Keys(in ...map[string]interface{}) ([]string, error) { return coll.Keys(in...) } // Values - -func (f *CollFuncs) Values(in ...map[string]interface{}) ([]interface{}, error) { +func (CollFuncs) Values(in ...map[string]interface{}) ([]interface{}, error) { return coll.Values(in...) } // Append - -func (f *CollFuncs) Append(v interface{}, list interface{}) ([]interface{}, error) { +func (CollFuncs) Append(v interface{}, list interface{}) ([]interface{}, error) { return coll.Append(v, list) } // Prepend - -func (f *CollFuncs) Prepend(v interface{}, list interface{}) ([]interface{}, error) { +func (CollFuncs) Prepend(v interface{}, list interface{}) ([]interface{}, error) { return coll.Prepend(v, list) } // Uniq - -func (f *CollFuncs) Uniq(in interface{}) ([]interface{}, error) { +func (CollFuncs) Uniq(in interface{}) ([]interface{}, error) { return coll.Uniq(in) } // Reverse - -func (f *CollFuncs) Reverse(in interface{}) ([]interface{}, error) { +func (CollFuncs) Reverse(in interface{}) ([]interface{}, error) { return coll.Reverse(in) } // Merge - -func (f *CollFuncs) Merge(dst map[string]interface{}, src ...map[string]interface{}) (map[string]interface{}, error) { +func (CollFuncs) Merge(dst map[string]interface{}, src ...map[string]interface{}) (map[string]interface{}, error) { return coll.Merge(dst, src...) } // Sort - -func (f *CollFuncs) Sort(args ...interface{}) ([]interface{}, error) { +func (CollFuncs) Sort(args ...interface{}) ([]interface{}, error) { var ( key string list interface{} @@ -126,12 +121,12 @@ func (f *CollFuncs) Sort(args ...interface{}) ([]interface{}, error) { } // JSONPath - -func (f *CollFuncs) JSONPath(p string, in interface{}) (interface{}, error) { +func (CollFuncs) JSONPath(p string, in interface{}) (interface{}, error) { return coll.JSONPath(p, in) } // Flatten - -func (f *CollFuncs) Flatten(args ...interface{}) ([]interface{}, error) { +func (CollFuncs) Flatten(args ...interface{}) ([]interface{}, error) { if len(args) == 0 || len(args) > 2 { return nil, errors.Errorf("wrong number of args: wanted 1 or 2, got %d", len(args)) } @@ -166,7 +161,7 @@ func pickOmitArgs(args ...interface{}) (map[string]interface{}, []string, error) } // Pick - -func (f *CollFuncs) Pick(args ...interface{}) (map[string]interface{}, error) { +func (CollFuncs) Pick(args ...interface{}) (map[string]interface{}, error) { m, keys, err := pickOmitArgs(args...) if err != nil { return nil, err @@ -175,7 +170,7 @@ func (f *CollFuncs) Pick(args ...interface{}) (map[string]interface{}, error) { } // Omit - -func (f *CollFuncs) Omit(args ...interface{}) (map[string]interface{}, error) { +func (CollFuncs) Omit(args ...interface{}) (map[string]interface{}, error) { m, keys, err := pickOmitArgs(args...) if err != nil { return nil, err diff --git a/funcs/coll_test.go b/funcs/coll_test.go index da554a13..44dcced4 100644 --- a/funcs/coll_test.go +++ b/funcs/coll_test.go @@ -1,13 +1,30 @@ package funcs import ( + "context" + "strconv" "testing" "github.com/stretchr/testify/assert" ) +func TestCreateCollFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateCollFuncs(ctx) + actual := fmap["coll"].(func() interface{}) + + assert.Same(t, ctx, actual().(*CollFuncs).ctx) + }) + } +} + func TestFlatten(t *testing.T) { - c := CollNS() + c := CollFuncs{} _, err := c.Flatten() assert.Error(t, err) diff --git a/funcs/conv.go b/funcs/conv.go index 986d4833..a6bf7c9f 100644 --- a/funcs/conv.go +++ b/funcs/conv.go @@ -3,25 +3,20 @@ package funcs import ( "context" "net/url" - "sync" "text/template" "github.com/hairyhenderson/gomplate/v3/coll" "github.com/hairyhenderson/gomplate/v3/conv" ) -var ( - convNS *ConvFuncs - convNSInit sync.Once -) - // ConvNS - +// Deprecated: don't use func ConvNS() *ConvFuncs { - convNSInit.Do(func() { convNS = &ConvFuncs{} }) - return convNS + return &ConvFuncs{} } // AddConvFuncs - +// Deprecated: use CreateConvFuncs instead func AddConvFuncs(f map[string]interface{}) { for k, v := range CreateConvFuncs(context.Background()) { f[k] = v @@ -30,10 +25,10 @@ func AddConvFuncs(f map[string]interface{}) { // CreateConvFuncs - func CreateConvFuncs(ctx context.Context) map[string]interface{} { - ns := ConvNS() - ns.ctx = ctx + ns := &ConvFuncs{ctx} + f := map[string]interface{}{} - f["conv"] = ConvNS + f["conv"] = func() interface{} { return ns } f["urlParse"] = ns.URL f["bool"] = ns.Bool @@ -48,102 +43,102 @@ type ConvFuncs struct { } // Bool - -func (f *ConvFuncs) Bool(s interface{}) bool { +func (ConvFuncs) Bool(s interface{}) bool { return conv.Bool(conv.ToString(s)) } // ToBool - -func (f *ConvFuncs) ToBool(in interface{}) bool { +func (ConvFuncs) ToBool(in interface{}) bool { return conv.ToBool(in) } // ToBools - -func (f *ConvFuncs) ToBools(in ...interface{}) []bool { +func (ConvFuncs) ToBools(in ...interface{}) []bool { return conv.ToBools(in...) } // Slice - -func (f *ConvFuncs) Slice(args ...interface{}) []interface{} { +func (ConvFuncs) Slice(args ...interface{}) []interface{} { return coll.Slice(args...) } // Join - -func (f *ConvFuncs) Join(in interface{}, sep string) (string, error) { +func (ConvFuncs) Join(in interface{}, sep string) (string, error) { return conv.Join(in, sep) } // Has - -func (f *ConvFuncs) Has(in interface{}, key string) bool { +func (ConvFuncs) Has(in interface{}, key string) bool { return coll.Has(in, key) } // ParseInt - -func (f *ConvFuncs) ParseInt(s interface{}, base, bitSize int) int64 { +func (ConvFuncs) ParseInt(s interface{}, base, bitSize int) int64 { return conv.MustParseInt(conv.ToString(s), base, bitSize) } // ParseFloat - -func (f *ConvFuncs) ParseFloat(s interface{}, bitSize int) float64 { +func (ConvFuncs) ParseFloat(s interface{}, bitSize int) float64 { return conv.MustParseFloat(conv.ToString(s), bitSize) } // ParseUint - -func (f *ConvFuncs) ParseUint(s interface{}, base, bitSize int) uint64 { +func (ConvFuncs) ParseUint(s interface{}, base, bitSize int) uint64 { return conv.MustParseUint(conv.ToString(s), base, bitSize) } // Atoi - -func (f *ConvFuncs) Atoi(s interface{}) int { +func (ConvFuncs) Atoi(s interface{}) int { return conv.MustAtoi(conv.ToString(s)) } // URL - -func (f *ConvFuncs) URL(s interface{}) (*url.URL, error) { +func (ConvFuncs) URL(s interface{}) (*url.URL, error) { return url.Parse(conv.ToString(s)) } // ToInt64 - -func (f *ConvFuncs) ToInt64(in interface{}) int64 { +func (ConvFuncs) ToInt64(in interface{}) int64 { return conv.ToInt64(in) } // ToInt - -func (f *ConvFuncs) ToInt(in interface{}) int { +func (ConvFuncs) ToInt(in interface{}) int { return conv.ToInt(in) } // ToInt64s - -func (f *ConvFuncs) ToInt64s(in ...interface{}) []int64 { +func (ConvFuncs) ToInt64s(in ...interface{}) []int64 { return conv.ToInt64s(in...) } // ToInts - -func (f *ConvFuncs) ToInts(in ...interface{}) []int { +func (ConvFuncs) ToInts(in ...interface{}) []int { return conv.ToInts(in...) } // ToFloat64 - -func (f *ConvFuncs) ToFloat64(in interface{}) float64 { +func (ConvFuncs) ToFloat64(in interface{}) float64 { return conv.ToFloat64(in) } // ToFloat64s - -func (f *ConvFuncs) ToFloat64s(in ...interface{}) []float64 { +func (ConvFuncs) ToFloat64s(in ...interface{}) []float64 { return conv.ToFloat64s(in...) } // ToString - -func (f *ConvFuncs) ToString(in interface{}) string { +func (ConvFuncs) ToString(in interface{}) string { return conv.ToString(in) } // ToStrings - -func (f *ConvFuncs) ToStrings(in ...interface{}) []string { +func (ConvFuncs) ToStrings(in ...interface{}) []string { return conv.ToStrings(in...) } // Default - -func (f *ConvFuncs) Default(def, in interface{}) interface{} { +func (ConvFuncs) Default(def, in interface{}) interface{} { if truth, ok := template.IsTrue(in); truth && ok { return in } @@ -151,6 +146,6 @@ func (f *ConvFuncs) Default(def, in interface{}) interface{} { } // Dict - -func (f *ConvFuncs) Dict(in ...interface{}) (map[string]interface{}, error) { +func (ConvFuncs) Dict(in ...interface{}) (map[string]interface{}, error) { return coll.Dict(in...) } diff --git a/funcs/conv_test.go b/funcs/conv_test.go index 9fbfb8bb..b9aa757e 100644 --- a/funcs/conv_test.go +++ b/funcs/conv_test.go @@ -1,15 +1,32 @@ package funcs import ( + "context" "fmt" + "strconv" "testing" "github.com/stretchr/testify/assert" ) +func TestCreateConvFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateConvFuncs(ctx) + actual := fmap["conv"].(func() interface{}) + + assert.Same(t, ctx, actual().(*ConvFuncs).ctx) + }) + } +} + func TestDefault(t *testing.T) { s := struct{}{} - c := ConvNS() + c := &ConvFuncs{} def := "DEFAULT" data := []struct { val interface{} diff --git a/funcs/crypto.go b/funcs/crypto.go index 0b6049af..9a241fb9 100644 --- a/funcs/crypto.go +++ b/funcs/crypto.go @@ -7,7 +7,6 @@ import ( "crypto/sha256" "crypto/sha512" "fmt" - "sync" "golang.org/x/crypto/bcrypt" @@ -17,18 +16,14 @@ import ( "github.com/hairyhenderson/gomplate/v3/crypto" ) -var ( - cryptoNS *CryptoFuncs - cryptoNSInit sync.Once -) - // CryptoNS - the crypto namespace +// Deprecated: don't use func CryptoNS() *CryptoFuncs { - cryptoNSInit.Do(func() { cryptoNS = &CryptoFuncs{} }) - return cryptoNS + return &CryptoFuncs{} } // AddCryptoFuncs - +// Deprecated: use CreateCryptoFuncs instead func AddCryptoFuncs(f map[string]interface{}) { for k, v := range CreateCryptoFuncs(context.Background()) { f[k] = v @@ -37,10 +32,12 @@ func AddCryptoFuncs(f map[string]interface{}) { // CreateCryptoFuncs - func CreateCryptoFuncs(ctx context.Context) map[string]interface{} { - ns := CryptoNS() - ns.ctx = ctx + f := map[string]interface{}{} + + ns := &CryptoFuncs{ctx} - return map[string]interface{}{"crypto": CryptoNS} + f["crypto"] = func() interface{} { return ns } + return f } // CryptoFuncs - @@ -51,7 +48,7 @@ type CryptoFuncs struct { // PBKDF2 - Run the Password-Based Key Derivation Function #2 as defined in // RFC 2898 (PKCS #5 v2.0). This function outputs the binary result in hex // format. -func (f *CryptoFuncs) PBKDF2(password, salt, iter, keylen interface{}, hashFunc ...string) (k string, err error) { +func (CryptoFuncs) PBKDF2(password, salt, iter, keylen interface{}, hashFunc ...string) (k string, err error) { var h gcrypto.Hash if len(hashFunc) == 0 { h = gcrypto.SHA1 @@ -71,12 +68,12 @@ func (f *CryptoFuncs) PBKDF2(password, salt, iter, keylen interface{}, hashFunc } // WPAPSK - Convert an ASCII passphrase to WPA PSK for a given SSID -func (f *CryptoFuncs) WPAPSK(ssid, password interface{}) (string, error) { +func (f CryptoFuncs) WPAPSK(ssid, password interface{}) (string, error) { return f.PBKDF2(password, ssid, 4096, 32) } // SHA1 - Note: SHA-1 is cryptographically broken and should not be used for secure applications. -func (f *CryptoFuncs) SHA1(input interface{}) string { +func (CryptoFuncs) SHA1(input interface{}) string { in := toBytes(input) // nolint: gosec out := sha1.Sum(in) @@ -84,28 +81,28 @@ func (f *CryptoFuncs) SHA1(input interface{}) string { } // SHA224 - -func (f *CryptoFuncs) SHA224(input interface{}) string { +func (CryptoFuncs) SHA224(input interface{}) string { in := toBytes(input) out := sha256.Sum224(in) return fmt.Sprintf("%02x", out) } // SHA256 - -func (f *CryptoFuncs) SHA256(input interface{}) string { +func (CryptoFuncs) SHA256(input interface{}) string { in := toBytes(input) out := sha256.Sum256(in) return fmt.Sprintf("%02x", out) } // SHA384 - -func (f *CryptoFuncs) SHA384(input interface{}) string { +func (CryptoFuncs) SHA384(input interface{}) string { in := toBytes(input) out := sha512.Sum384(in) return fmt.Sprintf("%02x", out) } // SHA512 - -func (f *CryptoFuncs) SHA512(input interface{}) string { +func (CryptoFuncs) SHA512(input interface{}) string { in := toBytes(input) out := sha512.Sum512(in) return fmt.Sprintf("%02x", out) @@ -113,7 +110,7 @@ func (f *CryptoFuncs) SHA512(input interface{}) string { // SHA512_224 - //nolint: golint,stylecheck -func (f *CryptoFuncs) SHA512_224(input interface{}) string { +func (CryptoFuncs) SHA512_224(input interface{}) string { in := toBytes(input) out := sha512.Sum512_224(in) return fmt.Sprintf("%02x", out) @@ -121,14 +118,14 @@ func (f *CryptoFuncs) SHA512_224(input interface{}) string { // SHA512_256 - //nolint: golint,stylecheck -func (f *CryptoFuncs) SHA512_256(input interface{}) string { +func (CryptoFuncs) SHA512_256(input interface{}) string { in := toBytes(input) out := sha512.Sum512_256(in) return fmt.Sprintf("%02x", out) } // Bcrypt - -func (f *CryptoFuncs) Bcrypt(args ...interface{}) (string, error) { +func (CryptoFuncs) Bcrypt(args ...interface{}) (string, error) { input := "" cost := bcrypt.DefaultCost if len(args) == 0 { diff --git a/funcs/crypto_test.go b/funcs/crypto_test.go index b61643f7..adcaf731 100644 --- a/funcs/crypto_test.go +++ b/funcs/crypto_test.go @@ -2,6 +2,7 @@ package funcs import ( "context" + "strconv" "strings" "testing" @@ -9,19 +10,35 @@ import ( "github.com/stretchr/testify/assert" ) +func TestCreateCryptoFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateCryptoFuncs(ctx) + actual := fmap["crypto"].(func() interface{}) + + assert.Same(t, ctx, actual().(*CryptoFuncs).ctx) + }) + } +} + func testCryptoNS() *CryptoFuncs { ctx := context.Background() cfg := config.FromContext(ctx) cfg.Experimental = true ctx = config.ContextWithConfig(ctx, cfg) - c := CryptoNS() - c.ctx = ctx - return c + + return &CryptoFuncs{ + ctx: ctx, + } } func TestPBKDF2(t *testing.T) { c := testCryptoNS() - dk, err := cryptoNS.PBKDF2("password", []byte("IEEE"), "4096", 32) + dk, err := c.PBKDF2("password", []byte("IEEE"), "4096", 32) assert.Equal(t, "f42c6fc52df0ebef9ebb4b90b38a5f902e83fe1b135a70e23aed762e9710a12e", dk) assert.NoError(t, err) @@ -34,7 +51,8 @@ func TestPBKDF2(t *testing.T) { } func TestWPAPSK(t *testing.T) { - dk, err := cryptoNS.WPAPSK("password", "MySSID") + c := testCryptoNS() + dk, err := c.WPAPSK("password", "MySSID") assert.Equal(t, "3a98def84b11644a17ebcc9b17955d2360ce8b8a85b8a78413fc551d722a84e7", dk) assert.NoError(t, err) } @@ -59,6 +77,10 @@ func TestSHA(t *testing.T) { } func TestBcrypt(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow test") + } + in := "foo" c := testCryptoNS() actual, err := c.Bcrypt(in) @@ -94,6 +116,10 @@ func TestRSAGenerateKey(t *testing.T) { } func TestRSACrypt(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow test") + } + c := testCryptoNS() key, err := c.RSAGenerateKey() assert.NoError(t, err) diff --git a/funcs/data.go b/funcs/data.go index 0a9c4a8a..a6e48ba3 100644 --- a/funcs/data.go +++ b/funcs/data.go @@ -2,24 +2,19 @@ package funcs import ( "context" - "sync" "github.com/hairyhenderson/gomplate/v3/conv" "github.com/hairyhenderson/gomplate/v3/data" ) -var ( - dataNS *DataFuncs - dataNSInit sync.Once -) - // DataNS - +// Deprecated: don't use func DataNS() *DataFuncs { - dataNSInit.Do(func() { dataNS = &DataFuncs{} }) - return dataNS + return &DataFuncs{} } // AddDataFuncs - +// Deprecated: use CreateDataFuncs instead func AddDataFuncs(f map[string]interface{}, d *data.Data) { for k, v := range CreateDataFuncs(context.Background(), d) { f[k] = v @@ -36,10 +31,9 @@ func CreateDataFuncs(ctx context.Context, d *data.Data) map[string]interface{} { f["defineDatasource"] = d.DefineDatasource f["include"] = d.Include - ns := DataNS() - ns.ctx = ctx + ns := &DataFuncs{ctx} - f["data"] = DataNS + f["data"] = func() interface{} { return ns } f["json"] = ns.JSON f["jsonArray"] = ns.JSONArray diff --git a/funcs/data_test.go b/funcs/data_test.go new file mode 100644 index 00000000..fee6494a --- /dev/null +++ b/funcs/data_test.go @@ -0,0 +1,24 @@ +package funcs + +import ( + "context" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCreateDataFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateDataFuncs(ctx, nil) + actual := fmap["data"].(func() interface{}) + + assert.Same(t, ctx, actual().(*DataFuncs).ctx) + }) + } +} diff --git a/funcs/doc.go b/funcs/doc.go index bf2d8f20..cb1a5f5c 100644 --- a/funcs/doc.go +++ b/funcs/doc.go @@ -5,12 +5,18 @@ functions to be used in 'text/template' templates. The different namespaces can be added individually: f := template.FuncMap{} - funcs.AddMathFuncs(f) - funcs.AddNetFuncs(f) + for k, v := range funcs.CreateMathFuncs(ctx) { + f[k] = v + } + for k, v := range funcs.CreateNetFuncs(ctx) { + f[k] = v + } Even though the functions are exported, these are not intended to be called programmatically by external consumers, but instead only to be used as template functions. +Deprecated: This package will be made internal in a future major version. + */ package funcs diff --git a/funcs/env.go b/funcs/env.go index 3188eb96..858290bc 100644 --- a/funcs/env.go +++ b/funcs/env.go @@ -2,24 +2,19 @@ package funcs import ( "context" - "sync" "github.com/hairyhenderson/gomplate/v3/conv" "github.com/hairyhenderson/gomplate/v3/env" ) -var ( - ef *EnvFuncs - efInit sync.Once -) - // EnvNS - the Env namespace +// Deprecated: don't use func EnvNS() *EnvFuncs { - efInit.Do(func() { ef = &EnvFuncs{} }) - return ef + return &EnvFuncs{} } // AddEnvFuncs - +// Deprecated: use CreateEnvFuncs instead func AddEnvFuncs(f map[string]interface{}) { for k, v := range CreateEnvFuncs(context.Background()) { f[k] = v @@ -28,10 +23,10 @@ func AddEnvFuncs(f map[string]interface{}) { // CreateEnvFuncs - func CreateEnvFuncs(ctx context.Context) map[string]interface{} { - ns := EnvNS() - ns.ctx = ctx + ns := &EnvFuncs{ctx} + return map[string]interface{}{ - "env": EnvNS, + "env": func() interface{} { return ns }, "getenv": ns.Getenv, } } @@ -42,11 +37,11 @@ type EnvFuncs struct { } // Getenv - -func (f *EnvFuncs) Getenv(key interface{}, def ...string) string { +func (EnvFuncs) Getenv(key interface{}, def ...string) string { return env.Getenv(conv.ToString(key), def...) } // ExpandEnv - -func (f *EnvFuncs) ExpandEnv(s interface{}) string { +func (EnvFuncs) ExpandEnv(s interface{}) string { return env.ExpandEnv(conv.ToString(s)) } diff --git a/funcs/env_test.go b/funcs/env_test.go index 95db230b..6e47d469 100644 --- a/funcs/env_test.go +++ b/funcs/env_test.go @@ -1,12 +1,29 @@ package funcs import ( + "context" "os" + "strconv" "testing" "github.com/stretchr/testify/assert" ) +func TestCreateEnvFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateEnvFuncs(ctx) + actual := fmap["env"].(func() interface{}) + + assert.Same(t, ctx, actual().(*EnvFuncs).ctx) + }) + } +} + func TestEnvGetenv(t *testing.T) { ef := &EnvFuncs{} expected := os.Getenv("USER") diff --git a/funcs/file.go b/funcs/file.go index 0f063b6e..c503bb04 100644 --- a/funcs/file.go +++ b/funcs/file.go @@ -3,25 +3,20 @@ package funcs import ( "context" "os" - "sync" "github.com/hairyhenderson/gomplate/v3/conv" "github.com/hairyhenderson/gomplate/v3/file" "github.com/spf13/afero" ) -var ( - ff *FileFuncs - ffInit sync.Once -) - // FileNS - the File namespace +// Deprecated: don't use func FileNS() *FileFuncs { - ffInit.Do(func() { ff = &FileFuncs{fs: afero.NewOsFs()} }) - return ff + return &FileFuncs{} } // AddFileFuncs - +// Deprecated: use CreateFileFuncs instead func AddFileFuncs(f map[string]interface{}) { for k, v := range CreateFileFuncs(context.Background()) { f[k] = v @@ -30,9 +25,13 @@ func AddFileFuncs(f map[string]interface{}) { // CreateFileFuncs - func CreateFileFuncs(ctx context.Context) map[string]interface{} { - ns := FileNS() - ns.ctx = ctx - return map[string]interface{}{"file": FileNS} + ns := &FileFuncs{ + ctx: ctx, + fs: afero.NewOsFs(), + } + return map[string]interface{}{ + "file": func() interface{} { return ns }, + } } // FileFuncs - diff --git a/funcs/file_test.go b/funcs/file_test.go index 1202854c..66c08781 100644 --- a/funcs/file_test.go +++ b/funcs/file_test.go @@ -1,13 +1,30 @@ package funcs import ( + "context" "path/filepath" + "strconv" "testing" "github.com/spf13/afero" "github.com/stretchr/testify/assert" ) +func TestCreateFileFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateFileFuncs(ctx) + actual := fmap["file"].(func() interface{}) + + assert.Same(t, ctx, actual().(*FileFuncs).ctx) + }) + } +} + func TestFileExists(t *testing.T) { fs := afero.NewMemMapFs() ff := &FileFuncs{fs: fs} diff --git a/funcs/filepath.go b/funcs/filepath.go index a86c4742..9c9d59c2 100644 --- a/funcs/filepath.go +++ b/funcs/filepath.go @@ -3,23 +3,18 @@ package funcs import ( "context" "path/filepath" - "sync" "github.com/hairyhenderson/gomplate/v3/conv" ) -var ( - fpf *FilePathFuncs - fpfInit sync.Once -) - // FilePathNS - the Path namespace +// Deprecated: don't use func FilePathNS() *FilePathFuncs { - fpfInit.Do(func() { fpf = &FilePathFuncs{} }) - return fpf + return &FilePathFuncs{} } // AddFilePathFuncs - +// Deprecated: use CreateFilePathFuncs instead func AddFilePathFuncs(f map[string]interface{}) { for k, v := range CreateFilePathFuncs(context.Background()) { f[k] = v @@ -28,9 +23,11 @@ func AddFilePathFuncs(f map[string]interface{}) { // CreateFilePathFuncs - func CreateFilePathFuncs(ctx context.Context) map[string]interface{} { - ns := FilePathNS() - ns.ctx = ctx - return map[string]interface{}{"filepath": FilePathNS} + ns := &FilePathFuncs{ctx} + + return map[string]interface{}{ + "filepath": func() interface{} { return ns }, + } } // FilePathFuncs - diff --git a/funcs/filepath_test.go b/funcs/filepath_test.go index 79b0a481..b2994cbd 100644 --- a/funcs/filepath_test.go +++ b/funcs/filepath_test.go @@ -1,30 +1,24 @@ -//+build !windows - package funcs import ( + "context" + "strconv" "testing" "github.com/stretchr/testify/assert" ) -func TestFilePathFuncs(t *testing.T) { - f := FilePathNS() - assert.Equal(t, "bar", f.Base("foo/bar")) - assert.Equal(t, "bar", f.Base("/foo/bar")) +func TestCreateFilePathFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateFilePathFuncs(ctx) + actual := fmap["filepath"].(func() interface{}) - assert.Equal(t, "/foo/baz", f.Clean("/foo/bar/../baz")) - assert.Equal(t, "foo", f.Dir("foo/bar")) - assert.Equal(t, ".txt", f.Ext("/foo/bar/baz.txt")) - assert.False(t, f.IsAbs("foo/bar")) - assert.True(t, f.IsAbs("/foo/bar")) - assert.Equal(t, "foo/bar/qux", f.Join("foo", "bar", "baz", "..", "qux")) - m, _ := f.Match("*.txt", "foo.json") - assert.False(t, m) - m, _ = f.Match("*.txt", "foo.txt") - assert.True(t, m) - r, _ := f.Rel("/foo/bar", "/foo/bar/baz") - assert.Equal(t, "baz", r) - assert.Equal(t, []string{"/foo/bar/", "baz"}, f.Split("/foo/bar/baz")) - assert.Equal(t, "", f.VolumeName("/foo/bar")) + assert.Same(t, ctx, actual().(*FilePathFuncs).ctx) + }) + } } diff --git a/funcs/filepath_unix_test.go b/funcs/filepath_unix_test.go new file mode 100644 index 00000000..71266642 --- /dev/null +++ b/funcs/filepath_unix_test.go @@ -0,0 +1,30 @@ +//+build !windows + +package funcs + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFilePathFuncs(t *testing.T) { + f := &FilePathFuncs{} + assert.Equal(t, "bar", f.Base("foo/bar")) + assert.Equal(t, "bar", f.Base("/foo/bar")) + + assert.Equal(t, "/foo/baz", f.Clean("/foo/bar/../baz")) + assert.Equal(t, "foo", f.Dir("foo/bar")) + assert.Equal(t, ".txt", f.Ext("/foo/bar/baz.txt")) + assert.False(t, f.IsAbs("foo/bar")) + assert.True(t, f.IsAbs("/foo/bar")) + assert.Equal(t, "foo/bar/qux", f.Join("foo", "bar", "baz", "..", "qux")) + m, _ := f.Match("*.txt", "foo.json") + assert.False(t, m) + m, _ = f.Match("*.txt", "foo.txt") + assert.True(t, m) + r, _ := f.Rel("/foo/bar", "/foo/bar/baz") + assert.Equal(t, "baz", r) + assert.Equal(t, []string{"/foo/bar/", "baz"}, f.Split("/foo/bar/baz")) + assert.Equal(t, "", f.VolumeName("/foo/bar")) +} diff --git a/funcs/filepath_windows_test.go b/funcs/filepath_windows_test.go index 26cba948..6bfc1cb0 100644 --- a/funcs/filepath_windows_test.go +++ b/funcs/filepath_windows_test.go @@ -9,7 +9,7 @@ import ( ) func TestFilePathFuncs(t *testing.T) { - f := FilePathNS() + f := &FilePathFuncs{} assert.Equal(t, "bar", f.Base(`foo\bar`)) assert.Equal(t, "bar", f.Base("C:/foo/bar")) assert.Equal(t, "bar", f.Base(`C:\foo\bar`)) diff --git a/funcs/gcp.go b/funcs/gcp.go index 8b479506..41c507b6 100644 --- a/funcs/gcp.go +++ b/funcs/gcp.go @@ -7,23 +7,14 @@ import ( "github.com/hairyhenderson/gomplate/v3/gcp" ) -var ( - gcpf *GcpFuncs - gcpfInit sync.Once -) - // GCPNS - the gcp namespace +// Deprecated: don't use func GCPNS() *GcpFuncs { - gcpfInit.Do(func() { - gcpf = &GcpFuncs{ - gcpopts: gcp.GetClientOptions(), - } - }) - return gcpf + return &GcpFuncs{gcpopts: gcp.GetClientOptions()} } // AddGCPFuncs - -// Deprecated: use CreateGCPFuncs +// Deprecated: use CreateGCPFuncs instead func AddGCPFuncs(f map[string]interface{}) { for k, v := range CreateGCPFuncs(context.Background()) { f[k] = v @@ -32,11 +23,13 @@ func AddGCPFuncs(f map[string]interface{}) { // CreateGCPFuncs - func CreateGCPFuncs(ctx context.Context) map[string]interface{} { - f := map[string]interface{}{} - ns := GCPNS() - ns.ctx = ctx - f["gcp"] = GCPNS - return f + ns := &GcpFuncs{ + ctx: ctx, + gcpopts: gcp.GetClientOptions(), + } + return map[string]interface{}{ + "gcp": func() interface{} { return ns }, + } } // GcpFuncs - diff --git a/funcs/gcp_test.go b/funcs/gcp_test.go new file mode 100644 index 00000000..b79af6d7 --- /dev/null +++ b/funcs/gcp_test.go @@ -0,0 +1,24 @@ +package funcs + +import ( + "context" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCreateGCPFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateGCPFuncs(ctx) + actual := fmap["gcp"].(func() interface{}) + + assert.Same(t, ctx, actual().(*GcpFuncs).ctx) + }) + } +} diff --git a/funcs/math.go b/funcs/math.go index b3ec70b8..890aa5b4 100644 --- a/funcs/math.go +++ b/funcs/math.go @@ -5,25 +5,20 @@ import ( "fmt" gmath "math" "strconv" - "sync" "github.com/hairyhenderson/gomplate/v3/conv" "github.com/hairyhenderson/gomplate/v3/math" ) -var ( - mathNS *MathFuncs - mathNSInit sync.Once -) - // MathNS - the math namespace +// Deprecated: don't use func MathNS() *MathFuncs { - mathNSInit.Do(func() { mathNS = &MathFuncs{} }) - return mathNS + return &MathFuncs{} } // AddMathFuncs - +// Deprecated: use CreateMathFuncs instead func AddMathFuncs(f map[string]interface{}) { for k, v := range CreateMathFuncs(context.Background()) { f[k] = v @@ -33,9 +28,9 @@ func AddMathFuncs(f map[string]interface{}) { // CreateMathFuncs - func CreateMathFuncs(ctx context.Context) map[string]interface{} { f := map[string]interface{}{} - ns := MathNS() - ns.ctx = ctx - f["math"] = MathNS + + ns := &MathFuncs{ctx} + f["math"] = func() interface{} { return ns } f["add"] = ns.Add f["sub"] = ns.Sub @@ -53,7 +48,7 @@ type MathFuncs struct { } // IsInt - -func (f *MathFuncs) IsInt(n interface{}) bool { +func (f MathFuncs) IsInt(n interface{}) bool { switch i := n.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: return true @@ -65,7 +60,7 @@ func (f *MathFuncs) IsInt(n interface{}) bool { } // IsFloat - -func (f *MathFuncs) IsFloat(n interface{}) bool { +func (f MathFuncs) IsFloat(n interface{}) bool { switch i := n.(type) { case float32, float64: return true @@ -82,7 +77,7 @@ func (f *MathFuncs) IsFloat(n interface{}) bool { return false } -func (f *MathFuncs) containsFloat(n ...interface{}) bool { +func (f MathFuncs) containsFloat(n ...interface{}) bool { c := false for _, v := range n { if f.IsFloat(v) { @@ -93,12 +88,12 @@ func (f *MathFuncs) containsFloat(n ...interface{}) bool { } // IsNum - -func (f *MathFuncs) IsNum(n interface{}) bool { +func (f MathFuncs) IsNum(n interface{}) bool { return f.IsInt(n) || f.IsFloat(n) } // Abs - -func (f *MathFuncs) Abs(n interface{}) interface{} { +func (f MathFuncs) Abs(n interface{}) interface{} { m := gmath.Abs(conv.ToFloat64(n)) if f.IsInt(n) { return conv.ToInt64(m) @@ -107,7 +102,7 @@ func (f *MathFuncs) Abs(n interface{}) interface{} { } // Add - -func (f *MathFuncs) Add(n ...interface{}) interface{} { +func (f MathFuncs) Add(n ...interface{}) interface{} { if f.containsFloat(n...) { nums := conv.ToFloat64s(n...) var x float64 @@ -125,7 +120,7 @@ func (f *MathFuncs) Add(n ...interface{}) interface{} { } // Mul - -func (f *MathFuncs) Mul(n ...interface{}) interface{} { +func (f MathFuncs) Mul(n ...interface{}) interface{} { if f.containsFloat(n...) { nums := conv.ToFloat64s(n...) x := 1. @@ -143,7 +138,7 @@ func (f *MathFuncs) Mul(n ...interface{}) interface{} { } // Sub - -func (f *MathFuncs) Sub(a, b interface{}) interface{} { +func (f MathFuncs) Sub(a, b interface{}) interface{} { if f.containsFloat(a, b) { return conv.ToFloat64(a) - conv.ToFloat64(b) } @@ -151,7 +146,7 @@ func (f *MathFuncs) Sub(a, b interface{}) interface{} { } // Div - -func (f *MathFuncs) Div(a, b interface{}) (interface{}, error) { +func (f MathFuncs) Div(a, b interface{}) (interface{}, error) { divisor := conv.ToFloat64(a) dividend := conv.ToFloat64(b) if dividend == 0 { @@ -161,12 +156,12 @@ func (f *MathFuncs) Div(a, b interface{}) (interface{}, error) { } // Rem - -func (f *MathFuncs) Rem(a, b interface{}) interface{} { +func (f MathFuncs) Rem(a, b interface{}) interface{} { return conv.ToInt64(a) % conv.ToInt64(b) } // Pow - -func (f *MathFuncs) Pow(a, b interface{}) interface{} { +func (f MathFuncs) Pow(a, b interface{}) interface{} { r := gmath.Pow(conv.ToFloat64(a), conv.ToFloat64(b)) if f.IsFloat(a) { return r @@ -176,7 +171,7 @@ func (f *MathFuncs) Pow(a, b interface{}) interface{} { // Seq - return a sequence from `start` to `end`, in steps of `step` // start and step are optional, and default to 1. -func (f *MathFuncs) Seq(n ...interface{}) ([]int64, error) { +func (f MathFuncs) Seq(n ...interface{}) ([]int64, error) { start := int64(1) end := int64(0) step := int64(1) @@ -199,7 +194,7 @@ func (f *MathFuncs) Seq(n ...interface{}) ([]int64, error) { } // Max - -func (f *MathFuncs) Max(a interface{}, b ...interface{}) (interface{}, error) { +func (f MathFuncs) Max(a interface{}, b ...interface{}) (interface{}, error) { if f.IsFloat(a) || f.containsFloat(b...) { m := conv.ToFloat64(a) for _, n := range conv.ToFloat64s(b...) { @@ -217,7 +212,7 @@ func (f *MathFuncs) Max(a interface{}, b ...interface{}) (interface{}, error) { } // Min - -func (f *MathFuncs) Min(a interface{}, b ...interface{}) (interface{}, error) { +func (f MathFuncs) Min(a interface{}, b ...interface{}) (interface{}, error) { if f.IsFloat(a) || f.containsFloat(b...) { m := conv.ToFloat64(a) for _, n := range conv.ToFloat64s(b...) { @@ -235,16 +230,16 @@ func (f *MathFuncs) Min(a interface{}, b ...interface{}) (interface{}, error) { } // Ceil - -func (f *MathFuncs) Ceil(n interface{}) interface{} { +func (f MathFuncs) Ceil(n interface{}) interface{} { return gmath.Ceil(conv.ToFloat64(n)) } // Floor - -func (f *MathFuncs) Floor(n interface{}) interface{} { +func (f MathFuncs) Floor(n interface{}) interface{} { return gmath.Floor(conv.ToFloat64(n)) } // Round - -func (f *MathFuncs) Round(n interface{}) interface{} { +func (f MathFuncs) Round(n interface{}) interface{} { return gmath.Round(conv.ToFloat64(n)) } diff --git a/funcs/math_test.go b/funcs/math_test.go index 7643ac79..ca0d6aff 100644 --- a/funcs/math_test.go +++ b/funcs/math_test.go @@ -1,15 +1,32 @@ package funcs import ( + "context" "fmt" gmath "math" + "strconv" "testing" "github.com/stretchr/testify/assert" ) +func TestCreateMathFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateMathFuncs(ctx) + actual := fmap["math"].(func() interface{}) + + assert.Same(t, ctx, actual().(*MathFuncs).ctx) + }) + } +} + func TestAdd(t *testing.T) { - m := MathNS() + m := MathFuncs{} assert.Equal(t, int64(12), m.Add(1, 1, 2, 3, 5)) assert.Equal(t, int64(2), m.Add(1, 1)) assert.Equal(t, int64(1), m.Add(1)) @@ -18,7 +35,7 @@ func TestAdd(t *testing.T) { } func TestMul(t *testing.T) { - m := MathNS() + m := MathFuncs{} assert.Equal(t, int64(30), m.Mul(1, 1, 2, 3, 5)) assert.Equal(t, int64(1), m.Mul(1, 1)) assert.Equal(t, int64(1), m.Mul(1)) @@ -28,7 +45,7 @@ func TestMul(t *testing.T) { } func TestSub(t *testing.T) { - m := MathNS() + m := MathFuncs{} assert.Equal(t, int64(0), m.Sub(1, 1)) assert.Equal(t, int64(-10), m.Sub(-5, 5)) assert.Equal(t, int64(-41), m.Sub(true, "42")) @@ -36,7 +53,7 @@ func TestSub(t *testing.T) { } func mustDiv(a, b interface{}) interface{} { - m := MathNS() + m := MathFuncs{} r, err := m.Div(a, b) if err != nil { return -1 @@ -45,7 +62,7 @@ func mustDiv(a, b interface{}) interface{} { } func TestDiv(t *testing.T) { - m := MathNS() + m := MathFuncs{} _, err := m.Div(1, 0) assert.Error(t, err) assert.Equal(t, 1., mustDiv(1, 1)) @@ -55,19 +72,19 @@ func TestDiv(t *testing.T) { } func TestRem(t *testing.T) { - m := MathNS() + m := MathFuncs{} assert.Equal(t, int64(0), m.Rem(1, 1)) assert.Equal(t, int64(2), m.Rem(5, 3.0)) } func TestPow(t *testing.T) { - m := MathNS() + m := MathFuncs{} assert.Equal(t, int64(4), m.Pow(2, "2")) assert.Equal(t, 2.25, m.Pow(1.5, 2)) } func mustSeq(n ...interface{}) []int64 { - m := MathNS() + m := MathFuncs{} s, err := m.Seq(n...) if err != nil { panic(err) @@ -75,7 +92,7 @@ func mustSeq(n ...interface{}) []int64 { return s } func TestSeq(t *testing.T) { - m := MathNS() + m := MathFuncs{} assert.EqualValues(t, []int64{0, 1, 2, 3}, mustSeq(0, 3)) assert.EqualValues(t, []int64{1, 0}, mustSeq(0)) assert.EqualValues(t, []int64{0, 2, 4}, mustSeq(0, 4, 2)) @@ -124,7 +141,7 @@ func TestIsIntFloatNum(t *testing.T) { {nil, false, false}, {true, false, false}, } - m := MathNS() + m := MathFuncs{} for _, tt := range tests { tt := tt t.Run(fmt.Sprintf("%T(%#v)", tt.in, tt.in), func(t *testing.T) { @@ -139,7 +156,7 @@ func BenchmarkIsFloat(b *testing.B) { data := []interface{}{ 0, 1, -1, uint(42), uint8(255), uint16(42), uint32(42), uint64(42), int(42), int8(127), int16(42), int32(42), int64(42), float32(18.3), float64(18.3), 1.5, -18.6, "42", "052", "0xff", "-42", "-0", "3.14", "-3.14", "0.00", "NaN", "-Inf", "+Inf", "", "foo", nil, true, } - m := MathNS() + m := MathFuncs{} for _, n := range data { n := n b.Run(fmt.Sprintf("%T(%v)", n, n), func(b *testing.B) { @@ -151,7 +168,7 @@ func BenchmarkIsFloat(b *testing.B) { } func TestMax(t *testing.T) { - m := MathNS() + m := MathFuncs{} data := []struct { expected interface{} n []interface{} @@ -180,7 +197,7 @@ func TestMax(t *testing.T) { } func TestMin(t *testing.T) { - m := MathNS() + m := MathFuncs{} data := []struct { expected interface{} n []interface{} @@ -209,7 +226,7 @@ func TestMin(t *testing.T) { } func TestContainsFloat(t *testing.T) { - m := MathNS() + m := MathFuncs{} data := []struct { n []interface{} expected bool @@ -239,7 +256,7 @@ func TestContainsFloat(t *testing.T) { } func TestCeil(t *testing.T) { - m := MathNS() + m := MathFuncs{} data := []struct { n interface{} a float64 @@ -261,7 +278,7 @@ func TestCeil(t *testing.T) { } func TestFloor(t *testing.T) { - m := MathNS() + m := MathFuncs{} data := []struct { n interface{} a float64 @@ -283,7 +300,7 @@ func TestFloor(t *testing.T) { } func TestRound(t *testing.T) { - m := MathNS() + m := MathFuncs{} data := []struct { n interface{} a float64 @@ -309,7 +326,7 @@ func TestRound(t *testing.T) { } func TestAbs(t *testing.T) { - m := MathNS() + m := MathFuncs{} data := []struct { n interface{} a interface{} diff --git a/funcs/net.go b/funcs/net.go index 922289b7..73481fe2 100644 --- a/funcs/net.go +++ b/funcs/net.go @@ -3,34 +3,32 @@ package funcs import ( "context" stdnet "net" - "sync" "github.com/hairyhenderson/gomplate/v3/conv" "github.com/hairyhenderson/gomplate/v3/net" ) -var ( - netNS *NetFuncs - netNSInit sync.Once -) - // NetNS - the net namespace +// Deprecated: don't use func NetNS() *NetFuncs { - netNSInit.Do(func() { netNS = &NetFuncs{} }) - return netNS + return &NetFuncs{} } // AddNetFuncs - +// Deprecated: use CreateNetFuncs instead func AddNetFuncs(f map[string]interface{}) { - f["net"] = NetNS + for k, v := range CreateNetFuncs(context.Background()) { + f[k] = v + } } // CreateNetFuncs - func CreateNetFuncs(ctx context.Context) map[string]interface{} { - ns := NetNS() - ns.ctx = ctx - return map[string]interface{}{"net": NetNS} + ns := &NetFuncs{ctx} + return map[string]interface{}{ + "net": func() interface{} { return ns }, + } } // NetFuncs - @@ -39,31 +37,31 @@ type NetFuncs struct { } // LookupIP - -func (f *NetFuncs) LookupIP(name interface{}) (string, error) { +func (f NetFuncs) LookupIP(name interface{}) (string, error) { return net.LookupIP(conv.ToString(name)) } // LookupIPs - -func (f *NetFuncs) LookupIPs(name interface{}) ([]string, error) { +func (f NetFuncs) LookupIPs(name interface{}) ([]string, error) { return net.LookupIPs(conv.ToString(name)) } // LookupCNAME - -func (f *NetFuncs) LookupCNAME(name interface{}) (string, error) { +func (f NetFuncs) LookupCNAME(name interface{}) (string, error) { return net.LookupCNAME(conv.ToString(name)) } // LookupSRV - -func (f *NetFuncs) LookupSRV(name interface{}) (*stdnet.SRV, error) { +func (f NetFuncs) LookupSRV(name interface{}) (*stdnet.SRV, error) { return net.LookupSRV(conv.ToString(name)) } // LookupSRVs - -func (f *NetFuncs) LookupSRVs(name interface{}) ([]*stdnet.SRV, error) { +func (f NetFuncs) LookupSRVs(name interface{}) ([]*stdnet.SRV, error) { return net.LookupSRVs(conv.ToString(name)) } // LookupTXT - -func (f *NetFuncs) LookupTXT(name interface{}) ([]string, error) { +func (f NetFuncs) LookupTXT(name interface{}) ([]string, error) { return net.LookupTXT(conv.ToString(name)) } diff --git a/funcs/net_test.go b/funcs/net_test.go index 2a4eb951..54f032ae 100644 --- a/funcs/net_test.go +++ b/funcs/net_test.go @@ -1,12 +1,29 @@ package funcs import ( + "context" + "strconv" "testing" "github.com/stretchr/testify/assert" ) +func TestCreateNetFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateNetFuncs(ctx) + actual := fmap["net"].(func() interface{}) + + assert.Same(t, ctx, actual().(*NetFuncs).ctx) + }) + } +} + func TestNetLookupIP(t *testing.T) { - n := &NetFuncs{} + n := NetFuncs{} assert.Equal(t, "127.0.0.1", must(n.LookupIP("localhost"))) } diff --git a/funcs/path.go b/funcs/path.go index 419114cf..556eb7e6 100644 --- a/funcs/path.go +++ b/funcs/path.go @@ -3,23 +3,18 @@ package funcs import ( "context" "path" - "sync" "github.com/hairyhenderson/gomplate/v3/conv" ) -var ( - pf *PathFuncs - pfInit sync.Once -) - // PathNS - the Path namespace +// Deprecated: don't use func PathNS() *PathFuncs { - pfInit.Do(func() { pf = &PathFuncs{} }) - return pf + return &PathFuncs{} } // AddPathFuncs - +// Deprecated: use CreatePathFuncs instead func AddPathFuncs(f map[string]interface{}) { for k, v := range CreatePathFuncs(context.Background()) { f[k] = v @@ -28,9 +23,10 @@ func AddPathFuncs(f map[string]interface{}) { // CreatePathFuncs - func CreatePathFuncs(ctx context.Context) map[string]interface{} { - ns := PathNS() - ns.ctx = ctx - return map[string]interface{}{"path": PathNS} + ns := &PathFuncs{ctx} + return map[string]interface{}{ + "path": func() interface{} { return ns }, + } } // PathFuncs - @@ -39,43 +35,43 @@ type PathFuncs struct { } // Base - -func (f *PathFuncs) Base(in interface{}) string { +func (PathFuncs) Base(in interface{}) string { return path.Base(conv.ToString(in)) } // Clean - -func (f *PathFuncs) Clean(in interface{}) string { +func (PathFuncs) Clean(in interface{}) string { return path.Clean(conv.ToString(in)) } // Dir - -func (f *PathFuncs) Dir(in interface{}) string { +func (PathFuncs) Dir(in interface{}) string { return path.Dir(conv.ToString(in)) } // Ext - -func (f *PathFuncs) Ext(in interface{}) string { +func (PathFuncs) Ext(in interface{}) string { return path.Ext(conv.ToString(in)) } // IsAbs - -func (f *PathFuncs) IsAbs(in interface{}) bool { +func (PathFuncs) IsAbs(in interface{}) bool { return path.IsAbs(conv.ToString(in)) } // Join - -func (f *PathFuncs) Join(elem ...interface{}) string { +func (PathFuncs) Join(elem ...interface{}) string { s := conv.ToStrings(elem...) return path.Join(s...) } // Match - -func (f *PathFuncs) Match(pattern, name interface{}) (matched bool, err error) { +func (PathFuncs) Match(pattern, name interface{}) (matched bool, err error) { return path.Match(conv.ToString(pattern), conv.ToString(name)) } // Split - -func (f *PathFuncs) Split(in interface{}) []string { +func (PathFuncs) Split(in interface{}) []string { dir, file := path.Split(conv.ToString(in)) return []string{dir, file} } diff --git a/funcs/path_test.go b/funcs/path_test.go index 0ccfc648..62fec664 100644 --- a/funcs/path_test.go +++ b/funcs/path_test.go @@ -1,13 +1,30 @@ package funcs import ( + "context" + "strconv" "testing" "github.com/stretchr/testify/assert" ) +func TestCreatePathFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreatePathFuncs(ctx) + actual := fmap["path"].(func() interface{}) + + assert.Same(t, ctx, actual().(*PathFuncs).ctx) + }) + } +} + func TestPathFuncs(t *testing.T) { - p := PathNS() + p := PathFuncs{} assert.Equal(t, "bar", p.Base("foo/bar")) assert.Equal(t, "bar", p.Base("/foo/bar")) diff --git a/funcs/random.go b/funcs/random.go index 51d0ce7e..6c571bf2 100644 --- a/funcs/random.go +++ b/funcs/random.go @@ -3,7 +3,6 @@ package funcs import ( "context" "strconv" - "sync" "unicode/utf8" "github.com/hairyhenderson/gomplate/v3/conv" @@ -12,18 +11,14 @@ import ( "github.com/pkg/errors" ) -var ( - randomNS *RandomFuncs - randomNSInit sync.Once -) - // RandomNS - +// Deprecated: don't use func RandomNS() *RandomFuncs { - randomNSInit.Do(func() { randomNS = &RandomFuncs{} }) - return randomNS + return &RandomFuncs{} } // AddRandomFuncs - +// Deprecated: use CreateRandomFuncs instead func AddRandomFuncs(f map[string]interface{}) { for k, v := range CreateRandomFuncs(context.Background()) { f[k] = v @@ -32,9 +27,10 @@ func AddRandomFuncs(f map[string]interface{}) { // CreateRandomFuncs - func CreateRandomFuncs(ctx context.Context) map[string]interface{} { - ns := RandomNS() - ns.ctx = ctx - return map[string]interface{}{"random": RandomNS} + ns := &RandomFuncs{ctx} + return map[string]interface{}{ + "random": func() interface{} { return ns }, + } } // RandomFuncs - @@ -43,22 +39,22 @@ type RandomFuncs struct { } // ASCII - -func (f *RandomFuncs) ASCII(count interface{}) (string, error) { +func (RandomFuncs) ASCII(count interface{}) (string, error) { return random.StringBounds(conv.ToInt(count), ' ', '~') } // Alpha - -func (f *RandomFuncs) Alpha(count interface{}) (string, error) { +func (RandomFuncs) Alpha(count interface{}) (string, error) { return random.StringRE(conv.ToInt(count), "[[:alpha:]]") } // AlphaNum - -func (f *RandomFuncs) AlphaNum(count interface{}) (string, error) { +func (RandomFuncs) AlphaNum(count interface{}) (string, error) { return random.StringRE(conv.ToInt(count), "[[:alnum:]]") } // String - -func (f *RandomFuncs) String(count interface{}, args ...interface{}) (s string, err error) { +func (RandomFuncs) String(count interface{}, args ...interface{}) (s string, err error) { c := conv.ToInt(count) if c == 0 { return "", errors.New("count must be greater than 0") @@ -120,7 +116,7 @@ func toCodePoints(l, u string) (rune, rune, error) { } // Item - -func (f *RandomFuncs) Item(items interface{}) (interface{}, error) { +func (RandomFuncs) Item(items interface{}) (interface{}, error) { i, err := iconv.InterfaceSlice(items) if err != nil { return nil, err @@ -129,7 +125,7 @@ func (f *RandomFuncs) Item(items interface{}) (interface{}, error) { } // Number - -func (f *RandomFuncs) Number(args ...interface{}) (int64, error) { +func (RandomFuncs) Number(args ...interface{}) (int64, error) { var min, max int64 min, max = 0, 100 switch len(args) { @@ -144,7 +140,7 @@ func (f *RandomFuncs) Number(args ...interface{}) (int64, error) { } // Float - -func (f *RandomFuncs) Float(args ...interface{}) (float64, error) { +func (RandomFuncs) Float(args ...interface{}) (float64, error) { var min, max float64 min, max = 0, 1.0 switch len(args) { diff --git a/funcs/random_test.go b/funcs/random_test.go index 6466c11a..f2a3ac2d 100644 --- a/funcs/random_test.go +++ b/funcs/random_test.go @@ -1,14 +1,31 @@ package funcs import ( + "context" + "strconv" "testing" "unicode/utf8" "github.com/stretchr/testify/assert" ) +func TestCreateRandomFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateRandomFuncs(ctx) + actual := fmap["random"].(func() interface{}) + + assert.Same(t, ctx, actual().(*RandomFuncs).ctx) + }) + } +} + func TestASCII(t *testing.T) { - f := &RandomFuncs{} + f := RandomFuncs{} s, err := f.ASCII(0) assert.NoError(t, err) assert.Empty(t, s) @@ -20,7 +37,11 @@ func TestASCII(t *testing.T) { } func TestAlpha(t *testing.T) { - f := &RandomFuncs{} + if testing.Short() { + t.Skip("skipping slow test") + } + + f := RandomFuncs{} s, err := f.Alpha(0) assert.NoError(t, err) assert.Empty(t, s) @@ -32,7 +53,11 @@ func TestAlpha(t *testing.T) { } func TestAlphaNum(t *testing.T) { - f := &RandomFuncs{} + if testing.Short() { + t.Skip("skipping slow test") + } + + f := RandomFuncs{} s, err := f.AlphaNum(0) assert.NoError(t, err) assert.Empty(t, s) @@ -72,7 +97,11 @@ func TestToCodePoints(t *testing.T) { } func TestString(t *testing.T) { - f := &RandomFuncs{} + if testing.Short() { + t.Skip("skipping slow test") + } + + f := RandomFuncs{} out, err := f.String(1) assert.NoError(t, err) assert.Len(t, out, 1) @@ -111,7 +140,7 @@ func TestString(t *testing.T) { } func TestItem(t *testing.T) { - f := &RandomFuncs{} + f := RandomFuncs{} _, err := f.Item(nil) assert.Error(t, err) @@ -134,7 +163,7 @@ func TestItem(t *testing.T) { } func TestNumber(t *testing.T) { - f := &RandomFuncs{} + f := RandomFuncs{} n, err := f.Number() assert.NoError(t, err) assert.True(t, 0 <= n && n <= 100, n) @@ -156,7 +185,7 @@ func TestNumber(t *testing.T) { } func TestFloat(t *testing.T) { - f := &RandomFuncs{} + f := RandomFuncs{} n, err := f.Float() assert.NoError(t, err) assert.InDelta(t, 0.5, n, 0.5) diff --git a/funcs/regexp.go b/funcs/regexp.go index 5aa1bdfb..6fb4e690 100644 --- a/funcs/regexp.go +++ b/funcs/regexp.go @@ -2,7 +2,6 @@ package funcs import ( "context" - "sync" "github.com/pkg/errors" @@ -10,18 +9,14 @@ import ( "github.com/hairyhenderson/gomplate/v3/regexp" ) -var ( - reNS *ReFuncs - reNSInit sync.Once -) - // ReNS - +// Deprecated: don't use func ReNS() *ReFuncs { - reNSInit.Do(func() { reNS = &ReFuncs{} }) - return reNS + return &ReFuncs{} } // AddReFuncs - +// Deprecated: use CreateReFuncs instead func AddReFuncs(f map[string]interface{}) { for k, v := range CreateReFuncs(context.Background()) { f[k] = v @@ -30,9 +25,10 @@ func AddReFuncs(f map[string]interface{}) { // CreateReFuncs - func CreateReFuncs(ctx context.Context) map[string]interface{} { - ns := ReNS() - ns.ctx = ctx - return map[string]interface{}{"regexp": ReNS} + ns := &ReFuncs{ctx} + return map[string]interface{}{ + "regexp": func() interface{} { return ns }, + } } // ReFuncs - @@ -41,12 +37,12 @@ type ReFuncs struct { } // Find - -func (f *ReFuncs) Find(re, input interface{}) (string, error) { +func (ReFuncs) Find(re, input interface{}) (string, error) { return regexp.Find(conv.ToString(re), conv.ToString(input)) } // FindAll - -func (f *ReFuncs) FindAll(args ...interface{}) ([]string, error) { +func (ReFuncs) FindAll(args ...interface{}) ([]string, error) { re := "" n := 0 input := "" @@ -66,31 +62,31 @@ func (f *ReFuncs) FindAll(args ...interface{}) ([]string, error) { } // Match - -func (f *ReFuncs) Match(re, input interface{}) bool { +func (ReFuncs) Match(re, input interface{}) bool { return regexp.Match(conv.ToString(re), conv.ToString(input)) } // QuoteMeta - -func (f *ReFuncs) QuoteMeta(in interface{}) string { +func (ReFuncs) QuoteMeta(in interface{}) string { return regexp.QuoteMeta(conv.ToString(in)) } // Replace - -func (f *ReFuncs) Replace(re, replacement, input interface{}) string { +func (ReFuncs) Replace(re, replacement, input interface{}) string { return regexp.Replace(conv.ToString(re), conv.ToString(replacement), conv.ToString(input)) } // ReplaceLiteral - -func (f *ReFuncs) ReplaceLiteral(re, replacement, input interface{}) (string, error) { +func (ReFuncs) ReplaceLiteral(re, replacement, input interface{}) (string, error) { return regexp.ReplaceLiteral(conv.ToString(re), conv.ToString(replacement), conv.ToString(input)) } // Split - -func (f *ReFuncs) Split(args ...interface{}) ([]string, error) { +func (ReFuncs) Split(args ...interface{}) ([]string, error) { re := "" n := -1 input := "" diff --git a/funcs/regexp_test.go b/funcs/regexp_test.go index 905c4146..869cbc9f 100644 --- a/funcs/regexp_test.go +++ b/funcs/regexp_test.go @@ -1,11 +1,28 @@ package funcs import ( + "context" + "strconv" "testing" "github.com/stretchr/testify/assert" ) +func TestCreateReFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateReFuncs(ctx) + actual := fmap["regexp"].(func() interface{}) + + assert.Same(t, ctx, actual().(*ReFuncs).ctx) + }) + } +} + func TestReplace(t *testing.T) { re := &ReFuncs{} assert.Equal(t, "hello world", re.Replace("i", "ello", "hi world")) diff --git a/funcs/sockaddr.go b/funcs/sockaddr.go index f1bb4561..c4554a77 100644 --- a/funcs/sockaddr.go +++ b/funcs/sockaddr.go @@ -2,33 +2,29 @@ package funcs import ( "context" - "sync" "github.com/hashicorp/go-sockaddr" "github.com/hashicorp/go-sockaddr/template" ) -var ( - sockaddrNS *SockaddrFuncs - sockaddrNSInit sync.Once -) - // SockaddrNS - the sockaddr namespace +// Deprecated: don't use func SockaddrNS() *SockaddrFuncs { - sockaddrNSInit.Do(func() { sockaddrNS = &SockaddrFuncs{} }) - return sockaddrNS + return &SockaddrFuncs{} } // AddSockaddrFuncs - +// Deprecated: use CreateSockaddrFuncs instead func AddSockaddrFuncs(f map[string]interface{}) { f["sockaddr"] = SockaddrNS } // CreateSockaddrFuncs - func CreateSockaddrFuncs(ctx context.Context) map[string]interface{} { - ns := SockaddrNS() - ns.ctx = ctx - return map[string]interface{}{"sockaddr": SockaddrNS} + ns := &SockaddrFuncs{ctx} + return map[string]interface{}{ + "sockaddr": func() interface{} { return ns }, + } } // SockaddrFuncs - @@ -37,96 +33,96 @@ type SockaddrFuncs struct { } // GetAllInterfaces - -func (f *SockaddrFuncs) GetAllInterfaces() (sockaddr.IfAddrs, error) { +func (SockaddrFuncs) GetAllInterfaces() (sockaddr.IfAddrs, error) { return sockaddr.GetAllInterfaces() } // GetDefaultInterfaces - -func (f *SockaddrFuncs) GetDefaultInterfaces() (sockaddr.IfAddrs, error) { +func (SockaddrFuncs) GetDefaultInterfaces() (sockaddr.IfAddrs, error) { return sockaddr.GetDefaultInterfaces() } // GetPrivateInterfaces - -func (f *SockaddrFuncs) GetPrivateInterfaces() (sockaddr.IfAddrs, error) { +func (SockaddrFuncs) GetPrivateInterfaces() (sockaddr.IfAddrs, error) { return sockaddr.GetPrivateInterfaces() } // GetPublicInterfaces - -func (f *SockaddrFuncs) GetPublicInterfaces() (sockaddr.IfAddrs, error) { +func (SockaddrFuncs) GetPublicInterfaces() (sockaddr.IfAddrs, error) { return sockaddr.GetPublicInterfaces() } // Sort - -func (f *SockaddrFuncs) Sort(selectorParam string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { +func (SockaddrFuncs) Sort(selectorParam string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { return sockaddr.SortIfBy(selectorParam, inputIfAddrs) } // Exclude - -func (f *SockaddrFuncs) Exclude(selectorName, selectorParam string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { +func (SockaddrFuncs) Exclude(selectorName, selectorParam string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { return sockaddr.ExcludeIfs(selectorName, selectorParam, inputIfAddrs) } // Include - -func (f *SockaddrFuncs) Include(selectorName, selectorParam string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { +func (SockaddrFuncs) Include(selectorName, selectorParam string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { return sockaddr.IncludeIfs(selectorName, selectorParam, inputIfAddrs) } // Attr - -func (f *SockaddrFuncs) Attr(selectorName string, ifAddrsRaw interface{}) (string, error) { +func (SockaddrFuncs) Attr(selectorName string, ifAddrsRaw interface{}) (string, error) { return template.Attr(selectorName, ifAddrsRaw) } // Join - -func (f *SockaddrFuncs) Join(selectorName, joinString string, inputIfAddrs sockaddr.IfAddrs) (string, error) { +func (SockaddrFuncs) Join(selectorName, joinString string, inputIfAddrs sockaddr.IfAddrs) (string, error) { return sockaddr.JoinIfAddrs(selectorName, joinString, inputIfAddrs) } // Limit - -func (f *SockaddrFuncs) Limit(lim uint, in sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { +func (SockaddrFuncs) Limit(lim uint, in sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { return sockaddr.LimitIfAddrs(lim, in) } // Offset - -func (f *SockaddrFuncs) Offset(off int, in sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { +func (SockaddrFuncs) Offset(off int, in sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { return sockaddr.OffsetIfAddrs(off, in) } // Unique - -func (f *SockaddrFuncs) Unique(selectorName string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { +func (SockaddrFuncs) Unique(selectorName string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { return sockaddr.UniqueIfAddrsBy(selectorName, inputIfAddrs) } // Math - -func (f *SockaddrFuncs) Math(operation, value string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { +func (SockaddrFuncs) Math(operation, value string, inputIfAddrs sockaddr.IfAddrs) (sockaddr.IfAddrs, error) { return sockaddr.IfAddrsMath(operation, value, inputIfAddrs) } // GetPrivateIP - -func (f *SockaddrFuncs) GetPrivateIP() (string, error) { +func (SockaddrFuncs) GetPrivateIP() (string, error) { return sockaddr.GetPrivateIP() } // GetPrivateIPs - -func (f *SockaddrFuncs) GetPrivateIPs() (string, error) { +func (SockaddrFuncs) GetPrivateIPs() (string, error) { return sockaddr.GetPrivateIPs() } // GetPublicIP - -func (f *SockaddrFuncs) GetPublicIP() (string, error) { +func (SockaddrFuncs) GetPublicIP() (string, error) { return sockaddr.GetPublicIP() } // GetPublicIPs - -func (f *SockaddrFuncs) GetPublicIPs() (string, error) { +func (SockaddrFuncs) GetPublicIPs() (string, error) { return sockaddr.GetPublicIPs() } // GetInterfaceIP - -func (f *SockaddrFuncs) GetInterfaceIP(namedIfRE string) (string, error) { +func (SockaddrFuncs) GetInterfaceIP(namedIfRE string) (string, error) { return sockaddr.GetInterfaceIP(namedIfRE) } // GetInterfaceIPs - -func (f *SockaddrFuncs) GetInterfaceIPs(namedIfRE string) (string, error) { +func (SockaddrFuncs) GetInterfaceIPs(namedIfRE string) (string, error) { return sockaddr.GetInterfaceIPs(namedIfRE) } diff --git a/funcs/sockaddr_test.go b/funcs/sockaddr_test.go new file mode 100644 index 00000000..b50228f7 --- /dev/null +++ b/funcs/sockaddr_test.go @@ -0,0 +1,24 @@ +package funcs + +import ( + "context" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCreateSockaddrFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateSockaddrFuncs(ctx) + actual := fmap["sockaddr"].(func() interface{}) + + assert.Same(t, ctx, actual().(*SockaddrFuncs).ctx) + }) + } +} diff --git a/funcs/strings.go b/funcs/strings.go index 7a18c96e..2e0582c2 100644 --- a/funcs/strings.go +++ b/funcs/strings.go @@ -9,7 +9,6 @@ import ( "context" "fmt" "reflect" - "sync" "unicode/utf8" "github.com/Masterminds/goutils" @@ -22,18 +21,14 @@ import ( gompstrings "github.com/hairyhenderson/gomplate/v3/strings" ) -var ( - strNS *StringFuncs - strNSInit sync.Once -) - // StrNS - +// Deprecated: don't use func StrNS() *StringFuncs { - strNSInit.Do(func() { strNS = &StringFuncs{} }) - return strNS + return &StringFuncs{} } // AddStringFuncs - +// Deprecated: use CreateStringFuncs instead func AddStringFuncs(f map[string]interface{}) { for k, v := range CreateStringFuncs(context.Background()) { f[k] = v @@ -43,10 +38,9 @@ func AddStringFuncs(f map[string]interface{}) { // CreateStringFuncs - func CreateStringFuncs(ctx context.Context) map[string]interface{} { f := map[string]interface{}{} - ns := StrNS() - ns.ctx = ctx - f["strings"] = StrNS + ns := &StringFuncs{ctx} + f["strings"] = func() interface{} { return ns } f["replaceAll"] = ns.ReplaceAll f["title"] = ns.Title @@ -75,7 +69,7 @@ type StringFuncs struct { } // Abbrev - -func (f *StringFuncs) Abbrev(args ...interface{}) (string, error) { +func (StringFuncs) Abbrev(args ...interface{}) (string, error) { str := "" offset := 0 maxWidth := 0 @@ -98,27 +92,27 @@ func (f *StringFuncs) Abbrev(args ...interface{}) (string, error) { } // ReplaceAll - -func (f *StringFuncs) ReplaceAll(old, new string, s interface{}) string { +func (StringFuncs) ReplaceAll(old, new string, s interface{}) string { return strings.ReplaceAll(conv.ToString(s), old, new) } // Contains - -func (f *StringFuncs) Contains(substr string, s interface{}) bool { +func (StringFuncs) Contains(substr string, s interface{}) bool { return strings.Contains(conv.ToString(s), substr) } // HasPrefix - -func (f *StringFuncs) HasPrefix(prefix string, s interface{}) bool { +func (StringFuncs) HasPrefix(prefix string, s interface{}) bool { return strings.HasPrefix(conv.ToString(s), prefix) } // HasSuffix - -func (f *StringFuncs) HasSuffix(suffix string, s interface{}) bool { +func (StringFuncs) HasSuffix(suffix string, s interface{}) bool { return strings.HasSuffix(conv.ToString(s), suffix) } // Repeat - -func (f *StringFuncs) Repeat(count int, s interface{}) (string, error) { +func (StringFuncs) Repeat(count int, s interface{}) (string, error) { if count < 0 { return "", errors.Errorf("negative count %d", count) } @@ -132,7 +126,7 @@ func (f *StringFuncs) Repeat(count int, s interface{}) (string, error) { // Sort - // // Deprecated: use coll.Sort instead -func (f *StringFuncs) Sort(list interface{}) ([]string, error) { +func (StringFuncs) Sort(list interface{}) ([]string, error) { switch v := list.(type) { case []string: return gompstrings.Sort(v), nil @@ -149,57 +143,57 @@ func (f *StringFuncs) Sort(list interface{}) ([]string, error) { } // Split - -func (f *StringFuncs) Split(sep string, s interface{}) []string { +func (StringFuncs) Split(sep string, s interface{}) []string { return strings.Split(conv.ToString(s), sep) } // SplitN - -func (f *StringFuncs) SplitN(sep string, n int, s interface{}) []string { +func (StringFuncs) SplitN(sep string, n int, s interface{}) []string { return strings.SplitN(conv.ToString(s), sep, n) } // Trim - -func (f *StringFuncs) Trim(cutset string, s interface{}) string { +func (StringFuncs) Trim(cutset string, s interface{}) string { return strings.Trim(conv.ToString(s), cutset) } // TrimPrefix - -func (f *StringFuncs) TrimPrefix(cutset string, s interface{}) string { +func (StringFuncs) TrimPrefix(cutset string, s interface{}) string { return strings.TrimPrefix(conv.ToString(s), cutset) } // TrimSuffix - -func (f *StringFuncs) TrimSuffix(cutset string, s interface{}) string { +func (StringFuncs) TrimSuffix(cutset string, s interface{}) string { return strings.TrimSuffix(conv.ToString(s), cutset) } // Title - -func (f *StringFuncs) Title(s interface{}) string { +func (StringFuncs) Title(s interface{}) string { return strings.Title(conv.ToString(s)) } // ToUpper - -func (f *StringFuncs) ToUpper(s interface{}) string { +func (StringFuncs) ToUpper(s interface{}) string { return strings.ToUpper(conv.ToString(s)) } // ToLower - -func (f *StringFuncs) ToLower(s interface{}) string { +func (StringFuncs) ToLower(s interface{}) string { return strings.ToLower(conv.ToString(s)) } // TrimSpace - -func (f *StringFuncs) TrimSpace(s interface{}) string { +func (StringFuncs) TrimSpace(s interface{}) string { return strings.TrimSpace(conv.ToString(s)) } // Trunc - -func (f *StringFuncs) Trunc(length int, s interface{}) string { +func (StringFuncs) Trunc(length int, s interface{}) string { return gompstrings.Trunc(length, conv.ToString(s)) } // Indent - -func (f *StringFuncs) Indent(args ...interface{}) (string, error) { +func (StringFuncs) Indent(args ...interface{}) (string, error) { input := conv.ToString(args[len(args)-1]) indent := " " width := 1 @@ -228,17 +222,17 @@ func (f *StringFuncs) Indent(args ...interface{}) (string, error) { } // Slug - -func (f *StringFuncs) Slug(in interface{}) string { +func (StringFuncs) Slug(in interface{}) string { return slug.Make(conv.ToString(in)) } // Quote - -func (f *StringFuncs) Quote(in interface{}) string { +func (StringFuncs) Quote(in interface{}) string { return fmt.Sprintf("%q", conv.ToString(in)) } // ShellQuote - -func (f *StringFuncs) ShellQuote(in interface{}) string { +func (StringFuncs) ShellQuote(in interface{}) string { val := reflect.ValueOf(in) switch val.Kind() { case reflect.Array, reflect.Slice: @@ -256,29 +250,29 @@ func (f *StringFuncs) ShellQuote(in interface{}) string { } // Squote - -func (f *StringFuncs) Squote(in interface{}) string { +func (StringFuncs) Squote(in interface{}) string { s := conv.ToString(in) s = strings.ReplaceAll(s, `'`, `''`) return fmt.Sprintf("'%s'", s) } // SnakeCase - -func (f *StringFuncs) SnakeCase(in interface{}) (string, error) { +func (StringFuncs) SnakeCase(in interface{}) (string, error) { return gompstrings.SnakeCase(conv.ToString(in)), nil } // CamelCase - -func (f *StringFuncs) CamelCase(in interface{}) (string, error) { +func (StringFuncs) CamelCase(in interface{}) (string, error) { return gompstrings.CamelCase(conv.ToString(in)), nil } // KebabCase - -func (f *StringFuncs) KebabCase(in interface{}) (string, error) { +func (StringFuncs) KebabCase(in interface{}) (string, error) { return gompstrings.KebabCase(conv.ToString(in)), nil } // WordWrap - -func (f *StringFuncs) WordWrap(args ...interface{}) (string, error) { +func (StringFuncs) WordWrap(args ...interface{}) (string, error) { if len(args) == 0 || len(args) > 3 { return "", errors.Errorf("expected 1, 2, or 3 args, got %d", len(args)) } @@ -301,7 +295,7 @@ func (f *StringFuncs) WordWrap(args ...interface{}) (string, error) { } // RuneCount - like len(s), but for runes -func (f *StringFuncs) RuneCount(args ...interface{}) (int, error) { +func (StringFuncs) RuneCount(args ...interface{}) (int, error) { s := "" for _, arg := range args { s += conv.ToString(arg) diff --git a/funcs/strings_test.go b/funcs/strings_test.go index 0975c6d1..94b72d60 100644 --- a/funcs/strings_test.go +++ b/funcs/strings_test.go @@ -1,11 +1,28 @@ package funcs import ( + "context" + "strconv" "testing" "github.com/stretchr/testify/assert" ) +func TestCreateStringFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateStringFuncs(ctx) + actual := fmap["strings"].(func() interface{}) + + assert.Same(t, ctx, actual().(*StringFuncs).ctx) + }) + } +} + func TestReplaceAll(t *testing.T) { sf := &StringFuncs{} diff --git a/funcs/test.go b/funcs/test.go index 1ceef651..88546018 100644 --- a/funcs/test.go +++ b/funcs/test.go @@ -3,7 +3,6 @@ package funcs import ( "context" "reflect" - "sync" "github.com/hairyhenderson/gomplate/v3/conv" "github.com/pkg/errors" @@ -11,18 +10,14 @@ import ( "github.com/hairyhenderson/gomplate/v3/test" ) -var ( - testNS *TestFuncs - testNSInit sync.Once -) - // TestNS - +// Deprecated: don't use func TestNS() *TestFuncs { - testNSInit.Do(func() { testNS = &TestFuncs{} }) - return testNS + return &TestFuncs{} } // AddTestFuncs - +// Deprecated: use CreateTestFuncs instead func AddTestFuncs(f map[string]interface{}) { for k, v := range CreateTestFuncs(context.Background()) { f[k] = v @@ -32,9 +27,9 @@ func AddTestFuncs(f map[string]interface{}) { // CreateTestFuncs - func CreateTestFuncs(ctx context.Context) map[string]interface{} { f := map[string]interface{}{} - ns := TestNS() - ns.ctx = ctx - f["test"] = TestNS + + ns := &TestFuncs{ctx} + f["test"] = func() interface{} { return ns } f["assert"] = ns.Assert f["fail"] = ns.Fail @@ -51,7 +46,7 @@ type TestFuncs struct { } // Assert - -func (f *TestFuncs) Assert(args ...interface{}) (string, error) { +func (TestFuncs) Assert(args ...interface{}) (string, error) { input := conv.ToBool(args[len(args)-1]) switch len(args) { case 1: @@ -68,7 +63,7 @@ func (f *TestFuncs) Assert(args ...interface{}) (string, error) { } // Fail - -func (f *TestFuncs) Fail(args ...interface{}) (string, error) { +func (TestFuncs) Fail(args ...interface{}) (string, error) { switch len(args) { case 0: return "", test.Fail("") @@ -80,7 +75,7 @@ func (f *TestFuncs) Fail(args ...interface{}) (string, error) { } // Required - -func (f *TestFuncs) Required(args ...interface{}) (interface{}, error) { +func (TestFuncs) Required(args ...interface{}) (interface{}, error) { switch len(args) { case 1: return test.Required("", args[0]) @@ -96,7 +91,7 @@ func (f *TestFuncs) Required(args ...interface{}) (interface{}, error) { } // Ternary - -func (f *TestFuncs) Ternary(tval, fval, b interface{}) interface{} { +func (TestFuncs) Ternary(tval, fval, b interface{}) interface{} { if conv.ToBool(b) { return tval } @@ -104,12 +99,12 @@ func (f *TestFuncs) Ternary(tval, fval, b interface{}) interface{} { } // Kind - return the kind of the argument -func (f *TestFuncs) Kind(arg interface{}) string { +func (TestFuncs) Kind(arg interface{}) string { return reflect.ValueOf(arg).Kind().String() } // IsKind - return whether or not the argument is of the given kind -func (f *TestFuncs) IsKind(kind string, arg interface{}) bool { +func (f TestFuncs) IsKind(kind string, arg interface{}) bool { k := f.Kind(arg) if kind == "number" { switch k { diff --git a/funcs/test_test.go b/funcs/test_test.go index 5fa672a5..fc5bbb19 100644 --- a/funcs/test_test.go +++ b/funcs/test_test.go @@ -1,11 +1,28 @@ package funcs import ( + "context" + "strconv" "testing" "github.com/stretchr/testify/assert" ) +func TestCreateTestFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateTestFuncs(ctx) + actual := fmap["test"].(func() interface{}) + + assert.Same(t, ctx, actual().(*TestFuncs).ctx) + }) + } +} + func TestAssert(t *testing.T) { f := TestNS() _, err := f.Assert(false) diff --git a/funcs/time.go b/funcs/time.go index d6b24acb..fb84f587 100644 --- a/funcs/time.go +++ b/funcs/time.go @@ -5,7 +5,6 @@ import ( "fmt" "strconv" "strings" - "sync" gotime "time" "github.com/hairyhenderson/gomplate/v3/conv" @@ -13,36 +12,30 @@ import ( "github.com/hairyhenderson/gomplate/v3/time" ) -var ( - timeNS *TimeFuncs - timeNSInit sync.Once -) - // TimeNS - +// Deprecated: don't use func TimeNS() *TimeFuncs { - timeNSInit.Do(func() { - timeNS = &TimeFuncs{ - ANSIC: gotime.ANSIC, - UnixDate: gotime.UnixDate, - RubyDate: gotime.RubyDate, - RFC822: gotime.RFC822, - RFC822Z: gotime.RFC822Z, - RFC850: gotime.RFC850, - RFC1123: gotime.RFC1123, - RFC1123Z: gotime.RFC1123Z, - RFC3339: gotime.RFC3339, - RFC3339Nano: gotime.RFC3339Nano, - Kitchen: gotime.Kitchen, - Stamp: gotime.Stamp, - StampMilli: gotime.StampMilli, - StampMicro: gotime.StampMicro, - StampNano: gotime.StampNano, - } - }) - return timeNS + return &TimeFuncs{ + ANSIC: gotime.ANSIC, + UnixDate: gotime.UnixDate, + RubyDate: gotime.RubyDate, + RFC822: gotime.RFC822, + RFC822Z: gotime.RFC822Z, + RFC850: gotime.RFC850, + RFC1123: gotime.RFC1123, + RFC1123Z: gotime.RFC1123Z, + RFC3339: gotime.RFC3339, + RFC3339Nano: gotime.RFC3339Nano, + Kitchen: gotime.Kitchen, + Stamp: gotime.Stamp, + StampMilli: gotime.StampMilli, + StampMicro: gotime.StampMicro, + StampNano: gotime.StampNano, + } } // AddTimeFuncs - +// Deprecated: use CreateTimeFuncs instead func AddTimeFuncs(f map[string]interface{}) { for k, v := range CreateTimeFuncs(context.Background()) { f[k] = v @@ -51,10 +44,28 @@ func AddTimeFuncs(f map[string]interface{}) { // CreateTimeFuncs - func CreateTimeFuncs(ctx context.Context) map[string]interface{} { - ns := TimeNS() - ns.ctx = ctx + ns := &TimeFuncs{ + ctx: ctx, + ANSIC: gotime.ANSIC, + UnixDate: gotime.UnixDate, + RubyDate: gotime.RubyDate, + RFC822: gotime.RFC822, + RFC822Z: gotime.RFC822Z, + RFC850: gotime.RFC850, + RFC1123: gotime.RFC1123, + RFC1123Z: gotime.RFC1123Z, + RFC3339: gotime.RFC3339, + RFC3339Nano: gotime.RFC3339Nano, + Kitchen: gotime.Kitchen, + Stamp: gotime.Stamp, + StampMilli: gotime.StampMilli, + StampMicro: gotime.StampMicro, + StampNano: gotime.StampNano, + } - return map[string]interface{}{"time": TimeNS} + return map[string]interface{}{ + "time": func() interface{} { return ns }, + } } // TimeFuncs - @@ -78,28 +89,28 @@ type TimeFuncs struct { } // ZoneName - return the local system's time zone's name -func (f *TimeFuncs) ZoneName() string { +func (TimeFuncs) ZoneName() string { return time.ZoneName() } // ZoneOffset - return the local system's time zone's name -func (f *TimeFuncs) ZoneOffset() int { +func (TimeFuncs) ZoneOffset() int { return time.ZoneOffset() } // Parse - -func (f *TimeFuncs) Parse(layout string, value interface{}) (gotime.Time, error) { +func (TimeFuncs) Parse(layout string, value interface{}) (gotime.Time, error) { return gotime.Parse(layout, conv.ToString(value)) } // ParseLocal - -func (f *TimeFuncs) ParseLocal(layout string, value interface{}) (gotime.Time, error) { +func (f TimeFuncs) ParseLocal(layout string, value interface{}) (gotime.Time, error) { tz := env.Getenv("TZ", "Local") return f.ParseInLocation(layout, tz, value) } // ParseInLocation - -func (f *TimeFuncs) ParseInLocation(layout, location string, value interface{}) (gotime.Time, error) { +func (TimeFuncs) ParseInLocation(layout, location string, value interface{}) (gotime.Time, error) { loc, err := gotime.LoadLocation(location) if err != nil { return gotime.Time{}, err @@ -108,13 +119,13 @@ func (f *TimeFuncs) ParseInLocation(layout, location string, value interface{}) } // Now - -func (f *TimeFuncs) Now() gotime.Time { +func (TimeFuncs) Now() gotime.Time { return gotime.Now() } // Unix - convert UNIX time (in seconds since the UNIX epoch) into a time.Time for further processing // Takes a string or number (int or float) -func (f *TimeFuncs) Unix(in interface{}) (gotime.Time, error) { +func (TimeFuncs) Unix(in interface{}) (gotime.Time, error) { sec, nsec, err := parseNum(in) if err != nil { return gotime.Time{}, err @@ -123,47 +134,47 @@ func (f *TimeFuncs) Unix(in interface{}) (gotime.Time, error) { } // Nanosecond - -func (f *TimeFuncs) Nanosecond(n interface{}) gotime.Duration { +func (TimeFuncs) Nanosecond(n interface{}) gotime.Duration { return gotime.Nanosecond * gotime.Duration(conv.ToInt64(n)) } // Microsecond - -func (f *TimeFuncs) Microsecond(n interface{}) gotime.Duration { +func (TimeFuncs) Microsecond(n interface{}) gotime.Duration { return gotime.Microsecond * gotime.Duration(conv.ToInt64(n)) } // Millisecond - -func (f *TimeFuncs) Millisecond(n interface{}) gotime.Duration { +func (TimeFuncs) Millisecond(n interface{}) gotime.Duration { return gotime.Millisecond * gotime.Duration(conv.ToInt64(n)) } // Second - -func (f *TimeFuncs) Second(n interface{}) gotime.Duration { +func (TimeFuncs) Second(n interface{}) gotime.Duration { return gotime.Second * gotime.Duration(conv.ToInt64(n)) } // Minute - -func (f *TimeFuncs) Minute(n interface{}) gotime.Duration { +func (TimeFuncs) Minute(n interface{}) gotime.Duration { return gotime.Minute * gotime.Duration(conv.ToInt64(n)) } // Hour - -func (f *TimeFuncs) Hour(n interface{}) gotime.Duration { +func (TimeFuncs) Hour(n interface{}) gotime.Duration { return gotime.Hour * gotime.Duration(conv.ToInt64(n)) } // ParseDuration - -func (f *TimeFuncs) ParseDuration(n interface{}) (gotime.Duration, error) { +func (TimeFuncs) ParseDuration(n interface{}) (gotime.Duration, error) { return gotime.ParseDuration(conv.ToString(n)) } // Since - -func (f *TimeFuncs) Since(n gotime.Time) gotime.Duration { +func (TimeFuncs) Since(n gotime.Time) gotime.Duration { return gotime.Since(n) } // Until - -func (f *TimeFuncs) Until(n gotime.Time) gotime.Duration { +func (TimeFuncs) Until(n gotime.Time) gotime.Duration { return gotime.Until(n) } diff --git a/funcs/time_test.go b/funcs/time_test.go index 99ab1e34..63c5f259 100644 --- a/funcs/time_test.go +++ b/funcs/time_test.go @@ -1,13 +1,30 @@ package funcs import ( + "context" "math" "math/big" + "strconv" "testing" "github.com/stretchr/testify/assert" ) +func TestCreateTimeFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateTimeFuncs(ctx) + actual := fmap["time"].(func() interface{}) + + assert.Same(t, ctx, actual().(*TimeFuncs).ctx) + }) + } +} + func TestParseNum(t *testing.T) { i, f, _ := parseNum("42") assert.Equal(t, int64(42), i) diff --git a/funcs/uuid.go b/funcs/uuid.go index 3799334e..bab667d0 100644 --- a/funcs/uuid.go +++ b/funcs/uuid.go @@ -2,27 +2,20 @@ package funcs import ( "context" - "sync" "github.com/hairyhenderson/gomplate/v3/conv" "github.com/google/uuid" ) -var ( - uuidNS *UUIDFuncs - uuidNSInit sync.Once -) - // UUIDNS - +// Deprecated: don't use func UUIDNS() *UUIDFuncs { - uuidNSInit.Do(func() { - uuidNS = &UUIDFuncs{} - }) - return uuidNS + return &UUIDFuncs{} } // AddUUIDFuncs - +// Deprecated: use CreateUUIDFuncs instead func AddUUIDFuncs(f map[string]interface{}) { for k, v := range CreateUUIDFuncs(context.Background()) { f[k] = v @@ -31,9 +24,10 @@ func AddUUIDFuncs(f map[string]interface{}) { // CreateUUIDFuncs - func CreateUUIDFuncs(ctx context.Context) map[string]interface{} { - ns := UUIDNS() - ns.ctx = ctx - return map[string]interface{}{"uuid": UUIDNS} + ns := &UUIDFuncs{ctx} + return map[string]interface{}{ + "uuid": func() interface{} { return ns }, + } } // UUIDFuncs - @@ -43,7 +37,7 @@ type UUIDFuncs struct { // V1 - return a version 1 UUID (based on the current MAC Address and the // current date/time). Use V4 instead in most cases. -func (f *UUIDFuncs) V1() (string, error) { +func (UUIDFuncs) V1() (string, error) { u, err := uuid.NewUUID() if err != nil { return "", err @@ -52,7 +46,7 @@ func (f *UUIDFuncs) V1() (string, error) { } // V4 - return a version 4 (random) UUID -func (f *UUIDFuncs) V4() (string, error) { +func (UUIDFuncs) V4() (string, error) { u, err := uuid.NewRandom() if err != nil { return "", err @@ -61,13 +55,13 @@ func (f *UUIDFuncs) V4() (string, error) { } // Nil - -func (f *UUIDFuncs) Nil() (string, error) { +func (UUIDFuncs) Nil() (string, error) { return uuid.Nil.String(), nil } // IsValid - checks if the given UUID is in the correct format. It does not // validate whether the version or variant are correct. -func (f *UUIDFuncs) IsValid(in interface{}) (bool, error) { +func (f UUIDFuncs) IsValid(in interface{}) (bool, error) { _, err := f.Parse(in) return err == nil, nil } @@ -78,7 +72,7 @@ func (f *UUIDFuncs) IsValid(in interface{}) (bool, error) { // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the // Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex // encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. -func (f *UUIDFuncs) Parse(in interface{}) (uuid.UUID, error) { +func (UUIDFuncs) Parse(in interface{}) (uuid.UUID, error) { u, err := uuid.Parse(conv.ToString(in)) if err != nil { return uuid.Nil, err diff --git a/funcs/uuid_test.go b/funcs/uuid_test.go index ef6cb4e8..974741d7 100644 --- a/funcs/uuid_test.go +++ b/funcs/uuid_test.go @@ -1,12 +1,29 @@ package funcs import ( + "context" "net/url" + "strconv" "testing" "github.com/stretchr/testify/assert" ) +func TestCreateUUIDFuncs(t *testing.T) { + for i := 0; i < 10; i++ { + // Run this a bunch to catch race conditions + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + fmap := CreateUUIDFuncs(ctx) + actual := fmap["uuid"].(func() interface{}) + + assert.Same(t, ctx, actual().(*UUIDFuncs).ctx) + }) + } +} + const ( uuidV1Pattern = "^[[:xdigit:]]{8}-[[:xdigit:]]{4}-1[[:xdigit:]]{3}-[89ab][[:xdigit:]]{3}-[[:xdigit:]]{12}$" uuidV4Pattern = "^[[:xdigit:]]{8}-[[:xdigit:]]{4}-4[[:xdigit:]]{3}-[89ab][[:xdigit:]]{3}-[[:xdigit:]]{12}$" |
