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 5package os_test 6 7import ( 8 "errors" 9 "internal/testenv" 10 "io/fs" 11 "os" 12 "path/filepath" 13 "runtime" 14 "testing" 15) 16 17type testStatAndLstatParams struct { 18 isLink bool 19 statCheck func(*testing.T, string, fs.FileInfo) 20 lstatCheck func(*testing.T, string, fs.FileInfo) 21} 22 23// testStatAndLstat verifies that all os.Stat, os.Lstat os.File.Stat and os.Readdir work. 24func testStatAndLstat(t *testing.T, path string, params testStatAndLstatParams) { 25 // test os.Stat 26 sfi, err := os.Stat(path) 27 if err != nil { 28 t.Error(err) 29 return 30 } 31 params.statCheck(t, path, sfi) 32 33 // test os.Lstat 34 lsfi, err := os.Lstat(path) 35 if err != nil { 36 t.Error(err) 37 return 38 } 39 params.lstatCheck(t, path, lsfi) 40 41 if params.isLink { 42 if os.SameFile(sfi, lsfi) { 43 t.Errorf("stat and lstat of %q should not be the same", path) 44 } 45 } else { 46 if !os.SameFile(sfi, lsfi) { 47 t.Errorf("stat and lstat of %q should be the same", path) 48 } 49 } 50 51 // test os.File.Stat 52 f, err := os.Open(path) 53 if err != nil { 54 t.Error(err) 55 return 56 } 57 defer f.Close() 58 59 sfi2, err := f.Stat() 60 if err != nil { 61 t.Error(err) 62 return 63 } 64 params.statCheck(t, path, sfi2) 65 66 if !os.SameFile(sfi, sfi2) { 67 t.Errorf("stat of open %q file and stat of %q should be the same", path, path) 68 } 69 70 if params.isLink { 71 if os.SameFile(sfi2, lsfi) { 72 t.Errorf("stat of opened %q file and lstat of %q should not be the same", path, path) 73 } 74 } else { 75 if !os.SameFile(sfi2, lsfi) { 76 t.Errorf("stat of opened %q file and lstat of %q should be the same", path, path) 77 } 78 } 79 80 parentdir, base := filepath.Split(path) 81 if parentdir == "" || base == "" { 82 // skip os.Readdir test of files without directory or file name component, 83 // such as directories with slash at the end or Windows device names. 84 return 85 } 86 87 parent, err := os.Open(parentdir) 88 if err != nil { 89 t.Error(err) 90 return 91 } 92 defer parent.Close() 93 94 fis, err := parent.Readdir(-1) 95 if err != nil { 96 t.Error(err) 97 return 98 } 99 var lsfi2 fs.FileInfo 100 for _, fi2 := range fis { 101 if fi2.Name() == base { 102 lsfi2 = fi2 103 break 104 } 105 } 106 if lsfi2 == nil { 107 t.Errorf("failed to find %q in its parent", path) 108 return 109 } 110 params.lstatCheck(t, path, lsfi2) 111 112 if !os.SameFile(lsfi, lsfi2) { 113 t.Errorf("lstat of %q file in %q directory and %q should be the same", lsfi2.Name(), parentdir, path) 114 } 115} 116 117// testIsDir verifies that fi refers to directory. 118func testIsDir(t *testing.T, path string, fi fs.FileInfo) { 119 t.Helper() 120 if !fi.IsDir() { 121 t.Errorf("%q should be a directory", path) 122 } 123 if fi.Mode()&fs.ModeSymlink != 0 { 124 t.Errorf("%q should not be a symlink", path) 125 } 126} 127 128// testIsSymlink verifies that fi refers to symlink. 129func testIsSymlink(t *testing.T, path string, fi fs.FileInfo) { 130 t.Helper() 131 if fi.IsDir() { 132 t.Errorf("%q should not be a directory", path) 133 } 134 if fi.Mode()&fs.ModeSymlink == 0 { 135 t.Errorf("%q should be a symlink", path) 136 } 137} 138 139// testIsFile verifies that fi refers to file. 140func testIsFile(t *testing.T, path string, fi fs.FileInfo) { 141 t.Helper() 142 if fi.IsDir() { 143 t.Errorf("%q should not be a directory", path) 144 } 145 if fi.Mode()&fs.ModeSymlink != 0 { 146 t.Errorf("%q should not be a symlink", path) 147 } 148} 149 150func testDirStats(t *testing.T, path string) { 151 params := testStatAndLstatParams{ 152 isLink: false, 153 statCheck: testIsDir, 154 lstatCheck: testIsDir, 155 } 156 testStatAndLstat(t, path, params) 157} 158 159func testFileStats(t *testing.T, path string) { 160 params := testStatAndLstatParams{ 161 isLink: false, 162 statCheck: testIsFile, 163 lstatCheck: testIsFile, 164 } 165 testStatAndLstat(t, path, params) 166} 167 168func testSymlinkStats(t *testing.T, path string, isdir bool) { 169 params := testStatAndLstatParams{ 170 isLink: true, 171 lstatCheck: testIsSymlink, 172 } 173 if isdir { 174 params.statCheck = testIsDir 175 } else { 176 params.statCheck = testIsFile 177 } 178 testStatAndLstat(t, path, params) 179} 180 181func testSymlinkSameFile(t *testing.T, path, link string) { 182 pathfi, err := os.Stat(path) 183 if err != nil { 184 t.Error(err) 185 return 186 } 187 188 linkfi, err := os.Stat(link) 189 if err != nil { 190 t.Error(err) 191 return 192 } 193 if !os.SameFile(pathfi, linkfi) { 194 t.Errorf("os.Stat(%q) and os.Stat(%q) are not the same file", path, link) 195 } 196 197 linkfi, err = os.Lstat(link) 198 if err != nil { 199 t.Error(err) 200 return 201 } 202 if os.SameFile(pathfi, linkfi) { 203 t.Errorf("os.Stat(%q) and os.Lstat(%q) are the same file", path, link) 204 } 205} 206 207func testSymlinkSameFileOpen(t *testing.T, link string) { 208 f, err := os.Open(link) 209 if err != nil { 210 t.Error(err) 211 return 212 } 213 defer f.Close() 214 215 fi, err := f.Stat() 216 if err != nil { 217 t.Error(err) 218 return 219 } 220 221 fi2, err := os.Stat(link) 222 if err != nil { 223 t.Error(err) 224 return 225 } 226 227 if !os.SameFile(fi, fi2) { 228 t.Errorf("os.Open(%q).Stat() and os.Stat(%q) are not the same file", link, link) 229 } 230} 231 232func TestDirAndSymlinkStats(t *testing.T) { 233 testenv.MustHaveSymlink(t) 234 t.Parallel() 235 236 tmpdir := t.TempDir() 237 dir := filepath.Join(tmpdir, "dir") 238 if err := os.Mkdir(dir, 0777); err != nil { 239 t.Fatal(err) 240 } 241 testDirStats(t, dir) 242 243 dirlink := filepath.Join(tmpdir, "link") 244 if err := os.Symlink(dir, dirlink); err != nil { 245 t.Fatal(err) 246 } 247 testSymlinkStats(t, dirlink, true) 248 testSymlinkSameFile(t, dir, dirlink) 249 testSymlinkSameFileOpen(t, dirlink) 250 251 linklink := filepath.Join(tmpdir, "linklink") 252 if err := os.Symlink(dirlink, linklink); err != nil { 253 t.Fatal(err) 254 } 255 testSymlinkStats(t, linklink, true) 256 testSymlinkSameFile(t, dir, linklink) 257 testSymlinkSameFileOpen(t, linklink) 258} 259 260func TestFileAndSymlinkStats(t *testing.T) { 261 testenv.MustHaveSymlink(t) 262 t.Parallel() 263 264 tmpdir := t.TempDir() 265 file := filepath.Join(tmpdir, "file") 266 if err := os.WriteFile(file, []byte(""), 0644); err != nil { 267 t.Fatal(err) 268 } 269 testFileStats(t, file) 270 271 filelink := filepath.Join(tmpdir, "link") 272 if err := os.Symlink(file, filelink); err != nil { 273 t.Fatal(err) 274 } 275 testSymlinkStats(t, filelink, false) 276 testSymlinkSameFile(t, file, filelink) 277 testSymlinkSameFileOpen(t, filelink) 278 279 linklink := filepath.Join(tmpdir, "linklink") 280 if err := os.Symlink(filelink, linklink); err != nil { 281 t.Fatal(err) 282 } 283 testSymlinkStats(t, linklink, false) 284 testSymlinkSameFile(t, file, linklink) 285 testSymlinkSameFileOpen(t, linklink) 286} 287 288// see issue 27225 for details 289func TestSymlinkWithTrailingSlash(t *testing.T) { 290 testenv.MustHaveSymlink(t) 291 t.Parallel() 292 293 tmpdir := t.TempDir() 294 dir := filepath.Join(tmpdir, "dir") 295 if err := os.Mkdir(dir, 0777); err != nil { 296 t.Fatal(err) 297 } 298 dirlink := filepath.Join(tmpdir, "link") 299 if err := os.Symlink(dir, dirlink); err != nil { 300 t.Fatal(err) 301 } 302 dirlinkWithSlash := dirlink + string(os.PathSeparator) 303 304 testDirStats(t, dirlinkWithSlash) 305 306 fi1, err := os.Stat(dir) 307 if err != nil { 308 t.Error(err) 309 return 310 } 311 fi2, err := os.Stat(dirlinkWithSlash) 312 if err != nil { 313 t.Error(err) 314 return 315 } 316 if !os.SameFile(fi1, fi2) { 317 t.Errorf("os.Stat(%q) and os.Stat(%q) are not the same file", dir, dirlinkWithSlash) 318 } 319} 320 321func TestStatConsole(t *testing.T) { 322 if runtime.GOOS != "windows" { 323 t.Skip("skipping on non-Windows") 324 } 325 t.Parallel() 326 consoleNames := []string{ 327 "CONIN$", 328 "CONOUT$", 329 "CON", 330 } 331 for _, name := range consoleNames { 332 params := testStatAndLstatParams{ 333 isLink: false, 334 statCheck: testIsFile, 335 lstatCheck: testIsFile, 336 } 337 testStatAndLstat(t, name, params) 338 testStatAndLstat(t, `\\.\`+name, params) 339 } 340} 341 342func TestClosedStat(t *testing.T) { 343 // Historically we do not seem to match ErrClosed on non-Unix systems. 344 switch runtime.GOOS { 345 case "windows", "plan9": 346 t.Skipf("skipping on %s", runtime.GOOS) 347 } 348 349 t.Parallel() 350 f, err := os.Open("testdata/hello") 351 if err != nil { 352 t.Fatal(err) 353 } 354 if err := f.Close(); err != nil { 355 t.Fatal(err) 356 } 357 _, err = f.Stat() 358 if err == nil { 359 t.Error("Stat succeeded on closed File") 360 } else if !errors.Is(err, os.ErrClosed) { 361 t.Errorf("error from Stat on closed file did not match ErrClosed: %q, type %T", err, err) 362 } 363} 364