pkg/path/testdata: adapt Go implementation
remove build tags and allow support for multiple OSes
Notes:
- evaluation is still fully hermetic
- many functions may default to Unix by default
- VolumeName defaults to Windows
- Resolve was added instead of Abs. The latter requires
a Getwd(), which wouldn't be hermetic. We could also
overload Join, and change its semantics, but this seems
a bit error-prone.
- In windows both \ and / are interpreted as slashes in
some contexts. It is therefore important to distinguish
between using Separator and IsSeparator.
- Note the super-cool tests. :)
Questions:
- Should VolumeName even take an OS argument?
Change-Id: I080bee599700a98e6019ca0666f72b4a59ba8da1
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7845
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/pkg/path/testdata/example_nix_test.go b/pkg/path/testdata/example_nix_test.go
index c9d6944..3c19da9 100644
--- a/pkg/path/testdata/example_nix_test.go
+++ b/pkg/path/testdata/example_nix_test.go
@@ -1,18 +1,31 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
// Copyright 2013 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.
-// +build !windows,!plan9
-
-package filepath_test
+package path_test
import (
"fmt"
- "path/filepath"
+
+ "cuelang.org/go/pkg/path"
)
func ExampleSplitList() {
- fmt.Println("On Unix:", filepath.SplitList("/a/b/c:/usr/bin"))
+ fmt.Println("On Unix:", path.SplitList("/a/b/c:/usr/bin", path.Unix))
// Output:
// On Unix: [/a/b/c /usr/bin]
}
@@ -27,7 +40,7 @@
fmt.Println("On Unix:")
for _, p := range paths {
- rel, err := filepath.Rel(base, p)
+ rel, err := path.Rel(base, p, path.Unix)
fmt.Printf("%q: %q %v\n", p, rel, err)
}
@@ -47,8 +60,8 @@
}
fmt.Println("On Unix:")
for _, p := range paths {
- dir, file := filepath.Split(p)
- fmt.Printf("input: %q\n\tdir: %q\n\tfile: %q\n", p, dir, file)
+ pair := path.Split(p, path.Unix)
+ fmt.Printf("input: %q\n\tdir: %q\n\tfile: %q\n", p, pair[0], pair[1])
}
// Output:
// On Unix:
@@ -68,12 +81,12 @@
func ExampleJoin() {
fmt.Println("On Unix:")
- fmt.Println(filepath.Join("a", "b", "c"))
- fmt.Println(filepath.Join("a", "b/c"))
- fmt.Println(filepath.Join("a/b", "c"))
- fmt.Println(filepath.Join("a/b", "/c"))
+ fmt.Println(path.Join([]string{"a", "b", "c"}, path.Unix))
+ fmt.Println(path.Join([]string{"a", "b/c"}, path.Unix))
+ fmt.Println(path.Join([]string{"a/b", "c"}, path.Unix))
+ fmt.Println(path.Join([]string{"a/b", "/c"}, path.Unix))
- fmt.Println(filepath.Join("a/b", "../../../xyz"))
+ fmt.Println(path.Join([]string{"a/b", "../../../xyz"}, path.Unix))
// Output:
// On Unix:
@@ -86,10 +99,10 @@
func ExampleMatch() {
fmt.Println("On Unix:")
- fmt.Println(filepath.Match("/home/catch/*", "/home/catch/foo"))
- fmt.Println(filepath.Match("/home/catch/*", "/home/catch/foo/bar"))
- fmt.Println(filepath.Match("/home/?opher", "/home/gopher"))
- fmt.Println(filepath.Match("/home/\\*", "/home/*"))
+ fmt.Println(path.Match("/home/catch/*", "/home/catch/foo", path.Unix))
+ fmt.Println(path.Match("/home/catch/*", "/home/catch/foo/bar", path.Unix))
+ fmt.Println(path.Match("/home/?opher", "/home/gopher", path.Unix))
+ fmt.Println(path.Match("/home/\\*", "/home/*", path.Unix))
// Output:
// On Unix:
@@ -101,15 +114,15 @@
func ExampleBase() {
fmt.Println("On Unix:")
- fmt.Println(filepath.Base("/foo/bar/baz.js"))
- fmt.Println(filepath.Base("/foo/bar/baz"))
- fmt.Println(filepath.Base("/foo/bar/baz/"))
- fmt.Println(filepath.Base("dev.txt"))
- fmt.Println(filepath.Base("../todo.txt"))
- fmt.Println(filepath.Base(".."))
- fmt.Println(filepath.Base("."))
- fmt.Println(filepath.Base("/"))
- fmt.Println(filepath.Base(""))
+ fmt.Println(path.Base("/foo/bar/baz.js", path.Unix))
+ fmt.Println(path.Base("/foo/bar/baz", path.Unix))
+ fmt.Println(path.Base("/foo/bar/baz/", path.Unix))
+ fmt.Println(path.Base("dev.txt", path.Unix))
+ fmt.Println(path.Base("../todo.txt", path.Unix))
+ fmt.Println(path.Base("..", path.Unix))
+ fmt.Println(path.Base(".", path.Unix))
+ fmt.Println(path.Base("/", path.Unix))
+ fmt.Println(path.Base("", path.Unix))
// Output:
// On Unix:
@@ -126,16 +139,16 @@
func ExampleDir() {
fmt.Println("On Unix:")
- fmt.Println(filepath.Dir("/foo/bar/baz.js"))
- fmt.Println(filepath.Dir("/foo/bar/baz"))
- fmt.Println(filepath.Dir("/foo/bar/baz/"))
- fmt.Println(filepath.Dir("/dirty//path///"))
- fmt.Println(filepath.Dir("dev.txt"))
- fmt.Println(filepath.Dir("../todo.txt"))
- fmt.Println(filepath.Dir(".."))
- fmt.Println(filepath.Dir("."))
- fmt.Println(filepath.Dir("/"))
- fmt.Println(filepath.Dir(""))
+ fmt.Println(path.Dir("/foo/bar/baz.js", path.Unix))
+ fmt.Println(path.Dir("/foo/bar/baz", path.Unix))
+ fmt.Println(path.Dir("/foo/bar/baz/", path.Unix))
+ fmt.Println(path.Dir("/dirty//path///", path.Unix))
+ fmt.Println(path.Dir("dev.txt", path.Unix))
+ fmt.Println(path.Dir("../todo.txt", path.Unix))
+ fmt.Println(path.Dir("..", path.Unix))
+ fmt.Println(path.Dir(".", path.Unix))
+ fmt.Println(path.Dir("/", path.Unix))
+ fmt.Println(path.Dir("", path.Unix))
// Output:
// On Unix:
@@ -153,12 +166,12 @@
func ExampleIsAbs() {
fmt.Println("On Unix:")
- fmt.Println(filepath.IsAbs("/home/gopher"))
- fmt.Println(filepath.IsAbs(".bashrc"))
- fmt.Println(filepath.IsAbs(".."))
- fmt.Println(filepath.IsAbs("."))
- fmt.Println(filepath.IsAbs("/"))
- fmt.Println(filepath.IsAbs(""))
+ fmt.Println(path.IsAbs("/home/gopher", path.Unix))
+ fmt.Println(path.IsAbs(".bashrc", path.Unix))
+ fmt.Println(path.IsAbs("..", path.Unix))
+ fmt.Println(path.IsAbs(".", path.Unix))
+ fmt.Println(path.IsAbs("/", path.Unix))
+ fmt.Println(path.IsAbs("", path.Unix))
// Output:
// On Unix:
diff --git a/pkg/path/testdata/example_test.go b/pkg/path/testdata/example_test.go
index a1d680e..726de43 100644
--- a/pkg/path/testdata/example_test.go
+++ b/pkg/path/testdata/example_test.go
@@ -1,18 +1,33 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
// Copyright 2017 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
+package path_test
import (
"fmt"
- "path/filepath"
+
+ "cuelang.org/go/pkg/path"
)
func ExampleExt() {
- fmt.Printf("No dots: %q\n", filepath.Ext("index"))
- fmt.Printf("One dot: %q\n", filepath.Ext("index.js"))
- fmt.Printf("Two dots: %q\n", filepath.Ext("main.test.js"))
+ fmt.Printf("No dots: %q\n", path.Ext("index", "unix"))
+ fmt.Printf("One dot: %q\n", path.Ext("index.js", "unix"))
+ fmt.Printf("Two dots: %q\n", path.Ext("main.test.js", "unix"))
// Output:
// No dots: ""
// One dot: ".js"
diff --git a/pkg/path/testdata/example_unix_walk_test.go b/pkg/path/testdata/example_unix_walk_test.go
deleted file mode 100644
index 66dc7f6..0000000
--- a/pkg/path/testdata/example_unix_walk_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2018 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.
-
-// +build !windows,!plan9
-
-package filepath_test
-
-import (
- "fmt"
- "io/fs"
- "io/ioutil"
- "os"
- "path/filepath"
-)
-
-func prepareTestDirTree(tree string) (string, error) {
- tmpDir, err := ioutil.TempDir("", "")
- if err != nil {
- return "", fmt.Errorf("error creating temp directory: %v\n", err)
- }
-
- err = os.MkdirAll(filepath.Join(tmpDir, tree), 0755)
- if err != nil {
- os.RemoveAll(tmpDir)
- return "", err
- }
-
- return tmpDir, nil
-}
-
-func ExampleWalk() {
- tmpDir, err := prepareTestDirTree("dir/to/walk/skip")
- if err != nil {
- fmt.Printf("unable to create test dir tree: %v\n", err)
- return
- }
- defer os.RemoveAll(tmpDir)
- os.Chdir(tmpDir)
-
- subDirToSkip := "skip"
-
- fmt.Println("On Unix:")
- err = filepath.Walk(".", func(path string, info fs.FileInfo, err error) error {
- if err != nil {
- fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", path, err)
- return err
- }
- if info.IsDir() && info.Name() == subDirToSkip {
- fmt.Printf("skipping a dir without errors: %+v \n", info.Name())
- return filepath.SkipDir
- }
- fmt.Printf("visited file or dir: %q\n", path)
- return nil
- })
- if err != nil {
- fmt.Printf("error walking the path %q: %v\n", tmpDir, err)
- return
- }
- // Output:
- // On Unix:
- // visited file or dir: "."
- // visited file or dir: "dir"
- // visited file or dir: "dir/to"
- // visited file or dir: "dir/to/walk"
- // skipping a dir without errors: skip
-}
diff --git a/pkg/path/testdata/export_test.go b/pkg/path/testdata/export_test.go
deleted file mode 100644
index 0cf9e3b..0000000
--- a/pkg/path/testdata/export_test.go
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright 2013 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
-
-var LstatP = &lstat
diff --git a/pkg/path/testdata/export_windows_test.go b/pkg/path/testdata/export_windows_test.go
deleted file mode 100644
index a7e2e64..0000000
--- a/pkg/path/testdata/export_windows_test.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright 2016 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
-
-var (
- ToNorm = toNorm
- NormBase = normBase
-)
diff --git a/pkg/path/testdata/match.go b/pkg/path/testdata/match.go
index c77a269..b18d78c 100644
--- a/pkg/path/testdata/match.go
+++ b/pkg/path/testdata/match.go
@@ -1,14 +1,25 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
// Copyright 2010 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
+package path
import (
"errors"
- "os"
- "runtime"
- "sort"
"strings"
"unicode/utf8"
)
@@ -41,18 +52,19 @@
// On Windows, escaping is disabled. Instead, '\\' is treated as
// path separator.
//
-func Match(pattern, name string) (matched bool, err error) {
+func Match(pattern, name string, o OS) (matched bool, err error) {
+ os := getOS(o)
Pattern:
for len(pattern) > 0 {
var star bool
var chunk string
- star, chunk, pattern = scanChunk(pattern)
+ star, chunk, pattern = scanChunk(pattern, os)
if star && chunk == "" {
// Trailing * matches rest of string unless it has a /.
- return !strings.Contains(name, string(Separator)), nil
+ return !strings.Contains(name, string(os.Separator)), nil
}
// Look for match at current position.
- t, ok, err := matchChunk(chunk, name)
+ t, ok, err := matchChunk(chunk, name, os)
// if we're the last chunk, make sure we've exhausted the name
// otherwise we'll give a false result even if we could still match
// using the star
@@ -66,8 +78,8 @@
if star {
// Look for match skipping i+1 bytes.
// Cannot skip /.
- for i := 0; i < len(name) && name[i] != Separator; i++ {
- t, ok, err := matchChunk(chunk, name[i+1:])
+ for i := 0; i < len(name) && name[i] != os.Separator; i++ {
+ t, ok, err := matchChunk(chunk, name[i+1:], os)
if ok {
// if we're the last chunk, make sure we exhausted the name
if len(pattern) == 0 && len(t) > 0 {
@@ -88,7 +100,7 @@
// scanChunk gets the next segment of pattern, which is a non-star string
// possibly preceded by a star.
-func scanChunk(pattern string) (star bool, chunk, rest string) {
+func scanChunk(pattern string, os os) (star bool, chunk, rest string) {
for len(pattern) > 0 && pattern[0] == '*' {
pattern = pattern[1:]
star = true
@@ -99,7 +111,7 @@
for i = 0; i < len(pattern); i++ {
switch pattern[i] {
case '\\':
- if runtime.GOOS != "windows" {
+ if !os.isWindows() {
// error check handled in matchChunk: bad pattern.
if i+1 < len(pattern) {
i++
@@ -121,7 +133,7 @@
// matchChunk checks whether chunk matches the beginning of s.
// If so, it returns the remainder of s (after the match).
// Chunk is all single-character operators: literals, char classes, and ?.
-func matchChunk(chunk, s string) (rest string, ok bool, err error) {
+func matchChunk(chunk, s string, os os) (rest string, ok bool, err error) {
// failed records whether the match has failed.
// After the match fails, the loop continues on processing chunk,
// checking that the pattern is well-formed but no longer reading s.
@@ -155,12 +167,12 @@
break
}
var lo, hi rune
- if lo, chunk, err = getEsc(chunk); err != nil {
+ if lo, chunk, err = getEsc(chunk, os); err != nil {
return "", false, err
}
hi = lo
if chunk[0] == '-' {
- if hi, chunk, err = getEsc(chunk[1:]); err != nil {
+ if hi, chunk, err = getEsc(chunk[1:], os); err != nil {
return "", false, err
}
}
@@ -175,7 +187,7 @@
case '?':
if !failed {
- if s[0] == Separator {
+ if s[0] == os.Separator {
failed = true
}
_, n := utf8.DecodeRuneInString(s)
@@ -184,7 +196,7 @@
chunk = chunk[1:]
case '\\':
- if runtime.GOOS != "windows" {
+ if !os.isWindows() {
chunk = chunk[1:]
if len(chunk) == 0 {
return "", false, ErrBadPattern
@@ -209,12 +221,12 @@
}
// getEsc gets a possibly-escaped character from chunk, for a character class.
-func getEsc(chunk string) (r rune, nchunk string, err error) {
+func getEsc(chunk string, os os) (r rune, nchunk string, err error) {
if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
err = ErrBadPattern
return
}
- if chunk[0] == '\\' && runtime.GOOS != "windows" {
+ if chunk[0] == '\\' && !os.isWindows() {
chunk = chunk[1:]
if len(chunk) == 0 {
err = ErrBadPattern
@@ -231,130 +243,3 @@
}
return
}
-
-// Glob returns the names of all files matching pattern or nil
-// if there is no matching file. The syntax of patterns is the same
-// as in Match. The pattern may describe hierarchical names such as
-// /usr/*/bin/ed (assuming the Separator is '/').
-//
-// Glob ignores file system errors such as I/O errors reading directories.
-// The only possible returned error is ErrBadPattern, when pattern
-// is malformed.
-func Glob(pattern string) (matches []string, err error) {
- // Check pattern is well-formed.
- if _, err := Match(pattern, ""); err != nil {
- return nil, err
- }
- if !hasMeta(pattern) {
- if _, err = os.Lstat(pattern); err != nil {
- return nil, nil
- }
- return []string{pattern}, nil
- }
-
- dir, file := Split(pattern)
- volumeLen := 0
- if runtime.GOOS == "windows" {
- volumeLen, dir = cleanGlobPathWindows(dir)
- } else {
- dir = cleanGlobPath(dir)
- }
-
- if !hasMeta(dir[volumeLen:]) {
- return glob(dir, file, nil)
- }
-
- // Prevent infinite recursion. See issue 15879.
- if dir == pattern {
- return nil, ErrBadPattern
- }
-
- var m []string
- m, err = Glob(dir)
- if err != nil {
- return
- }
- for _, d := range m {
- matches, err = glob(d, file, matches)
- if err != nil {
- return
- }
- }
- return
-}
-
-// cleanGlobPath prepares path for glob matching.
-func cleanGlobPath(path string) string {
- switch path {
- case "":
- return "."
- case string(Separator):
- // do nothing to the path
- return path
- default:
- return path[0 : len(path)-1] // chop off trailing separator
- }
-}
-
-// cleanGlobPathWindows is windows version of cleanGlobPath.
-func cleanGlobPathWindows(path string) (prefixLen int, cleaned string) {
- vollen := volumeNameLen(path)
- switch {
- case path == "":
- return 0, "."
- case vollen+1 == len(path) && os.IsPathSeparator(path[len(path)-1]): // /, \, C:\ and C:/
- // do nothing to the path
- return vollen + 1, path
- case vollen == len(path) && len(path) == 2: // C:
- return vollen, path + "." // convert C: into C:.
- default:
- if vollen >= len(path) {
- vollen = len(path) - 1
- }
- return vollen, path[0 : len(path)-1] // chop off trailing separator
- }
-}
-
-// glob searches for files matching pattern in the directory dir
-// and appends them to matches. If the directory cannot be
-// opened, it returns the existing matches. New matches are
-// added in lexicographical order.
-func glob(dir, pattern string, matches []string) (m []string, e error) {
- m = matches
- fi, err := os.Stat(dir)
- if err != nil {
- return // ignore I/O error
- }
- if !fi.IsDir() {
- return // ignore I/O error
- }
- d, err := os.Open(dir)
- if err != nil {
- return // ignore I/O error
- }
- defer d.Close()
-
- names, _ := d.Readdirnames(-1)
- sort.Strings(names)
-
- for _, n := range names {
- matched, err := Match(pattern, n)
- if err != nil {
- return m, err
- }
- if matched {
- m = append(m, Join(dir, n))
- }
- }
- return
-}
-
-// hasMeta reports whether path contains any of the magic characters
-// recognized by Match.
-func hasMeta(path string) bool {
- magicChars := `*?[`
- if runtime.GOOS != "windows" {
- magicChars = `*?[\`
- }
- return strings.ContainsAny(path, magicChars)
-}
diff --git a/pkg/path/testdata/match_test.go b/pkg/path/testdata/match_test.go
index 1c3b567..2b82e10 100644
--- a/pkg/path/testdata/match_test.go
+++ b/pkg/path/testdata/match_test.go
@@ -1,18 +1,24 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
// 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
+package path
import (
- "fmt"
- "internal/testenv"
- "io/ioutil"
- "os"
- . "path/filepath"
- "reflect"
- "runtime"
- "sort"
"strings"
"testing"
)
@@ -90,304 +96,23 @@
}
func TestMatch(t *testing.T) {
- for _, tt := range matchTests {
- pattern := tt.pattern
- s := tt.s
- if runtime.GOOS == "windows" {
- if strings.Contains(pattern, "\\") {
- // no escape allowed on windows.
- continue
+ for _, os := range []OS{Unix, Windows, Plan9} {
+ for _, tt := range matchTests {
+ pattern := tt.pattern
+ s := tt.s
+ if os == Windows {
+ if strings.Contains(pattern, "\\") {
+ // no escape allowed on windows.
+ continue
+ }
+ pattern = Clean(pattern, os)
+ s = Clean(s, os)
}
- pattern = Clean(pattern)
- s = Clean(s)
+ ok, err := Match(pattern, s, os)
+ if ok != tt.match || err != tt.err {
+ t.Errorf("Match(%#q, %#q, %q) = %v, %q want %v, %q",
+ pattern, s, os, ok, errp(err), tt.match, errp(tt.err))
+ }
}
- ok, err := Match(pattern, s)
- if ok != tt.match || err != tt.err {
- t.Errorf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err))
- }
- }
-}
-
-// contains reports whether vector contains the string s.
-func contains(vector []string, s string) bool {
- for _, elem := range vector {
- if elem == s {
- return true
- }
- }
- return false
-}
-
-var globTests = []struct {
- pattern, result string
-}{
- {"match.go", "match.go"},
- {"mat?h.go", "match.go"},
- {"*", "match.go"},
- {"../*/match.go", "../filepath/match.go"},
-}
-
-func TestGlob(t *testing.T) {
- for _, tt := range globTests {
- pattern := tt.pattern
- result := tt.result
- if runtime.GOOS == "windows" {
- pattern = Clean(pattern)
- result = Clean(result)
- }
- matches, err := Glob(pattern)
- if err != nil {
- t.Errorf("Glob error for %q: %s", pattern, err)
- continue
- }
- if !contains(matches, result) {
- t.Errorf("Glob(%#q) = %#v want %v", pattern, matches, result)
- }
- }
- for _, pattern := range []string{"no_match", "../*/no_match"} {
- matches, err := Glob(pattern)
- if err != nil {
- t.Errorf("Glob error for %q: %s", pattern, err)
- continue
- }
- if len(matches) != 0 {
- t.Errorf("Glob(%#q) = %#v want []", pattern, matches)
- }
- }
-}
-
-func TestGlobError(t *testing.T) {
- bad := []string{`[]`, `nonexist/[]`}
- for _, pattern := range bad {
- if _, err := Glob(pattern); err != ErrBadPattern {
- t.Errorf("Glob(%#q) returned err=%v, want ErrBadPattern", pattern, err)
- }
- }
-}
-
-func TestGlobUNC(t *testing.T) {
- // Just make sure this runs without crashing for now.
- // See issue 15879.
- Glob(`\\?\C:\*`)
-}
-
-var globSymlinkTests = []struct {
- path, dest string
- brokenLink bool
-}{
- {"test1", "link1", false},
- {"test2", "link2", true},
-}
-
-func TestGlobSymlink(t *testing.T) {
- testenv.MustHaveSymlink(t)
-
- tmpDir, err := ioutil.TempDir("", "globsymlink")
- if err != nil {
- t.Fatal("creating temp dir:", err)
- }
- defer os.RemoveAll(tmpDir)
-
- for _, tt := range globSymlinkTests {
- path := Join(tmpDir, tt.path)
- dest := Join(tmpDir, tt.dest)
- f, err := os.Create(path)
- if err != nil {
- t.Fatal(err)
- }
- if err := f.Close(); err != nil {
- t.Fatal(err)
- }
- err = os.Symlink(path, dest)
- if err != nil {
- t.Fatal(err)
- }
- if tt.brokenLink {
- // Break the symlink.
- os.Remove(path)
- }
- matches, err := Glob(dest)
- if err != nil {
- t.Errorf("GlobSymlink error for %q: %s", dest, err)
- }
- if !contains(matches, dest) {
- t.Errorf("Glob(%#q) = %#v want %v", dest, matches, dest)
- }
- }
-}
-
-type globTest struct {
- pattern string
- matches []string
-}
-
-func (test *globTest) buildWant(root string) []string {
- want := make([]string, 0)
- for _, m := range test.matches {
- want = append(want, root+FromSlash(m))
- }
- sort.Strings(want)
- return want
-}
-
-func (test *globTest) globAbs(root, rootPattern string) error {
- p := FromSlash(rootPattern + `\` + test.pattern)
- have, err := Glob(p)
- if err != nil {
- return err
- }
- sort.Strings(have)
- want := test.buildWant(root + `\`)
- if strings.Join(want, "_") == strings.Join(have, "_") {
- return nil
- }
- return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want)
-}
-
-func (test *globTest) globRel(root string) error {
- p := root + FromSlash(test.pattern)
- have, err := Glob(p)
- if err != nil {
- return err
- }
- sort.Strings(have)
- want := test.buildWant(root)
- if strings.Join(want, "_") == strings.Join(have, "_") {
- return nil
- }
- // try also matching version without root prefix
- wantWithNoRoot := test.buildWant("")
- if strings.Join(wantWithNoRoot, "_") == strings.Join(have, "_") {
- return nil
- }
- return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want)
-}
-
-func TestWindowsGlob(t *testing.T) {
- if runtime.GOOS != "windows" {
- t.Skipf("skipping windows specific test")
- }
-
- tmpDir, err := ioutil.TempDir("", "TestWindowsGlob")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpDir)
-
- // /tmp may itself be a symlink
- tmpDir, err = EvalSymlinks(tmpDir)
- if err != nil {
- t.Fatal("eval symlink for tmp dir:", err)
- }
-
- if len(tmpDir) < 3 {
- t.Fatalf("tmpDir path %q is too short", tmpDir)
- }
- if tmpDir[1] != ':' {
- t.Fatalf("tmpDir path %q must have drive letter in it", tmpDir)
- }
-
- dirs := []string{
- "a",
- "b",
- "dir/d/bin",
- }
- files := []string{
- "dir/d/bin/git.exe",
- }
- for _, dir := range dirs {
- err := os.MkdirAll(Join(tmpDir, dir), 0777)
- if err != nil {
- t.Fatal(err)
- }
- }
- for _, file := range files {
- err := ioutil.WriteFile(Join(tmpDir, file), nil, 0666)
- if err != nil {
- t.Fatal(err)
- }
- }
-
- tests := []globTest{
- {"a", []string{"a"}},
- {"b", []string{"b"}},
- {"c", []string{}},
- {"*", []string{"a", "b", "dir"}},
- {"d*", []string{"dir"}},
- {"*i*", []string{"dir"}},
- {"*r", []string{"dir"}},
- {"?ir", []string{"dir"}},
- {"?r", []string{}},
- {"d*/*/bin/git.exe", []string{"dir/d/bin/git.exe"}},
- }
-
- // test absolute paths
- for _, test := range tests {
- var p string
- err = test.globAbs(tmpDir, tmpDir)
- if err != nil {
- t.Error(err)
- }
- // test C:\*Documents and Settings\...
- p = tmpDir
- p = strings.Replace(p, `:\`, `:\*`, 1)
- err = test.globAbs(tmpDir, p)
- if err != nil {
- t.Error(err)
- }
- // test C:\Documents and Settings*\...
- p = tmpDir
- p = strings.Replace(p, `:\`, `:`, 1)
- p = strings.Replace(p, `\`, `*\`, 1)
- p = strings.Replace(p, `:`, `:\`, 1)
- err = test.globAbs(tmpDir, p)
- if err != nil {
- t.Error(err)
- }
- }
-
- // test relative paths
- wd, err := os.Getwd()
- if err != nil {
- t.Fatal(err)
- }
- err = os.Chdir(tmpDir)
- if err != nil {
- t.Fatal(err)
- }
- defer func() {
- err := os.Chdir(wd)
- if err != nil {
- t.Fatal(err)
- }
- }()
- for _, test := range tests {
- err := test.globRel("")
- if err != nil {
- t.Error(err)
- }
- err = test.globRel(`.\`)
- if err != nil {
- t.Error(err)
- }
- err = test.globRel(tmpDir[:2]) // C:
- if err != nil {
- t.Error(err)
- }
- }
-}
-
-func TestNonWindowsGlobEscape(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skipf("skipping non-windows specific test")
- }
- pattern := `\match.go`
- want := []string{"match.go"}
- matches, err := Glob(pattern)
- if err != nil {
- t.Fatalf("Glob error for %q: %s", pattern, err)
- }
- if !reflect.DeepEqual(matches, want) {
- t.Fatalf("Glob(%#q) = %v want %v", pattern, matches, want)
}
}
diff --git a/pkg/path/testdata/os.go b/pkg/path/testdata/os.go
new file mode 100644
index 0000000..d5de0be
--- /dev/null
+++ b/pkg/path/testdata/os.go
@@ -0,0 +1,75 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package path
+
+type OS string
+
+const (
+ Unix OS = "unix"
+ Windows OS = "windows"
+ Plan9 OS = "plan9"
+)
+
+// These types have been designed to minimize the diffs with the original Go
+// code, thereby minimizing potential toil in keeping them up to date.
+
+type os struct {
+ osInfo
+ Separator byte
+ ListSeparator byte
+}
+
+func (o os) isWindows() bool {
+ return o.Separator == '\\'
+}
+
+type osInfo interface {
+ IsPathSeparator(b byte) bool
+ splitList(path string) []string
+ volumeNameLen(path string) int
+ IsAbs(path string) (b bool)
+ HasPrefix(p, prefix string) bool
+ join(elem []string) string
+ sameWord(a, b string) bool
+}
+
+func getOS(o OS) os {
+ switch o {
+ case Windows:
+ return windows
+ case Plan9:
+ return plan9
+ default:
+ return unix
+ }
+}
+
+var (
+ plan9 = os{
+ osInfo: &plan9Info{},
+ Separator: plan9Separator,
+ ListSeparator: plan9ListSeparator,
+ }
+ unix = os{
+ osInfo: &unixInfo{},
+ Separator: unixSeparator,
+ ListSeparator: unixListSeparator,
+ }
+ windows = os{
+ osInfo: &windowsInfo{},
+ Separator: windowsSeparator,
+ ListSeparator: windowsListSeparator,
+ }
+)
diff --git a/pkg/path/testdata/path.go b/pkg/path/testdata/path.go
index 2e7b439..b18883d 100644
--- a/pkg/path/testdata/path.go
+++ b/pkg/path/testdata/path.go
@@ -1,3 +1,17 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
// 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.
@@ -9,13 +23,10 @@
// depending on the operating system. To process paths such as URLs
// that always use forward slashes regardless of the operating
// system, see the path package.
-package filepath
+package path
import (
"errors"
- "io/fs"
- "os"
- "sort"
"strings"
)
@@ -58,13 +69,14 @@
return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
}
-const (
- Separator = os.PathSeparator
- ListSeparator = os.PathListSeparator
-)
+// const (
+// Separator = os.PathSeparator
+// ListSeparator = os.PathListSeparator
+// )
// Clean returns the shortest path name equivalent to path
-// by purely lexical processing. It applies the following rules
+// by purely lexical processing. The default value for os is Unix.
+// It applies the following rules
// iteratively until no further processing can be done:
//
// 1. Replace multiple Separator elements with a single one.
@@ -86,14 +98,18 @@
// See also Rob Pike, ``Lexical File Names in Plan 9 or
// Getting Dot-Dot Right,''
// https://9p.io/sys/doc/lexnames.html
-func Clean(path string) string {
+func Clean(path string, os OS) string {
+ return clean(path, getOS(os))
+}
+
+func clean(path string, os os) string {
originalPath := path
- volLen := volumeNameLen(path)
+ volLen := os.volumeNameLen(path)
path = path[volLen:]
if path == "" {
if volLen > 1 && originalPath[1] != ':' {
// should be UNC
- return FromSlash(originalPath)
+ return fromSlash(originalPath, os)
}
return originalPath + "."
}
@@ -108,7 +124,7 @@
out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
r, dotdot := 0, 0
if rooted {
- out.append(Separator)
+ out.append(os.Separator)
r, dotdot = 1, 1
}
@@ -133,7 +149,7 @@
case !rooted:
// cannot backtrack, but not rooted, so append .. element.
if out.w > 0 {
- out.append(Separator)
+ out.append(os.Separator)
}
out.append('.')
out.append('.')
@@ -143,7 +159,7 @@
// real path element.
// add slash if needed
if rooted && out.w != 1 || !rooted && out.w != 0 {
- out.append(Separator)
+ out.append(os.Separator)
}
// copy element
for ; r < n && !os.IsPathSeparator(path[r]); r++ {
@@ -157,49 +173,58 @@
out.append('.')
}
- return FromSlash(out.string())
+ return fromSlash(out.string(), os)
}
// ToSlash returns the result of replacing each separator character
// in path with a slash ('/') character. Multiple separators are
// replaced by multiple slashes.
-func ToSlash(path string) string {
- if Separator == '/' {
+func ToSlash(path string, os OS) string {
+ return toSlash(path, getOS(os))
+}
+
+func toSlash(path string, os os) string {
+ if os.Separator == '/' {
return path
}
- return strings.ReplaceAll(path, string(Separator), "/")
+ return strings.ReplaceAll(path, string(os.Separator), "/")
}
// FromSlash returns the result of replacing each slash ('/') character
// in path with a separator character. Multiple slashes are replaced
// by multiple separators.
-func FromSlash(path string) string {
- if Separator == '/' {
+func FromSlash(path string, os OS) string {
+ return fromSlash(path, getOS(os))
+}
+
+func fromSlash(path string, os os) string {
+ if os.Separator == '/' {
return path
}
- return strings.ReplaceAll(path, "/", string(Separator))
+ return strings.ReplaceAll(path, "/", string(os.Separator))
}
// SplitList splits a list of paths joined by the OS-specific ListSeparator,
// usually found in PATH or GOPATH environment variables.
// Unlike strings.Split, SplitList returns an empty slice when passed an empty
// string.
-func SplitList(path string) []string {
- return splitList(path)
+func SplitList(path string, os OS) []string {
+ return getOS(os).splitList(path)
}
-// Split splits path immediately following the final Separator,
-// separating it into a directory and file name component.
-// If there is no Separator in path, Split returns an empty dir
-// and file set to path.
-// The returned values have the property that path = dir+file.
-func Split(path string) (dir, file string) {
- vol := VolumeName(path)
+// Split splits path immediately following the final slash and returns them as
+// the list [dir, file], separating it into a directory and file name component.
+// If there is no slash in path, Split returns an empty dir and file set to
+// path. The returned values have the property that path = dir+file.
+// The default value for os is Unix.
+func Split(path string, os OS) []string {
+ x := getOS(os)
+ vol := volumeName(path, x)
i := len(path) - 1
- for i >= len(vol) && !os.IsPathSeparator(path[i]) {
+ for i >= len(vol) && !x.IsPathSeparator(path[i]) {
i--
}
- return path[:i+1], path[i+1:]
+ return []string{path[:i+1], path[i+1:]}
}
// Join joins any number of path elements into a single path,
@@ -209,16 +234,18 @@
// an empty string.
// On Windows, the result will only be a UNC path if the first
// non-empty element is a UNC path.
-func Join(elem ...string) string {
- return join(elem)
+// The default value for os is Unix.
+func Join(elem []string, os OS) string {
+ return getOS(os).join(elem)
}
// Ext returns the file name extension used by path.
// The extension is the suffix beginning at the final dot
// in the final element of path; it is empty if there is
-// no dot.
-func Ext(path string) string {
- for i := len(path) - 1; i >= 0 && !os.IsPathSeparator(path[i]); i-- {
+// no dot. The default value for os is Unix.
+func Ext(path string, os OS) string {
+ x := getOS(os)
+ for i := len(path) - 1; i >= 0 && !x.IsPathSeparator(path[i]); i-- {
if path[i] == '.' {
return path[i:]
}
@@ -226,33 +253,16 @@
return ""
}
-// EvalSymlinks returns the path name after the evaluation of any symbolic
-// links.
-// If path is relative the result will be relative to the current directory,
-// unless one of the components is an absolute symbolic link.
-// EvalSymlinks calls Clean on the result.
-func EvalSymlinks(path string) (string, error) {
- return evalSymlinks(path)
-}
-
-// Abs returns an absolute representation of path.
-// If the path is not absolute it will be joined with the current
-// working directory to turn it into an absolute path. The absolute
-// path name for a given file is not guaranteed to be unique.
-// Abs calls Clean on the result.
-func Abs(path string) (string, error) {
- return abs(path)
-}
-
-func unixAbs(path string) (string, error) {
- if IsAbs(path) {
- return Clean(path), nil
+// Resolve reports the path of sub relative to dir. If sub is an absolute path,
+// or if dir is empty, it will return sub. If sub is empty, it will return dir.
+// Resolve calls Clean on the result. The default value for os is Unix.
+func Resolve(dir, sub string, os OS) string {
+ x := getOS(os)
+ if x.IsAbs(sub) {
+ return clean(sub, x)
}
- wd, err := os.Getwd()
- if err != nil {
- return "", err
- }
- return Join(wd, path), nil
+ dir = clean(dir, x)
+ return x.join([]string{dir, sub})
}
// Rel returns a relative path that is lexically equivalent to targpath when
@@ -262,13 +272,14 @@
// even if basepath and targpath share no elements.
// An error is returned if targpath can't be made relative to basepath or if
// knowing the current working directory would be necessary to compute it.
-// Rel calls Clean on the result.
-func Rel(basepath, targpath string) (string, error) {
- baseVol := VolumeName(basepath)
- targVol := VolumeName(targpath)
- base := Clean(basepath)
- targ := Clean(targpath)
- if sameWord(targ, base) {
+// Rel calls Clean on the result. The default value for os is Unix.
+func Rel(basepath, targpath string, os OS) (string, error) {
+ x := getOS(os)
+ baseVol := volumeName(basepath, x)
+ targVol := volumeName(targpath, x)
+ base := clean(basepath, x)
+ targ := clean(targpath, x)
+ if x.sameWord(targ, base) {
return ".", nil
}
base = base[len(baseVol):]
@@ -277,9 +288,9 @@
base = ""
}
// Can't use IsAbs - `\a` and `a` are both relative in Windows.
- baseSlashed := len(base) > 0 && base[0] == Separator
- targSlashed := len(targ) > 0 && targ[0] == Separator
- if baseSlashed != targSlashed || !sameWord(baseVol, targVol) {
+ baseSlashed := len(base) > 0 && base[0] == x.Separator
+ targSlashed := len(targ) > 0 && targ[0] == x.Separator
+ if baseSlashed != targSlashed || !x.sameWord(baseVol, targVol) {
return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
}
// Position base[b0:bi] and targ[t0:ti] at the first differing elements.
@@ -287,13 +298,13 @@
tl := len(targ)
var b0, bi, t0, ti int
for {
- for bi < bl && base[bi] != Separator {
+ for bi < bl && base[bi] != x.Separator {
bi++
}
- for ti < tl && targ[ti] != Separator {
+ for ti < tl && targ[ti] != x.Separator {
ti++
}
- if !sameWord(targ[t0:ti], base[b0:bi]) {
+ if !x.sameWord(targ[t0:ti], base[b0:bi]) {
break
}
if bi < bl {
@@ -310,7 +321,7 @@
}
if b0 != bl {
// Base elements left. Must go up before going down.
- seps := strings.Count(base[b0:bl], string(Separator))
+ seps := strings.Count(base[b0:bl], string(x.Separator))
size := 2 + seps*3
if tl != t0 {
size += 1 + tl - t0
@@ -318,12 +329,12 @@
buf := make([]byte, size)
n := copy(buf, "..")
for i := 0; i < seps; i++ {
- buf[n] = Separator
+ buf[n] = x.Separator
copy(buf[n+1:], "..")
n += 3
}
if t0 != tl {
- buf[n] = Separator
+ buf[n] = x.Separator
copy(buf[n+1:], targ[t0:])
}
return string(buf), nil
@@ -331,230 +342,25 @@
return targ[t0:], nil
}
-// SkipDir is used as a return value from WalkFuncs to indicate that
-// the directory named in the call is to be skipped. It is not returned
-// as an error by any function.
-var SkipDir error = fs.SkipDir
-
-// WalkFunc is the type of the function called by Walk to visit each each
-// file or directory.
-//
-// The path argument contains the argument to Walk as a prefix.
-// That is, if Walk is called with root argument "dir" and finds a file
-// named "a" in that directory, the walk function will be called with
-// argument "dir/a".
-//
-// The directory and file are joined with Join, which may clean the
-// directory name: if Walk is called with the root argument "x/../dir"
-// and finds a file named "a" in that directory, the walk function will
-// be called with argument "dir/a", not "x/../dir/a".
-//
-// The info argument is the fs.FileInfo for the named path.
-//
-// The error result returned by the function controls how Walk continues.
-// If the function returns the special value SkipDir, Walk skips the
-// current directory (path if info.IsDir() is true, otherwise path's
-// parent directory). Otherwise, if the function returns a non-nil error,
-// Walk stops entirely and returns that error.
-//
-// The err argument reports an error related to path, signaling that Walk
-// will not walk into that directory. The function can decide how to
-// handle that error; as described earlier, returning the error will
-// cause Walk to stop walking the entire tree.
-//
-// Walk calls the function with a non-nil err argument in two cases.
-//
-// First, if an os.Lstat on the root directory or any directory or file
-// in the tree fails, Walk calls the function with path set to that
-// directory or file's path, info set to nil, and err set to the error
-// from os.Lstat.
-//
-// Second, if a directory's Readdirnames method fails, Walk calls the
-// function with path set to the directory's path, info, set to an
-// fs.FileInfo describing the directory, and err set to the error from
-// Readdirnames.
-type WalkFunc func(path string, info fs.FileInfo, err error) error
-
-var lstat = os.Lstat // for testing
-
-// walkDir recursively descends path, calling walkDirFn.
-func walkDir(path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error {
- if err := walkDirFn(path, d, nil); err != nil || !d.IsDir() {
- if err == SkipDir && d.IsDir() {
- // Successfully skipped directory.
- err = nil
- }
- return err
- }
-
- dirs, err := readDir(path)
- if err != nil {
- // Second call, to report ReadDir error.
- err = walkDirFn(path, d, err)
- if err != nil {
- return err
- }
- }
-
- for _, d1 := range dirs {
- path1 := Join(path, d1.Name())
- if err := walkDir(path1, d1, walkDirFn); err != nil {
- if err == SkipDir {
- break
- }
- return err
- }
- }
- return nil
-}
-
-// walk recursively descends path, calling walkFn.
-func walk(path string, info fs.FileInfo, walkFn WalkFunc) error {
- if !info.IsDir() {
- return walkFn(path, info, nil)
- }
-
- names, err := readDirNames(path)
- err1 := walkFn(path, info, err)
- // If err != nil, walk can't walk into this directory.
- // err1 != nil means walkFn want walk to skip this directory or stop walking.
- // Therefore, if one of err and err1 isn't nil, walk will return.
- if err != nil || err1 != nil {
- // The caller's behavior is controlled by the return value, which is decided
- // by walkFn. walkFn may ignore err and return nil.
- // If walkFn returns SkipDir, it will be handled by the caller.
- // So walk should return whatever walkFn returns.
- return err1
- }
-
- for _, name := range names {
- filename := Join(path, name)
- fileInfo, err := lstat(filename)
- if err != nil {
- if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir {
- return err
- }
- } else {
- err = walk(filename, fileInfo, walkFn)
- if err != nil {
- if !fileInfo.IsDir() || err != SkipDir {
- return err
- }
- }
- }
- }
- return nil
-}
-
-// WalkDir walks the file tree rooted at root, calling fn for each file or
-// directory in the tree, including root.
-//
-// All errors that arise visiting files and directories are filtered by fn:
-// see the fs.WalkDirFunc documentation for details.
-//
-// The files are walked in lexical order, which makes the output deterministic
-// but requires WalkDir to read an entire directory into memory before proceeding
-// to walk that directory.
-//
-// WalkDir does not follow symbolic links.
-func WalkDir(root string, fn fs.WalkDirFunc) error {
- info, err := os.Lstat(root)
- if err != nil {
- err = fn(root, nil, err)
- } else {
- err = walkDir(root, &statDirEntry{info}, fn)
- }
- if err == SkipDir {
- return nil
- }
- return err
-}
-
-type statDirEntry struct {
- info fs.FileInfo
-}
-
-func (d *statDirEntry) Name() string { return d.info.Name() }
-func (d *statDirEntry) IsDir() bool { return d.info.IsDir() }
-func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() }
-func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil }
-
-// Walk walks the file tree rooted at root, calling fn for each file or
-// directory in the tree, including root.
-//
-// All errors that arise visiting files and directories are filtered by fn:
-// see the WalkFunc documentation for details.
-//
-// The files are walked in lexical order, which makes the output deterministic
-// but requires Walk to read an entire directory into memory before proceeding
-// to walk that directory.
-//
-// Walk does not follow symbolic links.
-//
-// Walk is less efficient than WalkDir, introduced in Go 1.16,
-// which avoids calling os.Lstat on every visited file or directory.
-func Walk(root string, fn WalkFunc) error {
- info, err := os.Lstat(root)
- if err != nil {
- err = fn(root, nil, err)
- } else {
- err = walk(root, info, fn)
- }
- if err == SkipDir {
- return nil
- }
- return err
-}
-
-// readDir reads the directory named by dirname and returns
-// a sorted list of directory entries.
-func readDir(dirname string) ([]fs.DirEntry, error) {
- f, err := os.Open(dirname)
- if err != nil {
- return nil, err
- }
- dirs, err := f.ReadDir(-1)
- f.Close()
- if err != nil {
- return nil, err
- }
- sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
- return dirs, nil
-}
-
-// readDirNames reads the directory named by dirname and returns
-// a sorted list of directory entry names.
-func readDirNames(dirname string) ([]string, error) {
- f, err := os.Open(dirname)
- if err != nil {
- return nil, err
- }
- names, err := f.Readdirnames(-1)
- f.Close()
- if err != nil {
- return nil, err
- }
- sort.Strings(names)
- return names, nil
-}
-
// Base returns the last element of path.
// Trailing path separators are removed before extracting the last element.
// If the path is empty, Base returns ".".
// If the path consists entirely of separators, Base returns a single separator.
-func Base(path string) string {
+// The default value for os is Unix.
+func Base(path string, os OS) string {
+ x := getOS(os)
if path == "" {
return "."
}
// Strip trailing slashes.
- for len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) {
+ for len(path) > 0 && x.IsPathSeparator(path[len(path)-1]) {
path = path[0 : len(path)-1]
}
// Throw away volume name
- path = path[len(VolumeName(path)):]
+ path = path[x.volumeNameLen(path):]
// Find the last element
i := len(path) - 1
- for i >= 0 && !os.IsPathSeparator(path[i]) {
+ for i >= 0 && !x.IsPathSeparator(path[i]) {
i--
}
if i >= 0 {
@@ -562,7 +368,7 @@
}
// If empty now, it had only slashes.
if path == "" {
- return string(Separator)
+ return string(x.Separator)
}
return path
}
@@ -573,13 +379,15 @@
// If the path is empty, Dir returns ".".
// If the path consists entirely of separators, Dir returns a single separator.
// The returned path does not end in a separator unless it is the root directory.
-func Dir(path string) string {
- vol := VolumeName(path)
+// The default value for os is Unix.
+func Dir(path string, os OS) string {
+ x := getOS(os)
+ vol := volumeName(path, x)
i := len(path) - 1
- for i >= len(vol) && !os.IsPathSeparator(path[i]) {
+ for i >= len(vol) && !x.IsPathSeparator(path[i]) {
i--
}
- dir := Clean(path[len(vol) : i+1])
+ dir := clean(path[len(vol):i+1], x)
if dir == "." && len(vol) > 2 {
// must be UNC
return vol
@@ -587,10 +395,20 @@
return vol + dir
}
+// IsAbs reports whether the path is absolute. The default value for os is Unix.
+func IsAbs(path string, os OS) bool {
+ return getOS(os).IsAbs(path)
+}
+
// VolumeName returns leading volume name.
// Given "C:\foo\bar" it returns "C:" on Windows.
// Given "\\host\share\foo" it returns "\\host\share".
// On other platforms it returns "".
-func VolumeName(path string) string {
- return path[:volumeNameLen(path)]
+// The default value for os is Windows.
+func VolumeName(path string, os OS) string {
+ return volumeName(path, getOS(os))
+}
+
+func volumeName(path string, os os) string {
+ return path[:os.volumeNameLen(path)]
}
diff --git a/pkg/path/testdata/path_nix.go b/pkg/path/testdata/path_nix.go
index ec497d9..eb1b193 100644
--- a/pkg/path/testdata/path_nix.go
+++ b/pkg/path/testdata/path_nix.go
@@ -1,21 +1,46 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
// Copyright 2010 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.
-// +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris
-
-package filepath
+package path
import "strings"
+type unixInfo struct{}
+
+var _ osInfo = unixInfo{}
+
+const (
+ unixListSeparator = ':'
+ unixSeparator = '/'
+)
+
+func (o unixInfo) IsPathSeparator(b byte) bool {
+ return b == unixSeparator
+}
+
// IsAbs reports whether the path is absolute.
-func IsAbs(path string) bool {
+func (o unixInfo) IsAbs(path string) bool {
return strings.HasPrefix(path, "/")
}
// volumeNameLen returns length of the leading volume name on Windows.
// It returns 0 elsewhere.
-func volumeNameLen(path string) int {
+func (o unixInfo) volumeNameLen(path string) int {
return 0
}
@@ -23,31 +48,27 @@
//
// Deprecated: HasPrefix does not respect path boundaries and
// does not ignore case when required.
-func HasPrefix(p, prefix string) bool {
+func (o unixInfo) HasPrefix(p, prefix string) bool {
return strings.HasPrefix(p, prefix)
}
-func splitList(path string) []string {
+func (o unixInfo) splitList(path string) []string {
if path == "" {
return []string{}
}
- return strings.Split(path, string(ListSeparator))
+ return strings.Split(path, string(unixListSeparator))
}
-func abs(path string) (string, error) {
- return unixAbs(path)
-}
-
-func join(elem []string) string {
+func (o unixInfo) join(elem []string) string {
// If there's a bug here, fix the logic in ./path_plan9.go too.
for i, e := range elem {
if e != "" {
- return Clean(strings.Join(elem[i:], string(Separator)))
+ return clean(strings.Join(elem[i:], string(unixSeparator)), unix)
}
}
return ""
}
-func sameWord(a, b string) bool {
+func (o unixInfo) sameWord(a, b string) bool {
return a == b
}
diff --git a/pkg/path/testdata/path_p9.go b/pkg/path/testdata/path_p9.go
index ec792fc..f3379ea 100644
--- a/pkg/path/testdata/path_p9.go
+++ b/pkg/path/testdata/path_p9.go
@@ -1,19 +1,44 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
// Copyright 2010 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
+package path
import "strings"
+const plan9Separator = '/'
+const plan9ListSeparator = '\000'
+
+type plan9Info struct{}
+
+var _ osInfo = plan9Info{}
+
+func (o plan9Info) IsPathSeparator(b byte) bool {
+ return b == plan9Separator
+}
+
// IsAbs reports whether the path is absolute.
-func IsAbs(path string) bool {
+func (o plan9Info) IsAbs(path string) bool {
return strings.HasPrefix(path, "/") || strings.HasPrefix(path, "#")
}
// volumeNameLen returns length of the leading volume name on Windows.
// It returns 0 elsewhere.
-func volumeNameLen(path string) int {
+func (o plan9Info) volumeNameLen(path string) int {
return 0
}
@@ -21,31 +46,27 @@
//
// Deprecated: HasPrefix does not respect path boundaries and
// does not ignore case when required.
-func HasPrefix(p, prefix string) bool {
+func (o plan9Info) HasPrefix(p, prefix string) bool {
return strings.HasPrefix(p, prefix)
}
-func splitList(path string) []string {
+func (o plan9Info) splitList(path string) []string {
if path == "" {
return []string{}
}
- return strings.Split(path, string(ListSeparator))
+ return strings.Split(path, string(plan9ListSeparator))
}
-func abs(path string) (string, error) {
- return unixAbs(path)
-}
-
-func join(elem []string) string {
+func (o plan9Info) join(elem []string) string {
// If there's a bug here, fix the logic in ./path_unix.go too.
for i, e := range elem {
if e != "" {
- return Clean(strings.Join(elem[i:], string(Separator)))
+ return clean(strings.Join(elem[i:], string(plan9Separator)), plan9)
}
}
return ""
}
-func sameWord(a, b string) bool {
+func (o plan9Info) sameWord(a, b string) bool {
return a == b
}
diff --git a/pkg/path/testdata/path_test.go b/pkg/path/testdata/path_test.go
index d760530..61381c6 100644
--- a/pkg/path/testdata/path_test.go
+++ b/pkg/path/testdata/path_test.go
@@ -1,22 +1,26 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
// 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
+package path
import (
- "errors"
- "fmt"
- "internal/testenv"
- "io/fs"
- "io/ioutil"
- "os"
- "path/filepath"
"reflect"
"runtime"
- "sort"
- "strings"
- "syscall"
"testing"
)
@@ -98,53 +102,57 @@
func TestClean(t *testing.T) {
tests := cleantests
- if runtime.GOOS == "windows" {
- for i := range tests {
- tests[i].result = filepath.FromSlash(tests[i].result)
+ for _, os := range []OS{Unix, Windows, Plan9} {
+ if os == Windows {
+ for i := range tests {
+ tests[i].result = FromSlash(tests[i].result, os)
+ }
+ tests = append(tests, wincleantests...)
}
- 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)
+ for _, test := range tests {
+ if s := Clean(test.path, os); s != test.result {
+ t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result)
+ }
+ if s := Clean(test.result, os); s != test.result {
+ t.Errorf("Clean(%q) = %q, want %q", test.result, 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
+ }
+
+ for _, test := range tests {
+ allocs := testing.AllocsPerRun(100, func() { Clean(test.result, os) })
+ if allocs > 0 {
+ t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
+ }
}
}
-
- if testing.Short() {
- t.Skip("skipping malloc count in short mode")
- }
- if runtime.GOMAXPROCS(0) > 1 {
- t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
- 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)
+ for _, o := range []OS{Unix, Windows, Plan9} {
+ sep := getOS(o).Separator
+
+ var slashtests = []PathTest{
+ {"", ""},
+ {"/", string(sep)},
+ {"/a/b", string([]byte{sep, 'a', sep, 'b'})},
+ {"a//b", string([]byte{'a', sep, sep, 'b'})},
}
- if s := filepath.ToSlash(test.result); s != test.path {
- t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path)
+
+ for _, test := range slashtests {
+ if s := FromSlash(test.path, o); s != test.result {
+ t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result)
+ }
+ if s := ToSlash(test.result, o); s != test.path {
+ t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path)
+ }
}
}
}
@@ -154,14 +162,6 @@
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`}},
@@ -185,13 +185,21 @@
}
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)
+ for _, os := range []OS{Unix, Windows, Plan9} {
+ sep := getOS(os).ListSeparator
+
+ tests := []SplitListTest{
+ {"", []string{}},
+ {string([]byte{'a', sep, 'b'}), []string{"a", "b"}},
+ {string([]byte{sep, 'a', sep, 'b'}), []string{"", "a", "b"}},
+ }
+ if os == Windows {
+ tests = append(tests, winsplitlisttests...)
+ }
+ for _, test := range tests {
+ if l := SplitList(test.list, os); !reflect.DeepEqual(l, test.result) {
+ t.Errorf("SplitList(%#q, %q) = %#q, want %#q", test.list, os, l, test.result)
+ }
}
}
}
@@ -222,14 +230,19 @@
}
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)
+ for _, os := range []OS{Windows, Unix} {
+ var splittests []SplitTest
+ splittests = unixsplittests
+ if os == Windows {
+ splittests = append(splittests, winsplittests...)
+ }
+ for _, test := range splittests {
+ pair := Split(test.path, os)
+ d, f := pair[0], pair[1]
+ if d != test.dir || f != test.file {
+ t.Errorf("Split(%q, %q) = %q, %q, want %q, %q",
+ test.path, os, d, f, test.dir, test.file)
+ }
}
}
}
@@ -295,13 +308,15 @@
}
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)
+ for _, os := range []OS{Unix, Windows} {
+ if os == Windows {
+ jointests = append(jointests, winjointests...)
+ }
+ for _, test := range jointests {
+ expected := FromSlash(test.path, os)
+ if p := Join(test.elem, os); p != expected {
+ t.Errorf("join(%q, %q) = %q, want %q", test.elem, os, p, expected)
+ }
}
}
}
@@ -319,343 +334,12 @@
}
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
+ for _, os := range []OS{Unix, Windows} {
+ for _, test := range exttests {
+ if x := Ext(test.path, os); x != test.ext {
+ t.Errorf("Ext(%q, %q) = %q, want %q", test.path, os, x, test.ext)
}
- 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(d fs.DirEntry, err error, errors *[]error, clear bool) error {
- name := d.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) {
- walk := func(root string, fn fs.WalkDirFunc) error {
- return filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
- return fn(path, &statDirEntry{info}, err)
- })
- }
- testWalk(t, walk, 1)
-}
-
-type statDirEntry struct {
- info fs.FileInfo
-}
-
-func (d *statDirEntry) Name() string { return d.info.Name() }
-func (d *statDirEntry) IsDir() bool { return d.info.IsDir() }
-func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() }
-func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil }
-
-func TestWalkDir(t *testing.T) {
- testWalk(t, filepath.WalkDir, 2)
-}
-
-func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) {
- if runtime.GOOS == "ios" {
- restore := chtmpdir(t)
- defer restore()
- }
-
- tmpDir, err := ioutil.TempDir("", "TestWalk")
- if err != nil {
- t.Fatal("creating temp dir:", err)
- }
- defer os.RemoveAll(tmpDir)
-
- origDir, err := os.Getwd()
- if err != nil {
- t.Fatal("finding working dir:", err)
- }
- if err = os.Chdir(tmpDir); err != nil {
- t.Fatal("entering temp dir:", err)
- }
- defer os.Chdir(origDir)
-
- makeTree(t)
- errors := make([]error, 0, 10)
- clear := true
- markFn := func(path string, d fs.DirEntry, err error) error {
- return mark(d, err, &errors, clear)
- }
- // Expect no errors.
- err = 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]
-
- t.Run("PermErr", func(t *testing.T) {
- // 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 runtime.GOOS == "windows" {
- t.Skip("skipping on Windows")
- }
- if os.Getuid() == 0 {
- t.Skip("skipping as root")
- }
- if testing.Short() {
- t.Skip("skipping in short mode")
- }
-
- // 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 -= errVisit
- tree.entries[3].mark -= errVisit
- err := 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 -= errVisit
- tree.entries[3].mark -= errVisit
- clear = false // error will stop processing
- err = 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)
- })
-}
-
-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) error {
- if strings.HasSuffix(path, "foo2") {
- sawFoo2 = true
- }
- if strings.HasSuffix(path, "foo1") {
- return filepath.SkipDir
- }
- return nil
- }
- walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
- walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
-
- check := func(t *testing.T, walk func(root string) error, root string) {
- t.Helper()
- sawFoo2 = false
- err = walk(root)
- if err != nil {
- t.Fatal(err)
- }
- if sawFoo2 {
- t.Errorf("SkipDir on file foo1 did not block processing of foo2")
- }
- }
-
- t.Run("Walk", func(t *testing.T) {
- Walk := func(root string) error { return filepath.Walk(td, walkFn) }
- check(t, Walk, td)
- check(t, Walk, filepath.Join(td, "dir"))
- })
- t.Run("WalkDir", func(t *testing.T) {
- WalkDir := func(root string) error { return filepath.WalkDir(td, walkDirFn) }
- check(t, WalkDir, td)
- check(t, WalkDir, filepath.Join(td, "dir"))
- })
-}
-
-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) (fs.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 fs.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)
}
}
@@ -686,17 +370,19 @@
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)
+ for _, os := range []OS{Unix, Windows} {
+ if os == Windows {
+ // make unix tests work on windows
+ for i := range tests {
+ tests[i].result = Clean(tests[i].result, os)
+ }
+ // add windows specific tests
+ tests = append(tests, winbasetests...)
}
- // 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)
+ for _, test := range tests {
+ if s := Base(test.path, os); s != test.result {
+ t.Errorf("Base(%q, %q) = %q, want %q", test.path, os, s, test.result)
+ }
}
}
}
@@ -729,18 +415,20 @@
}
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)
+ for _, os := range []OS{Unix, Windows} {
+ tests := dirtests
+ if os == Windows {
+ // make unix tests work on windows
+ for i := range tests {
+ tests[i].result = Clean(tests[i].result, os)
+ }
+ // add windows specific tests
+ tests = append(tests, windirtests...)
}
- // 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)
+ for _, test := range tests {
+ if s := Dir(test.path, os); s != test.result {
+ t.Errorf("Dir(%q, %q) = %q, want %q", test.path, os, s, test.result)
+ }
}
}
}
@@ -777,267 +465,41 @@
}
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})
- }
- // Test reserved names.
- tests = append(tests, IsAbsTest{os.DevNull, true})
- tests = append(tests, IsAbsTest{"NUL", true})
- tests = append(tests, IsAbsTest{"nul", true})
- tests = append(tests, IsAbsTest{"CON", true})
- } 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", "/"},
- {"test/link4", "../test2"},
- {"test2", "test/dir"},
- // Issue 23444.
- {"src", ""},
- {"src/pool", ""},
- {"src/pool/test", ""},
- {"src/versions", ""},
- {"src/versions/current", "../../version"},
- {"src/versions/v1", ""},
- {"src/versions/v1/modules", ""},
- {"src/versions/v1/modules/test", "../../../pool/test"},
- {"version", "src/versions/v1"},
-}
-
-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", "/"},
- {"test/link4/..", "test"},
- {"src/versions/current/modules/test", "src/pool/test"},
-}
-
-// 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)
+ for _, os := range []OS{Unix, Windows} {
+ var tests []IsAbsTest
+ if os == 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})
+ }
+ // Test reserved names.
+ // tests = append(tests, IsAbsTest{"/dev/null", true})
+ tests = append(tests, IsAbsTest{"NUL", true})
+ tests = append(tests, IsAbsTest{"nul", true})
+ tests = append(tests, IsAbsTest{"CON", true})
} 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)
+ tests = isabstests
}
- // 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)
+ for _, test := range tests {
+ if r := IsAbs(test.path, os); r != test.isAbs {
+ t.Errorf("IsAbs(%q, %q) = %v, want %v", test.path, os, r, test.isAbs)
+ }
}
}
}
+// // 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(Separator) + path
+// }
+
// Test directories relative to temporary directory.
// The tests are run in absTestDirs[0].
var absTestDirs = []string{
@@ -1064,115 +526,6 @@
"$/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.ReplaceAll(path, "$", root)
- 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)
- }
- }
-}
-
-// Empty path needs to be special-cased on Windows. See golang.org/issue/24441.
-// We test it separately from all other absTests because the empty string is not
-// a valid path, so it can't be used with os.Stat.
-func TestAbsEmptyString(t *testing.T) {
- root, err := ioutil.TempDir("", "TestAbsEmptyString")
- 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)
-
- info, err := os.Stat(root)
- if err != nil {
- t.Fatalf("%s: %s", root, err)
- }
-
- abspath, err := filepath.Abs("")
- if err != nil {
- t.Fatalf(`Abs("") error: %v`, err)
- }
- absinfo, err := os.Stat(abspath)
- if err != nil || !os.SameFile(absinfo, info) {
- t.Errorf(`Abs("")=%q, not the same file`, abspath)
- }
- if !filepath.IsAbs(abspath) {
- t.Errorf(`Abs("")=%q, not an absolute path`, abspath)
- }
- if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) {
- t.Errorf(`Abs("")=%q, isn't clean`, abspath)
- }
-}
-
type RelTests struct {
root, path, want string
}
@@ -1231,26 +584,28 @@
}
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)
+ for _, os := range []OS{Unix, Windows} {
+ tests := append([]RelTests{}, reltests...)
+ if os == Windows {
+ for i := range tests {
+ tests[i].want = FromSlash(tests[i].want, Windows)
}
- continue
+ tests = append(tests, winreltests...)
}
- 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)
+ for _, test := range tests {
+ got, err := Rel(test.root, test.path, os)
+ if test.want == "err" {
+ if err == nil {
+ t.Errorf("Rel(%q, %q, %q)=%q, want error", test.root, test.path, os, got)
+ }
+ continue
+ }
+ if err != nil {
+ t.Errorf("Rel(%q, %q, %q): want %q, got error: %s", test.root, test.path, os, test.want, err)
+ }
+ if got != test.want {
+ t.Errorf("Rel(%q, %q, %q)=%q, want %q", test.root, test.path, os, got, test.want)
+ }
}
}
}
@@ -1286,253 +641,14 @@
}
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)
+ for _, os := range []OS{Unix, Windows} {
+ if os != Windows {
+ return
}
- }
-}
-
-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
- if runtime.GOOS == "ios" {
- 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 fs.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")
+ for _, v := range volumenametests {
+ if vol := VolumeName(v.path, os); vol != v.vol {
+ t.Errorf("VolumeName(%q, %q)=%q, want %q", v.path, os, vol, v.vol)
}
- 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 fs.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)
-}
-
-func TestIssue29372(t *testing.T) {
- tmpDir, err := ioutil.TempDir("", "TestIssue29372")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpDir)
-
- path := filepath.Join(tmpDir, "file.txt")
- err = ioutil.WriteFile(path, nil, 0644)
- if err != nil {
- t.Fatal(err)
- }
-
- pathSeparator := string(filepath.Separator)
- tests := []string{
- path + strings.Repeat(pathSeparator, 1),
- path + strings.Repeat(pathSeparator, 2),
- path + strings.Repeat(pathSeparator, 1) + ".",
- path + strings.Repeat(pathSeparator, 2) + ".",
- path + strings.Repeat(pathSeparator, 1) + "..",
- path + strings.Repeat(pathSeparator, 2) + "..",
- }
-
- for i, test := range tests {
- _, err = filepath.EvalSymlinks(test)
- if err != syscall.ENOTDIR {
- t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err)
- }
- }
-}
-
-// Issue 30520 part 1.
-func TestEvalSymlinksAboveRoot(t *testing.T) {
- testenv.MustHaveSymlink(t)
-
- t.Parallel()
-
- tmpDir, err := ioutil.TempDir("", "TestEvalSymlinksAboveRoot")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpDir)
-
- evalTmpDir, err := filepath.EvalSymlinks(tmpDir)
- if err != nil {
- t.Fatal(err)
- }
-
- if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil {
- t.Fatal(err)
- }
- if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
- t.Fatal(err)
- }
- if err := ioutil.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
- t.Fatal(err)
- }
-
- // Count the number of ".." elements to get to the root directory.
- vol := filepath.VolumeName(evalTmpDir)
- c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator))
- var dd []string
- for i := 0; i < c+2; i++ {
- dd = append(dd, "..")
- }
-
- wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator))
-
- // Try different numbers of "..".
- for _, i := range []int{c, c + 1, c + 2} {
- check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator))
- if resolved, err := filepath.EvalSymlinks(check); err != nil {
- t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
- } else if !strings.HasSuffix(resolved, wantSuffix) {
- t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
- } else {
- t.Logf("EvalSymlinks(%q) = %q", check, resolved)
- }
- }
-}
-
-// Issue 30520 part 2.
-func TestEvalSymlinksAboveRootChdir(t *testing.T) {
- testenv.MustHaveSymlink(t)
-
- tmpDir, err := ioutil.TempDir("", "TestEvalSymlinksAboveRootChdir")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpDir)
-
- wd, err := os.Getwd()
- if err != nil {
- t.Fatal(err)
- }
- defer os.Chdir(wd)
-
- if err := os.Chdir(tmpDir); err != nil {
- t.Fatal(err)
- }
-
- subdir := filepath.Join("a", "b")
- if err := os.MkdirAll(subdir, 0777); err != nil {
- t.Fatal(err)
- }
- if err := os.Symlink(subdir, "c"); err != nil {
- t.Fatal(err)
- }
- if err := ioutil.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
- t.Fatal(err)
- }
-
- subdir = filepath.Join("d", "e", "f")
- if err := os.MkdirAll(subdir, 0777); err != nil {
- t.Fatal(err)
- }
- if err := os.Chdir(subdir); err != nil {
- t.Fatal(err)
- }
-
- check := filepath.Join("..", "..", "..", "c", "file")
- wantSuffix := filepath.Join("a", "b", "file")
- if resolved, err := filepath.EvalSymlinks(check); err != nil {
- t.Errorf("EvalSymlinks(%q) failed: %v", check, err)
- } else if !strings.HasSuffix(resolved, wantSuffix) {
- t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix)
- } else {
- t.Logf("EvalSymlinks(%q) = %q", check, resolved)
}
}
diff --git a/pkg/path/testdata/path_win.go b/pkg/path/testdata/path_win.go
index 445c868..054ae28 100644
--- a/pkg/path/testdata/path_win.go
+++ b/pkg/path/testdata/path_win.go
@@ -1,18 +1,44 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
// Copyright 2010 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
+package path
import (
"strings"
- "syscall"
+)
+
+type windowsInfo struct{}
+
+var _ osInfo = windowsInfo{}
+
+const (
+ windowsSeparator = '\\'
+ windowsListSeparator = ';'
)
func isSlash(c uint8) bool {
return c == '\\' || c == '/'
}
+func (os windowsInfo) IsPathSeparator(b byte) bool {
+ return isSlash(b)
+}
+
// reservedNames lists reserved Windows names. Search for PRN in
// https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
// for details.
@@ -24,7 +50,7 @@
// isReservedName returns true, if path is Windows reserved name.
// See reservedNames for the full list.
-func isReservedName(path string) bool {
+func (os windowsInfo) isReservedName(path string) bool {
if len(path) == 0 {
return false
}
@@ -37,11 +63,11 @@
}
// IsAbs reports whether the path is absolute.
-func IsAbs(path string) (b bool) {
- if isReservedName(path) {
+func (os windowsInfo) IsAbs(path string) (b bool) {
+ if os.isReservedName(path) {
return true
}
- l := volumeNameLen(path)
+ l := os.volumeNameLen(path)
if l == 0 {
return false
}
@@ -54,7 +80,7 @@
// volumeNameLen returns length of the leading volume name on Windows.
// It returns 0 elsewhere.
-func volumeNameLen(path string) int {
+func (os windowsInfo) volumeNameLen(path string) int {
if len(path) < 2 {
return 0
}
@@ -94,14 +120,14 @@
//
// Deprecated: HasPrefix does not respect path boundaries and
// does not ignore case when required.
-func HasPrefix(p, prefix string) bool {
+func (os windowsInfo) HasPrefix(p, prefix string) bool {
if strings.HasPrefix(p, prefix) {
return true
}
return strings.HasPrefix(strings.ToLower(p), strings.ToLower(prefix))
}
-func splitList(path string) []string {
+func (os windowsInfo) splitList(path string) []string {
// The same implementation is used in LookPath in os/exec;
// consider changing os/exec when changing this.
@@ -117,7 +143,7 @@
switch c := path[i]; {
case c == '"':
quo = !quo
- case c == ListSeparator && !quo:
+ case c == windowsListSeparator && !quo:
list = append(list, path[start:i])
start = i + 1
}
@@ -132,31 +158,17 @@
return list
}
-func abs(path string) (string, error) {
- if path == "" {
- // syscall.FullPath returns an error on empty path, because it's not a valid path.
- // To implement Abs behavior of returning working directory on empty string input,
- // special-case empty path by changing it to "." path. See golang.org/issue/24441.
- path = "."
- }
- fullPath, err := syscall.FullPath(path)
- if err != nil {
- return "", err
- }
- return Clean(fullPath), nil
-}
-
-func join(elem []string) string {
+func (os windowsInfo) join(elem []string) string {
for i, e := range elem {
if e != "" {
- return joinNonEmpty(elem[i:])
+ return os.joinNonEmpty(elem[i:])
}
}
return ""
}
// joinNonEmpty is like join, but it assumes that the first element is non-empty.
-func joinNonEmpty(elem []string) string {
+func (o windowsInfo) joinNonEmpty(elem []string) string {
if len(elem[0]) == 2 && elem[0][1] == ':' {
// First element is drive letter without terminating slash.
// Keep path relative to current directory on that drive.
@@ -167,34 +179,34 @@
break
}
}
- return Clean(elem[0] + strings.Join(elem[i:], string(Separator)))
+ return clean(elem[0]+strings.Join(elem[i:], string(windowsSeparator)), windows)
}
// The following logic prevents Join from inadvertently creating a
// UNC path on Windows. Unless the first element is a UNC path, Join
// shouldn't create a UNC path. See golang.org/issue/9167.
- p := Clean(strings.Join(elem, string(Separator)))
+ p := clean(strings.Join(elem, string(windowsSeparator)), windows)
if !isUNC(p) {
return p
}
// p == UNC only allowed when the first element is a UNC path.
- head := Clean(elem[0])
+ head := clean(elem[0], windows)
if isUNC(head) {
return p
}
// head + tail == UNC, but joining two non-UNC paths should not result
// in a UNC path. Undo creation of UNC path.
- tail := Clean(strings.Join(elem[1:], string(Separator)))
- if head[len(head)-1] == Separator {
+ tail := clean(strings.Join(elem[1:], string(windowsSeparator)), windows)
+ if head[len(head)-1] == windowsSeparator {
return head + tail
}
- return head + string(Separator) + tail
+ return head + string(windowsSeparator) + tail
}
// isUNC reports whether path is a UNC path.
func isUNC(path string) bool {
- return volumeNameLen(path) > 2
+ return windows.volumeNameLen(path) > 2
}
-func sameWord(a, b string) bool {
+func (o windowsInfo) sameWord(a, b string) bool {
return strings.EqualFold(a, b)
}
diff --git a/pkg/path/testdata/path_windows_test.go b/pkg/path/testdata/path_windows_test.go
index 9309a7d..28d1ebb 100644
--- a/pkg/path/testdata/path_windows_test.go
+++ b/pkg/path/testdata/path_windows_test.go
@@ -1,26 +1,36 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
// Copyright 2013 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
+package path
import (
"flag"
"fmt"
- "internal/testenv"
- "io/fs"
"io/ioutil"
- "os"
+ goos "os"
"os/exec"
- "path/filepath"
"reflect"
- "runtime/debug"
"strings"
"testing"
)
func TestWinSplitListTestsAreValid(t *testing.T) {
- comspec := os.Getenv("ComSpec")
+ comspec := goos.Getenv("ComSpec")
if comspec == "" {
t.Fatal("%ComSpec% must be set")
}
@@ -34,35 +44,35 @@
comspec string) {
const (
- cmdfile = `printdir.cmd`
- perm fs.FileMode = 0700
+ cmdfile = `printdir.cmd`
+ perm = 0700
)
tmp, err := ioutil.TempDir("", "testWinSplitListTestIsValid")
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
- defer os.RemoveAll(tmp)
+ defer goos.RemoveAll(tmp)
for i, d := range tt.result {
if d == "" {
continue
}
- if cd := filepath.Clean(d); filepath.VolumeName(cd) != "" ||
+ if cd := Clean(d, Windows); VolumeName(cd, Windows) != "" ||
cd[0] == '\\' || cd == ".." || (len(cd) >= 3 && cd[0:3] == `..\`) {
t.Errorf("%d,%d: %#q refers outside working directory", ti, i, d)
return
}
- dd := filepath.Join(tmp, d)
- if _, err := os.Stat(dd); err == nil {
+ dd := Join([]string{tmp, d}, Windows)
+ if _, err := goos.Stat(dd); err == nil {
t.Errorf("%d,%d: %#q already exists", ti, i, d)
return
}
- if err = os.MkdirAll(dd, perm); err != nil {
+ if err = goos.MkdirAll(dd, perm); err != nil {
t.Errorf("%d,%d: MkdirAll(%#q) failed: %v", ti, i, dd, err)
return
}
- fn, data := filepath.Join(dd, cmdfile), []byte("@echo "+d+"\r\n")
+ fn, data := Join([]string{dd, cmdfile}, Windows), []byte("@echo "+d+"\r\n")
if err = ioutil.WriteFile(fn, data, perm); err != nil {
t.Errorf("%d,%d: WriteFile(%#q) failed: %v", ti, i, fn, err)
return
@@ -70,7 +80,7 @@
}
// on some systems, SystemRoot is required for cmd to work
- systemRoot := os.Getenv("SystemRoot")
+ systemRoot := goos.Getenv("SystemRoot")
for i, d := range tt.result {
if d == "" {
@@ -93,7 +103,7 @@
return
default:
// unshadow cmdfile in next directory
- err = os.Remove(filepath.Join(tmp, d, cmdfile))
+ err = goos.Remove(Join([]string{tmp, d, cmdfile}, Windows))
if err != nil {
t.Fatalf("Remove test command failed: %v", err)
}
@@ -101,126 +111,6 @@
}
}
-func TestWindowsEvalSymlinks(t *testing.T) {
- testenv.MustHaveSymlink(t)
-
- tmpDir, err := ioutil.TempDir("", "TestWindowsEvalSymlinks")
- if err != nil {
- t.Fatal(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(err)
- }
-
- if len(tmpDir) < 3 {
- t.Fatalf("tmpDir path %q is too short", tmpDir)
- }
- if tmpDir[1] != ':' {
- t.Fatalf("tmpDir path %q must have drive letter in it", tmpDir)
- }
- test := EvalSymlinksTest{"test/linkabswin", tmpDir[:3]}
-
- // Create the symlink farm using relative paths.
- testdirs := append(EvalSymlinksTestDirs, test)
- for _, d := range testdirs {
- 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)
- }
- }
-
- path := simpleJoin(tmpDir, test.path)
-
- testEvalSymlinks(t, path, test.dest)
-
- testEvalSymlinksAfterChdir(t, path, ".", test.dest)
-
- testEvalSymlinksAfterChdir(t,
- path,
- filepath.VolumeName(tmpDir)+".",
- test.dest)
-
- testEvalSymlinksAfterChdir(t,
- simpleJoin(tmpDir, "test"),
- simpleJoin("..", test.path),
- test.dest)
-
- testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
-}
-
-// TestEvalSymlinksCanonicalNames verify that EvalSymlinks
-// returns "canonical" path names on windows.
-func TestEvalSymlinksCanonicalNames(t *testing.T) {
- tmp, err := ioutil.TempDir("", "evalsymlinkcanonical")
- if err != nil {
- t.Fatal("creating temp dir:", err)
- }
- defer os.RemoveAll(tmp)
-
- // ioutil.TempDir might return "non-canonical" name.
- cTmpName, err := filepath.EvalSymlinks(tmp)
- if err != nil {
- t.Errorf("EvalSymlinks(%q) error: %v", tmp, err)
- }
-
- dirs := []string{
- "test",
- "test/dir",
- "testing_long_dir",
- "TEST2",
- }
-
- for _, d := range dirs {
- dir := filepath.Join(cTmpName, d)
- err := os.Mkdir(dir, 0755)
- if err != nil {
- t.Fatal(err)
- }
- cname, err := filepath.EvalSymlinks(dir)
- if err != nil {
- t.Errorf("EvalSymlinks(%q) error: %v", dir, err)
- continue
- }
- if dir != cname {
- t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", dir, cname, dir)
- continue
- }
- // test non-canonical names
- test := strings.ToUpper(dir)
- p, err := filepath.EvalSymlinks(test)
- if err != nil {
- t.Errorf("EvalSymlinks(%q) error: %v", test, err)
- continue
- }
- if p != cname {
- t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", test, p, cname)
- continue
- }
- // another test
- test = strings.ToLower(dir)
- p, err = filepath.EvalSymlinks(test)
- if err != nil {
- t.Errorf("EvalSymlinks(%q) error: %v", test, err)
- continue
- }
- if p != cname {
- t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", test, p, cname)
- continue
- }
- }
-}
-
// checkVolume8dot3Setting runs "fsutil 8dot3name query c:" command
// (where c: is vol parameter) to discover "8dot3 name creation state".
// The state is combination of 2 flags. The global flag controls if it
@@ -287,301 +177,3 @@
}
var runFSModifyTests = flag.Bool("run_fs_modify_tests", false, "run tests which modify filesystem parameters")
-
-// This test assumes registry state of NtfsDisable8dot3NameCreation is 2,
-// the default (Volume level setting).
-func TestEvalSymlinksCanonicalNamesWith8dot3Disabled(t *testing.T) {
- if !*runFSModifyTests {
- t.Skip("skipping test that modifies file system setting; enable with -run_fs_modify_tests")
- }
- tempVol := filepath.VolumeName(os.TempDir())
- if len(tempVol) != 2 {
- t.Fatalf("unexpected temp volume name %q", tempVol)
- }
-
- err := checkVolume8dot3Setting(tempVol, true)
- if err != nil {
- t.Fatal(err)
- }
- err = setVolume8dot3Setting(tempVol, false)
- if err != nil {
- t.Fatal(err)
- }
- defer func() {
- err := setVolume8dot3Setting(tempVol, true)
- if err != nil {
- t.Fatal(err)
- }
- err = checkVolume8dot3Setting(tempVol, true)
- if err != nil {
- t.Fatal(err)
- }
- }()
- err = checkVolume8dot3Setting(tempVol, false)
- if err != nil {
- t.Fatal(err)
- }
- TestEvalSymlinksCanonicalNames(t)
-}
-
-func TestToNorm(t *testing.T) {
- stubBase := func(path string) (string, error) {
- vol := filepath.VolumeName(path)
- path = path[len(vol):]
-
- if strings.Contains(path, "/") {
- return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
- }
-
- if path == "" || path == "." || path == `\` {
- return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
- }
-
- i := strings.LastIndexByte(path, filepath.Separator)
- if i == len(path)-1 { // trailing '\' is invalid
- return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
- }
- if i == -1 {
- return strings.ToUpper(path), nil
- }
-
- return strings.ToUpper(path[i+1:]), nil
- }
-
- // On this test, toNorm should be same as string.ToUpper(filepath.Clean(path)) except empty string.
- tests := []struct {
- arg string
- want string
- }{
- {"", ""},
- {".", "."},
- {"./foo/bar", `FOO\BAR`},
- {"/", `\`},
- {"/foo/bar", `\FOO\BAR`},
- {"/foo/bar/baz/qux", `\FOO\BAR\BAZ\QUX`},
- {"foo/bar", `FOO\BAR`},
- {"C:/foo/bar", `C:\FOO\BAR`},
- {"C:foo/bar", `C:FOO\BAR`},
- {"c:/foo/bar", `C:\FOO\BAR`},
- {"C:/foo/bar", `C:\FOO\BAR`},
- {"C:/foo/bar/", `C:\FOO\BAR`},
- {`C:\foo\bar`, `C:\FOO\BAR`},
- {`C:\foo/bar\`, `C:\FOO\BAR`},
- {"C:/ふー/バー", `C:\ふー\バー`},
- }
-
- for _, test := range tests {
- got, err := filepath.ToNorm(test.arg, stubBase)
- if err != nil {
- t.Errorf("toNorm(%s) failed: %v\n", test.arg, err)
- } else if got != test.want {
- t.Errorf("toNorm(%s) returns %s, but %s expected\n", test.arg, got, test.want)
- }
- }
-
- testPath := `{{tmp}}\test\foo\bar`
-
- testsDir := []struct {
- wd string
- arg string
- want string
- }{
- // test absolute paths
- {".", `{{tmp}}\test\foo\bar`, `{{tmp}}\test\foo\bar`},
- {".", `{{tmp}}\.\test/foo\bar`, `{{tmp}}\test\foo\bar`},
- {".", `{{tmp}}\test\..\test\foo\bar`, `{{tmp}}\test\foo\bar`},
- {".", `{{tmp}}\TEST\FOO\BAR`, `{{tmp}}\test\foo\bar`},
-
- // test relative paths begin with drive letter
- {`{{tmp}}\test`, `{{tmpvol}}.`, `{{tmpvol}}.`},
- {`{{tmp}}\test`, `{{tmpvol}}..`, `{{tmpvol}}..`},
- {`{{tmp}}\test`, `{{tmpvol}}foo\bar`, `{{tmpvol}}foo\bar`},
- {`{{tmp}}\test`, `{{tmpvol}}.\foo\bar`, `{{tmpvol}}foo\bar`},
- {`{{tmp}}\test`, `{{tmpvol}}foo\..\foo\bar`, `{{tmpvol}}foo\bar`},
- {`{{tmp}}\test`, `{{tmpvol}}FOO\BAR`, `{{tmpvol}}foo\bar`},
-
- // test relative paths begin with '\'
- {"{{tmp}}", `{{tmpnovol}}\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
- {"{{tmp}}", `{{tmpnovol}}\.\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
- {"{{tmp}}", `{{tmpnovol}}\test\..\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
- {"{{tmp}}", `{{tmpnovol}}\TEST\FOO\BAR`, `{{tmpnovol}}\test\foo\bar`},
-
- // test relative paths begin without '\'
- {`{{tmp}}\test`, ".", `.`},
- {`{{tmp}}\test`, "..", `..`},
- {`{{tmp}}\test`, `foo\bar`, `foo\bar`},
- {`{{tmp}}\test`, `.\foo\bar`, `foo\bar`},
- {`{{tmp}}\test`, `foo\..\foo\bar`, `foo\bar`},
- {`{{tmp}}\test`, `FOO\BAR`, `foo\bar`},
-
- // test UNC paths
- {".", `\\localhost\c$`, `\\localhost\c$`},
- }
-
- tmp, err := ioutil.TempDir("", "testToNorm")
- if err != nil {
- t.Fatal(err)
- }
- defer func() {
- err := os.RemoveAll(tmp)
- if err != nil {
- t.Fatal(err)
- }
- }()
-
- // ioutil.TempDir might return "non-canonical" name.
- ctmp, err := filepath.EvalSymlinks(tmp)
- if err != nil {
- t.Fatal(err)
- }
-
- err = os.MkdirAll(strings.ReplaceAll(testPath, "{{tmp}}", ctmp), 0777)
- if err != nil {
- t.Fatal(err)
- }
-
- cwd, err := os.Getwd()
- if err != nil {
- t.Fatal(err)
- }
- defer func() {
- err := os.Chdir(cwd)
- if err != nil {
- t.Fatal(err)
- }
- }()
-
- tmpVol := filepath.VolumeName(ctmp)
- if len(tmpVol) != 2 {
- t.Fatalf("unexpected temp volume name %q", tmpVol)
- }
-
- tmpNoVol := ctmp[len(tmpVol):]
-
- replacer := strings.NewReplacer("{{tmp}}", ctmp, "{{tmpvol}}", tmpVol, "{{tmpnovol}}", tmpNoVol)
-
- for _, test := range testsDir {
- wd := replacer.Replace(test.wd)
- arg := replacer.Replace(test.arg)
- want := replacer.Replace(test.want)
-
- if test.wd == "." {
- err := os.Chdir(cwd)
- if err != nil {
- t.Error(err)
-
- continue
- }
- } else {
- err := os.Chdir(wd)
- if err != nil {
- t.Error(err)
-
- continue
- }
- }
-
- got, err := filepath.ToNorm(arg, filepath.NormBase)
- if err != nil {
- t.Errorf("toNorm(%s) failed: %v (wd=%s)\n", arg, err, wd)
- } else if got != want {
- t.Errorf("toNorm(%s) returns %s, but %s expected (wd=%s)\n", arg, got, want, wd)
- }
- }
-}
-
-func TestUNC(t *testing.T) {
- // Test that this doesn't go into an infinite recursion.
- // See golang.org/issue/15879.
- defer debug.SetMaxStack(debug.SetMaxStack(1e6))
- filepath.Glob(`\\?\c:\*`)
-}
-
-func testWalkMklink(t *testing.T, linktype string) {
- output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output()
- if !strings.Contains(string(output), fmt.Sprintf(" /%s ", linktype)) {
- t.Skipf(`skipping test; mklink does not supports /%s parameter`, linktype)
- }
- testWalkSymlink(t, func(target, link string) error {
- output, err := exec.Command("cmd", "/c", "mklink", "/"+linktype, link, target).CombinedOutput()
- if err != nil {
- return fmt.Errorf(`"mklink /%s %v %v" command failed: %v\n%v`, linktype, link, target, err, string(output))
- }
- return nil
- })
-}
-
-func TestWalkDirectoryJunction(t *testing.T) {
- testenv.MustHaveSymlink(t)
- testWalkMklink(t, "J")
-}
-
-func TestWalkDirectorySymlink(t *testing.T) {
- testenv.MustHaveSymlink(t)
- testWalkMklink(t, "D")
-}
-
-func TestNTNamespaceSymlink(t *testing.T) {
- output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output()
- if !strings.Contains(string(output), " /J ") {
- t.Skip("skipping test because mklink command does not support junctions")
- }
-
- tmpdir, err := ioutil.TempDir("", "TestNTNamespaceSymlink")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
-
- // Make sure tmpdir is not a symlink, otherwise tests will fail.
- tmpdir, err = filepath.EvalSymlinks(tmpdir)
- if err != nil {
- t.Fatal(err)
- }
-
- vol := filepath.VolumeName(tmpdir)
- output, err = exec.Command("cmd", "/c", "mountvol", vol, "/L").CombinedOutput()
- if err != nil {
- t.Fatalf("failed to run mountvol %v /L: %v %q", vol, err, output)
- }
- target := strings.Trim(string(output), " \n\r")
-
- dirlink := filepath.Join(tmpdir, "dirlink")
- output, err = exec.Command("cmd", "/c", "mklink", "/J", dirlink, target).CombinedOutput()
- if err != nil {
- t.Fatalf("failed to run mklink %v %v: %v %q", dirlink, target, err, output)
- }
-
- got, err := filepath.EvalSymlinks(dirlink)
- if err != nil {
- t.Fatal(err)
- }
- if want := vol + `\`; got != want {
- t.Errorf(`EvalSymlinks(%q): got %q, want %q`, dirlink, got, want)
- }
-
- // Make sure we have sufficient privilege to run mklink command.
- testenv.MustHaveSymlink(t)
-
- file := filepath.Join(tmpdir, "file")
- err = ioutil.WriteFile(file, []byte(""), 0666)
- if err != nil {
- t.Fatal(err)
- }
-
- target += file[len(filepath.VolumeName(file)):]
-
- filelink := filepath.Join(tmpdir, "filelink")
- output, err = exec.Command("cmd", "/c", "mklink", filelink, target).CombinedOutput()
- if err != nil {
- t.Fatalf("failed to run mklink %v %v: %v %q", filelink, target, err, output)
- }
-
- got, err = filepath.EvalSymlinks(filelink)
- if err != nil {
- t.Fatal(err)
- }
- if want := file; got != want {
- t.Errorf(`EvalSymlinks(%q): got %q, want %q`, filelink, got, want)
- }
-}
diff --git a/pkg/path/testdata/pathtxtar_test.go b/pkg/path/testdata/pathtxtar_test.go
new file mode 100644
index 0000000..af861d9
--- /dev/null
+++ b/pkg/path/testdata/pathtxtar_test.go
@@ -0,0 +1,25 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package path_test
+
+import (
+ "testing"
+
+ "cuelang.org/go/pkg/internal/builtintest"
+)
+
+func TestBuiltin(t *testing.T) {
+ builtintest.Run("path", t)
+}
diff --git a/pkg/path/testdata/symlink.go b/pkg/path/testdata/symlink.go
deleted file mode 100644
index 6fefd15..0000000
--- a/pkg/path/testdata/symlink.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2012 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
-
-import (
- "errors"
- "io/fs"
- "os"
- "runtime"
- "syscall"
-)
-
-func walkSymlinks(path string) (string, error) {
- volLen := volumeNameLen(path)
- pathSeparator := string(os.PathSeparator)
-
- if volLen < len(path) && os.IsPathSeparator(path[volLen]) {
- volLen++
- }
- vol := path[:volLen]
- dest := vol
- linksWalked := 0
- for start, end := volLen, volLen; start < len(path); start = end {
- for start < len(path) && os.IsPathSeparator(path[start]) {
- start++
- }
- end = start
- for end < len(path) && !os.IsPathSeparator(path[end]) {
- end++
- }
-
- // On Windows, "." can be a symlink.
- // We look it up, and use the value if it is absolute.
- // If not, we just return ".".
- isWindowsDot := runtime.GOOS == "windows" && path[volumeNameLen(path):] == "."
-
- // The next path component is in path[start:end].
- if end == start {
- // No more path components.
- break
- } else if path[start:end] == "." && !isWindowsDot {
- // Ignore path component ".".
- continue
- } else if path[start:end] == ".." {
- // Back up to previous component if possible.
- // Note that volLen includes any leading slash.
-
- // Set r to the index of the last slash in dest,
- // after the volume.
- var r int
- for r = len(dest) - 1; r >= volLen; r-- {
- if os.IsPathSeparator(dest[r]) {
- break
- }
- }
- if r < volLen || dest[r+1:] == ".." {
- // Either path has no slashes
- // (it's empty or just "C:")
- // or it ends in a ".." we had to keep.
- // Either way, keep this "..".
- if len(dest) > volLen {
- dest += pathSeparator
- }
- dest += ".."
- } else {
- // Discard everything since the last slash.
- dest = dest[:r]
- }
- continue
- }
-
- // Ordinary path component. Add it to result.
-
- if len(dest) > volumeNameLen(dest) && !os.IsPathSeparator(dest[len(dest)-1]) {
- dest += pathSeparator
- }
-
- dest += path[start:end]
-
- // Resolve symlink.
-
- fi, err := os.Lstat(dest)
- if err != nil {
- return "", err
- }
-
- if fi.Mode()&fs.ModeSymlink == 0 {
- if !fi.Mode().IsDir() && end < len(path) {
- return "", syscall.ENOTDIR
- }
- continue
- }
-
- // Found symlink.
-
- linksWalked++
- if linksWalked > 255 {
- return "", errors.New("EvalSymlinks: too many links")
- }
-
- link, err := os.Readlink(dest)
- if err != nil {
- return "", err
- }
-
- if isWindowsDot && !IsAbs(link) {
- // On Windows, if "." is a relative symlink,
- // just return ".".
- break
- }
-
- path = link + path[end:]
-
- v := volumeNameLen(link)
- if v > 0 {
- // Symlink to drive name is an absolute path.
- if v < len(link) && os.IsPathSeparator(link[v]) {
- v++
- }
- vol = link[:v]
- dest = vol
- end = len(vol)
- } else if len(link) > 0 && os.IsPathSeparator(link[0]) {
- // Symlink to absolute path.
- dest = link[:1]
- end = 1
- } else {
- // Symlink to relative path; replace last
- // path component in dest.
- var r int
- for r = len(dest) - 1; r >= volLen; r-- {
- if os.IsPathSeparator(dest[r]) {
- break
- }
- }
- if r < volLen {
- dest = vol
- } else {
- dest = dest[:r]
- }
- end = 0
- }
- }
- return Clean(dest), nil
-}
diff --git a/pkg/path/testdata/symlink_unix.go b/pkg/path/testdata/symlink_unix.go
deleted file mode 100644
index d20e63a..0000000
--- a/pkg/path/testdata/symlink_unix.go
+++ /dev/null
@@ -1,7 +0,0 @@
-// +build !windows
-
-package filepath
-
-func evalSymlinks(path string) (string, error) {
- return walkSymlinks(path)
-}
diff --git a/pkg/path/testdata/symlink_windows.go b/pkg/path/testdata/symlink_windows.go
deleted file mode 100644
index d72279e..0000000
--- a/pkg/path/testdata/symlink_windows.go
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright 2012 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
-
-import (
- "strings"
- "syscall"
-)
-
-// normVolumeName is like VolumeName, but makes drive letter upper case.
-// result of EvalSymlinks must be unique, so we have
-// EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`).
-func normVolumeName(path string) string {
- volume := VolumeName(path)
-
- if len(volume) > 2 { // isUNC
- return volume
- }
-
- return strings.ToUpper(volume)
-}
-
-// normBase returns the last element of path with correct case.
-func normBase(path string) (string, error) {
- p, err := syscall.UTF16PtrFromString(path)
- if err != nil {
- return "", err
- }
-
- var data syscall.Win32finddata
-
- h, err := syscall.FindFirstFile(p, &data)
- if err != nil {
- return "", err
- }
- syscall.FindClose(h)
-
- return syscall.UTF16ToString(data.FileName[:]), nil
-}
-
-// baseIsDotDot reports whether the last element of path is "..".
-// The given path should be 'Clean'-ed in advance.
-func baseIsDotDot(path string) bool {
- i := strings.LastIndexByte(path, Separator)
- return path[i+1:] == ".."
-}
-
-// toNorm returns the normalized path that is guaranteed to be unique.
-// It should accept the following formats:
-// * UNC paths (e.g \\server\share\foo\bar)
-// * absolute paths (e.g C:\foo\bar)
-// * relative paths begin with drive letter (e.g C:foo\bar, C:..\foo\bar, C:.., C:.)
-// * relative paths begin with '\' (e.g \foo\bar)
-// * relative paths begin without '\' (e.g foo\bar, ..\foo\bar, .., .)
-// The returned normalized path will be in the same form (of 5 listed above) as the input path.
-// If two paths A and B are indicating the same file with the same format, toNorm(A) should be equal to toNorm(B).
-// The normBase parameter should be equal to the normBase func, except for in tests. See docs on the normBase func.
-func toNorm(path string, normBase func(string) (string, error)) (string, error) {
- if path == "" {
- return path, nil
- }
-
- path = Clean(path)
-
- volume := normVolumeName(path)
- path = path[len(volume):]
-
- // skip special cases
- if path == "" || path == "." || path == `\` {
- return volume + path, nil
- }
-
- var normPath string
-
- for {
- if baseIsDotDot(path) {
- normPath = path + `\` + normPath
-
- break
- }
-
- name, err := normBase(volume + path)
- if err != nil {
- return "", err
- }
-
- normPath = name + `\` + normPath
-
- i := strings.LastIndexByte(path, Separator)
- if i == -1 {
- break
- }
- if i == 0 { // `\Go` or `C:\Go`
- normPath = `\` + normPath
-
- break
- }
-
- path = path[:i]
- }
-
- normPath = normPath[:len(normPath)-1] // remove trailing '\'
-
- return volume + normPath, nil
-}
-
-func evalSymlinks(path string) (string, error) {
- newpath, err := walkSymlinks(path)
- if err != nil {
- return "", err
- }
- newpath, err = toNorm(newpath, normBase)
- if err != nil {
- return "", err
- }
- return newpath, nil
-}