github.com/diafour/helm@v3.0.0-beta.3+incompatible/pkg/engine/engine_test.go (about) 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 "strings" 22 "sync" 23 "testing" 24 25 "helm.sh/helm/pkg/chart" 26 "helm.sh/helm/pkg/chartutil" 27 ) 28 29 func TestSortTemplates(t *testing.T) { 30 tpls := map[string]renderable{ 31 "/mychart/templates/foo.tpl": {}, 32 "/mychart/templates/charts/foo/charts/bar/templates/foo.tpl": {}, 33 "/mychart/templates/bar.tpl": {}, 34 "/mychart/templates/charts/foo/templates/bar.tpl": {}, 35 "/mychart/templates/_foo.tpl": {}, 36 "/mychart/templates/charts/foo/templates/foo.tpl": {}, 37 "/mychart/templates/charts/bar/templates/foo.tpl": {}, 38 } 39 got := sortTemplates(tpls) 40 if len(got) != len(tpls) { 41 t.Fatal("Sorted results are missing templates") 42 } 43 44 expect := []string{ 45 "/mychart/templates/charts/foo/charts/bar/templates/foo.tpl", 46 "/mychart/templates/charts/foo/templates/foo.tpl", 47 "/mychart/templates/charts/foo/templates/bar.tpl", 48 "/mychart/templates/charts/bar/templates/foo.tpl", 49 "/mychart/templates/foo.tpl", 50 "/mychart/templates/bar.tpl", 51 "/mychart/templates/_foo.tpl", 52 } 53 for i, e := range expect { 54 if got[i] != e { 55 t.Fatalf("\n\tExp:\n%s\n\tGot:\n%s", 56 strings.Join(expect, "\n"), 57 strings.Join(got, "\n"), 58 ) 59 } 60 } 61 } 62 63 func TestFuncMap(t *testing.T) { 64 fns := funcMap() 65 forbidden := []string{"env", "expandenv"} 66 for _, f := range forbidden { 67 if _, ok := fns[f]; ok { 68 t.Errorf("Forbidden function %s exists in FuncMap.", f) 69 } 70 } 71 72 // Test for Engine-specific template functions. 73 expect := []string{"include", "required", "tpl", "toYaml", "fromYaml", "toToml", "toJson", "fromJson"} 74 for _, f := range expect { 75 if _, ok := fns[f]; !ok { 76 t.Errorf("Expected add-on function %q", f) 77 } 78 } 79 } 80 81 func TestRender(t *testing.T) { 82 c := &chart.Chart{ 83 Metadata: &chart.Metadata{ 84 Name: "moby", 85 Version: "1.2.3", 86 }, 87 Templates: []*chart.File{ 88 {Name: "templates/test1", Data: []byte("{{.Values.outer | title }} {{.Values.inner | title}}")}, 89 {Name: "templates/test2", Data: []byte("{{.Values.global.callme | lower }}")}, 90 {Name: "templates/test3", Data: []byte("{{.noValue}}")}, 91 {Name: "templates/test4", Data: []byte("{{toJson .Values}}")}, 92 }, 93 Values: map[string]interface{}{"outer": "DEFAULT", "inner": "DEFAULT"}, 94 } 95 96 vals := map[string]interface{}{ 97 "Values": map[string]interface{}{ 98 "outer": "spouter", 99 "inner": "inn", 100 "global": map[string]interface{}{ 101 "callme": "Ishmael", 102 }, 103 }, 104 } 105 106 v, err := chartutil.CoalesceValues(c, vals) 107 if err != nil { 108 t.Fatalf("Failed to coalesce values: %s", err) 109 } 110 out, err := Render(c, v) 111 if err != nil { 112 t.Errorf("Failed to render templates: %s", err) 113 } 114 115 expect := map[string]string{ 116 "moby/templates/test1": "Spouter Inn", 117 "moby/templates/test2": "ishmael", 118 "moby/templates/test3": "", 119 "moby/templates/test4": `{"global":{"callme":"Ishmael"},"inner":"inn","outer":"spouter"}`, 120 } 121 122 for name, data := range expect { 123 if out[name] != data { 124 t.Errorf("Expected %q, got %q", data, out[name]) 125 } 126 } 127 } 128 129 func TestRenderInternals(t *testing.T) { 130 // Test the internals of the rendering tool. 131 132 vals := chartutil.Values{"Name": "one", "Value": "two"} 133 tpls := map[string]renderable{ 134 "one": {tpl: `Hello {{title .Name}}`, vals: vals}, 135 "two": {tpl: `Goodbye {{upper .Value}}`, vals: vals}, 136 // Test whether a template can reliably reference another template 137 // without regard for ordering. 138 "three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals}, 139 } 140 141 out, err := new(Engine).render(tpls) 142 if err != nil { 143 t.Fatalf("Failed template rendering: %s", err) 144 } 145 146 if len(out) != 3 { 147 t.Fatalf("Expected 3 templates, got %d", len(out)) 148 } 149 150 if out["one"] != "Hello One" { 151 t.Errorf("Expected 'Hello One', got %q", out["one"]) 152 } 153 154 if out["two"] != "Goodbye TWO" { 155 t.Errorf("Expected 'Goodbye TWO'. got %q", out["two"]) 156 } 157 158 if out["three"] != "Goodbye THREE" { 159 t.Errorf("Expected 'Goodbye THREE'. got %q", out["two"]) 160 } 161 } 162 163 func TestParallelRenderInternals(t *testing.T) { 164 // Make sure that we can use one Engine to run parallel template renders. 165 e := new(Engine) 166 var wg sync.WaitGroup 167 for i := 0; i < 20; i++ { 168 wg.Add(1) 169 go func(i int) { 170 tt := fmt.Sprintf("expect-%d", i) 171 tpls := map[string]renderable{ 172 "t": { 173 tpl: `{{.val}}`, 174 vals: map[string]interface{}{"val": tt}, 175 }, 176 } 177 out, err := e.render(tpls) 178 if err != nil { 179 t.Errorf("Failed to render %s: %s", tt, err) 180 } 181 if out["t"] != tt { 182 t.Errorf("Expected %q, got %q", tt, out["t"]) 183 } 184 wg.Done() 185 }(i) 186 } 187 wg.Wait() 188 } 189 190 func TestParseErrors(t *testing.T) { 191 vals := chartutil.Values{"Values": map[string]interface{}{}} 192 193 tplsUndefinedFunction := map[string]renderable{ 194 "undefined_function": {tpl: `{{foo}}`, vals: vals}, 195 } 196 _, err := new(Engine).render(tplsUndefinedFunction) 197 if err == nil { 198 t.Fatalf("Expected failures while rendering: %s", err) 199 } 200 expected := `parse error at (undefined_function:1): function "foo" not defined` 201 if err.Error() != expected { 202 t.Errorf("Expected '%s', got %q", expected, err.Error()) 203 } 204 } 205 206 func TestExecErrors(t *testing.T) { 207 vals := chartutil.Values{"Values": map[string]interface{}{}} 208 209 tplsMissingRequired := map[string]renderable{ 210 "missing_required": {tpl: `{{required "foo is required" .Values.foo}}`, vals: vals}, 211 } 212 _, err := new(Engine).render(tplsMissingRequired) 213 if err == nil { 214 t.Fatalf("Expected failures while rendering: %s", err) 215 } 216 expected := `execution error at (missing_required:1:2): foo is required` 217 if err.Error() != expected { 218 t.Errorf("Expected '%s', got %q", expected, err.Error()) 219 } 220 221 tplsMissingRequired = map[string]renderable{ 222 "missing_required_with_colons": {tpl: `{{required ":this: message: has many: colons:" .Values.foo}}`, vals: vals}, 223 } 224 _, err = new(Engine).render(tplsMissingRequired) 225 if err == nil { 226 t.Fatalf("Expected failures while rendering: %s", err) 227 } 228 expected = `execution error at (missing_required_with_colons:1:2): :this: message: has many: colons:` 229 if err.Error() != expected { 230 t.Errorf("Expected '%s', got %q", expected, err.Error()) 231 } 232 233 issue6044tpl := `{{ $someEmptyValue := "" }} 234 {{ $myvar := "abc" }} 235 {{- required (printf "%s: something is missing" $myvar) $someEmptyValue | repeat 0 }}` 236 tplsMissingRequired = map[string]renderable{ 237 "issue6044": {tpl: issue6044tpl, vals: vals}, 238 } 239 _, err = new(Engine).render(tplsMissingRequired) 240 if err == nil { 241 t.Fatalf("Expected failures while rendering: %s", err) 242 } 243 expected = `execution error at (issue6044:3:4): abc: something is missing` 244 if err.Error() != expected { 245 t.Errorf("Expected '%s', got %q", expected, err.Error()) 246 } 247 } 248 249 func TestAllTemplates(t *testing.T) { 250 ch1 := &chart.Chart{ 251 Metadata: &chart.Metadata{Name: "ch1"}, 252 Templates: []*chart.File{ 253 {Name: "templates/foo", Data: []byte("foo")}, 254 {Name: "templates/bar", Data: []byte("bar")}, 255 }, 256 } 257 dep1 := &chart.Chart{ 258 Metadata: &chart.Metadata{Name: "laboratory mice"}, 259 Templates: []*chart.File{ 260 {Name: "templates/pinky", Data: []byte("pinky")}, 261 {Name: "templates/brain", Data: []byte("brain")}, 262 }, 263 } 264 ch1.AddDependency(dep1) 265 266 dep2 := &chart.Chart{ 267 Metadata: &chart.Metadata{Name: "same thing we do every night"}, 268 Templates: []*chart.File{ 269 {Name: "templates/innermost", Data: []byte("innermost")}, 270 }, 271 } 272 dep1.AddDependency(dep2) 273 274 tpls := allTemplates(ch1, chartutil.Values{}) 275 if len(tpls) != 5 { 276 t.Errorf("Expected 5 charts, got %d", len(tpls)) 277 } 278 } 279 280 func TestRenderDependency(t *testing.T) { 281 deptpl := `{{define "myblock"}}World{{end}}` 282 toptpl := `Hello {{template "myblock"}}` 283 ch := &chart.Chart{ 284 Metadata: &chart.Metadata{Name: "outerchart"}, 285 Templates: []*chart.File{ 286 {Name: "templates/outer", Data: []byte(toptpl)}, 287 }, 288 } 289 ch.AddDependency(&chart.Chart{ 290 Metadata: &chart.Metadata{Name: "innerchart"}, 291 Templates: []*chart.File{ 292 {Name: "templates/inner", Data: []byte(deptpl)}, 293 }, 294 }) 295 296 out, err := Render(ch, map[string]interface{}{}) 297 if err != nil { 298 t.Fatalf("failed to render chart: %s", err) 299 } 300 301 if len(out) != 2 { 302 t.Errorf("Expected 2, got %d", len(out)) 303 } 304 305 expect := "Hello World" 306 if out["outerchart/templates/outer"] != expect { 307 t.Errorf("Expected %q, got %q", expect, out["outer"]) 308 } 309 310 } 311 312 func TestRenderNestedValues(t *testing.T) { 313 innerpath := "templates/inner.tpl" 314 outerpath := "templates/outer.tpl" 315 // Ensure namespacing rules are working. 316 deepestpath := "templates/inner.tpl" 317 checkrelease := "templates/release.tpl" 318 319 deepest := &chart.Chart{ 320 Metadata: &chart.Metadata{Name: "deepest"}, 321 Templates: []*chart.File{ 322 {Name: deepestpath, Data: []byte(`And this same {{.Values.what}} that smiles {{.Values.global.when}}`)}, 323 {Name: checkrelease, Data: []byte(`Tomorrow will be {{default "happy" .Release.Name }}`)}, 324 }, 325 Values: map[string]interface{}{"what": "milkshake"}, 326 } 327 328 inner := &chart.Chart{ 329 Metadata: &chart.Metadata{Name: "herrick"}, 330 Templates: []*chart.File{ 331 {Name: innerpath, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)}, 332 }, 333 Values: map[string]interface{}{"who": "Robert"}, 334 } 335 inner.AddDependency(deepest) 336 337 outer := &chart.Chart{ 338 Metadata: &chart.Metadata{Name: "top"}, 339 Templates: []*chart.File{ 340 {Name: outerpath, Data: []byte(`Gather ye {{.Values.what}} while ye may`)}, 341 }, 342 Values: map[string]interface{}{ 343 "what": "stinkweed", 344 "who": "me", 345 "herrick": map[string]interface{}{ 346 "who": "time", 347 }, 348 }, 349 } 350 outer.AddDependency(inner) 351 352 injValues := map[string]interface{}{ 353 "what": "rosebuds", 354 "herrick": map[string]interface{}{ 355 "deepest": map[string]interface{}{ 356 "what": "flower", 357 }, 358 }, 359 "global": map[string]interface{}{ 360 "when": "to-day", 361 }, 362 } 363 364 tmp, err := chartutil.CoalesceValues(outer, injValues) 365 if err != nil { 366 t.Fatalf("Failed to coalesce values: %s", err) 367 } 368 369 inject := chartutil.Values{ 370 "Values": tmp, 371 "Chart": outer.Metadata, 372 "Release": chartutil.Values{ 373 "Name": "dyin", 374 }, 375 } 376 377 t.Logf("Calculated values: %v", inject) 378 379 out, err := Render(outer, inject) 380 if err != nil { 381 t.Fatalf("failed to render templates: %s", err) 382 } 383 384 fullouterpath := "top/" + outerpath 385 if out[fullouterpath] != "Gather ye rosebuds while ye may" { 386 t.Errorf("Unexpected outer: %q", out[fullouterpath]) 387 } 388 389 fullinnerpath := "top/charts/herrick/" + innerpath 390 if out[fullinnerpath] != "Old time is still a-flyin'" { 391 t.Errorf("Unexpected inner: %q", out[fullinnerpath]) 392 } 393 394 fulldeepestpath := "top/charts/herrick/charts/deepest/" + deepestpath 395 if out[fulldeepestpath] != "And this same flower that smiles to-day" { 396 t.Errorf("Unexpected deepest: %q", out[fulldeepestpath]) 397 } 398 399 fullcheckrelease := "top/charts/herrick/charts/deepest/" + checkrelease 400 if out[fullcheckrelease] != "Tomorrow will be dyin" { 401 t.Errorf("Unexpected release: %q", out[fullcheckrelease]) 402 } 403 } 404 405 func TestRenderBuiltinValues(t *testing.T) { 406 inner := &chart.Chart{ 407 Metadata: &chart.Metadata{Name: "Latium"}, 408 Templates: []*chart.File{ 409 {Name: "templates/Lavinia", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, 410 {Name: "templates/From", Data: []byte(`{{.Files.author | printf "%s"}} {{.Files.Get "book/title.txt"}}`)}, 411 }, 412 Files: []*chart.File{ 413 {Name: "author", Data: []byte("Virgil")}, 414 {Name: "book/title.txt", Data: []byte("Aeneid")}, 415 }, 416 } 417 418 outer := &chart.Chart{ 419 Metadata: &chart.Metadata{Name: "Troy"}, 420 Templates: []*chart.File{ 421 {Name: "templates/Aeneas", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, 422 }, 423 } 424 outer.AddDependency(inner) 425 426 inject := chartutil.Values{ 427 "Values": "", 428 "Chart": outer.Metadata, 429 "Release": chartutil.Values{ 430 "Name": "Aeneid", 431 }, 432 } 433 434 t.Logf("Calculated values: %v", outer) 435 436 out, err := Render(outer, inject) 437 if err != nil { 438 t.Fatalf("failed to render templates: %s", err) 439 } 440 441 expects := map[string]string{ 442 "Troy/charts/Latium/templates/Lavinia": "Troy/charts/Latium/templates/LaviniaLatiumAeneid", 443 "Troy/templates/Aeneas": "Troy/templates/AeneasTroyAeneid", 444 "Troy/charts/Latium/templates/From": "Virgil Aeneid", 445 } 446 for file, expect := range expects { 447 if out[file] != expect { 448 t.Errorf("Expected %q, got %q", expect, out[file]) 449 } 450 } 451 452 } 453 454 func TestAlterFuncMap_include(t *testing.T) { 455 c := &chart.Chart{ 456 Metadata: &chart.Metadata{Name: "conrad"}, 457 Templates: []*chart.File{ 458 {Name: "templates/quote", Data: []byte(`{{include "conrad/templates/_partial" . | indent 2}} dead.`)}, 459 {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)}, 460 }, 461 } 462 463 v := chartutil.Values{ 464 "Values": "", 465 "Chart": c.Metadata, 466 "Release": chartutil.Values{ 467 "Name": "Mistah Kurtz", 468 }, 469 } 470 471 out, err := Render(c, v) 472 if err != nil { 473 t.Fatal(err) 474 } 475 476 expect := " Mistah Kurtz - he dead." 477 if got := out["conrad/templates/quote"]; got != expect { 478 t.Errorf("Expected %q, got %q (%v)", expect, got, out) 479 } 480 } 481 482 func TestAlterFuncMap_require(t *testing.T) { 483 c := &chart.Chart{ 484 Metadata: &chart.Metadata{Name: "conan"}, 485 Templates: []*chart.File{ 486 {Name: "templates/quote", Data: []byte(`All your base are belong to {{ required "A valid 'who' is required" .Values.who }}`)}, 487 {Name: "templates/bases", Data: []byte(`All {{ required "A valid 'bases' is required" .Values.bases }} of them!`)}, 488 }, 489 } 490 491 v := chartutil.Values{ 492 "Values": chartutil.Values{ 493 "who": "us", 494 "bases": 2, 495 }, 496 "Chart": c.Metadata, 497 "Release": chartutil.Values{ 498 "Name": "That 90s meme", 499 }, 500 } 501 502 out, err := Render(c, v) 503 if err != nil { 504 t.Fatal(err) 505 } 506 507 expectStr := "All your base are belong to us" 508 if gotStr := out["conan/templates/quote"]; gotStr != expectStr { 509 t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) 510 } 511 expectNum := "All 2 of them!" 512 if gotNum := out["conan/templates/bases"]; gotNum != expectNum { 513 t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out) 514 } 515 516 // test required without passing in needed values with lint mode on 517 // verifies lint replaces required with an empty string (should not fail) 518 lintValues := chartutil.Values{ 519 "Values": chartutil.Values{ 520 "who": "us", 521 }, 522 "Chart": c.Metadata, 523 "Release": chartutil.Values{ 524 "Name": "That 90s meme", 525 }, 526 } 527 var e Engine 528 e.LintMode = true 529 out, err = e.Render(c, lintValues) 530 if err != nil { 531 t.Fatal(err) 532 } 533 534 expectStr = "All your base are belong to us" 535 if gotStr := out["conan/templates/quote"]; gotStr != expectStr { 536 t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) 537 } 538 expectNum = "All of them!" 539 if gotNum := out["conan/templates/bases"]; gotNum != expectNum { 540 t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out) 541 } 542 } 543 544 func TestAlterFuncMap_tpl(t *testing.T) { 545 c := &chart.Chart{ 546 Metadata: &chart.Metadata{Name: "TplFunction"}, 547 Templates: []*chart.File{ 548 {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value}}" .}}`)}, 549 }, 550 } 551 552 v := chartutil.Values{ 553 "Values": chartutil.Values{ 554 "value": "myvalue", 555 }, 556 "Chart": c.Metadata, 557 "Release": chartutil.Values{ 558 "Name": "TestRelease", 559 }, 560 } 561 562 out, err := Render(c, v) 563 if err != nil { 564 t.Fatal(err) 565 } 566 567 expect := "Evaluate tpl Value: myvalue" 568 if got := out["TplFunction/templates/base"]; got != expect { 569 t.Errorf("Expected %q, got %q (%v)", expect, got, out) 570 } 571 } 572 573 func TestAlterFuncMap_tplfunc(t *testing.T) { 574 c := &chart.Chart{ 575 Metadata: &chart.Metadata{Name: "TplFunction"}, 576 Templates: []*chart.File{ 577 {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | quote}}" .}}`)}, 578 }, 579 } 580 581 v := chartutil.Values{ 582 "Values": chartutil.Values{ 583 "value": "myvalue", 584 }, 585 "Chart": c.Metadata, 586 "Release": chartutil.Values{ 587 "Name": "TestRelease", 588 }, 589 } 590 591 out, err := Render(c, v) 592 if err != nil { 593 t.Fatal(err) 594 } 595 596 expect := "Evaluate tpl Value: \"myvalue\"" 597 if got := out["TplFunction/templates/base"]; got != expect { 598 t.Errorf("Expected %q, got %q (%v)", expect, got, out) 599 } 600 } 601 602 func TestAlterFuncMap_tplinclude(t *testing.T) { 603 c := &chart.Chart{ 604 Metadata: &chart.Metadata{Name: "TplFunction"}, 605 Templates: []*chart.File{ 606 {Name: "templates/base", Data: []byte(`{{ tpl "{{include ` + "`" + `TplFunction/templates/_partial` + "`" + ` . | quote }}" .}}`)}, 607 {Name: "templates/_partial", Data: []byte(`{{.Template.Name}}`)}, 608 }, 609 } 610 v := chartutil.Values{ 611 "Values": chartutil.Values{ 612 "value": "myvalue", 613 }, 614 "Chart": c.Metadata, 615 "Release": chartutil.Values{ 616 "Name": "TestRelease", 617 }, 618 } 619 620 out, err := Render(c, v) 621 if err != nil { 622 t.Fatal(err) 623 } 624 625 expect := "\"TplFunction/templates/base\"" 626 if got := out["TplFunction/templates/base"]; got != expect { 627 t.Errorf("Expected %q, got %q (%v)", expect, got, out) 628 } 629 630 }