Fix corruption in templates that use title function (#3278)
This commit fixes data corruption in templates that use the title function as it used a shared cases.Title when casers should not be shared between goroutines. When templates that used the title function were executed at the same time, data corruption would occur in the text that was being cased. This is fixed using a separate caser for each function call. Add tests to assertDefaultFuncs are thread-safe Signed-off-by: George Robinson <george.robinson@grafana.com>
This commit is contained in:
parent
6bb49b2f1c
commit
012dbc9d81
|
@ -168,11 +168,14 @@ func (t *Template) ExecuteHTMLString(html string, data interface{}) (string, err
|
||||||
type FuncMap map[string]interface{}
|
type FuncMap map[string]interface{}
|
||||||
|
|
||||||
var DefaultFuncs = FuncMap{
|
var DefaultFuncs = FuncMap{
|
||||||
"toUpper": strings.ToUpper,
|
"toUpper": strings.ToUpper,
|
||||||
"toLower": strings.ToLower,
|
"toLower": strings.ToLower,
|
||||||
|
"title": func(text string) string {
|
||||||
|
// casers should not be shared between goroutines, instead
|
||||||
|
// create a new caser each time this function is called
|
||||||
|
return cases.Title(language.AmericanEnglish).String(text)
|
||||||
|
},
|
||||||
"trimSpace": strings.TrimSpace,
|
"trimSpace": strings.TrimSpace,
|
||||||
|
|
||||||
"title": cases.Title(language.AmericanEnglish).String,
|
|
||||||
// join is equal to strings.Join but inverts the argument order
|
// join is equal to strings.Join but inverts the argument order
|
||||||
// for easier pipelining in templates.
|
// for easier pipelining in templates.
|
||||||
"join": func(sep string, s []string) string {
|
"join": func(sep string, s []string) string {
|
||||||
|
|
|
@ -16,6 +16,7 @@ package template
|
||||||
import (
|
import (
|
||||||
tmplhtml "html/template"
|
tmplhtml "html/template"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
tmpltext "text/template"
|
tmpltext "text/template"
|
||||||
"time"
|
"time"
|
||||||
|
@ -465,3 +466,60 @@ func TestTemplateExpansionWithOptions(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This test asserts that template functions are thread-safe.
|
||||||
|
func TestTemplateFuncs(t *testing.T) {
|
||||||
|
tmpl, err := FromGlobs([]string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
title string
|
||||||
|
in string
|
||||||
|
data interface{}
|
||||||
|
exp string
|
||||||
|
}{{
|
||||||
|
title: "Template using toUpper",
|
||||||
|
in: `{{ "abc" | toUpper }}`,
|
||||||
|
exp: "ABC",
|
||||||
|
}, {
|
||||||
|
title: "Template using toLower",
|
||||||
|
in: `{{ "ABC" | toLower }}`,
|
||||||
|
exp: "abc",
|
||||||
|
}, {
|
||||||
|
title: "Template using title",
|
||||||
|
in: `{{ "abc" | title }}`,
|
||||||
|
exp: "Abc",
|
||||||
|
}, {
|
||||||
|
title: "Template using trimSpace",
|
||||||
|
in: `{{ " abc " | trimSpace }}`,
|
||||||
|
exp: "abc",
|
||||||
|
}, {
|
||||||
|
title: "Template using join",
|
||||||
|
in: `{{ . | join "," }}`,
|
||||||
|
data: []string{"abc", "def"},
|
||||||
|
exp: "abc,def",
|
||||||
|
}, {
|
||||||
|
title: "Template using match",
|
||||||
|
in: `{{ match "[a-z]+" "abc" }}`,
|
||||||
|
exp: "true",
|
||||||
|
}, {
|
||||||
|
title: "Template using reReplaceAll",
|
||||||
|
in: `{{ reReplaceAll "ab" "AB" "abc" }}`,
|
||||||
|
exp: "ABc",
|
||||||
|
}} {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.title, func(t *testing.T) {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
got, err := tmpl.ExecuteTextString(tc.in, tc.data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.exp, got)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue