605 lines
16 KiB
Go
605 lines
16 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// captureStderr redirects os.Stderr for the duration of fn and returns what
|
|
// was written. Used to verify the side-effecting print helpers without
|
|
// asserting against the real terminal.
|
|
func captureStderr(t *testing.T, fn func()) string {
|
|
t.Helper()
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
orig := os.Stderr
|
|
os.Stderr = w
|
|
defer func() { os.Stderr = orig }()
|
|
|
|
done := make(chan string, 1)
|
|
go func() {
|
|
var buf bytes.Buffer
|
|
_, _ = io.Copy(&buf, r)
|
|
done <- buf.String()
|
|
}()
|
|
|
|
fn()
|
|
w.Close()
|
|
return <-done
|
|
}
|
|
|
|
func TestUsageAndErrf(t *testing.T) {
|
|
out := captureStderr(t, func() { usage() })
|
|
if !strings.Contains(out, "gh repo-bootstrap <owner/repo>") {
|
|
t.Errorf("usage output missing header:\n%s", out)
|
|
}
|
|
|
|
out = captureStderr(t, func() { errf("hello %s %d", "world", 42) })
|
|
want := "gh-repo-bootstrap: hello world 42\n"
|
|
if out != want {
|
|
t.Errorf("errf = %q want %q", out, want)
|
|
}
|
|
}
|
|
|
|
func TestParseArgsHelp(t *testing.T) {
|
|
for _, flag := range []string{"-h", "--help"} {
|
|
_, err := parseArgs([]string{flag})
|
|
if !errors.Is(err, errHelp) {
|
|
t.Errorf("expected errHelp for %s, got %v", flag, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseArgsAllFlags(t *testing.T) {
|
|
args := []string{
|
|
"o/r",
|
|
"--branch", "trunk",
|
|
"--reviews", "0",
|
|
"--signed",
|
|
"--ruleset", "rs",
|
|
"--env", "production",
|
|
"--env", "staging",
|
|
"--bypass", "Team:7:pull_request",
|
|
"--upload-repo-secrets", "/tmp/repo.tfvars",
|
|
"--upload-env-secrets", "/tmp/envs",
|
|
"--state-dir", "/tmp/state",
|
|
"--destroy",
|
|
}
|
|
opts, err := parseArgs(args)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := &options{
|
|
repo: "o/r", owner: "o", name: "r",
|
|
branch: "trunk", reviews: 0, signed: true, ruleset: "rs",
|
|
envs: []string{"production", "staging"},
|
|
bypass: []bypassActor{{"Team", 7, "pull_request"}},
|
|
repoSecretsFile: "/tmp/repo.tfvars",
|
|
envSecretsDir: "/tmp/envs",
|
|
stateDir: "/tmp/state",
|
|
action: "destroy",
|
|
}
|
|
if !reflect.DeepEqual(opts, want) {
|
|
t.Errorf("got %+v\nwant %+v", opts, want)
|
|
}
|
|
}
|
|
|
|
func TestParseArgsDoubleDash(t *testing.T) {
|
|
// Positional repo after `--`.
|
|
opts, err := parseArgs([]string{"--", "o/r"})
|
|
if err != nil || opts.repo != "o/r" {
|
|
t.Errorf("got %+v err %v", opts, err)
|
|
}
|
|
// Two positionals after `--` is an error.
|
|
if _, err := parseArgs([]string{"--", "o/r", "extra"}); err == nil {
|
|
t.Errorf("expected error for two positionals after --")
|
|
}
|
|
}
|
|
|
|
func TestParseArgsMissingValue(t *testing.T) {
|
|
for _, args := range [][]string{
|
|
{"o/r", "--branch"},
|
|
{"o/r", "--reviews"},
|
|
{"o/r", "--ruleset"},
|
|
{"o/r", "--env"},
|
|
{"o/r", "--bypass"},
|
|
{"o/r", "--upload-repo-secrets"},
|
|
{"o/r", "--upload-env-secrets"},
|
|
{"o/r", "--state-dir"},
|
|
} {
|
|
if _, err := parseArgs(args); err == nil {
|
|
t.Errorf("expected missing-value error for %v", args)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseArgsEmptyOwnerName(t *testing.T) {
|
|
if _, err := parseArgs([]string{"/repo"}); err == nil {
|
|
t.Errorf("expected error for empty owner")
|
|
}
|
|
if _, err := parseArgs([]string{"owner/"}); err == nil {
|
|
t.Errorf("expected error for empty repo")
|
|
}
|
|
}
|
|
|
|
func TestDefaultStateDir(t *testing.T) {
|
|
t.Setenv("HOME", "/home/test")
|
|
|
|
if runtime.GOOS == "windows" {
|
|
t.Setenv("LOCALAPPDATA", `C:\Users\test\AppData\Local`)
|
|
got, err := defaultStateDir()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := filepath.Join(`C:\Users\test\AppData\Local`, "gh-repo-bootstrap")
|
|
if got != want {
|
|
t.Errorf("got %s want %s", got, want)
|
|
}
|
|
t.Setenv("LOCALAPPDATA", "")
|
|
got, err = defaultStateDir()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasSuffix(got, filepath.Join("AppData", "Local", "gh-repo-bootstrap")) {
|
|
t.Errorf("fallback path wrong: %s", got)
|
|
}
|
|
return
|
|
}
|
|
|
|
t.Setenv("XDG_STATE_HOME", "/xdg/state")
|
|
got, err := defaultStateDir()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != "/xdg/state/gh-repo-bootstrap" {
|
|
t.Errorf("XDG path wrong: %s", got)
|
|
}
|
|
|
|
t.Setenv("XDG_STATE_HOME", "")
|
|
got, err = defaultStateDir()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != "/home/test/.local/state/gh-repo-bootstrap" {
|
|
t.Errorf("home fallback wrong: %s", got)
|
|
}
|
|
}
|
|
|
|
func TestExtractModuleErrors(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("permission semantics differ on Windows")
|
|
}
|
|
// Target a path under a regular file: MkdirAll will fail.
|
|
tmp := t.TempDir()
|
|
blocker := filepath.Join(tmp, "block")
|
|
if err := os.WriteFile(blocker, []byte("x"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
target := filepath.Join(blocker, "child")
|
|
if err := extractModule(target); err == nil {
|
|
t.Errorf("expected mkdir error under regular file")
|
|
}
|
|
}
|
|
|
|
func TestMakeSecretsTmpDir(t *testing.T) {
|
|
dir, err := makeSecretsTmpDir()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
st, err := os.Stat(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !st.IsDir() {
|
|
t.Errorf("expected directory: %s", dir)
|
|
}
|
|
// The dir must be writable.
|
|
probe := filepath.Join(dir, "probe")
|
|
if err := os.WriteFile(probe, []byte("x"), 0o600); err != nil {
|
|
t.Errorf("tmpdir not writable: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEnsureGitHubTokenFromEnv(t *testing.T) {
|
|
t.Setenv("GITHUB_TOKEN", "from-env")
|
|
tok, err := ensureGitHubToken()
|
|
if err != nil || tok != "from-env" {
|
|
t.Errorf("env token: %q %v", tok, err)
|
|
}
|
|
}
|
|
|
|
func TestEnsureGitHubTokenFromFakeGh(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("posix shell required for fake gh")
|
|
}
|
|
bin := t.TempDir()
|
|
// Print token *with trailing whitespace* to verify TrimSpace.
|
|
gh := "#!/bin/sh\nprintf 'tok-from-gh \\n'\n"
|
|
if err := os.WriteFile(filepath.Join(bin, "gh"), []byte(gh), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv("PATH", bin)
|
|
t.Setenv("GITHUB_TOKEN", "")
|
|
tok, err := ensureGitHubToken()
|
|
if err != nil || tok != "tok-from-gh" {
|
|
t.Errorf("fake gh token: %q %v", tok, err)
|
|
}
|
|
}
|
|
|
|
func TestEnsureGitHubTokenEmptyFromGh(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("posix shell required for fake gh")
|
|
}
|
|
bin := t.TempDir()
|
|
gh := "#!/bin/sh\necho ''\n"
|
|
os.WriteFile(filepath.Join(bin, "gh"), []byte(gh), 0o755)
|
|
t.Setenv("PATH", bin)
|
|
t.Setenv("GITHUB_TOKEN", "")
|
|
if _, err := ensureGitHubToken(); err == nil || !strings.Contains(err.Error(), "empty token") {
|
|
t.Errorf("expected empty-token error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEnsureGitHubTokenGhMissing(t *testing.T) {
|
|
bin := t.TempDir() // no gh inside
|
|
t.Setenv("PATH", bin)
|
|
t.Setenv("GITHUB_TOKEN", "")
|
|
if _, err := ensureGitHubToken(); err == nil {
|
|
t.Errorf("expected error when gh is absent")
|
|
}
|
|
}
|
|
|
|
// fakeBin builds a tempdir containing fake gh and tofu shell scripts and
|
|
// returns the directory. The fake tofu logs each invocation's args to
|
|
// $tmpdir/tofu.log and exits with status from $tmpdir/tofu.exit (default 0).
|
|
func fakeBin(t *testing.T) string {
|
|
t.Helper()
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("posix shell required for fake binaries")
|
|
}
|
|
bin := t.TempDir()
|
|
gh := "#!/bin/sh\necho fake-token-from-gh\n"
|
|
tofu := `#!/bin/sh
|
|
echo "$@" >> "$BIN/tofu.log"
|
|
exit "$(cat "$BIN/tofu.exit" 2>/dev/null || echo 0)"
|
|
`
|
|
if err := os.WriteFile(filepath.Join(bin, "gh"), []byte(gh), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(bin, "tofu"), []byte(tofu), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv("BIN", bin)
|
|
// Tests that use this helper need /bin and /usr/bin on PATH so the fake
|
|
// tofu shell script can resolve `cat`. Tests that specifically want
|
|
// missing tools should set their own PATH.
|
|
t.Setenv("PATH", bin+":/usr/bin:/bin")
|
|
return bin
|
|
}
|
|
|
|
func TestRunTofuSuccessAndFailure(t *testing.T) {
|
|
bin := fakeBin(t)
|
|
|
|
wd := t.TempDir()
|
|
if err := runTofu(t.Context(), wd, "tok", "init", "-input=false"); err != nil {
|
|
t.Errorf("expected success: %v", err)
|
|
}
|
|
got, _ := os.ReadFile(filepath.Join(bin, "tofu.log"))
|
|
if !strings.Contains(string(got), "init -input=false") {
|
|
t.Errorf("tofu not invoked correctly: %s", got)
|
|
}
|
|
|
|
// Force a non-zero exit and verify error propagation.
|
|
os.WriteFile(filepath.Join(bin, "tofu.exit"), []byte("3\n"), 0o600)
|
|
if err := runTofu(t.Context(), wd, "tok", "plan"); err == nil {
|
|
t.Errorf("expected error from non-zero exit")
|
|
}
|
|
}
|
|
|
|
func TestRunTofuStartFailure(t *testing.T) {
|
|
// Empty PATH so exec.LookPath/Start fails inside runTofu.
|
|
t.Setenv("PATH", t.TempDir())
|
|
if err := runTofu(t.Context(), t.TempDir(), "tok"); err == nil {
|
|
t.Errorf("expected start error with no tofu on PATH")
|
|
}
|
|
}
|
|
|
|
func TestSignalContext(t *testing.T) {
|
|
ctx, cancel := signalContext()
|
|
defer cancel()
|
|
select {
|
|
case <-ctx.Done():
|
|
t.Errorf("ctx should not be done immediately")
|
|
default:
|
|
}
|
|
cancel()
|
|
<-ctx.Done() // should return promptly
|
|
}
|
|
|
|
func TestRunEndToEndPlan(t *testing.T) {
|
|
bin := fakeBin(t)
|
|
state := t.TempDir()
|
|
|
|
// Create a repo-secrets file so generateSecrets actually runs and the
|
|
// resulting -var-file gets passed to fake tofu.
|
|
repoSecrets := filepath.Join(t.TempDir(), "repo.tfvars")
|
|
os.WriteFile(repoSecrets, []byte("API = \"abc\"\n"), 0o600)
|
|
|
|
t.Setenv("GITHUB_TOKEN", "") // force gh fallback
|
|
|
|
oldArgs := os.Args
|
|
defer func() { os.Args = oldArgs }()
|
|
os.Args = []string{"gh-repo-bootstrap",
|
|
"owner/repo",
|
|
"--plan",
|
|
"--state-dir", state,
|
|
"--upload-repo-secrets", repoSecrets,
|
|
}
|
|
|
|
if err := run(); err != nil {
|
|
t.Fatalf("run() failed: %v", err)
|
|
}
|
|
|
|
// Expect main.tf and secrets.tf in state dir.
|
|
for _, want := range []string{"main.tf", "secrets.tf", filepath.Join(".module", "main.tf")} {
|
|
if _, err := os.Stat(filepath.Join(state, want)); err != nil {
|
|
t.Errorf("missing %s: %v", want, err)
|
|
}
|
|
}
|
|
// Verify tofu was called for init then plan, with -var-file on plan.
|
|
log, _ := os.ReadFile(filepath.Join(bin, "tofu.log"))
|
|
ls := string(log)
|
|
if !strings.Contains(ls, "init -input=false -upgrade") {
|
|
t.Errorf("init not logged: %s", ls)
|
|
}
|
|
if !strings.Contains(ls, "plan -input=false -var-file=") {
|
|
t.Errorf("plan with var-file not logged: %s", ls)
|
|
}
|
|
}
|
|
|
|
func TestRunEndToEndApplyAndDestroy(t *testing.T) {
|
|
bin := fakeBin(t)
|
|
t.Setenv("GITHUB_TOKEN", "preset")
|
|
|
|
oldArgs := os.Args
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
for _, action := range []string{"--destroy"} { // apply is the default
|
|
state := t.TempDir()
|
|
os.Args = []string{"gh-repo-bootstrap", "o/r", action, "--state-dir", state}
|
|
if err := run(); err != nil {
|
|
t.Fatalf("%s: run failed: %v", action, err)
|
|
}
|
|
}
|
|
|
|
// Default apply path.
|
|
state := t.TempDir()
|
|
os.Args = []string{"gh-repo-bootstrap", "o/r", "--state-dir", state}
|
|
if err := run(); err != nil {
|
|
t.Fatalf("apply: run failed: %v", err)
|
|
}
|
|
|
|
log, _ := os.ReadFile(filepath.Join(bin, "tofu.log"))
|
|
ls := string(log)
|
|
for _, want := range []string{
|
|
"destroy -input=false -auto-approve",
|
|
"apply -input=false -auto-approve",
|
|
} {
|
|
if !strings.Contains(ls, want) {
|
|
t.Errorf("missing %q in log:\n%s", want, ls)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRunBadArgs(t *testing.T) {
|
|
oldArgs := os.Args
|
|
defer func() { os.Args = oldArgs }()
|
|
os.Args = []string{"gh-repo-bootstrap", "no-slash"}
|
|
// Capture stderr so test output stays clean.
|
|
captureStderr(t, func() {
|
|
if err := run(); err == nil {
|
|
t.Errorf("expected error for bad args")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRunHelp(t *testing.T) {
|
|
oldArgs := os.Args
|
|
defer func() { os.Args = oldArgs }()
|
|
os.Args = []string{"gh-repo-bootstrap", "--help"}
|
|
err := run()
|
|
if !errors.Is(err, errHelp) {
|
|
t.Errorf("expected errHelp, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRunMissingTools(t *testing.T) {
|
|
// PATH with neither gh nor tofu.
|
|
t.Setenv("PATH", t.TempDir())
|
|
oldArgs := os.Args
|
|
defer func() { os.Args = oldArgs }()
|
|
os.Args = []string{"gh-repo-bootstrap", "o/r"}
|
|
captureStderr(t, func() {
|
|
if err := run(); err == nil {
|
|
t.Errorf("expected error when tofu/gh missing")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRunDefaultStateDir(t *testing.T) {
|
|
// Without --state-dir, run() resolves XDG_STATE_HOME and creates the dir.
|
|
bin := fakeBin(t)
|
|
xdg := t.TempDir()
|
|
t.Setenv("XDG_STATE_HOME", xdg)
|
|
t.Setenv("GITHUB_TOKEN", "preset")
|
|
|
|
oldArgs := os.Args
|
|
defer func() { os.Args = oldArgs }()
|
|
os.Args = []string{"gh-repo-bootstrap", "o/r", "--plan"}
|
|
if err := run(); err != nil {
|
|
t.Fatalf("run: %v", err)
|
|
}
|
|
want := filepath.Join(xdg, "gh-repo-bootstrap", "o__r", "main.tf")
|
|
if _, err := os.Stat(want); err != nil {
|
|
t.Errorf("expected default state dir to be used: %v", err)
|
|
}
|
|
_ = bin
|
|
}
|
|
|
|
func TestRunStateDirCreateFailure(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("permission semantics differ on Windows")
|
|
}
|
|
_ = fakeBin(t) // PATH gets gh+tofu
|
|
t.Setenv("GITHUB_TOKEN", "preset")
|
|
|
|
tmp := t.TempDir()
|
|
blocker := filepath.Join(tmp, "block")
|
|
os.WriteFile(blocker, []byte("x"), 0o600)
|
|
bad := filepath.Join(blocker, "child")
|
|
|
|
oldArgs := os.Args
|
|
defer func() { os.Args = oldArgs }()
|
|
os.Args = []string{"gh-repo-bootstrap", "o/r", "--plan", "--state-dir", bad}
|
|
if err := run(); err == nil {
|
|
t.Errorf("expected mkdir error")
|
|
}
|
|
}
|
|
|
|
func TestGenerateSecretsErrorPaths(t *testing.T) {
|
|
state := t.TempDir()
|
|
tmpd := t.TempDir()
|
|
|
|
// Env-secrets dir does not exist.
|
|
opts := &options{
|
|
envSecretsDir: filepath.Join(state, "nope"),
|
|
envs: []string{"production"},
|
|
}
|
|
if _, err := generateSecrets(state, tmpd, opts); err == nil {
|
|
t.Errorf("expected missing-dir error")
|
|
}
|
|
|
|
// Env-secrets dir is empty (no .tfvars files).
|
|
emptyDir := filepath.Join(state, "empty")
|
|
os.MkdirAll(emptyDir, 0o700)
|
|
opts.envSecretsDir = emptyDir
|
|
if _, err := generateSecrets(state, tmpd, opts); err == nil ||
|
|
!strings.Contains(err.Error(), "no *.tfvars") {
|
|
t.Errorf("expected empty-dir error, got %v", err)
|
|
}
|
|
|
|
// Env-secrets file has an invalid env-name in the basename.
|
|
weirdDir := filepath.Join(state, "weird")
|
|
os.MkdirAll(weirdDir, 0o700)
|
|
os.WriteFile(filepath.Join(weirdDir, "1bad.tfvars"), []byte("X = \"y\"\n"), 0o600)
|
|
opts.envSecretsDir = weirdDir
|
|
if _, err := generateSecrets(state, tmpd, opts); err == nil ||
|
|
!strings.Contains(err.Error(), "invalid env name") {
|
|
t.Errorf("expected invalid-env-name error, got %v", err)
|
|
}
|
|
|
|
// Env-secrets parse error propagates.
|
|
parseDir := filepath.Join(state, "parse")
|
|
os.MkdirAll(parseDir, 0o700)
|
|
os.WriteFile(filepath.Join(parseDir, "production.tfvars"),
|
|
[]byte("not a valid line\n"), 0o600)
|
|
opts.envSecretsDir = parseDir
|
|
if _, err := generateSecrets(state, tmpd, opts); err == nil {
|
|
t.Errorf("expected parse-error propagation")
|
|
}
|
|
|
|
// Repo-secrets file does not exist.
|
|
opts2 := &options{repoSecretsFile: filepath.Join(state, "nope.tfvars")}
|
|
if _, err := generateSecrets(state, tmpd, opts2); err == nil {
|
|
t.Errorf("expected missing repo-secrets error")
|
|
}
|
|
}
|
|
|
|
func TestParseSecretsFileUnreadable(t *testing.T) {
|
|
if runtime.GOOS == "windows" || os.Geteuid() == 0 {
|
|
t.Skip("chmod-based unreadable check not portable / root bypasses perms")
|
|
}
|
|
dir := t.TempDir()
|
|
p := filepath.Join(dir, "locked.tfvars")
|
|
os.WriteFile(p, []byte("X = \"y\"\n"), 0o600)
|
|
if err := os.Chmod(p, 0o000); err != nil {
|
|
t.Skip("cannot chmod 000")
|
|
}
|
|
defer os.Chmod(p, 0o600)
|
|
if _, err := parseSecretsFile(p); err == nil {
|
|
t.Errorf("expected unreadable error")
|
|
}
|
|
}
|
|
|
|
func TestParseBypassSpecTooManyParts(t *testing.T) {
|
|
if _, err := parseBypassSpec("Team:1:always:extra"); err == nil {
|
|
t.Errorf("expected error for 4-part spec")
|
|
}
|
|
if _, err := parseBypassSpec(":1:always"); err == nil {
|
|
t.Errorf("expected error for empty actor_type")
|
|
}
|
|
}
|
|
|
|
// TestMainEntryPoint exercises main() in a subprocess so we can observe its
|
|
// exit code / stderr handling without disturbing the test runner.
|
|
func TestMainEntryPoint(t *testing.T) {
|
|
if os.Getenv("GO_TEST_RUN_MAIN") == "1" {
|
|
// Strip the testing flags; user-supplied args follow "--".
|
|
for i, a := range os.Args {
|
|
if a == "--" {
|
|
os.Args = append([]string{os.Args[0]}, os.Args[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
main()
|
|
return
|
|
}
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("subprocess args plumbing differs on Windows; main is a 7-line wrapper")
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
args []string
|
|
wantExit int
|
|
wantErr string
|
|
}{
|
|
{"help", []string{"--help"}, 0, "gh repo-bootstrap"},
|
|
{"badArgs", []string{"no-slash"}, 1, "first argument must be"},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
cmd := exec.Command(os.Args[0], "-test.run=TestMainEntryPoint", "--")
|
|
cmd.Args = append(cmd.Args, c.args...)
|
|
cmd.Env = append(os.Environ(), "GO_TEST_RUN_MAIN=1")
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
cmd.Stdout = &stderr
|
|
err := cmd.Run()
|
|
rc := 0
|
|
if ee, ok := err.(*exec.ExitError); ok {
|
|
rc = ee.ExitCode()
|
|
} else if err != nil {
|
|
t.Fatalf("subprocess: %v", err)
|
|
}
|
|
if rc != c.wantExit {
|
|
t.Errorf("exit=%d want %d (stderr=%s)", rc, c.wantExit, stderr.String())
|
|
}
|
|
if !strings.Contains(stderr.String(), c.wantErr) {
|
|
t.Errorf("stderr missing %q", c.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|