1// Copyright 2017 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 build 16 17import ( 18 "bytes" 19 "context" 20 "fmt" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "reflect" 25 "strings" 26 "testing" 27 28 "android/soong/ui/logger" 29 smpb "android/soong/ui/metrics/metrics_proto" 30 "android/soong/ui/status" 31 32 "google.golang.org/protobuf/proto" 33) 34 35func testContext() Context { 36 return Context{&ContextImpl{ 37 Context: context.Background(), 38 Logger: logger.New(&bytes.Buffer{}), 39 Writer: &bytes.Buffer{}, 40 Status: &status.Status{}, 41 }} 42} 43 44func TestConfigParseArgsJK(t *testing.T) { 45 ctx := testContext() 46 47 testCases := []struct { 48 args []string 49 50 parallel int 51 keepGoing int 52 remaining []string 53 }{ 54 {nil, -1, -1, nil}, 55 56 {[]string{"-j"}, -1, -1, nil}, 57 {[]string{"-j1"}, 1, -1, nil}, 58 {[]string{"-j1234"}, 1234, -1, nil}, 59 60 {[]string{"-j", "1"}, 1, -1, nil}, 61 {[]string{"-j", "1234"}, 1234, -1, nil}, 62 {[]string{"-j", "1234", "abc"}, 1234, -1, []string{"abc"}}, 63 {[]string{"-j", "abc"}, -1, -1, []string{"abc"}}, 64 {[]string{"-j", "1abc"}, -1, -1, []string{"1abc"}}, 65 66 {[]string{"-k"}, -1, 0, nil}, 67 {[]string{"-k0"}, -1, 0, nil}, 68 {[]string{"-k1"}, -1, 1, nil}, 69 {[]string{"-k1234"}, -1, 1234, nil}, 70 71 {[]string{"-k", "0"}, -1, 0, nil}, 72 {[]string{"-k", "1"}, -1, 1, nil}, 73 {[]string{"-k", "1234"}, -1, 1234, nil}, 74 {[]string{"-k", "1234", "abc"}, -1, 1234, []string{"abc"}}, 75 {[]string{"-k", "abc"}, -1, 0, []string{"abc"}}, 76 {[]string{"-k", "1abc"}, -1, 0, []string{"1abc"}}, 77 78 // TODO: These are supported in Make, should we support them? 79 //{[]string{"-kj"}, -1, 0}, 80 //{[]string{"-kj8"}, 8, 0}, 81 82 // -jk is not valid in Make 83 } 84 85 for _, tc := range testCases { 86 t.Run(strings.Join(tc.args, " "), func(t *testing.T) { 87 defer logger.Recover(func(err error) { 88 t.Fatal(err) 89 }) 90 91 c := &configImpl{ 92 parallel: -1, 93 keepGoing: -1, 94 } 95 c.parseArgs(ctx, tc.args) 96 97 if c.parallel != tc.parallel { 98 t.Errorf("for %q, parallel:\nwant: %d\n got: %d\n", 99 strings.Join(tc.args, " "), 100 tc.parallel, c.parallel) 101 } 102 if c.keepGoing != tc.keepGoing { 103 t.Errorf("for %q, keep going:\nwant: %d\n got: %d\n", 104 strings.Join(tc.args, " "), 105 tc.keepGoing, c.keepGoing) 106 } 107 if !reflect.DeepEqual(c.arguments, tc.remaining) { 108 t.Errorf("for %q, remaining arguments:\nwant: %q\n got: %q\n", 109 strings.Join(tc.args, " "), 110 tc.remaining, c.arguments) 111 } 112 }) 113 } 114} 115 116func TestConfigParseArgsVars(t *testing.T) { 117 ctx := testContext() 118 119 testCases := []struct { 120 env []string 121 args []string 122 123 expectedEnv []string 124 remaining []string 125 }{ 126 {}, 127 { 128 env: []string{"A=bc"}, 129 130 expectedEnv: []string{"A=bc"}, 131 }, 132 { 133 args: []string{"abc"}, 134 135 remaining: []string{"abc"}, 136 }, 137 138 { 139 args: []string{"A=bc"}, 140 141 expectedEnv: []string{"A=bc"}, 142 }, 143 { 144 env: []string{"A=a"}, 145 args: []string{"A=bc"}, 146 147 expectedEnv: []string{"A=bc"}, 148 }, 149 150 { 151 env: []string{"A=a"}, 152 args: []string{"A=", "=b"}, 153 154 expectedEnv: []string{"A="}, 155 remaining: []string{"=b"}, 156 }, 157 } 158 159 for _, tc := range testCases { 160 t.Run(strings.Join(tc.args, " "), func(t *testing.T) { 161 defer logger.Recover(func(err error) { 162 t.Fatal(err) 163 }) 164 165 e := Environment(tc.env) 166 c := &configImpl{ 167 environ: &e, 168 } 169 c.parseArgs(ctx, tc.args) 170 171 if !reflect.DeepEqual([]string(*c.environ), tc.expectedEnv) { 172 t.Errorf("for env=%q args=%q, environment:\nwant: %q\n got: %q\n", 173 tc.env, tc.args, 174 tc.expectedEnv, []string(*c.environ)) 175 } 176 if !reflect.DeepEqual(c.arguments, tc.remaining) { 177 t.Errorf("for env=%q args=%q, remaining arguments:\nwant: %q\n got: %q\n", 178 tc.env, tc.args, 179 tc.remaining, c.arguments) 180 } 181 }) 182 } 183} 184 185func TestConfigCheckTopDir(t *testing.T) { 186 ctx := testContext() 187 buildRootDir := filepath.Dir(srcDirFileCheck) 188 expectedErrStr := fmt.Sprintf("Current working directory must be the source tree. %q not found.", srcDirFileCheck) 189 190 tests := []struct { 191 // ********* Setup ********* 192 // Test description. 193 description string 194 195 // ********* Action ********* 196 // If set to true, the build root file is created. 197 rootBuildFile bool 198 199 // The current path where Soong is being executed. 200 path string 201 202 // ********* Validation ********* 203 // Expecting error and validate the error string against expectedErrStr. 204 wantErr bool 205 }{{ 206 description: "current directory is the root source tree", 207 rootBuildFile: true, 208 path: ".", 209 wantErr: false, 210 }, { 211 description: "one level deep in the source tree", 212 rootBuildFile: true, 213 path: "1", 214 wantErr: true, 215 }, { 216 description: "very deep in the source tree", 217 rootBuildFile: true, 218 path: "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7", 219 wantErr: true, 220 }, { 221 description: "outside of source tree", 222 rootBuildFile: false, 223 path: "1/2/3/4/5", 224 wantErr: true, 225 }} 226 227 for _, tt := range tests { 228 t.Run(tt.description, func(t *testing.T) { 229 defer logger.Recover(func(err error) { 230 if !tt.wantErr { 231 t.Fatalf("Got unexpected error: %v", err) 232 } 233 if expectedErrStr != err.Error() { 234 t.Fatalf("expected %s, got %s", expectedErrStr, err.Error()) 235 } 236 }) 237 238 // Create the root source tree. 239 rootDir, err := ioutil.TempDir("", "") 240 if err != nil { 241 t.Fatal(err) 242 } 243 defer os.RemoveAll(rootDir) 244 245 // Create the build root file. This is to test if topDir returns an error if the build root 246 // file does not exist. 247 if tt.rootBuildFile { 248 dir := filepath.Join(rootDir, buildRootDir) 249 if err := os.MkdirAll(dir, 0755); err != nil { 250 t.Errorf("failed to create %s directory: %v", dir, err) 251 } 252 f := filepath.Join(rootDir, srcDirFileCheck) 253 if err := ioutil.WriteFile(f, []byte{}, 0644); err != nil { 254 t.Errorf("failed to create file %s: %v", f, err) 255 } 256 } 257 258 // Next block of code is to set the current directory. 259 dir := rootDir 260 if tt.path != "" { 261 dir = filepath.Join(dir, tt.path) 262 if err := os.MkdirAll(dir, 0755); err != nil { 263 t.Errorf("failed to create %s directory: %v", dir, err) 264 } 265 } 266 curDir, err := os.Getwd() 267 if err != nil { 268 t.Fatalf("failed to get the current directory: %v", err) 269 } 270 defer func() { os.Chdir(curDir) }() 271 272 if err := os.Chdir(dir); err != nil { 273 t.Fatalf("failed to change directory to %s: %v", dir, err) 274 } 275 276 checkTopDir(ctx) 277 }) 278 } 279} 280 281func TestConfigConvertToTarget(t *testing.T) { 282 tests := []struct { 283 // ********* Setup ********* 284 // Test description. 285 description string 286 287 // ********* Action ********* 288 // The current directory where Soong is being executed. 289 dir string 290 291 // The current prefix string to be pre-appended to the target. 292 prefix string 293 294 // ********* Validation ********* 295 // The expected target to be invoked in ninja. 296 expectedTarget string 297 }{{ 298 description: "one level directory in source tree", 299 dir: "test1", 300 prefix: "MODULES-IN-", 301 expectedTarget: "MODULES-IN-test1", 302 }, { 303 description: "multiple level directories in source tree", 304 dir: "test1/test2/test3/test4", 305 prefix: "GET-INSTALL-PATH-IN-", 306 expectedTarget: "GET-INSTALL-PATH-IN-test1-test2-test3-test4", 307 }} 308 for _, tt := range tests { 309 t.Run(tt.description, func(t *testing.T) { 310 target := convertToTarget(tt.dir, tt.prefix) 311 if target != tt.expectedTarget { 312 t.Errorf("expected %s, got %s for target", tt.expectedTarget, target) 313 } 314 }) 315 } 316} 317 318func setTop(t *testing.T, dir string) func() { 319 curDir, err := os.Getwd() 320 if err != nil { 321 t.Fatalf("failed to get current directory: %v", err) 322 } 323 if err := os.Chdir(dir); err != nil { 324 t.Fatalf("failed to change directory to top dir %s: %v", dir, err) 325 } 326 return func() { os.Chdir(curDir) } 327} 328 329func createBuildFiles(t *testing.T, topDir string, buildFiles []string) { 330 for _, buildFile := range buildFiles { 331 buildFile = filepath.Join(topDir, buildFile) 332 if err := ioutil.WriteFile(buildFile, []byte{}, 0644); err != nil { 333 t.Errorf("failed to create file %s: %v", buildFile, err) 334 } 335 } 336} 337 338func createDirectories(t *testing.T, topDir string, dirs []string) { 339 for _, dir := range dirs { 340 dir = filepath.Join(topDir, dir) 341 if err := os.MkdirAll(dir, 0755); err != nil { 342 t.Errorf("failed to create %s directory: %v", dir, err) 343 } 344 } 345} 346 347func TestConfigGetTargets(t *testing.T) { 348 ctx := testContext() 349 tests := []struct { 350 // ********* Setup ********* 351 // Test description. 352 description string 353 354 // Directories that exist in the source tree. 355 dirsInTrees []string 356 357 // Build files that exists in the source tree. 358 buildFiles []string 359 360 // ********* Action ********* 361 // Directories passed in to soong_ui. 362 dirs []string 363 364 // Current directory that the user executed the build action command. 365 curDir string 366 367 // ********* Validation ********* 368 // Expected targets from the function. 369 expectedTargets []string 370 371 // Expecting error from running test case. 372 errStr string 373 }{{ 374 description: "one target dir specified", 375 dirsInTrees: []string{"0/1/2/3"}, 376 buildFiles: []string{"0/1/2/3/Android.bp"}, 377 dirs: []string{"1/2/3"}, 378 curDir: "0", 379 expectedTargets: []string{"MODULES-IN-0-1-2-3"}, 380 }, { 381 description: "one target dir specified, build file does not exist", 382 dirsInTrees: []string{"0/1/2/3"}, 383 buildFiles: []string{}, 384 dirs: []string{"1/2/3"}, 385 curDir: "0", 386 errStr: "Build file not found for 0/1/2/3 directory", 387 }, { 388 description: "one target dir specified, invalid targets specified", 389 dirsInTrees: []string{"0/1/2/3"}, 390 buildFiles: []string{}, 391 dirs: []string{"1/2/3:t1:t2"}, 392 curDir: "0", 393 errStr: "1/2/3:t1:t2 not in proper directory:target1,target2,... format (\":\" was specified more than once)", 394 }, { 395 description: "one target dir specified, no targets specified but has colon", 396 dirsInTrees: []string{"0/1/2/3"}, 397 buildFiles: []string{"0/1/2/3/Android.bp"}, 398 dirs: []string{"1/2/3:"}, 399 curDir: "0", 400 expectedTargets: []string{"MODULES-IN-0-1-2-3"}, 401 }, { 402 description: "one target dir specified, two targets specified", 403 dirsInTrees: []string{"0/1/2/3"}, 404 buildFiles: []string{"0/1/2/3/Android.bp"}, 405 dirs: []string{"1/2/3:t1,t2"}, 406 curDir: "0", 407 expectedTargets: []string{"t1", "t2"}, 408 }, { 409 description: "one target dir specified, no targets and has a comma", 410 dirsInTrees: []string{"0/1/2/3"}, 411 buildFiles: []string{"0/1/2/3/Android.bp"}, 412 dirs: []string{"1/2/3:,"}, 413 curDir: "0", 414 errStr: "0/1/2/3 not in proper directory:target1,target2,... format", 415 }, { 416 description: "one target dir specified, improper targets defined", 417 dirsInTrees: []string{"0/1/2/3"}, 418 buildFiles: []string{"0/1/2/3/Android.bp"}, 419 dirs: []string{"1/2/3:,t1"}, 420 curDir: "0", 421 errStr: "0/1/2/3 not in proper directory:target1,target2,... format", 422 }, { 423 description: "one target dir specified, blank target", 424 dirsInTrees: []string{"0/1/2/3"}, 425 buildFiles: []string{"0/1/2/3/Android.bp"}, 426 dirs: []string{"1/2/3:t1,"}, 427 curDir: "0", 428 errStr: "0/1/2/3 not in proper directory:target1,target2,... format", 429 }, { 430 description: "one target dir specified, many targets specified", 431 dirsInTrees: []string{"0/1/2/3"}, 432 buildFiles: []string{"0/1/2/3/Android.bp"}, 433 dirs: []string{"1/2/3:t1,t2,t3,t4,t5,t6,t7,t8,t9,t10"}, 434 curDir: "0", 435 expectedTargets: []string{"t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10"}, 436 }, { 437 description: "one target dir specified, one target specified, build file does not exist", 438 dirsInTrees: []string{"0/1/2/3"}, 439 buildFiles: []string{}, 440 dirs: []string{"1/2/3:t1"}, 441 curDir: "0", 442 errStr: "Couldn't locate a build file from 0/1/2/3 directory", 443 }, { 444 description: "one target dir specified, one target specified, build file not in target dir", 445 dirsInTrees: []string{"0/1/2/3"}, 446 buildFiles: []string{"0/1/2/Android.mk"}, 447 dirs: []string{"1/2/3:t1"}, 448 curDir: "0", 449 errStr: "Couldn't locate a build file from 0/1/2/3 directory", 450 }, { 451 description: "one target dir specified, build file not in target dir", 452 dirsInTrees: []string{"0/1/2/3"}, 453 buildFiles: []string{"0/1/2/Android.mk"}, 454 dirs: []string{"1/2/3"}, 455 curDir: "0", 456 expectedTargets: []string{"MODULES-IN-0-1-2"}, 457 }, { 458 description: "multiple targets dir specified, targets specified", 459 dirsInTrees: []string{"0/1/2/3", "0/3/4"}, 460 buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, 461 dirs: []string{"1/2/3:t1,t2", "3/4:t3,t4,t5"}, 462 curDir: "0", 463 expectedTargets: []string{"t1", "t2", "t3", "t4", "t5"}, 464 }, { 465 description: "multiple targets dir specified, one directory has targets specified", 466 dirsInTrees: []string{"0/1/2/3", "0/3/4"}, 467 buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, 468 dirs: []string{"1/2/3:t1,t2", "3/4"}, 469 curDir: "0", 470 expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"}, 471 }, { 472 description: "two dirs specified, only one dir exist", 473 dirsInTrees: []string{"0/1/2/3"}, 474 buildFiles: []string{"0/1/2/3/Android.mk"}, 475 dirs: []string{"1/2/3:t1", "3/4"}, 476 curDir: "0", 477 errStr: "couldn't find directory 0/3/4", 478 }, { 479 description: "multiple targets dirs specified at root source tree", 480 dirsInTrees: []string{"0/1/2/3", "0/3/4"}, 481 buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, 482 dirs: []string{"0/1/2/3:t1,t2", "0/3/4"}, 483 curDir: ".", 484 expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"}, 485 }, { 486 description: "no directories specified", 487 dirsInTrees: []string{"0/1/2/3", "0/3/4"}, 488 buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, 489 dirs: []string{}, 490 curDir: ".", 491 }} 492 for _, tt := range tests { 493 t.Run(tt.description, func(t *testing.T) { 494 defer logger.Recover(func(err error) { 495 if tt.errStr == "" { 496 t.Fatalf("Got unexpected error: %v", err) 497 } 498 if tt.errStr != err.Error() { 499 t.Errorf("expected %s, got %s", tt.errStr, err.Error()) 500 } 501 }) 502 503 // Create the root source tree. 504 topDir, err := ioutil.TempDir("", "") 505 if err != nil { 506 t.Fatalf("failed to create temp dir: %v", err) 507 } 508 defer os.RemoveAll(topDir) 509 510 createDirectories(t, topDir, tt.dirsInTrees) 511 createBuildFiles(t, topDir, tt.buildFiles) 512 r := setTop(t, topDir) 513 defer r() 514 515 targets := getTargetsFromDirs(ctx, tt.curDir, tt.dirs, "MODULES-IN-") 516 if !reflect.DeepEqual(targets, tt.expectedTargets) { 517 t.Errorf("expected %v, got %v for targets", tt.expectedTargets, targets) 518 } 519 520 // If the execution reached here and there was an expected error code, the unit test case failed. 521 if tt.errStr != "" { 522 t.Errorf("expecting error %s", tt.errStr) 523 } 524 }) 525 } 526} 527 528func TestConfigFindBuildFile(t *testing.T) { 529 ctx := testContext() 530 531 tests := []struct { 532 // ********* Setup ********* 533 // Test description. 534 description string 535 536 // Array of build files to create in dir. 537 buildFiles []string 538 539 // Directories that exist in the source tree. 540 dirsInTrees []string 541 542 // ********* Action ********* 543 // The base directory is where findBuildFile is invoked. 544 dir string 545 546 // ********* Validation ********* 547 // Expected build file path to find. 548 expectedBuildFile string 549 }{{ 550 description: "build file exists at leaf directory", 551 buildFiles: []string{"1/2/3/Android.bp"}, 552 dirsInTrees: []string{"1/2/3"}, 553 dir: "1/2/3", 554 expectedBuildFile: "1/2/3/Android.mk", 555 }, { 556 description: "build file exists in all directory paths", 557 buildFiles: []string{"1/Android.mk", "1/2/Android.mk", "1/2/3/Android.mk"}, 558 dirsInTrees: []string{"1/2/3"}, 559 dir: "1/2/3", 560 expectedBuildFile: "1/2/3/Android.mk", 561 }, { 562 description: "build file does not exist in all directory paths", 563 buildFiles: []string{}, 564 dirsInTrees: []string{"1/2/3"}, 565 dir: "1/2/3", 566 expectedBuildFile: "", 567 }, { 568 description: "build file exists only at top directory", 569 buildFiles: []string{"Android.bp"}, 570 dirsInTrees: []string{"1/2/3"}, 571 dir: "1/2/3", 572 expectedBuildFile: "", 573 }, { 574 description: "build file exist in a subdirectory", 575 buildFiles: []string{"1/2/Android.bp"}, 576 dirsInTrees: []string{"1/2/3"}, 577 dir: "1/2/3", 578 expectedBuildFile: "1/2/Android.mk", 579 }, { 580 description: "build file exists in a subdirectory", 581 buildFiles: []string{"1/Android.mk"}, 582 dirsInTrees: []string{"1/2/3"}, 583 dir: "1/2/3", 584 expectedBuildFile: "1/Android.mk", 585 }, { 586 description: "top directory", 587 buildFiles: []string{"Android.bp"}, 588 dirsInTrees: []string{}, 589 dir: ".", 590 expectedBuildFile: "", 591 }, { 592 description: "build file exists in subdirectory", 593 buildFiles: []string{"1/2/3/Android.bp", "1/2/4/Android.bp"}, 594 dirsInTrees: []string{"1/2/3", "1/2/4"}, 595 dir: "1/2", 596 expectedBuildFile: "1/2/Android.mk", 597 }, { 598 description: "build file exists in parent subdirectory", 599 buildFiles: []string{"1/5/Android.bp"}, 600 dirsInTrees: []string{"1/2/3", "1/2/4", "1/5"}, 601 dir: "1/2", 602 expectedBuildFile: "1/Android.mk", 603 }, { 604 description: "build file exists in deep parent's subdirectory.", 605 buildFiles: []string{"1/5/6/Android.bp"}, 606 dirsInTrees: []string{"1/2/3", "1/2/4", "1/5/6", "1/5/7"}, 607 dir: "1/2", 608 expectedBuildFile: "1/Android.mk", 609 }} 610 611 for _, tt := range tests { 612 t.Run(tt.description, func(t *testing.T) { 613 defer logger.Recover(func(err error) { 614 t.Fatalf("Got unexpected error: %v", err) 615 }) 616 617 topDir, err := ioutil.TempDir("", "") 618 if err != nil { 619 t.Fatalf("failed to create temp dir: %v", err) 620 } 621 defer os.RemoveAll(topDir) 622 623 createDirectories(t, topDir, tt.dirsInTrees) 624 createBuildFiles(t, topDir, tt.buildFiles) 625 626 curDir, err := os.Getwd() 627 if err != nil { 628 t.Fatalf("Could not get working directory: %v", err) 629 } 630 defer func() { os.Chdir(curDir) }() 631 if err := os.Chdir(topDir); err != nil { 632 t.Fatalf("Could not change top dir to %s: %v", topDir, err) 633 } 634 635 buildFile := findBuildFile(ctx, tt.dir) 636 if buildFile != tt.expectedBuildFile { 637 t.Errorf("expected %q, got %q for build file", tt.expectedBuildFile, buildFile) 638 } 639 }) 640 } 641} 642 643func TestConfigSplitArgs(t *testing.T) { 644 tests := []struct { 645 // ********* Setup ********* 646 // Test description. 647 description string 648 649 // ********* Action ********* 650 // Arguments passed in to soong_ui. 651 args []string 652 653 // ********* Validation ********* 654 // Expected newArgs list after extracting the directories. 655 expectedNewArgs []string 656 657 // Expected directories 658 expectedDirs []string 659 }{{ 660 description: "flags but no directories specified", 661 args: []string{"showcommands", "-j", "-k"}, 662 expectedNewArgs: []string{"showcommands", "-j", "-k"}, 663 expectedDirs: []string{}, 664 }, { 665 description: "flags and one directory specified", 666 args: []string{"snod", "-j", "dir:target1,target2"}, 667 expectedNewArgs: []string{"snod", "-j"}, 668 expectedDirs: []string{"dir:target1,target2"}, 669 }, { 670 description: "flags and directories specified", 671 args: []string{"dist", "-k", "dir1", "dir2:target1,target2"}, 672 expectedNewArgs: []string{"dist", "-k"}, 673 expectedDirs: []string{"dir1", "dir2:target1,target2"}, 674 }, { 675 description: "only directories specified", 676 args: []string{"dir1", "dir2", "dir3:target1,target2"}, 677 expectedNewArgs: []string{}, 678 expectedDirs: []string{"dir1", "dir2", "dir3:target1,target2"}, 679 }} 680 for _, tt := range tests { 681 t.Run(tt.description, func(t *testing.T) { 682 args, dirs := splitArgs(tt.args) 683 if !reflect.DeepEqual(tt.expectedNewArgs, args) { 684 t.Errorf("expected %v, got %v for arguments", tt.expectedNewArgs, args) 685 } 686 if !reflect.DeepEqual(tt.expectedDirs, dirs) { 687 t.Errorf("expected %v, got %v for directories", tt.expectedDirs, dirs) 688 } 689 }) 690 } 691} 692 693type envVar struct { 694 name string 695 value string 696} 697 698type buildActionTestCase struct { 699 // ********* Setup ********* 700 // Test description. 701 description string 702 703 // Directories that exist in the source tree. 704 dirsInTrees []string 705 706 // Build files that exists in the source tree. 707 buildFiles []string 708 709 // Create root symlink that points to topDir. 710 rootSymlink bool 711 712 // ********* Action ********* 713 // Arguments passed in to soong_ui. 714 args []string 715 716 // Directory where the build action was invoked. 717 curDir string 718 719 // WITH_TIDY_ONLY environment variable specified. 720 tidyOnly string 721 722 // ********* Validation ********* 723 // Expected arguments to be in Config instance. 724 expectedArgs []string 725 726 // Expecting error from running test case. 727 expectedErrStr string 728} 729 730func testGetConfigArgs(t *testing.T, tt buildActionTestCase, action BuildAction) { 731 ctx := testContext() 732 733 defer logger.Recover(func(err error) { 734 if tt.expectedErrStr == "" { 735 t.Fatalf("Got unexpected error: %v", err) 736 } 737 if tt.expectedErrStr != err.Error() { 738 t.Errorf("expected %s, got %s", tt.expectedErrStr, err.Error()) 739 } 740 }) 741 742 // Environment variables to set it to blank on every test case run. 743 resetEnvVars := []string{ 744 "WITH_TIDY_ONLY", 745 } 746 747 for _, name := range resetEnvVars { 748 if err := os.Unsetenv(name); err != nil { 749 t.Fatalf("failed to unset environment variable %s: %v", name, err) 750 } 751 } 752 if tt.tidyOnly != "" { 753 if err := os.Setenv("WITH_TIDY_ONLY", tt.tidyOnly); err != nil { 754 t.Errorf("failed to set WITH_TIDY_ONLY to %s: %v", tt.tidyOnly, err) 755 } 756 } 757 758 // Create the root source tree. 759 topDir, err := ioutil.TempDir("", "") 760 if err != nil { 761 t.Fatalf("failed to create temp dir: %v", err) 762 } 763 defer os.RemoveAll(topDir) 764 765 createDirectories(t, topDir, tt.dirsInTrees) 766 createBuildFiles(t, topDir, tt.buildFiles) 767 768 if tt.rootSymlink { 769 // Create a secondary root source tree which points to the true root source tree. 770 symlinkTopDir, err := ioutil.TempDir("", "") 771 if err != nil { 772 t.Fatalf("failed to create symlink temp dir: %v", err) 773 } 774 defer os.RemoveAll(symlinkTopDir) 775 776 symlinkTopDir = filepath.Join(symlinkTopDir, "root") 777 err = os.Symlink(topDir, symlinkTopDir) 778 if err != nil { 779 t.Fatalf("failed to create symlink: %v", err) 780 } 781 topDir = symlinkTopDir 782 } 783 784 r := setTop(t, topDir) 785 defer r() 786 787 // The next block is to create the root build file. 788 rootBuildFileDir := filepath.Dir(srcDirFileCheck) 789 if err := os.MkdirAll(rootBuildFileDir, 0755); err != nil { 790 t.Fatalf("Failed to create %s directory: %v", rootBuildFileDir, err) 791 } 792 793 if err := ioutil.WriteFile(srcDirFileCheck, []byte{}, 0644); err != nil { 794 t.Fatalf("failed to create %s file: %v", srcDirFileCheck, err) 795 } 796 797 args := getConfigArgs(action, tt.curDir, ctx, tt.args) 798 if !reflect.DeepEqual(tt.expectedArgs, args) { 799 t.Fatalf("expected %v, got %v for config arguments", tt.expectedArgs, args) 800 } 801 802 // If the execution reached here and there was an expected error code, the unit test case failed. 803 if tt.expectedErrStr != "" { 804 t.Errorf("expecting error %s", tt.expectedErrStr) 805 } 806} 807 808func TestGetConfigArgsBuildModules(t *testing.T) { 809 tests := []buildActionTestCase{{ 810 description: "normal execution from the root source tree directory", 811 dirsInTrees: []string{"0/1/2", "0/2", "0/3"}, 812 buildFiles: []string{"0/1/2/Android.mk", "0/2/Android.bp", "0/3/Android.mk"}, 813 args: []string{"-j", "fake_module", "fake_module2"}, 814 curDir: ".", 815 tidyOnly: "", 816 expectedArgs: []string{"-j", "fake_module", "fake_module2"}, 817 }, { 818 description: "normal execution in deep directory", 819 dirsInTrees: []string{"0/1/2", "0/2", "0/3", "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6"}, 820 buildFiles: []string{"0/1/2/Android.mk", "0/2/Android.bp", "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/Android.mk"}, 821 args: []string{"-j", "fake_module", "fake_module2", "-k"}, 822 curDir: "1/2/3/4/5/6/7/8/9", 823 tidyOnly: "", 824 expectedArgs: []string{"-j", "fake_module", "fake_module2", "-k"}, 825 }, { 826 description: "normal execution in deep directory, no targets", 827 dirsInTrees: []string{"0/1/2", "0/2", "0/3", "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6"}, 828 buildFiles: []string{"0/1/2/Android.mk", "0/2/Android.bp", "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/Android.mk"}, 829 args: []string{"-j", "-k"}, 830 curDir: "1/2/3/4/5/6/7/8/9", 831 tidyOnly: "", 832 expectedArgs: []string{"-j", "-k"}, 833 }, { 834 description: "normal execution in root source tree, no args", 835 dirsInTrees: []string{"0/1/2", "0/2", "0/3"}, 836 buildFiles: []string{"0/1/2/Android.mk", "0/2/Android.bp"}, 837 args: []string{}, 838 curDir: "0/2", 839 tidyOnly: "", 840 expectedArgs: []string{}, 841 }, { 842 description: "normal execution in symlink root source tree, no args", 843 dirsInTrees: []string{"0/1/2", "0/2", "0/3"}, 844 buildFiles: []string{"0/1/2/Android.mk", "0/2/Android.bp"}, 845 rootSymlink: true, 846 args: []string{}, 847 curDir: "0/2", 848 tidyOnly: "", 849 expectedArgs: []string{}, 850 }} 851 for _, tt := range tests { 852 t.Run("build action BUILD_MODULES with dependencies, "+tt.description, func(t *testing.T) { 853 testGetConfigArgs(t, tt, BUILD_MODULES) 854 }) 855 } 856} 857 858func TestGetConfigArgsBuildModulesInDirectory(t *testing.T) { 859 tests := []buildActionTestCase{{ 860 description: "normal execution in a directory", 861 dirsInTrees: []string{"0/1/2"}, 862 buildFiles: []string{"0/1/2/Android.mk"}, 863 args: []string{"fake-module"}, 864 curDir: "0/1/2", 865 tidyOnly: "", 866 expectedArgs: []string{"fake-module", "MODULES-IN-0-1-2"}, 867 }, { 868 description: "build file in parent directory", 869 dirsInTrees: []string{"0/1/2"}, 870 buildFiles: []string{"0/1/Android.mk"}, 871 args: []string{}, 872 curDir: "0/1/2", 873 tidyOnly: "", 874 expectedArgs: []string{"MODULES-IN-0-1"}, 875 }, 876 { 877 description: "build file in parent directory, multiple module names passed in", 878 dirsInTrees: []string{"0/1/2"}, 879 buildFiles: []string{"0/1/Android.mk"}, 880 args: []string{"fake-module1", "fake-module2", "fake-module3"}, 881 curDir: "0/1/2", 882 tidyOnly: "", 883 expectedArgs: []string{"fake-module1", "fake-module2", "fake-module3", "MODULES-IN-0-1"}, 884 }, { 885 description: "build file in 2nd level parent directory", 886 dirsInTrees: []string{"0/1/2"}, 887 buildFiles: []string{"0/Android.bp"}, 888 args: []string{}, 889 curDir: "0/1/2", 890 tidyOnly: "", 891 expectedArgs: []string{"MODULES-IN-0"}, 892 }, { 893 description: "build action executed at root directory", 894 dirsInTrees: []string{}, 895 buildFiles: []string{}, 896 rootSymlink: false, 897 args: []string{}, 898 curDir: ".", 899 tidyOnly: "", 900 expectedArgs: []string{}, 901 }, { 902 description: "build action executed at root directory in symlink", 903 dirsInTrees: []string{}, 904 buildFiles: []string{}, 905 rootSymlink: true, 906 args: []string{}, 907 curDir: ".", 908 tidyOnly: "", 909 expectedArgs: []string{}, 910 }, { 911 description: "build file not found", 912 dirsInTrees: []string{"0/1/2"}, 913 buildFiles: []string{}, 914 args: []string{}, 915 curDir: "0/1/2", 916 tidyOnly: "", 917 expectedArgs: []string{"MODULES-IN-0-1-2"}, 918 expectedErrStr: "Build file not found for 0/1/2 directory", 919 }, { 920 description: "GET-INSTALL-PATH specified,", 921 dirsInTrees: []string{"0/1/2"}, 922 buildFiles: []string{"0/1/Android.mk"}, 923 args: []string{"GET-INSTALL-PATH", "-j", "-k", "GET-INSTALL-PATH"}, 924 curDir: "0/1/2", 925 tidyOnly: "", 926 expectedArgs: []string{"-j", "-k", "GET-INSTALL-PATH-IN-0-1"}, 927 }, { 928 description: "tidy only environment variable specified,", 929 dirsInTrees: []string{"0/1/2"}, 930 buildFiles: []string{"0/1/Android.mk"}, 931 args: []string{"GET-INSTALL-PATH"}, 932 curDir: "0/1/2", 933 tidyOnly: "true", 934 expectedArgs: []string{"tidy_only"}, 935 }, { 936 description: "normal execution in root directory with args", 937 dirsInTrees: []string{}, 938 buildFiles: []string{}, 939 args: []string{"-j", "-k", "fake_module"}, 940 curDir: "", 941 tidyOnly: "", 942 expectedArgs: []string{"-j", "-k", "fake_module"}, 943 }} 944 for _, tt := range tests { 945 t.Run("build action BUILD_MODULES_IN_DIR, "+tt.description, func(t *testing.T) { 946 testGetConfigArgs(t, tt, BUILD_MODULES_IN_A_DIRECTORY) 947 }) 948 } 949} 950 951func TestGetConfigArgsBuildModulesInDirectories(t *testing.T) { 952 tests := []buildActionTestCase{{ 953 description: "normal execution in a directory", 954 dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, 955 buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, 956 args: []string{"3.1/", "3.2/", "3.3/"}, 957 curDir: "0/1/2", 958 tidyOnly: "", 959 expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-2-3.3"}, 960 }, { 961 description: "GET-INSTALL-PATH specified", 962 dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3"}, 963 buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/Android.bp"}, 964 args: []string{"GET-INSTALL-PATH", "2/3.1/", "2/3.2", "3"}, 965 curDir: "0/1", 966 tidyOnly: "", 967 expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1-2-3.1", "GET-INSTALL-PATH-IN-0-1-2-3.2", "GET-INSTALL-PATH-IN-0-1"}, 968 }, { 969 description: "tidy only environment variable specified", 970 dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, 971 buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, 972 args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3"}, 973 curDir: "0/1/2", 974 tidyOnly: "1", 975 expectedArgs: []string{"tidy_only"}, 976 }, { 977 description: "normal execution from top dir directory", 978 dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"}, 979 buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/3/Android.bp", "0/2/Android.bp"}, 980 rootSymlink: false, 981 args: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"}, 982 curDir: ".", 983 tidyOnly: "", 984 expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-3", "MODULES-IN-0-2"}, 985 }, { 986 description: "normal execution from top dir directory in symlink", 987 dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"}, 988 buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/3/Android.bp", "0/2/Android.bp"}, 989 rootSymlink: true, 990 args: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"}, 991 curDir: ".", 992 tidyOnly: "", 993 expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-3", "MODULES-IN-0-2"}, 994 }} 995 for _, tt := range tests { 996 t.Run("build action BUILD_MODULES_IN_DIRS, "+tt.description, func(t *testing.T) { 997 testGetConfigArgs(t, tt, BUILD_MODULES_IN_DIRECTORIES) 998 }) 999 } 1000} 1001 1002func TestBuildConfig(t *testing.T) { 1003 tests := []struct { 1004 name string 1005 environ Environment 1006 arguments []string 1007 useBazel bool 1008 expectedBuildConfig *smpb.BuildConfig 1009 }{ 1010 { 1011 name: "none set", 1012 environ: Environment{}, 1013 expectedBuildConfig: &smpb.BuildConfig{ 1014 ForceUseGoma: proto.Bool(false), 1015 UseGoma: proto.Bool(false), 1016 UseRbe: proto.Bool(false), 1017 BazelAsNinja: proto.Bool(false), 1018 BazelMixedBuild: proto.Bool(false), 1019 }, 1020 }, 1021 { 1022 name: "force use goma", 1023 environ: Environment{"FORCE_USE_GOMA=1"}, 1024 expectedBuildConfig: &smpb.BuildConfig{ 1025 ForceUseGoma: proto.Bool(true), 1026 UseGoma: proto.Bool(false), 1027 UseRbe: proto.Bool(false), 1028 BazelAsNinja: proto.Bool(false), 1029 BazelMixedBuild: proto.Bool(false), 1030 }, 1031 }, 1032 { 1033 name: "use goma", 1034 environ: Environment{"USE_GOMA=1"}, 1035 expectedBuildConfig: &smpb.BuildConfig{ 1036 ForceUseGoma: proto.Bool(false), 1037 UseGoma: proto.Bool(true), 1038 UseRbe: proto.Bool(false), 1039 BazelAsNinja: proto.Bool(false), 1040 BazelMixedBuild: proto.Bool(false), 1041 }, 1042 }, 1043 { 1044 name: "use rbe", 1045 environ: Environment{"USE_RBE=1"}, 1046 expectedBuildConfig: &smpb.BuildConfig{ 1047 ForceUseGoma: proto.Bool(false), 1048 UseGoma: proto.Bool(false), 1049 UseRbe: proto.Bool(true), 1050 BazelAsNinja: proto.Bool(false), 1051 BazelMixedBuild: proto.Bool(false), 1052 }, 1053 }, 1054 { 1055 name: "use bazel as ninja", 1056 environ: Environment{}, 1057 useBazel: true, 1058 expectedBuildConfig: &smpb.BuildConfig{ 1059 ForceUseGoma: proto.Bool(false), 1060 UseGoma: proto.Bool(false), 1061 UseRbe: proto.Bool(false), 1062 BazelAsNinja: proto.Bool(true), 1063 BazelMixedBuild: proto.Bool(false), 1064 }, 1065 }, 1066 { 1067 name: "bazel mixed build", 1068 environ: Environment{"USE_BAZEL_ANALYSIS=1"}, 1069 expectedBuildConfig: &smpb.BuildConfig{ 1070 ForceUseGoma: proto.Bool(false), 1071 UseGoma: proto.Bool(false), 1072 UseRbe: proto.Bool(false), 1073 BazelAsNinja: proto.Bool(false), 1074 BazelMixedBuild: proto.Bool(true), 1075 }, 1076 }, 1077 { 1078 name: "specified targets", 1079 environ: Environment{}, 1080 useBazel: true, 1081 arguments: []string{"droid", "dist"}, 1082 expectedBuildConfig: &smpb.BuildConfig{ 1083 ForceUseGoma: proto.Bool(false), 1084 UseGoma: proto.Bool(false), 1085 UseRbe: proto.Bool(false), 1086 BazelAsNinja: proto.Bool(true), 1087 BazelMixedBuild: proto.Bool(false), 1088 Targets: []string{"droid", "dist"}, 1089 }, 1090 }, 1091 { 1092 name: "all set", 1093 environ: Environment{ 1094 "FORCE_USE_GOMA=1", 1095 "USE_GOMA=1", 1096 "USE_RBE=1", 1097 "USE_BAZEL_ANALYSIS=1", 1098 }, 1099 useBazel: true, 1100 expectedBuildConfig: &smpb.BuildConfig{ 1101 ForceUseGoma: proto.Bool(true), 1102 UseGoma: proto.Bool(true), 1103 UseRbe: proto.Bool(true), 1104 BazelAsNinja: proto.Bool(true), 1105 BazelMixedBuild: proto.Bool(true), 1106 }, 1107 }, 1108 } 1109 1110 for _, tc := range tests { 1111 t.Run(tc.name, func(t *testing.T) { 1112 c := &configImpl{ 1113 environ: &tc.environ, 1114 useBazel: tc.useBazel, 1115 arguments: tc.arguments, 1116 } 1117 config := Config{c} 1118 actualBuildConfig := buildConfig(config) 1119 if expected := tc.expectedBuildConfig; !proto.Equal(expected, actualBuildConfig) { 1120 t.Errorf("Expected build config != actual build config: %#v != %#v", *expected, *actualBuildConfig) 1121 } 1122 }) 1123 } 1124} 1125 1126func TestGetMetricsUploaderApp(t *testing.T) { 1127 1128 metricsUploaderDir := "metrics_uploader_dir" 1129 metricsUploaderBinary := "metrics_uploader_binary" 1130 metricsUploaderPath := filepath.Join(metricsUploaderDir, metricsUploaderBinary) 1131 tests := []struct { 1132 description string 1133 environ Environment 1134 createFiles bool 1135 expected string 1136 }{{ 1137 description: "Uploader binary exist", 1138 environ: Environment{"METRICS_UPLOADER=" + metricsUploaderPath}, 1139 createFiles: true, 1140 expected: metricsUploaderPath, 1141 }, { 1142 description: "Uploader binary not exist", 1143 environ: Environment{"METRICS_UPLOADER=" + metricsUploaderPath}, 1144 createFiles: false, 1145 expected: "", 1146 }, { 1147 description: "Uploader binary variable not set", 1148 createFiles: true, 1149 expected: "", 1150 }} 1151 1152 for _, tt := range tests { 1153 t.Run(tt.description, func(t *testing.T) { 1154 defer logger.Recover(func(err error) { 1155 t.Fatalf("got unexpected error: %v", err) 1156 }) 1157 1158 // Create the root source tree. 1159 topDir, err := ioutil.TempDir("", "") 1160 if err != nil { 1161 t.Fatalf("failed to create temp dir: %v", err) 1162 } 1163 defer os.RemoveAll(topDir) 1164 1165 expected := tt.expected 1166 if len(expected) > 0 { 1167 expected = filepath.Join(topDir, expected) 1168 } 1169 1170 if tt.createFiles { 1171 if err := os.MkdirAll(filepath.Join(topDir, metricsUploaderDir), 0755); err != nil { 1172 t.Errorf("failed to create %s directory: %v", metricsUploaderDir, err) 1173 } 1174 if err := ioutil.WriteFile(filepath.Join(topDir, metricsUploaderPath), []byte{}, 0644); err != nil { 1175 t.Errorf("failed to create file %s: %v", expected, err) 1176 } 1177 } 1178 1179 actual := GetMetricsUploader(topDir, &tt.environ) 1180 1181 if actual != expected { 1182 t.Errorf("expecting: %s, actual: %s", expected, actual) 1183 } 1184 }) 1185 } 1186} 1187