github.com/terraform-modules-krish/terratest@v0.29.0/modules/terraform/format_test.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 ) 9 10 func TestFormatTerraformVarsAsArgs(t *testing.T) { 11 t.Parallel() 12 13 testCases := []struct { 14 vars map[string]interface{} 15 expected []string 16 }{ 17 {map[string]interface{}{}, nil}, 18 {map[string]interface{}{"foo": "bar"}, []string{"-var", "foo=bar"}}, 19 {map[string]interface{}{"foo": 123}, []string{"-var", "foo=123"}}, 20 {map[string]interface{}{"foo": true}, []string{"-var", "foo=true"}}, 21 {map[string]interface{}{"foo": nil}, []string{"-var", "foo=null"}}, 22 {map[string]interface{}{"foo": []int{1, 2, 3}}, []string{"-var", "foo=[1, 2, 3]"}}, 23 {map[string]interface{}{"foo": map[string]string{"baz": "blah"}}, []string{"-var", "foo={\"baz\" = \"blah\"}"}}, 24 { 25 map[string]interface{}{"str": "bar", "int": -1, "bool": false, "list": []string{"foo", "bar", "baz"}, "map": map[string]int{"foo": 0}}, 26 []string{"-var", "str=bar", "-var", "int=-1", "-var", "bool=false", "-var", "list=[\"foo\", \"bar\", \"baz\"]", "-var", "map={\"foo\" = 0}"}, 27 }, 28 } 29 30 for _, testCase := range testCases { 31 checkResultWithRetry(t, 100, testCase.expected, fmt.Sprintf("FormatTerraformVarsAsArgs(%v)", testCase.vars), func() interface{} { 32 return FormatTerraformVarsAsArgs(testCase.vars) 33 }) 34 } 35 } 36 37 func TestPrimitiveToHclString(t *testing.T) { 38 t.Parallel() 39 40 testCases := []struct { 41 value interface{} 42 expected string 43 }{ 44 {"", ""}, 45 {"foo", "foo"}, 46 {"true", "true"}, 47 {true, "true"}, 48 {3, "3"}, 49 {nil, "null"}, 50 {[]int{1, 2, 3}, "[1 2 3]"}, // Anything that isn't a primitive is forced into a string 51 } 52 53 for _, testCase := range testCases { 54 actual := primitiveToHclString(testCase.value, false) 55 assert.Equal(t, testCase.expected, actual, "Value: %v", testCase.value) 56 } 57 } 58 59 func TestMapToHclString(t *testing.T) { 60 t.Parallel() 61 62 testCases := []struct { 63 value map[string]interface{} 64 expected string 65 }{ 66 {map[string]interface{}{}, "{}"}, 67 {map[string]interface{}{"key1": "value1"}, "{\"key1\" = \"value1\"}"}, 68 {map[string]interface{}{"key1": 123}, "{\"key1\" = 123}"}, 69 {map[string]interface{}{"key1": true}, "{\"key1\" = true}"}, 70 {map[string]interface{}{"key1": []int{1, 2, 3}}, "{\"key1\" = [1, 2, 3]}"}, // Any value that isn't a primitive is forced into a string 71 {map[string]interface{}{"key1": "value1", "key2": 0, "key3": false}, "{\"key1\" = \"value1\", \"key2\" = 0, \"key3\" = false}"}, 72 {map[string]interface{}{"key1.a.b.c": "value1"}, "{\"key1.a.b.c\" = \"value1\"}"}, 73 } 74 75 for _, testCase := range testCases { 76 checkResultWithRetry(t, 100, testCase.expected, fmt.Sprintf("mapToHclString(%v)", testCase.value), func() interface{} { 77 return mapToHclString(testCase.value) 78 }) 79 } 80 } 81 82 // Some of our tests execute code that loops over a map to produce output. The problem is that the order of map 83 // iteration is generally unpredictable and, to make it even more unpredictable, Go intentionally randomizes the 84 // iteration order (https://blog.golang.org/go-maps-in-action#TOC_7). Therefore, the order of items in the output 85 // is unpredictable, and doing a simple assert.Equals call will intermittently fail. 86 // 87 // We have a few unsatisfactory ways to solve this problem: 88 // 89 // 1. Enforce iteration order. This is easy to do in other languages, where you have built-in sorted maps. In Go, no 90 // such map exists, and if you create a custom one, you can't use the range keyword on it 91 // (http://stackoverflow.com/a/35810932/483528). As a result, we'd have to modify our implementation code to take 92 // iteration order into account which is a totally unnecessary feature that increases complexity. 93 // 2. We could parse the output string and do an order-independent comparison. However, that adds a bunch of parsing 94 // logic into the test code which is a totally unnecessary feature that increases complexity. 95 // 3. We accept that Go is a shitty language and, if the test fails, we re-run it a bunch of times in the hope that, if 96 // the bug is caused by key ordering, we will randomly get the proper order in a future run. The code being tested 97 // here is tiny & fast, so doing a hundred retries is still sub millisecond, so while ugly, this provides a very 98 // simple solution. 99 // 100 // Isn't it great that Go's designers built features into the language to prevent bugs that now force every Go 101 // developer to write thousands of lines of extra code like this, which is of course likely to contain bugs itself? 102 func checkResultWithRetry(t *testing.T, maxRetries int, expectedValue interface{}, description string, generateValue func() interface{}) { 103 for i := 0; i < maxRetries; i++ { 104 actualValue := generateValue() 105 if assert.ObjectsAreEqual(expectedValue, actualValue) { 106 return 107 } 108 t.Logf("Retry %d of %s failed: expected %v, got %v", i, description, expectedValue, actualValue) 109 } 110 111 assert.Fail(t, "checkResultWithRetry failed", "After %d retries, %s still not succeeding (see retries above)", description) 112 } 113 114 func TestSliceToHclString(t *testing.T) { 115 t.Parallel() 116 117 testCases := []struct { 118 value []interface{} 119 expected string 120 }{ 121 {[]interface{}{}, "[]"}, 122 {[]interface{}{"foo"}, "[\"foo\"]"}, 123 {[]interface{}{123}, "[123]"}, 124 {[]interface{}{true}, "[true]"}, 125 {[]interface{}{[]int{1, 2, 3}}, "[[1, 2, 3]]"}, // Any value that isn't a primitive is forced into a string 126 {[]interface{}{"foo", 0, false}, "[\"foo\", 0, false]"}, 127 {[]interface{}{map[string]interface{}{"foo": "bar"}}, "[{\"foo\" = \"bar\"}]"}, 128 {[]interface{}{map[string]interface{}{"foo": "bar"}, map[string]interface{}{"foo": "bar"}}, "[{\"foo\" = \"bar\"}, {\"foo\" = \"bar\"}]"}, 129 } 130 131 for _, testCase := range testCases { 132 actual := sliceToHclString(testCase.value) 133 assert.Equal(t, testCase.expected, actual, "Value: %v", testCase.value) 134 } 135 } 136 137 func TestToHclString(t *testing.T) { 138 t.Parallel() 139 140 testCases := []struct { 141 value interface{} 142 expected string 143 }{ 144 {"", ""}, 145 {"foo", "foo"}, 146 {123, "123"}, 147 {true, "true"}, 148 {[]int{1, 2, 3}, "[1, 2, 3]"}, 149 {[]string{"foo", "bar", "baz"}, "[\"foo\", \"bar\", \"baz\"]"}, 150 {map[string]string{"key1": "value1"}, "{\"key1\" = \"value1\"}"}, 151 {map[string]int{"key1": 123}, "{\"key1\" = 123}"}, 152 } 153 154 for _, testCase := range testCases { 155 actual := toHclString(testCase.value, false) 156 assert.Equal(t, testCase.expected, actual, "Value: %v", testCase.value) 157 } 158 } 159 160 func TestTryToConvertToGenericSlice(t *testing.T) { 161 t.Parallel() 162 163 testCases := []struct { 164 value interface{} 165 expectedSlice []interface{} 166 expectedIsSlice bool 167 }{ 168 {"", []interface{}{}, false}, 169 {"foo", []interface{}{}, false}, 170 {true, []interface{}{}, false}, 171 {531, []interface{}{}, false}, 172 {map[string]string{"foo": "bar"}, []interface{}{}, false}, 173 {[]string{}, []interface{}{}, true}, 174 {[]int{}, []interface{}{}, true}, 175 {[]bool{}, []interface{}{}, true}, 176 {[]interface{}{}, []interface{}{}, true}, 177 {[]string{"foo", "bar", "baz"}, []interface{}{"foo", "bar", "baz"}, true}, 178 {[]int{1, 2, 3}, []interface{}{1, 2, 3}, true}, 179 {[]bool{true, true, false}, []interface{}{true, true, false}, true}, 180 {[]interface{}{"foo", "bar", "baz"}, []interface{}{"foo", "bar", "baz"}, true}, 181 } 182 183 for _, testCase := range testCases { 184 actualSlice, actualIsSlice := tryToConvertToGenericSlice(testCase.value) 185 assert.Equal(t, testCase.expectedSlice, actualSlice, "Value: %v", testCase.value) 186 assert.Equal(t, testCase.expectedIsSlice, actualIsSlice, "Value: %v", testCase.value) 187 } 188 } 189 190 func TestTryToConvertToGenericMap(t *testing.T) { 191 t.Parallel() 192 193 testCases := []struct { 194 value interface{} 195 expectedMap map[string]interface{} 196 expectedIsMap bool 197 }{ 198 {"", map[string]interface{}{}, false}, 199 {"foo", map[string]interface{}{}, false}, 200 {true, map[string]interface{}{}, false}, 201 {531, map[string]interface{}{}, false}, 202 {[]string{"foo", "bar"}, map[string]interface{}{}, false}, 203 {map[int]int{}, map[string]interface{}{}, false}, 204 {map[bool]string{}, map[string]interface{}{}, false}, 205 {map[string]string{}, map[string]interface{}{}, true}, 206 {map[string]int{}, map[string]interface{}{}, true}, 207 {map[string]bool{}, map[string]interface{}{}, true}, 208 {map[string]interface{}{}, map[string]interface{}{}, true}, 209 {map[string]string{"key1": "value1", "key2": "value2"}, map[string]interface{}{"key1": "value1", "key2": "value2"}, true}, 210 {map[string]int{"key1": 1, "key2": 2, "key3": 3}, map[string]interface{}{"key1": 1, "key2": 2, "key3": 3}, true}, 211 {map[string]bool{"key1": true}, map[string]interface{}{"key1": true}, true}, 212 {map[string]interface{}{"key1": "value1"}, map[string]interface{}{"key1": "value1"}, true}, 213 } 214 215 for _, testCase := range testCases { 216 actualMap, actualIsMap := tryToConvertToGenericMap(testCase.value) 217 assert.Equal(t, testCase.expectedMap, actualMap, "Value: %v", testCase.value) 218 assert.Equal(t, testCase.expectedIsMap, actualIsMap, "Value: %v", testCase.value) 219 } 220 }