...

Source file src/helm.sh/helm/v3/pkg/engine/engine_test.go

Documentation: helm.sh/helm/v3/pkg/engine

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package engine
    18  
    19  import (
    20  	"fmt"
    21  	"path"
    22  	"strings"
    23  	"sync"
    24  	"testing"
    25  	"text/template"
    26  
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/client-go/dynamic"
    31  	"k8s.io/client-go/dynamic/fake"
    32  
    33  	"helm.sh/helm/v3/pkg/chart"
    34  	"helm.sh/helm/v3/pkg/chartutil"
    35  )
    36  
    37  func TestSortTemplates(t *testing.T) {
    38  	tpls := map[string]renderable{
    39  		"/mychart/templates/foo.tpl":                                 {},
    40  		"/mychart/templates/charts/foo/charts/bar/templates/foo.tpl": {},
    41  		"/mychart/templates/bar.tpl":                                 {},
    42  		"/mychart/templates/charts/foo/templates/bar.tpl":            {},
    43  		"/mychart/templates/_foo.tpl":                                {},
    44  		"/mychart/templates/charts/foo/templates/foo.tpl":            {},
    45  		"/mychart/templates/charts/bar/templates/foo.tpl":            {},
    46  	}
    47  	got := sortTemplates(tpls)
    48  	if len(got) != len(tpls) {
    49  		t.Fatal("Sorted results are missing templates")
    50  	}
    51  
    52  	expect := []string{
    53  		"/mychart/templates/charts/foo/charts/bar/templates/foo.tpl",
    54  		"/mychart/templates/charts/foo/templates/foo.tpl",
    55  		"/mychart/templates/charts/foo/templates/bar.tpl",
    56  		"/mychart/templates/charts/bar/templates/foo.tpl",
    57  		"/mychart/templates/foo.tpl",
    58  		"/mychart/templates/bar.tpl",
    59  		"/mychart/templates/_foo.tpl",
    60  	}
    61  	for i, e := range expect {
    62  		if got[i] != e {
    63  			t.Fatalf("\n\tExp:\n%s\n\tGot:\n%s",
    64  				strings.Join(expect, "\n"),
    65  				strings.Join(got, "\n"),
    66  			)
    67  		}
    68  	}
    69  }
    70  
    71  func TestFuncMap(t *testing.T) {
    72  	fns := funcMap()
    73  	forbidden := []string{"env", "expandenv"}
    74  	for _, f := range forbidden {
    75  		if _, ok := fns[f]; ok {
    76  			t.Errorf("Forbidden function %s exists in FuncMap.", f)
    77  		}
    78  	}
    79  
    80  	// Test for Engine-specific template functions.
    81  	expect := []string{"include", "required", "tpl", "toYaml", "fromYaml", "toToml", "toJson", "fromJson", "lookup"}
    82  	for _, f := range expect {
    83  		if _, ok := fns[f]; !ok {
    84  			t.Errorf("Expected add-on function %q", f)
    85  		}
    86  	}
    87  }
    88  
    89  func TestRender(t *testing.T) {
    90  	c := &chart.Chart{
    91  		Metadata: &chart.Metadata{
    92  			Name:    "moby",
    93  			Version: "1.2.3",
    94  		},
    95  		Templates: []*chart.File{
    96  			{Name: "templates/test1", Data: []byte("{{.Values.outer | title }} {{.Values.inner | title}}")},
    97  			{Name: "templates/test2", Data: []byte("{{.Values.global.callme | lower }}")},
    98  			{Name: "templates/test3", Data: []byte("{{.noValue}}")},
    99  			{Name: "templates/test4", Data: []byte("{{toJson .Values}}")},
   100  			{Name: "templates/test5", Data: []byte("{{getHostByName \"helm.sh\"}}")},
   101  		},
   102  		Values: map[string]interface{}{"outer": "DEFAULT", "inner": "DEFAULT"},
   103  	}
   104  
   105  	vals := map[string]interface{}{
   106  		"Values": map[string]interface{}{
   107  			"outer": "spouter",
   108  			"inner": "inn",
   109  			"global": map[string]interface{}{
   110  				"callme": "Ishmael",
   111  			},
   112  		},
   113  	}
   114  
   115  	v, err := chartutil.CoalesceValues(c, vals)
   116  	if err != nil {
   117  		t.Fatalf("Failed to coalesce values: %s", err)
   118  	}
   119  	out, err := Render(c, v)
   120  	if err != nil {
   121  		t.Errorf("Failed to render templates: %s", err)
   122  	}
   123  
   124  	expect := map[string]string{
   125  		"moby/templates/test1": "Spouter Inn",
   126  		"moby/templates/test2": "ishmael",
   127  		"moby/templates/test3": "",
   128  		"moby/templates/test4": `{"global":{"callme":"Ishmael"},"inner":"inn","outer":"spouter"}`,
   129  		"moby/templates/test5": "",
   130  	}
   131  
   132  	for name, data := range expect {
   133  		if out[name] != data {
   134  			t.Errorf("Expected %q, got %q", data, out[name])
   135  		}
   136  	}
   137  }
   138  
   139  func TestRenderRefsOrdering(t *testing.T) {
   140  	parentChart := &chart.Chart{
   141  		Metadata: &chart.Metadata{
   142  			Name:    "parent",
   143  			Version: "1.2.3",
   144  		},
   145  		Templates: []*chart.File{
   146  			{Name: "templates/_helpers.tpl", Data: []byte(`{{- define "test" -}}parent value{{- end -}}`)},
   147  			{Name: "templates/test.yaml", Data: []byte(`{{ tpl "{{ include \"test\" . }}" . }}`)},
   148  		},
   149  	}
   150  	childChart := &chart.Chart{
   151  		Metadata: &chart.Metadata{
   152  			Name:    "child",
   153  			Version: "1.2.3",
   154  		},
   155  		Templates: []*chart.File{
   156  			{Name: "templates/_helpers.tpl", Data: []byte(`{{- define "test" -}}child value{{- end -}}`)},
   157  		},
   158  	}
   159  	parentChart.AddDependency(childChart)
   160  
   161  	expect := map[string]string{
   162  		"parent/templates/test.yaml": "parent value",
   163  	}
   164  
   165  	for i := 0; i < 100; i++ {
   166  		out, err := Render(parentChart, chartutil.Values{})
   167  		if err != nil {
   168  			t.Fatalf("Failed to render templates: %s", err)
   169  		}
   170  
   171  		for name, data := range expect {
   172  			if out[name] != data {
   173  				t.Fatalf("Expected %q, got %q (iteration %d)", data, out[name], i+1)
   174  			}
   175  		}
   176  	}
   177  }
   178  
   179  func TestRenderInternals(t *testing.T) {
   180  	// Test the internals of the rendering tool.
   181  
   182  	vals := chartutil.Values{"Name": "one", "Value": "two"}
   183  	tpls := map[string]renderable{
   184  		"one": {tpl: `Hello {{title .Name}}`, vals: vals},
   185  		"two": {tpl: `Goodbye {{upper .Value}}`, vals: vals},
   186  		// Test whether a template can reliably reference another template
   187  		// without regard for ordering.
   188  		"three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals},
   189  	}
   190  
   191  	out, err := new(Engine).render(tpls)
   192  	if err != nil {
   193  		t.Fatalf("Failed template rendering: %s", err)
   194  	}
   195  
   196  	if len(out) != 3 {
   197  		t.Fatalf("Expected 3 templates, got %d", len(out))
   198  	}
   199  
   200  	if out["one"] != "Hello One" {
   201  		t.Errorf("Expected 'Hello One', got %q", out["one"])
   202  	}
   203  
   204  	if out["two"] != "Goodbye TWO" {
   205  		t.Errorf("Expected 'Goodbye TWO'. got %q", out["two"])
   206  	}
   207  
   208  	if out["three"] != "Goodbye THREE" {
   209  		t.Errorf("Expected 'Goodbye THREE'. got %q", out["two"])
   210  	}
   211  }
   212  
   213  func TestRenderWithDNS(t *testing.T) {
   214  	c := &chart.Chart{
   215  		Metadata: &chart.Metadata{
   216  			Name:    "moby",
   217  			Version: "1.2.3",
   218  		},
   219  		Templates: []*chart.File{
   220  			{Name: "templates/test1", Data: []byte("{{getHostByName \"helm.sh\"}}")},
   221  		},
   222  		Values: map[string]interface{}{},
   223  	}
   224  
   225  	vals := map[string]interface{}{
   226  		"Values": map[string]interface{}{},
   227  	}
   228  
   229  	v, err := chartutil.CoalesceValues(c, vals)
   230  	if err != nil {
   231  		t.Fatalf("Failed to coalesce values: %s", err)
   232  	}
   233  
   234  	var e Engine
   235  	e.EnableDNS = true
   236  	out, err := e.Render(c, v)
   237  	if err != nil {
   238  		t.Errorf("Failed to render templates: %s", err)
   239  	}
   240  
   241  	for _, val := range c.Templates {
   242  		fp := path.Join("moby", val.Name)
   243  		if out[fp] == "" {
   244  			t.Errorf("Expected IP address, got %q", out[fp])
   245  		}
   246  	}
   247  }
   248  
   249  type kindProps struct {
   250  	shouldErr  error
   251  	gvr        schema.GroupVersionResource
   252  	namespaced bool
   253  }
   254  
   255  type testClientProvider struct {
   256  	t       *testing.T
   257  	scheme  map[string]kindProps
   258  	objects []runtime.Object
   259  }
   260  
   261  func (p *testClientProvider) GetClientFor(apiVersion, kind string) (dynamic.NamespaceableResourceInterface, bool, error) {
   262  	props := p.scheme[path.Join(apiVersion, kind)]
   263  	if props.shouldErr != nil {
   264  		return nil, false, props.shouldErr
   265  	}
   266  	return fake.NewSimpleDynamicClient(runtime.NewScheme(), p.objects...).Resource(props.gvr), props.namespaced, nil
   267  }
   268  
   269  var _ ClientProvider = &testClientProvider{}
   270  
   271  // makeUnstructured is a convenience function for single-line creation of Unstructured objects.
   272  func makeUnstructured(apiVersion, kind, name, namespace string) *unstructured.Unstructured {
   273  	ret := &unstructured.Unstructured{Object: map[string]interface{}{
   274  		"apiVersion": apiVersion,
   275  		"kind":       kind,
   276  		"metadata": map[string]interface{}{
   277  			"name": name,
   278  		},
   279  	}}
   280  	if namespace != "" {
   281  		ret.Object["metadata"].(map[string]interface{})["namespace"] = namespace
   282  	}
   283  	return ret
   284  }
   285  
   286  func TestRenderWithClientProvider(t *testing.T) {
   287  	provider := &testClientProvider{
   288  		t: t,
   289  		scheme: map[string]kindProps{
   290  			"v1/Namespace": {
   291  				gvr: schema.GroupVersionResource{
   292  					Version:  "v1",
   293  					Resource: "namespaces",
   294  				},
   295  			},
   296  			"v1/Pod": {
   297  				gvr: schema.GroupVersionResource{
   298  					Version:  "v1",
   299  					Resource: "pods",
   300  				},
   301  				namespaced: true,
   302  			},
   303  		},
   304  		objects: []runtime.Object{
   305  			makeUnstructured("v1", "Namespace", "default", ""),
   306  			makeUnstructured("v1", "Pod", "pod1", "default"),
   307  			makeUnstructured("v1", "Pod", "pod2", "ns1"),
   308  			makeUnstructured("v1", "Pod", "pod3", "ns1"),
   309  		},
   310  	}
   311  
   312  	type testCase struct {
   313  		template string
   314  		output   string
   315  	}
   316  	cases := map[string]testCase{
   317  		"ns-single": {
   318  			template: `{{ (lookup "v1" "Namespace" "" "default").metadata.name }}`,
   319  			output:   "default",
   320  		},
   321  		"ns-list": {
   322  			template: `{{ (lookup "v1" "Namespace" "" "").items | len }}`,
   323  			output:   "1",
   324  		},
   325  		"ns-missing": {
   326  			template: `{{ (lookup "v1" "Namespace" "" "absent") }}`,
   327  			output:   "map[]",
   328  		},
   329  		"pod-single": {
   330  			template: `{{ (lookup "v1" "Pod" "default" "pod1").metadata.name }}`,
   331  			output:   "pod1",
   332  		},
   333  		"pod-list": {
   334  			template: `{{ (lookup "v1" "Pod" "ns1" "").items | len }}`,
   335  			output:   "2",
   336  		},
   337  		"pod-all": {
   338  			template: `{{ (lookup "v1" "Pod" "" "").items | len }}`,
   339  			output:   "3",
   340  		},
   341  		"pod-missing": {
   342  			template: `{{ (lookup "v1" "Pod" "" "ns2") }}`,
   343  			output:   "map[]",
   344  		},
   345  	}
   346  
   347  	c := &chart.Chart{
   348  		Metadata: &chart.Metadata{
   349  			Name:    "moby",
   350  			Version: "1.2.3",
   351  		},
   352  		Values: map[string]interface{}{},
   353  	}
   354  
   355  	for name, exp := range cases {
   356  		c.Templates = append(c.Templates, &chart.File{
   357  			Name: path.Join("templates", name),
   358  			Data: []byte(exp.template),
   359  		})
   360  	}
   361  
   362  	vals := map[string]interface{}{
   363  		"Values": map[string]interface{}{},
   364  	}
   365  
   366  	v, err := chartutil.CoalesceValues(c, vals)
   367  	if err != nil {
   368  		t.Fatalf("Failed to coalesce values: %s", err)
   369  	}
   370  
   371  	out, err := RenderWithClientProvider(c, v, provider)
   372  	if err != nil {
   373  		t.Errorf("Failed to render templates: %s", err)
   374  	}
   375  
   376  	for name, want := range cases {
   377  		t.Run(name, func(t *testing.T) {
   378  			key := path.Join("moby/templates", name)
   379  			if out[key] != want.output {
   380  				t.Errorf("Expected %q, got %q", want, out[key])
   381  			}
   382  		})
   383  	}
   384  }
   385  
   386  func TestRenderWithClientProvider_error(t *testing.T) {
   387  	c := &chart.Chart{
   388  		Metadata: &chart.Metadata{
   389  			Name:    "moby",
   390  			Version: "1.2.3",
   391  		},
   392  		Templates: []*chart.File{
   393  			{Name: "templates/error", Data: []byte(`{{ lookup "v1" "Error" "" "" }}`)},
   394  		},
   395  		Values: map[string]interface{}{},
   396  	}
   397  
   398  	vals := map[string]interface{}{
   399  		"Values": map[string]interface{}{},
   400  	}
   401  
   402  	v, err := chartutil.CoalesceValues(c, vals)
   403  	if err != nil {
   404  		t.Fatalf("Failed to coalesce values: %s", err)
   405  	}
   406  
   407  	provider := &testClientProvider{
   408  		t: t,
   409  		scheme: map[string]kindProps{
   410  			"v1/Error": {
   411  				shouldErr: fmt.Errorf("kaboom"),
   412  			},
   413  		},
   414  	}
   415  	_, err = RenderWithClientProvider(c, v, provider)
   416  	if err == nil || !strings.Contains(err.Error(), "kaboom") {
   417  		t.Errorf("Expected error from client provider when rendering, got %q", err)
   418  	}
   419  }
   420  
   421  func TestParallelRenderInternals(t *testing.T) {
   422  	// Make sure that we can use one Engine to run parallel template renders.
   423  	e := new(Engine)
   424  	var wg sync.WaitGroup
   425  	for i := 0; i < 20; i++ {
   426  		wg.Add(1)
   427  		go func(i int) {
   428  			tt := fmt.Sprintf("expect-%d", i)
   429  			tpls := map[string]renderable{
   430  				"t": {
   431  					tpl:  `{{.val}}`,
   432  					vals: map[string]interface{}{"val": tt},
   433  				},
   434  			}
   435  			out, err := e.render(tpls)
   436  			if err != nil {
   437  				t.Errorf("Failed to render %s: %s", tt, err)
   438  			}
   439  			if out["t"] != tt {
   440  				t.Errorf("Expected %q, got %q", tt, out["t"])
   441  			}
   442  			wg.Done()
   443  		}(i)
   444  	}
   445  	wg.Wait()
   446  }
   447  
   448  func TestParseErrors(t *testing.T) {
   449  	vals := chartutil.Values{"Values": map[string]interface{}{}}
   450  
   451  	tplsUndefinedFunction := map[string]renderable{
   452  		"undefined_function": {tpl: `{{foo}}`, vals: vals},
   453  	}
   454  	_, err := new(Engine).render(tplsUndefinedFunction)
   455  	if err == nil {
   456  		t.Fatalf("Expected failures while rendering: %s", err)
   457  	}
   458  	expected := `parse error at (undefined_function:1): function "foo" not defined`
   459  	if err.Error() != expected {
   460  		t.Errorf("Expected '%s', got %q", expected, err.Error())
   461  	}
   462  }
   463  
   464  func TestExecErrors(t *testing.T) {
   465  	vals := chartutil.Values{"Values": map[string]interface{}{}}
   466  	cases := []struct {
   467  		name     string
   468  		tpls     map[string]renderable
   469  		expected string
   470  	}{
   471  		{
   472  			name: "MissingRequired",
   473  			tpls: map[string]renderable{
   474  				"missing_required": {tpl: `{{required "foo is required" .Values.foo}}`, vals: vals},
   475  			},
   476  			expected: `execution error at (missing_required:1:2): foo is required`,
   477  		},
   478  		{
   479  			name: "MissingRequiredWithColons",
   480  			tpls: map[string]renderable{
   481  				"missing_required_with_colons": {tpl: `{{required ":this: message: has many: colons:" .Values.foo}}`, vals: vals},
   482  			},
   483  			expected: `execution error at (missing_required_with_colons:1:2): :this: message: has many: colons:`,
   484  		},
   485  		{
   486  			name: "Issue6044",
   487  			tpls: map[string]renderable{
   488  				"issue6044": {
   489  					vals: vals,
   490  					tpl: `{{ $someEmptyValue := "" }}
   491  {{ $myvar := "abc" }}
   492  {{- required (printf "%s: something is missing" $myvar) $someEmptyValue | repeat 0 }}`,
   493  				},
   494  			},
   495  			expected: `execution error at (issue6044:3:4): abc: something is missing`,
   496  		},
   497  		{
   498  			name: "MissingRequiredWithNewlines",
   499  			tpls: map[string]renderable{
   500  				"issue9981": {tpl: `{{required "foo is required\nmore info after the break" .Values.foo}}`, vals: vals},
   501  			},
   502  			expected: `execution error at (issue9981:1:2): foo is required
   503  more info after the break`,
   504  		},
   505  		{
   506  			name: "FailWithNewlines",
   507  			tpls: map[string]renderable{
   508  				"issue9981": {tpl: `{{fail "something is wrong\nlinebreak"}}`, vals: vals},
   509  			},
   510  			expected: `execution error at (issue9981:1:2): something is wrong
   511  linebreak`,
   512  		},
   513  	}
   514  
   515  	for _, tt := range cases {
   516  		t.Run(tt.name, func(t *testing.T) {
   517  			_, err := new(Engine).render(tt.tpls)
   518  			if err == nil {
   519  				t.Fatalf("Expected failures while rendering: %s", err)
   520  			}
   521  			if err.Error() != tt.expected {
   522  				t.Errorf("Expected %q, got %q", tt.expected, err.Error())
   523  			}
   524  		})
   525  	}
   526  }
   527  
   528  func TestFailErrors(t *testing.T) {
   529  	vals := chartutil.Values{"Values": map[string]interface{}{}}
   530  
   531  	failtpl := `All your base are belong to us{{ fail "This is an error" }}`
   532  	tplsFailed := map[string]renderable{
   533  		"failtpl": {tpl: failtpl, vals: vals},
   534  	}
   535  	_, err := new(Engine).render(tplsFailed)
   536  	if err == nil {
   537  		t.Fatalf("Expected failures while rendering: %s", err)
   538  	}
   539  	expected := `execution error at (failtpl:1:33): This is an error`
   540  	if err.Error() != expected {
   541  		t.Errorf("Expected '%s', got %q", expected, err.Error())
   542  	}
   543  
   544  	var e Engine
   545  	e.LintMode = true
   546  	out, err := e.render(tplsFailed)
   547  	if err != nil {
   548  		t.Fatal(err)
   549  	}
   550  
   551  	expectStr := "All your base are belong to us"
   552  	if gotStr := out["failtpl"]; gotStr != expectStr {
   553  		t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out)
   554  	}
   555  }
   556  
   557  func TestAllTemplates(t *testing.T) {
   558  	ch1 := &chart.Chart{
   559  		Metadata: &chart.Metadata{Name: "ch1"},
   560  		Templates: []*chart.File{
   561  			{Name: "templates/foo", Data: []byte("foo")},
   562  			{Name: "templates/bar", Data: []byte("bar")},
   563  		},
   564  	}
   565  	dep1 := &chart.Chart{
   566  		Metadata: &chart.Metadata{Name: "laboratory mice"},
   567  		Templates: []*chart.File{
   568  			{Name: "templates/pinky", Data: []byte("pinky")},
   569  			{Name: "templates/brain", Data: []byte("brain")},
   570  		},
   571  	}
   572  	ch1.AddDependency(dep1)
   573  
   574  	dep2 := &chart.Chart{
   575  		Metadata: &chart.Metadata{Name: "same thing we do every night"},
   576  		Templates: []*chart.File{
   577  			{Name: "templates/innermost", Data: []byte("innermost")},
   578  		},
   579  	}
   580  	dep1.AddDependency(dep2)
   581  
   582  	tpls := allTemplates(ch1, chartutil.Values{})
   583  	if len(tpls) != 5 {
   584  		t.Errorf("Expected 5 charts, got %d", len(tpls))
   585  	}
   586  }
   587  
   588  func TestChartValuesContainsIsRoot(t *testing.T) {
   589  	ch1 := &chart.Chart{
   590  		Metadata: &chart.Metadata{Name: "parent"},
   591  		Templates: []*chart.File{
   592  			{Name: "templates/isroot", Data: []byte("{{.Chart.IsRoot}}")},
   593  		},
   594  	}
   595  	dep1 := &chart.Chart{
   596  		Metadata: &chart.Metadata{Name: "child"},
   597  		Templates: []*chart.File{
   598  			{Name: "templates/isroot", Data: []byte("{{.Chart.IsRoot}}")},
   599  		},
   600  	}
   601  	ch1.AddDependency(dep1)
   602  
   603  	out, err := Render(ch1, chartutil.Values{})
   604  	if err != nil {
   605  		t.Fatalf("failed to render templates: %s", err)
   606  	}
   607  	expects := map[string]string{
   608  		"parent/charts/child/templates/isroot": "false",
   609  		"parent/templates/isroot":              "true",
   610  	}
   611  	for file, expect := range expects {
   612  		if out[file] != expect {
   613  			t.Errorf("Expected %q, got %q", expect, out[file])
   614  		}
   615  	}
   616  }
   617  
   618  func TestRenderDependency(t *testing.T) {
   619  	deptpl := `{{define "myblock"}}World{{end}}`
   620  	toptpl := `Hello {{template "myblock"}}`
   621  	ch := &chart.Chart{
   622  		Metadata: &chart.Metadata{Name: "outerchart"},
   623  		Templates: []*chart.File{
   624  			{Name: "templates/outer", Data: []byte(toptpl)},
   625  		},
   626  	}
   627  	ch.AddDependency(&chart.Chart{
   628  		Metadata: &chart.Metadata{Name: "innerchart"},
   629  		Templates: []*chart.File{
   630  			{Name: "templates/inner", Data: []byte(deptpl)},
   631  		},
   632  	})
   633  
   634  	out, err := Render(ch, map[string]interface{}{})
   635  	if err != nil {
   636  		t.Fatalf("failed to render chart: %s", err)
   637  	}
   638  
   639  	if len(out) != 2 {
   640  		t.Errorf("Expected 2, got %d", len(out))
   641  	}
   642  
   643  	expect := "Hello World"
   644  	if out["outerchart/templates/outer"] != expect {
   645  		t.Errorf("Expected %q, got %q", expect, out["outer"])
   646  	}
   647  
   648  }
   649  
   650  func TestRenderNestedValues(t *testing.T) {
   651  	innerpath := "templates/inner.tpl"
   652  	outerpath := "templates/outer.tpl"
   653  	// Ensure namespacing rules are working.
   654  	deepestpath := "templates/inner.tpl"
   655  	checkrelease := "templates/release.tpl"
   656  	// Ensure subcharts scopes are working.
   657  	subchartspath := "templates/subcharts.tpl"
   658  
   659  	deepest := &chart.Chart{
   660  		Metadata: &chart.Metadata{Name: "deepest"},
   661  		Templates: []*chart.File{
   662  			{Name: deepestpath, Data: []byte(`And this same {{.Values.what}} that smiles {{.Values.global.when}}`)},
   663  			{Name: checkrelease, Data: []byte(`Tomorrow will be {{default "happy" .Release.Name }}`)},
   664  		},
   665  		Values: map[string]interface{}{"what": "milkshake", "where": "here"},
   666  	}
   667  
   668  	inner := &chart.Chart{
   669  		Metadata: &chart.Metadata{Name: "herrick"},
   670  		Templates: []*chart.File{
   671  			{Name: innerpath, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)},
   672  		},
   673  		Values: map[string]interface{}{"who": "Robert", "what": "glasses"},
   674  	}
   675  	inner.AddDependency(deepest)
   676  
   677  	outer := &chart.Chart{
   678  		Metadata: &chart.Metadata{Name: "top"},
   679  		Templates: []*chart.File{
   680  			{Name: outerpath, Data: []byte(`Gather ye {{.Values.what}} while ye may`)},
   681  			{Name: subchartspath, Data: []byte(`The glorious Lamp of {{.Subcharts.herrick.Subcharts.deepest.Values.where}}, the {{.Subcharts.herrick.Values.what}}`)},
   682  		},
   683  		Values: map[string]interface{}{
   684  			"what": "stinkweed",
   685  			"who":  "me",
   686  			"herrick": map[string]interface{}{
   687  				"who":  "time",
   688  				"what": "Sun",
   689  			},
   690  		},
   691  	}
   692  	outer.AddDependency(inner)
   693  
   694  	injValues := map[string]interface{}{
   695  		"what": "rosebuds",
   696  		"herrick": map[string]interface{}{
   697  			"deepest": map[string]interface{}{
   698  				"what":  "flower",
   699  				"where": "Heaven",
   700  			},
   701  		},
   702  		"global": map[string]interface{}{
   703  			"when": "to-day",
   704  		},
   705  	}
   706  
   707  	tmp, err := chartutil.CoalesceValues(outer, injValues)
   708  	if err != nil {
   709  		t.Fatalf("Failed to coalesce values: %s", err)
   710  	}
   711  
   712  	inject := chartutil.Values{
   713  		"Values": tmp,
   714  		"Chart":  outer.Metadata,
   715  		"Release": chartutil.Values{
   716  			"Name": "dyin",
   717  		},
   718  	}
   719  
   720  	t.Logf("Calculated values: %v", inject)
   721  
   722  	out, err := Render(outer, inject)
   723  	if err != nil {
   724  		t.Fatalf("failed to render templates: %s", err)
   725  	}
   726  
   727  	fullouterpath := "top/" + outerpath
   728  	if out[fullouterpath] != "Gather ye rosebuds while ye may" {
   729  		t.Errorf("Unexpected outer: %q", out[fullouterpath])
   730  	}
   731  
   732  	fullinnerpath := "top/charts/herrick/" + innerpath
   733  	if out[fullinnerpath] != "Old time is still a-flyin'" {
   734  		t.Errorf("Unexpected inner: %q", out[fullinnerpath])
   735  	}
   736  
   737  	fulldeepestpath := "top/charts/herrick/charts/deepest/" + deepestpath
   738  	if out[fulldeepestpath] != "And this same flower that smiles to-day" {
   739  		t.Errorf("Unexpected deepest: %q", out[fulldeepestpath])
   740  	}
   741  
   742  	fullcheckrelease := "top/charts/herrick/charts/deepest/" + checkrelease
   743  	if out[fullcheckrelease] != "Tomorrow will be dyin" {
   744  		t.Errorf("Unexpected release: %q", out[fullcheckrelease])
   745  	}
   746  
   747  	fullchecksubcharts := "top/" + subchartspath
   748  	if out[fullchecksubcharts] != "The glorious Lamp of Heaven, the Sun" {
   749  		t.Errorf("Unexpected subcharts: %q", out[fullchecksubcharts])
   750  	}
   751  }
   752  
   753  func TestRenderBuiltinValues(t *testing.T) {
   754  	inner := &chart.Chart{
   755  		Metadata: &chart.Metadata{Name: "Latium"},
   756  		Templates: []*chart.File{
   757  			{Name: "templates/Lavinia", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)},
   758  			{Name: "templates/From", Data: []byte(`{{.Files.author | printf "%s"}} {{.Files.Get "book/title.txt"}}`)},
   759  		},
   760  		Files: []*chart.File{
   761  			{Name: "author", Data: []byte("Virgil")},
   762  			{Name: "book/title.txt", Data: []byte("Aeneid")},
   763  		},
   764  	}
   765  
   766  	outer := &chart.Chart{
   767  		Metadata: &chart.Metadata{Name: "Troy"},
   768  		Templates: []*chart.File{
   769  			{Name: "templates/Aeneas", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)},
   770  			{Name: "templates/Amata", Data: []byte(`{{.Subcharts.Latium.Chart.Name}} {{.Subcharts.Latium.Files.author | printf "%s"}}`)},
   771  		},
   772  	}
   773  	outer.AddDependency(inner)
   774  
   775  	inject := chartutil.Values{
   776  		"Values": "",
   777  		"Chart":  outer.Metadata,
   778  		"Release": chartutil.Values{
   779  			"Name": "Aeneid",
   780  		},
   781  	}
   782  
   783  	t.Logf("Calculated values: %v", outer)
   784  
   785  	out, err := Render(outer, inject)
   786  	if err != nil {
   787  		t.Fatalf("failed to render templates: %s", err)
   788  	}
   789  
   790  	expects := map[string]string{
   791  		"Troy/charts/Latium/templates/Lavinia": "Troy/charts/Latium/templates/LaviniaLatiumAeneid",
   792  		"Troy/templates/Aeneas":                "Troy/templates/AeneasTroyAeneid",
   793  		"Troy/templates/Amata":                 "Latium Virgil",
   794  		"Troy/charts/Latium/templates/From":    "Virgil Aeneid",
   795  	}
   796  	for file, expect := range expects {
   797  		if out[file] != expect {
   798  			t.Errorf("Expected %q, got %q", expect, out[file])
   799  		}
   800  	}
   801  
   802  }
   803  
   804  func TestAlterFuncMap_include(t *testing.T) {
   805  	c := &chart.Chart{
   806  		Metadata: &chart.Metadata{Name: "conrad"},
   807  		Templates: []*chart.File{
   808  			{Name: "templates/quote", Data: []byte(`{{include "conrad/templates/_partial" . | indent 2}} dead.`)},
   809  			{Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)},
   810  		},
   811  	}
   812  
   813  	// Check nested reference in include FuncMap
   814  	d := &chart.Chart{
   815  		Metadata: &chart.Metadata{Name: "nested"},
   816  		Templates: []*chart.File{
   817  			{Name: "templates/quote", Data: []byte(`{{include "nested/templates/quote" . | indent 2}} dead.`)},
   818  			{Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)},
   819  		},
   820  	}
   821  
   822  	v := chartutil.Values{
   823  		"Values": "",
   824  		"Chart":  c.Metadata,
   825  		"Release": chartutil.Values{
   826  			"Name": "Mistah Kurtz",
   827  		},
   828  	}
   829  
   830  	out, err := Render(c, v)
   831  	if err != nil {
   832  		t.Fatal(err)
   833  	}
   834  
   835  	expect := "  Mistah Kurtz - he dead."
   836  	if got := out["conrad/templates/quote"]; got != expect {
   837  		t.Errorf("Expected %q, got %q (%v)", expect, got, out)
   838  	}
   839  
   840  	_, err = Render(d, v)
   841  	expectErrName := "nested/templates/quote"
   842  	if err == nil {
   843  		t.Errorf("Expected err of nested reference name: %v", expectErrName)
   844  	}
   845  }
   846  
   847  func TestAlterFuncMap_require(t *testing.T) {
   848  	c := &chart.Chart{
   849  		Metadata: &chart.Metadata{Name: "conan"},
   850  		Templates: []*chart.File{
   851  			{Name: "templates/quote", Data: []byte(`All your base are belong to {{ required "A valid 'who' is required" .Values.who }}`)},
   852  			{Name: "templates/bases", Data: []byte(`All {{ required "A valid 'bases' is required" .Values.bases }} of them!`)},
   853  		},
   854  	}
   855  
   856  	v := chartutil.Values{
   857  		"Values": chartutil.Values{
   858  			"who":   "us",
   859  			"bases": 2,
   860  		},
   861  		"Chart": c.Metadata,
   862  		"Release": chartutil.Values{
   863  			"Name": "That 90s meme",
   864  		},
   865  	}
   866  
   867  	out, err := Render(c, v)
   868  	if err != nil {
   869  		t.Fatal(err)
   870  	}
   871  
   872  	expectStr := "All your base are belong to us"
   873  	if gotStr := out["conan/templates/quote"]; gotStr != expectStr {
   874  		t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out)
   875  	}
   876  	expectNum := "All 2 of them!"
   877  	if gotNum := out["conan/templates/bases"]; gotNum != expectNum {
   878  		t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out)
   879  	}
   880  
   881  	// test required without passing in needed values with lint mode on
   882  	// verifies lint replaces required with an empty string (should not fail)
   883  	lintValues := chartutil.Values{
   884  		"Values": chartutil.Values{
   885  			"who": "us",
   886  		},
   887  		"Chart": c.Metadata,
   888  		"Release": chartutil.Values{
   889  			"Name": "That 90s meme",
   890  		},
   891  	}
   892  	var e Engine
   893  	e.LintMode = true
   894  	out, err = e.Render(c, lintValues)
   895  	if err != nil {
   896  		t.Fatal(err)
   897  	}
   898  
   899  	expectStr = "All your base are belong to us"
   900  	if gotStr := out["conan/templates/quote"]; gotStr != expectStr {
   901  		t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out)
   902  	}
   903  	expectNum = "All  of them!"
   904  	if gotNum := out["conan/templates/bases"]; gotNum != expectNum {
   905  		t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out)
   906  	}
   907  }
   908  
   909  func TestAlterFuncMap_tpl(t *testing.T) {
   910  	c := &chart.Chart{
   911  		Metadata: &chart.Metadata{Name: "TplFunction"},
   912  		Templates: []*chart.File{
   913  			{Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value}}" .}}`)},
   914  		},
   915  	}
   916  
   917  	v := chartutil.Values{
   918  		"Values": chartutil.Values{
   919  			"value": "myvalue",
   920  		},
   921  		"Chart": c.Metadata,
   922  		"Release": chartutil.Values{
   923  			"Name": "TestRelease",
   924  		},
   925  	}
   926  
   927  	out, err := Render(c, v)
   928  	if err != nil {
   929  		t.Fatal(err)
   930  	}
   931  
   932  	expect := "Evaluate tpl Value: myvalue"
   933  	if got := out["TplFunction/templates/base"]; got != expect {
   934  		t.Errorf("Expected %q, got %q (%v)", expect, got, out)
   935  	}
   936  }
   937  
   938  func TestAlterFuncMap_tplfunc(t *testing.T) {
   939  	c := &chart.Chart{
   940  		Metadata: &chart.Metadata{Name: "TplFunction"},
   941  		Templates: []*chart.File{
   942  			{Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | quote}}" .}}`)},
   943  		},
   944  	}
   945  
   946  	v := chartutil.Values{
   947  		"Values": chartutil.Values{
   948  			"value": "myvalue",
   949  		},
   950  		"Chart": c.Metadata,
   951  		"Release": chartutil.Values{
   952  			"Name": "TestRelease",
   953  		},
   954  	}
   955  
   956  	out, err := Render(c, v)
   957  	if err != nil {
   958  		t.Fatal(err)
   959  	}
   960  
   961  	expect := "Evaluate tpl Value: \"myvalue\""
   962  	if got := out["TplFunction/templates/base"]; got != expect {
   963  		t.Errorf("Expected %q, got %q (%v)", expect, got, out)
   964  	}
   965  }
   966  
   967  func TestAlterFuncMap_tplinclude(t *testing.T) {
   968  	c := &chart.Chart{
   969  		Metadata: &chart.Metadata{Name: "TplFunction"},
   970  		Templates: []*chart.File{
   971  			{Name: "templates/base", Data: []byte(`{{ tpl "{{include ` + "`" + `TplFunction/templates/_partial` + "`" + ` .  | quote }}" .}}`)},
   972  			{Name: "templates/_partial", Data: []byte(`{{.Template.Name}}`)},
   973  		},
   974  	}
   975  	v := chartutil.Values{
   976  		"Values": chartutil.Values{
   977  			"value": "myvalue",
   978  		},
   979  		"Chart": c.Metadata,
   980  		"Release": chartutil.Values{
   981  			"Name": "TestRelease",
   982  		},
   983  	}
   984  
   985  	out, err := Render(c, v)
   986  	if err != nil {
   987  		t.Fatal(err)
   988  	}
   989  
   990  	expect := "\"TplFunction/templates/base\""
   991  	if got := out["TplFunction/templates/base"]; got != expect {
   992  		t.Errorf("Expected %q, got %q (%v)", expect, got, out)
   993  	}
   994  
   995  }
   996  
   997  func TestRenderRecursionLimit(t *testing.T) {
   998  	// endless recursion should produce an error
   999  	c := &chart.Chart{
  1000  		Metadata: &chart.Metadata{Name: "bad"},
  1001  		Templates: []*chart.File{
  1002  			{Name: "templates/base", Data: []byte(`{{include "recursion" . }}`)},
  1003  			{Name: "templates/recursion", Data: []byte(`{{define "recursion"}}{{include "recursion" . }}{{end}}`)},
  1004  		},
  1005  	}
  1006  	v := chartutil.Values{
  1007  		"Values": "",
  1008  		"Chart":  c.Metadata,
  1009  		"Release": chartutil.Values{
  1010  			"Name": "TestRelease",
  1011  		},
  1012  	}
  1013  	expectErr := "rendering template has a nested reference name: recursion: unable to execute template"
  1014  
  1015  	_, err := Render(c, v)
  1016  	if err == nil || !strings.HasSuffix(err.Error(), expectErr) {
  1017  		t.Errorf("Expected err with suffix: %s", expectErr)
  1018  	}
  1019  
  1020  	// calling the same function many times is ok
  1021  	times := 4000
  1022  	phrase := "All work and no play makes Jack a dull boy"
  1023  	printFunc := `{{define "overlook"}}{{printf "` + phrase + `\n"}}{{end}}`
  1024  	var repeatedIncl string
  1025  	for i := 0; i < times; i++ {
  1026  		repeatedIncl += `{{include "overlook" . }}`
  1027  	}
  1028  
  1029  	d := &chart.Chart{
  1030  		Metadata: &chart.Metadata{Name: "overlook"},
  1031  		Templates: []*chart.File{
  1032  			{Name: "templates/quote", Data: []byte(repeatedIncl)},
  1033  			{Name: "templates/_function", Data: []byte(printFunc)},
  1034  		},
  1035  	}
  1036  
  1037  	out, err := Render(d, v)
  1038  	if err != nil {
  1039  		t.Fatal(err)
  1040  	}
  1041  
  1042  	var expect string
  1043  	for i := 0; i < times; i++ {
  1044  		expect += phrase + "\n"
  1045  	}
  1046  	if got := out["overlook/templates/quote"]; got != expect {
  1047  		t.Errorf("Expected %q, got %q (%v)", expect, got, out)
  1048  	}
  1049  
  1050  }
  1051  
  1052  func TestRenderLoadTemplateForTplFromFile(t *testing.T) {
  1053  	c := &chart.Chart{
  1054  		Metadata: &chart.Metadata{Name: "TplLoadFromFile"},
  1055  		Templates: []*chart.File{
  1056  			{Name: "templates/base", Data: []byte(`{{ tpl (.Files.Get .Values.filename) . }}`)},
  1057  			{Name: "templates/_function", Data: []byte(`{{define "test-function"}}test-function{{end}}`)},
  1058  		},
  1059  		Files: []*chart.File{
  1060  			{Name: "test", Data: []byte(`{{ tpl (.Files.Get .Values.filename2) .}}`)},
  1061  			{Name: "test2", Data: []byte(`{{include "test-function" .}}{{define "nested-define"}}nested-define-content{{end}} {{include "nested-define" .}}`)},
  1062  		},
  1063  	}
  1064  
  1065  	v := chartutil.Values{
  1066  		"Values": chartutil.Values{
  1067  			"filename":  "test",
  1068  			"filename2": "test2",
  1069  		},
  1070  		"Chart": c.Metadata,
  1071  		"Release": chartutil.Values{
  1072  			"Name": "TestRelease",
  1073  		},
  1074  	}
  1075  
  1076  	out, err := Render(c, v)
  1077  	if err != nil {
  1078  		t.Fatal(err)
  1079  	}
  1080  
  1081  	expect := "test-function nested-define-content"
  1082  	if got := out["TplLoadFromFile/templates/base"]; got != expect {
  1083  		t.Fatalf("Expected %q, got %q", expect, got)
  1084  	}
  1085  }
  1086  
  1087  func TestRenderTplEmpty(t *testing.T) {
  1088  	c := &chart.Chart{
  1089  		Metadata: &chart.Metadata{Name: "TplEmpty"},
  1090  		Templates: []*chart.File{
  1091  			{Name: "templates/empty-string", Data: []byte(`{{tpl "" .}}`)},
  1092  			{Name: "templates/empty-action", Data: []byte(`{{tpl "{{ \"\"}}" .}}`)},
  1093  			{Name: "templates/only-defines", Data: []byte(`{{tpl "{{define \"not-invoked\"}}not-rendered{{end}}" .}}`)},
  1094  		},
  1095  	}
  1096  	v := chartutil.Values{
  1097  		"Chart": c.Metadata,
  1098  		"Release": chartutil.Values{
  1099  			"Name": "TestRelease",
  1100  		},
  1101  	}
  1102  
  1103  	out, err := Render(c, v)
  1104  	if err != nil {
  1105  		t.Fatal(err)
  1106  	}
  1107  
  1108  	expects := map[string]string{
  1109  		"TplEmpty/templates/empty-string": "",
  1110  		"TplEmpty/templates/empty-action": "",
  1111  		"TplEmpty/templates/only-defines": "",
  1112  	}
  1113  	for file, expect := range expects {
  1114  		if out[file] != expect {
  1115  			t.Errorf("Expected %q, got %q", expect, out[file])
  1116  		}
  1117  	}
  1118  }
  1119  
  1120  func TestRenderTplTemplateNames(t *testing.T) {
  1121  	// .Template.BasePath and .Name make it through
  1122  	c := &chart.Chart{
  1123  		Metadata: &chart.Metadata{Name: "TplTemplateNames"},
  1124  		Templates: []*chart.File{
  1125  			{Name: "templates/default-basepath", Data: []byte(`{{tpl "{{ .Template.BasePath }}" .}}`)},
  1126  			{Name: "templates/default-name", Data: []byte(`{{tpl "{{ .Template.Name }}" .}}`)},
  1127  			{Name: "templates/modified-basepath", Data: []byte(`{{tpl "{{ .Template.BasePath }}" .Values.dot}}`)},
  1128  			{Name: "templates/modified-name", Data: []byte(`{{tpl "{{ .Template.Name }}" .Values.dot}}`)},
  1129  			{Name: "templates/modified-field", Data: []byte(`{{tpl "{{ .Template.Field }}" .Values.dot}}`)},
  1130  		},
  1131  	}
  1132  	v := chartutil.Values{
  1133  		"Values": chartutil.Values{
  1134  			"dot": chartutil.Values{
  1135  				"Template": chartutil.Values{
  1136  					"BasePath": "path/to/template",
  1137  					"Name":     "name-of-template",
  1138  					"Field":    "extra-field",
  1139  				},
  1140  			},
  1141  		},
  1142  		"Chart": c.Metadata,
  1143  		"Release": chartutil.Values{
  1144  			"Name": "TestRelease",
  1145  		},
  1146  	}
  1147  
  1148  	out, err := Render(c, v)
  1149  	if err != nil {
  1150  		t.Fatal(err)
  1151  	}
  1152  
  1153  	expects := map[string]string{
  1154  		"TplTemplateNames/templates/default-basepath":  "TplTemplateNames/templates",
  1155  		"TplTemplateNames/templates/default-name":      "TplTemplateNames/templates/default-name",
  1156  		"TplTemplateNames/templates/modified-basepath": "path/to/template",
  1157  		"TplTemplateNames/templates/modified-name":     "name-of-template",
  1158  		"TplTemplateNames/templates/modified-field":    "extra-field",
  1159  	}
  1160  	for file, expect := range expects {
  1161  		if out[file] != expect {
  1162  			t.Errorf("Expected %q, got %q", expect, out[file])
  1163  		}
  1164  	}
  1165  }
  1166  
  1167  func TestRenderTplRedefines(t *testing.T) {
  1168  	// Redefining a template inside 'tpl' does not affect the outer definition
  1169  	c := &chart.Chart{
  1170  		Metadata: &chart.Metadata{Name: "TplRedefines"},
  1171  		Templates: []*chart.File{
  1172  			{Name: "templates/_partials", Data: []byte(`{{define "partial"}}original-in-partial{{end}}`)},
  1173  			{Name: "templates/partial", Data: []byte(
  1174  				`before: {{include "partial" .}}\n{{tpl .Values.partialText .}}\nafter: {{include "partial" .}}`,
  1175  			)},
  1176  			{Name: "templates/manifest", Data: []byte(
  1177  				`{{define "manifest"}}original-in-manifest{{end}}` +
  1178  					`before: {{include "manifest" .}}\n{{tpl .Values.manifestText .}}\nafter: {{include "manifest" .}}`,
  1179  			)},
  1180  			{Name: "templates/manifest-only", Data: []byte(
  1181  				`{{define "manifest-only"}}only-in-manifest{{end}}` +
  1182  					`before: {{include "manifest-only" .}}\n{{tpl .Values.manifestOnlyText .}}\nafter: {{include "manifest-only" .}}`,
  1183  			)},
  1184  			{Name: "templates/nested", Data: []byte(
  1185  				`{{define "nested"}}original-in-manifest{{end}}` +
  1186  					`{{define "nested-outer"}}original-outer-in-manifest{{end}}` +
  1187  					`before: {{include "nested" .}} {{include "nested-outer" .}}\n` +
  1188  					`{{tpl .Values.nestedText .}}\n` +
  1189  					`after: {{include "nested" .}} {{include "nested-outer" .}}`,
  1190  			)},
  1191  		},
  1192  	}
  1193  	v := chartutil.Values{
  1194  		"Values": chartutil.Values{
  1195  			"partialText":      `{{define "partial"}}redefined-in-tpl{{end}}tpl: {{include "partial" .}}`,
  1196  			"manifestText":     `{{define "manifest"}}redefined-in-tpl{{end}}tpl: {{include "manifest" .}}`,
  1197  			"manifestOnlyText": `tpl: {{include "manifest-only" .}}`,
  1198  			"nestedText": `{{define "nested"}}redefined-in-tpl{{end}}` +
  1199  				`{{define "nested-outer"}}redefined-outer-in-tpl{{end}}` +
  1200  				`before-inner-tpl: {{include "nested" .}} {{include "nested-outer" . }}\n` +
  1201  				`{{tpl .Values.innerText .}}\n` +
  1202  				`after-inner-tpl: {{include "nested" .}} {{include "nested-outer" . }}`,
  1203  			"innerText": `{{define "nested"}}redefined-in-inner-tpl{{end}}inner-tpl: {{include "nested" .}} {{include "nested-outer" . }}`,
  1204  		},
  1205  		"Chart": c.Metadata,
  1206  		"Release": chartutil.Values{
  1207  			"Name": "TestRelease",
  1208  		},
  1209  	}
  1210  
  1211  	out, err := Render(c, v)
  1212  	if err != nil {
  1213  		t.Fatal(err)
  1214  	}
  1215  
  1216  	expects := map[string]string{
  1217  		"TplRedefines/templates/partial":       `before: original-in-partial\ntpl: redefined-in-tpl\nafter: original-in-partial`,
  1218  		"TplRedefines/templates/manifest":      `before: original-in-manifest\ntpl: redefined-in-tpl\nafter: original-in-manifest`,
  1219  		"TplRedefines/templates/manifest-only": `before: only-in-manifest\ntpl: only-in-manifest\nafter: only-in-manifest`,
  1220  		"TplRedefines/templates/nested": `before: original-in-manifest original-outer-in-manifest\n` +
  1221  			`before-inner-tpl: redefined-in-tpl redefined-outer-in-tpl\n` +
  1222  			`inner-tpl: redefined-in-inner-tpl redefined-outer-in-tpl\n` +
  1223  			`after-inner-tpl: redefined-in-tpl redefined-outer-in-tpl\n` +
  1224  			`after: original-in-manifest original-outer-in-manifest`,
  1225  	}
  1226  	for file, expect := range expects {
  1227  		if out[file] != expect {
  1228  			t.Errorf("Expected %q, got %q", expect, out[file])
  1229  		}
  1230  	}
  1231  }
  1232  
  1233  func TestRenderTplMissingKey(t *testing.T) {
  1234  	// Rendering a missing key results in empty/zero output.
  1235  	c := &chart.Chart{
  1236  		Metadata: &chart.Metadata{Name: "TplMissingKey"},
  1237  		Templates: []*chart.File{
  1238  			{Name: "templates/manifest", Data: []byte(
  1239  				`missingValue: {{tpl "{{.Values.noSuchKey}}" .}}`,
  1240  			)},
  1241  		},
  1242  	}
  1243  	v := chartutil.Values{
  1244  		"Values": chartutil.Values{},
  1245  		"Chart":  c.Metadata,
  1246  		"Release": chartutil.Values{
  1247  			"Name": "TestRelease",
  1248  		},
  1249  	}
  1250  
  1251  	out, err := Render(c, v)
  1252  	if err != nil {
  1253  		t.Fatal(err)
  1254  	}
  1255  
  1256  	expects := map[string]string{
  1257  		"TplMissingKey/templates/manifest": `missingValue: `,
  1258  	}
  1259  	for file, expect := range expects {
  1260  		if out[file] != expect {
  1261  			t.Errorf("Expected %q, got %q", expect, out[file])
  1262  		}
  1263  	}
  1264  }
  1265  
  1266  func TestRenderTplMissingKeyString(t *testing.T) {
  1267  	// Rendering a missing key results in error
  1268  	c := &chart.Chart{
  1269  		Metadata: &chart.Metadata{Name: "TplMissingKeyStrict"},
  1270  		Templates: []*chart.File{
  1271  			{Name: "templates/manifest", Data: []byte(
  1272  				`missingValue: {{tpl "{{.Values.noSuchKey}}" .}}`,
  1273  			)},
  1274  		},
  1275  	}
  1276  	v := chartutil.Values{
  1277  		"Values": chartutil.Values{},
  1278  		"Chart":  c.Metadata,
  1279  		"Release": chartutil.Values{
  1280  			"Name": "TestRelease",
  1281  		},
  1282  	}
  1283  
  1284  	e := new(Engine)
  1285  	e.Strict = true
  1286  
  1287  	out, err := e.Render(c, v)
  1288  	if err == nil {
  1289  		t.Errorf("Expected error, got %v", out)
  1290  		return
  1291  	}
  1292  	switch err.(type) {
  1293  	case (template.ExecError):
  1294  		errTxt := fmt.Sprint(err)
  1295  		if !strings.Contains(errTxt, "noSuchKey") {
  1296  			t.Errorf("Expected error to contain 'noSuchKey', got %s", errTxt)
  1297  		}
  1298  	default:
  1299  		// Some unexpected error.
  1300  		t.Fatal(err)
  1301  	}
  1302  }
  1303  

View as plain text