1// Copyright 2021 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package android 16 17import ( 18 "android/soong/bazel" 19 "testing" 20) 21 22func TestExpandVars(t *testing.T) { 23 android_arm64_config := TestConfig("out", nil, "", nil) 24 android_arm64_config.BuildOS = Android 25 android_arm64_config.BuildArch = Arm64 26 27 testCases := []struct { 28 description string 29 config Config 30 stringScope ExportedStringVariables 31 stringListScope ExportedStringListVariables 32 configVars ExportedConfigDependingVariables 33 toExpand string 34 expectedValues []string 35 }{ 36 { 37 description: "no expansion for non-interpolated value", 38 toExpand: "foo", 39 expectedValues: []string{"foo"}, 40 }, 41 { 42 description: "single level expansion for string var", 43 stringScope: ExportedStringVariables{ 44 "foo": "bar", 45 }, 46 toExpand: "${foo}", 47 expectedValues: []string{"bar"}, 48 }, 49 { 50 description: "single level expansion with short-name for string var", 51 stringScope: ExportedStringVariables{ 52 "foo": "bar", 53 }, 54 toExpand: "${config.foo}", 55 expectedValues: []string{"bar"}, 56 }, 57 { 58 description: "single level expansion string list var", 59 stringListScope: ExportedStringListVariables{ 60 "foo": []string{"bar"}, 61 }, 62 toExpand: "${foo}", 63 expectedValues: []string{"bar"}, 64 }, 65 { 66 description: "mixed level expansion for string list var", 67 stringScope: ExportedStringVariables{ 68 "foo": "${bar}", 69 "qux": "hello", 70 }, 71 stringListScope: ExportedStringListVariables{ 72 "bar": []string{"baz", "${qux}"}, 73 }, 74 toExpand: "${foo}", 75 expectedValues: []string{"baz hello"}, 76 }, 77 { 78 description: "double level expansion", 79 stringListScope: ExportedStringListVariables{ 80 "foo": []string{"${bar}"}, 81 "bar": []string{"baz"}, 82 }, 83 toExpand: "${foo}", 84 expectedValues: []string{"baz"}, 85 }, 86 { 87 description: "double level expansion with a literal", 88 stringListScope: ExportedStringListVariables{ 89 "a": []string{"${b}", "c"}, 90 "b": []string{"d"}, 91 }, 92 toExpand: "${a}", 93 expectedValues: []string{"d c"}, 94 }, 95 { 96 description: "double level expansion, with two variables in a string", 97 stringListScope: ExportedStringListVariables{ 98 "a": []string{"${b} ${c}"}, 99 "b": []string{"d"}, 100 "c": []string{"e"}, 101 }, 102 toExpand: "${a}", 103 expectedValues: []string{"d e"}, 104 }, 105 { 106 description: "triple level expansion with two variables in a string", 107 stringListScope: ExportedStringListVariables{ 108 "a": []string{"${b} ${c}"}, 109 "b": []string{"${c}", "${d}"}, 110 "c": []string{"${d}"}, 111 "d": []string{"foo"}, 112 }, 113 toExpand: "${a}", 114 expectedValues: []string{"foo foo foo"}, 115 }, 116 { 117 description: "expansion with config depending vars", 118 configVars: ExportedConfigDependingVariables{ 119 "a": func(c Config) string { return c.BuildOS.String() }, 120 "b": func(c Config) string { return c.BuildArch.String() }, 121 }, 122 config: android_arm64_config, 123 toExpand: "${a}-${b}", 124 expectedValues: []string{"android-arm64"}, 125 }, 126 { 127 description: "double level multi type expansion", 128 stringListScope: ExportedStringListVariables{ 129 "platform": []string{"${os}-${arch}"}, 130 "const": []string{"const"}, 131 }, 132 configVars: ExportedConfigDependingVariables{ 133 "os": func(c Config) string { return c.BuildOS.String() }, 134 "arch": func(c Config) string { return c.BuildArch.String() }, 135 "foo": func(c Config) string { return "foo" }, 136 }, 137 config: android_arm64_config, 138 toExpand: "${const}/${platform}/${foo}", 139 expectedValues: []string{"const/android-arm64/foo"}, 140 }, 141 } 142 143 for _, testCase := range testCases { 144 t.Run(testCase.description, func(t *testing.T) { 145 output, _ := expandVar(testCase.config, testCase.toExpand, testCase.stringScope, testCase.stringListScope, testCase.configVars) 146 if len(output) != len(testCase.expectedValues) { 147 t.Errorf("Expected %d values, got %d", len(testCase.expectedValues), len(output)) 148 } 149 for i, actual := range output { 150 expectedValue := testCase.expectedValues[i] 151 if actual != expectedValue { 152 t.Errorf("Actual value '%s' doesn't match expected value '%s'", actual, expectedValue) 153 } 154 } 155 }) 156 } 157} 158 159func TestBazelToolchainVars(t *testing.T) { 160 testCases := []struct { 161 name string 162 config Config 163 vars ExportedVariables 164 expectedOut string 165 }{ 166 { 167 name: "exports strings", 168 vars: ExportedVariables{ 169 exportedStringVars: ExportedStringVariables{ 170 "a": "b", 171 "c": "d", 172 }, 173 }, 174 expectedOut: bazel.GeneratedBazelFileWarning + ` 175 176_a = "b" 177 178_c = "d" 179 180constants = struct( 181 a = _a, 182 c = _c, 183)`, 184 }, 185 { 186 name: "exports string lists", 187 vars: ExportedVariables{ 188 exportedStringListVars: ExportedStringListVariables{ 189 "a": []string{"b1", "b2"}, 190 "c": []string{"d1", "d2"}, 191 }, 192 }, 193 expectedOut: bazel.GeneratedBazelFileWarning + ` 194 195_a = [ 196 "b1", 197 "b2", 198] 199 200_c = [ 201 "d1", 202 "d2", 203] 204 205constants = struct( 206 a = _a, 207 c = _c, 208)`, 209 }, 210 { 211 name: "exports string lists dicts", 212 vars: ExportedVariables{ 213 exportedStringListDictVars: ExportedStringListDictVariables{ 214 "a": map[string][]string{"b1": {"b2"}}, 215 "c": map[string][]string{"d1": {"d2"}}, 216 }, 217 }, 218 expectedOut: bazel.GeneratedBazelFileWarning + ` 219 220_a = { 221 "b1": ["b2"], 222} 223 224_c = { 225 "d1": ["d2"], 226} 227 228constants = struct( 229 a = _a, 230 c = _c, 231)`, 232 }, 233 { 234 name: "exports dict with var refs", 235 vars: ExportedVariables{ 236 exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{ 237 "a": map[string]string{"b1": "${b2}"}, 238 "c": map[string]string{"d1": "${config.d2}"}, 239 }, 240 }, 241 expectedOut: bazel.GeneratedBazelFileWarning + ` 242 243_a = { 244 "b1": _b2, 245} 246 247_c = { 248 "d1": _d2, 249} 250 251constants = struct( 252 a = _a, 253 c = _c, 254)`, 255 }, 256 { 257 name: "sorts across types with variable references last", 258 vars: ExportedVariables{ 259 exportedStringVars: ExportedStringVariables{ 260 "b": "b-val", 261 "d": "d-val", 262 }, 263 exportedStringListVars: ExportedStringListVariables{ 264 "c": []string{"c-val"}, 265 "e": []string{"e-val"}, 266 }, 267 exportedStringListDictVars: ExportedStringListDictVariables{ 268 "a": map[string][]string{"a1": {"a2"}}, 269 "f": map[string][]string{"f1": {"f2"}}, 270 }, 271 exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{ 272 "aa": map[string]string{"b1": "${b}"}, 273 "cc": map[string]string{"d1": "${config.d}"}, 274 }, 275 }, 276 expectedOut: bazel.GeneratedBazelFileWarning + ` 277 278_a = { 279 "a1": ["a2"], 280} 281 282_b = "b-val" 283 284_c = ["c-val"] 285 286_d = "d-val" 287 288_e = ["e-val"] 289 290_f = { 291 "f1": ["f2"], 292} 293 294_aa = { 295 "b1": _b, 296} 297 298_cc = { 299 "d1": _d, 300} 301 302constants = struct( 303 a = _a, 304 b = _b, 305 c = _c, 306 d = _d, 307 e = _e, 308 f = _f, 309 aa = _aa, 310 cc = _cc, 311)`, 312 }, 313 } 314 315 for _, tc := range testCases { 316 t.Run(tc.name, func(t *testing.T) { 317 out := BazelToolchainVars(tc.config, tc.vars) 318 if out != tc.expectedOut { 319 t.Errorf("Expected \n%s, got \n%s", tc.expectedOut, out) 320 } 321 }) 322 } 323} 324 325func TestSplitStringKeepingQuotedSubstring(t *testing.T) { 326 testCases := []struct { 327 description string 328 s string 329 delimiter byte 330 split []string 331 }{ 332 { 333 description: "empty string returns single empty string", 334 s: "", 335 delimiter: ' ', 336 split: []string{ 337 "", 338 }, 339 }, 340 { 341 description: "string with single space returns two empty strings", 342 s: " ", 343 delimiter: ' ', 344 split: []string{ 345 "", 346 "", 347 }, 348 }, 349 { 350 description: "string with two spaces returns three empty strings", 351 s: " ", 352 delimiter: ' ', 353 split: []string{ 354 "", 355 "", 356 "", 357 }, 358 }, 359 { 360 description: "string with four words returns four word string", 361 s: "hello world with words", 362 delimiter: ' ', 363 split: []string{ 364 "hello", 365 "world", 366 "with", 367 "words", 368 }, 369 }, 370 { 371 description: "string with words and nested quote returns word strings and quote string", 372 s: `hello "world with" words`, 373 delimiter: ' ', 374 split: []string{ 375 "hello", 376 `"world with"`, 377 "words", 378 }, 379 }, 380 { 381 description: "string with escaped quote inside real quotes", 382 s: `hello \"world "with\" words"`, 383 delimiter: ' ', 384 split: []string{ 385 "hello", 386 `"world`, 387 `"with" words"`, 388 }, 389 }, 390 { 391 description: "string with words and escaped quotes returns word strings", 392 s: `hello \"world with\" words`, 393 delimiter: ' ', 394 split: []string{ 395 "hello", 396 `"world`, 397 `with"`, 398 "words", 399 }, 400 }, 401 { 402 description: "string which is single quoted substring returns only substring", 403 s: `"hello world with words"`, 404 delimiter: ' ', 405 split: []string{ 406 `"hello world with words"`, 407 }, 408 }, 409 { 410 description: "string starting with quote returns quoted string", 411 s: `"hello world with" words`, 412 delimiter: ' ', 413 split: []string{ 414 `"hello world with"`, 415 "words", 416 }, 417 }, 418 { 419 description: "string with starting quote and no ending quote returns quote to end of string", 420 s: `hello "world with words`, 421 delimiter: ' ', 422 split: []string{ 423 "hello", 424 `"world with words`, 425 }, 426 }, 427 { 428 description: "quoted string is treated as a single \"word\" unless separated by delimiter", 429 s: `hello "world"with words`, 430 delimiter: ' ', 431 split: []string{ 432 "hello", 433 `"world"with`, 434 "words", 435 }, 436 }, 437 } 438 439 for _, tc := range testCases { 440 t.Run(tc.description, func(t *testing.T) { 441 split := splitStringKeepingQuotedSubstring(tc.s, tc.delimiter) 442 if len(split) != len(tc.split) { 443 t.Fatalf("number of split string elements (%d) differs from expected (%d): split string (%v), expected (%v)", 444 len(split), len(tc.split), split, tc.split, 445 ) 446 } 447 for i := range split { 448 if split[i] != tc.split[i] { 449 t.Errorf("split string element (%d), %v, differs from expected, %v", i, split[i], tc.split[i]) 450 } 451 } 452 }) 453 } 454} 455