blob: 6439791c3e47bb977452cdb053202f98879ad4f0 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"context"
"io"
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
fintpb "go.fuchsia.dev/fuchsia/tools/integration/fint/proto"
"go.fuchsia.dev/fuchsia/tools/lib/osmisc"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
)
func TestParseArgsAndEnv(t *testing.T) {
testCases := []struct {
name string
args []string
env map[string]string
expected setArgs
expectErr bool
}{
{
name: "missing PRODUCT.BOARD",
args: []string{},
expectErr: true,
},
{
name: "valid PRODUCT.BOARD",
args: []string{"core.x64"},
expected: setArgs{
product: "core",
board: "x64",
},
},
{
name: "invalid PRODUCT.BOARD",
args: []string{"corex64"},
expectErr: true,
},
{
name: "multiple PRODUCT.BOARD args",
args: []string{"core.x64", "bringup.arm64"},
expectErr: true,
},
{
name: "verbose",
args: []string{"--verbose", "core.x64"},
expected: setArgs{
product: "core",
board: "x64",
verbose: true,
},
},
{
name: "universe packages",
args: []string{"core.x64", "--with", "u1,u2", "--with", "u3,u4"},
expected: setArgs{
product: "core",
board: "x64",
universePackages: []string{"u1", "u2", "u3", "u4"},
},
},
{
name: "base packages",
args: []string{"core.x64", "--with-base", "b1,b2", "--with-base", "b3,b4"},
expected: setArgs{
product: "core",
board: "x64",
basePackages: []string{"b1", "b2", "b3", "b4"},
},
},
{
name: "cache packages",
args: []string{"core.x64", "--with-cache", "c1,c2", "--with-cache", "c3,c4"},
expected: setArgs{
product: "core",
board: "x64",
cachePackages: []string{"c1", "c2", "c3", "c4"},
},
},
{
name: "host labels",
args: []string{"core.x64", "--with-host", "h1,h2", "--with-host", "h3,h4"},
expected: setArgs{
product: "core",
board: "x64",
hostLabels: []string{"h1", "h2", "h3", "h4"},
},
},
{
name: "variants",
args: []string{"core.x64", "--variant", "kasan,profile", "--variant", "ubsan"},
expected: setArgs{
product: "core",
board: "x64",
variants: []string{"kasan", "profile", "ubsan"},
},
},
{
name: "fuzz-with",
args: []string{"core.x64", "--fuzz-with", "asan,ubsan", "--fuzz-with", "kasan"},
expected: setArgs{
product: "core",
board: "x64",
fuzzSanitizers: []string{"asan", "ubsan", "kasan"},
},
},
{
name: "gn args",
args: []string{"core.x64", "--args", `foo=["bar", "baz"]`, "--args", "x=5"},
expected: setArgs{
product: "core",
board: "x64",
// --args values shouldn't be split at commas, since commas can
// be part of the args themselves.
gnArgs: []string{`foo=["bar", "baz"]`, "x=5"},
},
},
{
name: "release",
args: []string{"core.x64", "--release"},
expected: setArgs{
product: "core",
board: "x64",
isRelease: true,
},
},
{
name: "fint params path",
args: []string{"--fint-params-path", "foo.fint.textproto"},
expected: setArgs{
fintParamsPath: "foo.fint.textproto",
},
},
{
name: "rejects --goma and --ccache",
args: []string{"core.x64", "--goma", "--ccache"},
expectErr: true,
},
{
name: "rejects --goma and --no-goma",
args: []string{"core.x64", "--goma", "--no-goma"},
expectErr: true,
},
{
name: "rejects --ccache and --no-ccache",
args: []string{"core.x64", "--ccache", "--no-ccache"},
expectErr: true,
},
{
name: "honors top-level fx --dir flag",
args: []string{"core.x64"},
env: map[string]string{
// fx sets this env var if the top-level --dir flag is set.
buildDirEnvVar: "/usr/foo/fuchsia/out/foo",
},
expected: setArgs{
product: "core",
board: "x64",
buildDir: "/usr/foo/fuchsia/out/foo",
},
},
{
name: "rejects --dir and --auto-dir",
args: []string{"core.x64", "--auto-dir"},
env: map[string]string{
// fx sets this env var if the top-level --dir flag is set.
buildDirEnvVar: "/usr/foo/fuchsia/out/foo",
},
expectErr: true,
},
{
name: "auto dir",
args: []string{"bringup.arm64", "--auto-dir"},
expected: setArgs{
product: "bringup",
board: "arm64",
buildDir: "out/bringup.arm64",
},
},
{
name: "auto dir with variants",
args: []string{"core.x64", "--auto-dir", "--variant", "profile,kasan", "--variant", "ubsan"},
expected: setArgs{
product: "core",
board: "x64",
buildDir: "out/core.x64-profile-kasan-ubsan",
variants: []string{"profile", "kasan", "ubsan"},
},
},
{
name: "auto dir and release",
args: []string{"core.x64", "--auto-dir", "--variant", "foo", "--release"},
expected: setArgs{
product: "core",
board: "x64",
isRelease: true,
buildDir: "out/core.x64-foo-release",
variants: []string{"foo"},
},
},
{
name: "auto dir with complex variants",
args: []string{"core.x64", "--auto-dir", "--variant", "asan-fuzzer/foo"},
expectErr: true,
},
{
name: "simple boolean flags",
args: []string{"core.x64", "--netboot", "--cargo-toml-gen"},
expected: setArgs{
product: "core",
board: "x64",
netboot: true,
cargoTOMLGen: true,
},
},
{
name: "ide files",
args: []string{"core.x64", "--ide", "json,vs"},
expected: setArgs{
product: "core",
board: "x64",
ideFiles: []string{"json", "vs"},
},
},
{
name: "json ide scripts",
args: []string{"core.x64", "--json-ide-script", "//foo.py", "--json-ide-script", "//bar.py"},
expected: setArgs{
product: "core",
board: "x64",
jsonIDEScripts: []string{"//foo.py", "//bar.py"},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
checkoutDir := t.TempDir()
tc.expected.checkoutDir = checkoutDir
if tc.expected.buildDir == "" {
tc.expected.buildDir = defaultBuildDir
}
env := map[string]string{checkoutDirEnvVar: checkoutDir}
for k, v := range tc.env {
env[k] = v
}
cmd, err := parseArgsAndEnv(tc.args, env)
if err != nil {
if !tc.expectErr {
t.Fatalf("Parse args error: %s", err)
}
return
} else if tc.expectErr {
t.Fatalf("Expected parse args error but parsing succeeded")
}
opts := []cmp.Option{cmpopts.EquateEmpty(), cmp.AllowUnexported(setArgs{})}
if diff := cmp.Diff(&tc.expected, cmd, opts...); diff != "" {
t.Fatalf("Unexpected arg parse result (-want +got):\n%s", diff)
}
})
}
}
type fakeSubprocessRunner struct {
fail bool
}
func (r fakeSubprocessRunner) Run(ctx context.Context, cmd []string, stdout, stderr io.Writer) error {
return r.RunWithStdin(ctx, cmd, stdout, stderr, nil)
}
func (r fakeSubprocessRunner) RunWithStdin(context.Context, []string, io.Writer, io.Writer, io.Reader) error {
if r.fail {
return &exec.ExitError{}
}
return nil
}
func TestConstructStaticSpec(t *testing.T) {
testCases := []struct {
name string
args *setArgs
runner fakeSubprocessRunner
expected *fintpb.Static
}{
{
name: "basic",
args: &setArgs{
board: "arm64",
product: "bringup",
isRelease: true,
basePackages: []string{"base"},
cachePackages: []string{"cache"},
universePackages: []string{"universe"},
hostLabels: []string{"host"},
variants: []string{"variant"},
ideFiles: []string{"json"},
jsonIDEScripts: []string{"foo.py"},
gnArgs: []string{"args"},
noGoma: true,
},
expected: &fintpb.Static{
Board: "boards/arm64.gni",
Product: "products/bringup.gni",
Optimize: fintpb.Static_RELEASE,
BasePackages: []string{"base"},
CachePackages: []string{"cache"},
UniversePackages: []string{"universe"},
HostLabels: []string{"host"},
Variants: []string{"variant"},
IdeFiles: []string{"json"},
JsonIdeScripts: []string{"foo.py"},
GnArgs: []string{"args"},
UseGoma: false,
},
},
{
name: "ccache enabled",
args: &setArgs{
useCcache: true,
},
expected: &fintpb.Static{
GnArgs: []string{"use_ccache=true"},
UseGoma: false,
},
},
{
name: "goma enabled",
args: &setArgs{
useGoma: true,
},
expected: &fintpb.Static{
UseGoma: true,
},
},
{
name: "goma enabled by default",
args: &setArgs{},
expected: &fintpb.Static{
UseGoma: true,
},
},
{
name: "goma disabled if goma auth fails",
args: &setArgs{},
runner: fakeSubprocessRunner{fail: true},
expected: &fintpb.Static{
UseGoma: false,
},
},
{
name: "fuzzer variants",
args: &setArgs{
fuzzSanitizers: []string{"asan", "ubsan"},
},
expected: &fintpb.Static{
UseGoma: true,
Variants: append(fuzzerVariants("asan"), fuzzerVariants("ubsan")...),
},
},
{
name: "netboot",
args: &setArgs{
netboot: true,
},
expected: &fintpb.Static{
UseGoma: true,
GnArgs: []string{"enable_netboot=true"},
},
},
{
name: "cargo toml gen",
args: &setArgs{
basePackages: []string{"foo"},
cargoTOMLGen: true,
},
expected: &fintpb.Static{
UseGoma: true,
BasePackages: []string{"foo", "//build/rust:cargo_toml_gen"},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
if tc.args.board == "" {
tc.args.board = "x64"
}
if tc.args.product == "" {
tc.args.product = "core"
}
expected := &fintpb.Static{
Board: "boards/x64.gni",
Product: "products/core.gni",
Optimize: fintpb.Static_DEBUG,
GenerateCompdb: true,
CompdbTargets: []string{"default"},
ExportRustProject: true,
}
proto.Merge(expected, tc.expected)
checkoutDir := t.TempDir()
createFile(t, checkoutDir, expected.Board)
createFile(t, checkoutDir, expected.Product)
fx := fxRunner{sr: tc.runner, checkoutDir: checkoutDir}
got, err := constructStaticSpec(ctx, fx, checkoutDir, tc.args)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(expected, got, protocmp.Transform()); diff != "" {
t.Fatalf("Static spec from args is wrong (-want +got):\n%s", diff)
}
})
}
}
func TestFindGNIFile(t *testing.T) {
t.Run("finds file in root boards directory", func(t *testing.T) {
checkoutDir := t.TempDir()
path := filepath.Join("boards", "core.gni")
createFile(t, checkoutDir, path)
wantPath := "boards/core.gni"
gotPath, err := findGNIFile(checkoutDir, "boards", "core")
if err != nil {
t.Fatal(err)
}
if wantPath != gotPath {
t.Errorf("findGNIFile returned wrong path: want %s, got %s", wantPath, gotPath)
}
})
t.Run("checks vendor directories first", func(t *testing.T) {
checkoutDir := t.TempDir()
path := filepath.Join("vendor", "foo", "boards", "core.gni")
createFile(t, checkoutDir, path)
// Even if a matching file exists in the root "//boards" directory, we
// should prefer the file from the vendor directory.
createFile(t, checkoutDir, "boards", "core.gni")
gotPath, err := findGNIFile(checkoutDir, "boards", "core")
if err != nil {
t.Fatal(err)
}
if path != gotPath {
t.Errorf("findGNIFile returned wrong path: want %s, got %s", path, gotPath)
}
})
t.Run("returns an error if file doesn't exist", func(t *testing.T) {
checkoutDir := t.TempDir()
createFile(t, checkoutDir, "boards", "core.gni")
if path, err := findGNIFile(checkoutDir, "boards", "doesnotexist"); err == nil {
t.Fatalf("Expected findGNIFile to fail given a nonexistent board but got path: %s", path)
}
})
}
func createFile(t *testing.T, pathParts ...string) {
t.Helper()
path := filepath.Join(pathParts...)
f, err := osmisc.CreateFile(path)
if err != nil {
t.Fatal(err)
}
f.Close()
t.Cleanup(func() { os.Remove(path) })
}