Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Android/build/build/soong/ui/build/   (Android Betriebssystem Version 17©)  Datei vom 26.5.2026 mit Größe 34 kB image not shown  

Quelle  config_test.go   Sprache: unbekannt

 
Spracherkennung für: .go vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

// 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"
 "runtime"
 "strings"
 "testing"

 "android/soong/ui/logger"
 smpb "android/soong/ui/metrics/metrics_proto"
 "android/soong/ui/status"

 "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"}, -10, nil},
  {[]string{"-k0"}, -10, nil},
  {[]string{"-k1"}, -11, nil},
  {[]string{"-k1234"}, -11234, nil},

  {[]string{"-k", "0"}, -10, nil},
  {[]string{"-k", "1"}, -11, nil},
  {[]string{"-k", "1234"}, -11234, nil},
  {[]string{"-k", "1234", "abc"}, -11234, []string{"abc"}},
  {[]string{"-k", "abc"}, -10, []string{"abc"}},
  {[]string{"-k", "1abc"}, -10, []string{"1abc"}},

  // TODO: These are supported in Make, should we support them?
  //{[]string{"-kj"}, -10},
  //{[]string{"-kj8"}, 80},

  // -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 assertEquals[T ~bool | ~int32](t *testing.T, name string, expected, actual T) {
 if expected != actual {
  t.Errorf("Expected %s: %#v\nActual %s: %#v", name, expected, name, actual)
 }
}

func TestBuildConfig(t *testing.T) {
 tests := []struct {
  name                string
  environ             Environment
  arguments           []string
  expectedBuildConfig *smpb.BuildConfig
 }{
  {
   name:    "none set",
   environ: Environment{},
   expectedBuildConfig: &smpb.BuildConfig{
    UseRbe:                proto.Bool(false),
    NinjaWeightListSource: smpb.BuildConfig_NOT_USED.Enum(),
   },
  },
  {
   name:    "partial compile not used",
   environ: Environment{"SOONG_USE_PARTIAL_COMPILE=", "SOONG_PARTIAL_COMPILE=true"},
   expectedBuildConfig: &smpb.BuildConfig{
    UseRbe: proto.Bool(false),
    SoongEnvVars: &smpb.SoongEnvVars{
     PartialCompile:    proto.String("true"),
     UsePartialCompile: proto.String("false"),
    },
    NinjaWeightListSource: smpb.BuildConfig_NOT_USED.Enum(),
   },
  },
  {
   name:    "partial compile",
   environ: Environment{"SOONG_USE_PARTIAL_COMPILE=true", "SOONG_PARTIAL_COMPILE=true"},
   expectedBuildConfig: &smpb.BuildConfig{
    UseRbe: proto.Bool(false),
    SoongEnvVars: &smpb.SoongEnvVars{
     PartialCompile:    proto.String("true"),
     UsePartialCompile: proto.String("true"),
    },
    NinjaWeightListSource: smpb.BuildConfig_NOT_USED.Enum(),
   },
  },
  {
   // RBE is only supported on linux.
   name:    "use rbe",
   environ: Environment{"USE_RBE=1"},
   expectedBuildConfig: &smpb.BuildConfig{
    UseRbe:                proto.Bool(runtime.GOOS == "linux"),
    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}
   actual := buildConfig(config)
   assertEquals(t, "UseRbe", *tc.expectedBuildConfig.UseRbe, *actual.UseRbe)
   assertEquals(t, "NinjaWeightListSource", *tc.expectedBuildConfig.NinjaWeightListSource, *actual.NinjaWeightListSource)
  })
 }
}

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)
   }
  })
 }
}

[Dauer der Verarbeitung: 0.35 Sekunden, vorverarbeitet 2026-06-28]