blob: 0f9a6b4f9b2d97aa105d8d899e6b35c8a8b788cf [file] [log] [blame] [edit]
// Copyright 2024 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 integrationtests
import (
"bytes"
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/subcommands"
jirisubcommands "go.fuchsia.dev/jiri/cmd/jiri/subcommands"
"go.fuchsia.dev/jiri/cmdline"
"go.fuchsia.dev/jiri/envvar"
"go.fuchsia.dev/jiri/gitutil"
"go.fuchsia.dev/jiri/project"
)
// jiriInit returns a function that can be called to run jiri commands.
//
// For example:
//
// jiri := jiriInit(t, root)
// stdout := jiri("update", "-gc")
func jiriInit(t *testing.T, root string, initArgs ...string) func(args ...string) string {
t.Helper()
jiri := func(args ...string) string {
t.Helper()
var finalArgs []string
finalArgs = append(finalArgs, args...)
if len(args) > 0 {
var subcommand string
for _, arg := range args {
// Skip over global flags.
if !strings.HasPrefix(arg, "-") {
subcommand = args[0]
break
}
}
switch subcommand {
case "update", "run-hooks", "fetch-packages":
// Don't do retries with backoff. They make tests slower and
// aren't necessary since tests should be hermetic and
// deterministic.
finalArgs = append(finalArgs, "-attempts", "1")
}
}
var stdout, stderr bytes.Buffer
env := &cmdline.Env{
Stdout: &stdout,
Stderr: &stderr,
Vars: envvar.SliceToMap(os.Environ()),
}
args = append([]string{"--root", root}, args...)
commander, err := jirisubcommands.NewCommander(args)
if err != nil {
t.Fatal(err)
}
retcode := cmdline.Main(env, commander)
if retcode != subcommands.ExitSuccess {
t.Fatalf("%q failed:\n%s",
strings.Join(append([]string{"jiri"}, args...), " "),
string(stderr.Bytes()),
)
}
return string(stdout.Bytes())
}
initArgs = append([]string{"init", "-analytics-opt=false"}, initArgs...)
initArgs = append(initArgs, root)
jiri(initArgs...)
return jiri
}
func runSubprocess(t *testing.T, dir string, args ...string) string {
t.Helper()
cmd := exec.Command(args[0], args[1:]...)
cmd.Env = os.Environ()
gitConfig := map[string]string{
"init.defaultbranch": "main",
// Allow adding local git directories as submodules.
"protocol.file.allow": "always",
}
for k, v := range gitutil.GitConfigEnvVars(gitConfig) {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
}
cmd.Dir = dir
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
cmdline := strings.Join(args, " ")
msg := stderr.String()
if msg == "" && stdout.String() != "" {
msg = stdout.String()
}
t.Fatalf("%q failed: %s\n%s", cmdline, err, msg)
}
return string(stdout.Bytes())
}
func writeFile(t *testing.T, path string, contents string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
t.Fatal(err)
}
}
func readFile(t *testing.T, path string) string {
t.Helper()
b, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
return string(b)
}
func fileExists(t *testing.T, path string) bool {
t.Helper()
if _, err := os.Stat(path); err != nil {
if !errors.Is(err, os.ErrNotExist) {
t.Fatal(err)
}
return false
}
return true
}
func setupGitRepo(t *testing.T, dir string, files map[string]any) {
t.Helper()
runSubprocess(t, dir, "git", "init")
for path, contents := range files {
var s string
switch x := contents.(type) {
case []byte:
s = string(x)
case string:
s = x
case project.Manifest:
b, err := x.ToBytes()
if err != nil {
t.Fatal(err)
}
s = string(b)
default:
t.Fatalf("Invalid type for git repo file %s", path)
}
writeFile(t, filepath.Join(dir, path), s)
}
runSubprocess(t, dir, "git", "add", ".")
runSubprocess(t, dir, "git", "commit", "-m", "Initial commit")
}
func listDirRecursive(t *testing.T, dir string) []string {
t.Helper()
var files []string
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// Ignore hidden files.
if strings.HasPrefix(d.Name(), ".") {
if d.Type().IsDir() {
return fs.SkipDir
}
return nil
}
if d.Type().IsDir() {
return nil
}
rel, err := filepath.Rel(dir, path)
if err != nil {
return err
}
files = append(files, rel)
return nil
})
if err != nil {
t.Fatal(err)
}
slices.Sort(files)
return files
}
// checkDirContents asserts that the given `dir` contains the given `files`,
// including files in subdirectories.
func checkDirContents(t *testing.T, dir string, files []string) {
t.Helper()
got := listDirRecursive(t, dir)
if diff := cmp.Diff(files, got, cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" {
t.Errorf("Wrong directory contents (-want +got):\n%s", diff)
}
}
func currentRevision(t *testing.T, dir string) string {
t.Helper()
stdout := runSubprocess(t, dir, "git", "rev-parse", "HEAD")
return strings.TrimSpace(stdout)
}