Fixes nested workspace detection and symlink path issues in workspace.Find() - Remove filepath.EvalSymlinks() for consistency with os.Getwd() - Add isInWorktreePath() to detect polecats/crew directories - Continue walking up to outermost workspace when in worktree paths - Add integration tests for symlink and nested workspace scenarios
281 lines
7.1 KiB
Go
281 lines
7.1 KiB
Go
package workspace
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func realPath(t *testing.T, path string) string {
|
|
t.Helper()
|
|
real, err := filepath.EvalSymlinks(path)
|
|
if err != nil {
|
|
t.Fatalf("realpath: %v", err)
|
|
}
|
|
return real
|
|
}
|
|
|
|
func TestFindWithPrimaryMarker(t *testing.T) {
|
|
// Create temp workspace structure
|
|
root := realPath(t, t.TempDir())
|
|
mayorDir := filepath.Join(root, "mayor")
|
|
if err := os.MkdirAll(mayorDir, 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
townFile := filepath.Join(mayorDir, "town.json")
|
|
if err := os.WriteFile(townFile, []byte(`{"type":"town"}`), 0644); err != nil {
|
|
t.Fatalf("write: %v", err)
|
|
}
|
|
|
|
// Create nested directory
|
|
nested := filepath.Join(root, "some", "deep", "path")
|
|
if err := os.MkdirAll(nested, 0755); err != nil {
|
|
t.Fatalf("mkdir nested: %v", err)
|
|
}
|
|
|
|
// Find from nested should return root
|
|
found, err := Find(nested)
|
|
if err != nil {
|
|
t.Fatalf("Find: %v", err)
|
|
}
|
|
if found != root {
|
|
t.Errorf("Find = %q, want %q", found, root)
|
|
}
|
|
}
|
|
|
|
func TestFindWithSecondaryMarker(t *testing.T) {
|
|
// Create temp workspace with just mayor/ directory
|
|
root := realPath(t, t.TempDir())
|
|
mayorDir := filepath.Join(root, "mayor")
|
|
if err := os.MkdirAll(mayorDir, 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
|
|
// Create nested directory
|
|
nested := filepath.Join(root, "rigs", "test")
|
|
if err := os.MkdirAll(nested, 0755); err != nil {
|
|
t.Fatalf("mkdir nested: %v", err)
|
|
}
|
|
|
|
// Find from nested should return root
|
|
found, err := Find(nested)
|
|
if err != nil {
|
|
t.Fatalf("Find: %v", err)
|
|
}
|
|
if found != root {
|
|
t.Errorf("Find = %q, want %q", found, root)
|
|
}
|
|
}
|
|
|
|
func TestFindNotFound(t *testing.T) {
|
|
// Create temp dir with no markers
|
|
dir := t.TempDir()
|
|
|
|
found, err := Find(dir)
|
|
if err != nil {
|
|
t.Fatalf("Find: %v", err)
|
|
}
|
|
if found != "" {
|
|
t.Errorf("Find = %q, want empty string", found)
|
|
}
|
|
}
|
|
|
|
func TestFindOrErrorNotFound(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
_, err := FindOrError(dir)
|
|
if err != ErrNotFound {
|
|
t.Errorf("FindOrError = %v, want ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestFindAtRoot(t *testing.T) {
|
|
// Create workspace at temp root level
|
|
root := realPath(t, t.TempDir())
|
|
mayorDir := filepath.Join(root, "mayor")
|
|
if err := os.MkdirAll(mayorDir, 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
townFile := filepath.Join(mayorDir, "town.json")
|
|
if err := os.WriteFile(townFile, []byte(`{"type":"town"}`), 0644); err != nil {
|
|
t.Fatalf("write: %v", err)
|
|
}
|
|
|
|
// Find from root should return root
|
|
found, err := Find(root)
|
|
if err != nil {
|
|
t.Fatalf("Find: %v", err)
|
|
}
|
|
if found != root {
|
|
t.Errorf("Find = %q, want %q", found, root)
|
|
}
|
|
}
|
|
|
|
func TestIsWorkspace(t *testing.T) {
|
|
root := t.TempDir()
|
|
|
|
// Not a workspace initially
|
|
is, err := IsWorkspace(root)
|
|
if err != nil {
|
|
t.Fatalf("IsWorkspace: %v", err)
|
|
}
|
|
if is {
|
|
t.Error("expected not a workspace initially")
|
|
}
|
|
|
|
// Add primary marker (mayor/town.json)
|
|
mayorDir := filepath.Join(root, "mayor")
|
|
if err := os.MkdirAll(mayorDir, 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
townFile := filepath.Join(mayorDir, "town.json")
|
|
if err := os.WriteFile(townFile, []byte(`{"type":"town"}`), 0644); err != nil {
|
|
t.Fatalf("write: %v", err)
|
|
}
|
|
|
|
// Now is a workspace
|
|
is, err = IsWorkspace(root)
|
|
if err != nil {
|
|
t.Fatalf("IsWorkspace: %v", err)
|
|
}
|
|
if !is {
|
|
t.Error("expected to be a workspace")
|
|
}
|
|
}
|
|
|
|
func TestFindFromSymlinkedDir(t *testing.T) {
|
|
root := realPath(t, t.TempDir())
|
|
mayorDir := filepath.Join(root, "mayor")
|
|
if err := os.MkdirAll(mayorDir, 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
townFile := filepath.Join(mayorDir, "town.json")
|
|
if err := os.WriteFile(townFile, []byte(`{"type":"town"}`), 0644); err != nil {
|
|
t.Fatalf("write: %v", err)
|
|
}
|
|
|
|
linkTarget := filepath.Join(root, "actual")
|
|
if err := os.MkdirAll(linkTarget, 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
|
|
linkName := filepath.Join(root, "linked")
|
|
if err := os.Symlink(linkTarget, linkName); err != nil {
|
|
t.Skipf("symlink not supported: %v", err)
|
|
}
|
|
|
|
found, err := Find(linkName)
|
|
if err != nil {
|
|
t.Fatalf("Find: %v", err)
|
|
}
|
|
if found != root {
|
|
t.Errorf("Find = %q, want %q", found, root)
|
|
}
|
|
}
|
|
|
|
func TestFindPreservesSymlinkPath(t *testing.T) {
|
|
realRoot := t.TempDir()
|
|
resolved, err := filepath.EvalSymlinks(realRoot)
|
|
if err != nil {
|
|
t.Fatalf("EvalSymlinks: %v", err)
|
|
}
|
|
|
|
symRoot := filepath.Join(t.TempDir(), "symlink-workspace")
|
|
if err := os.Symlink(resolved, symRoot); err != nil {
|
|
t.Skipf("symlink not supported: %v", err)
|
|
}
|
|
|
|
mayorDir := filepath.Join(symRoot, "mayor")
|
|
if err := os.MkdirAll(mayorDir, 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
townFile := filepath.Join(mayorDir, "town.json")
|
|
if err := os.WriteFile(townFile, []byte(`{}`), 0644); err != nil {
|
|
t.Fatalf("write: %v", err)
|
|
}
|
|
|
|
subdir := filepath.Join(symRoot, "rigs", "project", "polecats", "worker")
|
|
if err := os.MkdirAll(subdir, 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
|
|
townRoot, err := Find(subdir)
|
|
if err != nil {
|
|
t.Fatalf("Find: %v", err)
|
|
}
|
|
|
|
if townRoot != symRoot {
|
|
t.Errorf("Find returned %q, want %q (symlink path preserved)", townRoot, symRoot)
|
|
}
|
|
|
|
relPath, err := filepath.Rel(townRoot, subdir)
|
|
if err != nil {
|
|
t.Fatalf("Rel: %v", err)
|
|
}
|
|
|
|
if relPath != "rigs/project/polecats/worker" {
|
|
t.Errorf("Rel = %q, want 'rigs/project/polecats/worker'", relPath)
|
|
}
|
|
}
|
|
|
|
func TestFindSkipsNestedWorkspaceInWorktree(t *testing.T) {
|
|
root := realPath(t, t.TempDir())
|
|
|
|
if err := os.MkdirAll(filepath.Join(root, "mayor"), 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(root, "mayor", "town.json"), []byte(`{"name":"outer"}`), 0644); err != nil {
|
|
t.Fatalf("write: %v", err)
|
|
}
|
|
|
|
polecatDir := filepath.Join(root, "myrig", "polecats", "worker")
|
|
if err := os.MkdirAll(filepath.Join(polecatDir, "mayor"), 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(polecatDir, "mayor", "town.json"), []byte(`{"name":"inner"}`), 0644); err != nil {
|
|
t.Fatalf("write: %v", err)
|
|
}
|
|
|
|
found, err := Find(polecatDir)
|
|
if err != nil {
|
|
t.Fatalf("Find: %v", err)
|
|
}
|
|
|
|
if found != root {
|
|
t.Errorf("Find = %q, want %q (should skip nested workspace in polecats/)", found, root)
|
|
}
|
|
|
|
rel, _ := filepath.Rel(found, polecatDir)
|
|
if rel != "myrig/polecats/worker" {
|
|
t.Errorf("Rel = %q, want 'myrig/polecats/worker'", rel)
|
|
}
|
|
}
|
|
|
|
func TestFindSkipsNestedWorkspaceInCrew(t *testing.T) {
|
|
root := realPath(t, t.TempDir())
|
|
|
|
if err := os.MkdirAll(filepath.Join(root, "mayor"), 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(root, "mayor", "town.json"), []byte(`{"name":"outer"}`), 0644); err != nil {
|
|
t.Fatalf("write: %v", err)
|
|
}
|
|
|
|
crewDir := filepath.Join(root, "myrig", "crew", "worker")
|
|
if err := os.MkdirAll(filepath.Join(crewDir, "mayor"), 0755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(crewDir, "mayor", "town.json"), []byte(`{"name":"inner"}`), 0644); err != nil {
|
|
t.Fatalf("write: %v", err)
|
|
}
|
|
|
|
found, err := Find(crewDir)
|
|
if err != nil {
|
|
t.Fatalf("Find: %v", err)
|
|
}
|
|
|
|
if found != root {
|
|
t.Errorf("Find = %q, want %q (should skip nested workspace in crew/)", found, root)
|
|
}
|
|
}
|