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  }