ca53874b48
Replace Unix-only hub test fixtures with a cross-platform helper executable. Document Windows installation and usage, and ignore the generated .exe artifact while retaining Linux build guidance.
248 lines
6.6 KiB
Go
248 lines
6.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
if os.Getenv("STACK_SYNC_TEST_HELPER") == "hub" {
|
|
marker := os.Getenv("STACK_SYNC_TEST_MARKER")
|
|
if err := os.WriteFile(marker, []byte(strings.Join(os.Args[1:], " ")), 0o644); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
os.Exit(0)
|
|
}
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
func installFakeHub(t *testing.T, dir string) string {
|
|
t.Helper()
|
|
source, err := os.Executable()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
name := "hub"
|
|
if runtime.GOOS == "windows" {
|
|
name += ".exe"
|
|
}
|
|
destination := filepath.Join(dir, name)
|
|
if err := os.Link(source, destination); err == nil {
|
|
return destination
|
|
}
|
|
|
|
input, err := os.Open(source)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer input.Close()
|
|
output, err := os.OpenFile(destination, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o755)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := io.Copy(output, input); err != nil {
|
|
_ = output.Close()
|
|
t.Fatal(err)
|
|
}
|
|
if err := output.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return destination
|
|
}
|
|
|
|
func git(t *testing.T, dir string, args ...string) {
|
|
t.Helper()
|
|
cmd := exec.Command("git", append([]string{"-C", dir}, args...)...)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("git %v: %v\n%s", args, err, out)
|
|
}
|
|
}
|
|
|
|
func makeRepo(t *testing.T, path string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(path, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
git(t, path, "init", "-q")
|
|
git(t, path, "config", "user.name", "Test")
|
|
git(t, path, "config", "user.email", "test@example.com")
|
|
if err := os.WriteFile(filepath.Join(path, "tracked.txt"), []byte("clean\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
git(t, path, "add", "tracked.txt")
|
|
git(t, path, "commit", "-qm", "initial")
|
|
git(t, path, "remote", "add", "origin", "https://example.invalid/repo.git")
|
|
}
|
|
|
|
func TestDiscoverNestedRepositories(t *testing.T) {
|
|
root := t.TempDir()
|
|
parent := filepath.Join(root, "parent")
|
|
child := filepath.Join(parent, "apps", "child")
|
|
makeRepo(t, parent)
|
|
makeRepo(t, child)
|
|
|
|
paths, err := discover(root, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(paths) != 2 || paths[0] != parent || paths[1] != child {
|
|
t.Fatalf("discover() = %#v", paths)
|
|
}
|
|
}
|
|
|
|
func TestNestedRepoDoesNotDirtyParent(t *testing.T) {
|
|
root := t.TempDir()
|
|
parent := filepath.Join(root, "parent")
|
|
child := filepath.Join(parent, "child")
|
|
makeRepo(t, parent)
|
|
makeRepo(t, child)
|
|
|
|
paths, _ := discover(root, nil)
|
|
got := inspect(root, parent, paths)
|
|
if !got.Eligible {
|
|
t.Fatalf("parent blocked by nested repo: %+v", got)
|
|
}
|
|
if len(got.NestedRepoEntries) == 0 {
|
|
t.Fatalf("expected nested repository entry to be recorded: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestDirtyFileBlocksRepository(t *testing.T) {
|
|
root := t.TempDir()
|
|
path := filepath.Join(root, "repo")
|
|
makeRepo(t, path)
|
|
if err := os.WriteFile(filepath.Join(path, "tracked.txt"), []byte("changed\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got := inspect(root, path, []string{path})
|
|
if got.Eligible || len(got.Dirty) != 1 {
|
|
t.Fatalf("dirty repository was not blocked: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestUntrackedFileAlongsideNestedRepoStillBlocksParent(t *testing.T) {
|
|
root := t.TempDir()
|
|
parent := filepath.Join(root, "parent")
|
|
child := filepath.Join(parent, "child")
|
|
makeRepo(t, parent)
|
|
makeRepo(t, child)
|
|
if err := os.WriteFile(filepath.Join(parent, "notes.txt"), []byte("work\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
paths, _ := discover(root, nil)
|
|
got := inspect(root, parent, paths)
|
|
if got.Eligible || len(got.Dirty) != 1 {
|
|
t.Fatalf("untracked file should block parent: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestSkippedDirectoriesAreNotSearched(t *testing.T) {
|
|
root := t.TempDir()
|
|
makeRepo(t, filepath.Join(root, "node_modules", "dependency"))
|
|
makeRepo(t, filepath.Join(root, "real"))
|
|
paths, err := discover(root, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(paths) != 1 || paths[0] != filepath.Join(root, "real") {
|
|
t.Fatalf("discover() = %#v", paths)
|
|
}
|
|
}
|
|
|
|
func TestSyncRechecksAndSkipsNewlyDirtyRepository(t *testing.T) {
|
|
root := t.TempDir()
|
|
path := filepath.Join(root, "repo")
|
|
makeRepo(t, path)
|
|
planned := inspect(root, path, []string{path})
|
|
if !planned.Eligible {
|
|
t.Fatalf("fixture should initially be eligible: %+v", planned)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(path, "late-change.txt"), []byte("do not touch\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bin := filepath.Join(root, "bin")
|
|
if err := os.Mkdir(bin, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
marker := filepath.Join(root, "hub-was-called")
|
|
installFakeHub(t, bin)
|
|
t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH"))
|
|
t.Setenv("STACK_SYNC_TEST_HELPER", "hub")
|
|
t.Setenv("STACK_SYNC_TEST_MARKER", marker)
|
|
|
|
results := syncAll(root, []repo{planned}, 1, time.Minute, io.Discard, true)
|
|
if len(results) != 1 || !results[0].Skipped || results[0].Success {
|
|
t.Fatalf("syncAll() = %+v", results)
|
|
}
|
|
if _, err := os.Stat(marker); !os.IsNotExist(err) {
|
|
t.Fatalf("hub ran despite the newly dirty worktree")
|
|
}
|
|
}
|
|
|
|
func TestSyncInvokesHubForCleanRepository(t *testing.T) {
|
|
root := t.TempDir()
|
|
path := filepath.Join(root, "repo")
|
|
makeRepo(t, path)
|
|
planned := inspect(root, path, []string{path})
|
|
|
|
bin := filepath.Join(root, "bin")
|
|
if err := os.Mkdir(bin, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
marker := filepath.Join(root, "hub-was-called")
|
|
installFakeHub(t, bin)
|
|
t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH"))
|
|
t.Setenv("STACK_SYNC_TEST_HELPER", "hub")
|
|
t.Setenv("STACK_SYNC_TEST_MARKER", marker)
|
|
|
|
results := syncAll(root, []repo{planned}, 1, time.Minute, io.Discard, true)
|
|
if len(results) != 1 || !results[0].Success || results[0].Skipped {
|
|
t.Fatalf("syncAll() = %+v", results)
|
|
}
|
|
args, err := os.ReadFile(marker)
|
|
if err != nil {
|
|
t.Fatalf("hub was not called: %v", err)
|
|
}
|
|
if string(args) != "sync --color=never" {
|
|
t.Fatalf("hub arguments = %q", args)
|
|
}
|
|
}
|
|
|
|
func TestScanWithBlockedRepositoryIsAValidReport(t *testing.T) {
|
|
root := t.TempDir()
|
|
path := filepath.Join(root, "repo")
|
|
makeRepo(t, path)
|
|
if err := os.WriteFile(filepath.Join(path, "untracked.txt"), []byte("work\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
code := run([]string{"scan", "--root", root}, bytes.NewReader(nil), &stdout, &stderr)
|
|
if code != 0 {
|
|
t.Fatalf("run() code = %d, stderr = %q", code, stderr.String())
|
|
}
|
|
if !bytes.Contains(stdout.Bytes(), []byte("BLOCKED: dirty worktree")) {
|
|
t.Fatalf("scan did not report blocked repository:\n%s", stdout.String())
|
|
}
|
|
}
|
|
|
|
func TestIndentNormalizesGitProgressOutput(t *testing.T) {
|
|
got := indent("first\rsecond\r\nthird", " ")
|
|
want := " first\n second\n third"
|
|
if got != want {
|
|
t.Fatalf("indent() = %q, want %q", got, want)
|
|
}
|
|
}
|