github.com/azure-devops-engineer/helm@v3.0.0-alpha.2+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 TestRenderErrors(t *testing.T) { 191 vals := chartutil.Values{"Values": map[string]interface{}{}} 192 193 tplsMissingRequired := map[string]renderable{ 194 "missing_required": {tpl: `{{required "foo is required" .Values.foo}}`, vals: vals}, 195 } 196 _, err := new(Engine).render(tplsMissingRequired) 197 if err == nil { 198 t.Fatalf("Expected failures while rendering: %s", err) 199 } 200 expected := `render error at (missing_required:1:2): foo is required` 201 if err.Error() != expected { 202 t.Errorf("Expected '%s', got %q", expected, err.Error()) 203 } 204 205 tplsUndefinedFunction := map[string]renderable{ 206 "undefined_function": {tpl: `{{foo}}`, vals: vals}, 207 } 208 _, err = new(Engine).render(tplsUndefinedFunction) 209 if err == nil { 210 t.Fatalf("Expected failures while rendering: %s", err) 211 } 212 expected = `render error at (undefined_function:1): function "foo" not defined` 213 if err.Error() != expected { 214 t.Errorf("Expected '%s', got %q", expected, err.Error()) 215 } 216 } 217 func TestAllTemplates(t *testing.T) { 218 ch1 := &chart.Chart{ 219 Metadata: &chart.Metadata{Name: "ch1"}, 220 Templates: []*chart.File{ 221 {Name: "templates/foo", Data: []byte("foo")}, 222 {Name: "templates/bar", Data: []byte("bar")}, 223 }, 224 } 225 dep1 := &chart.Chart{ 226 Metadata: &chart.Metadata{Name: "laboratory mice"}, 227 Templates: []*chart.File{ 228 {Name: "templates/pinky", Data: []byte("pinky")}, 229 {Name: "templates/brain", Data: []byte("brain")}, 230 }, 231 } 232 ch1.AddDependency(dep1) 233 234 dep2 := &chart.Chart{ 235 Metadata: &chart.Metadata{Name: "same thing we do every night"}, 236 Templates: []*chart.File{ 237 {Name: "templates/innermost", Data: []byte("innermost")}, 238 }, 239 } 240 dep1.AddDependency(dep2) 241 242 tpls := allTemplates(ch1, chartutil.Values{}) 243 if len(tpls) != 5 { 244 t.Errorf("Expected 5 charts, got %d", len(tpls)) 245 } 246 } 247 248 func TestRenderDependency(t *testing.T) { 249 deptpl := `{{define "myblock"}}World{{end}}` 250 toptpl := `Hello {{template "myblock"}}` 251 ch := &chart.Chart{ 252 Metadata: &chart.Metadata{Name: "outerchart"}, 253 Templates: []*chart.File{ 254 {Name: "templates/outer", Data: []byte(toptpl)}, 255 }, 256 } 257 ch.AddDependency(&chart.Chart{ 258 Metadata: &chart.Metadata{Name: "innerchart"}, 259 Templates: []*chart.File{ 260 {Name: "templates/inner", Data: []byte(deptpl)}, 261 }, 262 }) 263 264 out, err := Render(ch, map[string]interface{}{}) 265 if err != nil { 266 t.Fatalf("failed to render chart: %s", err) 267 } 268 269 if len(out) != 2 { 270 t.Errorf("Expected 2, got %d", len(out)) 271 } 272 273 expect := "Hello World" 274 if out["outerchart/templates/outer"] != expect { 275 t.Errorf("Expected %q, got %q", expect, out["outer"]) 276 } 277 278 } 279 280 func TestRenderNestedValues(t *testing.T) { 281 innerpath := "templates/inner.tpl" 282 outerpath := "templates/outer.tpl" 283 // Ensure namespacing rules are working. 284 deepestpath := "templates/inner.tpl" 285 checkrelease := "templates/release.tpl" 286 287 deepest := &chart.Chart{ 288 Metadata: &chart.Metadata{Name: "deepest"}, 289 Templates: []*chart.File{ 290 {Name: deepestpath, Data: []byte(`And this same {{.Values.what}} that smiles {{.Values.global.when}}`)}, 291 {Name: checkrelease, Data: []byte(`Tomorrow will be {{default "happy" .Release.Name }}`)}, 292 }, 293 Values: map[string]interface{}{"what": "milkshake"}, 294 } 295 296 inner := &chart.Chart{ 297 Metadata: &chart.Metadata{Name: "herrick"}, 298 Templates: []*chart.File{ 299 {Name: innerpath, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)}, 300 }, 301 Values: map[string]interface{}{"who": "Robert"}, 302 } 303 inner.AddDependency(deepest) 304 305 outer := &chart.Chart{ 306 Metadata: &chart.Metadata{Name: "top"}, 307 Templates: []*chart.File{ 308 {Name: outerpath, Data: []byte(`Gather ye {{.Values.what}} while ye may`)}, 309 }, 310 Values: map[string]interface{}{ 311 "what": "stinkweed", 312 "who": "me", 313 "herrick": map[string]interface{}{ 314 "who": "time", 315 }, 316 }, 317 } 318 outer.AddDependency(inner) 319 320 injValues := map[string]interface{}{ 321 "what": "rosebuds", 322 "herrick": map[string]interface{}{ 323 "deepest": map[string]interface{}{ 324 "what": "flower", 325 }, 326 }, 327 "global": map[string]interface{}{ 328 "when": "to-day", 329 }, 330 } 331 332 tmp, err := chartutil.CoalesceValues(outer, injValues) 333 if err != nil { 334 t.Fatalf("Failed to coalesce values: %s", err) 335 } 336 337 inject := chartutil.Values{ 338 "Values": tmp, 339 "Chart": outer.Metadata, 340 "Release": chartutil.Values{ 341 "Name": "dyin", 342 }, 343 } 344 345 t.Logf("Calculated values: %v", inject) 346 347 out, err := Render(outer, inject) 348 if err != nil { 349 t.Fatalf("failed to render templates: %s", err) 350 } 351 352 fullouterpath := "top/" + outerpath 353 if out[fullouterpath] != "Gather ye rosebuds while ye may" { 354 t.Errorf("Unexpected outer: %q", out[fullouterpath]) 355 } 356 357 fullinnerpath := "top/charts/herrick/" + innerpath 358 if out[fullinnerpath] != "Old time is still a-flyin'" { 359 t.Errorf("Unexpected inner: %q", out[fullinnerpath]) 360 } 361 362 fulldeepestpath := "top/charts/herrick/charts/deepest/" + deepestpath 363 if out[fulldeepestpath] != "And this same flower that smiles to-day" { 364 t.Errorf("Unexpected deepest: %q", out[fulldeepestpath]) 365 } 366 367 fullcheckrelease := "top/charts/herrick/charts/deepest/" + checkrelease 368 if out[fullcheckrelease] != "Tomorrow will be dyin" { 369 t.Errorf("Unexpected release: %q", out[fullcheckrelease]) 370 } 371 } 372 373 func TestRenderBuiltinValues(t *testing.T) { 374 inner := &chart.Chart{ 375 Metadata: &chart.Metadata{Name: "Latium"}, 376 Templates: []*chart.File{ 377 {Name: "templates/Lavinia", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, 378 {Name: "templates/From", Data: []byte(`{{.Files.author | printf "%s"}} {{.Files.Get "book/title.txt"}}`)}, 379 }, 380 Files: []*chart.File{ 381 {Name: "author", Data: []byte("Virgil")}, 382 {Name: "book/title.txt", Data: []byte("Aeneid")}, 383 }, 384 } 385 386 outer := &chart.Chart{ 387 Metadata: &chart.Metadata{Name: "Troy"}, 388 Templates: []*chart.File{ 389 {Name: "templates/Aeneas", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, 390 }, 391 } 392 outer.AddDependency(inner) 393 394 inject := chartutil.Values{ 395 "Values": "", 396 "Chart": outer.Metadata, 397 "Release": chartutil.Values{ 398 "Name": "Aeneid", 399 }, 400 } 401 402 t.Logf("Calculated values: %v", outer) 403 404 out, err := Render(outer, inject) 405 if err != nil { 406 t.Fatalf("failed to render templates: %s", err) 407 } 408 409 expects := map[string]string{ 410 "Troy/charts/Latium/templates/Lavinia": "Troy/charts/Latium/templates/LaviniaLatiumAeneid", 411 "Troy/templates/Aeneas": "Troy/templates/AeneasTroyAeneid", 412 "Troy/charts/Latium/templates/From": "Virgil Aeneid", 413 } 414 for file, expect := range expects { 415 if out[file] != expect { 416 t.Errorf("Expected %q, got %q", expect, out[file]) 417 } 418 } 419 420 } 421 422 func TestAlterFuncMap_include(t *testing.T) { 423 c := &chart.Chart{ 424 Metadata: &chart.Metadata{Name: "conrad"}, 425 Templates: []*chart.File{ 426 {Name: "templates/quote", Data: []byte(`{{include "conrad/templates/_partial" . | indent 2}} dead.`)}, 427 {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)}, 428 }, 429 } 430 431 v := chartutil.Values{ 432 "Values": "", 433 "Chart": c.Metadata, 434 "Release": chartutil.Values{ 435 "Name": "Mistah Kurtz", 436 }, 437 } 438 439 out, err := Render(c, v) 440 if err != nil { 441 t.Fatal(err) 442 } 443 444 expect := " Mistah Kurtz - he dead." 445 if got := out["conrad/templates/quote"]; got != expect { 446 t.Errorf("Expected %q, got %q (%v)", expect, got, out) 447 } 448 } 449 450 func TestAlterFuncMap_require(t *testing.T) { 451 c := &chart.Chart{ 452 Metadata: &chart.Metadata{Name: "conan"}, 453 Templates: []*chart.File{ 454 {Name: "templates/quote", Data: []byte(`All your base are belong to {{ required "A valid 'who' is required" .Values.who }}`)}, 455 {Name: "templates/bases", Data: []byte(`All {{ required "A valid 'bases' is required" .Values.bases }} of them!`)}, 456 }, 457 } 458 459 v := chartutil.Values{ 460 "Values": chartutil.Values{ 461 "who": "us", 462 "bases": 2, 463 }, 464 "Chart": c.Metadata, 465 "Release": chartutil.Values{ 466 "Name": "That 90s meme", 467 }, 468 } 469 470 out, err := Render(c, v) 471 if err != nil { 472 t.Fatal(err) 473 } 474 475 expectStr := "All your base are belong to us" 476 if gotStr := out["conan/templates/quote"]; gotStr != expectStr { 477 t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) 478 } 479 expectNum := "All 2 of them!" 480 if gotNum := out["conan/templates/bases"]; gotNum != expectNum { 481 t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out) 482 } 483 484 // test required without passing in needed values with lint mode on 485 // verifies lint replaces required with an empty string (should not fail) 486 lintValues := chartutil.Values{ 487 "Values": chartutil.Values{ 488 "who": "us", 489 }, 490 "Chart": c.Metadata, 491 "Release": chartutil.Values{ 492 "Name": "That 90s meme", 493 }, 494 } 495 var e Engine 496 e.LintMode = true 497 out, err = e.Render(c, lintValues) 498 if err != nil { 499 t.Fatal(err) 500 } 501 502 expectStr = "All your base are belong to us" 503 if gotStr := out["conan/templates/quote"]; gotStr != expectStr { 504 t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) 505 } 506 expectNum = "All of them!" 507 if gotNum := out["conan/templates/bases"]; gotNum != expectNum { 508 t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out) 509 } 510 } 511 512 func TestAlterFuncMap_tpl(t *testing.T) { 513 c := &chart.Chart{ 514 Metadata: &chart.Metadata{Name: "TplFunction"}, 515 Templates: []*chart.File{ 516 {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value}}" .}}`)}, 517 }, 518 } 519 520 v := chartutil.Values{ 521 "Values": chartutil.Values{ 522 "value": "myvalue", 523 }, 524 "Chart": c.Metadata, 525 "Release": chartutil.Values{ 526 "Name": "TestRelease", 527 }, 528 } 529 530 out, err := Render(c, v) 531 if err != nil { 532 t.Fatal(err) 533 } 534 535 expect := "Evaluate tpl Value: myvalue" 536 if got := out["TplFunction/templates/base"]; got != expect { 537 t.Errorf("Expected %q, got %q (%v)", expect, got, out) 538 } 539 } 540 541 func TestAlterFuncMap_tplfunc(t *testing.T) { 542 c := &chart.Chart{ 543 Metadata: &chart.Metadata{Name: "TplFunction"}, 544 Templates: []*chart.File{ 545 {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | quote}}" .}}`)}, 546 }, 547 } 548 549 v := chartutil.Values{ 550 "Values": chartutil.Values{ 551 "value": "myvalue", 552 }, 553 "Chart": c.Metadata, 554 "Release": chartutil.Values{ 555 "Name": "TestRelease", 556 }, 557 } 558 559 out, err := Render(c, v) 560 if err != nil { 561 t.Fatal(err) 562 } 563 564 expect := "Evaluate tpl Value: \"myvalue\"" 565 if got := out["TplFunction/templates/base"]; got != expect { 566 t.Errorf("Expected %q, got %q (%v)", expect, got, out) 567 } 568 } 569 570 func TestAlterFuncMap_tplinclude(t *testing.T) { 571 c := &chart.Chart{ 572 Metadata: &chart.Metadata{Name: "TplFunction"}, 573 Templates: []*chart.File{ 574 {Name: "templates/base", Data: []byte(`{{ tpl "{{include ` + "`" + `TplFunction/templates/_partial` + "`" + ` . | quote }}" .}}`)}, 575 {Name: "templates/_partial", Data: []byte(`{{.Template.Name}}`)}, 576 }, 577 } 578 v := chartutil.Values{ 579 "Values": chartutil.Values{ 580 "value": "myvalue", 581 }, 582 "Chart": c.Metadata, 583 "Release": chartutil.Values{ 584 "Name": "TestRelease", 585 }, 586 } 587 588 out, err := Render(c, v) 589 if err != nil { 590 t.Fatal(err) 591 } 592 593 expect := "\"TplFunction/templates/base\"" 594 if got := out["TplFunction/templates/base"]; got != expect { 595 t.Errorf("Expected %q, got %q (%v)", expect, got, out) 596 } 597 598 }