| // Copyright 2017 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package build |
| |
| import ( |
| "bytes" |
| "context" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "reflect" |
| "strings" |
| "testing" |
| |
| "android/soong/ui/logger" |
| smpb "android/soong/ui/metrics/metrics_proto" |
| "android/soong/ui/status" |
| |
| "google.golang.org/protobuf/encoding/prototext" |
| |
| "google.golang.org/protobuf/proto" |
| ) |
| |
| func testContext() Context { |
| return Context{&ContextImpl{ |
| Context: context.Background(), |
| Logger: logger.New(&bytes.Buffer{}), |
| Writer: &bytes.Buffer{}, |
| Status: &status.Status{}, |
| }} |
| } |
| |
| func TestConfigParseArgsJK(t *testing.T) { |
| ctx := testContext() |
| |
| testCases := []struct { |
| args []string |
| |
| parallel int |
| keepGoing int |
| remaining []string |
| }{ |
| {nil, -1, -1, nil}, |
| |
| {[]string{"-j"}, -1, -1, nil}, |
| {[]string{"-j1"}, 1, -1, nil}, |
| {[]string{"-j1234"}, 1234, -1, nil}, |
| |
| {[]string{"-j", "1"}, 1, -1, nil}, |
| {[]string{"-j", "1234"}, 1234, -1, nil}, |
| {[]string{"-j", "1234", "abc"}, 1234, -1, []string{"abc"}}, |
| {[]string{"-j", "abc"}, -1, -1, []string{"abc"}}, |
| {[]string{"-j", "1abc"}, -1, -1, []string{"1abc"}}, |
| |
| {[]string{"-k"}, -1, 0, nil}, |
| {[]string{"-k0"}, -1, 0, nil}, |
| {[]string{"-k1"}, -1, 1, nil}, |
| {[]string{"-k1234"}, -1, 1234, nil}, |
| |
| {[]string{"-k", "0"}, -1, 0, nil}, |
| {[]string{"-k", "1"}, -1, 1, nil}, |
| {[]string{"-k", "1234"}, -1, 1234, nil}, |
| {[]string{"-k", "1234", "abc"}, -1, 1234, []string{"abc"}}, |
| {[]string{"-k", "abc"}, -1, 0, []string{"abc"}}, |
| {[]string{"-k", "1abc"}, -1, 0, []string{"1abc"}}, |
| |
| // TODO: These are supported in Make, should we support them? |
| //{[]string{"-kj"}, -1, 0}, |
| //{[]string{"-kj8"}, 8, 0}, |
| |
| // -jk is not valid in Make |
| } |
| |
| for _, tc := range testCases { |
| t.Run(strings.Join(tc.args, " "), func(t *testing.T) { |
| defer logger.Recover(func(err error) { |
| t.Fatal(err) |
| }) |
| |
| env := Environment([]string{}) |
| c := &configImpl{ |
| environ: &env, |
| parallel: -1, |
| keepGoing: -1, |
| } |
| c.parseArgs(ctx, tc.args) |
| |
| if c.parallel != tc.parallel { |
| t.Errorf("for %q, parallel:\nwant: %d\n got: %d\n", |
| strings.Join(tc.args, " "), |
| tc.parallel, c.parallel) |
| } |
| if c.keepGoing != tc.keepGoing { |
| t.Errorf("for %q, keep going:\nwant: %d\n got: %d\n", |
| strings.Join(tc.args, " "), |
| tc.keepGoing, c.keepGoing) |
| } |
| if !reflect.DeepEqual(c.arguments, tc.remaining) { |
| t.Errorf("for %q, remaining arguments:\nwant: %q\n got: %q\n", |
| strings.Join(tc.args, " "), |
| tc.remaining, c.arguments) |
| } |
| }) |
| } |
| } |
| |
| func TestConfigParseArgsVars(t *testing.T) { |
| ctx := testContext() |
| |
| testCases := []struct { |
| env []string |
| args []string |
| |
| expectedEnv []string |
| remaining []string |
| }{ |
| {}, |
| { |
| env: []string{"A=bc"}, |
| |
| expectedEnv: []string{"A=bc"}, |
| }, |
| { |
| args: []string{"abc"}, |
| |
| remaining: []string{"abc"}, |
| }, |
| |
| { |
| args: []string{"A=bc"}, |
| |
| expectedEnv: []string{"A=bc"}, |
| }, |
| { |
| env: []string{"A=a"}, |
| args: []string{"A=bc"}, |
| |
| expectedEnv: []string{"A=bc"}, |
| }, |
| |
| { |
| env: []string{"A=a"}, |
| args: []string{"A=", "=b"}, |
| |
| expectedEnv: []string{"A="}, |
| remaining: []string{"=b"}, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| t.Run(strings.Join(tc.args, " "), func(t *testing.T) { |
| defer logger.Recover(func(err error) { |
| t.Fatal(err) |
| }) |
| |
| e := Environment(tc.env) |
| c := &configImpl{ |
| environ: &e, |
| } |
| c.parseArgs(ctx, tc.args) |
| |
| if !reflect.DeepEqual([]string(*c.environ), tc.expectedEnv) { |
| t.Errorf("for env=%q args=%q, environment:\nwant: %q\n got: %q\n", |
| tc.env, tc.args, |
| tc.expectedEnv, []string(*c.environ)) |
| } |
| if !reflect.DeepEqual(c.arguments, tc.remaining) { |
| t.Errorf("for env=%q args=%q, remaining arguments:\nwant: %q\n got: %q\n", |
| tc.env, tc.args, |
| tc.remaining, c.arguments) |
| } |
| }) |
| } |
| } |
| |
| func TestConfigCheckTopDir(t *testing.T) { |
| ctx := testContext() |
| buildRootDir := filepath.Dir(srcDirFileCheck) |
| expectedErrStr := fmt.Sprintf("Current working directory must be the source tree. %q not found.", srcDirFileCheck) |
| |
| tests := []struct { |
| // ********* Setup ********* |
| // Test description. |
| description string |
| |
| // ********* Action ********* |
| // If set to true, the build root file is created. |
| rootBuildFile bool |
| |
| // The current path where Soong is being executed. |
| path string |
| |
| // ********* Validation ********* |
| // Expecting error and validate the error string against expectedErrStr. |
| wantErr bool |
| }{{ |
| description: "current directory is the root source tree", |
| rootBuildFile: true, |
| path: ".", |
| wantErr: false, |
| }, { |
| description: "one level deep in the source tree", |
| rootBuildFile: true, |
| path: "1", |
| wantErr: true, |
| }, { |
| description: "very deep in the source tree", |
| rootBuildFile: true, |
| 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", |
| wantErr: true, |
| }, { |
| description: "outside of source tree", |
| rootBuildFile: false, |
| path: "1/2/3/4/5", |
| wantErr: true, |
| }} |
| |
| for _, tt := range tests { |
| t.Run(tt.description, func(t *testing.T) { |
| defer logger.Recover(func(err error) { |
| if !tt.wantErr { |
| t.Fatalf("Got unexpected error: %v", err) |
| } |
| if expectedErrStr != err.Error() { |
| t.Fatalf("expected %s, got %s", expectedErrStr, err.Error()) |
| } |
| }) |
| |
| // Create the root source tree. |
| rootDir, err := ioutil.TempDir("", "") |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer os.RemoveAll(rootDir) |
| |
| // Create the build root file. This is to test if topDir returns an error if the build root |
| // file does not exist. |
| if tt.rootBuildFile { |
| dir := filepath.Join(rootDir, buildRootDir) |
| if err := os.MkdirAll(dir, 0755); err != nil { |
| t.Errorf("failed to create %s directory: %v", dir, err) |
| } |
| f := filepath.Join(rootDir, srcDirFileCheck) |
| if err := ioutil.WriteFile(f, []byte{}, 0644); err != nil { |
| t.Errorf("failed to create file %s: %v", f, err) |
| } |
| } |
| |
| // Next block of code is to set the current directory. |
| dir := rootDir |
| if tt.path != "" { |
| dir = filepath.Join(dir, tt.path) |
| if err := os.MkdirAll(dir, 0755); err != nil { |
| t.Errorf("failed to create %s directory: %v", dir, err) |
| } |
| } |
| curDir, err := os.Getwd() |
| if err != nil { |
| t.Fatalf("failed to get the current directory: %v", err) |
| } |
| defer func() { os.Chdir(curDir) }() |
| |
| if err := os.Chdir(dir); err != nil { |
| t.Fatalf("failed to change directory to %s: %v", dir, err) |
| } |
| |
| checkTopDir(ctx) |
| }) |
| } |
| } |
| |
| func TestConfigConvertToTarget(t *testing.T) { |
| tests := []struct { |
| // ********* Setup ********* |
| // Test description. |
| description string |
| |
| // ********* Action ********* |
| // The current directory where Soong is being executed. |
| dir string |
| |
| // The current prefix string to be pre-appended to the target. |
| prefix string |
| |
| // ********* Validation ********* |
| // The expected target to be invoked in ninja. |
| expectedTarget string |
| }{{ |
| description: "one level directory in source tree", |
| dir: "test1", |
| prefix: "MODULES-IN-", |
| expectedTarget: "MODULES-IN-test1", |
| }, { |
| description: "multiple level directories in source tree", |
| dir: "test1/test2/test3/test4", |
| prefix: "GET-INSTALL-PATH-IN-", |
| expectedTarget: "GET-INSTALL-PATH-IN-test1-test2-test3-test4", |
| }} |
| for _, tt := range tests { |
| t.Run(tt.description, func(t *testing.T) { |
| target := convertToTarget(tt.dir, tt.prefix) |
| if target != tt.expectedTarget { |
| t.Errorf("expected %s, got %s for target", tt.expectedTarget, target) |
| } |
| }) |
| } |
| } |
| |
| func setTop(t *testing.T, dir string) func() { |
| curDir, err := os.Getwd() |
| if err != nil { |
| t.Fatalf("failed to get current directory: %v", err) |
| } |
| if err := os.Chdir(dir); err != nil { |
| t.Fatalf("failed to change directory to top dir %s: %v", dir, err) |
| } |
| return func() { os.Chdir(curDir) } |
| } |
| |
| func createBuildFiles(t *testing.T, topDir string, buildFiles []string) { |
| for _, buildFile := range buildFiles { |
| buildFile = filepath.Join(topDir, buildFile) |
| if err := ioutil.WriteFile(buildFile, []byte{}, 0644); err != nil { |
| t.Errorf("failed to create file %s: %v", buildFile, err) |
| } |
| } |
| } |
| |
| func createDirectories(t *testing.T, topDir string, dirs []string) { |
| for _, dir := range dirs { |
| dir = filepath.Join(topDir, dir) |
| if err := os.MkdirAll(dir, 0755); err != nil { |
| t.Errorf("failed to create %s directory: %v", dir, err) |
| } |
| } |
| } |
| |
| func TestConfigGetTargets(t *testing.T) { |
| ctx := testContext() |
| tests := []struct { |
| // ********* Setup ********* |
| // Test description. |
| description string |
| |
| // Directories that exist in the source tree. |
| dirsInTrees []string |
| |
| // Build files that exists in the source tree. |
| buildFiles []string |
| |
| // ********* Action ********* |
| // Directories passed in to soong_ui. |
| dirs []string |
| |
| // Current directory that the user executed the build action command. |
| curDir string |
| |
| // ********* Validation ********* |
| // Expected targets from the function. |
| expectedTargets []string |
| |
| // Expecting error from running test case. |
| errStr string |
| }{{ |
| description: "one target dir specified", |
| dirsInTrees: []string{"0/1/2/3"}, |
| buildFiles: []string{"0/1/2/3/Android.bp"}, |
| dirs: []string{"1/2/3"}, |
| curDir: "0", |
| expectedTargets: []string{"MODULES-IN-0-1-2-3"}, |
| }, { |
| description: "one target dir specified, build file does not exist", |
| dirsInTrees: []string{"0/1/2/3"}, |
| buildFiles: []string{}, |
| dirs: []string{"1/2/3"}, |
| curDir: "0", |
| errStr: "Build file not found for 0/1/2/3 directory", |
| }, { |
| description: "one target dir specified, invalid targets specified", |
| dirsInTrees: []string{"0/1/2/3"}, |
| buildFiles: []string{}, |
| dirs: []string{"1/2/3:t1:t2"}, |
| curDir: "0", |
| errStr: "1/2/3:t1:t2 not in proper directory:target1,target2,... format (\":\" was specified more than once)", |
| }, { |
| description: "one target dir specified, no targets specified but has colon", |
| dirsInTrees: []string{"0/1/2/3"}, |
| buildFiles: []string{"0/1/2/3/Android.bp"}, |
| dirs: []string{"1/2/3:"}, |
| curDir: "0", |
| expectedTargets: []string{"MODULES-IN-0-1-2-3"}, |
| }, { |
| description: "one target dir specified, two targets specified", |
| dirsInTrees: []string{"0/1/2/3"}, |
| buildFiles: []string{"0/1/2/3/Android.bp"}, |
| dirs: []string{"1/2/3:t1,t2"}, |
| curDir: "0", |
| expectedTargets: []string{"t1", "t2"}, |
| }, { |
| description: "one target dir specified, no targets and has a comma", |
| dirsInTrees: []string{"0/1/2/3"}, |
| buildFiles: []string{"0/1/2/3/Android.bp"}, |
| dirs: []string{"1/2/3:,"}, |
| curDir: "0", |
| errStr: "0/1/2/3 not in proper directory:target1,target2,... format", |
| }, { |
| description: "one target dir specified, improper targets defined", |
| dirsInTrees: []string{"0/1/2/3"}, |
| buildFiles: []string{"0/1/2/3/Android.bp"}, |
| dirs: []string{"1/2/3:,t1"}, |
| curDir: "0", |
| errStr: "0/1/2/3 not in proper directory:target1,target2,... format", |
| }, { |
| description: "one target dir specified, blank target", |
| dirsInTrees: []string{"0/1/2/3"}, |
| buildFiles: []string{"0/1/2/3/Android.bp"}, |
| dirs: []string{"1/2/3:t1,"}, |
| curDir: "0", |
| errStr: "0/1/2/3 not in proper directory:target1,target2,... format", |
| }, { |
| description: "one target dir specified, many targets specified", |
| dirsInTrees: []string{"0/1/2/3"}, |
| buildFiles: []string{"0/1/2/3/Android.bp"}, |
| dirs: []string{"1/2/3:t1,t2,t3,t4,t5,t6,t7,t8,t9,t10"}, |
| curDir: "0", |
| expectedTargets: []string{"t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10"}, |
| }, { |
| description: "one target dir specified, one target specified, build file does not exist", |
| dirsInTrees: []string{"0/1/2/3"}, |
| buildFiles: []string{}, |
| dirs: []string{"1/2/3:t1"}, |
| curDir: "0", |
| errStr: "Couldn't locate a build file from 0/1/2/3 directory", |
| }, { |
| description: "one target dir specified, one target specified, build file not in target dir", |
| dirsInTrees: []string{"0/1/2/3"}, |
| buildFiles: []string{"0/1/2/Android.mk"}, |
| dirs: []string{"1/2/3:t1"}, |
| curDir: "0", |
| errStr: "Couldn't locate a build file from 0/1/2/3 directory", |
| }, { |
| description: "one target dir specified, build file not in target dir", |
| dirsInTrees: []string{"0/1/2/3"}, |
| buildFiles: []string{"0/1/2/Android.mk"}, |
| dirs: []string{"1/2/3"}, |
| curDir: "0", |
| expectedTargets: []string{"MODULES-IN-0-1-2"}, |
| }, { |
| description: "multiple targets dir specified, targets specified", |
| dirsInTrees: []string{"0/1/2/3", "0/3/4"}, |
| buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, |
| dirs: []string{"1/2/3:t1,t2", "3/4:t3,t4,t5"}, |
| curDir: "0", |
| expectedTargets: []string{"t1", "t2", "t3", "t4", "t5"}, |
| }, { |
| description: "multiple targets dir specified, one directory has targets specified", |
| dirsInTrees: []string{"0/1/2/3", "0/3/4"}, |
| buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, |
| dirs: []string{"1/2/3:t1,t2", "3/4"}, |
| curDir: "0", |
| expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"}, |
| }, { |
| description: "two dirs specified, only one dir exist", |
| dirsInTrees: []string{"0/1/2/3"}, |
| buildFiles: []string{"0/1/2/3/Android.mk"}, |
| dirs: []string{"1/2/3:t1", "3/4"}, |
| curDir: "0", |
| errStr: "couldn't find directory 0/3/4", |
| }, { |
| description: "multiple targets dirs specified at root source tree", |
| dirsInTrees: []string{"0/1/2/3", "0/3/4"}, |
| buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, |
| dirs: []string{"0/1/2/3:t1,t2", "0/3/4"}, |
| curDir: ".", |
| expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"}, |
| }, { |
| description: "no directories specified", |
| dirsInTrees: []string{"0/1/2/3", "0/3/4"}, |
| buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"}, |
| dirs: []string{}, |
| curDir: ".", |
| }} |
| for _, tt := range tests { |
| t.Run(tt.description, func(t *testing.T) { |
| defer logger.Recover(func(err error) { |
| if tt.errStr == "" { |
| t.Fatalf("Got unexpected error: %v", err) |
| } |
| if tt.errStr != err.Error() { |
| t.Errorf("expected %s, got %s", tt.errStr, err.Error()) |
| } |
| }) |
| |
| // Create the root source tree. |
| topDir, err := ioutil.TempDir("", "") |
| if err != nil { |
| t.Fatalf("failed to create temp dir: %v", err) |
| } |
| defer os.RemoveAll(topDir) |
| |
| createDirectories(t, topDir, tt.dirsInTrees) |
| createBuildFiles(t, topDir, tt.buildFiles) |
| r := setTop(t, topDir) |
| defer r() |
| |
| targets := getTargetsFromDirs(ctx, tt.curDir, tt.dirs, "MODULES-IN-") |
| if !reflect.DeepEqual(targets, tt.expectedTargets) { |
| t.Errorf("expected %v, got %v for targets", tt.expectedTargets, targets) |
| } |
| |
| // If the execution reached here and there was an expected error code, the unit test case failed. |
| if tt.errStr != "" { |
| t.Errorf("expecting error %s", tt.errStr) |
| } |
| }) |
| } |
| } |
| |
| func TestConfigFindBuildFile(t *testing.T) { |
| ctx := testContext() |
| |
| tests := []struct { |
| // ********* Setup ********* |
| // Test description. |
| description string |
| |
| // Array of build files to create in dir. |
| buildFiles []string |
| |
| // Directories that exist in the source tree. |
| dirsInTrees []string |
| |
| // ********* Action ********* |
| // The base directory is where findBuildFile is invoked. |
| dir string |
| |
| // ********* Validation ********* |
| // Expected build file path to find. |
| expectedBuildFile string |
| }{{ |
| description: "build file exists at leaf directory", |
| buildFiles: []string{"1/2/3/Android.bp"}, |
| dirsInTrees: []string{"1/2/3"}, |
| dir: "1/2/3", |
| expectedBuildFile: "1/2/3/Android.mk", |
| }, { |
| description: "build file exists in all directory paths", |
| buildFiles: []string{"1/Android.mk", "1/2/Android.mk", "1/2/3/Android.mk"}, |
| dirsInTrees: []string{"1/2/3"}, |
| dir: "1/2/3", |
| expectedBuildFile: "1/2/3/Android.mk", |
| }, { |
| description: "build file does not exist in all directory paths", |
| buildFiles: []string{}, |
| dirsInTrees: []string{"1/2/3"}, |
| dir: "1/2/3", |
| expectedBuildFile: "", |
| }, { |
| description: "build file exists only at top directory", |
| buildFiles: []string{"Android.bp"}, |
| dirsInTrees: []string{"1/2/3"}, |
| dir: "1/2/3", |
| expectedBuildFile: "", |
| }, { |
| description: "build file exist in a subdirectory", |
| buildFiles: []string{"1/2/Android.bp"}, |
| dirsInTrees: []string{"1/2/3"}, |
| dir: "1/2/3", |
| expectedBuildFile: "1/2/Android.mk", |
| }, { |
| description: "build file exists in a subdirectory", |
| buildFiles: []string{"1/Android.mk"}, |
| dirsInTrees: []string{"1/2/3"}, |
| dir: "1/2/3", |
| expectedBuildFile: "1/Android.mk", |
| }, { |
| description: "top directory", |
| buildFiles: []string{"Android.bp"}, |
| dirsInTrees: []string{}, |
| dir: ".", |
| expectedBuildFile: "", |
| }, { |
| description: "build file exists in subdirectory", |
| buildFiles: []string{"1/2/3/Android.bp", "1/2/4/Android.bp"}, |
| dirsInTrees: []string{"1/2/3", "1/2/4"}, |
| dir: "1/2", |
| expectedBuildFile: "1/2/Android.mk", |
| }, { |
| description: "build file exists in parent subdirectory", |
| buildFiles: []string{"1/5/Android.bp"}, |
| dirsInTrees: []string{"1/2/3", "1/2/4", "1/5"}, |
| dir: "1/2", |
| expectedBuildFile: "1/Android.mk", |
| }, { |
| description: "build file exists in deep parent's subdirectory.", |
| buildFiles: []string{"1/5/6/Android.bp"}, |
| dirsInTrees: []string{"1/2/3", "1/2/4", "1/5/6", "1/5/7"}, |
| dir: "1/2", |
| expectedBuildFile: "1/Android.mk", |
| }} |
| |
| for _, tt := range tests { |
| t.Run(tt.description, func(t *testing.T) { |
| defer logger.Recover(func(err error) { |
| t.Fatalf("Got unexpected error: %v", err) |
| }) |
| |
| topDir, err := ioutil.TempDir("", "") |
| if err != nil { |
| t.Fatalf("failed to create temp dir: %v", err) |
| } |
| defer os.RemoveAll(topDir) |
| |
| createDirectories(t, topDir, tt.dirsInTrees) |
| createBuildFiles(t, topDir, tt.buildFiles) |
| |
| curDir, err := os.Getwd() |
| if err != nil { |
| t.Fatalf("Could not get working directory: %v", err) |
| } |
| defer func() { os.Chdir(curDir) }() |
| if err := os.Chdir(topDir); err != nil { |
| t.Fatalf("Could not change top dir to %s: %v", topDir, err) |
| } |
| |
| buildFile := findBuildFile(ctx, tt.dir) |
| if buildFile != tt.expectedBuildFile { |
| t.Errorf("expected %q, got %q for build file", tt.expectedBuildFile, buildFile) |
| } |
| }) |
| } |
| } |
| |
| func TestConfigSplitArgs(t *testing.T) { |
| tests := []struct { |
| // ********* Setup ********* |
| // Test description. |
| description string |
| |
| // ********* Action ********* |
| // Arguments passed in to soong_ui. |
| args []string |
| |
| // ********* Validation ********* |
| // Expected newArgs list after extracting the directories. |
| expectedNewArgs []string |
| |
| // Expected directories |
| expectedDirs []string |
| }{{ |
| description: "flags but no directories specified", |
| args: []string{"showcommands", "-j", "-k"}, |
| expectedNewArgs: []string{"showcommands", "-j", "-k"}, |
| expectedDirs: []string{}, |
| }, { |
| description: "flags and one directory specified", |
| args: []string{"snod", "-j", "dir:target1,target2"}, |
| expectedNewArgs: []string{"snod", "-j"}, |
| expectedDirs: []string{"dir:target1,target2"}, |
| }, { |
| description: "flags and directories specified", |
| args: []string{"dist", "-k", "dir1", "dir2:target1,target2"}, |
| expectedNewArgs: []string{"dist", "-k"}, |
| expectedDirs: []string{"dir1", "dir2:target1,target2"}, |
| }, { |
| description: "only directories specified", |
| args: []string{"dir1", "dir2", "dir3:target1,target2"}, |
| expectedNewArgs: []string{}, |
| expectedDirs: []string{"dir1", "dir2", "dir3:target1,target2"}, |
| }} |
| for _, tt := range tests { |
| t.Run(tt.description, func(t *testing.T) { |
| args, dirs := splitArgs(tt.args) |
| if !reflect.DeepEqual(tt.expectedNewArgs, args) { |
| t.Errorf("expected %v, got %v for arguments", tt.expectedNewArgs, args) |
| } |
| if !reflect.DeepEqual(tt.expectedDirs, dirs) { |
| t.Errorf("expected %v, got %v for directories", tt.expectedDirs, dirs) |
| } |
| }) |
| } |
| } |
| |
| type envVar struct { |
| name string |
| value string |
| } |
| |
| type buildActionTestCase struct { |
| // ********* Setup ********* |
| // Test description. |
| description string |
| |
| // Directories that exist in the source tree. |
| dirsInTrees []string |
| |
| // Build files that exists in the source tree. |
| buildFiles []string |
| |
| // Create root symlink that points to topDir. |
| rootSymlink bool |
| |
| // ********* Action ********* |
| // Arguments passed in to soong_ui. |
| args []string |
| |
| // Directory where the build action was invoked. |
| curDir string |
| |
| // WITH_TIDY_ONLY environment variable specified. |
| tidyOnly string |
| |
| // ********* Validation ********* |
| // Expected arguments to be in Config instance. |
| expectedArgs []string |
| |
| // Expecting error from running test case. |
| expectedErrStr string |
| } |
| |
| func testGetConfigArgs(t *testing.T, tt buildActionTestCase, action BuildAction) { |
| ctx := testContext() |
| |
| defer logger.Recover(func(err error) { |
| if tt.expectedErrStr == "" { |
| t.Fatalf("Got unexpected error: %v", err) |
| } |
| if tt.expectedErrStr != err.Error() { |
| t.Errorf("expected %s, got %s", tt.expectedErrStr, err.Error()) |
| } |
| }) |
| |
| // Environment variables to set it to blank on every test case run. |
| resetEnvVars := []string{ |
| "WITH_TIDY_ONLY", |
| } |
| |
| for _, name := range resetEnvVars { |
| if err := os.Unsetenv(name); err != nil { |
| t.Fatalf("failed to unset environment variable %s: %v", name, err) |
| } |
| } |
| if tt.tidyOnly != "" { |
| if err := os.Setenv("WITH_TIDY_ONLY", tt.tidyOnly); err != nil { |
| t.Errorf("failed to set WITH_TIDY_ONLY to %s: %v", tt.tidyOnly, err) |
| } |
| } |
| |
| // Create the root source tree. |
| topDir, err := ioutil.TempDir("", "") |
| if err != nil { |
| t.Fatalf("failed to create temp dir: %v", err) |
| } |
| defer os.RemoveAll(topDir) |
| |
| createDirectories(t, topDir, tt.dirsInTrees) |
| createBuildFiles(t, topDir, tt.buildFiles) |
| |
| if tt.rootSymlink { |
| // Create a secondary root source tree which points to the true root source tree. |
| symlinkTopDir, err := ioutil.TempDir("", "") |
| if err != nil { |
| t.Fatalf("failed to create symlink temp dir: %v", err) |
| } |
| defer os.RemoveAll(symlinkTopDir) |
| |
| symlinkTopDir = filepath.Join(symlinkTopDir, "root") |
| err = os.Symlink(topDir, symlinkTopDir) |
| if err != nil { |
| t.Fatalf("failed to create symlink: %v", err) |
| } |
| topDir = symlinkTopDir |
| } |
| |
| r := setTop(t, topDir) |
| defer r() |
| |
| // The next block is to create the root build file. |
| rootBuildFileDir := filepath.Dir(srcDirFileCheck) |
| if err := os.MkdirAll(rootBuildFileDir, 0755); err != nil { |
| t.Fatalf("Failed to create %s directory: %v", rootBuildFileDir, err) |
| } |
| |
| if err := ioutil.WriteFile(srcDirFileCheck, []byte{}, 0644); err != nil { |
| t.Fatalf("failed to create %s file: %v", srcDirFileCheck, err) |
| } |
| |
| args := getConfigArgs(action, tt.curDir, ctx, tt.args) |
| if !reflect.DeepEqual(tt.expectedArgs, args) { |
| t.Fatalf("expected %v, got %v for config arguments", tt.expectedArgs, args) |
| } |
| |
| // If the execution reached here and there was an expected error code, the unit test case failed. |
| if tt.expectedErrStr != "" { |
| t.Errorf("expecting error %s", tt.expectedErrStr) |
| } |
| } |
| |
| func TestGetConfigArgsBuildModules(t *testing.T) { |
| tests := []buildActionTestCase{{ |
| description: "normal execution from the root source tree directory", |
| dirsInTrees: []string{"0/1/2", "0/2", "0/3"}, |
| buildFiles: []string{"0/1/2/Android.mk", "0/2/Android.bp", "0/3/Android.mk"}, |
| args: []string{"-j", "fake_module", "fake_module2"}, |
| curDir: ".", |
| tidyOnly: "", |
| expectedArgs: []string{"-j", "fake_module", "fake_module2"}, |
| }, { |
| description: "normal execution in deep directory", |
| dirsInTrees: []string{"0/1/2", "0/2", "0/3", "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6"}, |
| 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"}, |
| args: []string{"-j", "fake_module", "fake_module2", "-k"}, |
| curDir: "1/2/3/4/5/6/7/8/9", |
| tidyOnly: "", |
| expectedArgs: []string{"-j", "fake_module", "fake_module2", "-k"}, |
| }, { |
| description: "normal execution in deep directory, no targets", |
| dirsInTrees: []string{"0/1/2", "0/2", "0/3", "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6"}, |
| 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"}, |
| args: []string{"-j", "-k"}, |
| curDir: "1/2/3/4/5/6/7/8/9", |
| tidyOnly: "", |
| expectedArgs: []string{"-j", "-k"}, |
| }, { |
| description: "normal execution in root source tree, no args", |
| dirsInTrees: []string{"0/1/2", "0/2", "0/3"}, |
| buildFiles: []string{"0/1/2/Android.mk", "0/2/Android.bp"}, |
| args: []string{}, |
| curDir: "0/2", |
| tidyOnly: "", |
| expectedArgs: []string{}, |
| }, { |
| description: "normal execution in symlink root source tree, no args", |
| dirsInTrees: []string{"0/1/2", "0/2", "0/3"}, |
| buildFiles: []string{"0/1/2/Android.mk", "0/2/Android.bp"}, |
| rootSymlink: true, |
| args: []string{}, |
| curDir: "0/2", |
| tidyOnly: "", |
| expectedArgs: []string{}, |
| }} |
| for _, tt := range tests { |
| t.Run("build action BUILD_MODULES with dependencies, "+tt.description, func(t *testing.T) { |
| testGetConfigArgs(t, tt, BUILD_MODULES) |
| }) |
| } |
| } |
| |
| func TestGetConfigArgsBuildModulesInDirectory(t *testing.T) { |
| tests := []buildActionTestCase{ |
| { |
| description: "normal execution in a directory", |
| dirsInTrees: []string{"0/1/2"}, |
| buildFiles: []string{"0/1/2/Android.mk"}, |
| args: []string{"fake-module"}, |
| curDir: "0/1/2", |
| tidyOnly: "", |
| expectedArgs: []string{"fake-module", "MODULES-IN-0-1-2"}, |
| }, { |
| description: "build file in parent directory", |
| dirsInTrees: []string{"0/1/2"}, |
| buildFiles: []string{"0/1/Android.mk"}, |
| args: []string{}, |
| curDir: "0/1/2", |
| tidyOnly: "", |
| expectedArgs: []string{"MODULES-IN-0-1"}, |
| }, |
| { |
| description: "build file in parent directory, multiple module names passed in", |
| dirsInTrees: []string{"0/1/2"}, |
| buildFiles: []string{"0/1/Android.mk"}, |
| args: []string{"fake-module1", "fake-module2", "fake-module3"}, |
| curDir: "0/1/2", |
| tidyOnly: "", |
| expectedArgs: []string{"fake-module1", "fake-module2", "fake-module3", "MODULES-IN-0-1"}, |
| }, { |
| description: "build file in 2nd level parent directory", |
| dirsInTrees: []string{"0/1/2"}, |
| buildFiles: []string{"0/Android.bp"}, |
| args: []string{}, |
| curDir: "0/1/2", |
| tidyOnly: "", |
| expectedArgs: []string{"MODULES-IN-0"}, |
| }, { |
| description: "build action executed at root directory", |
| dirsInTrees: []string{}, |
| buildFiles: []string{}, |
| rootSymlink: false, |
| args: []string{}, |
| curDir: ".", |
| tidyOnly: "", |
| expectedArgs: []string{}, |
| }, { |
| description: "build action executed at root directory in symlink", |
| dirsInTrees: []string{}, |
| buildFiles: []string{}, |
| rootSymlink: true, |
| args: []string{}, |
| curDir: ".", |
| tidyOnly: "", |
| expectedArgs: []string{}, |
| }, { |
| description: "build file not found", |
| dirsInTrees: []string{"0/1/2"}, |
| buildFiles: []string{}, |
| args: []string{}, |
| curDir: "0/1/2", |
| tidyOnly: "", |
| expectedArgs: []string{"MODULES-IN-0-1-2"}, |
| expectedErrStr: "Build file not found for 0/1/2 directory", |
| }, { |
| description: "GET-INSTALL-PATH specified,", |
| dirsInTrees: []string{"0/1/2"}, |
| buildFiles: []string{"0/1/Android.mk"}, |
| args: []string{"GET-INSTALL-PATH", "-j", "-k", "GET-INSTALL-PATH"}, |
| curDir: "0/1/2", |
| tidyOnly: "", |
| expectedArgs: []string{"-j", "-k", "GET-INSTALL-PATH-IN-0-1"}, |
| }, { |
| description: "tidy only environment variable specified,", |
| dirsInTrees: []string{"0/1/2"}, |
| buildFiles: []string{"0/1/Android.mk"}, |
| args: []string{"GET-INSTALL-PATH"}, |
| curDir: "0/1/2", |
| tidyOnly: "true", |
| expectedArgs: []string{"tidy_only"}, |
| }, { |
| description: "normal execution in root directory with args", |
| dirsInTrees: []string{}, |
| buildFiles: []string{}, |
| args: []string{"-j", "-k", "fake_module"}, |
| curDir: "", |
| tidyOnly: "", |
| expectedArgs: []string{"-j", "-k", "fake_module"}, |
| }, |
| } |
| for _, tt := range tests { |
| t.Run("build action BUILD_MODULES_IN_DIR, "+tt.description, func(t *testing.T) { |
| testGetConfigArgs(t, tt, BUILD_MODULES_IN_A_DIRECTORY) |
| }) |
| } |
| } |
| |
| func TestGetConfigArgsBuildModulesInDirectories(t *testing.T) { |
| tests := []buildActionTestCase{{ |
| description: "normal execution in a directory", |
| dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, |
| buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, |
| args: []string{"3.1/", "3.2/", "3.3/"}, |
| curDir: "0/1/2", |
| tidyOnly: "", |
| expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-2-3.3"}, |
| }, { |
| description: "GET-INSTALL-PATH specified", |
| dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3"}, |
| buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/Android.bp"}, |
| args: []string{"GET-INSTALL-PATH", "2/3.1/", "2/3.2", "3"}, |
| curDir: "0/1", |
| tidyOnly: "", |
| 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"}, |
| }, { |
| description: "tidy only environment variable specified", |
| dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"}, |
| buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"}, |
| args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3"}, |
| curDir: "0/1/2", |
| tidyOnly: "1", |
| expectedArgs: []string{"tidy_only"}, |
| }, { |
| description: "normal execution from top dir directory", |
| dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"}, |
| 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"}, |
| rootSymlink: false, |
| args: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"}, |
| curDir: ".", |
| tidyOnly: "", |
| 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"}, |
| }, { |
| description: "normal execution from top dir directory in symlink", |
| dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"}, |
| 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"}, |
| rootSymlink: true, |
| args: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"}, |
| curDir: ".", |
| tidyOnly: "", |
| 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"}, |
| }} |
| for _, tt := range tests { |
| t.Run("build action BUILD_MODULES_IN_DIRS, "+tt.description, func(t *testing.T) { |
| testGetConfigArgs(t, tt, BUILD_MODULES_IN_DIRECTORIES) |
| }) |
| } |
| } |
| |
| func TestBuildConfig(t *testing.T) { |
| tests := []struct { |
| name string |
| environ Environment |
| arguments []string |
| expectedBuildConfig *smpb.BuildConfig |
| }{ |
| { |
| name: "none set", |
| environ: Environment{}, |
| expectedBuildConfig: &smpb.BuildConfig{ |
| ForceUseGoma: proto.Bool(false), |
| UseGoma: proto.Bool(false), |
| UseRbe: proto.Bool(false), |
| NinjaWeightListSource: smpb.BuildConfig_NOT_USED.Enum(), |
| }, |
| }, |
| { |
| name: "force use goma", |
| environ: Environment{"FORCE_USE_GOMA=1"}, |
| expectedBuildConfig: &smpb.BuildConfig{ |
| ForceUseGoma: proto.Bool(true), |
| UseGoma: proto.Bool(false), |
| UseRbe: proto.Bool(false), |
| NinjaWeightListSource: smpb.BuildConfig_NOT_USED.Enum(), |
| }, |
| }, |
| { |
| name: "use goma", |
| environ: Environment{"USE_GOMA=1"}, |
| expectedBuildConfig: &smpb.BuildConfig{ |
| ForceUseGoma: proto.Bool(false), |
| UseGoma: proto.Bool(true), |
| UseRbe: proto.Bool(false), |
| NinjaWeightListSource: smpb.BuildConfig_NOT_USED.Enum(), |
| }, |
| }, |
| { |
| name: "use rbe", |
| environ: Environment{"USE_RBE=1"}, |
| expectedBuildConfig: &smpb.BuildConfig{ |
| ForceUseGoma: proto.Bool(false), |
| UseGoma: proto.Bool(false), |
| UseRbe: proto.Bool(true), |
| NinjaWeightListSource: smpb.BuildConfig_NOT_USED.Enum(), |
| }, |
| }, |
| } |
| |
| for _, tc := range tests { |
| t.Run(tc.name, func(t *testing.T) { |
| c := &configImpl{ |
| environ: &tc.environ, |
| arguments: tc.arguments, |
| } |
| config := Config{c} |
| actualBuildConfig := buildConfig(config) |
| if expected := tc.expectedBuildConfig; !proto.Equal(expected, actualBuildConfig) { |
| t.Errorf("Build config mismatch.\n"+ |
| "Expected build config: %#v\n"+ |
| "Actual build config: %#v", prototext.Format(expected), prototext.Format(actualBuildConfig)) |
| } |
| }) |
| } |
| } |
| |
| func TestGetMetricsUploaderApp(t *testing.T) { |
| |
| metricsUploaderDir := "metrics_uploader_dir" |
| metricsUploaderBinary := "metrics_uploader_binary" |
| metricsUploaderPath := filepath.Join(metricsUploaderDir, metricsUploaderBinary) |
| tests := []struct { |
| description string |
| environ Environment |
| createFiles bool |
| expected string |
| }{{ |
| description: "Uploader binary exist", |
| environ: Environment{"METRICS_UPLOADER=" + metricsUploaderPath}, |
| createFiles: true, |
| expected: metricsUploaderPath, |
| }, { |
| description: "Uploader binary not exist", |
| environ: Environment{"METRICS_UPLOADER=" + metricsUploaderPath}, |
| createFiles: false, |
| expected: "", |
| }, { |
| description: "Uploader binary variable not set", |
| createFiles: true, |
| expected: "", |
| }} |
| |
| for _, tt := range tests { |
| t.Run(tt.description, func(t *testing.T) { |
| defer logger.Recover(func(err error) { |
| t.Fatalf("got unexpected error: %v", err) |
| }) |
| |
| // Create the root source tree. |
| topDir, err := ioutil.TempDir("", "") |
| if err != nil { |
| t.Fatalf("failed to create temp dir: %v", err) |
| } |
| defer os.RemoveAll(topDir) |
| |
| expected := tt.expected |
| if len(expected) > 0 { |
| expected = filepath.Join(topDir, expected) |
| } |
| |
| if tt.createFiles { |
| if err := os.MkdirAll(filepath.Join(topDir, metricsUploaderDir), 0755); err != nil { |
| t.Errorf("failed to create %s directory: %v", metricsUploaderDir, err) |
| } |
| if err := ioutil.WriteFile(filepath.Join(topDir, metricsUploaderPath), []byte{}, 0644); err != nil { |
| t.Errorf("failed to create file %s: %v", expected, err) |
| } |
| } |
| |
| actual := GetMetricsUploader(topDir, &tt.environ) |
| |
| if actual != expected { |
| t.Errorf("expecting: %s, actual: %s", expected, actual) |
| } |
| }) |
| } |
| } |