github.com/splunk/dan1-qbec@v0.7.3/internal/eval/eval_test.go (about) 1 /* 2 Copyright 2019 Splunk Inc. 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 eval 18 19 import ( 20 "testing" 21 22 "github.com/splunk/qbec/internal/model" 23 "github.com/stretchr/testify/assert" 24 "github.com/stretchr/testify/require" 25 ) 26 27 func TestEvalParams(t *testing.T) { 28 paramsMap, err := Params("testdata/params.libsonnet", Context{ 29 Env: "dev", 30 Tag: "t1", 31 DefaultNs: "foobar", 32 Verbose: true, 33 }) 34 require.Nil(t, err) 35 a := assert.New(t) 36 comps, ok := paramsMap["components"].(map[string]interface{}) 37 require.True(t, ok) 38 base, ok := comps["base"].(map[string]interface{}) 39 require.True(t, ok) 40 a.EqualValues("dev", base["env"]) 41 a.EqualValues("foobar", base["ns"]) 42 a.EqualValues("t1", base["tag"]) 43 } 44 45 func TestEvalParamsNegative(t *testing.T) { 46 _, err := Params("testdata/params.invalid.libsonnet", Context{Env: "dev"}) 47 require.NotNil(t, err) 48 require.Contains(t, err.Error(), "end of file") 49 50 _, err = Params("testdata/params.non-object.libsonnet", Context{Env: "dev"}) 51 require.NotNil(t, err) 52 require.Contains(t, err.Error(), "cannot unmarshal array") 53 } 54 55 func TestEvalComponents(t *testing.T) { 56 objs, err := Components([]model.Component{ 57 { 58 Name: "b", 59 File: "testdata/components/b.yaml", 60 }, 61 { 62 Name: "c", 63 File: "testdata/components/c.jsonnet", 64 }, 65 { 66 Name: "a", 67 File: "testdata/components/a.json", 68 }, 69 }, Context{Env: "dev", Verbose: true, PostProcessFile: "testdata/components/pp/pp.jsonnet"}) 70 require.Nil(t, err) 71 require.Equal(t, 3, len(objs)) 72 a := assert.New(t) 73 74 obj := objs[0] 75 a.Equal("a", obj.Component()) 76 a.Equal("dev", obj.Environment()) 77 a.Equal("", obj.GroupVersionKind().Group) 78 a.Equal("v1", obj.GroupVersionKind().Version) 79 a.Equal("ConfigMap", obj.GroupVersionKind().Kind) 80 a.Equal("", obj.GetNamespace()) 81 a.Equal("json-config-map", obj.GetName()) 82 a.Equal("service2", obj.ToUnstructured().GetAnnotations()["team"]) 83 a.Equal("#svc2", obj.ToUnstructured().GetAnnotations()["slack"]) 84 85 obj = objs[1] 86 a.Equal("b", obj.Component()) 87 a.Equal("dev", obj.Environment()) 88 a.Equal("yaml-config-map", obj.GetName()) 89 a.Equal("service2", obj.ToUnstructured().GetAnnotations()["team"]) 90 a.Equal("#svc2", obj.ToUnstructured().GetAnnotations()["slack"]) 91 92 obj = objs[2] 93 a.Equal("c", obj.Component()) 94 a.Equal("dev", obj.Environment()) 95 a.Equal("jsonnet-config-map", obj.GetName()) 96 a.Equal("service2", obj.ToUnstructured().GetAnnotations()["team"]) 97 a.Equal("#svc2", obj.ToUnstructured().GetAnnotations()["slack"]) 98 } 99 100 func TestEvalComponentsClean(t *testing.T) { 101 objs, err := Components([]model.Component{ 102 { 103 Name: "a", 104 File: "testdata/components/a.json", 105 }, 106 }, Context{Env: "dev", CleanMode: true, PostProcessFile: "testdata/components/pp/pp.jsonnet"}) 107 require.Nil(t, err) 108 require.Equal(t, 1, len(objs)) 109 a := assert.New(t) 110 111 obj := objs[0] 112 a.Equal("a", obj.Component()) 113 a.Equal("dev", obj.Environment()) 114 a.Equal("", obj.GroupVersionKind().Group) 115 a.Equal("v1", obj.GroupVersionKind().Version) 116 a.Equal("ConfigMap", obj.GroupVersionKind().Kind) 117 a.Equal("", obj.GetNamespace()) 118 a.Equal("json-config-map", obj.GetName()) 119 a.Equal("", obj.ToUnstructured().GetAnnotations()["team"]) 120 a.Equal("", obj.ToUnstructured().GetAnnotations()["slack"]) 121 } 122 123 func TestEvalComponentsEdges(t *testing.T) { 124 goodComponents := []model.Component{ 125 {Name: "g1", File: "testdata/good-components/g1.jsonnet"}, 126 {Name: "g2", File: "testdata/good-components/g2.jsonnet"}, 127 {Name: "g3", File: "testdata/good-components/g3.jsonnet"}, 128 {Name: "g4", File: "testdata/good-components/g4.jsonnet"}, 129 {Name: "g5", File: "testdata/good-components/g5.jsonnet"}, 130 } 131 goodAssert := func(t *testing.T, ret []model.K8sLocalObject, err error) { 132 require.NotNil(t, err) 133 } 134 tests := []struct { 135 name string 136 components []model.Component 137 asserter func(*testing.T, []model.K8sLocalObject, error) 138 concurrency int 139 }{ 140 { 141 name: "no components", 142 asserter: func(t *testing.T, ret []model.K8sLocalObject, err error) { 143 require.Nil(t, err) 144 assert.Equal(t, 0, len(ret)) 145 }, 146 }, 147 { 148 name: "single bad", 149 components: []model.Component{{Name: "e1", File: "testdata/bad-components/e1.jsonnet"}}, 150 asserter: func(t *testing.T, ret []model.K8sLocalObject, err error) { 151 require.NotNil(t, err) 152 assert.Contains(t, err.Error(), "evaluate 'e1'") 153 }, 154 }, 155 { 156 name: "two bad", 157 components: []model.Component{ 158 {Name: "e1", File: "testdata/bad-components/e1.jsonnet"}, 159 {Name: "e2", File: "testdata/bad-components/e2.jsonnet"}, 160 }, 161 asserter: func(t *testing.T, ret []model.K8sLocalObject, err error) { 162 require.NotNil(t, err) 163 assert.Contains(t, err.Error(), "evaluate 'e1'") 164 assert.Contains(t, err.Error(), "evaluate 'e2'") 165 }, 166 }, 167 { 168 name: "many bad", 169 components: []model.Component{ 170 {Name: "e1", File: "testdata/bad-components/e1.jsonnet"}, 171 {Name: "e2", File: "testdata/bad-components/e2.jsonnet"}, 172 {Name: "e3", File: "testdata/bad-components/e3.jsonnet"}, 173 {Name: "e4", File: "testdata/bad-components/e4.jsonnet"}, 174 {Name: "e5", File: "testdata/bad-components/e5.jsonnet"}, 175 }, 176 asserter: func(t *testing.T, ret []model.K8sLocalObject, err error) { 177 require.NotNil(t, err) 178 assert.Contains(t, err.Error(), "... and 2 more errors") 179 }, 180 }, 181 { 182 name: "bad file", 183 components: []model.Component{ 184 {Name: "e1", File: "testdata/bad-components/XXX.jsonnet"}, 185 }, 186 asserter: func(t *testing.T, ret []model.K8sLocalObject, err error) { 187 require.NotNil(t, err) 188 assert.Contains(t, err.Error(), "no such file") 189 }, 190 }, 191 { 192 name: "negative concurrency", 193 components: goodComponents, 194 asserter: goodAssert, 195 concurrency: -10, 196 }, 197 { 198 name: "zero concurrency", 199 components: goodComponents, 200 asserter: goodAssert, 201 concurrency: 0, 202 }, 203 { 204 name: "4 concurrency", 205 components: goodComponents, 206 asserter: goodAssert, 207 concurrency: 4, 208 }, 209 { 210 name: "one concurrency", 211 components: goodComponents, 212 asserter: goodAssert, 213 concurrency: 1, 214 }, 215 { 216 name: "million concurrency", 217 components: goodComponents, 218 asserter: goodAssert, 219 concurrency: 1000000, 220 }, 221 } 222 for _, test := range tests { 223 t.Run(test.name, func(t *testing.T) { 224 ret, err := evalComponents(test.components, Context{ 225 Env: "dev", 226 Concurrency: test.concurrency, 227 }, postProc{}) 228 test.asserter(t, ret, err) 229 }) 230 } 231 } 232 233 func TestEvalComponentsBadJson(t *testing.T) { 234 _, err := Components([]model.Component{ 235 { 236 Name: "bad", 237 File: "testdata/components/bad.json", 238 }, 239 }, Context{Env: "dev"}) 240 require.NotNil(t, err) 241 require.Contains(t, err.Error(), "invalid character") 242 } 243 244 func TestEvalComponentsBadPosProcessor(t *testing.T) { 245 _, err := Components([]model.Component{ 246 { 247 Name: "bad", 248 File: "testdata/components/good.json", 249 }, 250 }, Context{Env: "dev", PostProcessFile: "foo/bar.jsonnet"}) 251 require.NotNil(t, err) 252 require.Contains(t, err.Error(), "read post-eval file:") 253 } 254 255 func TestEvalComponentsBadYaml(t *testing.T) { 256 _, err := Components([]model.Component{ 257 { 258 Name: "bad", 259 File: "testdata/components/bad.yaml", 260 }, 261 }, Context{Env: "dev"}) 262 require.NotNil(t, err) 263 require.Contains(t, err.Error(), "did not find expected node content") 264 } 265 266 func TestEvalComponentsBadObjects(t *testing.T) { 267 _, err := Components([]model.Component{ 268 { 269 Name: "bad", 270 File: "testdata/components/bad-objects.yaml", 271 }, 272 }, Context{Env: "dev"}) 273 require.NotNil(t, err) 274 require.Contains(t, err.Error(), `unexpected type for object (string) at path "$[0].foo"`) 275 } 276 277 func TestEvalPostProcessor(t *testing.T) { 278 obj := map[string]interface{}{ 279 "apiVersion": "v1", 280 "kind": "ConfigMap", 281 "metadata": map[string]interface{}{ 282 "name": "cm", 283 }, 284 "data": map[string]interface{}{ 285 "foo": "bar", 286 }, 287 } 288 tests := []struct { 289 name string 290 code string 291 asserter func(t *testing.T, ret map[string]interface{}, err error) 292 }{ 293 { 294 name: "add annotation", 295 code: `function (object) object + { metadata +: { annotations +:{ slack: '#crash' }}}`, 296 asserter: func(t *testing.T, ret map[string]interface{}, err error) { 297 require.Nil(t, err) 298 ann := ret["metadata"].(map[string]interface{})["annotations"].(map[string]interface{})["slack"] 299 assert.Equal(t, "#crash", ann) 300 }, 301 }, 302 { 303 name: "return scalar", 304 code: `function (object) "boo"`, 305 asserter: func(t *testing.T, ret map[string]interface{}, err error) { 306 require.NotNil(t, err) 307 assert.Equal(t, `post-eval did not return an object, "boo"`+"\n", err.Error()) 308 }, 309 }, 310 { 311 name: "return array", 312 code: `function (object) [ object ]`, 313 asserter: func(t *testing.T, ret map[string]interface{}, err error) { 314 require.NotNil(t, err) 315 assert.Contains(t, err.Error(), `post-eval did not return an object, [`) 316 }, 317 }, 318 { 319 name: "return k8s list", 320 code: `function (object) { apiVersion: "v1", kind: "List", items: [ object ] }`, 321 asserter: func(t *testing.T, ret map[string]interface{}, err error) { 322 require.NotNil(t, err) 323 assert.Contains(t, err.Error(), `post-eval did not return a K8s object,`) 324 }, 325 }, 326 { 327 name: "bad code", 328 code: `function (object) object2`, 329 asserter: func(t *testing.T, ret map[string]interface{}, err error) { 330 require.NotNil(t, err) 331 assert.Contains(t, err.Error(), `post-eval object: pp.jsonnet:1`) 332 }, 333 }, 334 { 335 name: "bad tla", 336 code: `function (o) o`, 337 asserter: func(t *testing.T, ret map[string]interface{}, err error) { 338 require.NotNil(t, err) 339 assert.Contains(t, err.Error(), `post-eval object: RUNTIME ERROR: function has no parameter object`) 340 }, 341 }, 342 } 343 344 for _, test := range tests { 345 t.Run(test.name, func(t *testing.T) { 346 ctx := Context{Env: "dev"} 347 pp := postProc{ctx: ctx, code: test.code, file: "pp.jsonnet"} 348 ret, err := pp.run(obj) 349 test.asserter(t, ret, err) 350 }) 351 } 352 }