this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

+672 -2711
+55 -42
pkg/path/testdata/example_nix_test.go
··· 1 + // Copyright 2020 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 1 15 // Copyright 2013 The Go Authors. All rights reserved. 2 16 // Use of this source code is governed by a BSD-style 3 17 // license that can be found in the LICENSE file. 4 18 5 - // +build !windows,!plan9 6 - 7 - package filepath_test 19 + package path_test 8 20 9 21 import ( 10 22 "fmt" 11 - "path/filepath" 23 + 24 + "cuelang.org/go/pkg/path" 12 25 ) 13 26 14 27 func ExampleSplitList() { 15 - fmt.Println("On Unix:", filepath.SplitList("/a/b/c:/usr/bin")) 28 + fmt.Println("On Unix:", path.SplitList("/a/b/c:/usr/bin", path.Unix)) 16 29 // Output: 17 30 // On Unix: [/a/b/c /usr/bin] 18 31 } ··· 27 40 28 41 fmt.Println("On Unix:") 29 42 for _, p := range paths { 30 - rel, err := filepath.Rel(base, p) 43 + rel, err := path.Rel(base, p, path.Unix) 31 44 fmt.Printf("%q: %q %v\n", p, rel, err) 32 45 } 33 46 ··· 47 60 } 48 61 fmt.Println("On Unix:") 49 62 for _, p := range paths { 50 - dir, file := filepath.Split(p) 51 - fmt.Printf("input: %q\n\tdir: %q\n\tfile: %q\n", p, dir, file) 63 + pair := path.Split(p, path.Unix) 64 + fmt.Printf("input: %q\n\tdir: %q\n\tfile: %q\n", p, pair[0], pair[1]) 52 65 } 53 66 // Output: 54 67 // On Unix: ··· 68 81 69 82 func ExampleJoin() { 70 83 fmt.Println("On Unix:") 71 - fmt.Println(filepath.Join("a", "b", "c")) 72 - fmt.Println(filepath.Join("a", "b/c")) 73 - fmt.Println(filepath.Join("a/b", "c")) 74 - fmt.Println(filepath.Join("a/b", "/c")) 84 + fmt.Println(path.Join([]string{"a", "b", "c"}, path.Unix)) 85 + fmt.Println(path.Join([]string{"a", "b/c"}, path.Unix)) 86 + fmt.Println(path.Join([]string{"a/b", "c"}, path.Unix)) 87 + fmt.Println(path.Join([]string{"a/b", "/c"}, path.Unix)) 75 88 76 - fmt.Println(filepath.Join("a/b", "../../../xyz")) 89 + fmt.Println(path.Join([]string{"a/b", "../../../xyz"}, path.Unix)) 77 90 78 91 // Output: 79 92 // On Unix: ··· 86 99 87 100 func ExampleMatch() { 88 101 fmt.Println("On Unix:") 89 - fmt.Println(filepath.Match("/home/catch/*", "/home/catch/foo")) 90 - fmt.Println(filepath.Match("/home/catch/*", "/home/catch/foo/bar")) 91 - fmt.Println(filepath.Match("/home/?opher", "/home/gopher")) 92 - fmt.Println(filepath.Match("/home/\\*", "/home/*")) 102 + fmt.Println(path.Match("/home/catch/*", "/home/catch/foo", path.Unix)) 103 + fmt.Println(path.Match("/home/catch/*", "/home/catch/foo/bar", path.Unix)) 104 + fmt.Println(path.Match("/home/?opher", "/home/gopher", path.Unix)) 105 + fmt.Println(path.Match("/home/\\*", "/home/*", path.Unix)) 93 106 94 107 // Output: 95 108 // On Unix: ··· 101 114 102 115 func ExampleBase() { 103 116 fmt.Println("On Unix:") 104 - fmt.Println(filepath.Base("/foo/bar/baz.js")) 105 - fmt.Println(filepath.Base("/foo/bar/baz")) 106 - fmt.Println(filepath.Base("/foo/bar/baz/")) 107 - fmt.Println(filepath.Base("dev.txt")) 108 - fmt.Println(filepath.Base("../todo.txt")) 109 - fmt.Println(filepath.Base("..")) 110 - fmt.Println(filepath.Base(".")) 111 - fmt.Println(filepath.Base("/")) 112 - fmt.Println(filepath.Base("")) 117 + fmt.Println(path.Base("/foo/bar/baz.js", path.Unix)) 118 + fmt.Println(path.Base("/foo/bar/baz", path.Unix)) 119 + fmt.Println(path.Base("/foo/bar/baz/", path.Unix)) 120 + fmt.Println(path.Base("dev.txt", path.Unix)) 121 + fmt.Println(path.Base("../todo.txt", path.Unix)) 122 + fmt.Println(path.Base("..", path.Unix)) 123 + fmt.Println(path.Base(".", path.Unix)) 124 + fmt.Println(path.Base("/", path.Unix)) 125 + fmt.Println(path.Base("", path.Unix)) 113 126 114 127 // Output: 115 128 // On Unix: ··· 126 139 127 140 func ExampleDir() { 128 141 fmt.Println("On Unix:") 129 - fmt.Println(filepath.Dir("/foo/bar/baz.js")) 130 - fmt.Println(filepath.Dir("/foo/bar/baz")) 131 - fmt.Println(filepath.Dir("/foo/bar/baz/")) 132 - fmt.Println(filepath.Dir("/dirty//path///")) 133 - fmt.Println(filepath.Dir("dev.txt")) 134 - fmt.Println(filepath.Dir("../todo.txt")) 135 - fmt.Println(filepath.Dir("..")) 136 - fmt.Println(filepath.Dir(".")) 137 - fmt.Println(filepath.Dir("/")) 138 - fmt.Println(filepath.Dir("")) 142 + fmt.Println(path.Dir("/foo/bar/baz.js", path.Unix)) 143 + fmt.Println(path.Dir("/foo/bar/baz", path.Unix)) 144 + fmt.Println(path.Dir("/foo/bar/baz/", path.Unix)) 145 + fmt.Println(path.Dir("/dirty//path///", path.Unix)) 146 + fmt.Println(path.Dir("dev.txt", path.Unix)) 147 + fmt.Println(path.Dir("../todo.txt", path.Unix)) 148 + fmt.Println(path.Dir("..", path.Unix)) 149 + fmt.Println(path.Dir(".", path.Unix)) 150 + fmt.Println(path.Dir("/", path.Unix)) 151 + fmt.Println(path.Dir("", path.Unix)) 139 152 140 153 // Output: 141 154 // On Unix: ··· 153 166 154 167 func ExampleIsAbs() { 155 168 fmt.Println("On Unix:") 156 - fmt.Println(filepath.IsAbs("/home/gopher")) 157 - fmt.Println(filepath.IsAbs(".bashrc")) 158 - fmt.Println(filepath.IsAbs("..")) 159 - fmt.Println(filepath.IsAbs(".")) 160 - fmt.Println(filepath.IsAbs("/")) 161 - fmt.Println(filepath.IsAbs("")) 169 + fmt.Println(path.IsAbs("/home/gopher", path.Unix)) 170 + fmt.Println(path.IsAbs(".bashrc", path.Unix)) 171 + fmt.Println(path.IsAbs("..", path.Unix)) 172 + fmt.Println(path.IsAbs(".", path.Unix)) 173 + fmt.Println(path.IsAbs("/", path.Unix)) 174 + fmt.Println(path.IsAbs("", path.Unix)) 162 175 163 176 // Output: 164 177 // On Unix:
+20 -5
pkg/path/testdata/example_test.go
··· 1 + // Copyright 2020 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 1 15 // Copyright 2017 The Go Authors. All rights reserved. 2 16 // Use of this source code is governed by a BSD-style 3 17 // license that can be found in the LICENSE file. 4 18 5 - package filepath_test 19 + package path_test 6 20 7 21 import ( 8 22 "fmt" 9 - "path/filepath" 23 + 24 + "cuelang.org/go/pkg/path" 10 25 ) 11 26 12 27 func ExampleExt() { 13 - fmt.Printf("No dots: %q\n", filepath.Ext("index")) 14 - fmt.Printf("One dot: %q\n", filepath.Ext("index.js")) 15 - fmt.Printf("Two dots: %q\n", filepath.Ext("main.test.js")) 28 + fmt.Printf("No dots: %q\n", path.Ext("index", "unix")) 29 + fmt.Printf("One dot: %q\n", path.Ext("index.js", "unix")) 30 + fmt.Printf("Two dots: %q\n", path.Ext("main.test.js", "unix")) 16 31 // Output: 17 32 // No dots: "" 18 33 // One dot: ".js"
-67
pkg/path/testdata/example_unix_walk_test.go
··· 1 - // Copyright 2018 The Go Authors. All rights reserved. 2 - // Use of this source code is governed by a BSD-style 3 - // license that can be found in the LICENSE file. 4 - 5 - // +build !windows,!plan9 6 - 7 - package filepath_test 8 - 9 - import ( 10 - "fmt" 11 - "io/fs" 12 - "io/ioutil" 13 - "os" 14 - "path/filepath" 15 - ) 16 - 17 - func prepareTestDirTree(tree string) (string, error) { 18 - tmpDir, err := ioutil.TempDir("", "") 19 - if err != nil { 20 - return "", fmt.Errorf("error creating temp directory: %v\n", err) 21 - } 22 - 23 - err = os.MkdirAll(filepath.Join(tmpDir, tree), 0755) 24 - if err != nil { 25 - os.RemoveAll(tmpDir) 26 - return "", err 27 - } 28 - 29 - return tmpDir, nil 30 - } 31 - 32 - func ExampleWalk() { 33 - tmpDir, err := prepareTestDirTree("dir/to/walk/skip") 34 - if err != nil { 35 - fmt.Printf("unable to create test dir tree: %v\n", err) 36 - return 37 - } 38 - defer os.RemoveAll(tmpDir) 39 - os.Chdir(tmpDir) 40 - 41 - subDirToSkip := "skip" 42 - 43 - fmt.Println("On Unix:") 44 - err = filepath.Walk(".", func(path string, info fs.FileInfo, err error) error { 45 - if err != nil { 46 - fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", path, err) 47 - return err 48 - } 49 - if info.IsDir() && info.Name() == subDirToSkip { 50 - fmt.Printf("skipping a dir without errors: %+v \n", info.Name()) 51 - return filepath.SkipDir 52 - } 53 - fmt.Printf("visited file or dir: %q\n", path) 54 - return nil 55 - }) 56 - if err != nil { 57 - fmt.Printf("error walking the path %q: %v\n", tmpDir, err) 58 - return 59 - } 60 - // Output: 61 - // On Unix: 62 - // visited file or dir: "." 63 - // visited file or dir: "dir" 64 - // visited file or dir: "dir/to" 65 - // visited file or dir: "dir/to/walk" 66 - // skipping a dir without errors: skip 67 - }
-7
pkg/path/testdata/export_test.go
··· 1 - // Copyright 2013 The Go Authors. All rights reserved. 2 - // Use of this source code is governed by a BSD-style 3 - // license that can be found in the LICENSE file. 4 - 5 - package filepath 6 - 7 - var LstatP = &lstat
-10
pkg/path/testdata/export_windows_test.go
··· 1 - // Copyright 2016 The Go Authors. All rights reserved. 2 - // Use of this source code is governed by a BSD-style 3 - // license that can be found in the LICENSE file. 4 - 5 - package filepath 6 - 7 - var ( 8 - ToNorm = toNorm 9 - NormBase = normBase 10 - )
+31 -146
pkg/path/testdata/match.go
··· 1 + // Copyright 2020 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 1 15 // Copyright 2010 The Go Authors. All rights reserved. 2 16 // Use of this source code is governed by a BSD-style 3 17 // license that can be found in the LICENSE file. 4 18 5 - package filepath 19 + package path 6 20 7 21 import ( 8 22 "errors" 9 - "os" 10 - "runtime" 11 - "sort" 12 23 "strings" 13 24 "unicode/utf8" 14 25 ) ··· 41 52 // On Windows, escaping is disabled. Instead, '\\' is treated as 42 53 // path separator. 43 54 // 44 - func Match(pattern, name string) (matched bool, err error) { 55 + func Match(pattern, name string, o OS) (matched bool, err error) { 56 + os := getOS(o) 45 57 Pattern: 46 58 for len(pattern) > 0 { 47 59 var star bool 48 60 var chunk string 49 - star, chunk, pattern = scanChunk(pattern) 61 + star, chunk, pattern = scanChunk(pattern, os) 50 62 if star && chunk == "" { 51 63 // Trailing * matches rest of string unless it has a /. 52 - return !strings.Contains(name, string(Separator)), nil 64 + return !strings.Contains(name, string(os.Separator)), nil 53 65 } 54 66 // Look for match at current position. 55 - t, ok, err := matchChunk(chunk, name) 67 + t, ok, err := matchChunk(chunk, name, os) 56 68 // if we're the last chunk, make sure we've exhausted the name 57 69 // otherwise we'll give a false result even if we could still match 58 70 // using the star ··· 66 78 if star { 67 79 // Look for match skipping i+1 bytes. 68 80 // Cannot skip /. 69 - for i := 0; i < len(name) && name[i] != Separator; i++ { 70 - t, ok, err := matchChunk(chunk, name[i+1:]) 81 + for i := 0; i < len(name) && name[i] != os.Separator; i++ { 82 + t, ok, err := matchChunk(chunk, name[i+1:], os) 71 83 if ok { 72 84 // if we're the last chunk, make sure we exhausted the name 73 85 if len(pattern) == 0 && len(t) > 0 { ··· 88 100 89 101 // scanChunk gets the next segment of pattern, which is a non-star string 90 102 // possibly preceded by a star. 91 - func scanChunk(pattern string) (star bool, chunk, rest string) { 103 + func scanChunk(pattern string, os os) (star bool, chunk, rest string) { 92 104 for len(pattern) > 0 && pattern[0] == '*' { 93 105 pattern = pattern[1:] 94 106 star = true ··· 99 111 for i = 0; i < len(pattern); i++ { 100 112 switch pattern[i] { 101 113 case '\\': 102 - if runtime.GOOS != "windows" { 114 + if !os.isWindows() { 103 115 // error check handled in matchChunk: bad pattern. 104 116 if i+1 < len(pattern) { 105 117 i++ ··· 121 133 // matchChunk checks whether chunk matches the beginning of s. 122 134 // If so, it returns the remainder of s (after the match). 123 135 // Chunk is all single-character operators: literals, char classes, and ?. 124 - func matchChunk(chunk, s string) (rest string, ok bool, err error) { 136 + func matchChunk(chunk, s string, os os) (rest string, ok bool, err error) { 125 137 // failed records whether the match has failed. 126 138 // After the match fails, the loop continues on processing chunk, 127 139 // checking that the pattern is well-formed but no longer reading s. ··· 155 167 break 156 168 } 157 169 var lo, hi rune 158 - if lo, chunk, err = getEsc(chunk); err != nil { 170 + if lo, chunk, err = getEsc(chunk, os); err != nil { 159 171 return "", false, err 160 172 } 161 173 hi = lo 162 174 if chunk[0] == '-' { 163 - if hi, chunk, err = getEsc(chunk[1:]); err != nil { 175 + if hi, chunk, err = getEsc(chunk[1:], os); err != nil { 164 176 return "", false, err 165 177 } 166 178 } ··· 175 187 176 188 case '?': 177 189 if !failed { 178 - if s[0] == Separator { 190 + if s[0] == os.Separator { 179 191 failed = true 180 192 } 181 193 _, n := utf8.DecodeRuneInString(s) ··· 184 196 chunk = chunk[1:] 185 197 186 198 case '\\': 187 - if runtime.GOOS != "windows" { 199 + if !os.isWindows() { 188 200 chunk = chunk[1:] 189 201 if len(chunk) == 0 { 190 202 return "", false, ErrBadPattern ··· 209 221 } 210 222 211 223 // getEsc gets a possibly-escaped character from chunk, for a character class. 212 - func getEsc(chunk string) (r rune, nchunk string, err error) { 224 + func getEsc(chunk string, os os) (r rune, nchunk string, err error) { 213 225 if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { 214 226 err = ErrBadPattern 215 227 return 216 228 } 217 - if chunk[0] == '\\' && runtime.GOOS != "windows" { 229 + if chunk[0] == '\\' && !os.isWindows() { 218 230 chunk = chunk[1:] 219 231 if len(chunk) == 0 { 220 232 err = ErrBadPattern ··· 231 243 } 232 244 return 233 245 } 234 - 235 - // Glob returns the names of all files matching pattern or nil 236 - // if there is no matching file. The syntax of patterns is the same 237 - // as in Match. The pattern may describe hierarchical names such as 238 - // /usr/*/bin/ed (assuming the Separator is '/'). 239 - // 240 - // Glob ignores file system errors such as I/O errors reading directories. 241 - // The only possible returned error is ErrBadPattern, when pattern 242 - // is malformed. 243 - func Glob(pattern string) (matches []string, err error) { 244 - // Check pattern is well-formed. 245 - if _, err := Match(pattern, ""); err != nil { 246 - return nil, err 247 - } 248 - if !hasMeta(pattern) { 249 - if _, err = os.Lstat(pattern); err != nil { 250 - return nil, nil 251 - } 252 - return []string{pattern}, nil 253 - } 254 - 255 - dir, file := Split(pattern) 256 - volumeLen := 0 257 - if runtime.GOOS == "windows" { 258 - volumeLen, dir = cleanGlobPathWindows(dir) 259 - } else { 260 - dir = cleanGlobPath(dir) 261 - } 262 - 263 - if !hasMeta(dir[volumeLen:]) { 264 - return glob(dir, file, nil) 265 - } 266 - 267 - // Prevent infinite recursion. See issue 15879. 268 - if dir == pattern { 269 - return nil, ErrBadPattern 270 - } 271 - 272 - var m []string 273 - m, err = Glob(dir) 274 - if err != nil { 275 - return 276 - } 277 - for _, d := range m { 278 - matches, err = glob(d, file, matches) 279 - if err != nil { 280 - return 281 - } 282 - } 283 - return 284 - } 285 - 286 - // cleanGlobPath prepares path for glob matching. 287 - func cleanGlobPath(path string) string { 288 - switch path { 289 - case "": 290 - return "." 291 - case string(Separator): 292 - // do nothing to the path 293 - return path 294 - default: 295 - return path[0 : len(path)-1] // chop off trailing separator 296 - } 297 - } 298 - 299 - // cleanGlobPathWindows is windows version of cleanGlobPath. 300 - func cleanGlobPathWindows(path string) (prefixLen int, cleaned string) { 301 - vollen := volumeNameLen(path) 302 - switch { 303 - case path == "": 304 - return 0, "." 305 - case vollen+1 == len(path) && os.IsPathSeparator(path[len(path)-1]): // /, \, C:\ and C:/ 306 - // do nothing to the path 307 - return vollen + 1, path 308 - case vollen == len(path) && len(path) == 2: // C: 309 - return vollen, path + "." // convert C: into C:. 310 - default: 311 - if vollen >= len(path) { 312 - vollen = len(path) - 1 313 - } 314 - return vollen, path[0 : len(path)-1] // chop off trailing separator 315 - } 316 - } 317 - 318 - // glob searches for files matching pattern in the directory dir 319 - // and appends them to matches. If the directory cannot be 320 - // opened, it returns the existing matches. New matches are 321 - // added in lexicographical order. 322 - func glob(dir, pattern string, matches []string) (m []string, e error) { 323 - m = matches 324 - fi, err := os.Stat(dir) 325 - if err != nil { 326 - return // ignore I/O error 327 - } 328 - if !fi.IsDir() { 329 - return // ignore I/O error 330 - } 331 - d, err := os.Open(dir) 332 - if err != nil { 333 - return // ignore I/O error 334 - } 335 - defer d.Close() 336 - 337 - names, _ := d.Readdirnames(-1) 338 - sort.Strings(names) 339 - 340 - for _, n := range names { 341 - matched, err := Match(pattern, n) 342 - if err != nil { 343 - return m, err 344 - } 345 - if matched { 346 - m = append(m, Join(dir, n)) 347 - } 348 - } 349 - return 350 - } 351 - 352 - // hasMeta reports whether path contains any of the magic characters 353 - // recognized by Match. 354 - func hasMeta(path string) bool { 355 - magicChars := `*?[` 356 - if runtime.GOOS != "windows" { 357 - magicChars = `*?[\` 358 - } 359 - return strings.ContainsAny(path, magicChars) 360 - }
+31 -306
pkg/path/testdata/match_test.go
··· 1 + // Copyright 2020 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 1 15 // Copyright 2009 The Go Authors. All rights reserved. 2 16 // Use of this source code is governed by a BSD-style 3 17 // license that can be found in the LICENSE file. 4 18 5 - package filepath_test 19 + package path 6 20 7 21 import ( 8 - "fmt" 9 - "internal/testenv" 10 - "io/ioutil" 11 - "os" 12 - . "path/filepath" 13 - "reflect" 14 - "runtime" 15 - "sort" 16 22 "strings" 17 23 "testing" 18 24 ) ··· 90 96 } 91 97 92 98 func TestMatch(t *testing.T) { 93 - for _, tt := range matchTests { 94 - pattern := tt.pattern 95 - s := tt.s 96 - if runtime.GOOS == "windows" { 97 - if strings.Contains(pattern, "\\") { 98 - // no escape allowed on windows. 99 - continue 99 + for _, os := range []OS{Unix, Windows, Plan9} { 100 + for _, tt := range matchTests { 101 + pattern := tt.pattern 102 + s := tt.s 103 + if os == Windows { 104 + if strings.Contains(pattern, "\\") { 105 + // no escape allowed on windows. 106 + continue 107 + } 108 + pattern = Clean(pattern, os) 109 + s = Clean(s, os) 110 + } 111 + ok, err := Match(pattern, s, os) 112 + if ok != tt.match || err != tt.err { 113 + t.Errorf("Match(%#q, %#q, %q) = %v, %q want %v, %q", 114 + pattern, s, os, ok, errp(err), tt.match, errp(tt.err)) 100 115 } 101 - pattern = Clean(pattern) 102 - s = Clean(s) 103 116 } 104 - ok, err := Match(pattern, s) 105 - if ok != tt.match || err != tt.err { 106 - t.Errorf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err)) 107 - } 108 - } 109 - } 110 - 111 - // contains reports whether vector contains the string s. 112 - func contains(vector []string, s string) bool { 113 - for _, elem := range vector { 114 - if elem == s { 115 - return true 116 - } 117 - } 118 - return false 119 - } 120 - 121 - var globTests = []struct { 122 - pattern, result string 123 - }{ 124 - {"match.go", "match.go"}, 125 - {"mat?h.go", "match.go"}, 126 - {"*", "match.go"}, 127 - {"../*/match.go", "../filepath/match.go"}, 128 - } 129 - 130 - func TestGlob(t *testing.T) { 131 - for _, tt := range globTests { 132 - pattern := tt.pattern 133 - result := tt.result 134 - if runtime.GOOS == "windows" { 135 - pattern = Clean(pattern) 136 - result = Clean(result) 137 - } 138 - matches, err := Glob(pattern) 139 - if err != nil { 140 - t.Errorf("Glob error for %q: %s", pattern, err) 141 - continue 142 - } 143 - if !contains(matches, result) { 144 - t.Errorf("Glob(%#q) = %#v want %v", pattern, matches, result) 145 - } 146 - } 147 - for _, pattern := range []string{"no_match", "../*/no_match"} { 148 - matches, err := Glob(pattern) 149 - if err != nil { 150 - t.Errorf("Glob error for %q: %s", pattern, err) 151 - continue 152 - } 153 - if len(matches) != 0 { 154 - t.Errorf("Glob(%#q) = %#v want []", pattern, matches) 155 - } 156 - } 157 - } 158 - 159 - func TestGlobError(t *testing.T) { 160 - bad := []string{`[]`, `nonexist/[]`} 161 - for _, pattern := range bad { 162 - if _, err := Glob(pattern); err != ErrBadPattern { 163 - t.Errorf("Glob(%#q) returned err=%v, want ErrBadPattern", pattern, err) 164 - } 165 - } 166 - } 167 - 168 - func TestGlobUNC(t *testing.T) { 169 - // Just make sure this runs without crashing for now. 170 - // See issue 15879. 171 - Glob(`\\?\C:\*`) 172 - } 173 - 174 - var globSymlinkTests = []struct { 175 - path, dest string 176 - brokenLink bool 177 - }{ 178 - {"test1", "link1", false}, 179 - {"test2", "link2", true}, 180 - } 181 - 182 - func TestGlobSymlink(t *testing.T) { 183 - testenv.MustHaveSymlink(t) 184 - 185 - tmpDir, err := ioutil.TempDir("", "globsymlink") 186 - if err != nil { 187 - t.Fatal("creating temp dir:", err) 188 - } 189 - defer os.RemoveAll(tmpDir) 190 - 191 - for _, tt := range globSymlinkTests { 192 - path := Join(tmpDir, tt.path) 193 - dest := Join(tmpDir, tt.dest) 194 - f, err := os.Create(path) 195 - if err != nil { 196 - t.Fatal(err) 197 - } 198 - if err := f.Close(); err != nil { 199 - t.Fatal(err) 200 - } 201 - err = os.Symlink(path, dest) 202 - if err != nil { 203 - t.Fatal(err) 204 - } 205 - if tt.brokenLink { 206 - // Break the symlink. 207 - os.Remove(path) 208 - } 209 - matches, err := Glob(dest) 210 - if err != nil { 211 - t.Errorf("GlobSymlink error for %q: %s", dest, err) 212 - } 213 - if !contains(matches, dest) { 214 - t.Errorf("Glob(%#q) = %#v want %v", dest, matches, dest) 215 - } 216 - } 217 - } 218 - 219 - type globTest struct { 220 - pattern string 221 - matches []string 222 - } 223 - 224 - func (test *globTest) buildWant(root string) []string { 225 - want := make([]string, 0) 226 - for _, m := range test.matches { 227 - want = append(want, root+FromSlash(m)) 228 - } 229 - sort.Strings(want) 230 - return want 231 - } 232 - 233 - func (test *globTest) globAbs(root, rootPattern string) error { 234 - p := FromSlash(rootPattern + `\` + test.pattern) 235 - have, err := Glob(p) 236 - if err != nil { 237 - return err 238 - } 239 - sort.Strings(have) 240 - want := test.buildWant(root + `\`) 241 - if strings.Join(want, "_") == strings.Join(have, "_") { 242 - return nil 243 - } 244 - return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want) 245 - } 246 - 247 - func (test *globTest) globRel(root string) error { 248 - p := root + FromSlash(test.pattern) 249 - have, err := Glob(p) 250 - if err != nil { 251 - return err 252 - } 253 - sort.Strings(have) 254 - want := test.buildWant(root) 255 - if strings.Join(want, "_") == strings.Join(have, "_") { 256 - return nil 257 - } 258 - // try also matching version without root prefix 259 - wantWithNoRoot := test.buildWant("") 260 - if strings.Join(wantWithNoRoot, "_") == strings.Join(have, "_") { 261 - return nil 262 - } 263 - return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want) 264 - } 265 - 266 - func TestWindowsGlob(t *testing.T) { 267 - if runtime.GOOS != "windows" { 268 - t.Skipf("skipping windows specific test") 269 - } 270 - 271 - tmpDir, err := ioutil.TempDir("", "TestWindowsGlob") 272 - if err != nil { 273 - t.Fatal(err) 274 - } 275 - defer os.RemoveAll(tmpDir) 276 - 277 - // /tmp may itself be a symlink 278 - tmpDir, err = EvalSymlinks(tmpDir) 279 - if err != nil { 280 - t.Fatal("eval symlink for tmp dir:", err) 281 - } 282 - 283 - if len(tmpDir) < 3 { 284 - t.Fatalf("tmpDir path %q is too short", tmpDir) 285 - } 286 - if tmpDir[1] != ':' { 287 - t.Fatalf("tmpDir path %q must have drive letter in it", tmpDir) 288 - } 289 - 290 - dirs := []string{ 291 - "a", 292 - "b", 293 - "dir/d/bin", 294 - } 295 - files := []string{ 296 - "dir/d/bin/git.exe", 297 - } 298 - for _, dir := range dirs { 299 - err := os.MkdirAll(Join(tmpDir, dir), 0777) 300 - if err != nil { 301 - t.Fatal(err) 302 - } 303 - } 304 - for _, file := range files { 305 - err := ioutil.WriteFile(Join(tmpDir, file), nil, 0666) 306 - if err != nil { 307 - t.Fatal(err) 308 - } 309 - } 310 - 311 - tests := []globTest{ 312 - {"a", []string{"a"}}, 313 - {"b", []string{"b"}}, 314 - {"c", []string{}}, 315 - {"*", []string{"a", "b", "dir"}}, 316 - {"d*", []string{"dir"}}, 317 - {"*i*", []string{"dir"}}, 318 - {"*r", []string{"dir"}}, 319 - {"?ir", []string{"dir"}}, 320 - {"?r", []string{}}, 321 - {"d*/*/bin/git.exe", []string{"dir/d/bin/git.exe"}}, 322 - } 323 - 324 - // test absolute paths 325 - for _, test := range tests { 326 - var p string 327 - err = test.globAbs(tmpDir, tmpDir) 328 - if err != nil { 329 - t.Error(err) 330 - } 331 - // test C:\*Documents and Settings\... 332 - p = tmpDir 333 - p = strings.Replace(p, `:\`, `:\*`, 1) 334 - err = test.globAbs(tmpDir, p) 335 - if err != nil { 336 - t.Error(err) 337 - } 338 - // test C:\Documents and Settings*\... 339 - p = tmpDir 340 - p = strings.Replace(p, `:\`, `:`, 1) 341 - p = strings.Replace(p, `\`, `*\`, 1) 342 - p = strings.Replace(p, `:`, `:\`, 1) 343 - err = test.globAbs(tmpDir, p) 344 - if err != nil { 345 - t.Error(err) 346 - } 347 - } 348 - 349 - // test relative paths 350 - wd, err := os.Getwd() 351 - if err != nil { 352 - t.Fatal(err) 353 - } 354 - err = os.Chdir(tmpDir) 355 - if err != nil { 356 - t.Fatal(err) 357 - } 358 - defer func() { 359 - err := os.Chdir(wd) 360 - if err != nil { 361 - t.Fatal(err) 362 - } 363 - }() 364 - for _, test := range tests { 365 - err := test.globRel("") 366 - if err != nil { 367 - t.Error(err) 368 - } 369 - err = test.globRel(`.\`) 370 - if err != nil { 371 - t.Error(err) 372 - } 373 - err = test.globRel(tmpDir[:2]) // C: 374 - if err != nil { 375 - t.Error(err) 376 - } 377 - } 378 - } 379 - 380 - func TestNonWindowsGlobEscape(t *testing.T) { 381 - if runtime.GOOS == "windows" { 382 - t.Skipf("skipping non-windows specific test") 383 - } 384 - pattern := `\match.go` 385 - want := []string{"match.go"} 386 - matches, err := Glob(pattern) 387 - if err != nil { 388 - t.Fatalf("Glob error for %q: %s", pattern, err) 389 - } 390 - if !reflect.DeepEqual(matches, want) { 391 - t.Fatalf("Glob(%#q) = %v want %v", pattern, matches, want) 392 117 } 393 118 }
+75
pkg/path/testdata/os.go
··· 1 + // Copyright 2020 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package path 16 + 17 + type OS string 18 + 19 + const ( 20 + Unix OS = "unix" 21 + Windows OS = "windows" 22 + Plan9 OS = "plan9" 23 + ) 24 + 25 + // These types have been designed to minimize the diffs with the original Go 26 + // code, thereby minimizing potential toil in keeping them up to date. 27 + 28 + type os struct { 29 + osInfo 30 + Separator byte 31 + ListSeparator byte 32 + } 33 + 34 + func (o os) isWindows() bool { 35 + return o.Separator == '\\' 36 + } 37 + 38 + type osInfo interface { 39 + IsPathSeparator(b byte) bool 40 + splitList(path string) []string 41 + volumeNameLen(path string) int 42 + IsAbs(path string) (b bool) 43 + HasPrefix(p, prefix string) bool 44 + join(elem []string) string 45 + sameWord(a, b string) bool 46 + } 47 + 48 + func getOS(o OS) os { 49 + switch o { 50 + case Windows: 51 + return windows 52 + case Plan9: 53 + return plan9 54 + default: 55 + return unix 56 + } 57 + } 58 + 59 + var ( 60 + plan9 = os{ 61 + osInfo: &plan9Info{}, 62 + Separator: plan9Separator, 63 + ListSeparator: plan9ListSeparator, 64 + } 65 + unix = os{ 66 + osInfo: &unixInfo{}, 67 + Separator: unixSeparator, 68 + ListSeparator: unixListSeparator, 69 + } 70 + windows = os{ 71 + osInfo: &windowsInfo{}, 72 + Separator: windowsSeparator, 73 + ListSeparator: windowsListSeparator, 74 + } 75 + )
+116 -298
pkg/path/testdata/path.go
··· 1 + // Copyright 2020 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 1 15 // Copyright 2009 The Go Authors. All rights reserved. 2 16 // Use of this source code is governed by a BSD-style 3 17 // license that can be found in the LICENSE file. ··· 9 23 // depending on the operating system. To process paths such as URLs 10 24 // that always use forward slashes regardless of the operating 11 25 // system, see the path package. 12 - package filepath 26 + package path 13 27 14 28 import ( 15 29 "errors" 16 - "io/fs" 17 - "os" 18 - "sort" 19 30 "strings" 20 31 ) 21 32 ··· 58 69 return b.volAndPath[:b.volLen] + string(b.buf[:b.w]) 59 70 } 60 71 61 - const ( 62 - Separator = os.PathSeparator 63 - ListSeparator = os.PathListSeparator 64 - ) 72 + // const ( 73 + // Separator = os.PathSeparator 74 + // ListSeparator = os.PathListSeparator 75 + // ) 65 76 66 77 // Clean returns the shortest path name equivalent to path 67 - // by purely lexical processing. It applies the following rules 78 + // by purely lexical processing. The default value for os is Unix. 79 + // It applies the following rules 68 80 // iteratively until no further processing can be done: 69 81 // 70 82 // 1. Replace multiple Separator elements with a single one. ··· 86 98 // See also Rob Pike, ``Lexical File Names in Plan 9 or 87 99 // Getting Dot-Dot Right,'' 88 100 // https://9p.io/sys/doc/lexnames.html 89 - func Clean(path string) string { 101 + func Clean(path string, os OS) string { 102 + return clean(path, getOS(os)) 103 + } 104 + 105 + func clean(path string, os os) string { 90 106 originalPath := path 91 - volLen := volumeNameLen(path) 107 + volLen := os.volumeNameLen(path) 92 108 path = path[volLen:] 93 109 if path == "" { 94 110 if volLen > 1 && originalPath[1] != ':' { 95 111 // should be UNC 96 - return FromSlash(originalPath) 112 + return fromSlash(originalPath, os) 97 113 } 98 114 return originalPath + "." 99 115 } ··· 108 124 out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen} 109 125 r, dotdot := 0, 0 110 126 if rooted { 111 - out.append(Separator) 127 + out.append(os.Separator) 112 128 r, dotdot = 1, 1 113 129 } 114 130 ··· 133 149 case !rooted: 134 150 // cannot backtrack, but not rooted, so append .. element. 135 151 if out.w > 0 { 136 - out.append(Separator) 152 + out.append(os.Separator) 137 153 } 138 154 out.append('.') 139 155 out.append('.') ··· 143 159 // real path element. 144 160 // add slash if needed 145 161 if rooted && out.w != 1 || !rooted && out.w != 0 { 146 - out.append(Separator) 162 + out.append(os.Separator) 147 163 } 148 164 // copy element 149 165 for ; r < n && !os.IsPathSeparator(path[r]); r++ { ··· 157 173 out.append('.') 158 174 } 159 175 160 - return FromSlash(out.string()) 176 + return fromSlash(out.string(), os) 161 177 } 162 178 163 179 // ToSlash returns the result of replacing each separator character 164 180 // in path with a slash ('/') character. Multiple separators are 165 181 // replaced by multiple slashes. 166 - func ToSlash(path string) string { 167 - if Separator == '/' { 182 + func ToSlash(path string, os OS) string { 183 + return toSlash(path, getOS(os)) 184 + } 185 + 186 + func toSlash(path string, os os) string { 187 + if os.Separator == '/' { 168 188 return path 169 189 } 170 - return strings.ReplaceAll(path, string(Separator), "/") 190 + return strings.ReplaceAll(path, string(os.Separator), "/") 171 191 } 172 192 173 193 // FromSlash returns the result of replacing each slash ('/') character 174 194 // in path with a separator character. Multiple slashes are replaced 175 195 // by multiple separators. 176 - func FromSlash(path string) string { 177 - if Separator == '/' { 196 + func FromSlash(path string, os OS) string { 197 + return fromSlash(path, getOS(os)) 198 + } 199 + 200 + func fromSlash(path string, os os) string { 201 + if os.Separator == '/' { 178 202 return path 179 203 } 180 - return strings.ReplaceAll(path, "/", string(Separator)) 204 + return strings.ReplaceAll(path, "/", string(os.Separator)) 181 205 } 182 206 183 207 // SplitList splits a list of paths joined by the OS-specific ListSeparator, 184 208 // usually found in PATH or GOPATH environment variables. 185 209 // Unlike strings.Split, SplitList returns an empty slice when passed an empty 186 210 // string. 187 - func SplitList(path string) []string { 188 - return splitList(path) 211 + func SplitList(path string, os OS) []string { 212 + return getOS(os).splitList(path) 189 213 } 190 214 191 - // Split splits path immediately following the final Separator, 192 - // separating it into a directory and file name component. 193 - // If there is no Separator in path, Split returns an empty dir 194 - // and file set to path. 195 - // The returned values have the property that path = dir+file. 196 - func Split(path string) (dir, file string) { 197 - vol := VolumeName(path) 215 + // Split splits path immediately following the final slash and returns them as 216 + // the list [dir, file], separating it into a directory and file name component. 217 + // If there is no slash in path, Split returns an empty dir and file set to 218 + // path. The returned values have the property that path = dir+file. 219 + // The default value for os is Unix. 220 + func Split(path string, os OS) []string { 221 + x := getOS(os) 222 + vol := volumeName(path, x) 198 223 i := len(path) - 1 199 - for i >= len(vol) && !os.IsPathSeparator(path[i]) { 224 + for i >= len(vol) && !x.IsPathSeparator(path[i]) { 200 225 i-- 201 226 } 202 - return path[:i+1], path[i+1:] 227 + return []string{path[:i+1], path[i+1:]} 203 228 } 204 229 205 230 // Join joins any number of path elements into a single path, ··· 209 234 // an empty string. 210 235 // On Windows, the result will only be a UNC path if the first 211 236 // non-empty element is a UNC path. 212 - func Join(elem ...string) string { 213 - return join(elem) 237 + // The default value for os is Unix. 238 + func Join(elem []string, os OS) string { 239 + return getOS(os).join(elem) 214 240 } 215 241 216 242 // Ext returns the file name extension used by path. 217 243 // The extension is the suffix beginning at the final dot 218 244 // in the final element of path; it is empty if there is 219 - // no dot. 220 - func Ext(path string) string { 221 - for i := len(path) - 1; i >= 0 && !os.IsPathSeparator(path[i]); i-- { 245 + // no dot. The default value for os is Unix. 246 + func Ext(path string, os OS) string { 247 + x := getOS(os) 248 + for i := len(path) - 1; i >= 0 && !x.IsPathSeparator(path[i]); i-- { 222 249 if path[i] == '.' { 223 250 return path[i:] 224 251 } ··· 226 253 return "" 227 254 } 228 255 229 - // EvalSymlinks returns the path name after the evaluation of any symbolic 230 - // links. 231 - // If path is relative the result will be relative to the current directory, 232 - // unless one of the components is an absolute symbolic link. 233 - // EvalSymlinks calls Clean on the result. 234 - func EvalSymlinks(path string) (string, error) { 235 - return evalSymlinks(path) 236 - } 237 - 238 - // Abs returns an absolute representation of path. 239 - // If the path is not absolute it will be joined with the current 240 - // working directory to turn it into an absolute path. The absolute 241 - // path name for a given file is not guaranteed to be unique. 242 - // Abs calls Clean on the result. 243 - func Abs(path string) (string, error) { 244 - return abs(path) 245 - } 246 - 247 - func unixAbs(path string) (string, error) { 248 - if IsAbs(path) { 249 - return Clean(path), nil 250 - } 251 - wd, err := os.Getwd() 252 - if err != nil { 253 - return "", err 256 + // Resolve reports the path of sub relative to dir. If sub is an absolute path, 257 + // or if dir is empty, it will return sub. If sub is empty, it will return dir. 258 + // Resolve calls Clean on the result. The default value for os is Unix. 259 + func Resolve(dir, sub string, os OS) string { 260 + x := getOS(os) 261 + if x.IsAbs(sub) { 262 + return clean(sub, x) 254 263 } 255 - return Join(wd, path), nil 264 + dir = clean(dir, x) 265 + return x.join([]string{dir, sub}) 256 266 } 257 267 258 268 // Rel returns a relative path that is lexically equivalent to targpath when ··· 262 272 // even if basepath and targpath share no elements. 263 273 // An error is returned if targpath can't be made relative to basepath or if 264 274 // knowing the current working directory would be necessary to compute it. 265 - // Rel calls Clean on the result. 266 - func Rel(basepath, targpath string) (string, error) { 267 - baseVol := VolumeName(basepath) 268 - targVol := VolumeName(targpath) 269 - base := Clean(basepath) 270 - targ := Clean(targpath) 271 - if sameWord(targ, base) { 275 + // Rel calls Clean on the result. The default value for os is Unix. 276 + func Rel(basepath, targpath string, os OS) (string, error) { 277 + x := getOS(os) 278 + baseVol := volumeName(basepath, x) 279 + targVol := volumeName(targpath, x) 280 + base := clean(basepath, x) 281 + targ := clean(targpath, x) 282 + if x.sameWord(targ, base) { 272 283 return ".", nil 273 284 } 274 285 base = base[len(baseVol):] ··· 277 288 base = "" 278 289 } 279 290 // Can't use IsAbs - `\a` and `a` are both relative in Windows. 280 - baseSlashed := len(base) > 0 && base[0] == Separator 281 - targSlashed := len(targ) > 0 && targ[0] == Separator 282 - if baseSlashed != targSlashed || !sameWord(baseVol, targVol) { 291 + baseSlashed := len(base) > 0 && base[0] == x.Separator 292 + targSlashed := len(targ) > 0 && targ[0] == x.Separator 293 + if baseSlashed != targSlashed || !x.sameWord(baseVol, targVol) { 283 294 return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath) 284 295 } 285 296 // Position base[b0:bi] and targ[t0:ti] at the first differing elements. ··· 287 298 tl := len(targ) 288 299 var b0, bi, t0, ti int 289 300 for { 290 - for bi < bl && base[bi] != Separator { 301 + for bi < bl && base[bi] != x.Separator { 291 302 bi++ 292 303 } 293 - for ti < tl && targ[ti] != Separator { 304 + for ti < tl && targ[ti] != x.Separator { 294 305 ti++ 295 306 } 296 - if !sameWord(targ[t0:ti], base[b0:bi]) { 307 + if !x.sameWord(targ[t0:ti], base[b0:bi]) { 297 308 break 298 309 } 299 310 if bi < bl { ··· 310 321 } 311 322 if b0 != bl { 312 323 // Base elements left. Must go up before going down. 313 - seps := strings.Count(base[b0:bl], string(Separator)) 324 + seps := strings.Count(base[b0:bl], string(x.Separator)) 314 325 size := 2 + seps*3 315 326 if tl != t0 { 316 327 size += 1 + tl - t0 ··· 318 329 buf := make([]byte, size) 319 330 n := copy(buf, "..") 320 331 for i := 0; i < seps; i++ { 321 - buf[n] = Separator 332 + buf[n] = x.Separator 322 333 copy(buf[n+1:], "..") 323 334 n += 3 324 335 } 325 336 if t0 != tl { 326 - buf[n] = Separator 337 + buf[n] = x.Separator 327 338 copy(buf[n+1:], targ[t0:]) 328 339 } 329 340 return string(buf), nil ··· 331 342 return targ[t0:], nil 332 343 } 333 344 334 - // SkipDir is used as a return value from WalkFuncs to indicate that 335 - // the directory named in the call is to be skipped. It is not returned 336 - // as an error by any function. 337 - var SkipDir error = fs.SkipDir 338 - 339 - // WalkFunc is the type of the function called by Walk to visit each each 340 - // file or directory. 341 - // 342 - // The path argument contains the argument to Walk as a prefix. 343 - // That is, if Walk is called with root argument "dir" and finds a file 344 - // named "a" in that directory, the walk function will be called with 345 - // argument "dir/a". 346 - // 347 - // The directory and file are joined with Join, which may clean the 348 - // directory name: if Walk is called with the root argument "x/../dir" 349 - // and finds a file named "a" in that directory, the walk function will 350 - // be called with argument "dir/a", not "x/../dir/a". 351 - // 352 - // The info argument is the fs.FileInfo for the named path. 353 - // 354 - // The error result returned by the function controls how Walk continues. 355 - // If the function returns the special value SkipDir, Walk skips the 356 - // current directory (path if info.IsDir() is true, otherwise path's 357 - // parent directory). Otherwise, if the function returns a non-nil error, 358 - // Walk stops entirely and returns that error. 359 - // 360 - // The err argument reports an error related to path, signaling that Walk 361 - // will not walk into that directory. The function can decide how to 362 - // handle that error; as described earlier, returning the error will 363 - // cause Walk to stop walking the entire tree. 364 - // 365 - // Walk calls the function with a non-nil err argument in two cases. 366 - // 367 - // First, if an os.Lstat on the root directory or any directory or file 368 - // in the tree fails, Walk calls the function with path set to that 369 - // directory or file's path, info set to nil, and err set to the error 370 - // from os.Lstat. 371 - // 372 - // Second, if a directory's Readdirnames method fails, Walk calls the 373 - // function with path set to the directory's path, info, set to an 374 - // fs.FileInfo describing the directory, and err set to the error from 375 - // Readdirnames. 376 - type WalkFunc func(path string, info fs.FileInfo, err error) error 377 - 378 - var lstat = os.Lstat // for testing 379 - 380 - // walkDir recursively descends path, calling walkDirFn. 381 - func walkDir(path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error { 382 - if err := walkDirFn(path, d, nil); err != nil || !d.IsDir() { 383 - if err == SkipDir && d.IsDir() { 384 - // Successfully skipped directory. 385 - err = nil 386 - } 387 - return err 388 - } 389 - 390 - dirs, err := readDir(path) 391 - if err != nil { 392 - // Second call, to report ReadDir error. 393 - err = walkDirFn(path, d, err) 394 - if err != nil { 395 - return err 396 - } 397 - } 398 - 399 - for _, d1 := range dirs { 400 - path1 := Join(path, d1.Name()) 401 - if err := walkDir(path1, d1, walkDirFn); err != nil { 402 - if err == SkipDir { 403 - break 404 - } 405 - return err 406 - } 407 - } 408 - return nil 409 - } 410 - 411 - // walk recursively descends path, calling walkFn. 412 - func walk(path string, info fs.FileInfo, walkFn WalkFunc) error { 413 - if !info.IsDir() { 414 - return walkFn(path, info, nil) 415 - } 416 - 417 - names, err := readDirNames(path) 418 - err1 := walkFn(path, info, err) 419 - // If err != nil, walk can't walk into this directory. 420 - // err1 != nil means walkFn want walk to skip this directory or stop walking. 421 - // Therefore, if one of err and err1 isn't nil, walk will return. 422 - if err != nil || err1 != nil { 423 - // The caller's behavior is controlled by the return value, which is decided 424 - // by walkFn. walkFn may ignore err and return nil. 425 - // If walkFn returns SkipDir, it will be handled by the caller. 426 - // So walk should return whatever walkFn returns. 427 - return err1 428 - } 429 - 430 - for _, name := range names { 431 - filename := Join(path, name) 432 - fileInfo, err := lstat(filename) 433 - if err != nil { 434 - if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir { 435 - return err 436 - } 437 - } else { 438 - err = walk(filename, fileInfo, walkFn) 439 - if err != nil { 440 - if !fileInfo.IsDir() || err != SkipDir { 441 - return err 442 - } 443 - } 444 - } 445 - } 446 - return nil 447 - } 448 - 449 - // WalkDir walks the file tree rooted at root, calling fn for each file or 450 - // directory in the tree, including root. 451 - // 452 - // All errors that arise visiting files and directories are filtered by fn: 453 - // see the fs.WalkDirFunc documentation for details. 454 - // 455 - // The files are walked in lexical order, which makes the output deterministic 456 - // but requires WalkDir to read an entire directory into memory before proceeding 457 - // to walk that directory. 458 - // 459 - // WalkDir does not follow symbolic links. 460 - func WalkDir(root string, fn fs.WalkDirFunc) error { 461 - info, err := os.Lstat(root) 462 - if err != nil { 463 - err = fn(root, nil, err) 464 - } else { 465 - err = walkDir(root, &statDirEntry{info}, fn) 466 - } 467 - if err == SkipDir { 468 - return nil 469 - } 470 - return err 471 - } 472 - 473 - type statDirEntry struct { 474 - info fs.FileInfo 475 - } 476 - 477 - func (d *statDirEntry) Name() string { return d.info.Name() } 478 - func (d *statDirEntry) IsDir() bool { return d.info.IsDir() } 479 - func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() } 480 - func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil } 481 - 482 - // Walk walks the file tree rooted at root, calling fn for each file or 483 - // directory in the tree, including root. 484 - // 485 - // All errors that arise visiting files and directories are filtered by fn: 486 - // see the WalkFunc documentation for details. 487 - // 488 - // The files are walked in lexical order, which makes the output deterministic 489 - // but requires Walk to read an entire directory into memory before proceeding 490 - // to walk that directory. 491 - // 492 - // Walk does not follow symbolic links. 493 - // 494 - // Walk is less efficient than WalkDir, introduced in Go 1.16, 495 - // which avoids calling os.Lstat on every visited file or directory. 496 - func Walk(root string, fn WalkFunc) error { 497 - info, err := os.Lstat(root) 498 - if err != nil { 499 - err = fn(root, nil, err) 500 - } else { 501 - err = walk(root, info, fn) 502 - } 503 - if err == SkipDir { 504 - return nil 505 - } 506 - return err 507 - } 508 - 509 - // readDir reads the directory named by dirname and returns 510 - // a sorted list of directory entries. 511 - func readDir(dirname string) ([]fs.DirEntry, error) { 512 - f, err := os.Open(dirname) 513 - if err != nil { 514 - return nil, err 515 - } 516 - dirs, err := f.ReadDir(-1) 517 - f.Close() 518 - if err != nil { 519 - return nil, err 520 - } 521 - sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() }) 522 - return dirs, nil 523 - } 524 - 525 - // readDirNames reads the directory named by dirname and returns 526 - // a sorted list of directory entry names. 527 - func readDirNames(dirname string) ([]string, error) { 528 - f, err := os.Open(dirname) 529 - if err != nil { 530 - return nil, err 531 - } 532 - names, err := f.Readdirnames(-1) 533 - f.Close() 534 - if err != nil { 535 - return nil, err 536 - } 537 - sort.Strings(names) 538 - return names, nil 539 - } 540 - 541 345 // Base returns the last element of path. 542 346 // Trailing path separators are removed before extracting the last element. 543 347 // If the path is empty, Base returns ".". 544 348 // If the path consists entirely of separators, Base returns a single separator. 545 - func Base(path string) string { 349 + // The default value for os is Unix. 350 + func Base(path string, os OS) string { 351 + x := getOS(os) 546 352 if path == "" { 547 353 return "." 548 354 } 549 355 // Strip trailing slashes. 550 - for len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) { 356 + for len(path) > 0 && x.IsPathSeparator(path[len(path)-1]) { 551 357 path = path[0 : len(path)-1] 552 358 } 553 359 // Throw away volume name 554 - path = path[len(VolumeName(path)):] 360 + path = path[x.volumeNameLen(path):] 555 361 // Find the last element 556 362 i := len(path) - 1 557 - for i >= 0 && !os.IsPathSeparator(path[i]) { 363 + for i >= 0 && !x.IsPathSeparator(path[i]) { 558 364 i-- 559 365 } 560 366 if i >= 0 { ··· 562 368 } 563 369 // If empty now, it had only slashes. 564 370 if path == "" { 565 - return string(Separator) 371 + return string(x.Separator) 566 372 } 567 373 return path 568 374 } ··· 573 379 // If the path is empty, Dir returns ".". 574 380 // If the path consists entirely of separators, Dir returns a single separator. 575 381 // The returned path does not end in a separator unless it is the root directory. 576 - func Dir(path string) string { 577 - vol := VolumeName(path) 382 + // The default value for os is Unix. 383 + func Dir(path string, os OS) string { 384 + x := getOS(os) 385 + vol := volumeName(path, x) 578 386 i := len(path) - 1 579 - for i >= len(vol) && !os.IsPathSeparator(path[i]) { 387 + for i >= len(vol) && !x.IsPathSeparator(path[i]) { 580 388 i-- 581 389 } 582 - dir := Clean(path[len(vol) : i+1]) 390 + dir := clean(path[len(vol):i+1], x) 583 391 if dir == "." && len(vol) > 2 { 584 392 // must be UNC 585 393 return vol ··· 587 395 return vol + dir 588 396 } 589 397 398 + // IsAbs reports whether the path is absolute. The default value for os is Unix. 399 + func IsAbs(path string, os OS) bool { 400 + return getOS(os).IsAbs(path) 401 + } 402 + 590 403 // VolumeName returns leading volume name. 591 404 // Given "C:\foo\bar" it returns "C:" on Windows. 592 405 // Given "\\host\share\foo" it returns "\\host\share". 593 406 // On other platforms it returns "". 594 - func VolumeName(path string) string { 595 - return path[:volumeNameLen(path)] 407 + // The default value for os is Windows. 408 + func VolumeName(path string, os OS) string { 409 + return volumeName(path, getOS(os)) 410 + } 411 + 412 + func volumeName(path string, os os) string { 413 + return path[:os.volumeNameLen(path)] 596 414 }
+36 -15
pkg/path/testdata/path_nix.go
··· 1 + // Copyright 2020 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 1 15 // Copyright 2010 The Go Authors. All rights reserved. 2 16 // Use of this source code is governed by a BSD-style 3 17 // license that can be found in the LICENSE file. 4 18 5 - // +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris 19 + package path 20 + 21 + import "strings" 22 + 23 + type unixInfo struct{} 24 + 25 + var _ osInfo = unixInfo{} 6 26 7 - package filepath 27 + const ( 28 + unixListSeparator = ':' 29 + unixSeparator = '/' 30 + ) 8 31 9 - import "strings" 32 + func (o unixInfo) IsPathSeparator(b byte) bool { 33 + return b == unixSeparator 34 + } 10 35 11 36 // IsAbs reports whether the path is absolute. 12 - func IsAbs(path string) bool { 37 + func (o unixInfo) IsAbs(path string) bool { 13 38 return strings.HasPrefix(path, "/") 14 39 } 15 40 16 41 // volumeNameLen returns length of the leading volume name on Windows. 17 42 // It returns 0 elsewhere. 18 - func volumeNameLen(path string) int { 43 + func (o unixInfo) volumeNameLen(path string) int { 19 44 return 0 20 45 } 21 46 ··· 23 48 // 24 49 // Deprecated: HasPrefix does not respect path boundaries and 25 50 // does not ignore case when required. 26 - func HasPrefix(p, prefix string) bool { 51 + func (o unixInfo) HasPrefix(p, prefix string) bool { 27 52 return strings.HasPrefix(p, prefix) 28 53 } 29 54 30 - func splitList(path string) []string { 55 + func (o unixInfo) splitList(path string) []string { 31 56 if path == "" { 32 57 return []string{} 33 58 } 34 - return strings.Split(path, string(ListSeparator)) 35 - } 36 - 37 - func abs(path string) (string, error) { 38 - return unixAbs(path) 59 + return strings.Split(path, string(unixListSeparator)) 39 60 } 40 61 41 - func join(elem []string) string { 62 + func (o unixInfo) join(elem []string) string { 42 63 // If there's a bug here, fix the logic in ./path_plan9.go too. 43 64 for i, e := range elem { 44 65 if e != "" { 45 - return Clean(strings.Join(elem[i:], string(Separator))) 66 + return clean(strings.Join(elem[i:], string(unixSeparator)), unix) 46 67 } 47 68 } 48 69 return "" 49 70 } 50 71 51 - func sameWord(a, b string) bool { 72 + func (o unixInfo) sameWord(a, b string) bool { 52 73 return a == b 53 74 }
+34 -13
pkg/path/testdata/path_p9.go
··· 1 + // Copyright 2020 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 1 15 // Copyright 2010 The Go Authors. All rights reserved. 2 16 // Use of this source code is governed by a BSD-style 3 17 // license that can be found in the LICENSE file. 4 18 5 - package filepath 19 + package path 6 20 7 21 import "strings" 8 22 23 + const plan9Separator = '/' 24 + const plan9ListSeparator = '\000' 25 + 26 + type plan9Info struct{} 27 + 28 + var _ osInfo = plan9Info{} 29 + 30 + func (o plan9Info) IsPathSeparator(b byte) bool { 31 + return b == plan9Separator 32 + } 33 + 9 34 // IsAbs reports whether the path is absolute. 10 - func IsAbs(path string) bool { 35 + func (o plan9Info) IsAbs(path string) bool { 11 36 return strings.HasPrefix(path, "/") || strings.HasPrefix(path, "#") 12 37 } 13 38 14 39 // volumeNameLen returns length of the leading volume name on Windows. 15 40 // It returns 0 elsewhere. 16 - func volumeNameLen(path string) int { 41 + func (o plan9Info) volumeNameLen(path string) int { 17 42 return 0 18 43 } 19 44 ··· 21 46 // 22 47 // Deprecated: HasPrefix does not respect path boundaries and 23 48 // does not ignore case when required. 24 - func HasPrefix(p, prefix string) bool { 49 + func (o plan9Info) HasPrefix(p, prefix string) bool { 25 50 return strings.HasPrefix(p, prefix) 26 51 } 27 52 28 - func splitList(path string) []string { 53 + func (o plan9Info) splitList(path string) []string { 29 54 if path == "" { 30 55 return []string{} 31 56 } 32 - return strings.Split(path, string(ListSeparator)) 57 + return strings.Split(path, string(plan9ListSeparator)) 33 58 } 34 59 35 - func abs(path string) (string, error) { 36 - return unixAbs(path) 37 - } 38 - 39 - func join(elem []string) string { 60 + func (o plan9Info) join(elem []string) string { 40 61 // If there's a bug here, fix the logic in ./path_unix.go too. 41 62 for i, e := range elem { 42 63 if e != "" { 43 - return Clean(strings.Join(elem[i:], string(Separator))) 64 + return clean(strings.Join(elem[i:], string(plan9Separator)), plan9) 44 65 } 45 66 } 46 67 return "" 47 68 } 48 69 49 - func sameWord(a, b string) bool { 70 + func (o plan9Info) sameWord(a, b string) bool { 50 71 return a == b 51 72 }
+175 -1059
pkg/path/testdata/path_test.go
··· 1 + // Copyright 2020 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 1 15 // Copyright 2009 The Go Authors. All rights reserved. 2 16 // Use of this source code is governed by a BSD-style 3 17 // license that can be found in the LICENSE file. 4 18 5 - package filepath_test 19 + package path 6 20 7 21 import ( 8 - "errors" 9 - "fmt" 10 - "internal/testenv" 11 - "io/fs" 12 - "io/ioutil" 13 - "os" 14 - "path/filepath" 15 22 "reflect" 16 23 "runtime" 17 - "sort" 18 - "strings" 19 - "syscall" 20 24 "testing" 21 25 ) 22 26 ··· 98 102 99 103 func TestClean(t *testing.T) { 100 104 tests := cleantests 101 - if runtime.GOOS == "windows" { 102 - for i := range tests { 103 - tests[i].result = filepath.FromSlash(tests[i].result) 104 - } 105 - tests = append(tests, wincleantests...) 106 - } 107 - for _, test := range tests { 108 - if s := filepath.Clean(test.path); s != test.result { 109 - t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result) 105 + for _, os := range []OS{Unix, Windows, Plan9} { 106 + if os == Windows { 107 + for i := range tests { 108 + tests[i].result = FromSlash(tests[i].result, os) 109 + } 110 + tests = append(tests, wincleantests...) 110 111 } 111 - if s := filepath.Clean(test.result); s != test.result { 112 - t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result) 112 + for _, test := range tests { 113 + if s := Clean(test.path, os); s != test.result { 114 + t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result) 115 + } 116 + if s := Clean(test.result, os); s != test.result { 117 + t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result) 118 + } 113 119 } 114 - } 115 120 116 - if testing.Short() { 117 - t.Skip("skipping malloc count in short mode") 118 - } 119 - if runtime.GOMAXPROCS(0) > 1 { 120 - t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1") 121 - return 122 - } 121 + if testing.Short() { 122 + t.Skip("skipping malloc count in short mode") 123 + } 124 + if runtime.GOMAXPROCS(0) > 1 { 125 + t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1") 126 + return 127 + } 123 128 124 - for _, test := range tests { 125 - allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) }) 126 - if allocs > 0 { 127 - t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs) 129 + for _, test := range tests { 130 + allocs := testing.AllocsPerRun(100, func() { Clean(test.result, os) }) 131 + if allocs > 0 { 132 + t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs) 133 + } 128 134 } 129 135 } 130 136 } 131 137 132 - const sep = filepath.Separator 138 + func TestFromAndToSlash(t *testing.T) { 139 + for _, o := range []OS{Unix, Windows, Plan9} { 140 + sep := getOS(o).Separator 133 141 134 - var slashtests = []PathTest{ 135 - {"", ""}, 136 - {"/", string(sep)}, 137 - {"/a/b", string([]byte{sep, 'a', sep, 'b'})}, 138 - {"a//b", string([]byte{'a', sep, sep, 'b'})}, 139 - } 142 + var slashtests = []PathTest{ 143 + {"", ""}, 144 + {"/", string(sep)}, 145 + {"/a/b", string([]byte{sep, 'a', sep, 'b'})}, 146 + {"a//b", string([]byte{'a', sep, sep, 'b'})}, 147 + } 140 148 141 - func TestFromAndToSlash(t *testing.T) { 142 - for _, test := range slashtests { 143 - if s := filepath.FromSlash(test.path); s != test.result { 144 - t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result) 145 - } 146 - if s := filepath.ToSlash(test.result); s != test.path { 147 - t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path) 149 + for _, test := range slashtests { 150 + if s := FromSlash(test.path, o); s != test.result { 151 + t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result) 152 + } 153 + if s := ToSlash(test.result, o); s != test.path { 154 + t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path) 155 + } 148 156 } 149 157 } 150 158 } ··· 152 160 type SplitListTest struct { 153 161 list string 154 162 result []string 155 - } 156 - 157 - const lsep = filepath.ListSeparator 158 - 159 - var splitlisttests = []SplitListTest{ 160 - {"", []string{}}, 161 - {string([]byte{'a', lsep, 'b'}), []string{"a", "b"}}, 162 - {string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}}, 163 163 } 164 164 165 165 var winsplitlisttests = []SplitListTest{ ··· 185 185 } 186 186 187 187 func TestSplitList(t *testing.T) { 188 - tests := splitlisttests 189 - if runtime.GOOS == "windows" { 190 - tests = append(tests, winsplitlisttests...) 191 - } 192 - for _, test := range tests { 193 - if l := filepath.SplitList(test.list); !reflect.DeepEqual(l, test.result) { 194 - t.Errorf("SplitList(%#q) = %#q, want %#q", test.list, l, test.result) 188 + for _, os := range []OS{Unix, Windows, Plan9} { 189 + sep := getOS(os).ListSeparator 190 + 191 + tests := []SplitListTest{ 192 + {"", []string{}}, 193 + {string([]byte{'a', sep, 'b'}), []string{"a", "b"}}, 194 + {string([]byte{sep, 'a', sep, 'b'}), []string{"", "a", "b"}}, 195 + } 196 + if os == Windows { 197 + tests = append(tests, winsplitlisttests...) 198 + } 199 + for _, test := range tests { 200 + if l := SplitList(test.list, os); !reflect.DeepEqual(l, test.result) { 201 + t.Errorf("SplitList(%#q, %q) = %#q, want %#q", test.list, os, l, test.result) 202 + } 195 203 } 196 204 } 197 205 } ··· 222 230 } 223 231 224 232 func TestSplit(t *testing.T) { 225 - var splittests []SplitTest 226 - splittests = unixsplittests 227 - if runtime.GOOS == "windows" { 228 - splittests = append(splittests, winsplittests...) 229 - } 230 - for _, test := range splittests { 231 - if d, f := filepath.Split(test.path); d != test.dir || f != test.file { 232 - t.Errorf("Split(%q) = %q, %q, want %q, %q", test.path, d, f, test.dir, test.file) 233 + for _, os := range []OS{Windows, Unix} { 234 + var splittests []SplitTest 235 + splittests = unixsplittests 236 + if os == Windows { 237 + splittests = append(splittests, winsplittests...) 238 + } 239 + for _, test := range splittests { 240 + pair := Split(test.path, os) 241 + d, f := pair[0], pair[1] 242 + if d != test.dir || f != test.file { 243 + t.Errorf("Split(%q, %q) = %q, %q, want %q, %q", 244 + test.path, os, d, f, test.dir, test.file) 245 + } 233 246 } 234 247 } 235 248 } ··· 295 308 } 296 309 297 310 func TestJoin(t *testing.T) { 298 - if runtime.GOOS == "windows" { 299 - jointests = append(jointests, winjointests...) 300 - } 301 - for _, test := range jointests { 302 - expected := filepath.FromSlash(test.path) 303 - if p := filepath.Join(test.elem...); p != expected { 304 - t.Errorf("join(%q) = %q, want %q", test.elem, p, expected) 311 + for _, os := range []OS{Unix, Windows} { 312 + if os == Windows { 313 + jointests = append(jointests, winjointests...) 314 + } 315 + for _, test := range jointests { 316 + expected := FromSlash(test.path, os) 317 + if p := Join(test.elem, os); p != expected { 318 + t.Errorf("join(%q, %q) = %q, want %q", test.elem, os, p, expected) 319 + } 305 320 } 306 321 } 307 322 } ··· 319 334 } 320 335 321 336 func TestExt(t *testing.T) { 322 - for _, test := range exttests { 323 - if x := filepath.Ext(test.path); x != test.ext { 324 - t.Errorf("Ext(%q) = %q, want %q", test.path, x, test.ext) 325 - } 326 - } 327 - } 328 - 329 - type Node struct { 330 - name string 331 - entries []*Node // nil if the entry is a file 332 - mark int 333 - } 334 - 335 - var tree = &Node{ 336 - "testdata", 337 - []*Node{ 338 - {"a", nil, 0}, 339 - {"b", []*Node{}, 0}, 340 - {"c", nil, 0}, 341 - { 342 - "d", 343 - []*Node{ 344 - {"x", nil, 0}, 345 - {"y", []*Node{}, 0}, 346 - { 347 - "z", 348 - []*Node{ 349 - {"u", nil, 0}, 350 - {"v", nil, 0}, 351 - }, 352 - 0, 353 - }, 354 - }, 355 - 0, 356 - }, 357 - }, 358 - 0, 359 - } 360 - 361 - func walkTree(n *Node, path string, f func(path string, n *Node)) { 362 - f(path, n) 363 - for _, e := range n.entries { 364 - walkTree(e, filepath.Join(path, e.name), f) 365 - } 366 - } 367 - 368 - func makeTree(t *testing.T) { 369 - walkTree(tree, tree.name, func(path string, n *Node) { 370 - if n.entries == nil { 371 - fd, err := os.Create(path) 372 - if err != nil { 373 - t.Errorf("makeTree: %v", err) 374 - return 337 + for _, os := range []OS{Unix, Windows} { 338 + for _, test := range exttests { 339 + if x := Ext(test.path, os); x != test.ext { 340 + t.Errorf("Ext(%q, %q) = %q, want %q", test.path, os, x, test.ext) 375 341 } 376 - fd.Close() 377 - } else { 378 - os.Mkdir(path, 0770) 379 342 } 380 - }) 381 - } 382 - 383 - func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) } 384 - 385 - func checkMarks(t *testing.T, report bool) { 386 - walkTree(tree, tree.name, func(path string, n *Node) { 387 - if n.mark != 1 && report { 388 - t.Errorf("node %s mark = %d; expected 1", path, n.mark) 389 - } 390 - n.mark = 0 391 - }) 392 - } 393 - 394 - // Assumes that each node name is unique. Good enough for a test. 395 - // If clear is true, any incoming error is cleared before return. The errors 396 - // are always accumulated, though. 397 - func mark(d fs.DirEntry, err error, errors *[]error, clear bool) error { 398 - name := d.Name() 399 - walkTree(tree, tree.name, func(path string, n *Node) { 400 - if n.name == name { 401 - n.mark++ 402 - } 403 - }) 404 - if err != nil { 405 - *errors = append(*errors, err) 406 - if clear { 407 - return nil 408 - } 409 - return err 410 - } 411 - return nil 412 - } 413 - 414 - func chtmpdir(t *testing.T) (restore func()) { 415 - oldwd, err := os.Getwd() 416 - if err != nil { 417 - t.Fatalf("chtmpdir: %v", err) 418 - } 419 - d, err := ioutil.TempDir("", "test") 420 - if err != nil { 421 - t.Fatalf("chtmpdir: %v", err) 422 - } 423 - if err := os.Chdir(d); err != nil { 424 - t.Fatalf("chtmpdir: %v", err) 425 - } 426 - return func() { 427 - if err := os.Chdir(oldwd); err != nil { 428 - t.Fatalf("chtmpdir: %v", err) 429 - } 430 - os.RemoveAll(d) 431 - } 432 - } 433 - 434 - func TestWalk(t *testing.T) { 435 - walk := func(root string, fn fs.WalkDirFunc) error { 436 - return filepath.Walk(root, func(path string, info fs.FileInfo, err error) error { 437 - return fn(path, &statDirEntry{info}, err) 438 - }) 439 - } 440 - testWalk(t, walk, 1) 441 - } 442 - 443 - type statDirEntry struct { 444 - info fs.FileInfo 445 - } 446 - 447 - func (d *statDirEntry) Name() string { return d.info.Name() } 448 - func (d *statDirEntry) IsDir() bool { return d.info.IsDir() } 449 - func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() } 450 - func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil } 451 - 452 - func TestWalkDir(t *testing.T) { 453 - testWalk(t, filepath.WalkDir, 2) 454 - } 455 - 456 - func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) { 457 - if runtime.GOOS == "ios" { 458 - restore := chtmpdir(t) 459 - defer restore() 460 - } 461 - 462 - tmpDir, err := ioutil.TempDir("", "TestWalk") 463 - if err != nil { 464 - t.Fatal("creating temp dir:", err) 465 - } 466 - defer os.RemoveAll(tmpDir) 467 - 468 - origDir, err := os.Getwd() 469 - if err != nil { 470 - t.Fatal("finding working dir:", err) 471 - } 472 - if err = os.Chdir(tmpDir); err != nil { 473 - t.Fatal("entering temp dir:", err) 474 - } 475 - defer os.Chdir(origDir) 476 - 477 - makeTree(t) 478 - errors := make([]error, 0, 10) 479 - clear := true 480 - markFn := func(path string, d fs.DirEntry, err error) error { 481 - return mark(d, err, &errors, clear) 482 - } 483 - // Expect no errors. 484 - err = walk(tree.name, markFn) 485 - if err != nil { 486 - t.Fatalf("no error expected, found: %s", err) 487 - } 488 - if len(errors) != 0 { 489 - t.Fatalf("unexpected errors: %s", errors) 490 - } 491 - checkMarks(t, true) 492 - errors = errors[0:0] 493 - 494 - t.Run("PermErr", func(t *testing.T) { 495 - // Test permission errors. Only possible if we're not root 496 - // and only on some file systems (AFS, FAT). To avoid errors during 497 - // all.bash on those file systems, skip during go test -short. 498 - if runtime.GOOS == "windows" { 499 - t.Skip("skipping on Windows") 500 - } 501 - if os.Getuid() == 0 { 502 - t.Skip("skipping as root") 503 - } 504 - if testing.Short() { 505 - t.Skip("skipping in short mode") 506 - } 507 - 508 - // introduce 2 errors: chmod top-level directories to 0 509 - os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0) 510 - os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0) 511 - 512 - // 3) capture errors, expect two. 513 - // mark respective subtrees manually 514 - markTree(tree.entries[1]) 515 - markTree(tree.entries[3]) 516 - // correct double-marking of directory itself 517 - tree.entries[1].mark -= errVisit 518 - tree.entries[3].mark -= errVisit 519 - err := walk(tree.name, markFn) 520 - if err != nil { 521 - t.Fatalf("expected no error return from Walk, got %s", err) 522 - } 523 - if len(errors) != 2 { 524 - t.Errorf("expected 2 errors, got %d: %s", len(errors), errors) 525 - } 526 - // the inaccessible subtrees were marked manually 527 - checkMarks(t, true) 528 - errors = errors[0:0] 529 - 530 - // 4) capture errors, stop after first error. 531 - // mark respective subtrees manually 532 - markTree(tree.entries[1]) 533 - markTree(tree.entries[3]) 534 - // correct double-marking of directory itself 535 - tree.entries[1].mark -= errVisit 536 - tree.entries[3].mark -= errVisit 537 - clear = false // error will stop processing 538 - err = walk(tree.name, markFn) 539 - if err == nil { 540 - t.Fatalf("expected error return from Walk") 541 - } 542 - if len(errors) != 1 { 543 - t.Errorf("expected 1 error, got %d: %s", len(errors), errors) 544 - } 545 - // the inaccessible subtrees were marked manually 546 - checkMarks(t, false) 547 - errors = errors[0:0] 548 - 549 - // restore permissions 550 - os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770) 551 - os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770) 552 - }) 553 - } 554 - 555 - func touch(t *testing.T, name string) { 556 - f, err := os.Create(name) 557 - if err != nil { 558 - t.Fatal(err) 559 - } 560 - if err := f.Close(); err != nil { 561 - t.Fatal(err) 562 - } 563 - } 564 - 565 - func TestWalkSkipDirOnFile(t *testing.T) { 566 - td, err := ioutil.TempDir("", "walktest") 567 - if err != nil { 568 - t.Fatal(err) 569 - } 570 - defer os.RemoveAll(td) 571 - 572 - if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil { 573 - t.Fatal(err) 574 - } 575 - touch(t, filepath.Join(td, "dir/foo1")) 576 - touch(t, filepath.Join(td, "dir/foo2")) 577 - 578 - sawFoo2 := false 579 - walker := func(path string) error { 580 - if strings.HasSuffix(path, "foo2") { 581 - sawFoo2 = true 582 - } 583 - if strings.HasSuffix(path, "foo1") { 584 - return filepath.SkipDir 585 - } 586 - return nil 587 - } 588 - walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) } 589 - walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) } 590 - 591 - check := func(t *testing.T, walk func(root string) error, root string) { 592 - t.Helper() 593 - sawFoo2 = false 594 - err = walk(root) 595 - if err != nil { 596 - t.Fatal(err) 597 - } 598 - if sawFoo2 { 599 - t.Errorf("SkipDir on file foo1 did not block processing of foo2") 600 - } 601 - } 602 - 603 - t.Run("Walk", func(t *testing.T) { 604 - Walk := func(root string) error { return filepath.Walk(td, walkFn) } 605 - check(t, Walk, td) 606 - check(t, Walk, filepath.Join(td, "dir")) 607 - }) 608 - t.Run("WalkDir", func(t *testing.T) { 609 - WalkDir := func(root string) error { return filepath.WalkDir(td, walkDirFn) } 610 - check(t, WalkDir, td) 611 - check(t, WalkDir, filepath.Join(td, "dir")) 612 - }) 613 - } 614 - 615 - func TestWalkFileError(t *testing.T) { 616 - td, err := ioutil.TempDir("", "walktest") 617 - if err != nil { 618 - t.Fatal(err) 619 - } 620 - defer os.RemoveAll(td) 621 - 622 - touch(t, filepath.Join(td, "foo")) 623 - touch(t, filepath.Join(td, "bar")) 624 - dir := filepath.Join(td, "dir") 625 - if err := os.MkdirAll(filepath.Join(td, "dir"), 0755); err != nil { 626 - t.Fatal(err) 627 - } 628 - touch(t, filepath.Join(dir, "baz")) 629 - touch(t, filepath.Join(dir, "stat-error")) 630 - defer func() { 631 - *filepath.LstatP = os.Lstat 632 - }() 633 - statErr := errors.New("some stat error") 634 - *filepath.LstatP = func(path string) (fs.FileInfo, error) { 635 - if strings.HasSuffix(path, "stat-error") { 636 - return nil, statErr 637 - } 638 - return os.Lstat(path) 639 - } 640 - got := map[string]error{} 641 - err = filepath.Walk(td, func(path string, fi fs.FileInfo, err error) error { 642 - rel, _ := filepath.Rel(td, path) 643 - got[filepath.ToSlash(rel)] = err 644 - return nil 645 - }) 646 - if err != nil { 647 - t.Errorf("Walk error: %v", err) 648 - } 649 - want := map[string]error{ 650 - ".": nil, 651 - "foo": nil, 652 - "bar": nil, 653 - "dir": nil, 654 - "dir/baz": nil, 655 - "dir/stat-error": statErr, 656 - } 657 - if !reflect.DeepEqual(got, want) { 658 - t.Errorf("Walked %#v; want %#v", got, want) 659 343 } 660 344 } 661 345 ··· 686 370 687 371 func TestBase(t *testing.T) { 688 372 tests := basetests 689 - if runtime.GOOS == "windows" { 690 - // make unix tests work on windows 691 - for i := range tests { 692 - tests[i].result = filepath.Clean(tests[i].result) 373 + for _, os := range []OS{Unix, Windows} { 374 + if os == Windows { 375 + // make unix tests work on windows 376 + for i := range tests { 377 + tests[i].result = Clean(tests[i].result, os) 378 + } 379 + // add windows specific tests 380 + tests = append(tests, winbasetests...) 693 381 } 694 - // add windows specific tests 695 - tests = append(tests, winbasetests...) 696 - } 697 - for _, test := range tests { 698 - if s := filepath.Base(test.path); s != test.result { 699 - t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result) 382 + for _, test := range tests { 383 + if s := Base(test.path, os); s != test.result { 384 + t.Errorf("Base(%q, %q) = %q, want %q", test.path, os, s, test.result) 385 + } 700 386 } 701 387 } 702 388 } ··· 729 415 } 730 416 731 417 func TestDir(t *testing.T) { 732 - tests := dirtests 733 - if runtime.GOOS == "windows" { 734 - // make unix tests work on windows 735 - for i := range tests { 736 - tests[i].result = filepath.Clean(tests[i].result) 418 + for _, os := range []OS{Unix, Windows} { 419 + tests := dirtests 420 + if os == Windows { 421 + // make unix tests work on windows 422 + for i := range tests { 423 + tests[i].result = Clean(tests[i].result, os) 424 + } 425 + // add windows specific tests 426 + tests = append(tests, windirtests...) 737 427 } 738 - // add windows specific tests 739 - tests = append(tests, windirtests...) 740 - } 741 - for _, test := range tests { 742 - if s := filepath.Dir(test.path); s != test.result { 743 - t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result) 428 + for _, test := range tests { 429 + if s := Dir(test.path, os); s != test.result { 430 + t.Errorf("Dir(%q, %q) = %q, want %q", test.path, os, s, test.result) 431 + } 744 432 } 745 433 } 746 434 } ··· 777 465 } 778 466 779 467 func TestIsAbs(t *testing.T) { 780 - var tests []IsAbsTest 781 - if runtime.GOOS == "windows" { 782 - tests = append(tests, winisabstests...) 783 - // All non-windows tests should fail, because they have no volume letter. 784 - for _, test := range isabstests { 785 - tests = append(tests, IsAbsTest{test.path, false}) 786 - } 787 - // All non-windows test should work as intended if prefixed with volume letter. 788 - for _, test := range isabstests { 789 - tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs}) 790 - } 791 - // Test reserved names. 792 - tests = append(tests, IsAbsTest{os.DevNull, true}) 793 - tests = append(tests, IsAbsTest{"NUL", true}) 794 - tests = append(tests, IsAbsTest{"nul", true}) 795 - tests = append(tests, IsAbsTest{"CON", true}) 796 - } else { 797 - tests = isabstests 798 - } 799 - 800 - for _, test := range tests { 801 - if r := filepath.IsAbs(test.path); r != test.isAbs { 802 - t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs) 803 - } 804 - } 805 - } 806 - 807 - type EvalSymlinksTest struct { 808 - // If dest is empty, the path is created; otherwise the dest is symlinked to the path. 809 - path, dest string 810 - } 811 - 812 - var EvalSymlinksTestDirs = []EvalSymlinksTest{ 813 - {"test", ""}, 814 - {"test/dir", ""}, 815 - {"test/dir/link3", "../../"}, 816 - {"test/link1", "../test"}, 817 - {"test/link2", "dir"}, 818 - {"test/linkabs", "/"}, 819 - {"test/link4", "../test2"}, 820 - {"test2", "test/dir"}, 821 - // Issue 23444. 822 - {"src", ""}, 823 - {"src/pool", ""}, 824 - {"src/pool/test", ""}, 825 - {"src/versions", ""}, 826 - {"src/versions/current", "../../version"}, 827 - {"src/versions/v1", ""}, 828 - {"src/versions/v1/modules", ""}, 829 - {"src/versions/v1/modules/test", "../../../pool/test"}, 830 - {"version", "src/versions/v1"}, 831 - } 832 - 833 - var EvalSymlinksTests = []EvalSymlinksTest{ 834 - {"test", "test"}, 835 - {"test/dir", "test/dir"}, 836 - {"test/dir/../..", "."}, 837 - {"test/link1", "test"}, 838 - {"test/link2", "test/dir"}, 839 - {"test/link1/dir", "test/dir"}, 840 - {"test/link2/..", "test"}, 841 - {"test/dir/link3", "."}, 842 - {"test/link2/link3/test", "test"}, 843 - {"test/linkabs", "/"}, 844 - {"test/link4/..", "test"}, 845 - {"src/versions/current/modules/test", "src/pool/test"}, 846 - } 847 - 848 - // simpleJoin builds a file name from the directory and path. 849 - // It does not use Join because we don't want ".." to be evaluated. 850 - func simpleJoin(dir, path string) string { 851 - return dir + string(filepath.Separator) + path 852 - } 853 - 854 - func testEvalSymlinks(t *testing.T, path, want string) { 855 - have, err := filepath.EvalSymlinks(path) 856 - if err != nil { 857 - t.Errorf("EvalSymlinks(%q) error: %v", path, err) 858 - return 859 - } 860 - if filepath.Clean(have) != filepath.Clean(want) { 861 - t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want) 862 - } 863 - } 864 - 865 - func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) { 866 - cwd, err := os.Getwd() 867 - if err != nil { 868 - t.Fatal(err) 869 - } 870 - defer func() { 871 - err := os.Chdir(cwd) 872 - if err != nil { 873 - t.Fatal(err) 874 - } 875 - }() 876 - 877 - err = os.Chdir(wd) 878 - if err != nil { 879 - t.Fatal(err) 880 - } 881 - 882 - have, err := filepath.EvalSymlinks(path) 883 - if err != nil { 884 - t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err) 885 - return 886 - } 887 - if filepath.Clean(have) != filepath.Clean(want) { 888 - t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want) 889 - } 890 - } 891 - 892 - func TestEvalSymlinks(t *testing.T) { 893 - testenv.MustHaveSymlink(t) 894 - 895 - tmpDir, err := ioutil.TempDir("", "evalsymlink") 896 - if err != nil { 897 - t.Fatal("creating temp dir:", err) 898 - } 899 - defer os.RemoveAll(tmpDir) 900 - 901 - // /tmp may itself be a symlink! Avoid the confusion, although 902 - // it means trusting the thing we're testing. 903 - tmpDir, err = filepath.EvalSymlinks(tmpDir) 904 - if err != nil { 905 - t.Fatal("eval symlink for tmp dir:", err) 906 - } 907 - 908 - // Create the symlink farm using relative paths. 909 - for _, d := range EvalSymlinksTestDirs { 910 - var err error 911 - path := simpleJoin(tmpDir, d.path) 912 - if d.dest == "" { 913 - err = os.Mkdir(path, 0755) 468 + for _, os := range []OS{Unix, Windows} { 469 + var tests []IsAbsTest 470 + if os == Windows { 471 + tests = append(tests, winisabstests...) 472 + // All non-windows tests should fail, because they have no volume letter. 473 + for _, test := range isabstests { 474 + tests = append(tests, IsAbsTest{test.path, false}) 475 + } 476 + // All non-windows test should work as intended if prefixed with volume letter. 477 + for _, test := range isabstests { 478 + tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs}) 479 + } 480 + // Test reserved names. 481 + // tests = append(tests, IsAbsTest{"/dev/null", true}) 482 + tests = append(tests, IsAbsTest{"NUL", true}) 483 + tests = append(tests, IsAbsTest{"nul", true}) 484 + tests = append(tests, IsAbsTest{"CON", true}) 914 485 } else { 915 - err = os.Symlink(d.dest, path) 916 - } 917 - if err != nil { 918 - t.Fatal(err) 486 + tests = isabstests 919 487 } 920 - } 921 488 922 - // Evaluate the symlink farm. 923 - for _, test := range EvalSymlinksTests { 924 - path := simpleJoin(tmpDir, test.path) 925 - 926 - dest := simpleJoin(tmpDir, test.dest) 927 - if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) { 928 - dest = test.dest 929 - } 930 - testEvalSymlinks(t, path, dest) 931 - 932 - // test EvalSymlinks(".") 933 - testEvalSymlinksAfterChdir(t, path, ".", ".") 934 - 935 - // test EvalSymlinks("C:.") on Windows 936 - if runtime.GOOS == "windows" { 937 - volDot := filepath.VolumeName(tmpDir) + "." 938 - testEvalSymlinksAfterChdir(t, path, volDot, volDot) 489 + for _, test := range tests { 490 + if r := IsAbs(test.path, os); r != test.isAbs { 491 + t.Errorf("IsAbs(%q, %q) = %v, want %v", test.path, os, r, test.isAbs) 492 + } 939 493 } 940 - 941 - // test EvalSymlinks(".."+path) 942 - dotdotPath := simpleJoin("..", test.dest) 943 - if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) { 944 - dotdotPath = test.dest 945 - } 946 - testEvalSymlinksAfterChdir(t, 947 - simpleJoin(tmpDir, "test"), 948 - simpleJoin("..", test.path), 949 - dotdotPath) 950 - 951 - // test EvalSymlinks(p) where p is relative path 952 - testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest) 953 494 } 954 495 } 955 496 956 - func TestEvalSymlinksIsNotExist(t *testing.T) { 957 - testenv.MustHaveSymlink(t) 958 - 959 - defer chtmpdir(t)() 960 - 961 - _, err := filepath.EvalSymlinks("notexist") 962 - if !os.IsNotExist(err) { 963 - t.Errorf("expected the file is not found, got %v\n", err) 964 - } 965 - 966 - err = os.Symlink("notexist", "link") 967 - if err != nil { 968 - t.Fatal(err) 969 - } 970 - defer os.Remove("link") 971 - 972 - _, err = filepath.EvalSymlinks("link") 973 - if !os.IsNotExist(err) { 974 - t.Errorf("expected the file is not found, got %v\n", err) 975 - } 976 - } 977 - 978 - func TestIssue13582(t *testing.T) { 979 - testenv.MustHaveSymlink(t) 980 - 981 - tmpDir, err := ioutil.TempDir("", "issue13582") 982 - if err != nil { 983 - t.Fatal(err) 984 - } 985 - defer os.RemoveAll(tmpDir) 986 - 987 - dir := filepath.Join(tmpDir, "dir") 988 - err = os.Mkdir(dir, 0755) 989 - if err != nil { 990 - t.Fatal(err) 991 - } 992 - linkToDir := filepath.Join(tmpDir, "link_to_dir") 993 - err = os.Symlink(dir, linkToDir) 994 - if err != nil { 995 - t.Fatal(err) 996 - } 997 - file := filepath.Join(linkToDir, "file") 998 - err = ioutil.WriteFile(file, nil, 0644) 999 - if err != nil { 1000 - t.Fatal(err) 1001 - } 1002 - link1 := filepath.Join(linkToDir, "link1") 1003 - err = os.Symlink(file, link1) 1004 - if err != nil { 1005 - t.Fatal(err) 1006 - } 1007 - link2 := filepath.Join(linkToDir, "link2") 1008 - err = os.Symlink(link1, link2) 1009 - if err != nil { 1010 - t.Fatal(err) 1011 - } 1012 - 1013 - // /tmp may itself be a symlink! 1014 - realTmpDir, err := filepath.EvalSymlinks(tmpDir) 1015 - if err != nil { 1016 - t.Fatal(err) 1017 - } 1018 - realDir := filepath.Join(realTmpDir, "dir") 1019 - realFile := filepath.Join(realDir, "file") 1020 - 1021 - tests := []struct { 1022 - path, want string 1023 - }{ 1024 - {dir, realDir}, 1025 - {linkToDir, realDir}, 1026 - {file, realFile}, 1027 - {link1, realFile}, 1028 - {link2, realFile}, 1029 - } 1030 - for i, test := range tests { 1031 - have, err := filepath.EvalSymlinks(test.path) 1032 - if err != nil { 1033 - t.Fatal(err) 1034 - } 1035 - if have != test.want { 1036 - t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want) 1037 - } 1038 - } 1039 - } 497 + // // simpleJoin builds a file name from the directory and path. 498 + // // It does not use Join because we don't want ".." to be evaluated. 499 + // func simpleJoin(dir, path string) string { 500 + // return dir + string(Separator) + path 501 + // } 1040 502 1041 503 // Test directories relative to temporary directory. 1042 504 // The tests are run in absTestDirs[0]. ··· 1064 526 "$/a/b/c/../../.././a/", 1065 527 } 1066 528 1067 - func TestAbs(t *testing.T) { 1068 - root, err := ioutil.TempDir("", "TestAbs") 1069 - if err != nil { 1070 - t.Fatal("TempDir failed: ", err) 1071 - } 1072 - defer os.RemoveAll(root) 1073 - 1074 - wd, err := os.Getwd() 1075 - if err != nil { 1076 - t.Fatal("getwd failed: ", err) 1077 - } 1078 - err = os.Chdir(root) 1079 - if err != nil { 1080 - t.Fatal("chdir failed: ", err) 1081 - } 1082 - defer os.Chdir(wd) 1083 - 1084 - for _, dir := range absTestDirs { 1085 - err = os.Mkdir(dir, 0777) 1086 - if err != nil { 1087 - t.Fatal("Mkdir failed: ", err) 1088 - } 1089 - } 1090 - 1091 - if runtime.GOOS == "windows" { 1092 - vol := filepath.VolumeName(root) 1093 - var extra []string 1094 - for _, path := range absTests { 1095 - if strings.Contains(path, "$") { 1096 - continue 1097 - } 1098 - path = vol + path 1099 - extra = append(extra, path) 1100 - } 1101 - absTests = append(absTests, extra...) 1102 - } 1103 - 1104 - err = os.Chdir(absTestDirs[0]) 1105 - if err != nil { 1106 - t.Fatal("chdir failed: ", err) 1107 - } 1108 - 1109 - for _, path := range absTests { 1110 - path = strings.ReplaceAll(path, "$", root) 1111 - info, err := os.Stat(path) 1112 - if err != nil { 1113 - t.Errorf("%s: %s", path, err) 1114 - continue 1115 - } 1116 - 1117 - abspath, err := filepath.Abs(path) 1118 - if err != nil { 1119 - t.Errorf("Abs(%q) error: %v", path, err) 1120 - continue 1121 - } 1122 - absinfo, err := os.Stat(abspath) 1123 - if err != nil || !os.SameFile(absinfo, info) { 1124 - t.Errorf("Abs(%q)=%q, not the same file", path, abspath) 1125 - } 1126 - if !filepath.IsAbs(abspath) { 1127 - t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath) 1128 - } 1129 - if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) { 1130 - t.Errorf("Abs(%q)=%q, isn't clean", path, abspath) 1131 - } 1132 - } 1133 - } 1134 - 1135 - // Empty path needs to be special-cased on Windows. See golang.org/issue/24441. 1136 - // We test it separately from all other absTests because the empty string is not 1137 - // a valid path, so it can't be used with os.Stat. 1138 - func TestAbsEmptyString(t *testing.T) { 1139 - root, err := ioutil.TempDir("", "TestAbsEmptyString") 1140 - if err != nil { 1141 - t.Fatal("TempDir failed: ", err) 1142 - } 1143 - defer os.RemoveAll(root) 1144 - 1145 - wd, err := os.Getwd() 1146 - if err != nil { 1147 - t.Fatal("getwd failed: ", err) 1148 - } 1149 - err = os.Chdir(root) 1150 - if err != nil { 1151 - t.Fatal("chdir failed: ", err) 1152 - } 1153 - defer os.Chdir(wd) 1154 - 1155 - info, err := os.Stat(root) 1156 - if err != nil { 1157 - t.Fatalf("%s: %s", root, err) 1158 - } 1159 - 1160 - abspath, err := filepath.Abs("") 1161 - if err != nil { 1162 - t.Fatalf(`Abs("") error: %v`, err) 1163 - } 1164 - absinfo, err := os.Stat(abspath) 1165 - if err != nil || !os.SameFile(absinfo, info) { 1166 - t.Errorf(`Abs("")=%q, not the same file`, abspath) 1167 - } 1168 - if !filepath.IsAbs(abspath) { 1169 - t.Errorf(`Abs("")=%q, not an absolute path`, abspath) 1170 - } 1171 - if filepath.IsAbs(abspath) && abspath != filepath.Clean(abspath) { 1172 - t.Errorf(`Abs("")=%q, isn't clean`, abspath) 1173 - } 1174 - } 1175 - 1176 529 type RelTests struct { 1177 530 root, path, want string 1178 531 } ··· 1231 584 } 1232 585 1233 586 func TestRel(t *testing.T) { 1234 - tests := append([]RelTests{}, reltests...) 1235 - if runtime.GOOS == "windows" { 1236 - for i := range tests { 1237 - tests[i].want = filepath.FromSlash(tests[i].want) 587 + for _, os := range []OS{Unix, Windows} { 588 + tests := append([]RelTests{}, reltests...) 589 + if os == Windows { 590 + for i := range tests { 591 + tests[i].want = FromSlash(tests[i].want, Windows) 592 + } 593 + tests = append(tests, winreltests...) 1238 594 } 1239 - tests = append(tests, winreltests...) 1240 - } 1241 - for _, test := range tests { 1242 - got, err := filepath.Rel(test.root, test.path) 1243 - if test.want == "err" { 1244 - if err == nil { 1245 - t.Errorf("Rel(%q, %q)=%q, want error", test.root, test.path, got) 595 + for _, test := range tests { 596 + got, err := Rel(test.root, test.path, os) 597 + if test.want == "err" { 598 + if err == nil { 599 + t.Errorf("Rel(%q, %q, %q)=%q, want error", test.root, test.path, os, got) 600 + } 601 + continue 602 + } 603 + if err != nil { 604 + t.Errorf("Rel(%q, %q, %q): want %q, got error: %s", test.root, test.path, os, test.want, err) 1246 605 } 1247 - continue 1248 - } 1249 - if err != nil { 1250 - t.Errorf("Rel(%q, %q): want %q, got error: %s", test.root, test.path, test.want, err) 1251 - } 1252 - if got != test.want { 1253 - t.Errorf("Rel(%q, %q)=%q, want %q", test.root, test.path, got, test.want) 606 + if got != test.want { 607 + t.Errorf("Rel(%q, %q, %q)=%q, want %q", test.root, test.path, os, got, test.want) 608 + } 1254 609 } 1255 610 } 1256 611 } ··· 1286 641 } 1287 642 1288 643 func TestVolumeName(t *testing.T) { 1289 - if runtime.GOOS != "windows" { 1290 - return 1291 - } 1292 - for _, v := range volumenametests { 1293 - if vol := filepath.VolumeName(v.path); vol != v.vol { 1294 - t.Errorf("VolumeName(%q)=%q, want %q", v.path, vol, v.vol) 1295 - } 1296 - } 1297 - } 1298 - 1299 - func TestDriveLetterInEvalSymlinks(t *testing.T) { 1300 - if runtime.GOOS != "windows" { 1301 - return 1302 - } 1303 - wd, _ := os.Getwd() 1304 - if len(wd) < 3 { 1305 - t.Errorf("Current directory path %q is too short", wd) 1306 - } 1307 - lp := strings.ToLower(wd) 1308 - up := strings.ToUpper(wd) 1309 - flp, err := filepath.EvalSymlinks(lp) 1310 - if err != nil { 1311 - t.Fatalf("EvalSymlinks(%q) failed: %q", lp, err) 1312 - } 1313 - fup, err := filepath.EvalSymlinks(up) 1314 - if err != nil { 1315 - t.Fatalf("EvalSymlinks(%q) failed: %q", up, err) 1316 - } 1317 - if flp != fup { 1318 - t.Errorf("Results of EvalSymlinks do not match: %q and %q", flp, fup) 1319 - } 1320 - } 1321 - 1322 - func TestBug3486(t *testing.T) { // https://golang.org/issue/3486 1323 - if runtime.GOOS == "ios" { 1324 - t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH) 1325 - } 1326 - root, err := filepath.EvalSymlinks(runtime.GOROOT() + "/test") 1327 - if err != nil { 1328 - t.Fatal(err) 1329 - } 1330 - bugs := filepath.Join(root, "fixedbugs") 1331 - ken := filepath.Join(root, "ken") 1332 - seenBugs := false 1333 - seenKen := false 1334 - err = filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error { 1335 - if err != nil { 1336 - t.Fatal(err) 644 + for _, os := range []OS{Unix, Windows} { 645 + if os != Windows { 646 + return 1337 647 } 1338 - 1339 - switch pth { 1340 - case bugs: 1341 - seenBugs = true 1342 - return filepath.SkipDir 1343 - case ken: 1344 - if !seenBugs { 1345 - t.Fatal("filepath.Walk out of order - ken before fixedbugs") 648 + for _, v := range volumenametests { 649 + if vol := VolumeName(v.path, os); vol != v.vol { 650 + t.Errorf("VolumeName(%q, %q)=%q, want %q", v.path, os, vol, v.vol) 1346 651 } 1347 - seenKen = true 1348 652 } 1349 - return nil 1350 - }) 1351 - if err != nil { 1352 - t.Fatal(err) 1353 - } 1354 - if !seenKen { 1355 - t.Fatalf("%q not seen", ken) 1356 - } 1357 - } 1358 - 1359 - func testWalkSymlink(t *testing.T, mklink func(target, link string) error) { 1360 - tmpdir, err := ioutil.TempDir("", "testWalkSymlink") 1361 - if err != nil { 1362 - t.Fatal(err) 1363 - } 1364 - defer os.RemoveAll(tmpdir) 1365 - 1366 - wd, err := os.Getwd() 1367 - if err != nil { 1368 - t.Fatal(err) 1369 - } 1370 - defer os.Chdir(wd) 1371 - 1372 - err = os.Chdir(tmpdir) 1373 - if err != nil { 1374 - t.Fatal(err) 1375 - } 1376 - 1377 - err = mklink(tmpdir, "link") 1378 - if err != nil { 1379 - t.Fatal(err) 1380 - } 1381 - 1382 - var visited []string 1383 - err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error { 1384 - if err != nil { 1385 - t.Fatal(err) 1386 - } 1387 - rel, err := filepath.Rel(tmpdir, path) 1388 - if err != nil { 1389 - t.Fatal(err) 1390 - } 1391 - visited = append(visited, rel) 1392 - return nil 1393 - }) 1394 - if err != nil { 1395 - t.Fatal(err) 1396 - } 1397 - sort.Strings(visited) 1398 - want := []string{".", "link"} 1399 - if fmt.Sprintf("%q", visited) != fmt.Sprintf("%q", want) { 1400 - t.Errorf("unexpected paths visited %q, want %q", visited, want) 1401 - } 1402 - } 1403 - 1404 - func TestWalkSymlink(t *testing.T) { 1405 - testenv.MustHaveSymlink(t) 1406 - testWalkSymlink(t, os.Symlink) 1407 - } 1408 - 1409 - func TestIssue29372(t *testing.T) { 1410 - tmpDir, err := ioutil.TempDir("", "TestIssue29372") 1411 - if err != nil { 1412 - t.Fatal(err) 1413 - } 1414 - defer os.RemoveAll(tmpDir) 1415 - 1416 - path := filepath.Join(tmpDir, "file.txt") 1417 - err = ioutil.WriteFile(path, nil, 0644) 1418 - if err != nil { 1419 - t.Fatal(err) 1420 - } 1421 - 1422 - pathSeparator := string(filepath.Separator) 1423 - tests := []string{ 1424 - path + strings.Repeat(pathSeparator, 1), 1425 - path + strings.Repeat(pathSeparator, 2), 1426 - path + strings.Repeat(pathSeparator, 1) + ".", 1427 - path + strings.Repeat(pathSeparator, 2) + ".", 1428 - path + strings.Repeat(pathSeparator, 1) + "..", 1429 - path + strings.Repeat(pathSeparator, 2) + "..", 1430 - } 1431 - 1432 - for i, test := range tests { 1433 - _, err = filepath.EvalSymlinks(test) 1434 - if err != syscall.ENOTDIR { 1435 - t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err) 1436 - } 1437 - } 1438 - } 1439 - 1440 - // Issue 30520 part 1. 1441 - func TestEvalSymlinksAboveRoot(t *testing.T) { 1442 - testenv.MustHaveSymlink(t) 1443 - 1444 - t.Parallel() 1445 - 1446 - tmpDir, err := ioutil.TempDir("", "TestEvalSymlinksAboveRoot") 1447 - if err != nil { 1448 - t.Fatal(err) 1449 - } 1450 - defer os.RemoveAll(tmpDir) 1451 - 1452 - evalTmpDir, err := filepath.EvalSymlinks(tmpDir) 1453 - if err != nil { 1454 - t.Fatal(err) 1455 - } 1456 - 1457 - if err := os.Mkdir(filepath.Join(evalTmpDir, "a"), 0777); err != nil { 1458 - t.Fatal(err) 1459 - } 1460 - if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil { 1461 - t.Fatal(err) 1462 - } 1463 - if err := ioutil.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil { 1464 - t.Fatal(err) 1465 - } 1466 - 1467 - // Count the number of ".." elements to get to the root directory. 1468 - vol := filepath.VolumeName(evalTmpDir) 1469 - c := strings.Count(evalTmpDir[len(vol):], string(os.PathSeparator)) 1470 - var dd []string 1471 - for i := 0; i < c+2; i++ { 1472 - dd = append(dd, "..") 1473 - } 1474 - 1475 - wantSuffix := strings.Join([]string{"a", "file"}, string(os.PathSeparator)) 1476 - 1477 - // Try different numbers of "..". 1478 - for _, i := range []int{c, c + 1, c + 2} { 1479 - check := strings.Join([]string{evalTmpDir, strings.Join(dd[:i], string(os.PathSeparator)), evalTmpDir[len(vol)+1:], "b", "file"}, string(os.PathSeparator)) 1480 - if resolved, err := filepath.EvalSymlinks(check); err != nil { 1481 - t.Errorf("EvalSymlinks(%q) failed: %v", check, err) 1482 - } else if !strings.HasSuffix(resolved, wantSuffix) { 1483 - t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix) 1484 - } else { 1485 - t.Logf("EvalSymlinks(%q) = %q", check, resolved) 1486 - } 1487 - } 1488 - } 1489 - 1490 - // Issue 30520 part 2. 1491 - func TestEvalSymlinksAboveRootChdir(t *testing.T) { 1492 - testenv.MustHaveSymlink(t) 1493 - 1494 - tmpDir, err := ioutil.TempDir("", "TestEvalSymlinksAboveRootChdir") 1495 - if err != nil { 1496 - t.Fatal(err) 1497 - } 1498 - defer os.RemoveAll(tmpDir) 1499 - 1500 - wd, err := os.Getwd() 1501 - if err != nil { 1502 - t.Fatal(err) 1503 - } 1504 - defer os.Chdir(wd) 1505 - 1506 - if err := os.Chdir(tmpDir); err != nil { 1507 - t.Fatal(err) 1508 - } 1509 - 1510 - subdir := filepath.Join("a", "b") 1511 - if err := os.MkdirAll(subdir, 0777); err != nil { 1512 - t.Fatal(err) 1513 - } 1514 - if err := os.Symlink(subdir, "c"); err != nil { 1515 - t.Fatal(err) 1516 - } 1517 - if err := ioutil.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil { 1518 - t.Fatal(err) 1519 - } 1520 - 1521 - subdir = filepath.Join("d", "e", "f") 1522 - if err := os.MkdirAll(subdir, 0777); err != nil { 1523 - t.Fatal(err) 1524 - } 1525 - if err := os.Chdir(subdir); err != nil { 1526 - t.Fatal(err) 1527 - } 1528 - 1529 - check := filepath.Join("..", "..", "..", "c", "file") 1530 - wantSuffix := filepath.Join("a", "b", "file") 1531 - if resolved, err := filepath.EvalSymlinks(check); err != nil { 1532 - t.Errorf("EvalSymlinks(%q) failed: %v", check, err) 1533 - } else if !strings.HasSuffix(resolved, wantSuffix) { 1534 - t.Errorf("EvalSymlinks(%q) = %q does not end with %q", check, resolved, wantSuffix) 1535 - } else { 1536 - t.Logf("EvalSymlinks(%q) = %q", check, resolved) 1537 653 } 1538 654 }
+47 -35
pkg/path/testdata/path_win.go
··· 1 + // Copyright 2020 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 1 15 // Copyright 2010 The Go Authors. All rights reserved. 2 16 // Use of this source code is governed by a BSD-style 3 17 // license that can be found in the LICENSE file. 4 18 5 - package filepath 19 + package path 6 20 7 21 import ( 8 22 "strings" 9 - "syscall" 23 + ) 24 + 25 + type windowsInfo struct{} 26 + 27 + var _ osInfo = windowsInfo{} 28 + 29 + const ( 30 + windowsSeparator = '\\' 31 + windowsListSeparator = ';' 10 32 ) 11 33 12 34 func isSlash(c uint8) bool { 13 35 return c == '\\' || c == '/' 36 + } 37 + 38 + func (os windowsInfo) IsPathSeparator(b byte) bool { 39 + return isSlash(b) 14 40 } 15 41 16 42 // reservedNames lists reserved Windows names. Search for PRN in ··· 24 50 25 51 // isReservedName returns true, if path is Windows reserved name. 26 52 // See reservedNames for the full list. 27 - func isReservedName(path string) bool { 53 + func (os windowsInfo) isReservedName(path string) bool { 28 54 if len(path) == 0 { 29 55 return false 30 56 } ··· 37 63 } 38 64 39 65 // IsAbs reports whether the path is absolute. 40 - func IsAbs(path string) (b bool) { 41 - if isReservedName(path) { 66 + func (os windowsInfo) IsAbs(path string) (b bool) { 67 + if os.isReservedName(path) { 42 68 return true 43 69 } 44 - l := volumeNameLen(path) 70 + l := os.volumeNameLen(path) 45 71 if l == 0 { 46 72 return false 47 73 } ··· 54 80 55 81 // volumeNameLen returns length of the leading volume name on Windows. 56 82 // It returns 0 elsewhere. 57 - func volumeNameLen(path string) int { 83 + func (os windowsInfo) volumeNameLen(path string) int { 58 84 if len(path) < 2 { 59 85 return 0 60 86 } ··· 94 120 // 95 121 // Deprecated: HasPrefix does not respect path boundaries and 96 122 // does not ignore case when required. 97 - func HasPrefix(p, prefix string) bool { 123 + func (os windowsInfo) HasPrefix(p, prefix string) bool { 98 124 if strings.HasPrefix(p, prefix) { 99 125 return true 100 126 } 101 127 return strings.HasPrefix(strings.ToLower(p), strings.ToLower(prefix)) 102 128 } 103 129 104 - func splitList(path string) []string { 130 + func (os windowsInfo) splitList(path string) []string { 105 131 // The same implementation is used in LookPath in os/exec; 106 132 // consider changing os/exec when changing this. 107 133 ··· 117 143 switch c := path[i]; { 118 144 case c == '"': 119 145 quo = !quo 120 - case c == ListSeparator && !quo: 146 + case c == windowsListSeparator && !quo: 121 147 list = append(list, path[start:i]) 122 148 start = i + 1 123 149 } ··· 132 158 return list 133 159 } 134 160 135 - func abs(path string) (string, error) { 136 - if path == "" { 137 - // syscall.FullPath returns an error on empty path, because it's not a valid path. 138 - // To implement Abs behavior of returning working directory on empty string input, 139 - // special-case empty path by changing it to "." path. See golang.org/issue/24441. 140 - path = "." 141 - } 142 - fullPath, err := syscall.FullPath(path) 143 - if err != nil { 144 - return "", err 145 - } 146 - return Clean(fullPath), nil 147 - } 148 - 149 - func join(elem []string) string { 161 + func (os windowsInfo) join(elem []string) string { 150 162 for i, e := range elem { 151 163 if e != "" { 152 - return joinNonEmpty(elem[i:]) 164 + return os.joinNonEmpty(elem[i:]) 153 165 } 154 166 } 155 167 return "" 156 168 } 157 169 158 170 // joinNonEmpty is like join, but it assumes that the first element is non-empty. 159 - func joinNonEmpty(elem []string) string { 171 + func (o windowsInfo) joinNonEmpty(elem []string) string { 160 172 if len(elem[0]) == 2 && elem[0][1] == ':' { 161 173 // First element is drive letter without terminating slash. 162 174 // Keep path relative to current directory on that drive. ··· 167 179 break 168 180 } 169 181 } 170 - return Clean(elem[0] + strings.Join(elem[i:], string(Separator))) 182 + return clean(elem[0]+strings.Join(elem[i:], string(windowsSeparator)), windows) 171 183 } 172 184 // The following logic prevents Join from inadvertently creating a 173 185 // UNC path on Windows. Unless the first element is a UNC path, Join 174 186 // shouldn't create a UNC path. See golang.org/issue/9167. 175 - p := Clean(strings.Join(elem, string(Separator))) 187 + p := clean(strings.Join(elem, string(windowsSeparator)), windows) 176 188 if !isUNC(p) { 177 189 return p 178 190 } 179 191 // p == UNC only allowed when the first element is a UNC path. 180 - head := Clean(elem[0]) 192 + head := clean(elem[0], windows) 181 193 if isUNC(head) { 182 194 return p 183 195 } 184 196 // head + tail == UNC, but joining two non-UNC paths should not result 185 197 // in a UNC path. Undo creation of UNC path. 186 - tail := Clean(strings.Join(elem[1:], string(Separator))) 187 - if head[len(head)-1] == Separator { 198 + tail := clean(strings.Join(elem[1:], string(windowsSeparator)), windows) 199 + if head[len(head)-1] == windowsSeparator { 188 200 return head + tail 189 201 } 190 - return head + string(Separator) + tail 202 + return head + string(windowsSeparator) + tail 191 203 } 192 204 193 205 // isUNC reports whether path is a UNC path. 194 206 func isUNC(path string) bool { 195 - return volumeNameLen(path) > 2 207 + return windows.volumeNameLen(path) > 2 196 208 } 197 209 198 - func sameWord(a, b string) bool { 210 + func (o windowsInfo) sameWord(a, b string) bool { 199 211 return strings.EqualFold(a, b) 200 212 }
+27 -435
pkg/path/testdata/path_windows_test.go
··· 1 + // Copyright 2020 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 1 15 // Copyright 2013 The Go Authors. All rights reserved. 2 16 // Use of this source code is governed by a BSD-style 3 17 // license that can be found in the LICENSE file. 4 18 5 - package filepath_test 19 + package path 6 20 7 21 import ( 8 22 "flag" 9 23 "fmt" 10 - "internal/testenv" 11 - "io/fs" 12 24 "io/ioutil" 13 - "os" 25 + goos "os" 14 26 "os/exec" 15 - "path/filepath" 16 27 "reflect" 17 - "runtime/debug" 18 28 "strings" 19 29 "testing" 20 30 ) 21 31 22 32 func TestWinSplitListTestsAreValid(t *testing.T) { 23 - comspec := os.Getenv("ComSpec") 33 + comspec := goos.Getenv("ComSpec") 24 34 if comspec == "" { 25 35 t.Fatal("%ComSpec% must be set") 26 36 } ··· 34 44 comspec string) { 35 45 36 46 const ( 37 - cmdfile = `printdir.cmd` 38 - perm fs.FileMode = 0700 47 + cmdfile = `printdir.cmd` 48 + perm = 0700 39 49 ) 40 50 41 51 tmp, err := ioutil.TempDir("", "testWinSplitListTestIsValid") 42 52 if err != nil { 43 53 t.Fatalf("TempDir failed: %v", err) 44 54 } 45 - defer os.RemoveAll(tmp) 55 + defer goos.RemoveAll(tmp) 46 56 47 57 for i, d := range tt.result { 48 58 if d == "" { 49 59 continue 50 60 } 51 - if cd := filepath.Clean(d); filepath.VolumeName(cd) != "" || 61 + if cd := Clean(d, Windows); VolumeName(cd, Windows) != "" || 52 62 cd[0] == '\\' || cd == ".." || (len(cd) >= 3 && cd[0:3] == `..\`) { 53 63 t.Errorf("%d,%d: %#q refers outside working directory", ti, i, d) 54 64 return 55 65 } 56 - dd := filepath.Join(tmp, d) 57 - if _, err := os.Stat(dd); err == nil { 66 + dd := Join([]string{tmp, d}, Windows) 67 + if _, err := goos.Stat(dd); err == nil { 58 68 t.Errorf("%d,%d: %#q already exists", ti, i, d) 59 69 return 60 70 } 61 - if err = os.MkdirAll(dd, perm); err != nil { 71 + if err = goos.MkdirAll(dd, perm); err != nil { 62 72 t.Errorf("%d,%d: MkdirAll(%#q) failed: %v", ti, i, dd, err) 63 73 return 64 74 } 65 - fn, data := filepath.Join(dd, cmdfile), []byte("@echo "+d+"\r\n") 75 + fn, data := Join([]string{dd, cmdfile}, Windows), []byte("@echo "+d+"\r\n") 66 76 if err = ioutil.WriteFile(fn, data, perm); err != nil { 67 77 t.Errorf("%d,%d: WriteFile(%#q) failed: %v", ti, i, fn, err) 68 78 return ··· 70 80 } 71 81 72 82 // on some systems, SystemRoot is required for cmd to work 73 - systemRoot := os.Getenv("SystemRoot") 83 + systemRoot := goos.Getenv("SystemRoot") 74 84 75 85 for i, d := range tt.result { 76 86 if d == "" { ··· 93 103 return 94 104 default: 95 105 // unshadow cmdfile in next directory 96 - err = os.Remove(filepath.Join(tmp, d, cmdfile)) 106 + err = goos.Remove(Join([]string{tmp, d, cmdfile}, Windows)) 97 107 if err != nil { 98 108 t.Fatalf("Remove test command failed: %v", err) 99 109 } ··· 101 111 } 102 112 } 103 113 104 - func TestWindowsEvalSymlinks(t *testing.T) { 105 - testenv.MustHaveSymlink(t) 106 - 107 - tmpDir, err := ioutil.TempDir("", "TestWindowsEvalSymlinks") 108 - if err != nil { 109 - t.Fatal(err) 110 - } 111 - defer os.RemoveAll(tmpDir) 112 - 113 - // /tmp may itself be a symlink! Avoid the confusion, although 114 - // it means trusting the thing we're testing. 115 - tmpDir, err = filepath.EvalSymlinks(tmpDir) 116 - if err != nil { 117 - t.Fatal(err) 118 - } 119 - 120 - if len(tmpDir) < 3 { 121 - t.Fatalf("tmpDir path %q is too short", tmpDir) 122 - } 123 - if tmpDir[1] != ':' { 124 - t.Fatalf("tmpDir path %q must have drive letter in it", tmpDir) 125 - } 126 - test := EvalSymlinksTest{"test/linkabswin", tmpDir[:3]} 127 - 128 - // Create the symlink farm using relative paths. 129 - testdirs := append(EvalSymlinksTestDirs, test) 130 - for _, d := range testdirs { 131 - var err error 132 - path := simpleJoin(tmpDir, d.path) 133 - if d.dest == "" { 134 - err = os.Mkdir(path, 0755) 135 - } else { 136 - err = os.Symlink(d.dest, path) 137 - } 138 - if err != nil { 139 - t.Fatal(err) 140 - } 141 - } 142 - 143 - path := simpleJoin(tmpDir, test.path) 144 - 145 - testEvalSymlinks(t, path, test.dest) 146 - 147 - testEvalSymlinksAfterChdir(t, path, ".", test.dest) 148 - 149 - testEvalSymlinksAfterChdir(t, 150 - path, 151 - filepath.VolumeName(tmpDir)+".", 152 - test.dest) 153 - 154 - testEvalSymlinksAfterChdir(t, 155 - simpleJoin(tmpDir, "test"), 156 - simpleJoin("..", test.path), 157 - test.dest) 158 - 159 - testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest) 160 - } 161 - 162 - // TestEvalSymlinksCanonicalNames verify that EvalSymlinks 163 - // returns "canonical" path names on windows. 164 - func TestEvalSymlinksCanonicalNames(t *testing.T) { 165 - tmp, err := ioutil.TempDir("", "evalsymlinkcanonical") 166 - if err != nil { 167 - t.Fatal("creating temp dir:", err) 168 - } 169 - defer os.RemoveAll(tmp) 170 - 171 - // ioutil.TempDir might return "non-canonical" name. 172 - cTmpName, err := filepath.EvalSymlinks(tmp) 173 - if err != nil { 174 - t.Errorf("EvalSymlinks(%q) error: %v", tmp, err) 175 - } 176 - 177 - dirs := []string{ 178 - "test", 179 - "test/dir", 180 - "testing_long_dir", 181 - "TEST2", 182 - } 183 - 184 - for _, d := range dirs { 185 - dir := filepath.Join(cTmpName, d) 186 - err := os.Mkdir(dir, 0755) 187 - if err != nil { 188 - t.Fatal(err) 189 - } 190 - cname, err := filepath.EvalSymlinks(dir) 191 - if err != nil { 192 - t.Errorf("EvalSymlinks(%q) error: %v", dir, err) 193 - continue 194 - } 195 - if dir != cname { 196 - t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", dir, cname, dir) 197 - continue 198 - } 199 - // test non-canonical names 200 - test := strings.ToUpper(dir) 201 - p, err := filepath.EvalSymlinks(test) 202 - if err != nil { 203 - t.Errorf("EvalSymlinks(%q) error: %v", test, err) 204 - continue 205 - } 206 - if p != cname { 207 - t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", test, p, cname) 208 - continue 209 - } 210 - // another test 211 - test = strings.ToLower(dir) 212 - p, err = filepath.EvalSymlinks(test) 213 - if err != nil { 214 - t.Errorf("EvalSymlinks(%q) error: %v", test, err) 215 - continue 216 - } 217 - if p != cname { 218 - t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", test, p, cname) 219 - continue 220 - } 221 - } 222 - } 223 - 224 114 // checkVolume8dot3Setting runs "fsutil 8dot3name query c:" command 225 115 // (where c: is vol parameter) to discover "8dot3 name creation state". 226 116 // The state is combination of 2 flags. The global flag controls if it ··· 287 177 } 288 178 289 179 var runFSModifyTests = flag.Bool("run_fs_modify_tests", false, "run tests which modify filesystem parameters") 290 - 291 - // This test assumes registry state of NtfsDisable8dot3NameCreation is 2, 292 - // the default (Volume level setting). 293 - func TestEvalSymlinksCanonicalNamesWith8dot3Disabled(t *testing.T) { 294 - if !*runFSModifyTests { 295 - t.Skip("skipping test that modifies file system setting; enable with -run_fs_modify_tests") 296 - } 297 - tempVol := filepath.VolumeName(os.TempDir()) 298 - if len(tempVol) != 2 { 299 - t.Fatalf("unexpected temp volume name %q", tempVol) 300 - } 301 - 302 - err := checkVolume8dot3Setting(tempVol, true) 303 - if err != nil { 304 - t.Fatal(err) 305 - } 306 - err = setVolume8dot3Setting(tempVol, false) 307 - if err != nil { 308 - t.Fatal(err) 309 - } 310 - defer func() { 311 - err := setVolume8dot3Setting(tempVol, true) 312 - if err != nil { 313 - t.Fatal(err) 314 - } 315 - err = checkVolume8dot3Setting(tempVol, true) 316 - if err != nil { 317 - t.Fatal(err) 318 - } 319 - }() 320 - err = checkVolume8dot3Setting(tempVol, false) 321 - if err != nil { 322 - t.Fatal(err) 323 - } 324 - TestEvalSymlinksCanonicalNames(t) 325 - } 326 - 327 - func TestToNorm(t *testing.T) { 328 - stubBase := func(path string) (string, error) { 329 - vol := filepath.VolumeName(path) 330 - path = path[len(vol):] 331 - 332 - if strings.Contains(path, "/") { 333 - return "", fmt.Errorf("invalid path is given to base: %s", vol+path) 334 - } 335 - 336 - if path == "" || path == "." || path == `\` { 337 - return "", fmt.Errorf("invalid path is given to base: %s", vol+path) 338 - } 339 - 340 - i := strings.LastIndexByte(path, filepath.Separator) 341 - if i == len(path)-1 { // trailing '\' is invalid 342 - return "", fmt.Errorf("invalid path is given to base: %s", vol+path) 343 - } 344 - if i == -1 { 345 - return strings.ToUpper(path), nil 346 - } 347 - 348 - return strings.ToUpper(path[i+1:]), nil 349 - } 350 - 351 - // On this test, toNorm should be same as string.ToUpper(filepath.Clean(path)) except empty string. 352 - tests := []struct { 353 - arg string 354 - want string 355 - }{ 356 - {"", ""}, 357 - {".", "."}, 358 - {"./foo/bar", `FOO\BAR`}, 359 - {"/", `\`}, 360 - {"/foo/bar", `\FOO\BAR`}, 361 - {"/foo/bar/baz/qux", `\FOO\BAR\BAZ\QUX`}, 362 - {"foo/bar", `FOO\BAR`}, 363 - {"C:/foo/bar", `C:\FOO\BAR`}, 364 - {"C:foo/bar", `C:FOO\BAR`}, 365 - {"c:/foo/bar", `C:\FOO\BAR`}, 366 - {"C:/foo/bar", `C:\FOO\BAR`}, 367 - {"C:/foo/bar/", `C:\FOO\BAR`}, 368 - {`C:\foo\bar`, `C:\FOO\BAR`}, 369 - {`C:\foo/bar\`, `C:\FOO\BAR`}, 370 - {"C:/ふー/バー", `C:\ふー\バー`}, 371 - } 372 - 373 - for _, test := range tests { 374 - got, err := filepath.ToNorm(test.arg, stubBase) 375 - if err != nil { 376 - t.Errorf("toNorm(%s) failed: %v\n", test.arg, err) 377 - } else if got != test.want { 378 - t.Errorf("toNorm(%s) returns %s, but %s expected\n", test.arg, got, test.want) 379 - } 380 - } 381 - 382 - testPath := `{{tmp}}\test\foo\bar` 383 - 384 - testsDir := []struct { 385 - wd string 386 - arg string 387 - want string 388 - }{ 389 - // test absolute paths 390 - {".", `{{tmp}}\test\foo\bar`, `{{tmp}}\test\foo\bar`}, 391 - {".", `{{tmp}}\.\test/foo\bar`, `{{tmp}}\test\foo\bar`}, 392 - {".", `{{tmp}}\test\..\test\foo\bar`, `{{tmp}}\test\foo\bar`}, 393 - {".", `{{tmp}}\TEST\FOO\BAR`, `{{tmp}}\test\foo\bar`}, 394 - 395 - // test relative paths begin with drive letter 396 - {`{{tmp}}\test`, `{{tmpvol}}.`, `{{tmpvol}}.`}, 397 - {`{{tmp}}\test`, `{{tmpvol}}..`, `{{tmpvol}}..`}, 398 - {`{{tmp}}\test`, `{{tmpvol}}foo\bar`, `{{tmpvol}}foo\bar`}, 399 - {`{{tmp}}\test`, `{{tmpvol}}.\foo\bar`, `{{tmpvol}}foo\bar`}, 400 - {`{{tmp}}\test`, `{{tmpvol}}foo\..\foo\bar`, `{{tmpvol}}foo\bar`}, 401 - {`{{tmp}}\test`, `{{tmpvol}}FOO\BAR`, `{{tmpvol}}foo\bar`}, 402 - 403 - // test relative paths begin with '\' 404 - {"{{tmp}}", `{{tmpnovol}}\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`}, 405 - {"{{tmp}}", `{{tmpnovol}}\.\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`}, 406 - {"{{tmp}}", `{{tmpnovol}}\test\..\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`}, 407 - {"{{tmp}}", `{{tmpnovol}}\TEST\FOO\BAR`, `{{tmpnovol}}\test\foo\bar`}, 408 - 409 - // test relative paths begin without '\' 410 - {`{{tmp}}\test`, ".", `.`}, 411 - {`{{tmp}}\test`, "..", `..`}, 412 - {`{{tmp}}\test`, `foo\bar`, `foo\bar`}, 413 - {`{{tmp}}\test`, `.\foo\bar`, `foo\bar`}, 414 - {`{{tmp}}\test`, `foo\..\foo\bar`, `foo\bar`}, 415 - {`{{tmp}}\test`, `FOO\BAR`, `foo\bar`}, 416 - 417 - // test UNC paths 418 - {".", `\\localhost\c$`, `\\localhost\c$`}, 419 - } 420 - 421 - tmp, err := ioutil.TempDir("", "testToNorm") 422 - if err != nil { 423 - t.Fatal(err) 424 - } 425 - defer func() { 426 - err := os.RemoveAll(tmp) 427 - if err != nil { 428 - t.Fatal(err) 429 - } 430 - }() 431 - 432 - // ioutil.TempDir might return "non-canonical" name. 433 - ctmp, err := filepath.EvalSymlinks(tmp) 434 - if err != nil { 435 - t.Fatal(err) 436 - } 437 - 438 - err = os.MkdirAll(strings.ReplaceAll(testPath, "{{tmp}}", ctmp), 0777) 439 - if err != nil { 440 - t.Fatal(err) 441 - } 442 - 443 - cwd, err := os.Getwd() 444 - if err != nil { 445 - t.Fatal(err) 446 - } 447 - defer func() { 448 - err := os.Chdir(cwd) 449 - if err != nil { 450 - t.Fatal(err) 451 - } 452 - }() 453 - 454 - tmpVol := filepath.VolumeName(ctmp) 455 - if len(tmpVol) != 2 { 456 - t.Fatalf("unexpected temp volume name %q", tmpVol) 457 - } 458 - 459 - tmpNoVol := ctmp[len(tmpVol):] 460 - 461 - replacer := strings.NewReplacer("{{tmp}}", ctmp, "{{tmpvol}}", tmpVol, "{{tmpnovol}}", tmpNoVol) 462 - 463 - for _, test := range testsDir { 464 - wd := replacer.Replace(test.wd) 465 - arg := replacer.Replace(test.arg) 466 - want := replacer.Replace(test.want) 467 - 468 - if test.wd == "." { 469 - err := os.Chdir(cwd) 470 - if err != nil { 471 - t.Error(err) 472 - 473 - continue 474 - } 475 - } else { 476 - err := os.Chdir(wd) 477 - if err != nil { 478 - t.Error(err) 479 - 480 - continue 481 - } 482 - } 483 - 484 - got, err := filepath.ToNorm(arg, filepath.NormBase) 485 - if err != nil { 486 - t.Errorf("toNorm(%s) failed: %v (wd=%s)\n", arg, err, wd) 487 - } else if got != want { 488 - t.Errorf("toNorm(%s) returns %s, but %s expected (wd=%s)\n", arg, got, want, wd) 489 - } 490 - } 491 - } 492 - 493 - func TestUNC(t *testing.T) { 494 - // Test that this doesn't go into an infinite recursion. 495 - // See golang.org/issue/15879. 496 - defer debug.SetMaxStack(debug.SetMaxStack(1e6)) 497 - filepath.Glob(`\\?\c:\*`) 498 - } 499 - 500 - func testWalkMklink(t *testing.T, linktype string) { 501 - output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output() 502 - if !strings.Contains(string(output), fmt.Sprintf(" /%s ", linktype)) { 503 - t.Skipf(`skipping test; mklink does not supports /%s parameter`, linktype) 504 - } 505 - testWalkSymlink(t, func(target, link string) error { 506 - output, err := exec.Command("cmd", "/c", "mklink", "/"+linktype, link, target).CombinedOutput() 507 - if err != nil { 508 - return fmt.Errorf(`"mklink /%s %v %v" command failed: %v\n%v`, linktype, link, target, err, string(output)) 509 - } 510 - return nil 511 - }) 512 - } 513 - 514 - func TestWalkDirectoryJunction(t *testing.T) { 515 - testenv.MustHaveSymlink(t) 516 - testWalkMklink(t, "J") 517 - } 518 - 519 - func TestWalkDirectorySymlink(t *testing.T) { 520 - testenv.MustHaveSymlink(t) 521 - testWalkMklink(t, "D") 522 - } 523 - 524 - func TestNTNamespaceSymlink(t *testing.T) { 525 - output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output() 526 - if !strings.Contains(string(output), " /J ") { 527 - t.Skip("skipping test because mklink command does not support junctions") 528 - } 529 - 530 - tmpdir, err := ioutil.TempDir("", "TestNTNamespaceSymlink") 531 - if err != nil { 532 - t.Fatal(err) 533 - } 534 - defer os.RemoveAll(tmpdir) 535 - 536 - // Make sure tmpdir is not a symlink, otherwise tests will fail. 537 - tmpdir, err = filepath.EvalSymlinks(tmpdir) 538 - if err != nil { 539 - t.Fatal(err) 540 - } 541 - 542 - vol := filepath.VolumeName(tmpdir) 543 - output, err = exec.Command("cmd", "/c", "mountvol", vol, "/L").CombinedOutput() 544 - if err != nil { 545 - t.Fatalf("failed to run mountvol %v /L: %v %q", vol, err, output) 546 - } 547 - target := strings.Trim(string(output), " \n\r") 548 - 549 - dirlink := filepath.Join(tmpdir, "dirlink") 550 - output, err = exec.Command("cmd", "/c", "mklink", "/J", dirlink, target).CombinedOutput() 551 - if err != nil { 552 - t.Fatalf("failed to run mklink %v %v: %v %q", dirlink, target, err, output) 553 - } 554 - 555 - got, err := filepath.EvalSymlinks(dirlink) 556 - if err != nil { 557 - t.Fatal(err) 558 - } 559 - if want := vol + `\`; got != want { 560 - t.Errorf(`EvalSymlinks(%q): got %q, want %q`, dirlink, got, want) 561 - } 562 - 563 - // Make sure we have sufficient privilege to run mklink command. 564 - testenv.MustHaveSymlink(t) 565 - 566 - file := filepath.Join(tmpdir, "file") 567 - err = ioutil.WriteFile(file, []byte(""), 0666) 568 - if err != nil { 569 - t.Fatal(err) 570 - } 571 - 572 - target += file[len(filepath.VolumeName(file)):] 573 - 574 - filelink := filepath.Join(tmpdir, "filelink") 575 - output, err = exec.Command("cmd", "/c", "mklink", filelink, target).CombinedOutput() 576 - if err != nil { 577 - t.Fatalf("failed to run mklink %v %v: %v %q", filelink, target, err, output) 578 - } 579 - 580 - got, err = filepath.EvalSymlinks(filelink) 581 - if err != nil { 582 - t.Fatal(err) 583 - } 584 - if want := file; got != want { 585 - t.Errorf(`EvalSymlinks(%q): got %q, want %q`, filelink, got, want) 586 - } 587 - }
+25
pkg/path/testdata/pathtxtar_test.go
··· 1 + // Copyright 2020 CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package path_test 16 + 17 + import ( 18 + "testing" 19 + 20 + "cuelang.org/go/pkg/internal/builtintest" 21 + ) 22 + 23 + func TestBuiltin(t *testing.T) { 24 + builtintest.Run("path", t) 25 + }
-147
pkg/path/testdata/symlink.go
··· 1 - // Copyright 2012 The Go Authors. All rights reserved. 2 - // Use of this source code is governed by a BSD-style 3 - // license that can be found in the LICENSE file. 4 - 5 - package filepath 6 - 7 - import ( 8 - "errors" 9 - "io/fs" 10 - "os" 11 - "runtime" 12 - "syscall" 13 - ) 14 - 15 - func walkSymlinks(path string) (string, error) { 16 - volLen := volumeNameLen(path) 17 - pathSeparator := string(os.PathSeparator) 18 - 19 - if volLen < len(path) && os.IsPathSeparator(path[volLen]) { 20 - volLen++ 21 - } 22 - vol := path[:volLen] 23 - dest := vol 24 - linksWalked := 0 25 - for start, end := volLen, volLen; start < len(path); start = end { 26 - for start < len(path) && os.IsPathSeparator(path[start]) { 27 - start++ 28 - } 29 - end = start 30 - for end < len(path) && !os.IsPathSeparator(path[end]) { 31 - end++ 32 - } 33 - 34 - // On Windows, "." can be a symlink. 35 - // We look it up, and use the value if it is absolute. 36 - // If not, we just return ".". 37 - isWindowsDot := runtime.GOOS == "windows" && path[volumeNameLen(path):] == "." 38 - 39 - // The next path component is in path[start:end]. 40 - if end == start { 41 - // No more path components. 42 - break 43 - } else if path[start:end] == "." && !isWindowsDot { 44 - // Ignore path component ".". 45 - continue 46 - } else if path[start:end] == ".." { 47 - // Back up to previous component if possible. 48 - // Note that volLen includes any leading slash. 49 - 50 - // Set r to the index of the last slash in dest, 51 - // after the volume. 52 - var r int 53 - for r = len(dest) - 1; r >= volLen; r-- { 54 - if os.IsPathSeparator(dest[r]) { 55 - break 56 - } 57 - } 58 - if r < volLen || dest[r+1:] == ".." { 59 - // Either path has no slashes 60 - // (it's empty or just "C:") 61 - // or it ends in a ".." we had to keep. 62 - // Either way, keep this "..". 63 - if len(dest) > volLen { 64 - dest += pathSeparator 65 - } 66 - dest += ".." 67 - } else { 68 - // Discard everything since the last slash. 69 - dest = dest[:r] 70 - } 71 - continue 72 - } 73 - 74 - // Ordinary path component. Add it to result. 75 - 76 - if len(dest) > volumeNameLen(dest) && !os.IsPathSeparator(dest[len(dest)-1]) { 77 - dest += pathSeparator 78 - } 79 - 80 - dest += path[start:end] 81 - 82 - // Resolve symlink. 83 - 84 - fi, err := os.Lstat(dest) 85 - if err != nil { 86 - return "", err 87 - } 88 - 89 - if fi.Mode()&fs.ModeSymlink == 0 { 90 - if !fi.Mode().IsDir() && end < len(path) { 91 - return "", syscall.ENOTDIR 92 - } 93 - continue 94 - } 95 - 96 - // Found symlink. 97 - 98 - linksWalked++ 99 - if linksWalked > 255 { 100 - return "", errors.New("EvalSymlinks: too many links") 101 - } 102 - 103 - link, err := os.Readlink(dest) 104 - if err != nil { 105 - return "", err 106 - } 107 - 108 - if isWindowsDot && !IsAbs(link) { 109 - // On Windows, if "." is a relative symlink, 110 - // just return ".". 111 - break 112 - } 113 - 114 - path = link + path[end:] 115 - 116 - v := volumeNameLen(link) 117 - if v > 0 { 118 - // Symlink to drive name is an absolute path. 119 - if v < len(link) && os.IsPathSeparator(link[v]) { 120 - v++ 121 - } 122 - vol = link[:v] 123 - dest = vol 124 - end = len(vol) 125 - } else if len(link) > 0 && os.IsPathSeparator(link[0]) { 126 - // Symlink to absolute path. 127 - dest = link[:1] 128 - end = 1 129 - } else { 130 - // Symlink to relative path; replace last 131 - // path component in dest. 132 - var r int 133 - for r = len(dest) - 1; r >= volLen; r-- { 134 - if os.IsPathSeparator(dest[r]) { 135 - break 136 - } 137 - } 138 - if r < volLen { 139 - dest = vol 140 - } else { 141 - dest = dest[:r] 142 - } 143 - end = 0 144 - } 145 - } 146 - return Clean(dest), nil 147 - }