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