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