github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/fs/fs_test.go (about) 1 // +build linux darwin 2 3 /* 4 Copyright 2013 Google Inc. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package fs 20 21 import ( 22 "bytes" 23 "fmt" 24 "io" 25 "io/ioutil" 26 "log" 27 "os" 28 "os/exec" 29 "path/filepath" 30 "reflect" 31 "runtime" 32 "sort" 33 "strconv" 34 "strings" 35 "sync" 36 "testing" 37 "time" 38 39 "camlistore.org/pkg/test" 40 "camlistore.org/third_party/bazil.org/fuse/syscallx" 41 ) 42 43 var ( 44 errmu sync.Mutex 45 lasterr error 46 ) 47 48 func condSkip(t *testing.T) { 49 errmu.Lock() 50 defer errmu.Unlock() 51 if lasterr != nil { 52 t.Skipf("Skipping test; some other test already failed.") 53 } 54 if !(runtime.GOOS == "darwin" || runtime.GOOS == "linux") { 55 t.Skipf("Skipping test on OS %q", runtime.GOOS) 56 } 57 if runtime.GOOS == "darwin" { 58 _, err := os.Stat("/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs") 59 if os.IsNotExist(err) { 60 test.DependencyErrorOrSkip(t) 61 } else if err != nil { 62 t.Fatal(err) 63 } 64 } 65 } 66 67 func brokenTest(t *testing.T) { 68 if v, _ := strconv.ParseBool(os.Getenv("RUN_BROKEN_TESTS")); !v { 69 t.Skipf("Skipping broken tests without RUN_BROKEN_TESTS=1") 70 } 71 } 72 73 type mountEnv struct { 74 t *testing.T 75 mountPoint string 76 process *os.Process 77 } 78 79 func (e *mountEnv) Stat(s *stat) int64 { 80 file := filepath.Join(e.mountPoint, ".camli_fs_stats", s.name) 81 slurp, err := ioutil.ReadFile(file) 82 if err != nil { 83 e.t.Fatal(err) 84 } 85 slurp = bytes.TrimSpace(slurp) 86 v, err := strconv.ParseInt(string(slurp), 10, 64) 87 if err != nil { 88 e.t.Fatalf("unexpected value %q in file %s", slurp, file) 89 } 90 return v 91 } 92 93 func testName() string { 94 skip := 0 95 for { 96 pc, _, _, ok := runtime.Caller(skip) 97 skip++ 98 if !ok { 99 panic("Failed to find test name") 100 } 101 name := strings.TrimPrefix(runtime.FuncForPC(pc).Name(), "camlistore.org/pkg/fs.") 102 if strings.HasPrefix(name, "Test") { 103 return name 104 } 105 } 106 } 107 108 func inEmptyMutDir(t *testing.T, fn func(env *mountEnv, dir string)) { 109 cammountTest(t, func(env *mountEnv) { 110 dir := filepath.Join(env.mountPoint, "roots", testName()) 111 if err := os.Mkdir(dir, 0755); err != nil { 112 t.Fatalf("Failed to make roots/r dir: %v", err) 113 } 114 fi, err := os.Stat(dir) 115 if err != nil || !fi.IsDir() { 116 t.Fatalf("Stat of %s dir = %v, %v; want a directory", dir, fi, err) 117 } 118 fn(env, dir) 119 }) 120 } 121 122 func cammountTest(t *testing.T, fn func(env *mountEnv)) { 123 dupLog := io.MultiWriter(os.Stderr, testLog{t}) 124 log.SetOutput(dupLog) 125 defer log.SetOutput(os.Stderr) 126 127 w := test.GetWorld(t) 128 mountPoint, err := ioutil.TempDir("", "fs-test-mount") 129 if err != nil { 130 t.Fatal(err) 131 } 132 defer func() { 133 if err := os.RemoveAll(mountPoint); err != nil { 134 t.Fatal(err) 135 } 136 }() 137 verbose := "false" 138 var stderrDest io.Writer = ioutil.Discard 139 if v, _ := strconv.ParseBool(os.Getenv("VERBOSE_FUSE")); v { 140 verbose = "true" 141 stderrDest = testLog{t} 142 } 143 if v, _ := strconv.ParseBool(os.Getenv("VERBOSE_FUSE_STDERR")); v { 144 stderrDest = io.MultiWriter(stderrDest, os.Stderr) 145 } 146 147 mount := w.Cmd("cammount", "--debug="+verbose, mountPoint) 148 mount.Stderr = stderrDest 149 mount.Env = append(mount.Env, "CAMLI_TRACK_FS_STATS=1") 150 151 stdin, err := mount.StdinPipe() 152 if err != nil { 153 t.Fatal(err) 154 } 155 if err := w.Ping(); err != nil { 156 t.Fatal(err) 157 } 158 if err := mount.Start(); err != nil { 159 t.Fatal(err) 160 } 161 waitc := make(chan error, 1) 162 go func() { waitc <- mount.Wait() }() 163 defer func() { 164 log.Printf("Sending quit") 165 stdin.Write([]byte("q\n")) 166 select { 167 case <-time.After(5 * time.Second): 168 log.Printf("timeout waiting for cammount to finish") 169 mount.Process.Kill() 170 Unmount(mountPoint) 171 case err := <-waitc: 172 log.Printf("cammount exited: %v", err) 173 } 174 if !test.WaitFor(not(dirToBeFUSE(mountPoint)), 5*time.Second, 1*time.Second) { 175 // It didn't unmount. Try again. 176 Unmount(mountPoint) 177 } 178 }() 179 180 if !test.WaitFor(dirToBeFUSE(mountPoint), 5*time.Second, 100*time.Millisecond) { 181 t.Fatalf("error waiting for %s to be mounted", mountPoint) 182 } 183 fn(&mountEnv{ 184 t: t, 185 mountPoint: mountPoint, 186 process: mount.Process, 187 }) 188 189 } 190 191 func TestRoot(t *testing.T) { 192 condSkip(t) 193 cammountTest(t, func(env *mountEnv) { 194 f, err := os.Open(env.mountPoint) 195 if err != nil { 196 t.Fatal(err) 197 } 198 defer f.Close() 199 names, err := f.Readdirnames(-1) 200 if err != nil { 201 t.Fatal(err) 202 } 203 sort.Strings(names) 204 want := []string{"WELCOME.txt", "at", "date", "recent", "roots", "sha1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "tag"} 205 if !reflect.DeepEqual(names, want) { 206 t.Errorf("root directory = %q; want %q", names, want) 207 } 208 }) 209 } 210 211 type testLog struct { 212 t *testing.T 213 } 214 215 func (tl testLog) Write(p []byte) (n int, err error) { 216 tl.t.Log(strings.TrimSpace(string(p))) 217 return len(p), nil 218 } 219 220 func TestMutable(t *testing.T) { 221 condSkip(t) 222 inEmptyMutDir(t, func(env *mountEnv, rootDir string) { 223 filename := filepath.Join(rootDir, "x") 224 f, err := os.Create(filename) 225 if err != nil { 226 t.Fatalf("Create: %v", err) 227 } 228 if err := f.Close(); err != nil { 229 t.Fatalf("Close: %v", err) 230 } 231 fi, err := os.Stat(filename) 232 if err != nil { 233 t.Errorf("Stat error: %v", err) 234 } else if !fi.Mode().IsRegular() || fi.Size() != 0 { 235 t.Errorf("Stat of roots/r/x = %v size %d; want a %d byte regular file", fi.Mode(), fi.Size(), 0) 236 } 237 238 for _, str := range []string{"foo, ", "bar\n", "another line.\n"} { 239 f, err = os.OpenFile(filename, os.O_WRONLY|os.O_APPEND, 0644) 240 if err != nil { 241 t.Fatalf("OpenFile: %v", err) 242 } 243 if _, err := f.Write([]byte(str)); err != nil { 244 t.Logf("Error with append: %v", err) 245 t.Fatalf("Error appending %q to %s: %v", str, filename, err) 246 } 247 if err := f.Close(); err != nil { 248 t.Fatal(err) 249 } 250 } 251 ro0 := env.Stat(mutFileOpenRO) 252 slurp, err := ioutil.ReadFile(filename) 253 if err != nil { 254 t.Fatal(err) 255 } 256 if env.Stat(mutFileOpenRO)-ro0 != 1 { 257 t.Error("Read didn't trigger read-only path optimization.") 258 } 259 260 const want = "foo, bar\nanother line.\n" 261 fi, err = os.Stat(filename) 262 if err != nil { 263 t.Errorf("Stat error: %v", err) 264 } else if !fi.Mode().IsRegular() || fi.Size() != int64(len(want)) { 265 t.Errorf("Stat of roots/r/x = %v size %d; want a %d byte regular file", fi.Mode(), fi.Size(), len(want)) 266 } 267 if got := string(slurp); got != want { 268 t.Fatalf("contents = %q; want %q", got, want) 269 } 270 271 // Delete it. 272 if err := os.Remove(filename); err != nil { 273 t.Fatal(err) 274 } 275 276 // Gone? 277 if _, err := os.Stat(filename); !os.IsNotExist(err) { 278 t.Fatalf("expected file to be gone; got stat err = %v instead", err) 279 } 280 }) 281 } 282 283 func TestDifferentWriteTypes(t *testing.T) { 284 condSkip(t) 285 inEmptyMutDir(t, func(env *mountEnv, rootDir string) { 286 filename := filepath.Join(rootDir, "big") 287 288 writes := []struct { 289 name string 290 flag int 291 write []byte // if non-nil, Write is called 292 writeAt []byte // if non-nil, WriteAt is used 293 writePos int64 // writeAt position 294 want string // shortenString of remaining file 295 }{ 296 { 297 name: "write 8k of a", 298 flag: os.O_RDWR | os.O_CREATE | os.O_TRUNC, 299 write: bytes.Repeat([]byte("a"), 8<<10), 300 want: "a{8192}", 301 }, 302 { 303 name: "writeAt HI at offset 10", 304 flag: os.O_RDWR, 305 writeAt: []byte("HI"), 306 writePos: 10, 307 want: "a{10}HIa{8180}", 308 }, 309 { 310 name: "append single C", 311 flag: os.O_WRONLY | os.O_APPEND, 312 write: []byte("C"), 313 want: "a{10}HIa{8180}C", 314 }, 315 { 316 name: "append 8k of b", 317 flag: os.O_WRONLY | os.O_APPEND, 318 write: bytes.Repeat([]byte("b"), 8<<10), 319 want: "a{10}HIa{8180}Cb{8192}", 320 }, 321 } 322 323 for _, wr := range writes { 324 f, err := os.OpenFile(filename, wr.flag, 0644) 325 if err != nil { 326 t.Fatalf("%s: OpenFile: %v", wr.name, err) 327 } 328 if wr.write != nil { 329 if n, err := f.Write(wr.write); err != nil || n != len(wr.write) { 330 t.Fatalf("%s: Write = (%v, %v); want (%d, nil)", wr.name, n, err, len(wr.write)) 331 } 332 } 333 if wr.writeAt != nil { 334 if n, err := f.WriteAt(wr.writeAt, wr.writePos); err != nil || n != len(wr.writeAt) { 335 t.Fatalf("%s: WriteAt = (%v, %v); want (%d, nil)", wr.name, n, err, len(wr.writeAt)) 336 } 337 } 338 if err := f.Close(); err != nil { 339 t.Fatalf("%s: Close: %v", wr.name, err) 340 } 341 342 slurp, err := ioutil.ReadFile(filename) 343 if err != nil { 344 t.Fatalf("%s: Slurp: %v", wr.name, err) 345 } 346 if got := shortenString(string(slurp)); got != wr.want { 347 t.Fatalf("%s: afterwards, file = %q; want %q", wr.name, got, wr.want) 348 } 349 350 } 351 352 // Delete it. 353 if err := os.Remove(filename); err != nil { 354 t.Fatal(err) 355 } 356 }) 357 } 358 359 func statStr(name string) string { 360 fi, err := os.Stat(name) 361 if os.IsNotExist(err) { 362 return "ENOENT" 363 } 364 if err != nil { 365 return "err=" + err.Error() 366 } 367 return fmt.Sprintf("file %v, size %d", fi.Mode(), fi.Size()) 368 } 369 370 func TestRename(t *testing.T) { 371 condSkip(t) 372 inEmptyMutDir(t, func(env *mountEnv, rootDir string) { 373 name1 := filepath.Join(rootDir, "1") 374 name2 := filepath.Join(rootDir, "2") 375 subdir := filepath.Join(rootDir, "dir") 376 name3 := filepath.Join(subdir, "3") 377 378 contents := []byte("Some file contents") 379 const gone = "ENOENT" 380 const reg = "file -rw-------, size 18" 381 382 if err := ioutil.WriteFile(name1, contents, 0644); err != nil { 383 t.Fatal(err) 384 } 385 if err := os.Mkdir(subdir, 0755); err != nil { 386 t.Fatal(err) 387 } 388 389 if got, want := statStr(name1), reg; got != want { 390 t.Errorf("name1 = %q; want %q", got, want) 391 } 392 if err := os.Rename(name1, name2); err != nil { 393 t.Fatal(err) 394 } 395 if got, want := statStr(name1), gone; got != want { 396 t.Errorf("name1 = %q; want %q", got, want) 397 } 398 if got, want := statStr(name2), reg; got != want { 399 t.Errorf("name2 = %q; want %q", got, want) 400 } 401 402 // Moving to a different directory. 403 if err := os.Rename(name2, name3); err != nil { 404 t.Fatal(err) 405 } 406 if got, want := statStr(name2), gone; got != want { 407 t.Errorf("name2 = %q; want %q", got, want) 408 } 409 if got, want := statStr(name3), reg; got != want { 410 t.Errorf("name3 = %q; want %q", got, want) 411 } 412 }) 413 } 414 415 func parseXattrList(from []byte) map[string]bool { 416 attrNames := bytes.Split(from, []byte{0}) 417 m := map[string]bool{} 418 for _, nm := range attrNames { 419 if len(nm) == 0 { 420 continue 421 } 422 m[string(nm)] = true 423 } 424 return m 425 } 426 427 func TestXattr(t *testing.T) { 428 condSkip(t) 429 inEmptyMutDir(t, func(env *mountEnv, rootDir string) { 430 name1 := filepath.Join(rootDir, "1") 431 attr1 := "attr1" 432 attr2 := "attr2" 433 434 contents := []byte("Some file contents") 435 436 if err := ioutil.WriteFile(name1, contents, 0644); err != nil { 437 t.Fatal(err) 438 } 439 440 buf := make([]byte, 8192) 441 // list empty 442 n, err := syscallx.Listxattr(name1, buf) 443 if err != nil { 444 t.Errorf("Error in initial listxattr: %v", err) 445 } 446 if n != 0 { 447 t.Errorf("Expected zero-length xattr list, got %q", buf[:n]) 448 } 449 450 // get missing 451 n, err = syscallx.Getxattr(name1, attr1, buf) 452 if err == nil { 453 t.Errorf("Expected error getting non-existent xattr, got %q", buf[:n]) 454 } 455 456 // Set (two different attributes) 457 err = syscallx.Setxattr(name1, attr1, []byte("hello1"), 0) 458 if err != nil { 459 t.Fatalf("Error setting xattr: %v", err) 460 } 461 err = syscallx.Setxattr(name1, attr2, []byte("hello2"), 0) 462 if err != nil { 463 t.Fatalf("Error setting xattr: %v", err) 464 } 465 // Alternate value for first attribute 466 err = syscallx.Setxattr(name1, attr1, []byte("hello1a"), 0) 467 if err != nil { 468 t.Fatalf("Error setting xattr: %v", err) 469 } 470 471 // list attrs 472 n, err = syscallx.Listxattr(name1, buf) 473 if err != nil { 474 t.Errorf("Error in initial listxattr: %v", err) 475 } 476 m := parseXattrList(buf[:n]) 477 if !(len(m) == 2 && m[attr1] && m[attr2]) { 478 t.Errorf("Missing an attribute: %q", buf[:n]) 479 } 480 481 // Remove attr 482 err = syscallx.Removexattr(name1, attr2) 483 if err != nil { 484 t.Errorf("Failed to remove attr: %v", err) 485 } 486 487 // List attrs 488 n, err = syscallx.Listxattr(name1, buf) 489 if err != nil { 490 t.Errorf("Error in initial listxattr: %v", err) 491 } 492 m = parseXattrList(buf[:n]) 493 if !(len(m) == 1 && m[attr1]) { 494 t.Errorf("Missing an attribute: %q", buf[:n]) 495 } 496 497 // Get remaining attr 498 n, err = syscallx.Getxattr(name1, attr1, buf) 499 if err != nil { 500 t.Errorf("Error getting attr1: %v", err) 501 } 502 if string(buf[:n]) != "hello1a" { 503 t.Logf("Expected hello1a, got %q", buf[:n]) 504 } 505 }) 506 } 507 508 func TestSymlink(t *testing.T) { 509 condSkip(t) 510 // Do it all once, unmount, re-mount and then check again. 511 // TODO(bradfitz): do this same pattern (unmount and remount) in the other tests. 512 var suffix string 513 var link string 514 const target = "../../some-target" // arbitrary string. some-target is fake. 515 check := func() { 516 fi, err := os.Lstat(link) 517 if err != nil { 518 t.Fatalf("Stat: %v", err) 519 } 520 if fi.Mode()&os.ModeSymlink == 0 { 521 t.Errorf("Mode = %v; want Symlink bit set", fi.Mode()) 522 } 523 got, err := os.Readlink(link) 524 if err != nil { 525 t.Fatalf("Readlink: %v", err) 526 } 527 if got != target { 528 t.Errorf("ReadLink = %q; want %q", got, target) 529 } 530 } 531 inEmptyMutDir(t, func(env *mountEnv, rootDir string) { 532 // Save for second test: 533 link = filepath.Join(rootDir, "some-link") 534 suffix = strings.TrimPrefix(link, env.mountPoint) 535 536 if err := os.Symlink(target, link); err != nil { 537 t.Fatalf("Symlink: %v", err) 538 } 539 t.Logf("Checking in first process...") 540 check() 541 }) 542 cammountTest(t, func(env *mountEnv) { 543 t.Logf("Checking in second process...") 544 link = env.mountPoint + suffix 545 check() 546 }) 547 } 548 549 func TestFinderCopy(t *testing.T) { 550 if runtime.GOOS != "darwin" { 551 t.Skipf("Skipping Darwin-specific test.") 552 } 553 condSkip(t) 554 inEmptyMutDir(t, func(env *mountEnv, destDir string) { 555 f, err := ioutil.TempFile("", "finder-copy-file") 556 if err != nil { 557 t.Fatal(err) 558 } 559 defer os.Remove(f.Name()) 560 want := []byte("Some data for Finder to copy.") 561 if _, err := f.Write(want); err != nil { 562 t.Fatal(err) 563 } 564 if err := f.Close(); err != nil { 565 t.Fatal(err) 566 } 567 568 cmd := exec.Command("osascript") 569 script := fmt.Sprintf(` 570 tell application "Finder" 571 copy file POSIX file %q to folder POSIX file %q 572 end tell 573 `, f.Name(), destDir) 574 cmd.Stdin = strings.NewReader(script) 575 576 if out, err := cmd.CombinedOutput(); err != nil { 577 t.Fatalf("Error running AppleScript: %v, %s", err, out) 578 } else { 579 t.Logf("AppleScript said: %q", out) 580 } 581 582 destFile := filepath.Join(destDir, filepath.Base(f.Name())) 583 fi, err := os.Stat(destFile) 584 if err != nil { 585 t.Errorf("Stat = %v, %v", fi, err) 586 } 587 if fi.Size() != int64(len(want)) { 588 t.Errorf("Dest stat size = %d; want %d", fi.Size(), len(want)) 589 } 590 slurp, err := ioutil.ReadFile(destFile) 591 if err != nil { 592 t.Fatalf("ReadFile: %v", err) 593 } 594 if !bytes.Equal(slurp, want) { 595 t.Errorf("Dest file = %q; want %q", slurp, want) 596 } 597 }) 598 } 599 600 func TestTextEdit(t *testing.T) { 601 if testing.Short() { 602 t.Skipf("Skipping in short mode") 603 } 604 if runtime.GOOS != "darwin" { 605 t.Skipf("Skipping Darwin-specific test.") 606 } 607 condSkip(t) 608 inEmptyMutDir(t, func(env *mountEnv, testDir string) { 609 var ( 610 testFile = filepath.Join(testDir, "some-text-file.txt") 611 content1 = []byte("Some text content.") 612 content2 = []byte("Some replacement content.") 613 ) 614 if err := ioutil.WriteFile(testFile, content1, 0644); err != nil { 615 t.Fatal(err) 616 } 617 618 cmd := exec.Command("osascript") 619 script := fmt.Sprintf(` 620 tell application "TextEdit" 621 activate 622 open POSIX file %q 623 tell front document 624 set paragraph 1 to %q as text 625 save 626 close 627 end tell 628 end tell 629 `, testFile, content2) 630 cmd.Stdin = strings.NewReader(script) 631 632 if out, err := cmd.CombinedOutput(); err != nil { 633 t.Fatalf("Error running AppleScript: %v, %s", err, out) 634 } else { 635 t.Logf("AppleScript said: %q", out) 636 } 637 638 fi, err := os.Stat(testFile) 639 if err != nil { 640 t.Errorf("Stat = %v, %v", fi, err) 641 } else if fi.Size() != int64(len(content2)) { 642 t.Errorf("Stat size = %d; want %d", fi.Size(), len(content2)) 643 } 644 slurp, err := ioutil.ReadFile(testFile) 645 if err != nil { 646 t.Fatalf("ReadFile: %v", err) 647 } 648 if !bytes.Equal(slurp, content2) { 649 t.Errorf("File = %q; want %q", slurp, content2) 650 } 651 }) 652 } 653 654 func not(cond func() bool) func() bool { 655 return func() bool { 656 return !cond() 657 } 658 } 659 660 func dirToBeFUSE(dir string) func() bool { 661 return func() bool { 662 out, err := exec.Command("df", dir).CombinedOutput() 663 if err != nil { 664 return false 665 } 666 if runtime.GOOS == "darwin" { 667 if strings.Contains(string(out), "mount_osxfusefs@") { 668 return true 669 } 670 return false 671 } 672 if runtime.GOOS == "linux" { 673 return strings.Contains(string(out), "/dev/fuse") && 674 strings.Contains(string(out), dir) 675 } 676 return false 677 } 678 } 679 680 // shortenString reduces any run of 5 or more identical bytes to "x{17}". 681 // "hello" => "hello" 682 // "fooooooooooooooooo" => "fo{17}" 683 func shortenString(v string) string { 684 var buf bytes.Buffer 685 var last byte 686 var run int 687 flush := func() { 688 switch { 689 case run == 0: 690 case run < 5: 691 for i := 0; i < run; i++ { 692 buf.WriteByte(last) 693 } 694 default: 695 buf.WriteByte(last) 696 fmt.Fprintf(&buf, "{%d}", run) 697 } 698 run = 0 699 } 700 for i := 0; i < len(v); i++ { 701 b := v[i] 702 if b != last { 703 flush() 704 } 705 last = b 706 run++ 707 } 708 flush() 709 return buf.String() 710 }