Files
kjol/tools/tsgo/internal/nativepath/symlink_windows_test.go
2026-07-09 16:50:43 -04:00

186 lines
5.1 KiB
Go

package nativepath
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"gotest.tools/v3/assert"
)
func TestIsSymlinkOrReparsePoint(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
t.Run("regular file", func(t *testing.T) {
t.Parallel()
file := filepath.Join(tmp, "regular.txt")
assert.NilError(t, os.WriteFile(file, []byte("hello"), 0o666))
assert.Equal(t, IsSymlinkOrReparsePoint(file), false)
})
t.Run("regular directory", func(t *testing.T) {
t.Parallel()
dir := filepath.Join(tmp, "regular-dir")
assert.NilError(t, os.MkdirAll(dir, 0o777))
assert.Equal(t, IsSymlinkOrReparsePoint(dir), false)
})
t.Run("junction point", func(t *testing.T) {
t.Parallel()
target := filepath.Join(tmp, "junction-target")
link := filepath.Join(tmp, "junction-link")
assert.NilError(t, os.MkdirAll(target, 0o777))
mklink(t, target, link, true)
assert.Equal(t, IsSymlinkOrReparsePoint(link), true)
})
t.Run("file symlink", func(t *testing.T) {
t.Parallel()
target := filepath.Join(tmp, "symlink-target.txt")
link := filepath.Join(tmp, "symlink-link.txt")
assert.NilError(t, os.WriteFile(target, []byte("hello"), 0o666))
mklink(t, target, link, false)
assert.Equal(t, IsSymlinkOrReparsePoint(link), true)
})
t.Run("directory symlink", func(t *testing.T) {
t.Parallel()
target := filepath.Join(tmp, "dir-symlink-target")
link := filepath.Join(tmp, "dir-symlink-link")
assert.NilError(t, os.MkdirAll(target, 0o777))
mklink(t, target, link, false)
assert.Equal(t, IsSymlinkOrReparsePoint(link), true)
})
t.Run("nonexistent path", func(t *testing.T) {
t.Parallel()
nonexistent := filepath.Join(tmp, "does-not-exist")
assert.Equal(t, IsSymlinkOrReparsePoint(nonexistent), false)
})
t.Run("empty path", func(t *testing.T) {
t.Parallel()
assert.Equal(t, IsSymlinkOrReparsePoint(""), false)
})
t.Run("invalid path with null byte", func(t *testing.T) {
t.Parallel()
assert.Equal(t, IsSymlinkOrReparsePoint("invalid\x00path"), false)
})
}
func TestIsSymlinkOrReparsePointLongPath(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
// Create a deeply nested path that exceeds 248 characters
longPathBase := tmp
pathComponent := "very_long_directory_name_to_exceed_max_path_limit_abcdefghijklmnopqrstuvwxyz"
for len(longPathBase) < 250 {
longPathBase = filepath.Join(longPathBase, pathComponent)
}
target := filepath.Join(longPathBase, "target")
link := filepath.Join(longPathBase, "link")
// Use \\?\ prefix to enable long path support for mklink
longTarget := `\\?\` + target
longLink := `\\?\` + link
assert.NilError(t, os.MkdirAll(longTarget, 0o777))
assert.NilError(t, exec.Command("cmd", "/c", "mklink", "/J", longLink, longTarget).Run())
// With long path support enabled, this should work even for paths >= 248 chars
assert.Equal(t, IsSymlinkOrReparsePoint(link), true)
}
func TestIsSymlinkOrReparsePointNestedInSymlink(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
// Create a structure: target/inner-target, link -> target, then check link/inner-link
target := filepath.Join(tmp, "target")
innerTarget := filepath.Join(target, "inner-target")
assert.NilError(t, os.MkdirAll(innerTarget, 0o777))
link := filepath.Join(tmp, "link")
mklink(t, target, link, true)
// Create a junction inside the target
innerLink := filepath.Join(target, "inner-link")
mklink(t, innerTarget, innerLink, true)
// Check the junction through the symlink path
nestedPath := filepath.Join(link, "inner-link")
assert.Equal(t, IsSymlinkOrReparsePoint(nestedPath), true)
}
func TestIsSymlinkOrReparsePointRelativePath(t *testing.T) { //nolint:paralleltest // Cannot use t.Parallel() with t.Chdir()
tmp := t.TempDir()
t.Chdir(tmp)
target := "target-rel"
link := "link-rel"
assert.NilError(t, os.MkdirAll(target, 0o777))
mklink(t, target, link, true)
assert.Equal(t, IsSymlinkOrReparsePoint(link), true)
assert.Equal(t, IsSymlinkOrReparsePoint(target), false)
}
func BenchmarkIsSymlinkOrJunction(b *testing.B) {
tmp := b.TempDir()
regularFile := filepath.Join(tmp, "regular.txt")
assert.NilError(b, os.WriteFile(regularFile, []byte("hello"), 0o666))
target := filepath.Join(tmp, "target")
link := filepath.Join(tmp, "link")
assert.NilError(b, os.MkdirAll(target, 0o777))
assert.NilError(b, exec.Command("cmd", "/c", "mklink", "/J", link, target).Run())
b.Run("regular file", func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
IsSymlinkOrReparsePoint(regularFile)
}
})
b.Run("junction", func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
IsSymlinkOrReparsePoint(link)
}
})
b.Run("nonexistent", func(b *testing.B) {
b.ReportAllocs()
nonexistent := filepath.Join(tmp, "does-not-exist")
for b.Loop() {
IsSymlinkOrReparsePoint(nonexistent)
}
})
}
func mklink(tb testing.TB, target, link string, isDir bool) {
tb.Helper()
if isDir {
assert.NilError(tb, exec.Command("cmd", "/c", "mklink", "/J", link, target).Run())
} else {
err := os.Symlink(target, link)
if err != nil && strings.Contains(err.Error(), "A required privilege is not held by the client") {
tb.Log(err)
tb.Skip("file symlink support is not enabled without elevation or developer mode")
}
assert.NilError(tb, err)
}
}