docker-gen/internal/template/template_test.go
Richard Hansen c7b991e01d feat: New eval function to evaluate a template
This makes it possible to:
  * modularize a template by using an embedded template (defined with
    Go's built-in `define` action) as a function whose return value is
    the expansion of the template
  * post-process the expansion of a template (e.g., pipe it to
    Sprig's `indent` function)

For additional context, see
<https://github.com/golang/go/issues/54748>.  Sprig is unlikely to add
an equivalent to this function any time soon: the function must be a
closure around the template, meaning either Sprig or Go would have to
change its API.
2023-01-23 20:43:52 -05:00

196 lines
5.2 KiB
Go

package template
import (
"bytes"
"errors"
"reflect"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
type templateTestList []struct {
tmpl string
context interface{}
expected interface{}
}
func (tests templateTestList) run(t *testing.T) {
for n, test := range tests {
test := test
t.Run(strconv.Itoa(n), func(t *testing.T) {
t.Parallel()
wantErr, _ := test.expected.(error)
want, ok := test.expected.(string)
if !ok && wantErr == nil {
t.Fatalf("test bug: want a string or error for .expected, got %v", test.expected)
}
tmpl, err := newTemplate("testTemplate").Parse(test.tmpl)
if err != nil {
t.Fatalf("Template parse failed: %v", err)
}
var b bytes.Buffer
err = tmpl.ExecuteTemplate(&b, "testTemplate", test.context)
got := b.String()
if err != nil {
if wantErr != nil {
return
}
t.Fatalf("Error executing template: %v", err)
} else if wantErr != nil {
t.Fatalf("Expected error, got %v", got)
}
if want != got {
t.Fatalf("Incorrect output found; want %#v, got %#v", want, got)
}
})
}
}
func TestGetArrayValues(t *testing.T) {
values := []string{"foor", "bar", "baz"}
var expectedType *reflect.Value
arrayValues, err := getArrayValues("testFunc", values)
assert.NoError(t, err)
assert.IsType(t, expectedType, arrayValues)
assert.Equal(t, "bar", arrayValues.Index(1).String())
arrayValues, err = getArrayValues("testFunc", &values)
assert.NoError(t, err)
assert.IsType(t, expectedType, arrayValues)
assert.Equal(t, "baz", arrayValues.Index(2).String())
arrayValues, err = getArrayValues("testFunc", "foo")
assert.Error(t, err)
assert.Nil(t, arrayValues)
}
func TestIsBlank(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"", true},
{" ", true},
{" ", true},
{"\t", true},
{"\t\n\v\f\r\u0085\u00A0", true},
{"a", false},
{" a ", false},
{"a ", false},
{" a", false},
{"日本語", false},
}
for _, i := range tests {
v := isBlank(i.input)
if v != i.expected {
t.Fatalf("expected '%v'. got '%v'", i.expected, v)
}
}
}
func TestRemoveBlankLines(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"", ""},
{"\r\n\r\n", ""},
{"line1\nline2", "line1\nline2"},
{"line1\n\nline2", "line1\nline2"},
{"\n\n\n\nline1\n\nline2", "line1\nline2"},
{"\n\n\n\n\n \n \n \n", ""},
// windows line endings \r\n
{"line1\r\nline2", "line1\r\nline2"},
{"line1\r\n\r\nline2", "line1\r\nline2"},
// keep last new line
{"line1\n", "line1\n"},
{"line1\r\n", "line1\r\n"},
}
for _, i := range tests {
output := new(bytes.Buffer)
removeBlankLines(strings.NewReader(i.input), output)
if output.String() != i.expected {
t.Fatalf("expected '%v'. got '%v'", i.expected, output)
}
}
}
// TestSprig ensures that the migration to sprig to provide certain functions did not break
// compatibility with existing templates.
func TestSprig(t *testing.T) {
for _, tc := range []struct {
desc string
tts templateTestList
}{
{"dict", templateTestList{
{`{{ $d := dict "a" "b" }}{{ if eq (index $d "a") "b" }}ok{{ end }}`, nil, `ok`},
{`{{ $d := dict "a" "b" }}{{ if eq (index $d "x") nil }}ok{{ end }}`, nil, `ok`},
{`{{ $d := dict "a" "b" "c" (dict "d" "e") }}{{ if eq (index $d "c" "d") "e" }}ok{{ end }}`, nil, `ok`},
}},
{"first", templateTestList{
{`{{ if eq (first $) "a"}}ok{{ end }}`, []string{"a", "b"}, `ok`},
{`{{ if eq (first $) "a"}}ok{{ end }}`, [2]string{"a", "b"}, `ok`},
}},
{"hasPrefix", templateTestList{
{`{{ if hasPrefix "tcp://" "tcp://127.0.0.1:2375" }}ok{{ end }}`, nil, `ok`},
{`{{ if not (hasPrefix "udp://" "tcp://127.0.0.1:2375") }}ok{{ end }}`, nil, `ok`},
}},
{"hasSuffix", templateTestList{
{`{{ if hasSuffix ".local" "myhost.local" }}ok{{ end }}`, nil, `ok`},
{`{{ if not (hasSuffix ".example" "myhost.local") }}ok{{ end }}`, nil, `ok`},
}},
{"last", templateTestList{
{`{{ if eq (last $) "b"}}ok{{ end }}`, []string{"a", "b"}, `ok`},
{`{{ if eq (last $) "b"}}ok{{ end }}`, [2]string{"a", "b"}, `ok`},
}},
{"trim", templateTestList{
{`{{ if eq (trim " myhost.local ") "myhost.local" }}ok{{ end }}`, nil, `ok`},
}},
} {
t.Run(tc.desc, func(t *testing.T) {
tc.tts.run(t)
})
}
}
func TestEval(t *testing.T) {
for _, tc := range []struct {
desc string
tts templateTestList
}{
{"undefined", templateTestList{
{`{{eval "missing"}}`, nil, errors.New("")},
{`{{eval "missing" nil}}`, nil, errors.New("")},
{`{{eval "missing" "abc"}}`, nil, errors.New("")},
{`{{eval "missing" "abc" "def"}}`, nil, errors.New("")},
}},
// The purpose of the "ctx" context is to assert that $ and . inside the template is the
// eval argument, not the global context.
{"noArg", templateTestList{
{`{{define "T"}}{{$}}{{.}}{{end}}{{eval "T"}}`, "ctx", "<no value><no value>"},
}},
{"nilArg", templateTestList{
{`{{define "T"}}{{$}}{{.}}{{end}}{{eval "T" nil}}`, "ctx", "<no value><no value>"},
}},
{"oneArg", templateTestList{
{`{{define "T"}}{{$}}{{.}}{{end}}{{eval "T" "arg"}}`, "ctx", "argarg"},
}},
{"moreThanOneArg", templateTestList{
{`{{define "T"}}{{$}}{{.}}{{end}}{{eval "T" "a" "b"}}`, "ctx", errors.New("")},
}},
} {
t.Run(tc.desc, func(t *testing.T) {
tc.tts.run(t)
})
}
}