Share link dependency evaluation with host access
This commit is contained in:
@@ -136,6 +136,129 @@ func TestNativeLinkResolutionRejectsEscapesAndSkipsWhenFollowingIsDisabled(t *te
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostLinkResolutionMemoizesCompletedDependencies(t *testing.T) {
|
||||
// The small graph stays within host symlink-depth limits while exceeding the
|
||||
// bounded linear operation allowance without completed-target reuse.
|
||||
const linkCount = 5
|
||||
root := t.TempDir()
|
||||
configurationRoot := filepath.Join(root, "workspace", "config")
|
||||
graph := filepath.Join(configurationRoot, "graph")
|
||||
selected := filepath.Join(configurationRoot, "selected")
|
||||
for _, directory := range []string{graph, selected} {
|
||||
if err := os.MkdirAll(directory, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
for index := 0; index < linkCount; index++ {
|
||||
name := fmt.Sprintf("s%02d", index)
|
||||
target := "."
|
||||
if index > 0 {
|
||||
previous := fmt.Sprintf("s%02d", index-1)
|
||||
target = previous + "/" + previous
|
||||
}
|
||||
if err := os.Symlink(target, filepath.Join(graph, name)); err != nil {
|
||||
t.Skipf("symlinks unavailable: %v", err)
|
||||
}
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(graph, "item.yml"), []byte("value: linked\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
aliasTarget := fmt.Sprintf("../graph/s%02d", linkCount-1)
|
||||
if err := os.Symlink(aliasTarget, filepath.Join(selected, "entry")); err != nil {
|
||||
t.Skipf("symlinks unavailable: %v", err)
|
||||
}
|
||||
containedAbsolute := filepath.Join(graph, "s00")
|
||||
if err := os.Symlink(containedAbsolute, filepath.Join(selected, "absolute")); err != nil {
|
||||
t.Skipf("absolute symlinks unavailable: %v", err)
|
||||
}
|
||||
outside := t.TempDir()
|
||||
if err := os.Symlink(outside, filepath.Join(selected, "escape")); err != nil {
|
||||
t.Skipf("symlinks unavailable: %v", err)
|
||||
}
|
||||
|
||||
access, err := openHostAccess(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer access.close()
|
||||
name := filepath.ToSlash(filepath.Join("workspace", "config", "selected", "entry"))
|
||||
operations := hostImportLinkOperations(access)
|
||||
var operationCount int
|
||||
lstat := operations.lstat
|
||||
readlink := operations.readlink
|
||||
operations.lstat = func(candidate string) (fs.FileInfo, error) {
|
||||
operationCount++
|
||||
if operationCount > linkCount*20+40 {
|
||||
return nil, errors.New("host link operation limit exceeded")
|
||||
}
|
||||
return lstat(candidate)
|
||||
}
|
||||
operations.readlink = func(candidate string) (string, error) {
|
||||
operationCount++
|
||||
if operationCount > linkCount*20+40 {
|
||||
return "", errors.New("host link operation limit exceeded")
|
||||
}
|
||||
return readlink(candidate)
|
||||
}
|
||||
resolved, err := resolveImportLinkPath(name, operations)
|
||||
if err != nil || resolved != filepath.ToSlash(filepath.Join("workspace", "config", "graph")) {
|
||||
t.Fatalf("host repeated-link resolution = %q, %v", resolved, err)
|
||||
}
|
||||
if operationCount == 0 || operationCount > linkCount*20+40 {
|
||||
t.Fatalf("host link operations = %d, want 1..%d", operationCount, linkCount*20+40)
|
||||
}
|
||||
|
||||
contained, err := resolveHostImportPath(access, filepath.Join("workspace", "config", "selected", "absolute"))
|
||||
if err != nil || contained != filepath.ToSlash(filepath.Join("workspace", "config", "graph")) {
|
||||
t.Fatalf("contained absolute link = %q, %v", contained, err)
|
||||
}
|
||||
if _, err := resolveHostImportPath(access, filepath.Join("workspace", "config", "selected", "escape")); !errors.Is(err, errImportPathOutsideRoot) {
|
||||
t.Fatalf("escaping absolute link error = %v, want outside-root rejection", err)
|
||||
}
|
||||
|
||||
for _, selector := range []string{"directory", "glob"} {
|
||||
t.Run(selector, func(t *testing.T) {
|
||||
var diagnostics []Diagnostic
|
||||
if selector == "directory" {
|
||||
_, diagnostics = selectDirectoryWithDiagnostics(t, access, root, " - dir: workspace/config/selected/entry\n follow: true\n")
|
||||
} else {
|
||||
_, diagnostics = selectGlobForTestWithDiagnostics(t, access, root, " - glob: 'workspace/config/selected/entry/*.yml'\n follow: true\n")
|
||||
}
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("host selector diagnostics = %#v", diagnostics)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostLinkResolutionKeepsNamespacesIndependent(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
target string
|
||||
}{
|
||||
{name: "left", target: "left-target"},
|
||||
{name: "right", target: "right-target"},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
if err := os.Mkdir(filepath.Join(root, test.target), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(test.target, filepath.Join(root, "alias")); err != nil {
|
||||
t.Skipf("symlinks unavailable: %v", err)
|
||||
}
|
||||
access, err := openHostAccess(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer access.close()
|
||||
if got, err := resolveHostImportPath(access, "alias"); err != nil || got != test.target {
|
||||
t.Fatalf("namespace link = %q, %v; want %q", got, err, test.target)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type countingReadLinkFS struct {
|
||||
filesystem fs.FS
|
||||
links fs.ReadLinkFS
|
||||
|
||||
Reference in New Issue
Block a user