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