this repo has no description
0
fork

Configure Feed

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

pkg/path/testdata: stage filepath files from Go

As we are going to support functions for all OSes, we
cannot rely on the current Go API. We copy the
implementation and modify.

This is a simple copy from tip in 1.15.
Modifications are done in followup CLs and files
will eventually be moved to pkg/path after
modifications.

Change-Id: I8f0feffab6c711ae78a1c6b63e370229a9e7cbe1
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7843
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>

+4326
+20
pkg/path/testdata/example_test.go
··· 1 + // Copyright 2017 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_test 6 + 7 + import ( 8 + "fmt" 9 + "path/filepath" 10 + ) 11 + 12 + 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")) 16 + // Output: 17 + // No dots: "" 18 + // One dot: ".js" 19 + // Two dots: ".js" 20 + }
+171
pkg/path/testdata/example_unix_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 + // +build !windows,!plan9 6 + 7 + package filepath_test 8 + 9 + import ( 10 + "fmt" 11 + "path/filepath" 12 + ) 13 + 14 + func ExampleSplitList() { 15 + fmt.Println("On Unix:", filepath.SplitList("/a/b/c:/usr/bin")) 16 + // Output: 17 + // On Unix: [/a/b/c /usr/bin] 18 + } 19 + 20 + func ExampleRel() { 21 + paths := []string{ 22 + "/a/b/c", 23 + "/b/c", 24 + "./b/c", 25 + } 26 + base := "/a" 27 + 28 + fmt.Println("On Unix:") 29 + for _, p := range paths { 30 + rel, err := filepath.Rel(base, p) 31 + fmt.Printf("%q: %q %v\n", p, rel, err) 32 + } 33 + 34 + // Output: 35 + // On Unix: 36 + // "/a/b/c": "b/c" <nil> 37 + // "/b/c": "../b/c" <nil> 38 + // "./b/c": "" Rel: can't make ./b/c relative to /a 39 + } 40 + 41 + func ExampleSplit() { 42 + paths := []string{ 43 + "/home/arnie/amelia.jpg", 44 + "/mnt/photos/", 45 + "rabbit.jpg", 46 + "/usr/local//go", 47 + } 48 + fmt.Println("On Unix:") 49 + 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) 52 + } 53 + // Output: 54 + // On Unix: 55 + // input: "/home/arnie/amelia.jpg" 56 + // dir: "/home/arnie/" 57 + // file: "amelia.jpg" 58 + // input: "/mnt/photos/" 59 + // dir: "/mnt/photos/" 60 + // file: "" 61 + // input: "rabbit.jpg" 62 + // dir: "" 63 + // file: "rabbit.jpg" 64 + // input: "/usr/local//go" 65 + // dir: "/usr/local//" 66 + // file: "go" 67 + } 68 + 69 + func ExampleJoin() { 70 + 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")) 75 + 76 + fmt.Println(filepath.Join("a/b", "../../../xyz")) 77 + 78 + // Output: 79 + // On Unix: 80 + // a/b/c 81 + // a/b/c 82 + // a/b/c 83 + // a/b/c 84 + // ../xyz 85 + } 86 + 87 + func ExampleMatch() { 88 + 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/*")) 93 + 94 + // Output: 95 + // On Unix: 96 + // true <nil> 97 + // false <nil> 98 + // true <nil> 99 + // true <nil> 100 + } 101 + 102 + func ExampleBase() { 103 + 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("")) 113 + 114 + // Output: 115 + // On Unix: 116 + // baz.js 117 + // baz 118 + // baz 119 + // dev.txt 120 + // todo.txt 121 + // .. 122 + // . 123 + // / 124 + // . 125 + } 126 + 127 + func ExampleDir() { 128 + 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("")) 139 + 140 + // Output: 141 + // On Unix: 142 + // /foo/bar 143 + // /foo/bar 144 + // /foo/bar/baz 145 + // /dirty/path 146 + // . 147 + // .. 148 + // . 149 + // . 150 + // / 151 + // . 152 + } 153 + 154 + func ExampleIsAbs() { 155 + 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("")) 162 + 163 + // Output: 164 + // On Unix: 165 + // true 166 + // false 167 + // false 168 + // false 169 + // true 170 + // false 171 + }
+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 + )
+360
pkg/path/testdata/match.go
··· 1 + // Copyright 2010 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 + "os" 10 + "runtime" 11 + "sort" 12 + "strings" 13 + "unicode/utf8" 14 + ) 15 + 16 + // ErrBadPattern indicates a pattern was malformed. 17 + var ErrBadPattern = errors.New("syntax error in pattern") 18 + 19 + // Match reports whether name matches the shell file name pattern. 20 + // The pattern syntax is: 21 + // 22 + // pattern: 23 + // { term } 24 + // term: 25 + // '*' matches any sequence of non-Separator characters 26 + // '?' matches any single non-Separator character 27 + // '[' [ '^' ] { character-range } ']' 28 + // character class (must be non-empty) 29 + // c matches character c (c != '*', '?', '\\', '[') 30 + // '\\' c matches character c 31 + // 32 + // character-range: 33 + // c matches character c (c != '\\', '-', ']') 34 + // '\\' c matches character c 35 + // lo '-' hi matches character c for lo <= c <= hi 36 + // 37 + // Match requires pattern to match all of name, not just a substring. 38 + // The only possible returned error is ErrBadPattern, when pattern 39 + // is malformed. 40 + // 41 + // On Windows, escaping is disabled. Instead, '\\' is treated as 42 + // path separator. 43 + // 44 + func Match(pattern, name string) (matched bool, err error) { 45 + Pattern: 46 + for len(pattern) > 0 { 47 + var star bool 48 + var chunk string 49 + star, chunk, pattern = scanChunk(pattern) 50 + if star && chunk == "" { 51 + // Trailing * matches rest of string unless it has a /. 52 + return !strings.Contains(name, string(Separator)), nil 53 + } 54 + // Look for match at current position. 55 + t, ok, err := matchChunk(chunk, name) 56 + // if we're the last chunk, make sure we've exhausted the name 57 + // otherwise we'll give a false result even if we could still match 58 + // using the star 59 + if ok && (len(t) == 0 || len(pattern) > 0) { 60 + name = t 61 + continue 62 + } 63 + if err != nil { 64 + return false, err 65 + } 66 + if star { 67 + // Look for match skipping i+1 bytes. 68 + // Cannot skip /. 69 + for i := 0; i < len(name) && name[i] != Separator; i++ { 70 + t, ok, err := matchChunk(chunk, name[i+1:]) 71 + if ok { 72 + // if we're the last chunk, make sure we exhausted the name 73 + if len(pattern) == 0 && len(t) > 0 { 74 + continue 75 + } 76 + name = t 77 + continue Pattern 78 + } 79 + if err != nil { 80 + return false, err 81 + } 82 + } 83 + } 84 + return false, nil 85 + } 86 + return len(name) == 0, nil 87 + } 88 + 89 + // scanChunk gets the next segment of pattern, which is a non-star string 90 + // possibly preceded by a star. 91 + func scanChunk(pattern string) (star bool, chunk, rest string) { 92 + for len(pattern) > 0 && pattern[0] == '*' { 93 + pattern = pattern[1:] 94 + star = true 95 + } 96 + inrange := false 97 + var i int 98 + Scan: 99 + for i = 0; i < len(pattern); i++ { 100 + switch pattern[i] { 101 + case '\\': 102 + if runtime.GOOS != "windows" { 103 + // error check handled in matchChunk: bad pattern. 104 + if i+1 < len(pattern) { 105 + i++ 106 + } 107 + } 108 + case '[': 109 + inrange = true 110 + case ']': 111 + inrange = false 112 + case '*': 113 + if !inrange { 114 + break Scan 115 + } 116 + } 117 + } 118 + return star, pattern[0:i], pattern[i:] 119 + } 120 + 121 + // matchChunk checks whether chunk matches the beginning of s. 122 + // If so, it returns the remainder of s (after the match). 123 + // Chunk is all single-character operators: literals, char classes, and ?. 124 + func matchChunk(chunk, s string) (rest string, ok bool, err error) { 125 + // failed records whether the match has failed. 126 + // After the match fails, the loop continues on processing chunk, 127 + // checking that the pattern is well-formed but no longer reading s. 128 + failed := false 129 + for len(chunk) > 0 { 130 + if !failed && len(s) == 0 { 131 + failed = true 132 + } 133 + switch chunk[0] { 134 + case '[': 135 + // character class 136 + var r rune 137 + if !failed { 138 + var n int 139 + r, n = utf8.DecodeRuneInString(s) 140 + s = s[n:] 141 + } 142 + chunk = chunk[1:] 143 + // possibly negated 144 + negated := false 145 + if len(chunk) > 0 && chunk[0] == '^' { 146 + negated = true 147 + chunk = chunk[1:] 148 + } 149 + // parse all ranges 150 + match := false 151 + nrange := 0 152 + for { 153 + if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 { 154 + chunk = chunk[1:] 155 + break 156 + } 157 + var lo, hi rune 158 + if lo, chunk, err = getEsc(chunk); err != nil { 159 + return "", false, err 160 + } 161 + hi = lo 162 + if chunk[0] == '-' { 163 + if hi, chunk, err = getEsc(chunk[1:]); err != nil { 164 + return "", false, err 165 + } 166 + } 167 + if lo <= r && r <= hi { 168 + match = true 169 + } 170 + nrange++ 171 + } 172 + if match == negated { 173 + failed = true 174 + } 175 + 176 + case '?': 177 + if !failed { 178 + if s[0] == Separator { 179 + failed = true 180 + } 181 + _, n := utf8.DecodeRuneInString(s) 182 + s = s[n:] 183 + } 184 + chunk = chunk[1:] 185 + 186 + case '\\': 187 + if runtime.GOOS != "windows" { 188 + chunk = chunk[1:] 189 + if len(chunk) == 0 { 190 + return "", false, ErrBadPattern 191 + } 192 + } 193 + fallthrough 194 + 195 + default: 196 + if !failed { 197 + if chunk[0] != s[0] { 198 + failed = true 199 + } 200 + s = s[1:] 201 + } 202 + chunk = chunk[1:] 203 + } 204 + } 205 + if failed { 206 + return "", false, nil 207 + } 208 + return s, true, nil 209 + } 210 + 211 + // getEsc gets a possibly-escaped character from chunk, for a character class. 212 + func getEsc(chunk string) (r rune, nchunk string, err error) { 213 + if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { 214 + err = ErrBadPattern 215 + return 216 + } 217 + if chunk[0] == '\\' && runtime.GOOS != "windows" { 218 + chunk = chunk[1:] 219 + if len(chunk) == 0 { 220 + err = ErrBadPattern 221 + return 222 + } 223 + } 224 + r, n := utf8.DecodeRuneInString(chunk) 225 + if r == utf8.RuneError && n == 1 { 226 + err = ErrBadPattern 227 + } 228 + nchunk = chunk[n:] 229 + if len(nchunk) == 0 { 230 + err = ErrBadPattern 231 + } 232 + return 233 + } 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 + }
+393
pkg/path/testdata/match_test.go
··· 1 + // Copyright 2009 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_test 6 + 7 + import ( 8 + "fmt" 9 + "internal/testenv" 10 + "io/ioutil" 11 + "os" 12 + . "path/filepath" 13 + "reflect" 14 + "runtime" 15 + "sort" 16 + "strings" 17 + "testing" 18 + ) 19 + 20 + type MatchTest struct { 21 + pattern, s string 22 + match bool 23 + err error 24 + } 25 + 26 + var matchTests = []MatchTest{ 27 + {"abc", "abc", true, nil}, 28 + {"*", "abc", true, nil}, 29 + {"*c", "abc", true, nil}, 30 + {"a*", "a", true, nil}, 31 + {"a*", "abc", true, nil}, 32 + {"a*", "ab/c", false, nil}, 33 + {"a*/b", "abc/b", true, nil}, 34 + {"a*/b", "a/c/b", false, nil}, 35 + {"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil}, 36 + {"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil}, 37 + {"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil}, 38 + {"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil}, 39 + {"a*b?c*x", "abxbbxdbxebxczzx", true, nil}, 40 + {"a*b?c*x", "abxbbxdbxebxczzy", false, nil}, 41 + {"ab[c]", "abc", true, nil}, 42 + {"ab[b-d]", "abc", true, nil}, 43 + {"ab[e-g]", "abc", false, nil}, 44 + {"ab[^c]", "abc", false, nil}, 45 + {"ab[^b-d]", "abc", false, nil}, 46 + {"ab[^e-g]", "abc", true, nil}, 47 + {"a\\*b", "a*b", true, nil}, 48 + {"a\\*b", "ab", false, nil}, 49 + {"a?b", "a☺b", true, nil}, 50 + {"a[^a]b", "a☺b", true, nil}, 51 + {"a???b", "a☺b", false, nil}, 52 + {"a[^a][^a][^a]b", "a☺b", false, nil}, 53 + {"[a-ζ]*", "α", true, nil}, 54 + {"*[a-ζ]", "A", false, nil}, 55 + {"a?b", "a/b", false, nil}, 56 + {"a*b", "a/b", false, nil}, 57 + {"[\\]a]", "]", true, nil}, 58 + {"[\\-]", "-", true, nil}, 59 + {"[x\\-]", "x", true, nil}, 60 + {"[x\\-]", "-", true, nil}, 61 + {"[x\\-]", "z", false, nil}, 62 + {"[\\-x]", "x", true, nil}, 63 + {"[\\-x]", "-", true, nil}, 64 + {"[\\-x]", "a", false, nil}, 65 + {"[]a]", "]", false, ErrBadPattern}, 66 + {"[-]", "-", false, ErrBadPattern}, 67 + {"[x-]", "x", false, ErrBadPattern}, 68 + {"[x-]", "-", false, ErrBadPattern}, 69 + {"[x-]", "z", false, ErrBadPattern}, 70 + {"[-x]", "x", false, ErrBadPattern}, 71 + {"[-x]", "-", false, ErrBadPattern}, 72 + {"[-x]", "a", false, ErrBadPattern}, 73 + {"\\", "a", false, ErrBadPattern}, 74 + {"[a-b-c]", "a", false, ErrBadPattern}, 75 + {"[", "a", false, ErrBadPattern}, 76 + {"[^", "a", false, ErrBadPattern}, 77 + {"[^bc", "a", false, ErrBadPattern}, 78 + {"a[", "a", false, ErrBadPattern}, 79 + {"a[", "ab", false, ErrBadPattern}, 80 + {"a[", "x", false, ErrBadPattern}, 81 + {"a/b[", "x", false, ErrBadPattern}, 82 + {"*x", "xxx", true, nil}, 83 + } 84 + 85 + func errp(e error) string { 86 + if e == nil { 87 + return "<nil>" 88 + } 89 + return e.Error() 90 + } 91 + 92 + 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 100 + } 101 + pattern = Clean(pattern) 102 + s = Clean(s) 103 + } 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 + } 393 + }
+596
pkg/path/testdata/path.go
··· 1 + // Copyright 2009 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 implements utility routines for manipulating filename paths 6 + // in a way compatible with the target operating system-defined file paths. 7 + // 8 + // The filepath package uses either forward slashes or backslashes, 9 + // depending on the operating system. To process paths such as URLs 10 + // that always use forward slashes regardless of the operating 11 + // system, see the path package. 12 + package filepath 13 + 14 + import ( 15 + "errors" 16 + "io/fs" 17 + "os" 18 + "sort" 19 + "strings" 20 + ) 21 + 22 + // A lazybuf is a lazily constructed path buffer. 23 + // It supports append, reading previously appended bytes, 24 + // and retrieving the final string. It does not allocate a buffer 25 + // to hold the output until that output diverges from s. 26 + type lazybuf struct { 27 + path string 28 + buf []byte 29 + w int 30 + volAndPath string 31 + volLen int 32 + } 33 + 34 + func (b *lazybuf) index(i int) byte { 35 + if b.buf != nil { 36 + return b.buf[i] 37 + } 38 + return b.path[i] 39 + } 40 + 41 + func (b *lazybuf) append(c byte) { 42 + if b.buf == nil { 43 + if b.w < len(b.path) && b.path[b.w] == c { 44 + b.w++ 45 + return 46 + } 47 + b.buf = make([]byte, len(b.path)) 48 + copy(b.buf, b.path[:b.w]) 49 + } 50 + b.buf[b.w] = c 51 + b.w++ 52 + } 53 + 54 + func (b *lazybuf) string() string { 55 + if b.buf == nil { 56 + return b.volAndPath[:b.volLen+b.w] 57 + } 58 + return b.volAndPath[:b.volLen] + string(b.buf[:b.w]) 59 + } 60 + 61 + const ( 62 + Separator = os.PathSeparator 63 + ListSeparator = os.PathListSeparator 64 + ) 65 + 66 + // Clean returns the shortest path name equivalent to path 67 + // by purely lexical processing. It applies the following rules 68 + // iteratively until no further processing can be done: 69 + // 70 + // 1. Replace multiple Separator elements with a single one. 71 + // 2. Eliminate each . path name element (the current directory). 72 + // 3. Eliminate each inner .. path name element (the parent directory) 73 + // along with the non-.. element that precedes it. 74 + // 4. Eliminate .. elements that begin a rooted path: 75 + // that is, replace "/.." by "/" at the beginning of a path, 76 + // assuming Separator is '/'. 77 + // 78 + // The returned path ends in a slash only if it represents a root directory, 79 + // such as "/" on Unix or `C:\` on Windows. 80 + // 81 + // Finally, any occurrences of slash are replaced by Separator. 82 + // 83 + // If the result of this process is an empty string, Clean 84 + // returns the string ".". 85 + // 86 + // See also Rob Pike, ``Lexical File Names in Plan 9 or 87 + // Getting Dot-Dot Right,'' 88 + // https://9p.io/sys/doc/lexnames.html 89 + func Clean(path string) string { 90 + originalPath := path 91 + volLen := volumeNameLen(path) 92 + path = path[volLen:] 93 + if path == "" { 94 + if volLen > 1 && originalPath[1] != ':' { 95 + // should be UNC 96 + return FromSlash(originalPath) 97 + } 98 + return originalPath + "." 99 + } 100 + rooted := os.IsPathSeparator(path[0]) 101 + 102 + // Invariants: 103 + // reading from path; r is index of next byte to process. 104 + // writing to buf; w is index of next byte to write. 105 + // dotdot is index in buf where .. must stop, either because 106 + // it is the leading slash or it is a leading ../../.. prefix. 107 + n := len(path) 108 + out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen} 109 + r, dotdot := 0, 0 110 + if rooted { 111 + out.append(Separator) 112 + r, dotdot = 1, 1 113 + } 114 + 115 + for r < n { 116 + switch { 117 + case os.IsPathSeparator(path[r]): 118 + // empty path element 119 + r++ 120 + case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): 121 + // . element 122 + r++ 123 + case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): 124 + // .. element: remove to last separator 125 + r += 2 126 + switch { 127 + case out.w > dotdot: 128 + // can backtrack 129 + out.w-- 130 + for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) { 131 + out.w-- 132 + } 133 + case !rooted: 134 + // cannot backtrack, but not rooted, so append .. element. 135 + if out.w > 0 { 136 + out.append(Separator) 137 + } 138 + out.append('.') 139 + out.append('.') 140 + dotdot = out.w 141 + } 142 + default: 143 + // real path element. 144 + // add slash if needed 145 + if rooted && out.w != 1 || !rooted && out.w != 0 { 146 + out.append(Separator) 147 + } 148 + // copy element 149 + for ; r < n && !os.IsPathSeparator(path[r]); r++ { 150 + out.append(path[r]) 151 + } 152 + } 153 + } 154 + 155 + // Turn empty string into "." 156 + if out.w == 0 { 157 + out.append('.') 158 + } 159 + 160 + return FromSlash(out.string()) 161 + } 162 + 163 + // ToSlash returns the result of replacing each separator character 164 + // in path with a slash ('/') character. Multiple separators are 165 + // replaced by multiple slashes. 166 + func ToSlash(path string) string { 167 + if Separator == '/' { 168 + return path 169 + } 170 + return strings.ReplaceAll(path, string(Separator), "/") 171 + } 172 + 173 + // FromSlash returns the result of replacing each slash ('/') character 174 + // in path with a separator character. Multiple slashes are replaced 175 + // by multiple separators. 176 + func FromSlash(path string) string { 177 + if Separator == '/' { 178 + return path 179 + } 180 + return strings.ReplaceAll(path, "/", string(Separator)) 181 + } 182 + 183 + // SplitList splits a list of paths joined by the OS-specific ListSeparator, 184 + // usually found in PATH or GOPATH environment variables. 185 + // Unlike strings.Split, SplitList returns an empty slice when passed an empty 186 + // string. 187 + func SplitList(path string) []string { 188 + return splitList(path) 189 + } 190 + 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) 198 + i := len(path) - 1 199 + for i >= len(vol) && !os.IsPathSeparator(path[i]) { 200 + i-- 201 + } 202 + return path[:i+1], path[i+1:] 203 + } 204 + 205 + // Join joins any number of path elements into a single path, 206 + // separating them with an OS specific Separator. Empty elements 207 + // are ignored. The result is Cleaned. However, if the argument 208 + // list is empty or all its elements are empty, Join returns 209 + // an empty string. 210 + // On Windows, the result will only be a UNC path if the first 211 + // non-empty element is a UNC path. 212 + func Join(elem ...string) string { 213 + return join(elem) 214 + } 215 + 216 + // Ext returns the file name extension used by path. 217 + // The extension is the suffix beginning at the final dot 218 + // 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-- { 222 + if path[i] == '.' { 223 + return path[i:] 224 + } 225 + } 226 + return "" 227 + } 228 + 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 254 + } 255 + return Join(wd, path), nil 256 + } 257 + 258 + // Rel returns a relative path that is lexically equivalent to targpath when 259 + // joined to basepath with an intervening separator. That is, 260 + // Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself. 261 + // On success, the returned path will always be relative to basepath, 262 + // even if basepath and targpath share no elements. 263 + // An error is returned if targpath can't be made relative to basepath or if 264 + // 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) { 272 + return ".", nil 273 + } 274 + base = base[len(baseVol):] 275 + targ = targ[len(targVol):] 276 + if base == "." { 277 + base = "" 278 + } 279 + // 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) { 283 + return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath) 284 + } 285 + // Position base[b0:bi] and targ[t0:ti] at the first differing elements. 286 + bl := len(base) 287 + tl := len(targ) 288 + var b0, bi, t0, ti int 289 + for { 290 + for bi < bl && base[bi] != Separator { 291 + bi++ 292 + } 293 + for ti < tl && targ[ti] != Separator { 294 + ti++ 295 + } 296 + if !sameWord(targ[t0:ti], base[b0:bi]) { 297 + break 298 + } 299 + if bi < bl { 300 + bi++ 301 + } 302 + if ti < tl { 303 + ti++ 304 + } 305 + b0 = bi 306 + t0 = ti 307 + } 308 + if base[b0:bi] == ".." { 309 + return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath) 310 + } 311 + if b0 != bl { 312 + // Base elements left. Must go up before going down. 313 + seps := strings.Count(base[b0:bl], string(Separator)) 314 + size := 2 + seps*3 315 + if tl != t0 { 316 + size += 1 + tl - t0 317 + } 318 + buf := make([]byte, size) 319 + n := copy(buf, "..") 320 + for i := 0; i < seps; i++ { 321 + buf[n] = Separator 322 + copy(buf[n+1:], "..") 323 + n += 3 324 + } 325 + if t0 != tl { 326 + buf[n] = Separator 327 + copy(buf[n+1:], targ[t0:]) 328 + } 329 + return string(buf), nil 330 + } 331 + return targ[t0:], nil 332 + } 333 + 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 + // Base returns the last element of path. 542 + // Trailing path separators are removed before extracting the last element. 543 + // If the path is empty, Base returns ".". 544 + // If the path consists entirely of separators, Base returns a single separator. 545 + func Base(path string) string { 546 + if path == "" { 547 + return "." 548 + } 549 + // Strip trailing slashes. 550 + for len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) { 551 + path = path[0 : len(path)-1] 552 + } 553 + // Throw away volume name 554 + path = path[len(VolumeName(path)):] 555 + // Find the last element 556 + i := len(path) - 1 557 + for i >= 0 && !os.IsPathSeparator(path[i]) { 558 + i-- 559 + } 560 + if i >= 0 { 561 + path = path[i+1:] 562 + } 563 + // If empty now, it had only slashes. 564 + if path == "" { 565 + return string(Separator) 566 + } 567 + return path 568 + } 569 + 570 + // Dir returns all but the last element of path, typically the path's directory. 571 + // After dropping the final element, Dir calls Clean on the path and trailing 572 + // slashes are removed. 573 + // If the path is empty, Dir returns ".". 574 + // If the path consists entirely of separators, Dir returns a single separator. 575 + // 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) 578 + i := len(path) - 1 579 + for i >= len(vol) && !os.IsPathSeparator(path[i]) { 580 + i-- 581 + } 582 + dir := Clean(path[len(vol) : i+1]) 583 + if dir == "." && len(vol) > 2 { 584 + // must be UNC 585 + return vol 586 + } 587 + return vol + dir 588 + } 589 + 590 + // VolumeName returns leading volume name. 591 + // Given "C:\foo\bar" it returns "C:" on Windows. 592 + // Given "\\host\share\foo" it returns "\\host\share". 593 + // On other platforms it returns "". 594 + func VolumeName(path string) string { 595 + return path[:volumeNameLen(path)] 596 + }
+51
pkg/path/testdata/path_plan9.go
··· 1 + // Copyright 2010 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 "strings" 8 + 9 + // IsAbs reports whether the path is absolute. 10 + func IsAbs(path string) bool { 11 + return strings.HasPrefix(path, "/") || strings.HasPrefix(path, "#") 12 + } 13 + 14 + // volumeNameLen returns length of the leading volume name on Windows. 15 + // It returns 0 elsewhere. 16 + func volumeNameLen(path string) int { 17 + return 0 18 + } 19 + 20 + // HasPrefix exists for historical compatibility and should not be used. 21 + // 22 + // Deprecated: HasPrefix does not respect path boundaries and 23 + // does not ignore case when required. 24 + func HasPrefix(p, prefix string) bool { 25 + return strings.HasPrefix(p, prefix) 26 + } 27 + 28 + func splitList(path string) []string { 29 + if path == "" { 30 + return []string{} 31 + } 32 + return strings.Split(path, string(ListSeparator)) 33 + } 34 + 35 + func abs(path string) (string, error) { 36 + return unixAbs(path) 37 + } 38 + 39 + func join(elem []string) string { 40 + // If there's a bug here, fix the logic in ./path_unix.go too. 41 + for i, e := range elem { 42 + if e != "" { 43 + return Clean(strings.Join(elem[i:], string(Separator))) 44 + } 45 + } 46 + return "" 47 + } 48 + 49 + func sameWord(a, b string) bool { 50 + return a == b 51 + }
+1538
pkg/path/testdata/path_test.go
··· 1 + // Copyright 2009 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_test 6 + 7 + import ( 8 + "errors" 9 + "fmt" 10 + "internal/testenv" 11 + "io/fs" 12 + "io/ioutil" 13 + "os" 14 + "path/filepath" 15 + "reflect" 16 + "runtime" 17 + "sort" 18 + "strings" 19 + "syscall" 20 + "testing" 21 + ) 22 + 23 + type PathTest struct { 24 + path, result string 25 + } 26 + 27 + var cleantests = []PathTest{ 28 + // Already clean 29 + {"abc", "abc"}, 30 + {"abc/def", "abc/def"}, 31 + {"a/b/c", "a/b/c"}, 32 + {".", "."}, 33 + {"..", ".."}, 34 + {"../..", "../.."}, 35 + {"../../abc", "../../abc"}, 36 + {"/abc", "/abc"}, 37 + {"/", "/"}, 38 + 39 + // Empty is current dir 40 + {"", "."}, 41 + 42 + // Remove trailing slash 43 + {"abc/", "abc"}, 44 + {"abc/def/", "abc/def"}, 45 + {"a/b/c/", "a/b/c"}, 46 + {"./", "."}, 47 + {"../", ".."}, 48 + {"../../", "../.."}, 49 + {"/abc/", "/abc"}, 50 + 51 + // Remove doubled slash 52 + {"abc//def//ghi", "abc/def/ghi"}, 53 + {"//abc", "/abc"}, 54 + {"///abc", "/abc"}, 55 + {"//abc//", "/abc"}, 56 + {"abc//", "abc"}, 57 + 58 + // Remove . elements 59 + {"abc/./def", "abc/def"}, 60 + {"/./abc/def", "/abc/def"}, 61 + {"abc/.", "abc"}, 62 + 63 + // Remove .. elements 64 + {"abc/def/ghi/../jkl", "abc/def/jkl"}, 65 + {"abc/def/../ghi/../jkl", "abc/jkl"}, 66 + {"abc/def/..", "abc"}, 67 + {"abc/def/../..", "."}, 68 + {"/abc/def/../..", "/"}, 69 + {"abc/def/../../..", ".."}, 70 + {"/abc/def/../../..", "/"}, 71 + {"abc/def/../../../ghi/jkl/../../../mno", "../../mno"}, 72 + {"/../abc", "/abc"}, 73 + 74 + // Combinations 75 + {"abc/./../def", "def"}, 76 + {"abc//./../def", "def"}, 77 + {"abc/../../././../def", "../../def"}, 78 + } 79 + 80 + var wincleantests = []PathTest{ 81 + {`c:`, `c:.`}, 82 + {`c:\`, `c:\`}, 83 + {`c:\abc`, `c:\abc`}, 84 + {`c:abc\..\..\.\.\..\def`, `c:..\..\def`}, 85 + {`c:\abc\def\..\..`, `c:\`}, 86 + {`c:\..\abc`, `c:\abc`}, 87 + {`c:..\abc`, `c:..\abc`}, 88 + {`\`, `\`}, 89 + {`/`, `\`}, 90 + {`\\i\..\c$`, `\c$`}, 91 + {`\\i\..\i\c$`, `\i\c$`}, 92 + {`\\i\..\I\c$`, `\I\c$`}, 93 + {`\\host\share\foo\..\bar`, `\\host\share\bar`}, 94 + {`//host/share/foo/../baz`, `\\host\share\baz`}, 95 + {`\\a\b\..\c`, `\\a\b\c`}, 96 + {`\\a\b`, `\\a\b`}, 97 + } 98 + 99 + func TestClean(t *testing.T) { 100 + 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) 110 + } 111 + if s := filepath.Clean(test.result); s != test.result { 112 + t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result) 113 + } 114 + } 115 + 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 + } 123 + 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) 128 + } 129 + } 130 + } 131 + 132 + const sep = filepath.Separator 133 + 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 + } 140 + 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) 148 + } 149 + } 150 + } 151 + 152 + type SplitListTest struct { 153 + list string 154 + 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 + } 164 + 165 + var winsplitlisttests = []SplitListTest{ 166 + // quoted 167 + {`"a"`, []string{`a`}}, 168 + 169 + // semicolon 170 + {`";"`, []string{`;`}}, 171 + {`"a;b"`, []string{`a;b`}}, 172 + {`";";`, []string{`;`, ``}}, 173 + {`;";"`, []string{``, `;`}}, 174 + 175 + // partially quoted 176 + {`a";"b`, []string{`a;b`}}, 177 + {`a; ""b`, []string{`a`, ` b`}}, 178 + {`"a;b`, []string{`a;b`}}, 179 + {`""a;b`, []string{`a`, `b`}}, 180 + {`"""a;b`, []string{`a;b`}}, 181 + {`""""a;b`, []string{`a`, `b`}}, 182 + {`a";b`, []string{`a;b`}}, 183 + {`a;b";c`, []string{`a`, `b;c`}}, 184 + {`"a";b";c`, []string{`a`, `b;c`}}, 185 + } 186 + 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) 195 + } 196 + } 197 + } 198 + 199 + type SplitTest struct { 200 + path, dir, file string 201 + } 202 + 203 + var unixsplittests = []SplitTest{ 204 + {"a/b", "a/", "b"}, 205 + {"a/b/", "a/b/", ""}, 206 + {"a/", "a/", ""}, 207 + {"a", "", "a"}, 208 + {"/", "/", ""}, 209 + } 210 + 211 + var winsplittests = []SplitTest{ 212 + {`c:`, `c:`, ``}, 213 + {`c:/`, `c:/`, ``}, 214 + {`c:/foo`, `c:/`, `foo`}, 215 + {`c:/foo/bar`, `c:/foo/`, `bar`}, 216 + {`//host/share`, `//host/share`, ``}, 217 + {`//host/share/`, `//host/share/`, ``}, 218 + {`//host/share/foo`, `//host/share/`, `foo`}, 219 + {`\\host\share`, `\\host\share`, ``}, 220 + {`\\host\share\`, `\\host\share\`, ``}, 221 + {`\\host\share\foo`, `\\host\share\`, `foo`}, 222 + } 223 + 224 + 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 + } 234 + } 235 + } 236 + 237 + type JoinTest struct { 238 + elem []string 239 + path string 240 + } 241 + 242 + var jointests = []JoinTest{ 243 + // zero parameters 244 + {[]string{}, ""}, 245 + 246 + // one parameter 247 + {[]string{""}, ""}, 248 + {[]string{"/"}, "/"}, 249 + {[]string{"a"}, "a"}, 250 + 251 + // two parameters 252 + {[]string{"a", "b"}, "a/b"}, 253 + {[]string{"a", ""}, "a"}, 254 + {[]string{"", "b"}, "b"}, 255 + {[]string{"/", "a"}, "/a"}, 256 + {[]string{"/", "a/b"}, "/a/b"}, 257 + {[]string{"/", ""}, "/"}, 258 + {[]string{"//", "a"}, "/a"}, 259 + {[]string{"/a", "b"}, "/a/b"}, 260 + {[]string{"a/", "b"}, "a/b"}, 261 + {[]string{"a/", ""}, "a"}, 262 + {[]string{"", ""}, ""}, 263 + 264 + // three parameters 265 + {[]string{"/", "a", "b"}, "/a/b"}, 266 + } 267 + 268 + var winjointests = []JoinTest{ 269 + {[]string{`directory`, `file`}, `directory\file`}, 270 + {[]string{`C:\Windows\`, `System32`}, `C:\Windows\System32`}, 271 + {[]string{`C:\Windows\`, ``}, `C:\Windows`}, 272 + {[]string{`C:\`, `Windows`}, `C:\Windows`}, 273 + {[]string{`C:`, `a`}, `C:a`}, 274 + {[]string{`C:`, `a\b`}, `C:a\b`}, 275 + {[]string{`C:`, `a`, `b`}, `C:a\b`}, 276 + {[]string{`C:`, ``, `b`}, `C:b`}, 277 + {[]string{`C:`, ``, ``, `b`}, `C:b`}, 278 + {[]string{`C:`, ``}, `C:.`}, 279 + {[]string{`C:`, ``, ``}, `C:.`}, 280 + {[]string{`C:.`, `a`}, `C:a`}, 281 + {[]string{`C:a`, `b`}, `C:a\b`}, 282 + {[]string{`C:a`, `b`, `d`}, `C:a\b\d`}, 283 + {[]string{`\\host\share`, `foo`}, `\\host\share\foo`}, 284 + {[]string{`\\host\share\foo`}, `\\host\share\foo`}, 285 + {[]string{`//host/share`, `foo/bar`}, `\\host\share\foo\bar`}, 286 + {[]string{`\`}, `\`}, 287 + {[]string{`\`, ``}, `\`}, 288 + {[]string{`\`, `a`}, `\a`}, 289 + {[]string{`\\`, `a`}, `\a`}, 290 + {[]string{`\`, `a`, `b`}, `\a\b`}, 291 + {[]string{`\\`, `a`, `b`}, `\a\b`}, 292 + {[]string{`\`, `\\a\b`, `c`}, `\a\b\c`}, 293 + {[]string{`\\a`, `b`, `c`}, `\a\b\c`}, 294 + {[]string{`\\a\`, `b`, `c`}, `\a\b\c`}, 295 + } 296 + 297 + 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) 305 + } 306 + } 307 + } 308 + 309 + type ExtTest struct { 310 + path, ext string 311 + } 312 + 313 + var exttests = []ExtTest{ 314 + {"path.go", ".go"}, 315 + {"path.pb.go", ".go"}, 316 + {"a.dir/b", ""}, 317 + {"a.dir/b.go", ".go"}, 318 + {"a.dir/", ""}, 319 + } 320 + 321 + 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 375 + } 376 + fd.Close() 377 + } else { 378 + os.Mkdir(path, 0770) 379 + } 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 + } 660 + } 661 + 662 + var basetests = []PathTest{ 663 + {"", "."}, 664 + {".", "."}, 665 + {"/.", "."}, 666 + {"/", "/"}, 667 + {"////", "/"}, 668 + {"x/", "x"}, 669 + {"abc", "abc"}, 670 + {"abc/def", "def"}, 671 + {"a/b/.x", ".x"}, 672 + {"a/b/c.", "c."}, 673 + {"a/b/c.x", "c.x"}, 674 + } 675 + 676 + var winbasetests = []PathTest{ 677 + {`c:\`, `\`}, 678 + {`c:.`, `.`}, 679 + {`c:\a\b`, `b`}, 680 + {`c:a\b`, `b`}, 681 + {`c:a\b\c`, `c`}, 682 + {`\\host\share\`, `\`}, 683 + {`\\host\share\a`, `a`}, 684 + {`\\host\share\a\b`, `b`}, 685 + } 686 + 687 + func TestBase(t *testing.T) { 688 + 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) 693 + } 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) 700 + } 701 + } 702 + } 703 + 704 + var dirtests = []PathTest{ 705 + {"", "."}, 706 + {".", "."}, 707 + {"/.", "/"}, 708 + {"/", "/"}, 709 + {"////", "/"}, 710 + {"/foo", "/"}, 711 + {"x/", "x"}, 712 + {"abc", "."}, 713 + {"abc/def", "abc"}, 714 + {"a/b/.x", "a/b"}, 715 + {"a/b/c.", "a/b"}, 716 + {"a/b/c.x", "a/b"}, 717 + } 718 + 719 + var windirtests = []PathTest{ 720 + {`c:\`, `c:\`}, 721 + {`c:.`, `c:.`}, 722 + {`c:\a\b`, `c:\a`}, 723 + {`c:a\b`, `c:a`}, 724 + {`c:a\b\c`, `c:a\b`}, 725 + {`\\host\share`, `\\host\share`}, 726 + {`\\host\share\`, `\\host\share\`}, 727 + {`\\host\share\a`, `\\host\share\`}, 728 + {`\\host\share\a\b`, `\\host\share\a`}, 729 + } 730 + 731 + 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) 737 + } 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) 744 + } 745 + } 746 + } 747 + 748 + type IsAbsTest struct { 749 + path string 750 + isAbs bool 751 + } 752 + 753 + var isabstests = []IsAbsTest{ 754 + {"", false}, 755 + {"/", true}, 756 + {"/usr/bin/gcc", true}, 757 + {"..", false}, 758 + {"/a/../bb", true}, 759 + {".", false}, 760 + {"./", false}, 761 + {"lala", false}, 762 + } 763 + 764 + var winisabstests = []IsAbsTest{ 765 + {`C:\`, true}, 766 + {`c\`, false}, 767 + {`c::`, false}, 768 + {`c:`, false}, 769 + {`/`, false}, 770 + {`\`, false}, 771 + {`\Windows`, false}, 772 + {`c:a\b`, false}, 773 + {`c:\a\b`, true}, 774 + {`c:/a/b`, true}, 775 + {`\\host\share\foo`, true}, 776 + {`//host/share/foo/bar`, true}, 777 + } 778 + 779 + 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) 914 + } else { 915 + err = os.Symlink(d.dest, path) 916 + } 917 + if err != nil { 918 + t.Fatal(err) 919 + } 920 + } 921 + 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) 939 + } 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 + } 954 + } 955 + 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 + } 1040 + 1041 + // Test directories relative to temporary directory. 1042 + // The tests are run in absTestDirs[0]. 1043 + var absTestDirs = []string{ 1044 + "a", 1045 + "a/b", 1046 + "a/b/c", 1047 + } 1048 + 1049 + // Test paths relative to temporary directory. $ expands to the directory. 1050 + // The tests are run in absTestDirs[0]. 1051 + // We create absTestDirs first. 1052 + var absTests = []string{ 1053 + ".", 1054 + "b", 1055 + "b/", 1056 + "../a", 1057 + "../a/b", 1058 + "../a/b/./c/../../.././a", 1059 + "../a/b/./c/../../.././a/", 1060 + "$", 1061 + "$/.", 1062 + "$/a/../a/b", 1063 + "$/a/b/c/../../.././a", 1064 + "$/a/b/c/../../.././a/", 1065 + } 1066 + 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 + type RelTests struct { 1177 + root, path, want string 1178 + } 1179 + 1180 + var reltests = []RelTests{ 1181 + {"a/b", "a/b", "."}, 1182 + {"a/b/.", "a/b", "."}, 1183 + {"a/b", "a/b/.", "."}, 1184 + {"./a/b", "a/b", "."}, 1185 + {"a/b", "./a/b", "."}, 1186 + {"ab/cd", "ab/cde", "../cde"}, 1187 + {"ab/cd", "ab/c", "../c"}, 1188 + {"a/b", "a/b/c/d", "c/d"}, 1189 + {"a/b", "a/b/../c", "../c"}, 1190 + {"a/b/../c", "a/b", "../b"}, 1191 + {"a/b/c", "a/c/d", "../../c/d"}, 1192 + {"a/b", "c/d", "../../c/d"}, 1193 + {"a/b/c/d", "a/b", "../.."}, 1194 + {"a/b/c/d", "a/b/", "../.."}, 1195 + {"a/b/c/d/", "a/b", "../.."}, 1196 + {"a/b/c/d/", "a/b/", "../.."}, 1197 + {"../../a/b", "../../a/b/c/d", "c/d"}, 1198 + {"/a/b", "/a/b", "."}, 1199 + {"/a/b/.", "/a/b", "."}, 1200 + {"/a/b", "/a/b/.", "."}, 1201 + {"/ab/cd", "/ab/cde", "../cde"}, 1202 + {"/ab/cd", "/ab/c", "../c"}, 1203 + {"/a/b", "/a/b/c/d", "c/d"}, 1204 + {"/a/b", "/a/b/../c", "../c"}, 1205 + {"/a/b/../c", "/a/b", "../b"}, 1206 + {"/a/b/c", "/a/c/d", "../../c/d"}, 1207 + {"/a/b", "/c/d", "../../c/d"}, 1208 + {"/a/b/c/d", "/a/b", "../.."}, 1209 + {"/a/b/c/d", "/a/b/", "../.."}, 1210 + {"/a/b/c/d/", "/a/b", "../.."}, 1211 + {"/a/b/c/d/", "/a/b/", "../.."}, 1212 + {"/../../a/b", "/../../a/b/c/d", "c/d"}, 1213 + {".", "a/b", "a/b"}, 1214 + {".", "..", ".."}, 1215 + 1216 + // can't do purely lexically 1217 + {"..", ".", "err"}, 1218 + {"..", "a", "err"}, 1219 + {"../..", "..", "err"}, 1220 + {"a", "/a", "err"}, 1221 + {"/a", "a", "err"}, 1222 + } 1223 + 1224 + var winreltests = []RelTests{ 1225 + {`C:a\b\c`, `C:a/b/d`, `..\d`}, 1226 + {`C:\`, `D:\`, `err`}, 1227 + {`C:`, `D:`, `err`}, 1228 + {`C:\Projects`, `c:\projects\src`, `src`}, 1229 + {`C:\Projects`, `c:\projects`, `.`}, 1230 + {`C:\Projects\a\..`, `c:\projects`, `.`}, 1231 + } 1232 + 1233 + 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) 1238 + } 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) 1246 + } 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) 1254 + } 1255 + } 1256 + } 1257 + 1258 + type VolumeNameTest struct { 1259 + path string 1260 + vol string 1261 + } 1262 + 1263 + var volumenametests = []VolumeNameTest{ 1264 + {`c:/foo/bar`, `c:`}, 1265 + {`c:`, `c:`}, 1266 + {`2:`, ``}, 1267 + {``, ``}, 1268 + {`\\\host`, ``}, 1269 + {`\\\host\`, ``}, 1270 + {`\\\host\share`, ``}, 1271 + {`\\\host\\share`, ``}, 1272 + {`\\host`, ``}, 1273 + {`//host`, ``}, 1274 + {`\\host\`, ``}, 1275 + {`//host/`, ``}, 1276 + {`\\host\share`, `\\host\share`}, 1277 + {`//host/share`, `//host/share`}, 1278 + {`\\host\share\`, `\\host\share`}, 1279 + {`//host/share/`, `//host/share`}, 1280 + {`\\host\share\foo`, `\\host\share`}, 1281 + {`//host/share/foo`, `//host/share`}, 1282 + {`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`}, 1283 + {`//host/share//foo///bar////baz`, `//host/share`}, 1284 + {`\\host\share\foo\..\bar`, `\\host\share`}, 1285 + {`//host/share/foo/../bar`, `//host/share`}, 1286 + } 1287 + 1288 + 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) 1337 + } 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") 1346 + } 1347 + seenKen = true 1348 + } 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 + } 1538 + }
+53
pkg/path/testdata/path_unix.go
··· 1 + // Copyright 2010 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 aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris 6 + 7 + package filepath 8 + 9 + import "strings" 10 + 11 + // IsAbs reports whether the path is absolute. 12 + func IsAbs(path string) bool { 13 + return strings.HasPrefix(path, "/") 14 + } 15 + 16 + // volumeNameLen returns length of the leading volume name on Windows. 17 + // It returns 0 elsewhere. 18 + func volumeNameLen(path string) int { 19 + return 0 20 + } 21 + 22 + // HasPrefix exists for historical compatibility and should not be used. 23 + // 24 + // Deprecated: HasPrefix does not respect path boundaries and 25 + // does not ignore case when required. 26 + func HasPrefix(p, prefix string) bool { 27 + return strings.HasPrefix(p, prefix) 28 + } 29 + 30 + func splitList(path string) []string { 31 + if path == "" { 32 + return []string{} 33 + } 34 + return strings.Split(path, string(ListSeparator)) 35 + } 36 + 37 + func abs(path string) (string, error) { 38 + return unixAbs(path) 39 + } 40 + 41 + func join(elem []string) string { 42 + // If there's a bug here, fix the logic in ./path_plan9.go too. 43 + for i, e := range elem { 44 + if e != "" { 45 + return Clean(strings.Join(elem[i:], string(Separator))) 46 + } 47 + } 48 + return "" 49 + } 50 + 51 + func sameWord(a, b string) bool { 52 + return a == b 53 + }
+200
pkg/path/testdata/path_windows.go
··· 1 + // Copyright 2010 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 + "strings" 9 + "syscall" 10 + ) 11 + 12 + func isSlash(c uint8) bool { 13 + return c == '\\' || c == '/' 14 + } 15 + 16 + // reservedNames lists reserved Windows names. Search for PRN in 17 + // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file 18 + // for details. 19 + var reservedNames = []string{ 20 + "CON", "PRN", "AUX", "NUL", 21 + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", 22 + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", 23 + } 24 + 25 + // isReservedName returns true, if path is Windows reserved name. 26 + // See reservedNames for the full list. 27 + func isReservedName(path string) bool { 28 + if len(path) == 0 { 29 + return false 30 + } 31 + for _, reserved := range reservedNames { 32 + if strings.EqualFold(path, reserved) { 33 + return true 34 + } 35 + } 36 + return false 37 + } 38 + 39 + // IsAbs reports whether the path is absolute. 40 + func IsAbs(path string) (b bool) { 41 + if isReservedName(path) { 42 + return true 43 + } 44 + l := volumeNameLen(path) 45 + if l == 0 { 46 + return false 47 + } 48 + path = path[l:] 49 + if path == "" { 50 + return false 51 + } 52 + return isSlash(path[0]) 53 + } 54 + 55 + // volumeNameLen returns length of the leading volume name on Windows. 56 + // It returns 0 elsewhere. 57 + func volumeNameLen(path string) int { 58 + if len(path) < 2 { 59 + return 0 60 + } 61 + // with drive letter 62 + c := path[0] 63 + if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') { 64 + return 2 65 + } 66 + // is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx 67 + if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) && 68 + !isSlash(path[2]) && path[2] != '.' { 69 + // first, leading `\\` and next shouldn't be `\`. its server name. 70 + for n := 3; n < l-1; n++ { 71 + // second, next '\' shouldn't be repeated. 72 + if isSlash(path[n]) { 73 + n++ 74 + // third, following something characters. its share name. 75 + if !isSlash(path[n]) { 76 + if path[n] == '.' { 77 + break 78 + } 79 + for ; n < l; n++ { 80 + if isSlash(path[n]) { 81 + break 82 + } 83 + } 84 + return n 85 + } 86 + break 87 + } 88 + } 89 + } 90 + return 0 91 + } 92 + 93 + // HasPrefix exists for historical compatibility and should not be used. 94 + // 95 + // Deprecated: HasPrefix does not respect path boundaries and 96 + // does not ignore case when required. 97 + func HasPrefix(p, prefix string) bool { 98 + if strings.HasPrefix(p, prefix) { 99 + return true 100 + } 101 + return strings.HasPrefix(strings.ToLower(p), strings.ToLower(prefix)) 102 + } 103 + 104 + func splitList(path string) []string { 105 + // The same implementation is used in LookPath in os/exec; 106 + // consider changing os/exec when changing this. 107 + 108 + if path == "" { 109 + return []string{} 110 + } 111 + 112 + // Split path, respecting but preserving quotes. 113 + list := []string{} 114 + start := 0 115 + quo := false 116 + for i := 0; i < len(path); i++ { 117 + switch c := path[i]; { 118 + case c == '"': 119 + quo = !quo 120 + case c == ListSeparator && !quo: 121 + list = append(list, path[start:i]) 122 + start = i + 1 123 + } 124 + } 125 + list = append(list, path[start:]) 126 + 127 + // Remove quotes. 128 + for i, s := range list { 129 + list[i] = strings.ReplaceAll(s, `"`, ``) 130 + } 131 + 132 + return list 133 + } 134 + 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 { 150 + for i, e := range elem { 151 + if e != "" { 152 + return joinNonEmpty(elem[i:]) 153 + } 154 + } 155 + return "" 156 + } 157 + 158 + // joinNonEmpty is like join, but it assumes that the first element is non-empty. 159 + func joinNonEmpty(elem []string) string { 160 + if len(elem[0]) == 2 && elem[0][1] == ':' { 161 + // First element is drive letter without terminating slash. 162 + // Keep path relative to current directory on that drive. 163 + // Skip empty elements. 164 + i := 1 165 + for ; i < len(elem); i++ { 166 + if elem[i] != "" { 167 + break 168 + } 169 + } 170 + return Clean(elem[0] + strings.Join(elem[i:], string(Separator))) 171 + } 172 + // The following logic prevents Join from inadvertently creating a 173 + // UNC path on Windows. Unless the first element is a UNC path, Join 174 + // shouldn't create a UNC path. See golang.org/issue/9167. 175 + p := Clean(strings.Join(elem, string(Separator))) 176 + if !isUNC(p) { 177 + return p 178 + } 179 + // p == UNC only allowed when the first element is a UNC path. 180 + head := Clean(elem[0]) 181 + if isUNC(head) { 182 + return p 183 + } 184 + // head + tail == UNC, but joining two non-UNC paths should not result 185 + // 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 { 188 + return head + tail 189 + } 190 + return head + string(Separator) + tail 191 + } 192 + 193 + // isUNC reports whether path is a UNC path. 194 + func isUNC(path string) bool { 195 + return volumeNameLen(path) > 2 196 + } 197 + 198 + func sameWord(a, b string) bool { 199 + return strings.EqualFold(a, b) 200 + }
+587
pkg/path/testdata/path_windows_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_test 6 + 7 + import ( 8 + "flag" 9 + "fmt" 10 + "internal/testenv" 11 + "io/fs" 12 + "io/ioutil" 13 + "os" 14 + "os/exec" 15 + "path/filepath" 16 + "reflect" 17 + "runtime/debug" 18 + "strings" 19 + "testing" 20 + ) 21 + 22 + func TestWinSplitListTestsAreValid(t *testing.T) { 23 + comspec := os.Getenv("ComSpec") 24 + if comspec == "" { 25 + t.Fatal("%ComSpec% must be set") 26 + } 27 + 28 + for ti, tt := range winsplitlisttests { 29 + testWinSplitListTestIsValid(t, ti, tt, comspec) 30 + } 31 + } 32 + 33 + func testWinSplitListTestIsValid(t *testing.T, ti int, tt SplitListTest, 34 + comspec string) { 35 + 36 + const ( 37 + cmdfile = `printdir.cmd` 38 + perm fs.FileMode = 0700 39 + ) 40 + 41 + tmp, err := ioutil.TempDir("", "testWinSplitListTestIsValid") 42 + if err != nil { 43 + t.Fatalf("TempDir failed: %v", err) 44 + } 45 + defer os.RemoveAll(tmp) 46 + 47 + for i, d := range tt.result { 48 + if d == "" { 49 + continue 50 + } 51 + if cd := filepath.Clean(d); filepath.VolumeName(cd) != "" || 52 + cd[0] == '\\' || cd == ".." || (len(cd) >= 3 && cd[0:3] == `..\`) { 53 + t.Errorf("%d,%d: %#q refers outside working directory", ti, i, d) 54 + return 55 + } 56 + dd := filepath.Join(tmp, d) 57 + if _, err := os.Stat(dd); err == nil { 58 + t.Errorf("%d,%d: %#q already exists", ti, i, d) 59 + return 60 + } 61 + if err = os.MkdirAll(dd, perm); err != nil { 62 + t.Errorf("%d,%d: MkdirAll(%#q) failed: %v", ti, i, dd, err) 63 + return 64 + } 65 + fn, data := filepath.Join(dd, cmdfile), []byte("@echo "+d+"\r\n") 66 + if err = ioutil.WriteFile(fn, data, perm); err != nil { 67 + t.Errorf("%d,%d: WriteFile(%#q) failed: %v", ti, i, fn, err) 68 + return 69 + } 70 + } 71 + 72 + // on some systems, SystemRoot is required for cmd to work 73 + systemRoot := os.Getenv("SystemRoot") 74 + 75 + for i, d := range tt.result { 76 + if d == "" { 77 + continue 78 + } 79 + exp := []byte(d + "\r\n") 80 + cmd := &exec.Cmd{ 81 + Path: comspec, 82 + Args: []string{`/c`, cmdfile}, 83 + Env: []string{`Path=` + systemRoot + "/System32;" + tt.list, `SystemRoot=` + systemRoot}, 84 + Dir: tmp, 85 + } 86 + out, err := cmd.CombinedOutput() 87 + switch { 88 + case err != nil: 89 + t.Errorf("%d,%d: execution error %v\n%q", ti, i, err, out) 90 + return 91 + case !reflect.DeepEqual(out, exp): 92 + t.Errorf("%d,%d: expected %#q, got %#q", ti, i, exp, out) 93 + return 94 + default: 95 + // unshadow cmdfile in next directory 96 + err = os.Remove(filepath.Join(tmp, d, cmdfile)) 97 + if err != nil { 98 + t.Fatalf("Remove test command failed: %v", err) 99 + } 100 + } 101 + } 102 + } 103 + 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 + // checkVolume8dot3Setting runs "fsutil 8dot3name query c:" command 225 + // (where c: is vol parameter) to discover "8dot3 name creation state". 226 + // The state is combination of 2 flags. The global flag controls if it 227 + // is per volume or global setting: 228 + // 0 - Enable 8dot3 name creation on all volumes on the system 229 + // 1 - Disable 8dot3 name creation on all volumes on the system 230 + // 2 - Set 8dot3 name creation on a per volume basis 231 + // 3 - Disable 8dot3 name creation on all volumes except the system volume 232 + // If global flag is set to 2, then per-volume flag needs to be examined: 233 + // 0 - Enable 8dot3 name creation on this volume 234 + // 1 - Disable 8dot3 name creation on this volume 235 + // checkVolume8dot3Setting verifies that "8dot3 name creation" flags 236 + // are set to 2 and 0, if enabled parameter is true, or 2 and 1, if enabled 237 + // is false. Otherwise checkVolume8dot3Setting returns error. 238 + func checkVolume8dot3Setting(vol string, enabled bool) error { 239 + // It appears, on some systems "fsutil 8dot3name query ..." command always 240 + // exits with error. Ignore exit code, and look at fsutil output instead. 241 + out, _ := exec.Command("fsutil", "8dot3name", "query", vol).CombinedOutput() 242 + // Check that system has "Volume level setting" set. 243 + expected := "The registry state of NtfsDisable8dot3NameCreation is 2, the default (Volume level setting)" 244 + if !strings.Contains(string(out), expected) { 245 + // Windows 10 version of fsutil has different output message. 246 + expectedWindow10 := "The registry state is: 2 (Per volume setting - the default)" 247 + if !strings.Contains(string(out), expectedWindow10) { 248 + return fmt.Errorf("fsutil output should contain %q, but is %q", expected, string(out)) 249 + } 250 + } 251 + // Now check the volume setting. 252 + expected = "Based on the above two settings, 8dot3 name creation is %s on %s" 253 + if enabled { 254 + expected = fmt.Sprintf(expected, "enabled", vol) 255 + } else { 256 + expected = fmt.Sprintf(expected, "disabled", vol) 257 + } 258 + if !strings.Contains(string(out), expected) { 259 + return fmt.Errorf("unexpected fsutil output: %q", string(out)) 260 + } 261 + return nil 262 + } 263 + 264 + func setVolume8dot3Setting(vol string, enabled bool) error { 265 + cmd := []string{"fsutil", "8dot3name", "set", vol} 266 + if enabled { 267 + cmd = append(cmd, "0") 268 + } else { 269 + cmd = append(cmd, "1") 270 + } 271 + // It appears, on some systems "fsutil 8dot3name set ..." command always 272 + // exits with error. Ignore exit code, and look at fsutil output instead. 273 + out, _ := exec.Command(cmd[0], cmd[1:]...).CombinedOutput() 274 + if string(out) != "\r\nSuccessfully set 8dot3name behavior.\r\n" { 275 + // Windows 10 version of fsutil has different output message. 276 + expectedWindow10 := "Successfully %s 8dot3name generation on %s\r\n" 277 + if enabled { 278 + expectedWindow10 = fmt.Sprintf(expectedWindow10, "enabled", vol) 279 + } else { 280 + expectedWindow10 = fmt.Sprintf(expectedWindow10, "disabled", vol) 281 + } 282 + if string(out) != expectedWindow10 { 283 + return fmt.Errorf("%v command failed: %q", cmd, string(out)) 284 + } 285 + } 286 + return nil 287 + } 288 + 289 + 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 + }
+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 + }