github.com/tooploox/oya@v0.0.21-0.20230524103240-1cda1861aad6/pkg/template/scope_test.go (about) 1 package template_test 2 3 import ( 4 "encoding/gob" 5 "fmt" 6 "os" 7 "testing" 8 9 "github.com/tooploox/oya/pkg/template" 10 tu "github.com/tooploox/oya/testutil" 11 ) 12 13 func identity(scope template.Scope) template.Scope { 14 return scope 15 } 16 17 func mutation(scope template.Scope) template.Scope { 18 scope["bar"] = "baz" 19 return scope 20 } 21 22 func merge(scope template.Scope) template.Scope { 23 return scope.Merge(template.Scope{"bar": "baz"}) 24 } 25 26 func deepcopy(dst, src interface{}) error { 27 r, w, err := os.Pipe() 28 if err != nil { 29 return err 30 } 31 enc := gob.NewEncoder(w) 32 err = enc.Encode(src) 33 if err != nil { 34 return err 35 } 36 dec := gob.NewDecoder(r) 37 return dec.Decode(dst) 38 } 39 40 func TestScope_UpdateScopeAt(t *testing.T) { 41 testCases := []struct { 42 desc string 43 scope template.Scope 44 path string 45 force bool 46 f func(template.Scope) template.Scope 47 expectedScope template.Scope 48 }{ 49 { 50 desc: "empty path, return original", 51 scope: template.Scope{}, 52 path: "", 53 f: identity, 54 expectedScope: template.Scope{}, 55 }, 56 { 57 desc: "empty path, make an in-place update", 58 scope: template.Scope{}, 59 path: "", 60 f: mutation, 61 expectedScope: template.Scope{ 62 "bar": "baz", 63 }, 64 }, 65 { 66 desc: "empty path, return updated copy", 67 scope: template.Scope{}, 68 path: "", 69 f: merge, 70 expectedScope: template.Scope{ 71 "bar": "baz", 72 }, 73 }, 74 { 75 desc: "non-existent path, return original", 76 scope: template.Scope{}, 77 path: "foo", 78 f: identity, 79 expectedScope: template.Scope{ 80 "foo": template.Scope{}, 81 }, 82 }, 83 { 84 desc: "non-existent path, make an in-place update", 85 scope: template.Scope{}, 86 path: "foo", 87 f: mutation, 88 expectedScope: template.Scope{ 89 "foo": template.Scope{ 90 "bar": "baz", 91 }, 92 }, 93 }, 94 { 95 desc: "non-existent path, return updated copy", 96 scope: template.Scope{}, 97 path: "foo", 98 f: merge, 99 expectedScope: template.Scope{ 100 "foo": template.Scope{ 101 "bar": "baz", 102 }, 103 }, 104 }, 105 { 106 desc: "non-existent deep path, return original", 107 scope: template.Scope{}, 108 path: "foo.xxx.yyy", 109 f: identity, 110 expectedScope: template.Scope{ 111 "foo": template.Scope{ 112 "xxx": template.Scope{ 113 "yyy": template.Scope{}, 114 }, 115 }, 116 }, 117 }, 118 { 119 desc: "non-existent path, make an in-place update", 120 scope: template.Scope{}, 121 path: "foo.xxx.yyy", 122 f: mutation, 123 expectedScope: template.Scope{ 124 "foo": template.Scope{ 125 "xxx": template.Scope{ 126 "yyy": template.Scope{ 127 "bar": "baz", 128 }, 129 }, 130 }, 131 }, 132 }, 133 { 134 desc: "non-existent path, return updated copy", 135 scope: template.Scope{}, 136 path: "foo.xxx.yyy", 137 f: merge, 138 expectedScope: template.Scope{ 139 "foo": template.Scope{ 140 "xxx": template.Scope{ 141 "yyy": template.Scope{ 142 "bar": "baz", 143 }, 144 }, 145 }, 146 }, 147 }, 148 { 149 desc: "existing non-compatible path", 150 scope: template.Scope{ 151 "foo": "aaa", 152 }, 153 path: "foo.xxx.yyy", 154 f: merge, 155 force: true, 156 expectedScope: template.Scope{ 157 "foo": template.Scope{ 158 "xxx": template.Scope{ 159 "yyy": template.Scope{ 160 "bar": "baz", 161 }, 162 }, 163 }, 164 }, 165 }, 166 } 167 168 for _, tc := range testCases { 169 var scope template.Scope 170 err := deepcopy(&scope, tc.scope) // Because f can mutate it. 171 tu.AssertNoErr(t, err, "deepcopy failed") 172 err = scope.UpdateScopeAt(tc.path, tc.f, tc.force) 173 tu.AssertNoErr(t, err, "UpdateScopeAt failed in test case %q", tc.desc) 174 tu.AssertObjectsEqualMsg(t, tc.expectedScope, scope, "In test case %q", tc.desc) 175 } 176 } 177 178 func ExampleScope_Flat() { 179 scope := template.Scope{ 180 "foo": map[interface{}]interface{}{ 181 "bar": "baz", 182 "qux": []interface{}{ 183 "1", "2", 3, 184 }, 185 "abc": map[string]interface{}{ 186 "123": true, 187 }, 188 }, 189 } 190 flattened := scope.Flat() 191 fmt.Println("foo.bar:", flattened["foo.bar"]) 192 _, ok := flattened["foo"] 193 fmt.Println("foo exists?", ok) 194 fmt.Println("foo.qux.0:", flattened["foo.qux.0"]) 195 fmt.Println("foo.qux.1:", flattened["foo.qux.1"]) 196 fmt.Println("foo.qux.2:", flattened["foo.qux.2"]) 197 fmt.Println("foo.abc.123:", flattened["foo.abc.123"]) 198 // Output: 199 // foo.bar: baz 200 // foo exists? false 201 // foo.qux.0: 1 202 // foo.qux.1: 2 203 // foo.qux.2: 3 204 // foo.abc.123: true 205 } 206 207 func TestMerge(t *testing.T) { 208 testCases := []struct { 209 desc string 210 lhs, rhs, expected template.Scope 211 }{ 212 { 213 desc: "empty scopes", 214 lhs: template.Scope{}, 215 rhs: template.Scope{}, 216 expected: template.Scope{}, 217 }, 218 { 219 desc: "no overlapping keys", 220 lhs: template.Scope{"foo": "bar"}, 221 rhs: template.Scope{"baz": "qux"}, 222 expected: template.Scope{"foo": "bar", "baz": "qux"}, 223 }, 224 { 225 desc: "overlapping keys", 226 lhs: template.Scope{"foo": "xxx"}, 227 rhs: template.Scope{"baz": "qux", "foo": "bar"}, 228 expected: template.Scope{"foo": "bar", "baz": "qux"}, 229 }, 230 { 231 desc: "deep merge", 232 lhs: template.Scope{"foo": map[interface{}]interface{}{"bar": "xxxxx", "baz": "orange"}}, 233 rhs: template.Scope{"foo": map[interface{}]interface{}{"bar": "apple", "qux": "peach"}}, 234 expected: template.Scope{"foo": map[interface{}]interface{}{"bar": "apple", "baz": "orange", "qux": "peach"}}, 235 }, 236 { 237 desc: "very deep merge", 238 lhs: template.Scope{"root": map[interface{}]interface{}{ 239 "foo": map[interface{}]interface{}{ 240 "bar": "xxxxx", 241 "baz": "orange", 242 }}, 243 }, 244 rhs: template.Scope{"root": map[interface{}]interface{}{ 245 "foo": map[interface{}]interface{}{ 246 "bar": "apple", 247 "qux": "peach", 248 }}, 249 }, 250 expected: template.Scope{"root": map[interface{}]interface{}{ 251 "foo": map[interface{}]interface{}{ 252 "bar": "apple", 253 "baz": "orange", 254 "qux": "peach", 255 }}, 256 }, 257 }, 258 } 259 260 for _, tc := range testCases { 261 actual := tc.lhs.Merge(tc.rhs) 262 tu.AssertObjectsEqual(t, tc.expected, actual) 263 } 264 } 265 266 func TestAssocAt(t *testing.T) { 267 testCases := []struct { 268 desc string 269 scope template.Scope 270 path string 271 value interface{} 272 force bool 273 isErrExpected bool 274 expectedScope template.Scope 275 }{ 276 { 277 desc: "empty path", 278 scope: template.Scope{}, 279 path: "", 280 value: "foo", 281 isErrExpected: true, 282 expectedScope: template.Scope{}, 283 }, 284 { 285 desc: "non-existent key", 286 scope: template.Scope{}, 287 path: "foo", 288 value: "bar", 289 expectedScope: template.Scope{ 290 "foo": "bar", 291 }, 292 }, 293 { 294 desc: "non-existent nested path", 295 scope: template.Scope{}, 296 path: "foo.bar", 297 value: "baz", 298 expectedScope: template.Scope{ 299 "foo": template.Scope{ 300 "bar": "baz", 301 }, 302 }, 303 }, 304 { 305 desc: "existing key", 306 scope: template.Scope{ 307 "foo": "xxx", 308 }, 309 path: "foo", 310 value: "bar", 311 expectedScope: template.Scope{ 312 "foo": "bar", 313 }, 314 }, 315 { 316 desc: "existing nested path", 317 scope: template.Scope{ 318 "foo": template.Scope{ 319 "bar": "xxx", 320 }, 321 }, 322 path: "foo.bar", 323 value: "baz", 324 expectedScope: template.Scope{ 325 "foo": template.Scope{ 326 "bar": "baz", 327 }, 328 }, 329 }, 330 { 331 desc: "existing incompatible value", 332 scope: template.Scope{ 333 "foo": "xxx", 334 }, 335 path: "foo.bar", 336 value: "baz", 337 force: false, 338 isErrExpected: true, 339 }, 340 { 341 desc: "existing incompatible value, force", 342 scope: template.Scope{ 343 "foo": "xxx", 344 }, 345 path: "foo.bar", 346 value: "baz", 347 force: true, 348 expectedScope: template.Scope{ 349 "foo": template.Scope{ 350 "bar": "baz", 351 }, 352 }, 353 }, 354 } 355 356 for _, tc := range testCases { 357 err := tc.scope.AssocAt(tc.path, tc.value, tc.force) 358 if tc.isErrExpected { 359 tu.AssertErr(t, err, "AssocAt unexpectedly succeeded in test case %q", tc.desc) 360 361 } else { 362 tu.AssertNoErr(t, err, "AssocAt failed in test case %q", tc.desc) 363 tu.AssertObjectsEqualMsg(t, tc.expectedScope, tc.scope, "In test case %q", tc.desc) 364 } 365 } 366 367 }