github.com/VertebrateResequencing/muxfys@v3.0.5+incompatible/muxfys_test.go (about) 1 // Copyright © 2017, 2018 Genome Research Limited 2 // Author: Sendu Bala <sb10@sanger.ac.uk>. 3 // 4 // This file is part of muxfys. 5 // 6 // muxfys is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU Lesser General Public License as published by 8 // the Free Software Foundation, either version 3 of the License, or 9 // (at your option) any later version. 10 // 11 // muxfys is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU Lesser General Public License for more details. 15 // 16 // You should have received a copy of the GNU Lesser General Public License 17 // along with muxfys. If not, see <http://www.gnu.org/licenses/>. 18 19 package muxfys 20 21 import ( 22 "fmt" 23 "io" 24 "io/ioutil" 25 "log" 26 "os" 27 "os/user" 28 "path/filepath" 29 "strings" 30 "sync" 31 "syscall" 32 "testing" 33 "time" 34 35 "github.com/inconshreveable/log15" 36 . "github.com/smartystreets/goconvey/convey" 37 ) 38 39 var uploadFail bool 40 var resetMutex sync.Mutex 41 var resetFail bool 42 43 // openedObject is what a localAccessor returns from its OpenFile() method. It 44 // is just a wrapper around the return value from os.Open that allows us to 45 // make reads fails at will, for testing purposes. 46 type openedObject struct { 47 object *os.File 48 } 49 50 func (f *openedObject) Read(b []byte) (int, error) { 51 resetMutex.Lock() 52 defer resetMutex.Unlock() 53 if resetFail { 54 return 0, fmt.Errorf("connection reset by peer") 55 } 56 return f.object.Read(b) 57 } 58 59 func (f *openedObject) Seek(offset int64, whence int) (int64, error) { 60 return f.object.Seek(offset, whence) 61 } 62 63 func (f *openedObject) Close() error { 64 return f.object.Close() 65 } 66 67 // localAccessor implements RemoteAccessor: it just accesses the local POSIX 68 // file system for testing purposes. 69 type localAccessor struct { 70 target string 71 } 72 73 func (a *localAccessor) copyFile(source, dest string) error { 74 in, err := os.Open(source) 75 if err != nil { 76 return err 77 } 78 defer in.Close() 79 dir := filepath.Dir(dest) 80 err = os.MkdirAll(dir, 0700) 81 if err != nil { 82 return err 83 } 84 out, err := os.Create(dest) 85 if err != nil { 86 return err 87 } 88 defer func() { 89 cerr := out.Close() 90 if err == nil { 91 err = cerr 92 } 93 }() 94 if _, err = io.Copy(out, in); err != nil { 95 return err 96 } 97 return out.Sync() 98 } 99 100 // DownloadFile implements RemoteAccessor by deferring to local fs. 101 func (a *localAccessor) DownloadFile(source, dest string) (err error) { 102 return a.copyFile(source, dest) 103 } 104 105 // UploadFile implements RemoteAccessor by deferring to local fs. 106 func (a *localAccessor) UploadFile(source, dest, contentType string) error { 107 if uploadFail { 108 return fmt.Errorf("upload failed") 109 } 110 return a.copyFile(source, dest) 111 } 112 113 // UploadData implements RemoteAccessor by deferring to local fs. 114 func (a *localAccessor) UploadData(data io.Reader, dest string) error { 115 if uploadFail { 116 return fmt.Errorf("upload failed") 117 } 118 dir := filepath.Dir(dest) 119 err := os.MkdirAll(dir, 0700) 120 if err != nil { 121 return err 122 } 123 out, err := os.Create(dest) 124 if err != nil { 125 return err 126 } 127 defer func() { 128 cerr := out.Close() 129 if err == nil { 130 err = cerr 131 } 132 }() 133 if _, err = io.Copy(out, data); err != nil { 134 return err 135 } 136 return out.Sync() 137 } 138 139 // ListEntries implements RemoteAccessor by deferring to local fs. 140 func (a *localAccessor) ListEntries(dir string) ([]RemoteAttr, error) { 141 resetMutex.Lock() 142 defer resetMutex.Unlock() 143 if resetFail { 144 return nil, fmt.Errorf("connection reset by peer") 145 } 146 entries, err := ioutil.ReadDir(dir) 147 if err != nil { 148 return nil, err 149 } 150 var ras []RemoteAttr 151 for _, entry := range entries { 152 name := entry.Name() 153 if entry.IsDir() { 154 name += "/" 155 } 156 ras = append(ras, RemoteAttr{ 157 Name: dir + name, 158 Size: entry.Size(), 159 MTime: entry.ModTime(), 160 }) 161 } 162 return ras, err 163 } 164 165 // OpenFile implements RemoteAccessor by deferring to local fs. 166 func (a *localAccessor) OpenFile(path string, offset int64) (io.ReadCloser, error) { 167 resetMutex.Lock() 168 defer resetMutex.Unlock() 169 if resetFail { 170 return nil, fmt.Errorf("connection reset by peer") 171 } 172 f, err := os.Open(path) 173 if offset > 0 { 174 f.Seek(offset, io.SeekStart) 175 } 176 return &openedObject{object: f}, err 177 } 178 179 // Seek implements RemoteAccessor by deferring to local fs. 180 func (a *localAccessor) Seek(path string, rc io.ReadCloser, offset int64) (io.ReadCloser, error) { 181 object := rc.(*openedObject) 182 _, err := object.Seek(offset, io.SeekStart) 183 return object, err 184 } 185 186 // CopyFile implements RemoteAccessor by deferring to local fs. 187 func (a *localAccessor) CopyFile(source, dest string) error { 188 return a.copyFile(source, dest) 189 } 190 191 // DeleteFile implements RemoteAccessor by deferring to local fs. 192 func (a *localAccessor) DeleteFile(path string) error { 193 return os.Remove(path) 194 } 195 196 // DeleteIncompleteUpload implements RemoteAccessor by deferring to local fs. 197 func (a *localAccessor) DeleteIncompleteUpload(path string) error { 198 return os.Remove(path) 199 } 200 201 // ErrorIsNotExists implements RemoteAccessor by deferring to os. 202 func (a *localAccessor) ErrorIsNotExists(err error) bool { 203 return os.IsNotExist(err) 204 } 205 206 // ErrorIsNoQuota implements RemoteAccessor by deferring to os. 207 func (a *localAccessor) ErrorIsNoQuota(err error) bool { 208 return false // *** is there a standard error for running out of disk space? 209 } 210 211 // Target implements RemoteAccessor by returning the initial target we were 212 // configured with. 213 func (a *localAccessor) Target() string { 214 return a.target 215 } 216 217 // RemotePath implements RemoteAccessor by using the initially configured target. 218 func (a *localAccessor) RemotePath(relPath string) string { 219 return filepath.Join(a.target, relPath) 220 } 221 222 // LocalPath implements RemoteAccessor by adding nothing extra. 223 func (a *localAccessor) LocalPath(baseDir, remotePath string) string { 224 return filepath.Join(baseDir, remotePath) 225 } 226 227 func TestMuxFys(t *testing.T) { 228 user, errt := user.Current() 229 if errt != nil { 230 log.Fatal(errt) 231 } 232 233 // *** the cache deletion tests no longer work on nfs, don't know why! 234 // pwd, err := os.Getwd() // doing these tests from an nfs mounted home dir reveals some bugs that were fixed 235 // if err != nil { 236 // log.Fatal(err) 237 // } 238 239 tmpdir, errt := ioutil.TempDir("", "muxfys_testing") 240 if errt != nil { 241 log.Fatal(errt) 242 } 243 defer os.RemoveAll(tmpdir) 244 245 errt = os.Chdir(tmpdir) 246 if errt != nil { 247 log.Fatal(errt) 248 } 249 cacheBase := filepath.Join(tmpdir, "cacheBase") 250 os.MkdirAll(cacheBase, os.FileMode(0777)) 251 252 cachePermanent := filepath.Join(tmpdir, "cachePermanent") 253 os.MkdirAll(cachePermanent, os.FileMode(0777)) 254 255 sourcePoint := filepath.Join(tmpdir, "source") 256 os.MkdirAll(sourcePoint, os.FileMode(0777)) 257 errt = ioutil.WriteFile(filepath.Join(sourcePoint, "read.file"), []byte("test1\ntest2\n"), 0644) 258 if errt != nil { 259 log.Fatal(errt) 260 } 261 262 sourceOtherDir := filepath.Join(sourcePoint, "other") 263 os.MkdirAll(sourceOtherDir, os.FileMode(0777)) 264 errt = ioutil.WriteFile(filepath.Join(sourceOtherDir, "read2.file"), []byte("test\n"), 0644) 265 if errt != nil { 266 log.Fatal(errt) 267 } 268 269 f, errt := os.Create(filepath.Join(sourcePoint, "large.file")) 270 if errt != nil { 271 log.Fatal(errt) 272 } 273 for i := 1; i <= 10000; i++ { 274 _, errt = f.WriteString(fmt.Sprintf("test%d\n", i)) 275 if errt != nil { 276 log.Fatal(errt) 277 } 278 } 279 f.Sync() 280 f.Close() 281 282 accessor := &localAccessor{ 283 target: sourcePoint, 284 } 285 286 sourceSubDir := filepath.Join(sourcePoint, "subdir") 287 accessorNonExistent := &localAccessor{ 288 target: sourceSubDir, 289 } 290 291 // for testing purposes we override exitFunc and deathSignals 292 var i int 293 var efm sync.Mutex 294 exitFunc = func(code int) { 295 efm.Lock() 296 defer efm.Unlock() 297 i = code 298 } 299 deathSignals = []os.Signal{syscall.SIGUSR1} 300 301 Convey("You can make a New MuxFys with an explicit Mount", t, func() { 302 explicitMount := filepath.Join(tmpdir, "explicitMount") 303 cfg := &Config{ 304 Mount: explicitMount, 305 CacheBase: cacheBase, 306 Verbose: true, 307 Retries: 2, 308 } 309 fs, errn := New(cfg) 310 So(errn, ShouldBeNil) 311 312 Convey("You can Mount() read-only uncached", func() { 313 remoteConfig := &RemoteConfig{ 314 Accessor: accessor, 315 CacheData: false, 316 Write: false, 317 } 318 errm := fs.Mount(remoteConfig) 319 So(errm, ShouldBeNil) 320 defer fs.Unmount() 321 322 Convey("Once mounted you can't mount again", func() { 323 err := fs.Mount(remoteConfig) 324 So(err, ShouldNotBeNil) 325 So(err.Error(), ShouldEqual, "Can't mount more that once at a time") 326 }) 327 328 Convey("You can Unmount()", func() { 329 err := fs.Unmount() 330 So(err, ShouldBeNil) 331 }) 332 333 Convey("You can UnmountOnDeath()", func() { 334 So(fs.handlingSignals, ShouldBeFalse) 335 fs.UnmountOnDeath() 336 So(fs.handlingSignals, ShouldBeTrue) 337 So(fs.mounted, ShouldBeTrue) 338 So(i, ShouldEqual, 0) 339 340 // doing it again is harmless 341 fs.UnmountOnDeath() 342 343 syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) 344 <-time.After(500 * time.Millisecond) 345 346 fs.mutex.Lock() 347 defer fs.mutex.Unlock() 348 So(fs.mounted, ShouldBeFalse) 349 efm.Lock() 350 defer efm.Unlock() 351 So(i, ShouldEqual, 1) 352 i = 0 353 }) 354 355 Convey("You can Unmount() while UnmountOnDeath() is active", func() { 356 fs.UnmountOnDeath() 357 So(fs.mounted, ShouldBeTrue) 358 So(i, ShouldEqual, 0) 359 360 err := fs.Unmount() 361 So(err, ShouldBeNil) 362 So(fs.mounted, ShouldBeFalse) 363 364 syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) 365 <-time.After(500 * time.Millisecond) 366 367 So(i, ShouldEqual, 0) 368 }) 369 370 Convey("Reads are retried on connection reset by peer", func() { 371 f, err := os.Open(filepath.Join(explicitMount, "large.file")) 372 So(err, ShouldBeNil) 373 b := make([]byte, 6) 374 _, err = f.Read(b) 375 So(err, ShouldBeNil) 376 So(string(b), ShouldEqual, "test1\n") 377 378 defer func() { 379 f.Close() 380 fs.Unmount() 381 }() 382 383 f.Seek(60003, 0) 384 385 resetMutex.Lock() 386 resetFail = true 387 resetMutex.Unlock() 388 go func() { 389 <-time.After(3 * time.Second) 390 resetMutex.Lock() 391 resetFail = false 392 resetMutex.Unlock() 393 }() 394 395 before := time.Now() 396 b = make([]byte, 9) 397 _, err = f.Read(b) 398 after := time.Since(before) 399 So(err, ShouldBeNil) 400 So(string(b), ShouldEqual, "test6791\n") 401 So(after.Seconds(), ShouldBeGreaterThanOrEqualTo, 3) 402 }) 403 }) 404 405 Convey("You can Mount() writable cached", func() { 406 remoteConfig := &RemoteConfig{ 407 Accessor: accessor, 408 CacheData: true, 409 Write: true, 410 } 411 errm := fs.Mount(remoteConfig) 412 So(errm, ShouldBeNil) 413 defer fs.Unmount() 414 415 Convey("You can Unmount()", func() { 416 err := fs.Unmount() 417 So(err, ShouldBeNil) 418 So(checkEmpty(cacheBase), ShouldBeTrue) 419 }) 420 421 Convey("Unmount() after reading files fully deletes the cache dir", func() { 422 data, err := ioutil.ReadFile(filepath.Join(explicitMount, "read.file")) 423 So(err, ShouldBeNil) 424 So(string(data), ShouldEqual, "test1\ntest2\n") 425 err = fs.Unmount() 426 So(err, ShouldBeNil) 427 So(checkEmpty(cacheBase), ShouldBeTrue) 428 }) 429 430 Convey("Unmounting after creating files uploads them", func() { 431 sourceFile1 := filepath.Join(sourcePoint, "created1.file") 432 _, err := os.Stat(sourceFile1) 433 So(err, ShouldNotBeNil) 434 sourceFile2 := filepath.Join(sourcePoint, "created2.file") 435 _, err = os.Stat(sourceFile2) 436 So(err, ShouldNotBeNil) 437 438 f, err := os.OpenFile(filepath.Join(explicitMount, "created1.file"), os.O_RDWR|os.O_CREATE, 0666) 439 So(err, ShouldBeNil) 440 f.Close() 441 defer os.Remove(sourceFile1) 442 f, err = os.OpenFile(filepath.Join(explicitMount, "created2.file"), os.O_RDWR|os.O_CREATE, 0666) 443 So(err, ShouldBeNil) 444 f.Close() 445 defer os.Remove(sourceFile2) 446 447 // they don't exist prior to unmount 448 _, err = os.Stat(sourceFile1) 449 So(err, ShouldNotBeNil) 450 _, err = os.Stat(sourceFile2) 451 So(err, ShouldNotBeNil) 452 453 err = fs.Unmount() 454 So(err, ShouldBeNil) 455 456 _, err = os.Stat(sourceFile1) 457 So(err, ShouldBeNil) 458 _, err = os.Stat(sourceFile2) 459 So(err, ShouldBeNil) 460 461 Convey("SetLogHandler() lets you log events", func() { 462 recs := make(chan *log15.Record, 10) 463 SetLogHandler(log15.ChannelHandler(recs)) 464 465 err := fs.Mount(remoteConfig) 466 So(err, ShouldBeNil) 467 468 _, err = os.Stat(filepath.Join(explicitMount, "created1.file")) 469 So(err, ShouldBeNil) 470 471 rec := <-recs 472 So(rec.Ctx[7], ShouldEqual, "ListEntries") 473 SetLogHandler(log15.DiscardHandler()) 474 close(recs) 475 }) 476 }) 477 478 Convey("Unmounting reports failure to upload", func() { 479 sourceFile := filepath.Join(sourcePoint, "created.file") 480 _, err := os.Stat(sourceFile) 481 So(err, ShouldNotBeNil) 482 483 f, err := os.OpenFile(filepath.Join(explicitMount, "created.file"), os.O_RDWR|os.O_CREATE, 0666) 484 So(err, ShouldBeNil) 485 f.Close() 486 487 uploadFail = true 488 defer func() { 489 uploadFail = false 490 }() 491 defer os.Remove(sourceFile) 492 493 err = fs.Unmount() 494 So(err, ShouldNotBeNil) 495 So(err.Error(), ShouldEqual, "failed to upload 1 files") 496 497 Convey("Logs() tells you what happened", func() { 498 logs := fs.Logs() 499 So(len(logs), ShouldEqual, 2) 500 So(logs[1], ShouldContainSubstring, "lvl=eror") 501 So(logs[1], ShouldContainSubstring, `msg="Remote call failed"`) 502 So(logs[1], ShouldContainSubstring, "pkg=muxfys") 503 So(logs[1], ShouldContainSubstring, "mount="+explicitMount) 504 So(logs[1], ShouldContainSubstring, "target="+sourcePoint) 505 So(logs[1], ShouldContainSubstring, "call=UploadFile") 506 So(logs[1], ShouldContainSubstring, "path="+sourceFile) 507 So(logs[1], ShouldContainSubstring, "retries=2") 508 So(logs[1], ShouldContainSubstring, "walltime=") 509 So(logs[1], ShouldContainSubstring, `err="upload failed"`) 510 So(logs[1], ShouldContainSubstring, "caller=remote.go") 511 }) 512 }) 513 514 Convey("We try the desired number of times to access bad remotes", func() { 515 resetMutex.Lock() 516 resetFail = true 517 resetMutex.Unlock() 518 defer func() { 519 resetMutex.Lock() 520 resetFail = false 521 resetMutex.Unlock() 522 }() 523 524 entries, err := ioutil.ReadDir(explicitMount) 525 So(err, ShouldBeNil) // *** not sure why this doesn't give an err 526 So(len(entries), ShouldEqual, 0) 527 528 Convey("Logs() tells you what happened", func() { 529 logs := fs.Logs() 530 So(len(logs), ShouldEqual, 1) 531 So(logs[0], ShouldContainSubstring, "lvl=eror") 532 So(logs[0], ShouldContainSubstring, `msg="Remote call failed"`) 533 So(logs[0], ShouldContainSubstring, "pkg=muxfys") 534 So(logs[0], ShouldContainSubstring, "mount="+explicitMount) 535 So(logs[0], ShouldContainSubstring, "target="+sourcePoint) 536 So(logs[0], ShouldContainSubstring, "call=ListEntries") 537 So(logs[0], ShouldContainSubstring, "path=/") 538 So(logs[0], ShouldContainSubstring, "retries=2") 539 So(logs[0], ShouldContainSubstring, `err="connection reset by peer"`) 540 }) 541 }) 542 543 Convey("We try greater than the desired number of times to access a good remote that turns bad", func() { 544 entries, err := ioutil.ReadDir(explicitMount) 545 So(err, ShouldBeNil) 546 So(len(entries), ShouldEqual, 3) 547 548 resetMutex.Lock() 549 resetFail = true 550 resetMutex.Unlock() 551 go func() { 552 <-time.After(1 * time.Second) 553 resetMutex.Lock() 554 resetFail = false 555 resetMutex.Unlock() 556 }() 557 558 entries, err = ioutil.ReadDir(explicitMount + "/other") 559 So(err, ShouldBeNil) 560 So(len(entries), ShouldEqual, 1) 561 562 Convey("Logs() tells you what happened", func() { 563 logs := fs.Logs() 564 So(len(logs), ShouldBeGreaterThanOrEqualTo, 5) 565 So(logs[1], ShouldContainSubstring, "lvl=warn") 566 So(logs[1], ShouldContainSubstring, "call=ListEntries") 567 So(logs[1], ShouldContainSubstring, `err="connection reset by peer"`) 568 So(logs[1], ShouldContainSubstring, `retries=0`) 569 lastLog := logs[len(logs)-1] 570 So(lastLog, ShouldContainSubstring, "lvl=info") 571 So(lastLog, ShouldContainSubstring, "call=ListEntries") 572 So(lastLog, ShouldContainSubstring, `previous_err="connection reset by peer"`) 573 moreRetries := false 574 if strings.Contains(lastLog, "retries=3") || strings.Contains(lastLog, "retries=4") || strings.Contains(lastLog, "retries=5") { 575 moreRetries = true 576 } 577 So(moreRetries, ShouldBeTrue) 578 }) 579 }) 580 581 Convey("You can't have 2 writeable remotes", func() { 582 err := fs.Unmount() 583 So(err, ShouldBeNil) 584 err = fs.Mount(remoteConfig, remoteConfig) 585 So(err, ShouldNotBeNil) 586 So(err.Error(), ShouldEqual, "You can't have more than one writeable remote") 587 }) 588 589 Convey("UnmountOnDeath() will exit(2) on failure to unmount", func() { 590 fs.UnmountOnDeath() 591 So(fs.mounted, ShouldBeTrue) 592 So(i, ShouldEqual, 0) 593 594 f, err := os.OpenFile(filepath.Join(explicitMount, "opened.file"), os.O_RDWR|os.O_CREATE, 0666) 595 So(err, ShouldBeNil) 596 597 syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) 598 <-time.After(500 * time.Millisecond) 599 600 So(fs.mounted, ShouldBeTrue) 601 efm.Lock() 602 defer efm.Unlock() 603 So(i, ShouldEqual, 2) 604 i = 0 605 606 f.Close() 607 err = fs.Unmount() 608 So(err, ShouldBeNil) 609 So(fs.mounted, ShouldBeFalse) 610 }) 611 }) 612 613 Convey("You can Mount() writable uncached", func() { 614 remoteConfig := &RemoteConfig{ 615 Accessor: accessor, 616 CacheData: false, 617 Write: true, 618 } 619 errm := fs.Mount(remoteConfig) 620 So(errm, ShouldBeNil) 621 defer fs.Unmount() 622 623 Convey("You can Unmount()", func() { 624 err := fs.Unmount() 625 So(err, ShouldBeNil) 626 }) 627 628 Convey("Creating files immediately uploads them", func() { 629 sourceFile1 := filepath.Join(sourcePoint, "created1.file") 630 _, err := os.Stat(sourceFile1) 631 So(err, ShouldNotBeNil) 632 sourceFile2 := filepath.Join(sourcePoint, "created2.file") 633 _, err = os.Stat(sourceFile2) 634 So(err, ShouldNotBeNil) 635 636 f, err := os.OpenFile(filepath.Join(explicitMount, "created1.file"), os.O_RDWR|os.O_CREATE, 0666) 637 So(err, ShouldBeNil) 638 f.Close() 639 defer os.Remove(sourceFile1) 640 f, err = os.OpenFile(filepath.Join(explicitMount, "created2.file"), os.O_RDWR|os.O_CREATE, 0666) 641 So(err, ShouldBeNil) 642 f.Close() 643 defer os.Remove(sourceFile2) 644 645 // they exist prior to unmount 646 <-time.After(50 * time.Millisecond) 647 _, err = os.Stat(sourceFile1) 648 So(err, ShouldBeNil) 649 _, err = os.Stat(sourceFile2) 650 So(err, ShouldBeNil) 651 }) 652 653 Convey("You can write data directly to the remote", func() { 654 sourceFile := filepath.Join(sourcePoint, "stream.file") 655 _, err := os.Stat(sourceFile) 656 So(err, ShouldNotBeNil) 657 658 mountFile := filepath.Join(explicitMount, "stream.file") 659 f, err := os.OpenFile(mountFile, os.O_RDWR|os.O_CREATE, 0666) 660 So(err, ShouldBeNil) 661 defer os.Remove(sourceFile) 662 663 info, err := os.Stat(sourceFile) 664 So(err, ShouldBeNil) 665 So(info.Size(), ShouldEqual, 0) 666 667 f.WriteString("test\n") 668 669 info, err = os.Stat(sourceFile) 670 So(err, ShouldBeNil) 671 So(info.Size(), ShouldEqual, 5) 672 673 info, err = os.Stat(mountFile) 674 So(err, ShouldBeNil) 675 So(info.Size(), ShouldEqual, 5) 676 677 f.WriteString("test2\n") 678 679 info, err = os.Stat(sourceFile) 680 So(err, ShouldBeNil) 681 So(info.Size(), ShouldEqual, 11) 682 683 info, err = os.Stat(mountFile) 684 So(err, ShouldBeNil) 685 So(info.Size(), ShouldEqual, 11) 686 687 f.Close() 688 err = fs.Unmount() 689 So(err, ShouldBeNil) 690 }) 691 692 Convey("You can't have 2 writeable remotes", func() { 693 err := fs.Unmount() 694 So(err, ShouldBeNil) 695 err = fs.Mount(remoteConfig, remoteConfig) 696 So(err, ShouldNotBeNil) 697 So(err.Error(), ShouldEqual, "You can't have more than one writeable remote") 698 }) 699 }) 700 701 Convey("You can Mount() read-only to a non-existent sub-dir", func() { 702 remoteConfig := &RemoteConfig{ 703 Accessor: accessorNonExistent, 704 CacheData: false, 705 Write: false, 706 } 707 err := fs.Mount(remoteConfig) 708 So(err, ShouldBeNil) 709 defer fs.Unmount() 710 711 Convey("Getting the contents of the dir works", func() { 712 entries, err := ioutil.ReadDir(explicitMount) 713 So(err, ShouldBeNil) 714 So(len(entries), ShouldEqual, 0) 715 }) 716 }) 717 718 Convey("You can Mount() writable cached to a non-existent sub-dir", func() { 719 remoteConfig := &RemoteConfig{ 720 Accessor: accessorNonExistent, 721 CacheData: true, 722 Write: true, 723 } 724 errm := fs.Mount(remoteConfig) 725 So(errm, ShouldBeNil) 726 defer fs.Unmount() 727 defer os.RemoveAll(sourceSubDir) 728 729 Convey("You can Unmount()", func() { 730 err := fs.Unmount() 731 So(err, ShouldBeNil) 732 So(checkEmpty(cacheBase), ShouldBeTrue) 733 }) 734 735 Convey("Getting the contents of the dir works", func() { 736 entries, err := ioutil.ReadDir(explicitMount) 737 So(err, ShouldBeNil) 738 So(len(entries), ShouldEqual, 0) 739 }) 740 741 Convey("Unmounting after creating a file uploads it", func() { 742 sourceFile1 := filepath.Join(sourceSubDir, "created1.file") 743 _, err := os.Stat(sourceFile1) 744 So(err, ShouldNotBeNil) 745 746 f, err := os.OpenFile(filepath.Join(explicitMount, "created1.file"), os.O_RDWR|os.O_CREATE, 0666) 747 So(err, ShouldBeNil) 748 f.Close() 749 defer os.Remove(sourceFile1) 750 751 // doesn't exist prior to unmount 752 _, err = os.Stat(sourceFile1) 753 So(err, ShouldNotBeNil) 754 755 err = fs.Unmount() 756 So(err, ShouldBeNil) 757 758 // does exist afterwards 759 _, err = os.Stat(sourceFile1) 760 So(err, ShouldBeNil) 761 }) 762 }) 763 764 Convey("You can Mount() writable uncached to a non-existent sub-dir", func() { 765 remoteConfig := &RemoteConfig{ 766 Accessor: accessorNonExistent, 767 CacheData: false, 768 Write: true, 769 } 770 errm := fs.Mount(remoteConfig) 771 So(errm, ShouldBeNil) 772 defer fs.Unmount() 773 defer os.RemoveAll(sourceSubDir) 774 775 Convey("You can Unmount()", func() { 776 err := fs.Unmount() 777 So(err, ShouldBeNil) 778 }) 779 780 Convey("Getting the contents of the dir works", func() { 781 entries, err := ioutil.ReadDir(explicitMount) 782 So(err, ShouldBeNil) 783 So(len(entries), ShouldEqual, 0) 784 }) 785 786 Convey("Creating a file immediately uploads it", func() { 787 sourceFile1 := filepath.Join(sourceSubDir, "created1.file") 788 _, err := os.Stat(sourceFile1) 789 So(err, ShouldNotBeNil) 790 791 f, err := os.OpenFile(filepath.Join(explicitMount, "created1.file"), os.O_RDWR|os.O_CREATE, 0666) 792 So(err, ShouldBeNil) 793 f.Close() 794 defer os.Remove(sourceFile1) 795 796 // exists prior to unmount 797 <-time.After(50 * time.Millisecond) 798 _, err = os.Stat(sourceFile1) 799 So(err, ShouldBeNil) 800 }) 801 }) 802 803 Convey("You can Mount() read-only with a permanent cache", func() { 804 remoteConfig := &RemoteConfig{ 805 Accessor: accessor, 806 CacheDir: cachePermanent, 807 Write: false, 808 } 809 err := fs.Mount(remoteConfig) 810 So(err, ShouldBeNil) 811 defer fs.Unmount() 812 813 Convey("You can Unmount()", func() { 814 err := fs.Unmount() 815 So(err, ShouldBeNil) 816 So(checkEmpty(cachePermanent), ShouldBeTrue) 817 }) 818 819 Convey("Unmount() after reading files does not delete the cached files", func() { 820 data, err := ioutil.ReadFile(filepath.Join(explicitMount, "read.file")) 821 So(err, ShouldBeNil) 822 So(string(data), ShouldEqual, "test1\ntest2\n") 823 err = fs.Unmount() 824 So(err, ShouldBeNil) 825 So(checkEmpty(cacheBase), ShouldBeTrue) 826 So(checkEmpty(cachePermanent), ShouldBeFalse) 827 828 Convey("Remounting and re-reading reads from the cached file", func() { 829 // hack the cached file so we know we read from it and not 830 // source; currently cache files are only validated based on 831 // size 832 cf := filepath.Join(cachePermanent, sourcePoint, "read.file") 833 err = ioutil.WriteFile(cf, []byte("test1\ntestX\n"), 0644) 834 So(err, ShouldBeNil) 835 836 err = fs.Mount(remoteConfig) 837 So(err, ShouldBeNil) 838 data, err := ioutil.ReadFile(filepath.Join(explicitMount, "read.file")) 839 So(err, ShouldBeNil) 840 So(string(data), ShouldEqual, "test1\ntestX\n") 841 }) 842 }) 843 }) 844 845 Convey("You must supply at least one RemoteConfig to Mount()", func() { 846 err := fs.Mount() 847 So(err, ShouldNotBeNil) 848 So(err.Error(), ShouldEqual, "At least one RemoteConfig must be supplied") 849 }) 850 851 Convey("You can't Mount() with a bad CacheDir", func() { 852 remoteConfig := &RemoteConfig{ 853 Accessor: accessor, 854 CacheDir: "/!", 855 } 856 err := fs.Mount(remoteConfig) 857 So(err, ShouldNotBeNil) 858 }) 859 860 Convey("UnmountOnDeath does nothing prior to mounting", func() { 861 So(fs.handlingSignals, ShouldBeFalse) 862 fs.UnmountOnDeath() 863 So(fs.handlingSignals, ShouldBeFalse) 864 }) 865 }) 866 867 Convey("You can make a New MuxFys with a default Mount", t, func() { 868 defaultMnt := filepath.Join(tmpdir, "mnt") 869 fs, err := New(&Config{}) 870 So(err, ShouldBeNil) 871 So(fs.mountPoint, ShouldEqual, defaultMnt) 872 _, err = os.Stat(defaultMnt) 873 So(err, ShouldBeNil) 874 }) 875 876 Convey("You can make a New MuxFys with an explicit ~ Mount", t, func() { 877 expectedMount := filepath.Join(user.HomeDir, ".muxfys_test_mount_dir") 878 explicitMount := "~/.muxfys_test_mount_dir" 879 cfg := &Config{ 880 Mount: explicitMount, 881 } 882 fs, err := New(cfg) 883 defer os.RemoveAll(expectedMount) 884 So(err, ShouldBeNil) 885 So(fs.mountPoint, ShouldEqual, expectedMount) 886 _, err = os.Stat(expectedMount) 887 So(err, ShouldBeNil) 888 889 Convey("This fails for invalid home dir specs", func() { 890 explicitMount := "~.muxfys_test_mount_dir" 891 cfg := &Config{ 892 Mount: explicitMount, 893 } 894 _, err := New(cfg) 895 So(err, ShouldNotBeNil) 896 }) 897 }) 898 899 if user.Name != "root" { 900 Convey("You can't make a New MuxFys with Mount point in /", t, func() { 901 explicitMount := "/.muxfys_test_mount_dir" 902 cfg := &Config{ 903 Mount: explicitMount, 904 } 905 _, err := New(cfg) 906 defer os.RemoveAll(explicitMount) 907 So(err, ShouldNotBeNil) 908 }) 909 } 910 911 Convey("You can't make a New MuxFys using a file as a Mount", t, func() { 912 explicitMount := filepath.Join(tmpdir, "mntfile") 913 os.OpenFile(explicitMount, os.O_RDONLY|os.O_CREATE, 0666) 914 cfg := &Config{ 915 Mount: explicitMount, 916 } 917 _, err := New(cfg) 918 defer os.RemoveAll(explicitMount) 919 So(err, ShouldNotBeNil) 920 }) 921 922 Convey("You can't make a New MuxFys using a Mount that already contains files", t, func() { 923 explicitMount := filepath.Join(tmpdir, "mntfull") 924 err := os.MkdirAll(explicitMount, os.FileMode(0777)) 925 So(err, ShouldBeNil) 926 os.OpenFile(filepath.Join(explicitMount, "mntfile"), os.O_RDONLY|os.O_CREATE, 0666) 927 cfg := &Config{ 928 Mount: explicitMount, 929 } 930 _, err = New(cfg) 931 defer os.RemoveAll(explicitMount) 932 So(err, ShouldNotBeNil) 933 So(err.Error(), ShouldContainSubstring, "was not empty") 934 }) 935 } 936 937 // checkEmpty checks if the given directory is empty. 938 func checkEmpty(dir string) bool { 939 f, err := os.Open(dir) 940 if err != nil { 941 return false 942 } 943 defer f.Close() 944 945 _, err = f.Readdirnames(1) 946 return err == io.EOF 947 }