blob: b1222fe936ec3fc96ef0c5ea6e468ddf5e3fe220 [file] [log] [blame]
// 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)
}
})
}
}