mirror of
https://github.com/autc04/Retro68.git
synced 2024-09-08 20:55:01 +00:00
1300 lines
29 KiB
Go
1300 lines
29 KiB
Go
// Copyright 2009 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package filepath_test
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"internal/testenv"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type PathTest struct {
|
|
path, result string
|
|
}
|
|
|
|
var cleantests = []PathTest{
|
|
// Already clean
|
|
{"abc", "abc"},
|
|
{"abc/def", "abc/def"},
|
|
{"a/b/c", "a/b/c"},
|
|
{".", "."},
|
|
{"..", ".."},
|
|
{"../..", "../.."},
|
|
{"../../abc", "../../abc"},
|
|
{"/abc", "/abc"},
|
|
{"/", "/"},
|
|
|
|
// Empty is current dir
|
|
{"", "."},
|
|
|
|
// Remove trailing slash
|
|
{"abc/", "abc"},
|
|
{"abc/def/", "abc/def"},
|
|
{"a/b/c/", "a/b/c"},
|
|
{"./", "."},
|
|
{"../", ".."},
|
|
{"../../", "../.."},
|
|
{"/abc/", "/abc"},
|
|
|
|
// Remove doubled slash
|
|
{"abc//def//ghi", "abc/def/ghi"},
|
|
{"//abc", "/abc"},
|
|
{"///abc", "/abc"},
|
|
{"//abc//", "/abc"},
|
|
{"abc//", "abc"},
|
|
|
|
// Remove . elements
|
|
{"abc/./def", "abc/def"},
|
|
{"/./abc/def", "/abc/def"},
|
|
{"abc/.", "abc"},
|
|
|
|
// Remove .. elements
|
|
{"abc/def/ghi/../jkl", "abc/def/jkl"},
|
|
{"abc/def/../ghi/../jkl", "abc/jkl"},
|
|
{"abc/def/..", "abc"},
|
|
{"abc/def/../..", "."},
|
|
{"/abc/def/../..", "/"},
|
|
{"abc/def/../../..", ".."},
|
|
{"/abc/def/../../..", "/"},
|
|
{"abc/def/../../../ghi/jkl/../../../mno", "../../mno"},
|
|
{"/../abc", "/abc"},
|
|
|
|
// Combinations
|
|
{"abc/./../def", "def"},
|
|
{"abc//./../def", "def"},
|
|
{"abc/../../././../def", "../../def"},
|
|
}
|
|
|
|
var wincleantests = []PathTest{
|
|
{`c:`, `c:.`},
|
|
{`c:\`, `c:\`},
|
|
{`c:\abc`, `c:\abc`},
|
|
{`c:abc\..\..\.\.\..\def`, `c:..\..\def`},
|
|
{`c:\abc\def\..\..`, `c:\`},
|
|
{`c:\..\abc`, `c:\abc`},
|
|
{`c:..\abc`, `c:..\abc`},
|
|
{`\`, `\`},
|
|
{`/`, `\`},
|
|
{`\\i\..\c$`, `\c$`},
|
|
{`\\i\..\i\c$`, `\i\c$`},
|
|
{`\\i\..\I\c$`, `\I\c$`},
|
|
{`\\host\share\foo\..\bar`, `\\host\share\bar`},
|
|
{`//host/share/foo/../baz`, `\\host\share\baz`},
|
|
{`\\a\b\..\c`, `\\a\b\c`},
|
|
{`\\a\b`, `\\a\b`},
|
|
}
|
|
|
|
func TestClean(t *testing.T) {
|
|
tests := cleantests
|
|
if runtime.GOOS == "windows" {
|
|
for i := range tests {
|
|
tests[i].result = filepath.FromSlash(tests[i].result)
|
|
}
|
|
tests = append(tests, wincleantests...)
|
|
}
|
|
for _, test := range tests {
|
|
if s := filepath.Clean(test.path); s != test.result {
|
|
t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result)
|
|
}
|
|
if s := filepath.Clean(test.result); s != test.result {
|
|
t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
|
|
}
|
|
}
|
|
|
|
if testing.Short() {
|
|
t.Skip("skipping malloc count in short mode")
|
|
}
|
|
if runtime.GOMAXPROCS(0) > 1 {
|
|
t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
|
|
return
|
|
}
|
|
|
|
t.Log("Skipping AllocsPerRun for gccgo")
|
|
return
|
|
|
|
for _, test := range tests {
|
|
allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) })
|
|
if allocs > 0 {
|
|
t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
|
|
}
|
|
}
|
|
}
|
|
|
|
const sep = filepath.Separator
|
|
|
|
var slashtests = []PathTest{
|
|
{"", ""},
|
|
{"/", string(sep)},
|
|
{"/a/b", string([]byte{sep, 'a', sep, 'b'})},
|
|
{"a//b", string([]byte{'a', sep, sep, 'b'})},
|
|
}
|
|
|
|
func TestFromAndToSlash(t *testing.T) {
|
|
for _, test := range slashtests {
|
|
if s := filepath.FromSlash(test.path); s != test.result {
|
|
t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result)
|
|
}
|
|
if s := filepath.ToSlash(test.result); s != test.path {
|
|
t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path)
|
|
}
|
|
}
|
|
}
|
|
|
|
type SplitListTest struct {
|
|
list string
|
|
result []string
|
|
}
|
|
|
|
const lsep = filepath.ListSeparator
|
|
|
|
var splitlisttests = []SplitListTest{
|
|
{"", []string{}},
|
|
{string([]byte{'a', lsep, 'b'}), []string{"a", "b"}},
|
|
{string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}},
|
|
}
|
|
|
|
var winsplitlisttests = []SplitListTest{
|
|
// quoted
|
|
{`"a"`, []string{`a`}},
|
|
|
|
// semicolon
|
|
{`";"`, []string{`;`}},
|
|
{`"a;b"`, []string{`a;b`}},
|
|
{`";";`, []string{`;`, ``}},
|
|
{`;";"`, []string{``, `;`}},
|
|
|
|
// partially quoted
|
|
{`a";"b`, []string{`a;b`}},
|
|
{`a; ""b`, []string{`a`, ` b`}},
|
|
{`"a;b`, []string{`a;b`}},
|
|
{`""a;b`, []string{`a`, `b`}},
|
|
{`"""a;b`, []string{`a;b`}},
|
|
{`""""a;b`, []string{`a`, `b`}},
|
|
{`a";b`, []string{`a;b`}},
|
|
{`a;b";c`, []string{`a`, `b;c`}},
|
|
{`"a";b";c`, []string{`a`, `b;c`}},
|
|
}
|
|
|
|
func TestSplitList(t *testing.T) {
|
|
tests := splitlisttests
|
|
if runtime.GOOS == "windows" {
|
|
tests = append(tests, winsplitlisttests...)
|
|
}
|
|
for _, test := range tests {
|
|
if l := filepath.SplitList(test.list); !reflect.DeepEqual(l, test.result) {
|
|
t.Errorf("SplitList(%#q) = %#q, want %#q", test.list, l, test.result)
|
|
}
|
|
}
|
|
}
|
|
|
|
type SplitTest struct {
|
|
path, dir, file string
|
|
}
|
|
|
|
var unixsplittests = []SplitTest{
|
|
{"a/b", "a/", "b"},
|
|
{"a/b/", "a/b/", ""},
|
|
{"a/", "a/", ""},
|
|
{"a", "", "a"},
|
|
{"/", "/", ""},
|
|
}
|
|
|
|
var winsplittests = []SplitTest{
|
|
{`c:`, `c:`, ``},
|
|
{`c:/`, `c:/`, ``},
|
|
{`c:/foo`, `c:/`, `foo`},
|
|
{`c:/foo/bar`, `c:/foo/`, `bar`},
|
|
{`//host/share`, `//host/share`, ``},
|
|
{`//host/share/`, `//host/share/`, ``},
|
|
{`//host/share/foo`, `//host/share/`, `foo`},
|
|
{`\\host\share`, `\\host\share`, ``},
|
|
{`\\host\share\`, `\\host\share\`, ``},
|
|
{`\\host\share\foo`, `\\host\share\`, `foo`},
|
|
}
|
|
|
|
func TestSplit(t *testing.T) {
|
|
var splittests []SplitTest
|
|
splittests = unixsplittests
|
|
if runtime.GOOS == "windows" {
|
|
splittests = append(splittests, winsplittests...)
|
|
}
|
|
for _, test := range splittests {
|
|
if d, f := filepath.Split(test.path); d != test.dir || f != test.file {
|
|
t.Errorf("Split(%q) = %q, %q, want %q, %q", test.path, d, f, test.dir, test.file)
|
|
}
|
|
}
|
|
}
|
|
|
|
type JoinTest struct {
|
|
elem []string
|
|
path string
|
|
}
|
|
|
|
var jointests = []JoinTest{
|
|
// zero parameters
|
|
{[]string{}, ""},
|
|
|
|
// one parameter
|
|
{[]string{""}, ""},
|
|
{[]string{"/"}, "/"},
|
|
{[]string{"a"}, "a"},
|
|
|
|
// two parameters
|
|
{[]string{"a", "b"}, "a/b"},
|
|
{[]string{"a", ""}, "a"},
|
|
{[]string{"", "b"}, "b"},
|
|
{[]string{"/", "a"}, "/a"},
|
|
{[]string{"/", "a/b"}, "/a/b"},
|
|
{[]string{"/", ""}, "/"},
|
|
{[]string{"//", "a"}, "/a"},
|
|
{[]string{"/a", "b"}, "/a/b"},
|
|
{[]string{"a/", "b"}, "a/b"},
|
|
{[]string{"a/", ""}, "a"},
|
|
{[]string{"", ""}, ""},
|
|
|
|
// three parameters
|
|
{[]string{"/", "a", "b"}, "/a/b"},
|
|
}
|
|
|
|
var winjointests = []JoinTest{
|
|
{[]string{`directory`, `file`}, `directory\file`},
|
|
{[]string{`C:\Windows\`, `System32`}, `C:\Windows\System32`},
|
|
{[]string{`C:\Windows\`, ``}, `C:\Windows`},
|
|
{[]string{`C:\`, `Windows`}, `C:\Windows`},
|
|
{[]string{`C:`, `a`}, `C:a`},
|
|
{[]string{`C:`, `a\b`}, `C:a\b`},
|
|
{[]string{`C:`, `a`, `b`}, `C:a\b`},
|
|
{[]string{`C:.`, `a`}, `C:a`},
|
|
{[]string{`C:a`, `b`}, `C:a\b`},
|
|
{[]string{`C:a`, `b`, `d`}, `C:a\b\d`},
|
|
{[]string{`\\host\share`, `foo`}, `\\host\share\foo`},
|
|
{[]string{`\\host\share\foo`}, `\\host\share\foo`},
|
|
{[]string{`//host/share`, `foo/bar`}, `\\host\share\foo\bar`},
|
|
{[]string{`\`}, `\`},
|
|
{[]string{`\`, ``}, `\`},
|
|
{[]string{`\`, `a`}, `\a`},
|
|
{[]string{`\\`, `a`}, `\a`},
|
|
{[]string{`\`, `a`, `b`}, `\a\b`},
|
|
{[]string{`\\`, `a`, `b`}, `\a\b`},
|
|
{[]string{`\`, `\\a\b`, `c`}, `\a\b\c`},
|
|
{[]string{`\\a`, `b`, `c`}, `\a\b\c`},
|
|
{[]string{`\\a\`, `b`, `c`}, `\a\b\c`},
|
|
}
|
|
|
|
func TestJoin(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
jointests = append(jointests, winjointests...)
|
|
}
|
|
for _, test := range jointests {
|
|
expected := filepath.FromSlash(test.path)
|
|
if p := filepath.Join(test.elem...); p != expected {
|
|
t.Errorf("join(%q) = %q, want %q", test.elem, p, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
type ExtTest struct {
|
|
path, ext string
|
|
}
|
|
|
|
var exttests = []ExtTest{
|
|
{"path.go", ".go"},
|
|
{"path.pb.go", ".go"},
|
|
{"a.dir/b", ""},
|
|
{"a.dir/b.go", ".go"},
|
|
{"a.dir/", ""},
|
|
}
|
|
|
|
func TestExt(t *testing.T) {
|
|
for _, test := range exttests {
|
|
if x := filepath.Ext(test.path); x != test.ext {
|
|
t.Errorf("Ext(%q) = %q, want %q", test.path, x, test.ext)
|
|
}
|
|
}
|
|
}
|
|
|
|
type Node struct {
|
|
name string
|
|
entries []*Node // nil if the entry is a file
|
|
mark int
|
|
}
|
|
|
|
var tree = &Node{
|
|
"testdata",
|
|
[]*Node{
|
|
{"a", nil, 0},
|
|
{"b", []*Node{}, 0},
|
|
{"c", nil, 0},
|
|
{
|
|
"d",
|
|
[]*Node{
|
|
{"x", nil, 0},
|
|
{"y", []*Node{}, 0},
|
|
{
|
|
"z",
|
|
[]*Node{
|
|
{"u", nil, 0},
|
|
{"v", nil, 0},
|
|
},
|
|
0,
|
|
},
|
|
},
|
|
0,
|
|
},
|
|
},
|
|
0,
|
|
}
|
|
|
|
func walkTree(n *Node, path string, f func(path string, n *Node)) {
|
|
f(path, n)
|
|
for _, e := range n.entries {
|
|
walkTree(e, filepath.Join(path, e.name), f)
|
|
}
|
|
}
|
|
|
|
func makeTree(t *testing.T) {
|
|
walkTree(tree, tree.name, func(path string, n *Node) {
|
|
if n.entries == nil {
|
|
fd, err := os.Create(path)
|
|
if err != nil {
|
|
t.Errorf("makeTree: %v", err)
|
|
return
|
|
}
|
|
fd.Close()
|
|
} else {
|
|
os.Mkdir(path, 0770)
|
|
}
|
|
})
|
|
}
|
|
|
|
func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
|
|
|
|
func checkMarks(t *testing.T, report bool) {
|
|
walkTree(tree, tree.name, func(path string, n *Node) {
|
|
if n.mark != 1 && report {
|
|
t.Errorf("node %s mark = %d; expected 1", path, n.mark)
|
|
}
|
|
n.mark = 0
|
|
})
|
|
}
|
|
|
|
// Assumes that each node name is unique. Good enough for a test.
|
|
// If clear is true, any incoming error is cleared before return. The errors
|
|
// are always accumulated, though.
|
|
func mark(info os.FileInfo, err error, errors *[]error, clear bool) error {
|
|
name := info.Name()
|
|
walkTree(tree, tree.name, func(path string, n *Node) {
|
|
if n.name == name {
|
|
n.mark++
|
|
}
|
|
})
|
|
if err != nil {
|
|
*errors = append(*errors, err)
|
|
if clear {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func chtmpdir(t *testing.T) (restore func()) {
|
|
oldwd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("chtmpdir: %v", err)
|
|
}
|
|
d, err := ioutil.TempDir("", "test")
|
|
if err != nil {
|
|
t.Fatalf("chtmpdir: %v", err)
|
|
}
|
|
if err := os.Chdir(d); err != nil {
|
|
t.Fatalf("chtmpdir: %v", err)
|
|
}
|
|
return func() {
|
|
if err := os.Chdir(oldwd); err != nil {
|
|
t.Fatalf("chtmpdir: %v", err)
|
|
}
|
|
os.RemoveAll(d)
|
|
}
|
|
}
|
|
|
|
func TestWalk(t *testing.T) {
|
|
if runtime.GOOS == "darwin" {
|
|
switch runtime.GOARCH {
|
|
case "arm", "arm64":
|
|
restore := chtmpdir(t)
|
|
defer restore()
|
|
}
|
|
}
|
|
makeTree(t)
|
|
errors := make([]error, 0, 10)
|
|
clear := true
|
|
markFn := func(path string, info os.FileInfo, err error) error {
|
|
return mark(info, err, &errors, clear)
|
|
}
|
|
// Expect no errors.
|
|
err := filepath.Walk(tree.name, markFn)
|
|
if err != nil {
|
|
t.Fatalf("no error expected, found: %s", err)
|
|
}
|
|
if len(errors) != 0 {
|
|
t.Fatalf("unexpected errors: %s", errors)
|
|
}
|
|
checkMarks(t, true)
|
|
errors = errors[0:0]
|
|
|
|
// Test permission errors. Only possible if we're not root
|
|
// and only on some file systems (AFS, FAT). To avoid errors during
|
|
// all.bash on those file systems, skip during go test -short.
|
|
if os.Getuid() > 0 && !testing.Short() {
|
|
// introduce 2 errors: chmod top-level directories to 0
|
|
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
|
|
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
|
|
|
|
// 3) capture errors, expect two.
|
|
// mark respective subtrees manually
|
|
markTree(tree.entries[1])
|
|
markTree(tree.entries[3])
|
|
// correct double-marking of directory itself
|
|
tree.entries[1].mark--
|
|
tree.entries[3].mark--
|
|
err := filepath.Walk(tree.name, markFn)
|
|
if err != nil {
|
|
t.Fatalf("expected no error return from Walk, got %s", err)
|
|
}
|
|
if len(errors) != 2 {
|
|
t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
|
|
}
|
|
// the inaccessible subtrees were marked manually
|
|
checkMarks(t, true)
|
|
errors = errors[0:0]
|
|
|
|
// 4) capture errors, stop after first error.
|
|
// mark respective subtrees manually
|
|
markTree(tree.entries[1])
|
|
markTree(tree.entries[3])
|
|
// correct double-marking of directory itself
|
|
tree.entries[1].mark--
|
|
tree.entries[3].mark--
|
|
clear = false // error will stop processing
|
|
err = filepath.Walk(tree.name, markFn)
|
|
if err == nil {
|
|
t.Fatalf("expected error return from Walk")
|
|
}
|
|
if len(errors) != 1 {
|
|
t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
|
|
}
|
|
// the inaccessible subtrees were marked manually
|
|
checkMarks(t, false)
|
|
errors = errors[0:0]
|
|
|
|
// restore permissions
|
|
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
|
|
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
|
|
}
|
|
|
|
// cleanup
|
|
if err := os.RemoveAll(tree.name); err != nil {
|
|
t.Errorf("removeTree: %v", err)
|
|
}
|
|
}
|
|
|
|
func touch(t *testing.T, name string) {
|
|
f, err := os.Create(name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestWalkSkipDirOnFile(t *testing.T) {
|
|
td, err := ioutil.TempDir("", "walktest")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(td)
|
|
|
|
if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
touch(t, filepath.Join(td, "dir/foo1"))
|
|
touch(t, filepath.Join(td, "dir/foo2"))
|
|
|
|
sawFoo2 := false
|
|
walker := func(path string, info os.FileInfo, err error) error {
|
|
if strings.HasSuffix(path, "foo2") {
|
|
sawFoo2 = true
|
|
}
|
|
if strings.HasSuffix(path, "foo1") {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
|
|
err = filepath.Walk(td, walker)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if sawFoo2 {
|
|
t.Errorf("SkipDir on file foo1 did not block processing of foo2")
|
|
}
|
|
|
|
err = filepath.Walk(filepath.Join(td, "dir"), walker)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if sawFoo2 {
|
|
t.Errorf("SkipDir on file foo1 did not block processing of foo2")
|
|
}
|
|
}
|
|
|
|
func TestWalkFileError(t *testing.T) {
|
|
td, err := ioutil.TempDir("", "walktest")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(td)
|
|
|
|
touch(t, filepath.Join(td, "foo"))
|
|
touch(t, filepath.Join(td, "bar"))
|
|
dir := filepath.Join(td, "dir")
|
|
if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
touch(t, filepath.Join(dir, "baz"))
|
|
touch(t, filepath.Join(dir, "stat-error"))
|
|
defer func() {
|
|
*filepath.LstatP = os.Lstat
|
|
}()
|
|
statErr := errors.New("some stat error")
|
|
*filepath.LstatP = func(path string) (os.FileInfo, error) {
|
|
if strings.HasSuffix(path, "stat-error") {
|
|
return nil, statErr
|
|
}
|
|
return os.Lstat(path)
|
|
}
|
|
got := map[string]error{}
|
|
err = filepath.Walk(td, func(path string, fi os.FileInfo, err error) error {
|
|
rel, _ := filepath.Rel(td, path)
|
|
got[filepath.ToSlash(rel)] = err
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Errorf("Walk error: %v", err)
|
|
}
|
|
want := map[string]error{
|
|
".": nil,
|
|
"foo": nil,
|
|
"bar": nil,
|
|
"dir": nil,
|
|
"dir/baz": nil,
|
|
"dir/stat-error": statErr,
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("Walked %#v; want %#v", got, want)
|
|
}
|
|
}
|
|
|
|
var basetests = []PathTest{
|
|
{"", "."},
|
|
{".", "."},
|
|
{"/.", "."},
|
|
{"/", "/"},
|
|
{"////", "/"},
|
|
{"x/", "x"},
|
|
{"abc", "abc"},
|
|
{"abc/def", "def"},
|
|
{"a/b/.x", ".x"},
|
|
{"a/b/c.", "c."},
|
|
{"a/b/c.x", "c.x"},
|
|
}
|
|
|
|
var winbasetests = []PathTest{
|
|
{`c:\`, `\`},
|
|
{`c:.`, `.`},
|
|
{`c:\a\b`, `b`},
|
|
{`c:a\b`, `b`},
|
|
{`c:a\b\c`, `c`},
|
|
{`\\host\share\`, `\`},
|
|
{`\\host\share\a`, `a`},
|
|
{`\\host\share\a\b`, `b`},
|
|
}
|
|
|
|
func TestBase(t *testing.T) {
|
|
tests := basetests
|
|
if runtime.GOOS == "windows" {
|
|
// make unix tests work on windows
|
|
for i := range tests {
|
|
tests[i].result = filepath.Clean(tests[i].result)
|
|
}
|
|
// add windows specific tests
|
|
tests = append(tests, winbasetests...)
|
|
}
|
|
for _, test := range tests {
|
|
if s := filepath.Base(test.path); s != test.result {
|
|
t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result)
|
|
}
|
|
}
|
|
}
|
|
|
|
var dirtests = []PathTest{
|
|
{"", "."},
|
|
{".", "."},
|
|
{"/.", "/"},
|
|
{"/", "/"},
|
|
{"////", "/"},
|
|
{"/foo", "/"},
|
|
{"x/", "x"},
|
|
{"abc", "."},
|
|
{"abc/def", "abc"},
|
|
{"a/b/.x", "a/b"},
|
|
{"a/b/c.", "a/b"},
|
|
{"a/b/c.x", "a/b"},
|
|
}
|
|
|
|
var windirtests = []PathTest{
|
|
{`c:\`, `c:\`},
|
|
{`c:.`, `c:.`},
|
|
{`c:\a\b`, `c:\a`},
|
|
{`c:a\b`, `c:a`},
|
|
{`c:a\b\c`, `c:a\b`},
|
|
{`\\host\share`, `\\host\share`},
|
|
{`\\host\share\`, `\\host\share\`},
|
|
{`\\host\share\a`, `\\host\share\`},
|
|
{`\\host\share\a\b`, `\\host\share\a`},
|
|
}
|
|
|
|
func TestDir(t *testing.T) {
|
|
tests := dirtests
|
|
if runtime.GOOS == "windows" {
|
|
// make unix tests work on windows
|
|
for i := range tests {
|
|
tests[i].result = filepath.Clean(tests[i].result)
|
|
}
|
|
// add windows specific tests
|
|
tests = append(tests, windirtests...)
|
|
}
|
|
for _, test := range tests {
|
|
if s := filepath.Dir(test.path); s != test.result {
|
|
t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
|
|
}
|
|
}
|
|
}
|
|
|
|
type IsAbsTest struct {
|
|
path string
|
|
isAbs bool
|
|
}
|
|
|
|
var isabstests = []IsAbsTest{
|
|
{"", false},
|
|
{"/", true},
|
|
{"/usr/bin/gcc", true},
|
|
{"..", false},
|
|
{"/a/../bb", true},
|
|
{".", false},
|
|
{"./", false},
|
|
{"lala", false},
|
|
}
|
|
|
|
var winisabstests = []IsAbsTest{
|
|
{`C:\`, true},
|
|
{`c\`, false},
|
|
{`c::`, false},
|
|
{`c:`, false},
|
|
{`/`, false},
|
|
{`\`, false},
|
|
{`\Windows`, false},
|
|
{`c:a\b`, false},
|
|
{`c:\a\b`, true},
|
|
{`c:/a/b`, true},
|
|
{`\\host\share\foo`, true},
|
|
{`//host/share/foo/bar`, true},
|
|
}
|
|
|
|
func TestIsAbs(t *testing.T) {
|
|
var tests []IsAbsTest
|
|
if runtime.GOOS == "windows" {
|
|
tests = append(tests, winisabstests...)
|
|
// All non-windows tests should fail, because they have no volume letter.
|
|
for _, test := range isabstests {
|
|
tests = append(tests, IsAbsTest{test.path, false})
|
|
}
|
|
// All non-windows test should work as intended if prefixed with volume letter.
|
|
for _, test := range isabstests {
|
|
tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
|
|
}
|
|
} else {
|
|
tests = isabstests
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if r := filepath.IsAbs(test.path); r != test.isAbs {
|
|
t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
|
|
}
|
|
}
|
|
}
|
|
|
|
type EvalSymlinksTest struct {
|
|
// If dest is empty, the path is created; otherwise the dest is symlinked to the path.
|
|
path, dest string
|
|
}
|
|
|
|
var EvalSymlinksTestDirs = []EvalSymlinksTest{
|
|
{"test", ""},
|
|
{"test/dir", ""},
|
|
{"test/dir/link3", "../../"},
|
|
{"test/link1", "../test"},
|
|
{"test/link2", "dir"},
|
|
{"test/linkabs", "/"},
|
|
}
|
|
|
|
var EvalSymlinksTests = []EvalSymlinksTest{
|
|
{"test", "test"},
|
|
{"test/dir", "test/dir"},
|
|
{"test/dir/../..", "."},
|
|
{"test/link1", "test"},
|
|
{"test/link2", "test/dir"},
|
|
{"test/link1/dir", "test/dir"},
|
|
{"test/link2/..", "test"},
|
|
{"test/dir/link3", "."},
|
|
{"test/link2/link3/test", "test"},
|
|
{"test/linkabs", "/"},
|
|
}
|
|
|
|
// simpleJoin builds a file name from the directory and path.
|
|
// It does not use Join because we don't want ".." to be evaluated.
|
|
func simpleJoin(dir, path string) string {
|
|
return dir + string(filepath.Separator) + path
|
|
}
|
|
|
|
func testEvalSymlinks(t *testing.T, path, want string) {
|
|
have, err := filepath.EvalSymlinks(path)
|
|
if err != nil {
|
|
t.Errorf("EvalSymlinks(%q) error: %v", path, err)
|
|
return
|
|
}
|
|
if filepath.Clean(have) != filepath.Clean(want) {
|
|
t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want)
|
|
}
|
|
}
|
|
|
|
func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
err := os.Chdir(cwd)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}()
|
|
|
|
err = os.Chdir(wd)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
have, err := filepath.EvalSymlinks(path)
|
|
if err != nil {
|
|
t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err)
|
|
return
|
|
}
|
|
if filepath.Clean(have) != filepath.Clean(want) {
|
|
t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want)
|
|
}
|
|
}
|
|
|
|
func TestEvalSymlinks(t *testing.T) {
|
|
testenv.MustHaveSymlink(t)
|
|
|
|
tmpDir, err := ioutil.TempDir("", "evalsymlink")
|
|
if err != nil {
|
|
t.Fatal("creating temp dir:", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
// /tmp may itself be a symlink! Avoid the confusion, although
|
|
// it means trusting the thing we're testing.
|
|
tmpDir, err = filepath.EvalSymlinks(tmpDir)
|
|
if err != nil {
|
|
t.Fatal("eval symlink for tmp dir:", err)
|
|
}
|
|
|
|
// Create the symlink farm using relative paths.
|
|
for _, d := range EvalSymlinksTestDirs {
|
|
var err error
|
|
path := simpleJoin(tmpDir, d.path)
|
|
if d.dest == "" {
|
|
err = os.Mkdir(path, 0755)
|
|
} else {
|
|
err = os.Symlink(d.dest, path)
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Evaluate the symlink farm.
|
|
for _, test := range EvalSymlinksTests {
|
|
path := simpleJoin(tmpDir, test.path)
|
|
|
|
dest := simpleJoin(tmpDir, test.dest)
|
|
if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
|
|
dest = test.dest
|
|
}
|
|
testEvalSymlinks(t, path, dest)
|
|
|
|
// test EvalSymlinks(".")
|
|
testEvalSymlinksAfterChdir(t, path, ".", ".")
|
|
|
|
// test EvalSymlinks("C:.") on Windows
|
|
if runtime.GOOS == "windows" {
|
|
volDot := filepath.VolumeName(tmpDir) + "."
|
|
testEvalSymlinksAfterChdir(t, path, volDot, volDot)
|
|
}
|
|
|
|
// test EvalSymlinks(".."+path)
|
|
dotdotPath := simpleJoin("..", test.dest)
|
|
if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) {
|
|
dotdotPath = test.dest
|
|
}
|
|
testEvalSymlinksAfterChdir(t,
|
|
simpleJoin(tmpDir, "test"),
|
|
simpleJoin("..", test.path),
|
|
dotdotPath)
|
|
|
|
// test EvalSymlinks(p) where p is relative path
|
|
testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
|
|
}
|
|
}
|
|
|
|
func TestEvalSymlinksIsNotExist(t *testing.T) {
|
|
testenv.MustHaveSymlink(t)
|
|
|
|
defer chtmpdir(t)()
|
|
|
|
_, err := filepath.EvalSymlinks("notexist")
|
|
if !os.IsNotExist(err) {
|
|
t.Errorf("expected the file is not found, got %v\n", err)
|
|
}
|
|
|
|
err = os.Symlink("notexist", "link")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Remove("link")
|
|
|
|
_, err = filepath.EvalSymlinks("link")
|
|
if !os.IsNotExist(err) {
|
|
t.Errorf("expected the file is not found, got %v\n", err)
|
|
}
|
|
}
|
|
|
|
func TestIssue13582(t *testing.T) {
|
|
testenv.MustHaveSymlink(t)
|
|
|
|
tmpDir, err := ioutil.TempDir("", "issue13582")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
dir := filepath.Join(tmpDir, "dir")
|
|
err = os.Mkdir(dir, 0755)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
linkToDir := filepath.Join(tmpDir, "link_to_dir")
|
|
err = os.Symlink(dir, linkToDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
file := filepath.Join(linkToDir, "file")
|
|
err = ioutil.WriteFile(file, nil, 0644)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
link1 := filepath.Join(linkToDir, "link1")
|
|
err = os.Symlink(file, link1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
link2 := filepath.Join(linkToDir, "link2")
|
|
err = os.Symlink(link1, link2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// /tmp may itself be a symlink!
|
|
realTmpDir, err := filepath.EvalSymlinks(tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
realDir := filepath.Join(realTmpDir, "dir")
|
|
realFile := filepath.Join(realDir, "file")
|
|
|
|
tests := []struct {
|
|
path, want string
|
|
}{
|
|
{dir, realDir},
|
|
{linkToDir, realDir},
|
|
{file, realFile},
|
|
{link1, realFile},
|
|
{link2, realFile},
|
|
}
|
|
for i, test := range tests {
|
|
have, err := filepath.EvalSymlinks(test.path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if have != test.want {
|
|
t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test directories relative to temporary directory.
|
|
// The tests are run in absTestDirs[0].
|
|
var absTestDirs = []string{
|
|
"a",
|
|
"a/b",
|
|
"a/b/c",
|
|
}
|
|
|
|
// Test paths relative to temporary directory. $ expands to the directory.
|
|
// The tests are run in absTestDirs[0].
|
|
// We create absTestDirs first.
|
|
var absTests = []string{
|
|
".",
|
|
"b",
|
|
"b/",
|
|
"../a",
|
|
"../a/b",
|
|
"../a/b/./c/../../.././a",
|
|
"../a/b/./c/../../.././a/",
|
|
"$",
|
|
"$/.",
|
|
"$/a/../a/b",
|
|
"$/a/b/c/../../.././a",
|
|
"$/a/b/c/../../.././a/",
|
|
}
|
|
|
|
func TestAbs(t *testing.T) {
|
|
root, err := ioutil.TempDir("", "TestAbs")
|
|
if err != nil {
|
|
t.Fatal("TempDir failed: ", err)
|
|
}
|
|
defer os.RemoveAll(root)
|
|
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal("getwd failed: ", err)
|
|
}
|
|
err = os.Chdir(root)
|
|
if err != nil {
|
|
t.Fatal("chdir failed: ", err)
|
|
}
|
|
defer os.Chdir(wd)
|
|
|
|
for _, dir := range absTestDirs {
|
|
err = os.Mkdir(dir, 0777)
|
|
if err != nil {
|
|
t.Fatal("Mkdir failed: ", err)
|
|
}
|
|
}
|
|
|
|
if runtime.GOOS == "windows" {
|
|
vol := filepath.VolumeName(root)
|
|
var extra []string
|
|
for _, path := range absTests {
|
|
if strings.Contains(path, "$") {
|
|
continue
|
|
}
|
|
path = vol + path
|
|
extra = append(extra, path)
|
|
}
|
|
absTests = append(absTests, extra...)
|
|
}
|
|
|
|
err = os.Chdir(absTestDirs[0])
|
|
if err != nil {
|
|
t.Fatal("chdir failed: ", err)
|
|
}
|
|
|
|
for _, path := range absTests {
|
|
path = strings.Replace(path, "$", root, -1)
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
t.Errorf("%s: %s", path, err)
|
|
continue
|
|
}
|
|
|
|
abspath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
t.Errorf("Abs(%q) error: %v", path, err)
|
|
continue
|
|
}
|
|
absinfo, err := os.Stat(abspath)
|
|
if err != nil || !os.SameFile(absinfo, info) {
|
|
t.Errorf("Abs(%q)=%q, not the same file", path, abspath)
|
|
}
|
|
if !filepath.IsAbs(abspath) {
|
|
t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath)
|
|
}
|
|
if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
|
|
t.Errorf("Abs(%q)=%q, isn't clean", path, abspath)
|
|
}
|
|
}
|
|
}
|
|
|
|
type RelTests struct {
|
|
root, path, want string
|
|
}
|
|
|
|
var reltests = []RelTests{
|
|
{"a/b", "a/b", "."},
|
|
{"a/b/.", "a/b", "."},
|
|
{"a/b", "a/b/.", "."},
|
|
{"./a/b", "a/b", "."},
|
|
{"a/b", "./a/b", "."},
|
|
{"ab/cd", "ab/cde", "../cde"},
|
|
{"ab/cd", "ab/c", "../c"},
|
|
{"a/b", "a/b/c/d", "c/d"},
|
|
{"a/b", "a/b/../c", "../c"},
|
|
{"a/b/../c", "a/b", "../b"},
|
|
{"a/b/c", "a/c/d", "../../c/d"},
|
|
{"a/b", "c/d", "../../c/d"},
|
|
{"a/b/c/d", "a/b", "../.."},
|
|
{"a/b/c/d", "a/b/", "../.."},
|
|
{"a/b/c/d/", "a/b", "../.."},
|
|
{"a/b/c/d/", "a/b/", "../.."},
|
|
{"../../a/b", "../../a/b/c/d", "c/d"},
|
|
{"/a/b", "/a/b", "."},
|
|
{"/a/b/.", "/a/b", "."},
|
|
{"/a/b", "/a/b/.", "."},
|
|
{"/ab/cd", "/ab/cde", "../cde"},
|
|
{"/ab/cd", "/ab/c", "../c"},
|
|
{"/a/b", "/a/b/c/d", "c/d"},
|
|
{"/a/b", "/a/b/../c", "../c"},
|
|
{"/a/b/../c", "/a/b", "../b"},
|
|
{"/a/b/c", "/a/c/d", "../../c/d"},
|
|
{"/a/b", "/c/d", "../../c/d"},
|
|
{"/a/b/c/d", "/a/b", "../.."},
|
|
{"/a/b/c/d", "/a/b/", "../.."},
|
|
{"/a/b/c/d/", "/a/b", "../.."},
|
|
{"/a/b/c/d/", "/a/b/", "../.."},
|
|
{"/../../a/b", "/../../a/b/c/d", "c/d"},
|
|
{".", "a/b", "a/b"},
|
|
{".", "..", ".."},
|
|
|
|
// can't do purely lexically
|
|
{"..", ".", "err"},
|
|
{"..", "a", "err"},
|
|
{"../..", "..", "err"},
|
|
{"a", "/a", "err"},
|
|
{"/a", "a", "err"},
|
|
}
|
|
|
|
var winreltests = []RelTests{
|
|
{`C:a\b\c`, `C:a/b/d`, `..\d`},
|
|
{`C:\`, `D:\`, `err`},
|
|
{`C:`, `D:`, `err`},
|
|
{`C:\Projects`, `c:\projects\src`, `src`},
|
|
{`C:\Projects`, `c:\projects`, `.`},
|
|
{`C:\Projects\a\..`, `c:\projects`, `.`},
|
|
}
|
|
|
|
func TestRel(t *testing.T) {
|
|
tests := append([]RelTests{}, reltests...)
|
|
if runtime.GOOS == "windows" {
|
|
for i := range tests {
|
|
tests[i].want = filepath.FromSlash(tests[i].want)
|
|
}
|
|
tests = append(tests, winreltests...)
|
|
}
|
|
for _, test := range tests {
|
|
got, err := filepath.Rel(test.root, test.path)
|
|
if test.want == "err" {
|
|
if err == nil {
|
|
t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got)
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err)
|
|
}
|
|
if got != test.want {
|
|
t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
type VolumeNameTest struct {
|
|
path string
|
|
vol string
|
|
}
|
|
|
|
var volumenametests = []VolumeNameTest{
|
|
{`c:/foo/bar`, `c:`},
|
|
{`c:`, `c:`},
|
|
{`2:`, ``},
|
|
{``, ``},
|
|
{`\\\host`, ``},
|
|
{`\\\host\`, ``},
|
|
{`\\\host\share`, ``},
|
|
{`\\\host\\share`, ``},
|
|
{`\\host`, ``},
|
|
{`//host`, ``},
|
|
{`\\host\`, ``},
|
|
{`//host/`, ``},
|
|
{`\\host\share`, `\\host\share`},
|
|
{`//host/share`, `//host/share`},
|
|
{`\\host\share\`, `\\host\share`},
|
|
{`//host/share/`, `//host/share`},
|
|
{`\\host\share\foo`, `\\host\share`},
|
|
{`//host/share/foo`, `//host/share`},
|
|
{`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
|
|
{`//host/share//foo///bar////baz`, `//host/share`},
|
|
{`\\host\share\foo\..\bar`, `\\host\share`},
|
|
{`//host/share/foo/../bar`, `//host/share`},
|
|
}
|
|
|
|
func TestVolumeName(t *testing.T) {
|
|
if runtime.GOOS != "windows" {
|
|
return
|
|
}
|
|
for _, v := range volumenametests {
|
|
if vol := filepath.VolumeName(v.path); vol != v.vol {
|
|
t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDriveLetterInEvalSymlinks(t *testing.T) {
|
|
if runtime.GOOS != "windows" {
|
|
return
|
|
}
|
|
wd, _ := os.Getwd()
|
|
if len(wd) < 3 {
|
|
t.Errorf("Current directory path %q is too short", wd)
|
|
}
|
|
lp := strings.ToLower(wd)
|
|
up := strings.ToUpper(wd)
|
|
flp, err := filepath.EvalSymlinks(lp)
|
|
if err != nil {
|
|
t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err)
|
|
}
|
|
fup, err := filepath.EvalSymlinks(up)
|
|
if err != nil {
|
|
t.Fatalf("EvalSymlinks(%q) failed: %q", up, err)
|
|
}
|
|
if flp != fup {
|
|
t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup)
|
|
}
|
|
}
|
|
|
|
func TestBug3486(t *testing.T) { // https://golang.org/issue/3486
|
|
t.Skip("skipping test because gccgo sources are arranged differently.")
|
|
if runtime.GOOS == "darwin" {
|
|
switch runtime.GOARCH {
|
|
case "arm", "arm64":
|
|
t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
}
|
|
}
|
|
root, err := filepath.EvalSymlinks(runtime.GOROOT() + "/test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
bugs := filepath.Join(root, "fixedbugs")
|
|
ken := filepath.Join(root, "ken")
|
|
seenBugs := false
|
|
seenKen := false
|
|
err = filepath.Walk(root, func(pth string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
switch pth {
|
|
case bugs:
|
|
seenBugs = true
|
|
return filepath.SkipDir
|
|
case ken:
|
|
if !seenBugs {
|
|
t.Fatal("filepath.Walk out of order - ken before fixedbugs")
|
|
}
|
|
seenKen = true
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !seenKen {
|
|
t.Fatalf("%q not seen", ken)
|
|
}
|
|
}
|
|
|
|
func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
|
|
tmpdir, err := ioutil.TempDir("", "testWalkSymlink")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Chdir(wd)
|
|
|
|
err = os.Chdir(tmpdir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = mklink(tmpdir, "link")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var visited []string
|
|
err = filepath.Walk(tmpdir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rel, err := filepath.Rel(tmpdir, path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
visited = append(visited, rel)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sort.Strings(visited)
|
|
want := []string{".", "link"}
|
|
if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) {
|
|
t.Errorf("unexpected paths visited %q, want %q", visited, want)
|
|
}
|
|
}
|
|
|
|
func TestWalkSymlink(t *testing.T) {
|
|
testenv.MustHaveSymlink(t)
|
|
testWalkSymlink(t, os.Symlink)
|
|
}
|