// Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 package expansion_test import ( "fmt" "testing" . "sigs.k8s.io/kustomize/api/internal/accumulator/expansion" ) type expected struct { count int edited string } func TestMapReference(t *testing.T) { type env struct { Name string Value interface{} } envs := []env{ { Name: "FOO", Value: "bar", }, { Name: "ZOO", Value: "$(FOO)-1", }, { Name: "BLU", Value: "$(ZOO)-2", }, { Name: "INT", Value: 2, }, { Name: "ZINT", Value: "$(INT)", }, { Name: "BOOL", Value: true, }, { Name: "ZBOOL", Value: "$(BOOL)", }, } declaredEnv := map[string]interface{}{ "FOO": "bar", "ZOO": "$(FOO)-1", "BLU": "$(ZOO)-2", "INT": "2", "ZINT": "$(INT)", "BOOL": "true", "ZBOOL": "$(BOOL)", } counts := make(map[string]int) mapping := MappingFuncFor(counts, declaredEnv) for _, env := range envs { declaredEnv[env.Name] = Expand(fmt.Sprintf("%v", env.Value), mapping) } expectedEnv := map[string]expected{ "FOO": {count: 1, edited: "bar"}, "ZOO": {count: 1, edited: "bar-1"}, "BLU": {count: 0, edited: "bar-1-2"}, "INT": {count: 1, edited: "2"}, "ZINT": {count: 0, edited: "2"}, "BOOL": {count: 1, edited: "true"}, "ZBOOL": {count: 0, edited: "true"}, } for k, v := range expectedEnv { if e, a := v, declaredEnv[k]; e.edited != a || e.count != counts[k] { t.Errorf("Expected %v count=%d, got %v count=%d", e.edited, e.count, a, counts[k]) } else { delete(declaredEnv, k) } } if len(declaredEnv) != 0 { t.Errorf("Unexpected keys in declared env: %v", declaredEnv) } } func TestMapping(t *testing.T) { context := map[string]interface{}{ "VAR_A": "A", "VAR_B": "B", "VAR_C": "C", "VAR_REF": "$(VAR_A)", "VAR_EMPTY": "", } doExpansionTest(t, context) } func TestMappingDual(t *testing.T) { context := map[string]interface{}{ "VAR_A": "A", "VAR_EMPTY": "", } context2 := map[string]interface{}{ "VAR_B": "B", "VAR_C": "C", "VAR_REF": "$(VAR_A)", } doExpansionTest(t, context, context2) } func doExpansionTest(t *testing.T, context ...map[string]interface{}) { cases := []struct { name string input string expected string counts map[string]int }{ { name: "whole string", input: "$(VAR_A)", expected: "A", counts: map[string]int{"VAR_A": 1}, }, { name: "repeat", input: "$(VAR_A)-$(VAR_A)", expected: "A-A", counts: map[string]int{"VAR_A": 2}, }, { name: "multiple repeats", input: "$(VAR_A)-$(VAR_B)-$(VAR_B)-$(VAR_B)-$(VAR_A)", expected: "A-B-B-B-A", counts: map[string]int{"VAR_A": 2, "VAR_B": 3}, }, { name: "beginning", input: "$(VAR_A)-1", expected: "A-1", counts: map[string]int{"VAR_A": 1}, }, { name: "middle", input: "___$(VAR_B)___", expected: "___B___", counts: map[string]int{"VAR_B": 1}, }, { name: "end", input: "___$(VAR_C)", expected: "___C", counts: map[string]int{"VAR_C": 1}, }, { name: "compound", input: "$(VAR_A)_$(VAR_B)_$(VAR_C)", expected: "A_B_C", counts: map[string]int{"VAR_A": 1, "VAR_B": 1, "VAR_C": 1}, }, { name: "escape & expand", input: "$$(VAR_B)_$(VAR_A)", expected: "$(VAR_B)_A", counts: map[string]int{"VAR_A": 1}, }, { name: "compound escape", input: "$$(VAR_A)_$$(VAR_B)", expected: "$(VAR_A)_$(VAR_B)", }, { name: "mixed in escapes", input: "f000-$$VAR_A", expected: "f000-$VAR_A", }, { name: "backslash escape ignored", input: "foo\\$(VAR_C)bar", expected: "foo\\Cbar", counts: map[string]int{"VAR_C": 1}, }, { name: "backslash escape ignored", input: "foo\\\\$(VAR_C)bar", expected: "foo\\\\Cbar", counts: map[string]int{"VAR_C": 1}, }, { name: "lots of backslashes", input: "foo\\\\\\\\$(VAR_A)bar", expected: "foo\\\\\\\\Abar", counts: map[string]int{"VAR_A": 1}, }, { name: "nested var references", input: "$(VAR_A$(VAR_B))", expected: "$(VAR_A$(VAR_B))", }, { name: "nested var references second type", input: "$(VAR_A$(VAR_B)", expected: "$(VAR_A$(VAR_B)", }, { name: "value is a reference", input: "$(VAR_REF)", expected: "$(VAR_A)", counts: map[string]int{"VAR_REF": 1}, }, { name: "value is a reference x 2", input: "%%$(VAR_REF)--$(VAR_REF)%%", expected: "%%$(VAR_A)--$(VAR_A)%%", counts: map[string]int{"VAR_REF": 2}, }, { name: "empty var", input: "foo$(VAR_EMPTY)bar", expected: "foobar", counts: map[string]int{"VAR_EMPTY": 1}, }, { name: "unterminated expression", input: "foo$(VAR_Awhoops!", expected: "foo$(VAR_Awhoops!", }, { name: "expression without operator", input: "f00__(VAR_A)__", expected: "f00__(VAR_A)__", }, { name: "shell special vars pass through", input: "$?_boo_$!", expected: "$?_boo_$!", }, { name: "bare operators are ignored", input: "$VAR_A", expected: "$VAR_A", }, { name: "undefined vars are passed through", input: "$(VAR_DNE)", expected: "$(VAR_DNE)", }, { name: "multiple (even) operators, var undefined", input: "$$$$$$(BIG_MONEY)", expected: "$$$(BIG_MONEY)", }, { name: "multiple (even) operators, var defined", input: "$$$$$$(VAR_A)", expected: "$$$(VAR_A)", }, { name: "multiple (odd) operators, var undefined", input: "$$$$$$$(GOOD_ODDS)", expected: "$$$$(GOOD_ODDS)", }, { name: "multiple (odd) operators, var defined", input: "$$$$$$$(VAR_A)", expected: "$$$A", counts: map[string]int{"VAR_A": 1}, }, { name: "missing open expression", input: "$VAR_A)", expected: "$VAR_A)", }, { name: "shell syntax ignored", input: "${VAR_A}", expected: "${VAR_A}", }, { name: "trailing incomplete expression not consumed", input: "$(VAR_B)_______$(A", expected: "B_______$(A", counts: map[string]int{"VAR_B": 1}, }, { name: "trailing incomplete expression, no content, is not consumed", input: "$(VAR_C)_______$(", expected: "C_______$(", counts: map[string]int{"VAR_C": 1}, }, { name: "operator at end of input string is preserved", input: "$(VAR_A)foobarzab$", expected: "Afoobarzab$", counts: map[string]int{"VAR_A": 1}, }, { name: "shell escaped incomplete expr", input: "foo-\\$(VAR_A", expected: "foo-\\$(VAR_A", }, { name: "lots of $( in middle", input: "--$($($($($--", expected: "--$($($($($--", }, { name: "lots of $( in beginning", input: "$($($($($--foo$(", expected: "$($($($($--foo$(", }, { name: "lots of $( at end", input: "foo0--$($($($(", expected: "foo0--$($($($(", }, { name: "escaped operators in variable names are not escaped", input: "$(foo$$var)", expected: "$(foo$$var)", }, { name: "newline not expanded", input: "\n", expected: "\n", }, } for _, tc := range cases { counts := make(map[string]int) mapping := MappingFuncFor(counts, context...) expanded := Expand(fmt.Sprintf("%v", tc.input), mapping) if e, a := tc.expected, expanded; e != a { t.Errorf("%v: expected %q, got %q", tc.name, e, a) } if len(counts) != len(tc.counts) { t.Errorf("%v: len(counts)=%d != len(tc.counts)=%d", tc.name, len(counts), len(tc.counts)) } if len(tc.counts) > 0 { for k, expectedCount := range tc.counts { c, ok := counts[k] if ok { if c != expectedCount { t.Errorf( "%v: k=%s, expected count %d, got %d", tc.name, k, expectedCount, c) } } else { t.Errorf( "%v: k=%s, expected count %d, got zero", tc.name, k, expectedCount) } } } } }