github.com/Andyfoo/golang/x/net@v0.0.0-20190901054642-57c1bf301704/webdav/file_test.go (about) 1 // Copyright 2014 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 webdav 6 7 import ( 8 "context" 9 "encoding/xml" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "os" 14 "path" 15 "path/filepath" 16 "reflect" 17 "runtime" 18 "sort" 19 "strconv" 20 "strings" 21 "testing" 22 ) 23 24 func TestSlashClean(t *testing.T) { 25 testCases := []string{ 26 "", 27 ".", 28 "/", 29 "/./", 30 "//", 31 "//.", 32 "//a", 33 "/a", 34 "/a/b/c", 35 "/a//b/./../c/d/", 36 "a", 37 "a/b/c", 38 } 39 for _, tc := range testCases { 40 got := slashClean(tc) 41 want := path.Clean("/" + tc) 42 if got != want { 43 t.Errorf("tc=%q: got %q, want %q", tc, got, want) 44 } 45 } 46 } 47 48 func TestDirResolve(t *testing.T) { 49 testCases := []struct { 50 dir, name, want string 51 }{ 52 {"/", "", "/"}, 53 {"/", "/", "/"}, 54 {"/", ".", "/"}, 55 {"/", "./a", "/a"}, 56 {"/", "..", "/"}, 57 {"/", "..", "/"}, 58 {"/", "../", "/"}, 59 {"/", "../.", "/"}, 60 {"/", "../a", "/a"}, 61 {"/", "../..", "/"}, 62 {"/", "../bar/a", "/bar/a"}, 63 {"/", "../baz/a", "/baz/a"}, 64 {"/", "...", "/..."}, 65 {"/", ".../a", "/.../a"}, 66 {"/", ".../..", "/"}, 67 {"/", "a", "/a"}, 68 {"/", "a/./b", "/a/b"}, 69 {"/", "a/../../b", "/b"}, 70 {"/", "a/../b", "/b"}, 71 {"/", "a/b", "/a/b"}, 72 {"/", "a/b/c/../../d", "/a/d"}, 73 {"/", "a/b/c/../../../d", "/d"}, 74 {"/", "a/b/c/../../../../d", "/d"}, 75 {"/", "a/b/c/d", "/a/b/c/d"}, 76 77 {"/foo/bar", "", "/foo/bar"}, 78 {"/foo/bar", "/", "/foo/bar"}, 79 {"/foo/bar", ".", "/foo/bar"}, 80 {"/foo/bar", "./a", "/foo/bar/a"}, 81 {"/foo/bar", "..", "/foo/bar"}, 82 {"/foo/bar", "../", "/foo/bar"}, 83 {"/foo/bar", "../.", "/foo/bar"}, 84 {"/foo/bar", "../a", "/foo/bar/a"}, 85 {"/foo/bar", "../..", "/foo/bar"}, 86 {"/foo/bar", "../bar/a", "/foo/bar/bar/a"}, 87 {"/foo/bar", "../baz/a", "/foo/bar/baz/a"}, 88 {"/foo/bar", "...", "/foo/bar/..."}, 89 {"/foo/bar", ".../a", "/foo/bar/.../a"}, 90 {"/foo/bar", ".../..", "/foo/bar"}, 91 {"/foo/bar", "a", "/foo/bar/a"}, 92 {"/foo/bar", "a/./b", "/foo/bar/a/b"}, 93 {"/foo/bar", "a/../../b", "/foo/bar/b"}, 94 {"/foo/bar", "a/../b", "/foo/bar/b"}, 95 {"/foo/bar", "a/b", "/foo/bar/a/b"}, 96 {"/foo/bar", "a/b/c/../../d", "/foo/bar/a/d"}, 97 {"/foo/bar", "a/b/c/../../../d", "/foo/bar/d"}, 98 {"/foo/bar", "a/b/c/../../../../d", "/foo/bar/d"}, 99 {"/foo/bar", "a/b/c/d", "/foo/bar/a/b/c/d"}, 100 101 {"/foo/bar/", "", "/foo/bar"}, 102 {"/foo/bar/", "/", "/foo/bar"}, 103 {"/foo/bar/", ".", "/foo/bar"}, 104 {"/foo/bar/", "./a", "/foo/bar/a"}, 105 {"/foo/bar/", "..", "/foo/bar"}, 106 107 {"/foo//bar///", "", "/foo/bar"}, 108 {"/foo//bar///", "/", "/foo/bar"}, 109 {"/foo//bar///", ".", "/foo/bar"}, 110 {"/foo//bar///", "./a", "/foo/bar/a"}, 111 {"/foo//bar///", "..", "/foo/bar"}, 112 113 {"/x/y/z", "ab/c\x00d/ef", ""}, 114 115 {".", "", "."}, 116 {".", "/", "."}, 117 {".", ".", "."}, 118 {".", "./a", "a"}, 119 {".", "..", "."}, 120 {".", "..", "."}, 121 {".", "../", "."}, 122 {".", "../.", "."}, 123 {".", "../a", "a"}, 124 {".", "../..", "."}, 125 {".", "../bar/a", "bar/a"}, 126 {".", "../baz/a", "baz/a"}, 127 {".", "...", "..."}, 128 {".", ".../a", ".../a"}, 129 {".", ".../..", "."}, 130 {".", "a", "a"}, 131 {".", "a/./b", "a/b"}, 132 {".", "a/../../b", "b"}, 133 {".", "a/../b", "b"}, 134 {".", "a/b", "a/b"}, 135 {".", "a/b/c/../../d", "a/d"}, 136 {".", "a/b/c/../../../d", "d"}, 137 {".", "a/b/c/../../../../d", "d"}, 138 {".", "a/b/c/d", "a/b/c/d"}, 139 140 {"", "", "."}, 141 {"", "/", "."}, 142 {"", ".", "."}, 143 {"", "./a", "a"}, 144 {"", "..", "."}, 145 } 146 147 for _, tc := range testCases { 148 d := Dir(filepath.FromSlash(tc.dir)) 149 if got := filepath.ToSlash(d.resolve(tc.name)); got != tc.want { 150 t.Errorf("dir=%q, name=%q: got %q, want %q", tc.dir, tc.name, got, tc.want) 151 } 152 } 153 } 154 155 func TestWalk(t *testing.T) { 156 type walkStep struct { 157 name, frag string 158 final bool 159 } 160 161 testCases := []struct { 162 dir string 163 want []walkStep 164 }{ 165 {"", []walkStep{ 166 {"", "", true}, 167 }}, 168 {"/", []walkStep{ 169 {"", "", true}, 170 }}, 171 {"/a", []walkStep{ 172 {"", "a", true}, 173 }}, 174 {"/a/", []walkStep{ 175 {"", "a", true}, 176 }}, 177 {"/a/b", []walkStep{ 178 {"", "a", false}, 179 {"a", "b", true}, 180 }}, 181 {"/a/b/", []walkStep{ 182 {"", "a", false}, 183 {"a", "b", true}, 184 }}, 185 {"/a/b/c", []walkStep{ 186 {"", "a", false}, 187 {"a", "b", false}, 188 {"b", "c", true}, 189 }}, 190 // The following test case is the one mentioned explicitly 191 // in the method description. 192 {"/foo/bar/x", []walkStep{ 193 {"", "foo", false}, 194 {"foo", "bar", false}, 195 {"bar", "x", true}, 196 }}, 197 } 198 199 ctx := context.Background() 200 201 for _, tc := range testCases { 202 fs := NewMemFS().(*memFS) 203 204 parts := strings.Split(tc.dir, "/") 205 for p := 2; p < len(parts); p++ { 206 d := strings.Join(parts[:p], "/") 207 if err := fs.Mkdir(ctx, d, 0666); err != nil { 208 t.Errorf("tc.dir=%q: mkdir: %q: %v", tc.dir, d, err) 209 } 210 } 211 212 i, prevFrag := 0, "" 213 err := fs.walk("test", tc.dir, func(dir *memFSNode, frag string, final bool) error { 214 got := walkStep{ 215 name: prevFrag, 216 frag: frag, 217 final: final, 218 } 219 want := tc.want[i] 220 221 if got != want { 222 return fmt.Errorf("got %+v, want %+v", got, want) 223 } 224 i, prevFrag = i+1, frag 225 return nil 226 }) 227 if err != nil { 228 t.Errorf("tc.dir=%q: %v", tc.dir, err) 229 } 230 } 231 } 232 233 // find appends to ss the names of the named file and its children. It is 234 // analogous to the Unix find command. 235 // 236 // The returned strings are not guaranteed to be in any particular order. 237 func find(ctx context.Context, ss []string, fs FileSystem, name string) ([]string, error) { 238 stat, err := fs.Stat(ctx, name) 239 if err != nil { 240 return nil, err 241 } 242 ss = append(ss, name) 243 if stat.IsDir() { 244 f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0) 245 if err != nil { 246 return nil, err 247 } 248 defer f.Close() 249 children, err := f.Readdir(-1) 250 if err != nil { 251 return nil, err 252 } 253 for _, c := range children { 254 ss, err = find(ctx, ss, fs, path.Join(name, c.Name())) 255 if err != nil { 256 return nil, err 257 } 258 } 259 } 260 return ss, nil 261 } 262 263 func testFS(t *testing.T, fs FileSystem) { 264 errStr := func(err error) string { 265 switch { 266 case os.IsExist(err): 267 return "errExist" 268 case os.IsNotExist(err): 269 return "errNotExist" 270 case err != nil: 271 return "err" 272 } 273 return "ok" 274 } 275 276 // The non-"find" non-"stat" test cases should change the file system state. The 277 // indentation of the "find"s and "stat"s helps distinguish such test cases. 278 testCases := []string{ 279 " stat / want dir", 280 " stat /a want errNotExist", 281 " stat /d want errNotExist", 282 " stat /d/e want errNotExist", 283 "create /a A want ok", 284 " stat /a want 1", 285 "create /d/e EEE want errNotExist", 286 "mk-dir /a want errExist", 287 "mk-dir /d/m want errNotExist", 288 "mk-dir /d want ok", 289 " stat /d want dir", 290 "create /d/e EEE want ok", 291 " stat /d/e want 3", 292 " find / /a /d /d/e", 293 "create /d/f FFFF want ok", 294 "create /d/g GGGGGGG want ok", 295 "mk-dir /d/m want ok", 296 "mk-dir /d/m want errExist", 297 "create /d/m/p PPPPP want ok", 298 " stat /d/e want 3", 299 " stat /d/f want 4", 300 " stat /d/g want 7", 301 " stat /d/h want errNotExist", 302 " stat /d/m want dir", 303 " stat /d/m/p want 5", 304 " find / /a /d /d/e /d/f /d/g /d/m /d/m/p", 305 "rm-all /d want ok", 306 " stat /a want 1", 307 " stat /d want errNotExist", 308 " stat /d/e want errNotExist", 309 " stat /d/f want errNotExist", 310 " stat /d/g want errNotExist", 311 " stat /d/m want errNotExist", 312 " stat /d/m/p want errNotExist", 313 " find / /a", 314 "mk-dir /d/m want errNotExist", 315 "mk-dir /d want ok", 316 "create /d/f FFFF want ok", 317 "rm-all /d/f want ok", 318 "mk-dir /d/m want ok", 319 "rm-all /z want ok", 320 "rm-all / want err", 321 "create /b BB want ok", 322 " stat / want dir", 323 " stat /a want 1", 324 " stat /b want 2", 325 " stat /c want errNotExist", 326 " stat /d want dir", 327 " stat /d/m want dir", 328 " find / /a /b /d /d/m", 329 "move__ o=F /b /c want ok", 330 " stat /b want errNotExist", 331 " stat /c want 2", 332 " stat /d/m want dir", 333 " stat /d/n want errNotExist", 334 " find / /a /c /d /d/m", 335 "move__ o=F /d/m /d/n want ok", 336 "create /d/n/q QQQQ want ok", 337 " stat /d/m want errNotExist", 338 " stat /d/n want dir", 339 " stat /d/n/q want 4", 340 "move__ o=F /d /d/n/z want err", 341 "move__ o=T /c /d/n/q want ok", 342 " stat /c want errNotExist", 343 " stat /d/n/q want 2", 344 " find / /a /d /d/n /d/n/q", 345 "create /d/n/r RRRRR want ok", 346 "mk-dir /u want ok", 347 "mk-dir /u/v want ok", 348 "move__ o=F /d/n /u want errExist", 349 "create /t TTTTTT want ok", 350 "move__ o=F /d/n /t want errExist", 351 "rm-all /t want ok", 352 "move__ o=F /d/n /t want ok", 353 " stat /d want dir", 354 " stat /d/n want errNotExist", 355 " stat /d/n/r want errNotExist", 356 " stat /t want dir", 357 " stat /t/q want 2", 358 " stat /t/r want 5", 359 " find / /a /d /t /t/q /t/r /u /u/v", 360 "move__ o=F /t / want errExist", 361 "move__ o=T /t /u/v want ok", 362 " stat /u/v/r want 5", 363 "move__ o=F / /z want err", 364 " find / /a /d /u /u/v /u/v/q /u/v/r", 365 " stat /a want 1", 366 " stat /b want errNotExist", 367 " stat /c want errNotExist", 368 " stat /u/v/r want 5", 369 "copy__ o=F d=0 /a /b want ok", 370 "copy__ o=T d=0 /a /c want ok", 371 " stat /a want 1", 372 " stat /b want 1", 373 " stat /c want 1", 374 " stat /u/v/r want 5", 375 "copy__ o=F d=0 /u/v/r /b want errExist", 376 " stat /b want 1", 377 "copy__ o=T d=0 /u/v/r /b want ok", 378 " stat /a want 1", 379 " stat /b want 5", 380 " stat /u/v/r want 5", 381 "rm-all /a want ok", 382 "rm-all /b want ok", 383 "mk-dir /u/v/w want ok", 384 "create /u/v/w/s SSSSSSSS want ok", 385 " stat /d want dir", 386 " stat /d/x want errNotExist", 387 " stat /d/y want errNotExist", 388 " stat /u/v/r want 5", 389 " stat /u/v/w/s want 8", 390 " find / /c /d /u /u/v /u/v/q /u/v/r /u/v/w /u/v/w/s", 391 "copy__ o=T d=0 /u/v /d/x want ok", 392 "copy__ o=T d=∞ /u/v /d/y want ok", 393 "rm-all /u want ok", 394 " stat /d/x want dir", 395 " stat /d/x/q want errNotExist", 396 " stat /d/x/r want errNotExist", 397 " stat /d/x/w want errNotExist", 398 " stat /d/x/w/s want errNotExist", 399 " stat /d/y want dir", 400 " stat /d/y/q want 2", 401 " stat /d/y/r want 5", 402 " stat /d/y/w want dir", 403 " stat /d/y/w/s want 8", 404 " stat /u want errNotExist", 405 " find / /c /d /d/x /d/y /d/y/q /d/y/r /d/y/w /d/y/w/s", 406 "copy__ o=F d=∞ /d/y /d/x want errExist", 407 } 408 409 ctx := context.Background() 410 411 for i, tc := range testCases { 412 tc = strings.TrimSpace(tc) 413 j := strings.IndexByte(tc, ' ') 414 if j < 0 { 415 t.Fatalf("test case #%d %q: invalid command", i, tc) 416 } 417 op, arg := tc[:j], tc[j+1:] 418 419 switch op { 420 default: 421 t.Fatalf("test case #%d %q: invalid operation %q", i, tc, op) 422 423 case "create": 424 parts := strings.Split(arg, " ") 425 if len(parts) != 4 || parts[2] != "want" { 426 t.Fatalf("test case #%d %q: invalid write", i, tc) 427 } 428 f, opErr := fs.OpenFile(ctx, parts[0], os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 429 if got := errStr(opErr); got != parts[3] { 430 t.Fatalf("test case #%d %q: OpenFile: got %q (%v), want %q", i, tc, got, opErr, parts[3]) 431 } 432 if f != nil { 433 if _, err := f.Write([]byte(parts[1])); err != nil { 434 t.Fatalf("test case #%d %q: Write: %v", i, tc, err) 435 } 436 if err := f.Close(); err != nil { 437 t.Fatalf("test case #%d %q: Close: %v", i, tc, err) 438 } 439 } 440 441 case "find": 442 got, err := find(ctx, nil, fs, "/") 443 if err != nil { 444 t.Fatalf("test case #%d %q: find: %v", i, tc, err) 445 } 446 sort.Strings(got) 447 want := strings.Split(arg, " ") 448 if !reflect.DeepEqual(got, want) { 449 t.Fatalf("test case #%d %q:\ngot %s\nwant %s", i, tc, got, want) 450 } 451 452 case "copy__", "mk-dir", "move__", "rm-all", "stat": 453 nParts := 3 454 switch op { 455 case "copy__": 456 nParts = 6 457 case "move__": 458 nParts = 5 459 } 460 parts := strings.Split(arg, " ") 461 if len(parts) != nParts { 462 t.Fatalf("test case #%d %q: invalid %s", i, tc, op) 463 } 464 465 got, opErr := "", error(nil) 466 switch op { 467 case "copy__": 468 depth := 0 469 if parts[1] == "d=∞" { 470 depth = infiniteDepth 471 } 472 _, opErr = copyFiles(ctx, fs, parts[2], parts[3], parts[0] == "o=T", depth, 0) 473 case "mk-dir": 474 opErr = fs.Mkdir(ctx, parts[0], 0777) 475 case "move__": 476 _, opErr = moveFiles(ctx, fs, parts[1], parts[2], parts[0] == "o=T") 477 case "rm-all": 478 opErr = fs.RemoveAll(ctx, parts[0]) 479 case "stat": 480 var stat os.FileInfo 481 fileName := parts[0] 482 if stat, opErr = fs.Stat(ctx, fileName); opErr == nil { 483 if stat.IsDir() { 484 got = "dir" 485 } else { 486 got = strconv.Itoa(int(stat.Size())) 487 } 488 489 if fileName == "/" { 490 // For a Dir FileSystem, the virtual file system root maps to a 491 // real file system name like "/tmp/webdav-test012345", which does 492 // not end with "/". We skip such cases. 493 } else if statName := stat.Name(); path.Base(fileName) != statName { 494 t.Fatalf("test case #%d %q: file name %q inconsistent with stat name %q", 495 i, tc, fileName, statName) 496 } 497 } 498 } 499 if got == "" { 500 got = errStr(opErr) 501 } 502 503 if parts[len(parts)-2] != "want" { 504 t.Fatalf("test case #%d %q: invalid %s", i, tc, op) 505 } 506 if want := parts[len(parts)-1]; got != want { 507 t.Fatalf("test case #%d %q: got %q (%v), want %q", i, tc, got, opErr, want) 508 } 509 } 510 } 511 } 512 513 func TestDir(t *testing.T) { 514 switch runtime.GOOS { 515 case "nacl": 516 t.Skip("see golang.org/issue/12004") 517 case "plan9": 518 t.Skip("see golang.org/issue/11453") 519 } 520 521 td, err := ioutil.TempDir("", "webdav-test") 522 if err != nil { 523 t.Fatal(err) 524 } 525 defer os.RemoveAll(td) 526 testFS(t, Dir(td)) 527 } 528 529 func TestMemFS(t *testing.T) { 530 testFS(t, NewMemFS()) 531 } 532 533 func TestMemFSRoot(t *testing.T) { 534 ctx := context.Background() 535 fs := NewMemFS() 536 for i := 0; i < 5; i++ { 537 stat, err := fs.Stat(ctx, "/") 538 if err != nil { 539 t.Fatalf("i=%d: Stat: %v", i, err) 540 } 541 if !stat.IsDir() { 542 t.Fatalf("i=%d: Stat.IsDir is false, want true", i) 543 } 544 545 f, err := fs.OpenFile(ctx, "/", os.O_RDONLY, 0) 546 if err != nil { 547 t.Fatalf("i=%d: OpenFile: %v", i, err) 548 } 549 defer f.Close() 550 children, err := f.Readdir(-1) 551 if err != nil { 552 t.Fatalf("i=%d: Readdir: %v", i, err) 553 } 554 if len(children) != i { 555 t.Fatalf("i=%d: got %d children, want %d", i, len(children), i) 556 } 557 558 if _, err := f.Write(make([]byte, 1)); err == nil { 559 t.Fatalf("i=%d: Write: got nil error, want non-nil", i) 560 } 561 562 if err := fs.Mkdir(ctx, fmt.Sprintf("/dir%d", i), 0777); err != nil { 563 t.Fatalf("i=%d: Mkdir: %v", i, err) 564 } 565 } 566 } 567 568 func TestMemFileReaddir(t *testing.T) { 569 ctx := context.Background() 570 fs := NewMemFS() 571 if err := fs.Mkdir(ctx, "/foo", 0777); err != nil { 572 t.Fatalf("Mkdir: %v", err) 573 } 574 readdir := func(count int) ([]os.FileInfo, error) { 575 f, err := fs.OpenFile(ctx, "/foo", os.O_RDONLY, 0) 576 if err != nil { 577 t.Fatalf("OpenFile: %v", err) 578 } 579 defer f.Close() 580 return f.Readdir(count) 581 } 582 if got, err := readdir(-1); len(got) != 0 || err != nil { 583 t.Fatalf("readdir(-1): got %d fileInfos with err=%v, want 0, <nil>", len(got), err) 584 } 585 if got, err := readdir(+1); len(got) != 0 || err != io.EOF { 586 t.Fatalf("readdir(+1): got %d fileInfos with err=%v, want 0, EOF", len(got), err) 587 } 588 } 589 590 func TestMemFile(t *testing.T) { 591 testCases := []string{ 592 "wantData ", 593 "wantSize 0", 594 "write abc", 595 "wantData abc", 596 "write de", 597 "wantData abcde", 598 "wantSize 5", 599 "write 5*x", 600 "write 4*y+2*z", 601 "write 3*st", 602 "wantData abcdexxxxxyyyyzzststst", 603 "wantSize 22", 604 "seek set 4 want 4", 605 "write EFG", 606 "wantData abcdEFGxxxyyyyzzststst", 607 "wantSize 22", 608 "seek set 2 want 2", 609 "read cdEF", 610 "read Gx", 611 "seek cur 0 want 8", 612 "seek cur 2 want 10", 613 "seek cur -1 want 9", 614 "write J", 615 "wantData abcdEFGxxJyyyyzzststst", 616 "wantSize 22", 617 "seek cur -4 want 6", 618 "write ghijk", 619 "wantData abcdEFghijkyyyzzststst", 620 "wantSize 22", 621 "read yyyz", 622 "seek cur 0 want 15", 623 "write ", 624 "seek cur 0 want 15", 625 "read ", 626 "seek cur 0 want 15", 627 "seek end -3 want 19", 628 "write ZZ", 629 "wantData abcdEFghijkyyyzzstsZZt", 630 "wantSize 22", 631 "write 4*A", 632 "wantData abcdEFghijkyyyzzstsZZAAAA", 633 "wantSize 25", 634 "seek end 0 want 25", 635 "seek end -5 want 20", 636 "read Z+4*A", 637 "write 5*B", 638 "wantData abcdEFghijkyyyzzstsZZAAAABBBBB", 639 "wantSize 30", 640 "seek end 10 want 40", 641 "write C", 642 "wantData abcdEFghijkyyyzzstsZZAAAABBBBB..........C", 643 "wantSize 41", 644 "write D", 645 "wantData abcdEFghijkyyyzzstsZZAAAABBBBB..........CD", 646 "wantSize 42", 647 "seek set 43 want 43", 648 "write E", 649 "wantData abcdEFghijkyyyzzstsZZAAAABBBBB..........CD.E", 650 "wantSize 44", 651 "seek set 0 want 0", 652 "write 5*123456789_", 653 "wantData 123456789_123456789_123456789_123456789_123456789_", 654 "wantSize 50", 655 "seek cur 0 want 50", 656 "seek cur -99 want err", 657 } 658 659 ctx := context.Background() 660 661 const filename = "/foo" 662 fs := NewMemFS() 663 f, err := fs.OpenFile(ctx, filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 664 if err != nil { 665 t.Fatalf("OpenFile: %v", err) 666 } 667 defer f.Close() 668 669 for i, tc := range testCases { 670 j := strings.IndexByte(tc, ' ') 671 if j < 0 { 672 t.Fatalf("test case #%d %q: invalid command", i, tc) 673 } 674 op, arg := tc[:j], tc[j+1:] 675 676 // Expand an arg like "3*a+2*b" to "aaabb". 677 parts := strings.Split(arg, "+") 678 for j, part := range parts { 679 if k := strings.IndexByte(part, '*'); k >= 0 { 680 repeatCount, repeatStr := part[:k], part[k+1:] 681 n, err := strconv.Atoi(repeatCount) 682 if err != nil { 683 t.Fatalf("test case #%d %q: invalid repeat count %q", i, tc, repeatCount) 684 } 685 parts[j] = strings.Repeat(repeatStr, n) 686 } 687 } 688 arg = strings.Join(parts, "") 689 690 switch op { 691 default: 692 t.Fatalf("test case #%d %q: invalid operation %q", i, tc, op) 693 694 case "read": 695 buf := make([]byte, len(arg)) 696 if _, err := io.ReadFull(f, buf); err != nil { 697 t.Fatalf("test case #%d %q: ReadFull: %v", i, tc, err) 698 } 699 if got := string(buf); got != arg { 700 t.Fatalf("test case #%d %q:\ngot %q\nwant %q", i, tc, got, arg) 701 } 702 703 case "seek": 704 parts := strings.Split(arg, " ") 705 if len(parts) != 4 { 706 t.Fatalf("test case #%d %q: invalid seek", i, tc) 707 } 708 709 whence := 0 710 switch parts[0] { 711 default: 712 t.Fatalf("test case #%d %q: invalid seek whence", i, tc) 713 case "set": 714 whence = os.SEEK_SET 715 case "cur": 716 whence = os.SEEK_CUR 717 case "end": 718 whence = os.SEEK_END 719 } 720 offset, err := strconv.Atoi(parts[1]) 721 if err != nil { 722 t.Fatalf("test case #%d %q: invalid offset %q", i, tc, parts[1]) 723 } 724 725 if parts[2] != "want" { 726 t.Fatalf("test case #%d %q: invalid seek", i, tc) 727 } 728 if parts[3] == "err" { 729 _, err := f.Seek(int64(offset), whence) 730 if err == nil { 731 t.Fatalf("test case #%d %q: Seek returned nil error, want non-nil", i, tc) 732 } 733 } else { 734 got, err := f.Seek(int64(offset), whence) 735 if err != nil { 736 t.Fatalf("test case #%d %q: Seek: %v", i, tc, err) 737 } 738 want, err := strconv.Atoi(parts[3]) 739 if err != nil { 740 t.Fatalf("test case #%d %q: invalid want %q", i, tc, parts[3]) 741 } 742 if got != int64(want) { 743 t.Fatalf("test case #%d %q: got %d, want %d", i, tc, got, want) 744 } 745 } 746 747 case "write": 748 n, err := f.Write([]byte(arg)) 749 if err != nil { 750 t.Fatalf("test case #%d %q: write: %v", i, tc, err) 751 } 752 if n != len(arg) { 753 t.Fatalf("test case #%d %q: write returned %d bytes, want %d", i, tc, n, len(arg)) 754 } 755 756 case "wantData": 757 g, err := fs.OpenFile(ctx, filename, os.O_RDONLY, 0666) 758 if err != nil { 759 t.Fatalf("test case #%d %q: OpenFile: %v", i, tc, err) 760 } 761 gotBytes, err := ioutil.ReadAll(g) 762 if err != nil { 763 t.Fatalf("test case #%d %q: ReadAll: %v", i, tc, err) 764 } 765 for i, c := range gotBytes { 766 if c == '\x00' { 767 gotBytes[i] = '.' 768 } 769 } 770 got := string(gotBytes) 771 if got != arg { 772 t.Fatalf("test case #%d %q:\ngot %q\nwant %q", i, tc, got, arg) 773 } 774 if err := g.Close(); err != nil { 775 t.Fatalf("test case #%d %q: Close: %v", i, tc, err) 776 } 777 778 case "wantSize": 779 n, err := strconv.Atoi(arg) 780 if err != nil { 781 t.Fatalf("test case #%d %q: invalid size %q", i, tc, arg) 782 } 783 fi, err := fs.Stat(ctx, filename) 784 if err != nil { 785 t.Fatalf("test case #%d %q: Stat: %v", i, tc, err) 786 } 787 if got, want := fi.Size(), int64(n); got != want { 788 t.Fatalf("test case #%d %q: got %d, want %d", i, tc, got, want) 789 } 790 } 791 } 792 } 793 794 // TestMemFileWriteAllocs tests that writing N consecutive 1KiB chunks to a 795 // memFile doesn't allocate a new buffer for each of those N times. Otherwise, 796 // calling io.Copy(aMemFile, src) is likely to have quadratic complexity. 797 func TestMemFileWriteAllocs(t *testing.T) { 798 if runtime.Compiler == "gccgo" { 799 t.Skip("gccgo allocates here") 800 } 801 ctx := context.Background() 802 fs := NewMemFS() 803 f, err := fs.OpenFile(ctx, "/xxx", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 804 if err != nil { 805 t.Fatalf("OpenFile: %v", err) 806 } 807 defer f.Close() 808 809 xxx := make([]byte, 1024) 810 for i := range xxx { 811 xxx[i] = 'x' 812 } 813 814 a := testing.AllocsPerRun(100, func() { 815 f.Write(xxx) 816 }) 817 // AllocsPerRun returns an integral value, so we compare the rounded-down 818 // number to zero. 819 if a > 0 { 820 t.Fatalf("%v allocs per run, want 0", a) 821 } 822 } 823 824 func BenchmarkMemFileWrite(b *testing.B) { 825 ctx := context.Background() 826 fs := NewMemFS() 827 xxx := make([]byte, 1024) 828 for i := range xxx { 829 xxx[i] = 'x' 830 } 831 832 b.ResetTimer() 833 for i := 0; i < b.N; i++ { 834 f, err := fs.OpenFile(ctx, "/xxx", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 835 if err != nil { 836 b.Fatalf("OpenFile: %v", err) 837 } 838 for j := 0; j < 100; j++ { 839 f.Write(xxx) 840 } 841 if err := f.Close(); err != nil { 842 b.Fatalf("Close: %v", err) 843 } 844 if err := fs.RemoveAll(ctx, "/xxx"); err != nil { 845 b.Fatalf("RemoveAll: %v", err) 846 } 847 } 848 } 849 850 func TestCopyMoveProps(t *testing.T) { 851 ctx := context.Background() 852 fs := NewMemFS() 853 create := func(name string) error { 854 f, err := fs.OpenFile(ctx, name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 855 if err != nil { 856 return err 857 } 858 _, wErr := f.Write([]byte("contents")) 859 cErr := f.Close() 860 if wErr != nil { 861 return wErr 862 } 863 return cErr 864 } 865 patch := func(name string, patches ...Proppatch) error { 866 f, err := fs.OpenFile(ctx, name, os.O_RDWR, 0666) 867 if err != nil { 868 return err 869 } 870 _, pErr := f.(DeadPropsHolder).Patch(patches) 871 cErr := f.Close() 872 if pErr != nil { 873 return pErr 874 } 875 return cErr 876 } 877 props := func(name string) (map[xml.Name]Property, error) { 878 f, err := fs.OpenFile(ctx, name, os.O_RDWR, 0666) 879 if err != nil { 880 return nil, err 881 } 882 m, pErr := f.(DeadPropsHolder).DeadProps() 883 cErr := f.Close() 884 if pErr != nil { 885 return nil, pErr 886 } 887 if cErr != nil { 888 return nil, cErr 889 } 890 return m, nil 891 } 892 893 p0 := Property{ 894 XMLName: xml.Name{Space: "x:", Local: "boat"}, 895 InnerXML: []byte("pea-green"), 896 } 897 p1 := Property{ 898 XMLName: xml.Name{Space: "x:", Local: "ring"}, 899 InnerXML: []byte("1 shilling"), 900 } 901 p2 := Property{ 902 XMLName: xml.Name{Space: "x:", Local: "spoon"}, 903 InnerXML: []byte("runcible"), 904 } 905 p3 := Property{ 906 XMLName: xml.Name{Space: "x:", Local: "moon"}, 907 InnerXML: []byte("light"), 908 } 909 910 if err := create("/src"); err != nil { 911 t.Fatalf("create /src: %v", err) 912 } 913 if err := patch("/src", Proppatch{Props: []Property{p0, p1}}); err != nil { 914 t.Fatalf("patch /src +p0 +p1: %v", err) 915 } 916 if _, err := copyFiles(ctx, fs, "/src", "/tmp", true, infiniteDepth, 0); err != nil { 917 t.Fatalf("copyFiles /src /tmp: %v", err) 918 } 919 if _, err := moveFiles(ctx, fs, "/tmp", "/dst", true); err != nil { 920 t.Fatalf("moveFiles /tmp /dst: %v", err) 921 } 922 if err := patch("/src", Proppatch{Props: []Property{p0}, Remove: true}); err != nil { 923 t.Fatalf("patch /src -p0: %v", err) 924 } 925 if err := patch("/src", Proppatch{Props: []Property{p2}}); err != nil { 926 t.Fatalf("patch /src +p2: %v", err) 927 } 928 if err := patch("/dst", Proppatch{Props: []Property{p1}, Remove: true}); err != nil { 929 t.Fatalf("patch /dst -p1: %v", err) 930 } 931 if err := patch("/dst", Proppatch{Props: []Property{p3}}); err != nil { 932 t.Fatalf("patch /dst +p3: %v", err) 933 } 934 935 gotSrc, err := props("/src") 936 if err != nil { 937 t.Fatalf("props /src: %v", err) 938 } 939 wantSrc := map[xml.Name]Property{ 940 p1.XMLName: p1, 941 p2.XMLName: p2, 942 } 943 if !reflect.DeepEqual(gotSrc, wantSrc) { 944 t.Fatalf("props /src:\ngot %v\nwant %v", gotSrc, wantSrc) 945 } 946 947 gotDst, err := props("/dst") 948 if err != nil { 949 t.Fatalf("props /dst: %v", err) 950 } 951 wantDst := map[xml.Name]Property{ 952 p0.XMLName: p0, 953 p3.XMLName: p3, 954 } 955 if !reflect.DeepEqual(gotDst, wantDst) { 956 t.Fatalf("props /dst:\ngot %v\nwant %v", gotDst, wantDst) 957 } 958 } 959 960 func TestWalkFS(t *testing.T) { 961 testCases := []struct { 962 desc string 963 buildfs []string 964 startAt string 965 depth int 966 walkFn filepath.WalkFunc 967 want []string 968 }{{ 969 "just root", 970 []string{}, 971 "/", 972 infiniteDepth, 973 nil, 974 []string{ 975 "/", 976 }, 977 }, { 978 "infinite walk from root", 979 []string{ 980 "mkdir /a", 981 "mkdir /a/b", 982 "touch /a/b/c", 983 "mkdir /a/d", 984 "mkdir /e", 985 "touch /f", 986 }, 987 "/", 988 infiniteDepth, 989 nil, 990 []string{ 991 "/", 992 "/a", 993 "/a/b", 994 "/a/b/c", 995 "/a/d", 996 "/e", 997 "/f", 998 }, 999 }, { 1000 "infinite walk from subdir", 1001 []string{ 1002 "mkdir /a", 1003 "mkdir /a/b", 1004 "touch /a/b/c", 1005 "mkdir /a/d", 1006 "mkdir /e", 1007 "touch /f", 1008 }, 1009 "/a", 1010 infiniteDepth, 1011 nil, 1012 []string{ 1013 "/a", 1014 "/a/b", 1015 "/a/b/c", 1016 "/a/d", 1017 }, 1018 }, { 1019 "depth 1 walk from root", 1020 []string{ 1021 "mkdir /a", 1022 "mkdir /a/b", 1023 "touch /a/b/c", 1024 "mkdir /a/d", 1025 "mkdir /e", 1026 "touch /f", 1027 }, 1028 "/", 1029 1, 1030 nil, 1031 []string{ 1032 "/", 1033 "/a", 1034 "/e", 1035 "/f", 1036 }, 1037 }, { 1038 "depth 1 walk from subdir", 1039 []string{ 1040 "mkdir /a", 1041 "mkdir /a/b", 1042 "touch /a/b/c", 1043 "mkdir /a/b/g", 1044 "mkdir /a/b/g/h", 1045 "touch /a/b/g/i", 1046 "touch /a/b/g/h/j", 1047 }, 1048 "/a/b", 1049 1, 1050 nil, 1051 []string{ 1052 "/a/b", 1053 "/a/b/c", 1054 "/a/b/g", 1055 }, 1056 }, { 1057 "depth 0 walk from subdir", 1058 []string{ 1059 "mkdir /a", 1060 "mkdir /a/b", 1061 "touch /a/b/c", 1062 "mkdir /a/b/g", 1063 "mkdir /a/b/g/h", 1064 "touch /a/b/g/i", 1065 "touch /a/b/g/h/j", 1066 }, 1067 "/a/b", 1068 0, 1069 nil, 1070 []string{ 1071 "/a/b", 1072 }, 1073 }, { 1074 "infinite walk from file", 1075 []string{ 1076 "mkdir /a", 1077 "touch /a/b", 1078 "touch /a/c", 1079 }, 1080 "/a/b", 1081 0, 1082 nil, 1083 []string{ 1084 "/a/b", 1085 }, 1086 }, { 1087 "infinite walk with skipped subdir", 1088 []string{ 1089 "mkdir /a", 1090 "mkdir /a/b", 1091 "touch /a/b/c", 1092 "mkdir /a/b/g", 1093 "mkdir /a/b/g/h", 1094 "touch /a/b/g/i", 1095 "touch /a/b/g/h/j", 1096 "touch /a/b/z", 1097 }, 1098 "/", 1099 infiniteDepth, 1100 func(path string, info os.FileInfo, err error) error { 1101 if path == "/a/b/g" { 1102 return filepath.SkipDir 1103 } 1104 return nil 1105 }, 1106 []string{ 1107 "/", 1108 "/a", 1109 "/a/b", 1110 "/a/b/c", 1111 "/a/b/z", 1112 }, 1113 }} 1114 ctx := context.Background() 1115 for _, tc := range testCases { 1116 fs, err := buildTestFS(tc.buildfs) 1117 if err != nil { 1118 t.Fatalf("%s: cannot create test filesystem: %v", tc.desc, err) 1119 } 1120 var got []string 1121 traceFn := func(path string, info os.FileInfo, err error) error { 1122 if tc.walkFn != nil { 1123 err = tc.walkFn(path, info, err) 1124 if err != nil { 1125 return err 1126 } 1127 } 1128 got = append(got, path) 1129 return nil 1130 } 1131 fi, err := fs.Stat(ctx, tc.startAt) 1132 if err != nil { 1133 t.Fatalf("%s: cannot stat: %v", tc.desc, err) 1134 } 1135 err = walkFS(ctx, fs, tc.depth, tc.startAt, fi, traceFn) 1136 if err != nil { 1137 t.Errorf("%s:\ngot error %v, want nil", tc.desc, err) 1138 continue 1139 } 1140 sort.Strings(got) 1141 sort.Strings(tc.want) 1142 if !reflect.DeepEqual(got, tc.want) { 1143 t.Errorf("%s:\ngot %q\nwant %q", tc.desc, got, tc.want) 1144 continue 1145 } 1146 } 1147 } 1148 1149 func buildTestFS(buildfs []string) (FileSystem, error) { 1150 // TODO: Could this be merged with the build logic in TestFS? 1151 1152 ctx := context.Background() 1153 fs := NewMemFS() 1154 for _, b := range buildfs { 1155 op := strings.Split(b, " ") 1156 switch op[0] { 1157 case "mkdir": 1158 err := fs.Mkdir(ctx, op[1], os.ModeDir|0777) 1159 if err != nil { 1160 return nil, err 1161 } 1162 case "touch": 1163 f, err := fs.OpenFile(ctx, op[1], os.O_RDWR|os.O_CREATE, 0666) 1164 if err != nil { 1165 return nil, err 1166 } 1167 f.Close() 1168 case "write": 1169 f, err := fs.OpenFile(ctx, op[1], os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 1170 if err != nil { 1171 return nil, err 1172 } 1173 _, err = f.Write([]byte(op[2])) 1174 f.Close() 1175 if err != nil { 1176 return nil, err 1177 } 1178 default: 1179 return nil, fmt.Errorf("unknown file operation %q", op[0]) 1180 } 1181 } 1182 return fs, nil 1183 }