github.com/VertebrateResequencing/muxfys/v4@v4.0.3/s3_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  	"bufio"
    23  	"bytes"
    24  	"fmt"
    25  	"io"
    26  	"io/ioutil"
    27  	"log"
    28  	"math"
    29  	"net/url"
    30  	"os"
    31  	"os/exec"
    32  	"path"
    33  	"path/filepath"
    34  	"runtime"
    35  	"sort"
    36  	"strconv"
    37  	"strings"
    38  	"sync"
    39  	"testing"
    40  	"time"
    41  
    42  	"github.com/hanwen/go-fuse/v2/fuse"
    43  	. "github.com/smartystreets/goconvey/convey"
    44  )
    45  
    46  func TestS3Localntegration(t *testing.T) {
    47  	// We will create test files on local disk and then start up minio server
    48  	// to give us an S3 system to test against.
    49  	//
    50  	// minio server can be installed by:
    51  	// go get -u github.com/minio/minio
    52  	//
    53  	// These tests will only run if minio has already been installed and this
    54  	// env var has been set: MUXFYS_S3_PORT (eg. set it to 9000)
    55  	//
    56  	// We must be able to start minio server on that port (ie. it can't already
    57  	// be running for some other purpose).
    58  
    59  	port := os.Getenv("MUXFYS_S3_PORT")
    60  	_, lperr := exec.LookPath("minio")
    61  	if port == "" || lperr != nil {
    62  		SkipConvey("Without MUXFYS_S3_PORT environment variable and minio being installed, we'll skip local S3 tests", t, func() {})
    63  		return
    64  	}
    65  
    66  	target := fmt.Sprintf("http://localhost:%s/user/wr_tests", port)
    67  	accessKey := "AKIAIOSFODNN7EXAMPLE"
    68  	secretKey := "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
    69  	origAKey := os.Getenv("AWS_ACCESS_KEY_ID")
    70  	origSKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
    71  	os.Setenv("AWS_ACCESS_KEY_ID", accessKey)
    72  	os.Setenv("AWS_SECRET_ACCESS_KEY", secretKey)
    73  	defer func() {
    74  		os.Setenv("AWS_ACCESS_KEY_ID", origAKey)
    75  		os.Setenv("AWS_SECRET_ACCESS_KEY", origSKey)
    76  	}()
    77  
    78  	// create our test files
    79  	tmpdir, err := ioutil.TempDir("", "muxfys_testing")
    80  	if err != nil {
    81  		log.Fatal(err)
    82  	}
    83  	defer os.RemoveAll(tmpdir)
    84  
    85  	err = os.Chdir(tmpdir) // because muxfys will create dirs relative to cwd
    86  	if err != nil {
    87  		log.Panic(err) // Panic instead of fatal so our deferred removal of tmpdir works
    88  	}
    89  
    90  	minioDir := filepath.Join(tmpdir, "minio")
    91  	wrTestsDir := filepath.Join(minioDir, "user", "wr_tests")
    92  	wrTestsSubDir := filepath.Join(wrTestsDir, "sub")
    93  	wrTestsDeepDir := filepath.Join(wrTestsSubDir, "deep")
    94  	err = os.MkdirAll(wrTestsDeepDir, os.FileMode(0700))
    95  	if err != nil {
    96  		log.Panic(err)
    97  	}
    98  	err = os.MkdirAll(filepath.Join(wrTestsDir, "emptyDir"), os.FileMode(0700))
    99  	if err != nil {
   100  		log.Panic(err)
   101  	}
   102  
   103  	bigFileSize := 10000000 // 10MB, also tested with 1GB and it's fine
   104  	err = exec.Command("dd", "if=/dev/zero", "of="+filepath.Join(wrTestsDir, "big.file"), fmt.Sprintf("bs=%d", bigFileSize), "count=1").Run()
   105  	if err != nil {
   106  		log.Panic(err)
   107  	}
   108  
   109  	f, err := os.Create(filepath.Join(wrTestsDir, "numalphanum.txt"))
   110  	if err != nil {
   111  		log.Panic(err)
   112  	}
   113  	_, err = f.WriteString("1234567890abcdefghijklmnopqrstuvwxyz1234567890\n")
   114  	if err != nil {
   115  		log.Panic(err)
   116  	}
   117  	f.Close()
   118  
   119  	f, err = os.Create(filepath.Join(wrTestsDir, "100k.lines"))
   120  	if err != nil {
   121  		log.Panic(err)
   122  	}
   123  	for i := 1; i <= 100000; i++ {
   124  		_, err = f.WriteString(fmt.Sprintf("%06d\n", i))
   125  		if err != nil {
   126  			log.Panic(err)
   127  		}
   128  	}
   129  	f.Close()
   130  
   131  	f, err = os.Create(filepath.Join(wrTestsSubDir, "empty.file"))
   132  	if err != nil {
   133  		log.Panic(err)
   134  	}
   135  	f.Close()
   136  
   137  	f, err = os.Create(filepath.Join(wrTestsDeepDir, "bar"))
   138  	if err != nil {
   139  		log.Panic(err)
   140  	}
   141  	_, err = f.WriteString("foo\n")
   142  	if err != nil {
   143  		log.Panic(err)
   144  	}
   145  	f.Close()
   146  
   147  	// start minio
   148  	os.Setenv("MINIO_ACCESS_KEY", accessKey)
   149  	os.Setenv("MINIO_SECRET_KEY", secretKey)
   150  	os.Setenv("MINIO_BROWSER", "off")
   151  	minioCmd := exec.Command("minio", "server", "--address", fmt.Sprintf("localhost:%s", port), minioDir)
   152  
   153  	// if all tests accessing what minio server is supposed to serve fail, debug
   154  	// minio's startup:
   155  	// go func() {
   156  	// 	<-time.After(30 * time.Second)
   157  	// 	minioCmd.Process.Kill()
   158  	// 	minioCmd.Wait()
   159  	// }()
   160  	// out, err := minioCmd.CombinedOutput()
   161  	// fmt.Println(string(out))
   162  	// fmt.Println(err.Error())
   163  	// fmt.Println(target)
   164  	// return
   165  
   166  	err = minioCmd.Start()
   167  	if err != nil {
   168  		log.Panic(err)
   169  	}
   170  	defer func() {
   171  		minioCmd.Process.Kill()
   172  		minioCmd.Wait()
   173  	}()
   174  
   175  	// give it time to become ready to respond to accesses
   176  	if os.Getenv("CI") == "true" {
   177  		<-time.After(30 * time.Second)
   178  	} else {
   179  		<-time.After(5 * time.Second)
   180  	}
   181  
   182  	s3IntegrationTests(t, tmpdir, target, accessKey, secretKey, bigFileSize, false)
   183  }
   184  
   185  func TestS3RemoteIntegration(t *testing.T) {
   186  	// For these tests to work, MUXFYS_REMOTES3_TARGET must be the full URL to
   187  	// an immediate child directory of a bucket that you have read and write
   188  	// permissions for, eg: https://cog.domain.com/bucket/wr_tests You must also
   189  	// have a ~/.s3cfg file with a [default] section specifying the same domain
   190  	// and scheme via host_base and use_https.
   191  	//
   192  	// The child directory must contain the following:
   193  	// perl -e 'for (1..100000) { printf "%06d\n", $_; }' > 100k.lines
   194  	// echo 1234567890abcdefghijklmnopqrstuvwxyz1234567890 > numalphanum.txt
   195  	// dd if=/dev/zero of=big.file bs=1073741824 count=1
   196  	// mkdir -p sub/deep
   197  	// touch sub/empty.file
   198  	// echo foo > sub/deep/bar
   199  	// export WR_BUCKET_SUB=s3://bucket/wr_tests
   200  	// s3cmd put 100k.lines $WR_BUCKET_SUB/100k.lines
   201  	// s3cmd put numalphanum.txt $WR_BUCKET_SUB/numalphanum.txt
   202  	// s3cmd put big.file $WR_BUCKET_SUB/big.file
   203  	// s3cmd put sub/empty.file $WR_BUCKET_SUB/sub/empty.file
   204  	// s3cmd put sub/deep/bar $WR_BUCKET_SUB/sub/deep/bar
   205  	// rm -fr 100k.lines numalphanum.txt big.file sub
   206  	// [use s3fs to mkdir s3://bucket/wr_tests/emptyDir]
   207  
   208  	target := os.Getenv("MUXFYS_REMOTES3_TARGET")
   209  	accessKey := os.Getenv("AWS_ACCESS_KEY_ID")
   210  	secretKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
   211  
   212  	if target == "" || accessKey == "" || secretKey == "" {
   213  		SkipConvey("Without MUXFYS_REMOTES3_TARGET, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables, we'll skip remote S3 tests", t, func() {})
   214  		return
   215  	}
   216  
   217  	tmpdir, err := ioutil.TempDir("", "muxfys_testing")
   218  	if err != nil {
   219  		log.Fatal(err)
   220  	}
   221  	defer os.RemoveAll(tmpdir)
   222  
   223  	err = os.Chdir(tmpdir)
   224  	if err != nil {
   225  		log.Panic(err)
   226  	}
   227  
   228  	s3IntegrationTests(t, tmpdir, target, accessKey, secretKey, 1073741824, true)
   229  }
   230  
   231  func s3IntegrationTests(t *testing.T, tmpdir, target, accessKey, secretKey string, bigFileSize int, doRemoteTests bool) {
   232  	// common configuration of muxfys
   233  	mountPoint := filepath.Join(tmpdir, "mount")
   234  	cacheDir := filepath.Join(tmpdir, "cacheDir")
   235  
   236  	manualConfig := &S3Config{
   237  		Target:    target,
   238  		AccessKey: accessKey,
   239  		SecretKey: secretKey,
   240  	}
   241  	accessor, errn := NewS3Accessor(manualConfig)
   242  	if errn != nil {
   243  		log.Panic(errn)
   244  	}
   245  
   246  	remoteConfig := &RemoteConfig{
   247  		Accessor:  accessor,
   248  		CacheData: true,
   249  		Write:     false,
   250  	}
   251  
   252  	cfg := &Config{
   253  		Mount:   mountPoint,
   254  		Retries: 3,
   255  		Verbose: false,
   256  	}
   257  
   258  	bigFileEntry := fmt.Sprintf("big.file:file:%d", bigFileSize)
   259  	Convey("You can configure S3 from the environment", t, func() {
   260  		envConfig, err := S3ConfigFromEnvironment("", "mybucket/subdir")
   261  		So(err, ShouldBeNil)
   262  		So(envConfig.AccessKey, ShouldEqual, manualConfig.AccessKey)
   263  		So(envConfig.SecretKey, ShouldEqual, manualConfig.SecretKey)
   264  		So(envConfig.Target, ShouldNotBeNil)
   265  		So(envConfig.Target, ShouldEndWith, "mybucket/subdir")
   266  
   267  		if doRemoteTests {
   268  			u, _ := url.Parse(target)
   269  			uNew := url.URL{
   270  				Scheme: u.Scheme,
   271  				Host:   u.Host,
   272  				Path:   "mybucket/subdir",
   273  			}
   274  			So(envConfig.Target, ShouldEqual, uNew.String())
   275  
   276  			envConfig2, err := S3ConfigFromEnvironment("default", "mybucket/subdir")
   277  			So(err, ShouldBeNil)
   278  			So(envConfig2.AccessKey, ShouldEqual, envConfig.AccessKey)
   279  			So(envConfig2.SecretKey, ShouldEqual, envConfig.SecretKey)
   280  			So(envConfig2.Target, ShouldEqual, envConfig.Target)
   281  
   282  			_, err = S3ConfigFromEnvironment("-fake-", "mybucket/subdir")
   283  			So(err, ShouldNotBeNil)
   284  
   285  			// *** how can we test chaining of ~/.s3cfg and ~/.aws/credentials
   286  			// without messing with those files?
   287  		}
   288  	})
   289  
   290  	var bigFileGetTime time.Duration
   291  	Convey("You can mount with local file caching", t, func() {
   292  		fs, errc := New(cfg)
   293  		So(errc, ShouldBeNil)
   294  
   295  		errm := fs.Mount(remoteConfig)
   296  		So(errm, ShouldBeNil)
   297  
   298  		defer func() {
   299  			erru := fs.Unmount()
   300  			So(erru, ShouldBeNil)
   301  		}()
   302  
   303  		Convey("You can read a whole file as well as parts of it by seeking", func() {
   304  			path := mountPoint + "/100k.lines"
   305  			read, err := streamFile(path, 0)
   306  			So(err, ShouldBeNil)
   307  			So(read, ShouldEqual, 700000)
   308  
   309  			read, err = streamFile(path, 350000)
   310  			So(err, ShouldBeNil)
   311  			So(read, ShouldEqual, 350000)
   312  
   313  			// make sure the contents are actually correct
   314  			var expected bytes.Buffer
   315  			for i := 1; i <= 100000; i++ {
   316  				expected.WriteString(fmt.Sprintf("%06d\n", i))
   317  			}
   318  			bytes, err := ioutil.ReadFile(path)
   319  			So(err, ShouldBeNil)
   320  			So(string(bytes), ShouldEqual, expected.String())
   321  		})
   322  
   323  		Convey("You can do random reads", func() {
   324  			// it works on a small file
   325  			path := mountPoint + "/numalphanum.txt"
   326  			r, err := os.Open(path)
   327  			So(err, ShouldBeNil)
   328  			defer r.Close()
   329  
   330  			r.Seek(36, io.SeekStart)
   331  
   332  			b := make([]byte, 10)
   333  			done, err := io.ReadFull(r, b)
   334  			So(err, ShouldBeNil)
   335  			So(done, ShouldEqual, 10)
   336  			So(b, ShouldResemble, []byte("1234567890"))
   337  
   338  			r.Seek(10, io.SeekStart)
   339  			b = make([]byte, 10)
   340  			done, err = io.ReadFull(r, b)
   341  			So(err, ShouldBeNil)
   342  			So(done, ShouldEqual, 10)
   343  			So(b, ShouldResemble, []byte("abcdefghij"))
   344  
   345  			// and it works on a big file
   346  			path = mountPoint + "/100k.lines"
   347  			rbig, err := os.Open(path)
   348  			So(err, ShouldBeNil)
   349  			defer rbig.Close()
   350  
   351  			rbig.Seek(350000, io.SeekStart)
   352  			b = make([]byte, 6)
   353  			done, err = io.ReadFull(rbig, b)
   354  			So(err, ShouldBeNil)
   355  			So(done, ShouldEqual, 6)
   356  			So(b, ShouldResemble, []byte("050001"))
   357  
   358  			rbig.Seek(175000, io.SeekStart)
   359  			b = make([]byte, 6)
   360  			done, err = io.ReadFull(rbig, b)
   361  			So(err, ShouldBeNil)
   362  			So(done, ShouldEqual, 6)
   363  			So(b, ShouldResemble, []byte("025001"))
   364  		})
   365  
   366  		Convey("You can read a very big file", func() {
   367  			path := mountPoint + "/big.file"
   368  			start := time.Now()
   369  			read, err := streamFile(path, 0)
   370  			bigFileGetTime = time.Since(start)
   371  			So(err, ShouldBeNil)
   372  			So(read, ShouldEqual, bigFileSize)
   373  		})
   374  
   375  		Convey("Reading a small part of a very big file doesn't download the entire file", func() {
   376  			path := mountPoint + "/big.file"
   377  			t := time.Now()
   378  			rbig, err := os.Open(path)
   379  			So(err, ShouldBeNil)
   380  
   381  			rbig.Seek(int64(bigFileSize/2), io.SeekStart)
   382  			b := make([]byte, 6)
   383  			done, err := io.ReadFull(rbig, b)
   384  			So(err, ShouldBeNil)
   385  			So(done, ShouldEqual, 6)
   386  			rbig.Close()
   387  			So(time.Since(t).Seconds(), ShouldBeLessThan, 1)
   388  
   389  			cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("big.file"))
   390  			stat, err := os.Stat(cachePath)
   391  			So(err, ShouldBeNil)
   392  			So(stat.Size(), ShouldEqual, bigFileSize)
   393  
   394  			cmd := exec.Command("du", "-B1", "--apparent-size", cachePath)
   395  			out, err := cmd.CombinedOutput()
   396  			So(err, ShouldBeNil)
   397  			So(string(out), ShouldStartWith, fmt.Sprintf("%d\t", bigFileSize))
   398  
   399  			// even though we seeked to half way and only tried to read 6
   400  			// bytes, the underlying system ends up sending a larger Read
   401  			// request around the desired point, where the size depends on
   402  			// the filesystem and other OS related things
   403  
   404  			cmd = exec.Command("du", "-B1", cachePath)
   405  			out, err = cmd.CombinedOutput()
   406  			So(err, ShouldBeNil)
   407  			parts := strings.Split(string(out), "\t")
   408  			i, err := strconv.Atoi(parts[0])
   409  			So(err, ShouldBeNil)
   410  			So(i, ShouldBeGreaterThan, 6)
   411  		})
   412  
   413  		Convey("You can read different parts of a file simultaneously from 1 mount", func() {
   414  			init := mountPoint + "/numalphanum.txt"
   415  			path := mountPoint + "/100k.lines"
   416  
   417  			// the first read takes longer than others, so read something
   418  			// to "initialise" minio
   419  			streamFile(init, 0)
   420  
   421  			// first get a reference for how long it takes to read the whole
   422  			// thing
   423  			t2 := time.Now()
   424  			read, errs := streamFile(path, 0)
   425  			wt := time.Since(t2)
   426  			So(errs, ShouldBeNil)
   427  			So(read, ShouldEqual, 700000)
   428  
   429  			// sanity check that re-reading uses our cache
   430  			t2 = time.Now()
   431  			streamFile(path, 0)
   432  			st := time.Since(t2)
   433  
   434  			// should have completed in under 90% of the time (since minio is
   435  			// local, the difference in reading from cache vs from minio is just
   436  			// the overhead of serving files from minio; if s3 was remote and
   437  			// slow this would be more like 20%)
   438  			et := time.Duration((wt.Nanoseconds()/100)*90) * time.Nanosecond
   439  			So(st, ShouldBeLessThan, et)
   440  
   441  			// remount to clear the cache
   442  			erru := fs.Unmount()
   443  			So(erru, ShouldBeNil)
   444  			errm := fs.Mount(remoteConfig)
   445  			So(errm, ShouldBeNil)
   446  			streamFile(init, 0)
   447  
   448  			// now read the whole file and half the file at the ~same time
   449  			times := make(chan time.Duration, 2)
   450  			errors := make(chan error, 2)
   451  			streamer := func(offset, size int) {
   452  				t := time.Now()
   453  				thisRead, thisErr := streamFile(path, int64(offset))
   454  				times <- time.Since(t)
   455  				if thisErr != nil {
   456  					errors <- thisErr
   457  					return
   458  				}
   459  				if thisRead != int64(size) {
   460  					errors <- fmt.Errorf("did not read %d bytes for offset %d (%d)", size, offset, thisRead)
   461  					return
   462  				}
   463  				errors <- nil
   464  			}
   465  
   466  			t2 = time.Now()
   467  			var wg sync.WaitGroup
   468  			wg.Add(2)
   469  			go func() {
   470  				defer wg.Done()
   471  				streamer(350000, 350000)
   472  			}()
   473  			go func() {
   474  				defer wg.Done()
   475  				streamer(0, 700000)
   476  			}()
   477  			wg.Wait()
   478  			ot := time.Since(t2)
   479  
   480  			// both should complete in not much more time than the slowest,
   481  			// and that shouldn't be much slower than when reading alone
   482  			// *** debugging shows that caching definitely is occurring as
   483  			// expected, but I can't really prove it with these timings...
   484  			So(<-errors, ShouldBeNil)
   485  			So(<-errors, ShouldBeNil)
   486  			pt1 := <-times
   487  			pt2 := <-times
   488  			eto := time.Duration((int64(math.Max(float64(pt1.Nanoseconds()), float64(pt2.Nanoseconds())))/100)*110) * time.Nanosecond
   489  			// fmt.Printf("\nwt: %s, pt1: %s, pt2: %s, ot: %s, eto: %s, ets: %s\n", wt, pt1, pt2, ot, eto, ets)
   490  			So(ot, ShouldBeLessThan, eto) // *** this can rarely fail, just have to repeat :(
   491  
   492  			// *** unforunately the variability is too high, with both
   493  			// pt1 and pt2 sometimes taking more than 2x longer to read
   494  			// compared to wt, even though the below passes most of the time
   495  			// ets := time.Duration((wt.Nanoseconds()/100)*150) * time.Nanosecond
   496  			// So(ot, ShouldBeLessThan, ets)
   497  		})
   498  
   499  		Convey("You can read different files simultaneously from 1 mount", func() {
   500  			init := mountPoint + "/numalphanum.txt"
   501  			path1 := mountPoint + "/100k.lines"
   502  			path2 := mountPoint + "/big.file"
   503  
   504  			streamFile(init, 0)
   505  
   506  			// first get a reference for how long it takes to read a certain
   507  			// sized chunk of each file
   508  			// t := time.Now()
   509  			read, err := streamFile(path1, 0)
   510  			// f1t := time.Since(t)
   511  			So(err, ShouldBeNil)
   512  			So(read, ShouldEqual, 700000)
   513  
   514  			// t = time.Now()
   515  			read, err = streamFile(path2, int64(bigFileSize-700000))
   516  			// f2t := time.Since(t)
   517  			So(err, ShouldBeNil)
   518  			So(read, ShouldEqual, 700000)
   519  
   520  			// remount to clear the cache
   521  			err = fs.Unmount()
   522  			So(err, ShouldBeNil)
   523  			err = fs.Mount(remoteConfig)
   524  			So(err, ShouldBeNil)
   525  			streamFile(init, 0)
   526  
   527  			// now repeat reading them at the ~same time
   528  			times := make(chan time.Duration, 2)
   529  			errors := make(chan error, 2)
   530  			streamer := func(path string, offset, size int) {
   531  				t := time.Now()
   532  				thisRead, thisErr := streamFile(path, int64(offset))
   533  				times <- time.Since(t)
   534  				if thisErr != nil {
   535  					errors <- thisErr
   536  					return
   537  				}
   538  				if thisRead != int64(size) {
   539  					errors <- fmt.Errorf("did not read %d bytes of %s at offset %d (%d)", size, path, offset, thisRead)
   540  					return
   541  				}
   542  				errors <- nil
   543  			}
   544  
   545  			t := time.Now()
   546  			var wg sync.WaitGroup
   547  			wg.Add(2)
   548  			go func() {
   549  				defer wg.Done()
   550  				streamer(path1, 0, 700000)
   551  			}()
   552  			go func() {
   553  				defer wg.Done()
   554  				streamer(path2, bigFileSize-700000, 700000)
   555  			}()
   556  			wg.Wait()
   557  			ot := time.Since(t)
   558  
   559  			// each should have completed in less than 190% of the time
   560  			// needed to read them sequentially, and both should have
   561  			// completed in less than 110% of the slowest one
   562  			So(<-errors, ShouldBeNil)
   563  			So(<-errors, ShouldBeNil)
   564  			pt1 := <-times
   565  			pt2 := <-times
   566  			// et1 := time.Duration((f1t.Nanoseconds()/100)*190) * time.Nanosecond
   567  			// et2 := time.Duration((f2t.Nanoseconds()/100)*190) * time.Nanosecond
   568  			var multiplier int64
   569  			if bigFileSize > 10000000 {
   570  				multiplier = 110
   571  			} else {
   572  				multiplier = 250
   573  			}
   574  			eto := time.Duration((int64(math.Max(float64(pt1.Nanoseconds()), float64(pt2.Nanoseconds())))/100)*multiplier) * time.Nanosecond
   575  			// *** these timing tests are too unreliable when using minio server
   576  			// So(pt1, ShouldBeLessThan, et1)
   577  			// So(pt2, ShouldBeLessThan, et2)
   578  			So(ot, ShouldBeLessThan, eto)
   579  		})
   580  
   581  		Convey("Trying to write in non Write mode fails", func() {
   582  			path := mountPoint + "/write.test"
   583  			b := []byte("write test\n")
   584  			err := ioutil.WriteFile(path, b, 0644)
   585  			So(err, ShouldNotBeNil)
   586  			perr, ok := err.(*os.PathError)
   587  			So(ok, ShouldBeTrue)
   588  			So(perr.Error(), ShouldContainSubstring, "operation not permitted")
   589  		})
   590  
   591  		Convey("You can't delete files either", func() {
   592  			path := mountPoint + "/big.file"
   593  			err := os.Remove(path)
   594  			So(err, ShouldNotBeNil)
   595  			perr, ok := err.(*os.PathError)
   596  			So(ok, ShouldBeTrue)
   597  			So(perr.Error(), ShouldContainSubstring, "operation not permitted")
   598  		})
   599  
   600  		Convey("And you can't rename files", func() {
   601  			path := mountPoint + "/big.file"
   602  			dest := mountPoint + "/1G.moved"
   603  			cmd := exec.Command("mv", path, dest)
   604  			err := cmd.Run()
   605  			So(err, ShouldNotBeNil)
   606  		})
   607  
   608  		Convey("You can't touch files in non Write mode", func() {
   609  			path := mountPoint + "/big.file"
   610  			cmd := exec.Command("touch", path)
   611  			err := cmd.Run()
   612  			So(err, ShouldNotBeNil)
   613  		})
   614  
   615  		Convey("You can't make, delete or rename directories in non Write mode", func() {
   616  			newDir := mountPoint + "/newdir_test"
   617  			cmd := exec.Command("mkdir", newDir)
   618  			err := cmd.Run()
   619  			So(err, ShouldNotBeNil)
   620  
   621  			path := mountPoint + "/sub"
   622  			cmd = exec.Command("rmdir", path)
   623  			err = cmd.Run()
   624  			So(err, ShouldNotBeNil)
   625  
   626  			cmd = exec.Command("mv", path, newDir)
   627  			err = cmd.Run()
   628  			So(err, ShouldNotBeNil)
   629  		})
   630  
   631  		Convey("Unmounting after reading a file deletes the cache dir", func() {
   632  			streamFile(mountPoint+"/numalphanum.txt", 0)
   633  			thisCacheDir := fs.remotes[0].cacheDir
   634  			_, err := os.Stat(thisCacheDir)
   635  			So(err, ShouldBeNil)
   636  			err = fs.Unmount()
   637  			So(err, ShouldBeNil)
   638  			_, err = os.Stat(thisCacheDir)
   639  			So(err, ShouldNotBeNil)
   640  			So(os.IsNotExist(err), ShouldBeTrue)
   641  		})
   642  	})
   643  
   644  	Convey("You can mount with local file caching in write mode", t, func() {
   645  		remoteConfig.Write = true
   646  		fs, errc := New(cfg)
   647  		So(errc, ShouldBeNil)
   648  
   649  		errm := fs.Mount(remoteConfig)
   650  		So(errm, ShouldBeNil)
   651  
   652  		defer func() {
   653  			erru := fs.Unmount()
   654  			remoteConfig.Write = false
   655  			So(erru, ShouldBeNil)
   656  		}()
   657  
   658  		Convey("Trying to write in write mode works", func() {
   659  			path := mountPoint + "/write.test"
   660  			b := []byte("write test\n")
   661  			errf := ioutil.WriteFile(path, b, 0644)
   662  			So(errf, ShouldBeNil)
   663  
   664  			// you can immediately read it back
   665  			bytes, errf := ioutil.ReadFile(path)
   666  			So(errf, ShouldBeNil)
   667  			So(bytes, ShouldResemble, b)
   668  
   669  			// (because it's in the the local cache)
   670  			thisCacheDir := fs.remotes[0].cacheDir
   671  			_, errf = os.Stat(thisCacheDir)
   672  			So(errf, ShouldBeNil)
   673  			cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
   674  			_, errf = os.Stat(cachePath)
   675  			So(errf, ShouldBeNil)
   676  
   677  			// and it's statable and listable
   678  			_, errf = os.Stat(path)
   679  			So(errf, ShouldBeNil)
   680  
   681  			entries, errf := ioutil.ReadDir(mountPoint)
   682  			So(errf, ShouldBeNil)
   683  			details := dirDetails(entries)
   684  			rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir", "write.test:file:11"}
   685  			So(details, ShouldResemble, rootEntries)
   686  
   687  			// unmounting causes the local cached file to be deleted
   688  			errf = fs.Unmount()
   689  			So(errf, ShouldBeNil)
   690  
   691  			_, errf = os.Stat(cachePath)
   692  			So(errf, ShouldNotBeNil)
   693  			So(os.IsNotExist(errf), ShouldBeTrue)
   694  			_, errf = os.Stat(thisCacheDir)
   695  			So(errf, ShouldNotBeNil)
   696  			So(os.IsNotExist(errf), ShouldBeTrue)
   697  			_, errf = os.Stat(path)
   698  			So(errf, ShouldNotBeNil)
   699  			So(os.IsNotExist(errf), ShouldBeTrue)
   700  
   701  			// remounting lets us read the file again - it actually got
   702  			// uploaded
   703  			errf = fs.Mount(remoteConfig)
   704  			So(errf, ShouldBeNil)
   705  
   706  			cachePath = fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
   707  			_, errf = os.Stat(cachePath)
   708  			So(errf, ShouldNotBeNil)
   709  			So(os.IsNotExist(errf), ShouldBeTrue)
   710  
   711  			bytes, errf = ioutil.ReadFile(path)
   712  			So(errf, ShouldBeNil)
   713  			So(bytes, ShouldResemble, b)
   714  
   715  			_, errf = os.Stat(cachePath)
   716  			So(errf, ShouldBeNil)
   717  
   718  			Convey("You can append to a cached file", func() {
   719  				f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
   720  				So(err, ShouldBeNil)
   721  
   722  				line2 := "line2\n"
   723  				_, err = f.WriteString(line2)
   724  				f.Close()
   725  				So(err, ShouldBeNil)
   726  
   727  				bytes, err = ioutil.ReadFile(path)
   728  				So(err, ShouldBeNil)
   729  				So(string(bytes), ShouldEqual, string(b)+line2)
   730  
   731  				err = fs.Unmount()
   732  				So(err, ShouldBeNil)
   733  
   734  				_, err = os.Stat(cachePath)
   735  				So(err, ShouldNotBeNil)
   736  				So(os.IsNotExist(err), ShouldBeTrue)
   737  				_, err = os.Stat(path)
   738  				So(err, ShouldNotBeNil)
   739  				So(os.IsNotExist(err), ShouldBeTrue)
   740  
   741  				err = fs.Mount(remoteConfig)
   742  				So(err, ShouldBeNil)
   743  
   744  				bytes, err = ioutil.ReadFile(path)
   745  				So(err, ShouldBeNil)
   746  				So(string(bytes), ShouldEqual, string(b)+line2)
   747  
   748  				Convey("You can truncate a cached file", func() {
   749  					err := os.Truncate(path, 0)
   750  					So(err, ShouldBeNil)
   751  
   752  					cachePath2 := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
   753  					stat, err := os.Stat(cachePath2)
   754  					So(err, ShouldBeNil)
   755  					So(stat.Size(), ShouldEqual, 0)
   756  					stat, err = os.Stat(path)
   757  					So(err, ShouldBeNil)
   758  					So(stat.Size(), ShouldEqual, 0)
   759  
   760  					err = fs.Unmount()
   761  					So(err, ShouldBeNil)
   762  
   763  					_, err = os.Stat(cachePath2)
   764  					So(err, ShouldNotBeNil)
   765  					So(os.IsNotExist(err), ShouldBeTrue)
   766  
   767  					err = fs.Mount(remoteConfig)
   768  					So(err, ShouldBeNil)
   769  
   770  					cachePath2 = fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
   771  					_, err = os.Stat(cachePath2)
   772  					So(err, ShouldNotBeNil)
   773  					So(os.IsNotExist(err), ShouldBeTrue)
   774  					stat, err = os.Stat(path)
   775  					So(err, ShouldBeNil)
   776  					So(stat.Size(), ShouldEqual, 0)
   777  					bytes, err = ioutil.ReadFile(path)
   778  					So(err, ShouldBeNil)
   779  					So(string(bytes), ShouldEqual, "")
   780  
   781  					Convey("You can delete files", func() {
   782  						err = os.Remove(path)
   783  						So(err, ShouldBeNil)
   784  
   785  						_, err = os.Stat(cachePath2)
   786  						So(err, ShouldNotBeNil)
   787  						So(os.IsNotExist(err), ShouldBeTrue)
   788  						_, err = os.Stat(path)
   789  						So(err, ShouldNotBeNil)
   790  						So(os.IsNotExist(err), ShouldBeTrue)
   791  					})
   792  				})
   793  
   794  				Convey("You can truncate a cached file using an offset", func() {
   795  					err := os.Truncate(path, 3)
   796  					So(err, ShouldBeNil)
   797  
   798  					cachePath2 := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
   799  					stat, err := os.Stat(cachePath2)
   800  					So(err, ShouldBeNil)
   801  					So(stat.Size(), ShouldEqual, 3)
   802  					stat, err = os.Stat(path)
   803  					So(err, ShouldBeNil)
   804  					So(stat.Size(), ShouldEqual, 3)
   805  
   806  					err = fs.Unmount()
   807  					So(err, ShouldBeNil)
   808  
   809  					_, err = os.Stat(cachePath2)
   810  					So(err, ShouldNotBeNil)
   811  					So(os.IsNotExist(err), ShouldBeTrue)
   812  
   813  					err = fs.Mount(remoteConfig)
   814  					So(err, ShouldBeNil)
   815  
   816  					cachePath2 = fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
   817  					_, err = os.Stat(cachePath2)
   818  					So(err, ShouldNotBeNil)
   819  					So(os.IsNotExist(err), ShouldBeTrue)
   820  					stat, err = os.Stat(path)
   821  					So(err, ShouldBeNil)
   822  					So(stat.Size(), ShouldEqual, 3)
   823  					bytes, err = ioutil.ReadFile(path)
   824  					So(err, ShouldBeNil)
   825  					So(string(bytes), ShouldEqual, "wri")
   826  
   827  					err = os.Remove(path)
   828  					So(err, ShouldBeNil)
   829  				})
   830  
   831  				Convey("You can truncate a cached file and then write to it", func() {
   832  					err := os.Truncate(path, 0)
   833  					So(err, ShouldBeNil)
   834  
   835  					f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
   836  					So(err, ShouldBeNil)
   837  
   838  					line := "trunc\n"
   839  					_, err = f.WriteString(line)
   840  					f.Close()
   841  					So(err, ShouldBeNil)
   842  
   843  					bytes, err = ioutil.ReadFile(path)
   844  					So(err, ShouldBeNil)
   845  					So(string(bytes), ShouldEqual, line)
   846  
   847  					cachePath2 := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
   848  					err = fs.Unmount()
   849  					So(err, ShouldBeNil)
   850  
   851  					_, err = os.Stat(cachePath2)
   852  					So(err, ShouldNotBeNil)
   853  					So(os.IsNotExist(err), ShouldBeTrue)
   854  					_, err = os.Stat(path)
   855  					So(err, ShouldNotBeNil)
   856  					So(os.IsNotExist(err), ShouldBeTrue)
   857  
   858  					err = fs.Mount(remoteConfig)
   859  					So(err, ShouldBeNil)
   860  
   861  					bytes, err = ioutil.ReadFile(path)
   862  					So(err, ShouldBeNil)
   863  					So(string(bytes), ShouldEqual, line)
   864  
   865  					err = os.Remove(path)
   866  					So(err, ShouldBeNil)
   867  				})
   868  			})
   869  
   870  			Convey("You can rename files using mv", func() {
   871  				dest := mountPoint + "/write.moved"
   872  				cmd := exec.Command("mv", path, dest)
   873  				err := cmd.Run()
   874  				So(err, ShouldBeNil)
   875  
   876  				bytes, err = ioutil.ReadFile(dest)
   877  				So(err, ShouldBeNil)
   878  				So(bytes, ShouldResemble, b)
   879  
   880  				_, err = os.Stat(path)
   881  				So(err, ShouldNotBeNil)
   882  
   883  				err = fs.Unmount()
   884  				So(err, ShouldBeNil)
   885  				err = fs.Mount(remoteConfig)
   886  				So(err, ShouldBeNil)
   887  
   888  				defer func() {
   889  					err = os.Remove(dest)
   890  					So(err, ShouldBeNil)
   891  				}()
   892  
   893  				bytes, err = ioutil.ReadFile(dest)
   894  				So(err, ShouldBeNil)
   895  				So(bytes, ShouldResemble, b)
   896  
   897  				_, err = os.Stat(dest)
   898  				So(err, ShouldBeNil)
   899  
   900  				_, err = os.Stat(path)
   901  				So(err, ShouldNotBeNil)
   902  			})
   903  
   904  			Convey("You can rename uncached files using os.Rename", func() {
   905  				// unmount first to clear the cache
   906  				err := fs.Unmount()
   907  				So(err, ShouldBeNil)
   908  				err = fs.Mount(remoteConfig)
   909  				So(err, ShouldBeNil)
   910  
   911  				dest := mountPoint + "/write.moved"
   912  				err = os.Rename(path, dest)
   913  				So(err, ShouldBeNil)
   914  
   915  				_, err = os.Stat(cachePath)
   916  				So(err, ShouldNotBeNil)
   917  				cachePathDest := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.moved"))
   918  				_, err = os.Stat(cachePathDest)
   919  				So(err, ShouldNotBeNil)
   920  
   921  				bytes, err = ioutil.ReadFile(dest)
   922  				So(err, ShouldBeNil)
   923  				So(bytes, ShouldResemble, b)
   924  
   925  				_, err = os.Stat(path)
   926  				So(err, ShouldNotBeNil)
   927  
   928  				err = fs.Unmount()
   929  				So(err, ShouldBeNil)
   930  				err = fs.Mount(remoteConfig)
   931  				So(err, ShouldBeNil)
   932  
   933  				defer func() {
   934  					err = os.Remove(dest)
   935  					So(err, ShouldBeNil)
   936  				}()
   937  
   938  				bytes, err = ioutil.ReadFile(dest)
   939  				So(err, ShouldBeNil)
   940  				So(bytes, ShouldResemble, b)
   941  
   942  				_, err = os.Stat(dest)
   943  				So(err, ShouldBeNil)
   944  
   945  				_, err = os.Stat(path)
   946  				So(err, ShouldNotBeNil)
   947  			})
   948  
   949  			Convey("You can rename cached and altered files", func() {
   950  				f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
   951  				So(err, ShouldBeNil)
   952  
   953  				line2 := "line2\n"
   954  				_, err = f.WriteString(line2)
   955  				f.Close()
   956  				So(err, ShouldBeNil)
   957  
   958  				bytes, err = ioutil.ReadFile(path)
   959  				So(err, ShouldBeNil)
   960  				So(string(bytes), ShouldEqual, string(b)+line2)
   961  
   962  				dest := mountPoint + "/write.moved"
   963  				err = os.Rename(path, dest)
   964  				So(err, ShouldBeNil)
   965  
   966  				_, err = os.Stat(cachePath)
   967  				So(err, ShouldNotBeNil)
   968  				cachePathDest := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.moved"))
   969  				_, err = os.Stat(cachePathDest)
   970  				So(err, ShouldBeNil)
   971  
   972  				bytes, err = ioutil.ReadFile(dest)
   973  				So(err, ShouldBeNil)
   974  				So(string(bytes), ShouldEqual, string(b)+line2)
   975  
   976  				_, err = os.Stat(path)
   977  				So(err, ShouldNotBeNil)
   978  
   979  				err = fs.Unmount()
   980  				So(err, ShouldBeNil)
   981  				err = fs.Mount(remoteConfig)
   982  				So(err, ShouldBeNil)
   983  
   984  				defer func() {
   985  					err = os.Remove(dest)
   986  					So(err, ShouldBeNil)
   987  				}()
   988  
   989  				bytes, err = ioutil.ReadFile(dest)
   990  				So(err, ShouldBeNil)
   991  				So(string(bytes), ShouldEqual, string(b)+line2)
   992  
   993  				_, err = os.Stat(dest)
   994  				So(err, ShouldBeNil)
   995  
   996  				_, err = os.Stat(path)
   997  				So(err, ShouldNotBeNil)
   998  			})
   999  		})
  1000  
  1001  		Convey("You can't rename remote directories", func() {
  1002  			newDir := mountPoint + "/newdir_test"
  1003  			subDir := mountPoint + "/sub"
  1004  			cmd := exec.Command("mv", subDir, newDir)
  1005  			err := cmd.Run()
  1006  			So(err, ShouldNotBeNil)
  1007  		})
  1008  
  1009  		Convey("You can't remove remote directories", func() {
  1010  			subDir := mountPoint + "/sub"
  1011  			cmd := exec.Command("rmdir", subDir)
  1012  			err := cmd.Run()
  1013  			So(err, ShouldNotBeNil)
  1014  		})
  1015  
  1016  		Convey("You can create directories and rename and remove those", func() {
  1017  			newDir := mountPoint + "/newdir_test"
  1018  			cmd := exec.Command("mkdir", newDir)
  1019  			err := cmd.Run()
  1020  			So(err, ShouldBeNil)
  1021  
  1022  			entries, err := ioutil.ReadDir(mountPoint)
  1023  			So(err, ShouldBeNil)
  1024  			details := dirDetails(entries)
  1025  			rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "newdir_test:dir", "numalphanum.txt:file:47", "sub:dir"}
  1026  			So(details, ShouldResemble, rootEntries)
  1027  
  1028  			movedDir := mountPoint + "/newdir_moved"
  1029  			cmd = exec.Command("mv", newDir, movedDir)
  1030  			err = cmd.Run()
  1031  			So(err, ShouldBeNil)
  1032  
  1033  			entries, err = ioutil.ReadDir(mountPoint)
  1034  			So(err, ShouldBeNil)
  1035  			details = dirDetails(entries)
  1036  			rootEntries = []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "newdir_moved:dir", "numalphanum.txt:file:47", "sub:dir"}
  1037  			So(details, ShouldResemble, rootEntries)
  1038  
  1039  			cmd = exec.Command("rmdir", movedDir)
  1040  			err = cmd.Run()
  1041  			So(err, ShouldBeNil)
  1042  
  1043  			Convey("You can create nested directories and add files to them", func() {
  1044  				nestedDir := mountPoint + "/newdir_test/a/b/c"
  1045  				err = os.MkdirAll(nestedDir, os.FileMode(700))
  1046  				So(err, ShouldBeNil)
  1047  
  1048  				path := nestedDir + "/write.nested"
  1049  				b := []byte("nested test\n")
  1050  				err := ioutil.WriteFile(path, b, 0644)
  1051  				So(err, ShouldBeNil)
  1052  
  1053  				bytes, err := ioutil.ReadFile(path)
  1054  				So(err, ShouldBeNil)
  1055  				So(bytes, ShouldResemble, b)
  1056  
  1057  				entries, err := ioutil.ReadDir(nestedDir)
  1058  				So(err, ShouldBeNil)
  1059  				details := dirDetails(entries)
  1060  				nestEntries := []string{"write.nested:file:12"}
  1061  				So(details, ShouldResemble, nestEntries)
  1062  
  1063  				err = fs.Unmount()
  1064  				So(err, ShouldBeNil)
  1065  				err = fs.Mount(remoteConfig)
  1066  				So(err, ShouldBeNil)
  1067  
  1068  				bytes, err = ioutil.ReadFile(path)
  1069  				So(err, ShouldBeNil)
  1070  				So(bytes, ShouldResemble, b)
  1071  
  1072  				os.Remove(path)
  1073  			})
  1074  		})
  1075  
  1076  		Convey("Given a local directory", func() {
  1077  			mvDir := filepath.Join(tmpdir, "mvtest")
  1078  			mvSubDir := filepath.Join(mvDir, "mvsubdir")
  1079  			errf := os.MkdirAll(mvSubDir, os.FileMode(0700))
  1080  			So(errf, ShouldBeNil)
  1081  			mvFile := filepath.Join(mvSubDir, "file")
  1082  			mvBytes := []byte("mvfile\n")
  1083  			errf = ioutil.WriteFile(mvFile, mvBytes, 0644)
  1084  			So(errf, ShouldBeNil)
  1085  			errf = ioutil.WriteFile(filepath.Join(mvDir, "a.file"), mvBytes, 0644)
  1086  			So(errf, ShouldBeNil)
  1087  
  1088  			Convey("You can mv it to the mount point", func() {
  1089  				mountDir := filepath.Join(mountPoint, "mvtest")
  1090  				dest := filepath.Join(mountDir, "mvsubdir", "file")
  1091  				dest2 := filepath.Join(mountDir, "a.file")
  1092  
  1093  				cmd := exec.Command("mv", mvDir, mountDir)
  1094  				err := cmd.Run()
  1095  				So(err, ShouldBeNil)
  1096  
  1097  				bytes, err := ioutil.ReadFile(dest)
  1098  				So(err, ShouldBeNil)
  1099  				So(bytes, ShouldResemble, mvBytes)
  1100  
  1101  				err = fs.Unmount()
  1102  				So(err, ShouldBeNil)
  1103  				err = fs.Mount(remoteConfig)
  1104  				So(err, ShouldBeNil)
  1105  
  1106  				defer func() {
  1107  					err = os.Remove(dest)
  1108  					So(err, ShouldBeNil)
  1109  					err = os.Remove(dest2)
  1110  					So(err, ShouldBeNil)
  1111  				}()
  1112  
  1113  				bytes, err = ioutil.ReadFile(dest)
  1114  				So(err, ShouldBeNil)
  1115  				So(bytes, ShouldResemble, mvBytes)
  1116  			})
  1117  
  1118  			Convey("You can mv its contents to the mount point", func() {
  1119  				dest := filepath.Join(mountPoint, "mvsubdir", "file")
  1120  				dest2 := filepath.Join(mountPoint, "a.file")
  1121  
  1122  				cmd := exec.Command("sh", "-c", fmt.Sprintf("mv %s/* %s/", mvDir, mountPoint))
  1123  				err := cmd.Run()
  1124  				So(err, ShouldBeNil)
  1125  
  1126  				bytes, err := ioutil.ReadFile(dest)
  1127  				So(err, ShouldBeNil)
  1128  				So(bytes, ShouldResemble, mvBytes)
  1129  
  1130  				err = fs.Unmount()
  1131  				So(err, ShouldBeNil)
  1132  				err = fs.Mount(remoteConfig)
  1133  				So(err, ShouldBeNil)
  1134  
  1135  				defer func() {
  1136  					err = os.Remove(dest)
  1137  					So(err, ShouldBeNil)
  1138  					err = os.Remove(dest2)
  1139  					So(err, ShouldBeNil)
  1140  				}()
  1141  
  1142  				bytes, err = ioutil.ReadFile(dest)
  1143  				So(err, ShouldBeNil)
  1144  				So(bytes, ShouldResemble, mvBytes)
  1145  			})
  1146  		})
  1147  
  1148  		Convey("Trying to read a non-existent file fails as expected", func() {
  1149  			name := "non-existent.file"
  1150  			path := mountPoint + "/" + name
  1151  			_, err := streamFile(path, 0)
  1152  			So(err, ShouldNotBeNil)
  1153  			So(os.IsNotExist(err), ShouldBeTrue)
  1154  		})
  1155  
  1156  		Convey("Trying to read an externally deleted file fails as expected", func() {
  1157  			name := "non-existent.file"
  1158  			path := mountPoint + "/" + name
  1159  			// we'll hack fs to make it think non-existent.file does exist
  1160  			// so we can test the behaviour of a file getting deleted
  1161  			// externally
  1162  			//ioutil.ReadDir(mountPoint) // *** can't figure out why this causes a race condition
  1163  			fs.GetAttr("/", &fuse.Context{})
  1164  			So(fs.files["big.file"], ShouldNotBeNil)
  1165  			So(fs.files[name], ShouldBeNil)
  1166  			fs.mapMutex.Lock()
  1167  			fs.addNewEntryToItsDir(name, fuse.S_IFREG)
  1168  			fs.files[name] = fs.files["big.file"]
  1169  			fs.fileToRemote[name] = fs.fileToRemote["big.file"]
  1170  			fs.mapMutex.Unlock()
  1171  			So(fs.files[name], ShouldNotBeNil)
  1172  			_, err := streamFile(path, 0)
  1173  			So(err, ShouldNotBeNil)
  1174  			So(os.IsNotExist(err), ShouldBeTrue)
  1175  			So(fs.files[name], ShouldNotBeNil) // *** unfortunately we only know it doesn't exist when we try to read, which means we can't update fs
  1176  		})
  1177  
  1178  		Convey("In write mode, you can create a file to test with...", func() {
  1179  			// create a file we can play with first
  1180  			path := mountPoint + "/write.test"
  1181  			b := []byte("write test\n")
  1182  			err := ioutil.WriteFile(path, b, 0644)
  1183  			So(err, ShouldBeNil)
  1184  
  1185  			err = fs.Unmount()
  1186  			So(err, ShouldBeNil)
  1187  
  1188  			defer func() {
  1189  				err = os.Remove(path)
  1190  				So(err, ShouldBeNil)
  1191  			}()
  1192  
  1193  			err = fs.Mount(remoteConfig)
  1194  			So(err, ShouldBeNil)
  1195  
  1196  			Convey("You can't write to a file you open RDONLY", func() {
  1197  				f, err := os.OpenFile(path, os.O_RDONLY, 0644)
  1198  				So(err, ShouldBeNil)
  1199  				_, err = f.WriteString("fails\n")
  1200  				f.Close()
  1201  				So(err, ShouldNotBeNil)
  1202  			})
  1203  
  1204  			Convey("You can append to an uncached file", func() {
  1205  				f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
  1206  				So(err, ShouldBeNil)
  1207  
  1208  				line2 := "line2\n"
  1209  				_, err = f.WriteString(line2)
  1210  				f.Close()
  1211  				So(err, ShouldBeNil)
  1212  
  1213  				bytes, err := ioutil.ReadFile(path)
  1214  				So(err, ShouldBeNil)
  1215  				So(string(bytes), ShouldEqual, string(b)+line2)
  1216  
  1217  				cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
  1218  				err = fs.Unmount()
  1219  				So(err, ShouldBeNil)
  1220  
  1221  				_, err = os.Stat(cachePath)
  1222  				So(err, ShouldNotBeNil)
  1223  				So(os.IsNotExist(err), ShouldBeTrue)
  1224  				_, err = os.Stat(path)
  1225  				So(err, ShouldNotBeNil)
  1226  				So(os.IsNotExist(err), ShouldBeTrue)
  1227  
  1228  				err = fs.Mount(remoteConfig)
  1229  				So(err, ShouldBeNil)
  1230  
  1231  				bytes, err = ioutil.ReadFile(path)
  1232  				So(err, ShouldBeNil)
  1233  				So(string(bytes), ShouldEqual, string(b)+line2)
  1234  			})
  1235  
  1236  			Convey("You can append to an uncached file and upload without reading the original part of the file", func() {
  1237  				f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
  1238  				So(err, ShouldBeNil)
  1239  
  1240  				line2 := "line2\n"
  1241  				_, err = f.WriteString(line2)
  1242  				f.Close()
  1243  				So(err, ShouldBeNil)
  1244  
  1245  				err = fs.Unmount()
  1246  				So(err, ShouldBeNil)
  1247  				err = fs.Mount(remoteConfig)
  1248  				So(err, ShouldBeNil)
  1249  
  1250  				bytes, err := ioutil.ReadFile(path)
  1251  				So(err, ShouldBeNil)
  1252  				So(string(bytes), ShouldEqual, string(b)+line2)
  1253  			})
  1254  
  1255  			Convey("You can append to a partially read file", func() {
  1256  				// first make the file bigger so we can avoid minimum file
  1257  				// read size issues
  1258  				f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
  1259  				So(err, ShouldBeNil)
  1260  
  1261  				for i := 2; i <= 10000; i++ {
  1262  					_, err = f.WriteString(fmt.Sprintf("line%d\n", i))
  1263  					if err != nil {
  1264  						break
  1265  					}
  1266  				}
  1267  				f.Close()
  1268  				So(err, ShouldBeNil)
  1269  
  1270  				err = fs.Unmount()
  1271  				So(err, ShouldBeNil)
  1272  				err = fs.Mount(remoteConfig)
  1273  				So(err, ShouldBeNil)
  1274  
  1275  				// now do a partial read
  1276  				r, err := os.Open(path)
  1277  				So(err, ShouldBeNil)
  1278  
  1279  				r.Seek(11, io.SeekStart)
  1280  
  1281  				b := make([]byte, 5)
  1282  				done, err := io.ReadFull(r, b)
  1283  				r.Close()
  1284  				So(err, ShouldBeNil)
  1285  				So(done, ShouldEqual, 5)
  1286  				So(string(b), ShouldEqual, "line2")
  1287  
  1288  				info, err := os.Stat(path)
  1289  				So(err, ShouldBeNil)
  1290  				So(info.Size(), ShouldEqual, 88899)
  1291  
  1292  				// now append
  1293  				f, err = os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
  1294  				So(err, ShouldBeNil)
  1295  
  1296  				newline := "line10001\n"
  1297  				_, err = f.WriteString(newline)
  1298  				f.Close()
  1299  				So(err, ShouldBeNil)
  1300  
  1301  				err = fs.Unmount()
  1302  				So(err, ShouldBeNil)
  1303  				err = fs.Mount(remoteConfig)
  1304  				So(err, ShouldBeNil)
  1305  
  1306  				// check it worked correctly
  1307  				info, err = os.Stat(path)
  1308  				So(err, ShouldBeNil)
  1309  				So(info.Size(), ShouldEqual, 88909)
  1310  
  1311  				r, err = os.Open(path)
  1312  				So(err, ShouldBeNil)
  1313  
  1314  				r.Seek(11, io.SeekStart)
  1315  
  1316  				b = make([]byte, 5)
  1317  				done, err = io.ReadFull(r, b)
  1318  				So(err, ShouldBeNil)
  1319  				So(done, ShouldEqual, 5)
  1320  				So(string(b), ShouldEqual, "line2")
  1321  
  1322  				r.Seek(88889, io.SeekStart)
  1323  
  1324  				b = make([]byte, 19)
  1325  				done, err = io.ReadFull(r, b)
  1326  				r.Close()
  1327  				So(err, ShouldBeNil)
  1328  				So(done, ShouldEqual, 19)
  1329  				So(string(b), ShouldEqual, "line10000\nline10001")
  1330  			})
  1331  
  1332  			Convey("You can truncate an uncached file", func() {
  1333  				err := os.Truncate(path, 0)
  1334  				So(err, ShouldBeNil)
  1335  
  1336  				cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
  1337  				stat, err := os.Stat(cachePath)
  1338  				So(err, ShouldBeNil)
  1339  				So(stat.Size(), ShouldEqual, 0)
  1340  				stat, err = os.Stat(path)
  1341  				So(err, ShouldBeNil)
  1342  				So(stat.Size(), ShouldEqual, 0)
  1343  
  1344  				err = fs.Unmount()
  1345  				So(err, ShouldBeNil)
  1346  
  1347  				_, err = os.Stat(cachePath)
  1348  				So(err, ShouldNotBeNil)
  1349  				So(os.IsNotExist(err), ShouldBeTrue)
  1350  
  1351  				err = fs.Mount(remoteConfig)
  1352  				So(err, ShouldBeNil)
  1353  
  1354  				_, err = os.Stat(cachePath)
  1355  				So(err, ShouldNotBeNil)
  1356  				So(os.IsNotExist(err), ShouldBeTrue)
  1357  				stat, err = os.Stat(path)
  1358  				So(err, ShouldBeNil)
  1359  				So(stat.Size(), ShouldEqual, 0)
  1360  				bytes, err := ioutil.ReadFile(path)
  1361  				So(err, ShouldBeNil)
  1362  				So(string(bytes), ShouldEqual, "")
  1363  			})
  1364  
  1365  			Convey("You can truncate an uncached file using an offset", func() {
  1366  				err := os.Truncate(path, 3)
  1367  				So(err, ShouldBeNil)
  1368  
  1369  				cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
  1370  				stat, err := os.Stat(cachePath)
  1371  				So(err, ShouldBeNil)
  1372  				So(stat.Size(), ShouldEqual, 3)
  1373  				stat, err = os.Stat(path)
  1374  				So(err, ShouldBeNil)
  1375  				So(stat.Size(), ShouldEqual, 3)
  1376  
  1377  				err = fs.Unmount()
  1378  				So(err, ShouldBeNil)
  1379  
  1380  				_, err = os.Stat(cachePath)
  1381  				So(err, ShouldNotBeNil)
  1382  				So(os.IsNotExist(err), ShouldBeTrue)
  1383  
  1384  				err = fs.Mount(remoteConfig)
  1385  				So(err, ShouldBeNil)
  1386  
  1387  				_, err = os.Stat(cachePath)
  1388  				So(err, ShouldNotBeNil)
  1389  				So(os.IsNotExist(err), ShouldBeTrue)
  1390  				stat, err = os.Stat(path)
  1391  				So(err, ShouldBeNil)
  1392  				So(stat.Size(), ShouldEqual, 3)
  1393  				bytes, err := ioutil.ReadFile(path)
  1394  				So(err, ShouldBeNil)
  1395  				So(string(bytes), ShouldEqual, "wri")
  1396  			})
  1397  
  1398  			Convey("You can truncate an uncached file using an Open call", func() {
  1399  				f, err := os.OpenFile(path, os.O_TRUNC, 0644)
  1400  				So(err, ShouldBeNil)
  1401  				f.Close()
  1402  
  1403  				cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
  1404  				stat, err := os.Stat(cachePath)
  1405  				So(err, ShouldBeNil)
  1406  				So(stat.Size(), ShouldEqual, 0)
  1407  				stat, err = os.Stat(path)
  1408  				So(err, ShouldBeNil)
  1409  				So(stat.Size(), ShouldEqual, 0)
  1410  
  1411  				err = fs.Unmount()
  1412  				So(err, ShouldBeNil)
  1413  
  1414  				_, err = os.Stat(cachePath)
  1415  				So(err, ShouldNotBeNil)
  1416  				So(os.IsNotExist(err), ShouldBeTrue)
  1417  
  1418  				err = fs.Mount(remoteConfig)
  1419  				So(err, ShouldBeNil)
  1420  
  1421  				_, err = os.Stat(cachePath)
  1422  				So(err, ShouldNotBeNil)
  1423  				So(os.IsNotExist(err), ShouldBeTrue)
  1424  				stat, err = os.Stat(path)
  1425  				So(err, ShouldBeNil)
  1426  				So(stat.Size(), ShouldEqual, 0)
  1427  				bytes, err := ioutil.ReadFile(path)
  1428  				So(err, ShouldBeNil)
  1429  				So(string(bytes), ShouldEqual, "")
  1430  			})
  1431  
  1432  			Convey("You can truncate an uncached file and immediately write to it", func() {
  1433  				err := os.Truncate(path, 0)
  1434  				So(err, ShouldBeNil)
  1435  
  1436  				f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
  1437  				So(err, ShouldBeNil)
  1438  
  1439  				line := "trunc\n"
  1440  				_, err = f.WriteString(line)
  1441  				f.Close()
  1442  				So(err, ShouldBeNil)
  1443  
  1444  				bytes, err := ioutil.ReadFile(path)
  1445  				So(err, ShouldBeNil)
  1446  				So(string(bytes), ShouldEqual, line)
  1447  
  1448  				cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
  1449  				err = fs.Unmount()
  1450  				So(err, ShouldBeNil)
  1451  
  1452  				_, err = os.Stat(cachePath)
  1453  				So(err, ShouldNotBeNil)
  1454  				So(os.IsNotExist(err), ShouldBeTrue)
  1455  				_, err = os.Stat(path)
  1456  				So(err, ShouldNotBeNil)
  1457  				So(os.IsNotExist(err), ShouldBeTrue)
  1458  
  1459  				err = fs.Mount(remoteConfig)
  1460  				So(err, ShouldBeNil)
  1461  
  1462  				bytes, err = ioutil.ReadFile(path)
  1463  				So(err, ShouldBeNil)
  1464  				So(string(bytes), ShouldEqual, line)
  1465  			})
  1466  
  1467  			SkipConvey("You can truncate an uncached file using an Open call and write to it", func() {
  1468  				f, err := os.OpenFile(path, os.O_TRUNC|os.O_WRONLY, 0644)
  1469  				So(err, ShouldBeNil)
  1470  				//*** this fails because it results in an fs.Open() call
  1471  				// where I see the os.O_WRONLY flag but not the os.O_TRUNC
  1472  				// flag
  1473  
  1474  				line := "trunc\n"
  1475  				_, err = f.WriteString(line)
  1476  				f.Close()
  1477  				So(err, ShouldBeNil)
  1478  
  1479  				bytes, err := ioutil.ReadFile(path)
  1480  				So(err, ShouldBeNil)
  1481  				So(string(bytes), ShouldEqual, line)
  1482  
  1483  				cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
  1484  				err = fs.Unmount()
  1485  				So(err, ShouldBeNil)
  1486  
  1487  				_, err = os.Stat(cachePath)
  1488  				So(err, ShouldNotBeNil)
  1489  				So(os.IsNotExist(err), ShouldBeTrue)
  1490  				_, err = os.Stat(path)
  1491  				So(err, ShouldNotBeNil)
  1492  				So(os.IsNotExist(err), ShouldBeTrue)
  1493  
  1494  				err = fs.Mount(remoteConfig)
  1495  				So(err, ShouldBeNil)
  1496  
  1497  				bytes, err = ioutil.ReadFile(path)
  1498  				So(err, ShouldBeNil)
  1499  				So(string(bytes), ShouldEqual, line)
  1500  			})
  1501  
  1502  			Convey("You can write to the mount point and immediately delete the file and get the correct listing", func() {
  1503  				entries, err := ioutil.ReadDir(mountPoint)
  1504  				So(err, ShouldBeNil)
  1505  				details := dirDetails(entries)
  1506  				subEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir", "write.test:file:11"}
  1507  				So(details, ShouldResemble, subEntries)
  1508  
  1509  				path2 := mountPoint + "/write.test2"
  1510  				b := []byte("write test2\n")
  1511  				err = ioutil.WriteFile(path2, b, 0644)
  1512  				So(err, ShouldBeNil)
  1513  
  1514  				// it's statable and listable
  1515  				_, err = os.Stat(path2)
  1516  				So(err, ShouldBeNil)
  1517  
  1518  				entries, err = ioutil.ReadDir(mountPoint)
  1519  				So(err, ShouldBeNil)
  1520  				details = dirDetails(entries)
  1521  				subEntries = []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir", "write.test2:file:12", "write.test:file:11"}
  1522  				So(details, ShouldResemble, subEntries)
  1523  
  1524  				// once deleted, it's no longer listed
  1525  				err = os.Remove(path2)
  1526  				So(err, ShouldBeNil)
  1527  
  1528  				_, err = os.Stat(path2)
  1529  				So(err, ShouldNotBeNil)
  1530  
  1531  				entries, err = ioutil.ReadDir(mountPoint)
  1532  				So(err, ShouldBeNil)
  1533  				details = dirDetails(entries)
  1534  				subEntries = []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir", "write.test:file:11"}
  1535  				So(details, ShouldResemble, subEntries)
  1536  
  1537  				// running unix ls reveals problems that ReadDir doesn't
  1538  				cmd := exec.Command("ls", "-alth", mountPoint)
  1539  				err = cmd.Run()
  1540  				So(err, ShouldBeNil)
  1541  			})
  1542  
  1543  			Convey("You can touch an uncached file", func() {
  1544  				info, err := os.Stat(path)
  1545  				So(err, ShouldBeNil)
  1546  				cmd := exec.Command("touch", "-d", "2006-01-02 15:04:05", path)
  1547  				err = cmd.Run()
  1548  				So(err, ShouldBeNil)
  1549  				info2, err := os.Stat(path)
  1550  				So(err, ShouldBeNil)
  1551  				So(info.ModTime().Unix(), ShouldNotAlmostEqual, info2.ModTime().Unix(), 62)
  1552  				So(info2.ModTime().String(), ShouldStartWith, "2006-01-02 15:04:05 +0000")
  1553  			})
  1554  
  1555  			Convey("You can immediately touch an uncached file", func() {
  1556  				cmd := exec.Command("touch", "-d", "2006-01-02 15:04:05", path)
  1557  				err := cmd.Run()
  1558  				So(err, ShouldBeNil)
  1559  
  1560  				// (looking at the contents of a subdir revealed a bug)
  1561  				entries, err := ioutil.ReadDir(mountPoint + "/sub")
  1562  				So(err, ShouldBeNil)
  1563  				details := dirDetails(entries)
  1564  				subEntries := []string{"deep:dir", "empty.file:file:0"}
  1565  				So(details, ShouldResemble, subEntries)
  1566  
  1567  				info, err := os.Stat(path)
  1568  				So(err, ShouldBeNil)
  1569  				So(info.ModTime().String(), ShouldStartWith, "2006-01-02 15:04:05 +0000")
  1570  			})
  1571  
  1572  			Convey("You can touch a cached file", func() {
  1573  				info, err := os.Stat(path)
  1574  				So(err, ShouldBeNil)
  1575  				_, err = ioutil.ReadFile(path)
  1576  				So(err, ShouldBeNil)
  1577  
  1578  				cmd := exec.Command("touch", "-d", "2006-01-02 15:04:05", path)
  1579  				err = cmd.Run()
  1580  				So(err, ShouldBeNil)
  1581  				info2, err := os.Stat(path)
  1582  				So(err, ShouldBeNil)
  1583  				So(info.ModTime().Unix(), ShouldNotAlmostEqual, info2.ModTime().Unix(), 62)
  1584  				So(info2.ModTime().String(), ShouldStartWith, "2006-01-02 15:04:05 +0000")
  1585  
  1586  				cmd = exec.Command("touch", "-d", "2007-01-02 15:04:05", path)
  1587  				err = cmd.Run()
  1588  				So(err, ShouldBeNil)
  1589  				info3, err := os.Stat(path)
  1590  				So(err, ShouldBeNil)
  1591  				So(info2.ModTime().Unix(), ShouldNotAlmostEqual, info3.ModTime().Unix(), 62)
  1592  				So(info3.ModTime().String(), ShouldStartWith, "2007-01-02 15:04:05 +0000")
  1593  			})
  1594  
  1595  			Convey("You can directly change the mtime on a cached file", func() {
  1596  				info, err := os.Stat(path)
  1597  				So(err, ShouldBeNil)
  1598  				_, err = ioutil.ReadFile(path)
  1599  				So(err, ShouldBeNil)
  1600  
  1601  				t := time.Now().Add(5 * time.Minute)
  1602  				err = os.Chtimes(path, t, t)
  1603  				So(err, ShouldBeNil)
  1604  				info2, err := os.Stat(path)
  1605  				So(err, ShouldBeNil)
  1606  				So(info.ModTime().Unix(), ShouldNotAlmostEqual, info2.ModTime().Unix(), 62)
  1607  				So(info2.ModTime().Unix(), ShouldAlmostEqual, t.Unix(), 2)
  1608  			})
  1609  
  1610  			Convey("But not an uncached one", func() {
  1611  				info, err := os.Stat(path)
  1612  				So(err, ShouldBeNil)
  1613  				t := time.Now().Add(5 * time.Minute)
  1614  				err = os.Chtimes(path, t, t)
  1615  				So(err, ShouldBeNil)
  1616  				info2, err := os.Stat(path)
  1617  				So(err, ShouldBeNil)
  1618  				So(info.ModTime().Unix(), ShouldAlmostEqual, info2.ModTime().Unix(), 62)
  1619  				So(info2.ModTime().Unix(), ShouldNotAlmostEqual, t.Unix(), 2)
  1620  			})
  1621  		})
  1622  
  1623  		Convey("You can immediately write in to a subdirectory", func() {
  1624  			path := mountPoint + "/sub/write.test"
  1625  			b := []byte("write test\n")
  1626  			err := ioutil.WriteFile(path, b, 0644)
  1627  			So(err, ShouldBeNil)
  1628  
  1629  			defer func() {
  1630  				err = os.Remove(path)
  1631  				So(err, ShouldBeNil)
  1632  			}()
  1633  
  1634  			// you can immediately read it back
  1635  			bytes, err := ioutil.ReadFile(path)
  1636  			So(err, ShouldBeNil)
  1637  			So(bytes, ShouldResemble, b)
  1638  
  1639  			// and it's statable and listable
  1640  			_, err = os.Stat(path)
  1641  			So(err, ShouldBeNil)
  1642  
  1643  			entries, err := ioutil.ReadDir(mountPoint + "/sub")
  1644  			So(err, ShouldBeNil)
  1645  			details := dirDetails(entries)
  1646  			subEntries := []string{"deep:dir", "empty.file:file:0", "write.test:file:11"}
  1647  			So(details, ShouldResemble, subEntries)
  1648  
  1649  			err = fs.Unmount()
  1650  			So(err, ShouldBeNil)
  1651  
  1652  			// remounting lets us read the file again - it actually got
  1653  			// uploaded
  1654  			err = fs.Mount(remoteConfig)
  1655  			So(err, ShouldBeNil)
  1656  
  1657  			bytes, err = ioutil.ReadFile(path)
  1658  			So(err, ShouldBeNil)
  1659  			So(bytes, ShouldResemble, b)
  1660  		})
  1661  
  1662  		Convey("You can write in to a subdirectory that has been previously listed", func() {
  1663  			entries, err := ioutil.ReadDir(mountPoint + "/sub")
  1664  			So(err, ShouldBeNil)
  1665  			details := dirDetails(entries)
  1666  			subEntries := []string{"deep:dir", "empty.file:file:0"}
  1667  			So(details, ShouldResemble, subEntries)
  1668  
  1669  			path := mountPoint + "/sub/write.test"
  1670  			b := []byte("write test\n")
  1671  			err = ioutil.WriteFile(path, b, 0644)
  1672  			So(err, ShouldBeNil)
  1673  
  1674  			defer func() {
  1675  				err = os.Remove(path)
  1676  				So(err, ShouldBeNil)
  1677  			}()
  1678  
  1679  			// you can immediately read it back
  1680  			bytes, err := ioutil.ReadFile(path)
  1681  			So(err, ShouldBeNil)
  1682  			So(bytes, ShouldResemble, b)
  1683  
  1684  			// and it's statable and listable
  1685  			_, err = os.Stat(path)
  1686  			So(err, ShouldBeNil)
  1687  
  1688  			entries, err = ioutil.ReadDir(mountPoint + "/sub")
  1689  			So(err, ShouldBeNil)
  1690  			details = dirDetails(entries)
  1691  			subEntries = []string{"deep:dir", "empty.file:file:0", "write.test:file:11"}
  1692  			So(details, ShouldResemble, subEntries)
  1693  
  1694  			err = fs.Unmount()
  1695  			So(err, ShouldBeNil)
  1696  
  1697  			// remounting lets us read the file again - it actually got
  1698  			// uploaded
  1699  			err = fs.Mount(remoteConfig)
  1700  			So(err, ShouldBeNil)
  1701  
  1702  			bytes, err = ioutil.ReadFile(path)
  1703  			So(err, ShouldBeNil)
  1704  			So(bytes, ShouldResemble, b)
  1705  		})
  1706  
  1707  		Convey("You can write in to a subdirectory and immediately delete the file and get the correct listing", func() {
  1708  			entries, err := ioutil.ReadDir(mountPoint + "/sub")
  1709  			So(err, ShouldBeNil)
  1710  			details := dirDetails(entries)
  1711  			subEntries := []string{"deep:dir", "empty.file:file:0"}
  1712  			So(details, ShouldResemble, subEntries)
  1713  
  1714  			path := mountPoint + "/sub/write.test"
  1715  			b := []byte("write test\n")
  1716  			err = ioutil.WriteFile(path, b, 0644)
  1717  			So(err, ShouldBeNil)
  1718  
  1719  			// it's statable and listable
  1720  			_, err = os.Stat(path)
  1721  			So(err, ShouldBeNil)
  1722  
  1723  			entries, err = ioutil.ReadDir(mountPoint + "/sub")
  1724  			So(err, ShouldBeNil)
  1725  			details = dirDetails(entries)
  1726  			subEntries = []string{"deep:dir", "empty.file:file:0", "write.test:file:11"}
  1727  			So(details, ShouldResemble, subEntries)
  1728  
  1729  			// once deleted, it's no longer listed
  1730  			err = os.Remove(path)
  1731  			So(err, ShouldBeNil)
  1732  
  1733  			_, err = os.Stat(path)
  1734  			So(err, ShouldNotBeNil)
  1735  
  1736  			entries, err = ioutil.ReadDir(mountPoint + "/sub")
  1737  			So(err, ShouldBeNil)
  1738  			details = dirDetails(entries)
  1739  			subEntries = []string{"deep:dir", "empty.file:file:0"}
  1740  			So(details, ShouldResemble, subEntries)
  1741  
  1742  			// running unix ls reveals problems that ReadDir doesn't
  1743  			cmd := exec.Command("ls", "-alth", mountPoint+"/sub")
  1744  			err = cmd.Run()
  1745  			So(err, ShouldBeNil)
  1746  		})
  1747  
  1748  		Convey("You can touch a non-existent file", func() {
  1749  			path := mountPoint + "/write.test"
  1750  			cmd := exec.Command("touch", path)
  1751  			err := cmd.Run()
  1752  			defer func() {
  1753  				err = os.Remove(path)
  1754  				So(err, ShouldBeNil)
  1755  			}()
  1756  			So(err, ShouldBeNil)
  1757  		})
  1758  
  1759  		Convey("You can write multiple files and they get uploaded in final mtime order", func() {
  1760  			path1 := mountPoint + "/write.test1"
  1761  			b := []byte("write test1\n")
  1762  			err := ioutil.WriteFile(path1, b, 0644)
  1763  			So(err, ShouldBeNil)
  1764  
  1765  			path2 := mountPoint + "/write.test2"
  1766  			b = []byte("write test2\n")
  1767  			err = ioutil.WriteFile(path2, b, 0644)
  1768  			So(err, ShouldBeNil)
  1769  
  1770  			path3 := mountPoint + "/write.test3"
  1771  			b = []byte("write test3\n")
  1772  			err = ioutil.WriteFile(path3, b, 0644)
  1773  			So(err, ShouldBeNil)
  1774  
  1775  			cmd := exec.Command("touch", "-d", "2006-01-02 15:04:05", path2)
  1776  			err = cmd.Run()
  1777  			So(err, ShouldBeNil)
  1778  
  1779  			t := time.Now().Add(5 * time.Minute)
  1780  			err = os.Chtimes(path1, t, t)
  1781  			So(err, ShouldBeNil)
  1782  
  1783  			err = fs.Unmount()
  1784  			So(err, ShouldBeNil)
  1785  
  1786  			defer func() {
  1787  				os.Remove(path1)
  1788  				os.Remove(path2)
  1789  				os.Remove(path3)
  1790  			}()
  1791  
  1792  			err = fs.Mount(remoteConfig)
  1793  			So(err, ShouldBeNil)
  1794  
  1795  			info1, err := os.Stat(path1)
  1796  			So(err, ShouldBeNil)
  1797  			info2, err := os.Stat(path2)
  1798  			So(err, ShouldBeNil)
  1799  			info3, err := os.Stat(path3)
  1800  			So(err, ShouldBeNil)
  1801  			So(info2.ModTime().Unix(), ShouldBeLessThanOrEqualTo, info3.ModTime().Unix())
  1802  			So(info3.ModTime().Unix(), ShouldBeLessThanOrEqualTo, info1.ModTime().Unix())
  1803  
  1804  			// *** unfortunately they only get second-resolution mtimes, and
  1805  			// they all get uploaded in the same second, so this isn't a very
  1806  			// good test... need uploads that take more than 1 second each...
  1807  		})
  1808  
  1809  		Convey("You can't create hard links", func() {
  1810  			source := mountPoint + "/numalphanum.txt"
  1811  			dest := mountPoint + "/link.hard"
  1812  			err := os.Link(source, dest)
  1813  			So(err, ShouldNotBeNil)
  1814  		})
  1815  
  1816  		Convey("You can create and use symbolic links", func() {
  1817  			source := mountPoint + "/numalphanum.txt"
  1818  			dest := mountPoint + "/link.soft"
  1819  			err := os.Symlink(source, dest)
  1820  			So(err, ShouldBeNil)
  1821  			bytes, err := ioutil.ReadFile(dest)
  1822  			So(err, ShouldBeNil)
  1823  			So(string(bytes), ShouldEqual, "1234567890abcdefghijklmnopqrstuvwxyz1234567890\n")
  1824  
  1825  			info, err := os.Lstat(dest)
  1826  			So(err, ShouldBeNil)
  1827  			So(info.Size(), ShouldEqual, 7)
  1828  
  1829  			d, err := os.Readlink(dest)
  1830  			So(err, ShouldBeNil)
  1831  			So(d, ShouldEqual, source)
  1832  
  1833  			Convey("But they're not uploaded", func() {
  1834  				err = fs.Unmount()
  1835  				So(err, ShouldBeNil)
  1836  				err = fs.Mount(remoteConfig)
  1837  				So(err, ShouldBeNil)
  1838  
  1839  				_, err = os.Stat(dest)
  1840  				So(err, ShouldNotBeNil)
  1841  			})
  1842  
  1843  			Convey("You can delete them", func() {
  1844  				err = os.Remove(dest)
  1845  				So(err, ShouldBeNil)
  1846  				_, err = os.Stat(dest)
  1847  				So(err, ShouldNotBeNil)
  1848  			})
  1849  		})
  1850  	})
  1851  
  1852  	Convey("You can mount with local file caching in an explicit location", t, func() {
  1853  		remoteConfig.CacheDir = cacheDir
  1854  		fs, err := New(cfg)
  1855  		So(err, ShouldBeNil)
  1856  
  1857  		err = fs.Mount(remoteConfig)
  1858  		So(err, ShouldBeNil)
  1859  
  1860  		defer func() {
  1861  			err = fs.Unmount()
  1862  			remoteConfig.CacheDir = ""
  1863  			So(err, ShouldBeNil)
  1864  		}()
  1865  
  1866  		path := mountPoint + "/numalphanum.txt"
  1867  		_, err = ioutil.ReadFile(path)
  1868  		So(err, ShouldBeNil)
  1869  
  1870  		cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("numalphanum.txt"))
  1871  		_, err = os.Stat(cachePath)
  1872  		So(err, ShouldBeNil)
  1873  		So(cachePath, ShouldStartWith, cacheDir)
  1874  
  1875  		Convey("Unmounting doesn't delete the cache", func() {
  1876  			err = fs.Unmount()
  1877  			So(err, ShouldBeNil)
  1878  
  1879  			_, err = os.Stat(cachePath)
  1880  			So(err, ShouldBeNil)
  1881  		})
  1882  
  1883  		Convey("You can read different parts of a file simultaneously from 1 mount, and it's only downloaded once", func() {
  1884  			init := mountPoint + "/numalphanum.txt"
  1885  			path := mountPoint + "/100k.lines"
  1886  
  1887  			// the first read takes longer than others, so read something
  1888  			// to "initialise" minio
  1889  			streamFile(init, 0)
  1890  
  1891  			// first get a reference for how long it takes to read the whole
  1892  			// thing
  1893  			t := time.Now()
  1894  			read, err := streamFile(path, 0)
  1895  			wt := time.Since(t)
  1896  			So(err, ShouldBeNil)
  1897  			So(read, ShouldEqual, 700000)
  1898  
  1899  			// sanity check that re-reading uses our cache
  1900  			t = time.Now()
  1901  			streamFile(path, 0)
  1902  			st := time.Since(t)
  1903  
  1904  			// should have completed in under 90% of the time
  1905  			et := time.Duration((wt.Nanoseconds()/100)*99) * time.Nanosecond
  1906  			So(st, ShouldBeLessThan, et)
  1907  
  1908  			// remount to clear the cache
  1909  			err = fs.Unmount()
  1910  			So(err, ShouldBeNil)
  1911  			err = fs.Mount(remoteConfig)
  1912  			So(err, ShouldBeNil)
  1913  			streamFile(init, 0)
  1914  
  1915  			// now read the whole file and half the file at the ~same time
  1916  			times := make(chan time.Duration, 2)
  1917  			errors := make(chan error, 2)
  1918  			streamer := func(offset, size int) {
  1919  				t2 := time.Now()
  1920  				thisRead, thisErr := streamFile(path, int64(offset))
  1921  				times <- time.Since(t2)
  1922  				if thisErr != nil {
  1923  					errors <- thisErr
  1924  					return
  1925  				}
  1926  				if thisRead != int64(size) {
  1927  					errors <- fmt.Errorf("did not read %d bytes for offset %d (%d)", size, offset, thisRead)
  1928  					return
  1929  				}
  1930  				errors <- nil
  1931  			}
  1932  
  1933  			t = time.Now()
  1934  			var wg sync.WaitGroup
  1935  			wg.Add(2)
  1936  			go func() {
  1937  				defer wg.Done()
  1938  				streamer(350000, 350000)
  1939  			}()
  1940  			go func() {
  1941  				defer wg.Done()
  1942  				streamer(0, 700000)
  1943  			}()
  1944  			wg.Wait()
  1945  			ot := time.Since(t)
  1946  
  1947  			// both should complete in not much more time than the slowest,
  1948  			// and that shouldn't be much slower than when reading alone
  1949  			// *** debugging shows that the file is only downloaded once,
  1950  			// but don't have a good way of proving that here
  1951  			So(<-errors, ShouldBeNil)
  1952  			So(<-errors, ShouldBeNil)
  1953  			pt1 := <-times
  1954  			pt2 := <-times
  1955  			var multiplier int64
  1956  			if runtime.NumCPU() == 1 {
  1957  				multiplier = 240
  1958  			} else {
  1959  				multiplier = 190
  1960  			}
  1961  			eto := time.Duration((int64(math.Max(float64(pt1.Nanoseconds()), float64(pt2.Nanoseconds())))/100)*multiplier) * time.Nanosecond
  1962  			// ets := time.Duration((wt.Nanoseconds()/100)*160) * time.Nanosecond
  1963  			// fmt.Printf("\nwt: %s, pt1: %s, pt2: %s, ot: %s, eto: %s, ets: %s\n", wt, pt1, pt2, ot, eto, ets)
  1964  			So(ot, ShouldBeLessThan, eto)
  1965  			// So(ot, ShouldBeLessThan, ets)
  1966  		})
  1967  
  1968  		Convey("You can read different files simultaneously from 1 mount", func() {
  1969  			init := mountPoint + "/numalphanum.txt"
  1970  			path1 := mountPoint + "/100k.lines"
  1971  			path2 := mountPoint + "/big.file"
  1972  
  1973  			streamFile(init, 0)
  1974  
  1975  			// first get a reference for how long it takes to read a certain
  1976  			// sized chunk of each file
  1977  			// t := time.Now()
  1978  			read, err := streamFile(path1, 0)
  1979  			// f1t := time.Since(t)
  1980  			So(err, ShouldBeNil)
  1981  			So(read, ShouldEqual, 700000)
  1982  
  1983  			// t = time.Now()
  1984  			read, err = streamFile(path2, int64(bigFileSize-700000))
  1985  			// f2t := time.Since(t)
  1986  			So(err, ShouldBeNil)
  1987  			So(read, ShouldEqual, 700000)
  1988  
  1989  			// remount to clear the cache
  1990  			err = fs.Unmount()
  1991  			So(err, ShouldBeNil)
  1992  			err = fs.Mount(remoteConfig)
  1993  			So(err, ShouldBeNil)
  1994  			streamFile(init, 0)
  1995  
  1996  			// now repeat reading them at the ~same time
  1997  			times := make(chan time.Duration, 2)
  1998  			errors := make(chan error, 2)
  1999  			streamer := func(path string, offset, size int) {
  2000  				t := time.Now()
  2001  				thisRead, thisErr := streamFile(path, int64(offset))
  2002  				times <- time.Since(t)
  2003  				if thisErr != nil {
  2004  					errors <- thisErr
  2005  					return
  2006  				}
  2007  				if thisRead != int64(size) {
  2008  					errors <- fmt.Errorf("did not read %d bytes of %s at offset %d (%d)", size, path, offset, thisRead)
  2009  					return
  2010  				}
  2011  				errors <- nil
  2012  			}
  2013  
  2014  			t := time.Now()
  2015  			var wg sync.WaitGroup
  2016  			wg.Add(2)
  2017  			go func() {
  2018  				defer wg.Done()
  2019  				streamer(path1, 0, 700000)
  2020  			}()
  2021  			go func() {
  2022  				defer wg.Done()
  2023  				streamer(path2, bigFileSize-700000, 700000)
  2024  			}()
  2025  			wg.Wait()
  2026  			ot := time.Since(t)
  2027  
  2028  			// each should have completed in less than 190% of the time
  2029  			// needed to read them sequentially, and both should have
  2030  			// completed in less than 110% of the slowest one
  2031  			So(<-errors, ShouldBeNil)
  2032  			So(<-errors, ShouldBeNil)
  2033  			pt1 := <-times
  2034  			pt2 := <-times
  2035  			// et1 := time.Duration((f1t.Nanoseconds()/100)*190) * time.Nanosecond
  2036  			// et2 := time.Duration((f2t.Nanoseconds()/100)*190) * time.Nanosecond
  2037  			var multiplier int64
  2038  			if bigFileSize > 10000000 {
  2039  				if runtime.NumCPU() == 1 {
  2040  					multiplier = 200
  2041  				} else {
  2042  					multiplier = 110
  2043  				}
  2044  			} else {
  2045  				if runtime.NumCPU() == 1 {
  2046  					multiplier = 350
  2047  				} else {
  2048  					multiplier = 250
  2049  				}
  2050  			}
  2051  			eto := time.Duration((int64(math.Max(float64(pt1.Nanoseconds()), float64(pt2.Nanoseconds())))/100)*multiplier) * time.Nanosecond
  2052  			// *** these timing tests are too unreliable when using minio server
  2053  			// So(pt1, ShouldBeLessThan, et1)
  2054  			// So(pt2, ShouldBeLessThan, et2)
  2055  			So(ot, ShouldBeLessThan, eto)
  2056  		})
  2057  	})
  2058  
  2059  	Convey("You can mount with local file caching in an explicit relative location", t, func() {
  2060  		remoteConfig.CacheDir = ".muxfys_test_cache_dir"
  2061  		fs, err := New(cfg)
  2062  		So(err, ShouldBeNil)
  2063  
  2064  		err = fs.Mount(remoteConfig)
  2065  		So(err, ShouldBeNil)
  2066  
  2067  		defer func() {
  2068  			err = fs.Unmount()
  2069  			So(err, ShouldBeNil)
  2070  			os.RemoveAll(remoteConfig.CacheDir)
  2071  			remoteConfig.CacheDir = ""
  2072  		}()
  2073  
  2074  		path := mountPoint + "/numalphanum.txt"
  2075  		_, err = ioutil.ReadFile(path)
  2076  		So(err, ShouldBeNil)
  2077  
  2078  		cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("numalphanum.txt"))
  2079  		_, err = os.Stat(cachePath)
  2080  		So(err, ShouldBeNil)
  2081  		cwd, _ := os.Getwd()
  2082  		So(cachePath, ShouldStartWith, filepath.Join(cwd, ".muxfys_test_cache_dir"))
  2083  
  2084  		Convey("Unmounting doesn't delete the cache", func() {
  2085  			err = fs.Unmount()
  2086  			So(err, ShouldBeNil)
  2087  
  2088  			_, err = os.Stat(cachePath)
  2089  			So(err, ShouldBeNil)
  2090  		})
  2091  	})
  2092  
  2093  	Convey("You can mount with local file caching relative to the home directory", t, func() {
  2094  		remoteConfig.CacheDir = "~/.wr_muxfys_test_cache_dir"
  2095  		fs, err := New(cfg)
  2096  		So(err, ShouldBeNil)
  2097  
  2098  		err = fs.Mount(remoteConfig)
  2099  		So(err, ShouldBeNil)
  2100  
  2101  		defer func() {
  2102  			err = fs.Unmount()
  2103  			remoteConfig.CacheDir = ""
  2104  			So(err, ShouldBeNil)
  2105  			os.RemoveAll(filepath.Join(os.Getenv("HOME"), ".wr_muxfys_test_cache_dir"))
  2106  		}()
  2107  
  2108  		path := mountPoint + "/numalphanum.txt"
  2109  		_, err = ioutil.ReadFile(path)
  2110  		So(err, ShouldBeNil)
  2111  
  2112  		cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("numalphanum.txt"))
  2113  		_, err = os.Stat(cachePath)
  2114  		So(err, ShouldBeNil)
  2115  
  2116  		So(cachePath, ShouldStartWith, filepath.Join(os.Getenv("HOME"), ".wr_muxfys_test_cache_dir"))
  2117  
  2118  		Convey("Unmounting doesn't delete the cache", func() {
  2119  			err = fs.Unmount()
  2120  			So(err, ShouldBeNil)
  2121  
  2122  			_, err = os.Stat(cachePath)
  2123  			So(err, ShouldBeNil)
  2124  		})
  2125  	})
  2126  
  2127  	Convey("You can mount with a relative mount point", t, func() {
  2128  		cfg.Mount = ".wr_muxfys_test_mount_dir"
  2129  		fs, err := New(cfg)
  2130  		So(err, ShouldBeNil)
  2131  
  2132  		err = fs.Mount(remoteConfig)
  2133  		So(err, ShouldBeNil)
  2134  
  2135  		defer func() {
  2136  			err = fs.Unmount()
  2137  			So(err, ShouldBeNil)
  2138  			os.RemoveAll(cfg.Mount)
  2139  			cfg.Mount = mountPoint
  2140  		}()
  2141  
  2142  		path := filepath.Join(cfg.Mount, "numalphanum.txt")
  2143  		_, err = ioutil.ReadFile(path)
  2144  		So(err, ShouldBeNil)
  2145  	})
  2146  
  2147  	Convey("You can mount with a ~/ mount point", t, func() {
  2148  		cfg.Mount = "~/.wr_muxfys_test_mount_dir"
  2149  		fs, err := New(cfg)
  2150  		So(err, ShouldBeNil)
  2151  
  2152  		err = fs.Mount(remoteConfig)
  2153  		So(err, ShouldBeNil)
  2154  
  2155  		defer func() {
  2156  			err = fs.Unmount()
  2157  			cfg.Mount = mountPoint
  2158  			So(err, ShouldBeNil)
  2159  			os.RemoveAll(filepath.Join(os.Getenv("HOME"), ".wr_muxfys_test_mount_dir"))
  2160  		}()
  2161  
  2162  		path := filepath.Join(os.Getenv("HOME"), ".wr_muxfys_test_mount_dir", "numalphanum.txt")
  2163  		_, err = ioutil.ReadFile(path)
  2164  		So(err, ShouldBeNil)
  2165  	})
  2166  
  2167  	Convey("You can mount with no defined mount point", t, func() {
  2168  		cfg.Mount = ""
  2169  		fs, err := New(cfg)
  2170  		So(err, ShouldBeNil)
  2171  
  2172  		err = fs.Mount(remoteConfig)
  2173  		So(err, ShouldBeNil)
  2174  
  2175  		defer func() {
  2176  			err = fs.Unmount()
  2177  			cfg.Mount = mountPoint
  2178  			So(err, ShouldBeNil)
  2179  			os.RemoveAll("mnt")
  2180  		}()
  2181  
  2182  		path := filepath.Join("mnt", "numalphanum.txt")
  2183  		_, err = ioutil.ReadFile(path)
  2184  		So(err, ShouldBeNil)
  2185  	})
  2186  
  2187  	Convey("You can't mount on a non-empty directory", t, func() {
  2188  		cfg.Mount = os.Getenv("HOME")
  2189  		_, err := New(cfg)
  2190  		defer func() {
  2191  			cfg.Mount = mountPoint
  2192  		}()
  2193  		So(err, ShouldNotBeNil)
  2194  		So(err.Error(), ShouldContainSubstring, "not empty")
  2195  	})
  2196  
  2197  	Convey("You can mount in write mode and not upload on unmount", t, func() {
  2198  		remoteConfig.Write = true
  2199  		fs, err := New(cfg)
  2200  		So(err, ShouldBeNil)
  2201  
  2202  		err = fs.Mount(remoteConfig)
  2203  		So(err, ShouldBeNil)
  2204  
  2205  		defer func() {
  2206  			err = fs.Unmount()
  2207  			remoteConfig.Write = false
  2208  			So(err, ShouldBeNil)
  2209  		}()
  2210  
  2211  		path := mountPoint + "/write.test"
  2212  		b := []byte("write test\n")
  2213  		err = ioutil.WriteFile(path, b, 0644)
  2214  		So(err, ShouldBeNil)
  2215  
  2216  		bytes, err := ioutil.ReadFile(path)
  2217  		So(err, ShouldBeNil)
  2218  		So(bytes, ShouldResemble, b)
  2219  
  2220  		cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test"))
  2221  		_, err = os.Stat(cachePath)
  2222  		So(err, ShouldBeNil)
  2223  
  2224  		// unmount without uploads
  2225  		err = fs.Unmount(true)
  2226  		So(err, ShouldBeNil)
  2227  
  2228  		_, err = os.Stat(cachePath)
  2229  		So(err, ShouldNotBeNil)
  2230  		So(os.IsNotExist(err), ShouldBeTrue)
  2231  		_, err = os.Stat(path)
  2232  		So(err, ShouldNotBeNil)
  2233  		So(os.IsNotExist(err), ShouldBeTrue)
  2234  
  2235  		// remounting reveals it did not get uploaded
  2236  		err = fs.Mount(remoteConfig)
  2237  		So(err, ShouldBeNil)
  2238  
  2239  		_, err = os.Stat(cachePath)
  2240  		So(err, ShouldNotBeNil)
  2241  		So(os.IsNotExist(err), ShouldBeTrue)
  2242  
  2243  		_, err = os.Stat(path)
  2244  		So(err, ShouldNotBeNil)
  2245  		So(os.IsNotExist(err), ShouldBeTrue)
  2246  	})
  2247  
  2248  	Convey("You can mount with verbose to get more logs", t, func() {
  2249  		origVerbose := cfg.Verbose
  2250  		cfg.Verbose = true
  2251  		defer func() {
  2252  			cfg.Verbose = origVerbose
  2253  		}()
  2254  		fs, err := New(cfg)
  2255  		So(err, ShouldBeNil)
  2256  
  2257  		err = fs.Mount(remoteConfig)
  2258  		So(err, ShouldBeNil)
  2259  
  2260  		_, err = ioutil.ReadDir(mountPoint)
  2261  		expectedErrorLog := ""
  2262  		if err != nil {
  2263  			expectedErrorLog = "ListEntries"
  2264  		}
  2265  
  2266  		err = fs.Unmount()
  2267  		So(err, ShouldBeNil)
  2268  
  2269  		logs := fs.Logs()
  2270  		So(logs, ShouldNotBeNil)
  2271  		var foundExpectedLog bool
  2272  		var foundErrorLog bool
  2273  		for _, log := range logs {
  2274  			if strings.Contains(log, "ListEntries") {
  2275  				foundExpectedLog = true
  2276  			}
  2277  			if expectedErrorLog != "" && strings.Contains(log, expectedErrorLog) {
  2278  				foundErrorLog = true
  2279  			}
  2280  		}
  2281  		So(foundExpectedLog, ShouldBeTrue)
  2282  		if expectedErrorLog != "" {
  2283  			So(foundErrorLog, ShouldBeTrue)
  2284  		}
  2285  	})
  2286  
  2287  	var bigFileGetTimeUncached time.Duration
  2288  	Convey("You can mount without local file caching", t, func() {
  2289  		remoteConfig.CacheData = false
  2290  		fs, errc := New(cfg)
  2291  		So(errc, ShouldBeNil)
  2292  
  2293  		errm := fs.Mount(remoteConfig)
  2294  		So(errm, ShouldBeNil)
  2295  
  2296  		defer func() {
  2297  			erru := fs.Unmount()
  2298  			So(erru, ShouldBeNil)
  2299  		}()
  2300  
  2301  		cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("big.file"))
  2302  		So(cachePath, ShouldBeBlank)
  2303  
  2304  		Convey("Listing mount directory and subdirs works", func() {
  2305  			s := time.Now()
  2306  			entries, err := ioutil.ReadDir(mountPoint)
  2307  			d := time.Since(s)
  2308  			So(err, ShouldBeNil)
  2309  
  2310  			details := dirDetails(entries)
  2311  			rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir"}
  2312  			So(details, ShouldResemble, rootEntries)
  2313  
  2314  			// test it twice in a row to make sure caching is ok
  2315  			s = time.Now()
  2316  			entries, err = ioutil.ReadDir(mountPoint)
  2317  			dc := time.Since(s)
  2318  			So(err, ShouldBeNil)
  2319  			So(dc.Nanoseconds(), ShouldBeLessThan, d.Nanoseconds()/4)
  2320  
  2321  			details = dirDetails(entries)
  2322  			So(details, ShouldResemble, rootEntries)
  2323  
  2324  			// test the sub directories
  2325  			entries, err = ioutil.ReadDir(mountPoint + "/sub")
  2326  			So(err, ShouldBeNil)
  2327  
  2328  			details = dirDetails(entries)
  2329  			So(details, ShouldResemble, []string{"deep:dir", "empty.file:file:0"})
  2330  
  2331  			entries, err = ioutil.ReadDir(mountPoint + "/sub/deep")
  2332  			So(err, ShouldBeNil)
  2333  
  2334  			details = dirDetails(entries)
  2335  			So(details, ShouldResemble, []string{"bar:file:4"})
  2336  
  2337  			if doRemoteTests {
  2338  				entries, err = ioutil.ReadDir(mountPoint + "/emptyDir")
  2339  				So(err, ShouldBeNil)
  2340  
  2341  				details = dirDetails(entries)
  2342  				So(len(details), ShouldEqual, 0)
  2343  			}
  2344  		})
  2345  
  2346  		Convey("You can immediately list a subdir", func() {
  2347  			entries, err := ioutil.ReadDir(mountPoint + "/sub")
  2348  			So(err, ShouldBeNil)
  2349  
  2350  			details := dirDetails(entries)
  2351  			So(details, ShouldResemble, []string{"deep:dir", "empty.file:file:0"})
  2352  		})
  2353  
  2354  		if doRemoteTests {
  2355  			Convey("You can immediately list an empty subdir", func() {
  2356  				entries, err := ioutil.ReadDir(mountPoint + "/emptyDir")
  2357  				So(err, ShouldBeNil)
  2358  
  2359  				details := dirDetails(entries)
  2360  				So(len(details), ShouldEqual, 0)
  2361  			})
  2362  		}
  2363  
  2364  		Convey("Trying to list a non-existent subdir fails as expected", func() {
  2365  			entries, err := ioutil.ReadDir(mountPoint + "/emptyDi")
  2366  			So(err, ShouldNotBeNil)
  2367  			So(os.IsNotExist(err), ShouldBeTrue)
  2368  			details := dirDetails(entries)
  2369  			So(len(details), ShouldEqual, 0)
  2370  		})
  2371  
  2372  		Convey("You can immediately list a deep subdir", func() {
  2373  			entries, err := ioutil.ReadDir(mountPoint + "/sub/deep")
  2374  			So(err, ShouldBeNil)
  2375  
  2376  			details := dirDetails(entries)
  2377  			So(details, ShouldResemble, []string{"bar:file:4"})
  2378  
  2379  			info, err := os.Stat(mountPoint + "/sub/deep/bar")
  2380  			So(err, ShouldBeNil)
  2381  			So(info.Name(), ShouldEqual, "bar")
  2382  			So(info.Size(), ShouldEqual, 4)
  2383  		})
  2384  
  2385  		Convey("You can immediately stat a deep file", func() {
  2386  			info, err := os.Stat(mountPoint + "/sub/deep/bar")
  2387  			So(err, ShouldBeNil)
  2388  			So(info.Name(), ShouldEqual, "bar")
  2389  			So(info.Size(), ShouldEqual, 4)
  2390  		})
  2391  
  2392  		Convey("You can read a whole file as well as parts of it by seeking", func() {
  2393  			path := mountPoint + "/100k.lines"
  2394  			read, err := streamFile(path, 0)
  2395  			So(err, ShouldBeNil)
  2396  			So(read, ShouldEqual, 700000)
  2397  
  2398  			read, err = streamFile(path, 350000)
  2399  			So(err, ShouldBeNil)
  2400  			So(read, ShouldEqual, 350000)
  2401  
  2402  			// make sure the contents are actually correct
  2403  			var expected bytes.Buffer
  2404  			for i := 1; i <= 100000; i++ {
  2405  				expected.WriteString(fmt.Sprintf("%06d\n", i))
  2406  			}
  2407  			bytes, err := ioutil.ReadFile(path)
  2408  			So(err, ShouldBeNil)
  2409  			So(string(bytes), ShouldEqual, expected.String())
  2410  		})
  2411  
  2412  		Convey("You can do random reads on large files", func() {
  2413  			// sanity check that it works on a small file
  2414  			path := mountPoint + "/numalphanum.txt"
  2415  			r, err := os.Open(path)
  2416  			So(err, ShouldBeNil)
  2417  			defer r.Close()
  2418  
  2419  			r.Seek(36, io.SeekStart)
  2420  
  2421  			b := make([]byte, 10)
  2422  			done, err := io.ReadFull(r, b)
  2423  			So(err, ShouldBeNil)
  2424  			So(done, ShouldEqual, 10)
  2425  			So(b, ShouldResemble, []byte("1234567890"))
  2426  
  2427  			r.Seek(10, io.SeekStart)
  2428  			b = make([]byte, 10)
  2429  			done, err = io.ReadFull(r, b)
  2430  			So(err, ShouldBeNil)
  2431  			So(done, ShouldEqual, 10)
  2432  			So(b, ShouldResemble, []byte("abcdefghij"))
  2433  
  2434  			// it also works on a big one
  2435  			path = mountPoint + "/100k.lines"
  2436  			rbig, err := os.Open(path)
  2437  			So(err, ShouldBeNil)
  2438  			defer rbig.Close()
  2439  
  2440  			rbig.Seek(350000, io.SeekStart)
  2441  			b = make([]byte, 6)
  2442  			done, err = io.ReadFull(rbig, b)
  2443  			So(err, ShouldBeNil)
  2444  			So(done, ShouldEqual, 6)
  2445  			So(b, ShouldResemble, []byte("050001"))
  2446  
  2447  			rbig.Seek(175000, io.SeekStart)
  2448  			b = make([]byte, 6)
  2449  			done, err = io.ReadFull(rbig, b)
  2450  			So(err, ShouldBeNil)
  2451  			So(done, ShouldEqual, 6)
  2452  			So(b, ShouldResemble, []byte("025001"))
  2453  		})
  2454  
  2455  		Convey("You can read a very big file", func() {
  2456  			ioutil.ReadDir(mountPoint) // we need to time reading the file, not stating it
  2457  			path := mountPoint + "/big.file"
  2458  			start := time.Now()
  2459  			read, err := streamFile(path, 0)
  2460  			bigFileGetTimeUncached = time.Since(start)
  2461  			// fmt.Printf("\n1G file read took %s cached vs %s uncached\n", bigFileGetTime, thisGetTime)
  2462  			So(err, ShouldBeNil)
  2463  			So(read, ShouldEqual, bigFileSize)
  2464  			So(math.Ceil(bigFileGetTimeUncached.Seconds()), ShouldBeLessThanOrEqualTo, math.Ceil(bigFileGetTime.Seconds())+2) // if it isn't, it's almost certainly a bug!
  2465  		})
  2466  
  2467  		Convey("You can read a very big file using cat", func() {
  2468  			ioutil.ReadDir(mountPoint)
  2469  			path := mountPoint + "/big.file"
  2470  			start := time.Now()
  2471  			err := exec.Command("cat", path).Run()
  2472  			So(err, ShouldBeNil)
  2473  			thisGetTime := time.Since(start)
  2474  			// fmt.Printf("\n1G file cat took %s \n", thisGetTime)
  2475  			So(err, ShouldBeNil)
  2476  
  2477  			// it should be about as quick as the above streamFile call: much
  2478  			// slower means a bug
  2479  			So(math.Ceil(thisGetTime.Seconds()), ShouldBeLessThanOrEqualTo, math.Ceil(bigFileGetTimeUncached.Seconds())+2)
  2480  		})
  2481  
  2482  		Convey("You can read a very big file using cp", func() {
  2483  			ioutil.ReadDir(mountPoint)
  2484  			path := mountPoint + "/big.file"
  2485  			start := time.Now()
  2486  			err := exec.Command("cp", path, "/dev/null").Run()
  2487  			So(err, ShouldBeNil)
  2488  			thisGetTime := time.Since(start)
  2489  			// fmt.Printf("\n1G file cp took %s \n", thisGetTime)
  2490  			So(err, ShouldBeNil)
  2491  
  2492  			// it should be about as quick as the above streamFile call: much
  2493  			// slower means a bug
  2494  			So(math.Ceil(thisGetTime.Seconds()), ShouldBeLessThanOrEqualTo, math.Ceil(bigFileGetTimeUncached.Seconds())+7)
  2495  		})
  2496  
  2497  		Convey("Trying to read a non-existent file fails as expected", func() {
  2498  			name := "non-existent.file"
  2499  			path := mountPoint + "/" + name
  2500  			_, err := streamFile(path, 0)
  2501  			So(err, ShouldNotBeNil)
  2502  			So(os.IsNotExist(err), ShouldBeTrue)
  2503  		})
  2504  
  2505  		Convey("Trying to read an externally deleted file fails as expected", func() {
  2506  			name := "non-existent.file"
  2507  			path := mountPoint + "/" + name
  2508  			// we'll hack fs to make it think non-existent.file does exist
  2509  			// so we can test the behaviour of a file getting deleted
  2510  			// externally
  2511  			fs.GetAttr("/", &fuse.Context{})
  2512  			fs.mapMutex.Lock()
  2513  			fs.addNewEntryToItsDir(name, fuse.S_IFREG)
  2514  			fs.files[name] = fs.files["big.file"]
  2515  			fs.fileToRemote[name] = fs.fileToRemote["big.file"]
  2516  			fs.mapMutex.Unlock()
  2517  			_, err := streamFile(path, 0)
  2518  			So(err, ShouldNotBeNil)
  2519  			So(os.IsNotExist(err), ShouldBeTrue)
  2520  		})
  2521  	})
  2522  
  2523  	Convey("You can mount in write mode without any caching", t, func() {
  2524  		remoteConfig.Write = true
  2525  		remoteConfig.CacheData = false
  2526  		fs, errc := New(cfg)
  2527  		So(errc, ShouldBeNil)
  2528  
  2529  		errm := fs.Mount(remoteConfig)
  2530  		So(errm, ShouldBeNil)
  2531  
  2532  		defer func() {
  2533  			erru := fs.Unmount()
  2534  			remoteConfig.Write = false
  2535  			So(erru, ShouldBeNil)
  2536  		}()
  2537  
  2538  		Convey("Trying to write works", func() {
  2539  			path := mountPoint + "/write.test"
  2540  			b := []byte("write test\n")
  2541  			err := ioutil.WriteFile(path, b, 0644)
  2542  			So(err, ShouldBeNil)
  2543  
  2544  			defer func() {
  2545  				err = os.Remove(path)
  2546  				So(err, ShouldBeNil)
  2547  			}()
  2548  
  2549  			// you can immediately read it back
  2550  			bytes, err := ioutil.ReadFile(path)
  2551  			So(err, ShouldBeNil)
  2552  			So(bytes, ShouldResemble, b)
  2553  
  2554  			// and it's statable and listable
  2555  			_, err = os.Stat(path)
  2556  			So(err, ShouldBeNil)
  2557  
  2558  			entries, err := ioutil.ReadDir(mountPoint)
  2559  			So(err, ShouldBeNil)
  2560  			details := dirDetails(entries)
  2561  			rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir", "write.test:file:11"}
  2562  			So(details, ShouldResemble, rootEntries)
  2563  
  2564  			err = fs.Unmount()
  2565  			So(err, ShouldBeNil)
  2566  
  2567  			_, err = os.Stat(path)
  2568  			So(err, ShouldNotBeNil)
  2569  			So(os.IsNotExist(err), ShouldBeTrue)
  2570  
  2571  			// remounting lets us read the file again - it actually got
  2572  			// uploaded
  2573  			err = fs.Mount(remoteConfig)
  2574  			So(err, ShouldBeNil)
  2575  
  2576  			bytes, err = ioutil.ReadFile(path)
  2577  			So(err, ShouldBeNil)
  2578  			So(bytes, ShouldResemble, b)
  2579  		})
  2580  
  2581  		Convey("You can upload empty files with cp", func() {
  2582  			path := mountPoint + "/touch.test"
  2583  			localPath := "touch.test"
  2584  			cmd := exec.Command("touch", localPath) // ("bash", "-c", "echo foo > touch.test")
  2585  			err := cmd.Run()
  2586  			defer func() {
  2587  				err = os.Remove(localPath)
  2588  				So(err, ShouldBeNil)
  2589  				err = os.Remove(path)
  2590  				So(err, ShouldBeNil)
  2591  			}()
  2592  			So(err, ShouldBeNil)
  2593  			cmd = exec.Command("cp", localPath, path)
  2594  			err = cmd.Run()
  2595  			So(err, ShouldBeNil)
  2596  
  2597  			<-time.After(1500 * time.Millisecond) // implementation for empty files does a final flush after 1s
  2598  
  2599  			_, err = os.Stat(path)
  2600  			So(err, ShouldBeNil)
  2601  
  2602  			err = fs.Unmount()
  2603  			So(err, ShouldBeNil)
  2604  
  2605  			_, err = os.Stat(path)
  2606  			So(err, ShouldNotBeNil)
  2607  			So(os.IsNotExist(err), ShouldBeTrue)
  2608  
  2609  			err = fs.Mount(remoteConfig)
  2610  			So(err, ShouldBeNil)
  2611  
  2612  			_, err = os.Stat(path)
  2613  			So(err, ShouldBeNil)
  2614  		})
  2615  
  2616  		Convey("You can create empty files with touch", func() {
  2617  			path := mountPoint + "/touch.test"
  2618  			cmd := exec.Command("touch", path)
  2619  			err := cmd.Run()
  2620  			defer func() {
  2621  				err = os.Remove(path)
  2622  				So(err, ShouldBeNil)
  2623  			}()
  2624  			So(err, ShouldBeNil)
  2625  
  2626  			<-time.After(1500 * time.Millisecond)
  2627  
  2628  			_, err = os.Stat(path)
  2629  			So(err, ShouldBeNil)
  2630  
  2631  			err = fs.Unmount()
  2632  			So(err, ShouldBeNil)
  2633  
  2634  			_, err = os.Stat(path)
  2635  			So(err, ShouldNotBeNil)
  2636  			So(os.IsNotExist(err), ShouldBeTrue)
  2637  
  2638  			err = fs.Mount(remoteConfig)
  2639  			So(err, ShouldBeNil)
  2640  
  2641  			_, err = os.Stat(path)
  2642  			So(err, ShouldBeNil)
  2643  		})
  2644  
  2645  		if bigFileSize > 10000000 {
  2646  			// *** -race flag isn't passed through to us, so can't limit this skip to just -race
  2647  			SkipConvey("Writing a very large file breaks machines with race detector on", func() {})
  2648  		} else {
  2649  			Convey("Writing a large file works", func() {
  2650  				// because minio-go currently uses a ~600MB bytes.Buffer (2x
  2651  				// allocation growth) during streaming upload, and dd itself creates
  2652  				// an input buffer of the size bs, we have to give dd a small bs and
  2653  				// increase the count instead. This way we don't run out of memory
  2654  				// even when bigFileSize is greater than physical memory on the
  2655  				// machine
  2656  
  2657  				path := mountPoint + "/write.test"
  2658  				err := exec.Command("dd", "if=/dev/zero", "of="+path, fmt.Sprintf("bs=%d", bigFileSize/1000), "count=1000").Run()
  2659  				So(err, ShouldBeNil)
  2660  
  2661  				defer func() {
  2662  					err = os.Remove(path)
  2663  					So(err, ShouldBeNil)
  2664  				}()
  2665  
  2666  				info, err := os.Stat(path)
  2667  				So(err, ShouldBeNil)
  2668  				expectedSize := (bigFileSize / 1000) * 1000
  2669  				So(info.Size(), ShouldEqual, expectedSize)
  2670  
  2671  				err = fs.Unmount()
  2672  				So(err, ShouldBeNil)
  2673  
  2674  				_, err = os.Stat(path)
  2675  				So(err, ShouldNotBeNil)
  2676  				So(os.IsNotExist(err), ShouldBeTrue)
  2677  
  2678  				err = fs.Mount(remoteConfig)
  2679  				So(err, ShouldBeNil)
  2680  
  2681  				info, err = os.Stat(path)
  2682  				So(err, ShouldBeNil)
  2683  				So(info.Size(), ShouldEqual, expectedSize)
  2684  			})
  2685  		}
  2686  
  2687  		Convey("Given a local directory", func() {
  2688  			mvDir := filepath.Join(tmpdir, "mvtest2")
  2689  			mvSubDir := filepath.Join(mvDir, "mvsubdir")
  2690  			errf := os.MkdirAll(mvSubDir, os.FileMode(0700))
  2691  			So(errf, ShouldBeNil)
  2692  			mvFile := filepath.Join(mvSubDir, "file")
  2693  			mvBytes := []byte("mvfile\n")
  2694  			errf = ioutil.WriteFile(mvFile, mvBytes, 0644)
  2695  			So(errf, ShouldBeNil)
  2696  			errf = ioutil.WriteFile(filepath.Join(mvDir, "a.file"), mvBytes, 0644)
  2697  			So(errf, ShouldBeNil)
  2698  
  2699  			Convey("You can mv it to the mount point", func() {
  2700  				mountDir := filepath.Join(mountPoint, "mvtest2")
  2701  				dest := filepath.Join(mountDir, "mvsubdir", "file")
  2702  				dest2 := filepath.Join(mountDir, "a.file")
  2703  
  2704  				cmd := exec.Command("sh", "-c", fmt.Sprintf("mv %s %s/", mvDir, mountPoint))
  2705  				out, err := cmd.CombinedOutput()
  2706  				So(err, ShouldBeNil)
  2707  				So(len(out), ShouldEqual, 0)
  2708  
  2709  				bytes, err := ioutil.ReadFile(dest)
  2710  				So(err, ShouldBeNil)
  2711  				So(bytes, ShouldResemble, mvBytes)
  2712  
  2713  				bytes, err = ioutil.ReadFile(dest2)
  2714  				So(err, ShouldBeNil)
  2715  				So(bytes, ShouldResemble, mvBytes)
  2716  
  2717  				_, err = os.Stat(filepath.Join(mountPoint, "mvtest2", "mvsubdir"))
  2718  				So(err, ShouldBeNil)
  2719  				_, err = os.Stat(mountDir)
  2720  				So(err, ShouldBeNil)
  2721  
  2722  				err = fs.Unmount()
  2723  				So(err, ShouldBeNil)
  2724  				err = fs.Mount(remoteConfig)
  2725  				So(err, ShouldBeNil)
  2726  
  2727  				defer func() {
  2728  					err = os.Remove(dest)
  2729  					So(err, ShouldBeNil)
  2730  					err = os.Remove(dest2)
  2731  					So(err, ShouldBeNil)
  2732  				}()
  2733  
  2734  				bytes, err = ioutil.ReadFile(dest)
  2735  				So(err, ShouldBeNil)
  2736  				So(bytes, ShouldResemble, mvBytes)
  2737  			})
  2738  
  2739  			Convey("You can mv its contents to the mount point", func() {
  2740  				dest := filepath.Join(mountPoint, "mvsubdir", "file")
  2741  				dest2 := filepath.Join(mountPoint, "a.file")
  2742  
  2743  				cmd := exec.Command("sh", "-c", fmt.Sprintf("mv %s/* %s/", mvDir, mountPoint))
  2744  				err := cmd.Run()
  2745  				So(err, ShouldBeNil)
  2746  
  2747  				bytes, err := ioutil.ReadFile(dest)
  2748  				So(err, ShouldBeNil)
  2749  				So(bytes, ShouldResemble, mvBytes)
  2750  
  2751  				err = fs.Unmount()
  2752  				So(err, ShouldBeNil)
  2753  				err = fs.Mount(remoteConfig)
  2754  				So(err, ShouldBeNil)
  2755  
  2756  				defer func() {
  2757  					err = os.Remove(dest)
  2758  					So(err, ShouldBeNil)
  2759  					err = os.Remove(dest2)
  2760  					So(err, ShouldBeNil)
  2761  				}()
  2762  
  2763  				bytes, err = ioutil.ReadFile(dest)
  2764  				So(err, ShouldBeNil)
  2765  				So(bytes, ShouldResemble, mvBytes)
  2766  			})
  2767  		})
  2768  	})
  2769  
  2770  	Convey("You can mount multiple remotes on the same mount point", t, func() {
  2771  		remoteConfig.CacheData = true
  2772  		manualConfig2 := &S3Config{
  2773  			Target:    target + "/sub",
  2774  			AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"),
  2775  			SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
  2776  		}
  2777  		accessor, errn = NewS3Accessor(manualConfig2)
  2778  		So(errn, ShouldBeNil)
  2779  		remoteConfig2 := &RemoteConfig{
  2780  			Accessor:  accessor,
  2781  			CacheData: true,
  2782  		}
  2783  
  2784  		fs, err := New(cfg)
  2785  		So(err, ShouldBeNil)
  2786  
  2787  		err = fs.Mount(remoteConfig, remoteConfig2)
  2788  		So(err, ShouldBeNil)
  2789  
  2790  		defer func() {
  2791  			err = fs.Unmount()
  2792  			So(err, ShouldBeNil)
  2793  		}()
  2794  
  2795  		Convey("Listing mount directory and subdirs works", func() {
  2796  			s := time.Now()
  2797  			entries, err := ioutil.ReadDir(mountPoint)
  2798  			d := time.Since(s)
  2799  			So(err, ShouldBeNil)
  2800  
  2801  			details := dirDetails(entries)
  2802  			rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "deep:dir", "empty.file:file:0", "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir"}
  2803  			So(details, ShouldResemble, rootEntries)
  2804  
  2805  			// test it twice in a row to make sure caching is ok
  2806  			s = time.Now()
  2807  			entries, err = ioutil.ReadDir(mountPoint)
  2808  			dc := time.Since(s)
  2809  			So(err, ShouldBeNil)
  2810  			So(dc.Nanoseconds(), ShouldBeLessThan, d.Nanoseconds()/4)
  2811  
  2812  			details = dirDetails(entries)
  2813  			So(details, ShouldResemble, rootEntries)
  2814  
  2815  			// test the sub directories
  2816  			entries, err = ioutil.ReadDir(mountPoint + "/sub")
  2817  			So(err, ShouldBeNil)
  2818  
  2819  			details = dirDetails(entries)
  2820  			So(details, ShouldResemble, []string{"deep:dir", "empty.file:file:0"})
  2821  
  2822  			entries, err = ioutil.ReadDir(mountPoint + "/sub/deep")
  2823  			So(err, ShouldBeNil)
  2824  
  2825  			details = dirDetails(entries)
  2826  			So(details, ShouldResemble, []string{"bar:file:4"})
  2827  
  2828  			// and the sub dirs of the second mount
  2829  			entries, err = ioutil.ReadDir(mountPoint + "/deep")
  2830  			So(err, ShouldBeNil)
  2831  
  2832  			details = dirDetails(entries)
  2833  			So(details, ShouldResemble, []string{"bar:file:4"})
  2834  		})
  2835  
  2836  		Convey("You can immediately list a subdir", func() {
  2837  			entries, err := ioutil.ReadDir(mountPoint + "/sub")
  2838  			So(err, ShouldBeNil)
  2839  
  2840  			details := dirDetails(entries)
  2841  			So(details, ShouldResemble, []string{"deep:dir", "empty.file:file:0"})
  2842  		})
  2843  
  2844  		Convey("You can immediately list a subdir of the second remote", func() {
  2845  			entries, err := ioutil.ReadDir(mountPoint + "/deep")
  2846  			So(err, ShouldBeNil)
  2847  
  2848  			details := dirDetails(entries)
  2849  			So(details, ShouldResemble, []string{"bar:file:4"})
  2850  
  2851  			info, err := os.Stat(mountPoint + "/deep/bar")
  2852  			So(err, ShouldBeNil)
  2853  			So(info.Name(), ShouldEqual, "bar")
  2854  			So(info.Size(), ShouldEqual, 4)
  2855  		})
  2856  
  2857  		Convey("You can immediately list a deep subdir", func() {
  2858  			entries, err := ioutil.ReadDir(mountPoint + "/sub/deep")
  2859  			So(err, ShouldBeNil)
  2860  
  2861  			details := dirDetails(entries)
  2862  			So(details, ShouldResemble, []string{"bar:file:4"})
  2863  
  2864  			info, err := os.Stat(mountPoint + "/sub/deep/bar")
  2865  			So(err, ShouldBeNil)
  2866  			So(info.Name(), ShouldEqual, "bar")
  2867  			So(info.Size(), ShouldEqual, 4)
  2868  		})
  2869  
  2870  		Convey("You can read files from both remotes", func() {
  2871  			path := mountPoint + "/deep/bar"
  2872  			bytes, err := ioutil.ReadFile(path)
  2873  			So(err, ShouldBeNil)
  2874  			So(string(bytes), ShouldEqual, "foo\n")
  2875  
  2876  			path = mountPoint + "/sub/deep/bar"
  2877  			bytes, err = ioutil.ReadFile(path)
  2878  			So(err, ShouldBeNil)
  2879  			So(string(bytes), ShouldEqual, "foo\n")
  2880  		})
  2881  	})
  2882  
  2883  	Convey("You can mount the bucket directly", t, func() {
  2884  		u, errp := url.Parse(target)
  2885  		So(errp, ShouldBeNil)
  2886  		parts := strings.Split(u.Path[1:], "/")
  2887  		manualConfig.Target = u.Scheme + "://" + u.Host + "/" + parts[0]
  2888  		accessor, errn = NewS3Accessor(manualConfig)
  2889  		So(errn, ShouldBeNil)
  2890  		remoteConfig := &RemoteConfig{
  2891  			Accessor:  accessor,
  2892  			CacheData: true,
  2893  			Write:     false,
  2894  		}
  2895  		fs, errc := New(cfg)
  2896  		So(errc, ShouldBeNil)
  2897  
  2898  		errm := fs.Mount(remoteConfig)
  2899  		So(errm, ShouldBeNil)
  2900  
  2901  		defer func() {
  2902  			erru := fs.Unmount()
  2903  			So(erru, ShouldBeNil)
  2904  		}()
  2905  
  2906  		Convey("Listing bucket directory works", func() {
  2907  			entries, err := ioutil.ReadDir(mountPoint)
  2908  			So(err, ShouldBeNil)
  2909  
  2910  			details := dirDetails(entries)
  2911  			So(details, ShouldContain, path.Join(parts[1:]...)+":dir")
  2912  		})
  2913  
  2914  		Convey("You can't mount more than once at a time", func() {
  2915  			err := fs.Mount(remoteConfig)
  2916  			So(err, ShouldNotBeNil)
  2917  		})
  2918  	})
  2919  
  2920  	Convey("For non-existent paths...", t, func() {
  2921  		manualConfig.Target = target + "/nonexistent/subdir"
  2922  		accessor, errn = NewS3Accessor(manualConfig)
  2923  		So(errn, ShouldBeNil)
  2924  
  2925  		Convey("You can mount them read-only", func() {
  2926  			remoteConfig := &RemoteConfig{
  2927  				Accessor:  accessor,
  2928  				CacheData: true,
  2929  				Write:     false,
  2930  			}
  2931  			fs, err := New(cfg)
  2932  			So(err, ShouldBeNil)
  2933  
  2934  			err = fs.Mount(remoteConfig)
  2935  			So(err, ShouldBeNil)
  2936  			defer fs.Unmount()
  2937  
  2938  			Convey("Getting the contents of the dir works", func() {
  2939  				entries, err := ioutil.ReadDir(mountPoint)
  2940  				So(err, ShouldBeNil)
  2941  				So(len(entries), ShouldEqual, 0)
  2942  			})
  2943  		})
  2944  
  2945  		Convey("You can mount them writeable and writes work", func() {
  2946  			remoteConfig := &RemoteConfig{
  2947  				Accessor:  accessor,
  2948  				CacheData: false,
  2949  				Write:     true,
  2950  			}
  2951  			fs, err := New(cfg)
  2952  			So(err, ShouldBeNil)
  2953  
  2954  			err = fs.Mount(remoteConfig)
  2955  			defer func() {
  2956  				err = fs.Unmount()
  2957  				So(err, ShouldBeNil)
  2958  			}()
  2959  			So(err, ShouldBeNil)
  2960  
  2961  			entries, err := ioutil.ReadDir(mountPoint)
  2962  			So(err, ShouldBeNil)
  2963  			So(len(entries), ShouldEqual, 0)
  2964  
  2965  			path := mountPoint + "/write.test"
  2966  			b := []byte("write test\n")
  2967  			err = ioutil.WriteFile(path, b, 0644)
  2968  			So(err, ShouldBeNil)
  2969  
  2970  			defer func() {
  2971  				err = os.Remove(path)
  2972  				So(err, ShouldBeNil)
  2973  			}()
  2974  
  2975  			// you can immediately read it back
  2976  			bytes, err := ioutil.ReadFile(path)
  2977  			So(err, ShouldBeNil)
  2978  			So(bytes, ShouldResemble, b)
  2979  
  2980  			// and it's statable and listable
  2981  			_, err = os.Stat(path)
  2982  			So(err, ShouldBeNil)
  2983  
  2984  			entries, err = ioutil.ReadDir(mountPoint)
  2985  			So(err, ShouldBeNil)
  2986  			details := dirDetails(entries)
  2987  			rootEntries := []string{"write.test:file:11"}
  2988  			So(details, ShouldResemble, rootEntries)
  2989  
  2990  			err = fs.Unmount()
  2991  			So(err, ShouldBeNil)
  2992  
  2993  			_, err = os.Stat(path)
  2994  			So(err, ShouldNotBeNil)
  2995  			So(os.IsNotExist(err), ShouldBeTrue)
  2996  
  2997  			// remounting lets us read the file again - it actually got
  2998  			// uploaded
  2999  			err = fs.Mount(remoteConfig)
  3000  			So(err, ShouldBeNil)
  3001  
  3002  			bytes, err = ioutil.ReadFile(path)
  3003  			So(err, ShouldBeNil)
  3004  			So(bytes, ShouldResemble, b)
  3005  		})
  3006  
  3007  		Convey("You can mount a non-empty dir for reading and a non-existent dir for writing", func() {
  3008  			remoteConfig := &RemoteConfig{
  3009  				Accessor:  accessor,
  3010  				CacheData: false,
  3011  				Write:     true,
  3012  			}
  3013  
  3014  			manualConfig2 := &S3Config{
  3015  				Target:    target,
  3016  				AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"),
  3017  				SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
  3018  			}
  3019  			accessor, errn = NewS3Accessor(manualConfig2)
  3020  			So(errn, ShouldBeNil)
  3021  			remoteConfig2 := &RemoteConfig{
  3022  				Accessor:  accessor,
  3023  				CacheData: false,
  3024  			}
  3025  
  3026  			fs, err := New(cfg)
  3027  			So(err, ShouldBeNil)
  3028  
  3029  			err = fs.Mount(remoteConfig2, remoteConfig)
  3030  			defer func() {
  3031  				err = fs.Unmount()
  3032  				So(err, ShouldBeNil)
  3033  			}()
  3034  			So(err, ShouldBeNil)
  3035  
  3036  			entries, err := ioutil.ReadDir(mountPoint)
  3037  			So(err, ShouldBeNil)
  3038  			So(len(entries), ShouldEqual, 5)
  3039  
  3040  			details := dirDetails(entries)
  3041  			rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir"}
  3042  			So(details, ShouldResemble, rootEntries)
  3043  
  3044  			Convey("Reads and writes work", func() {
  3045  				source := mountPoint + "/numalphanum.txt"
  3046  				dest := mountPoint + "/write.test"
  3047  				err := exec.Command("cp", source, dest).Run()
  3048  				So(err, ShouldBeNil)
  3049  
  3050  				defer func() {
  3051  					err = os.Remove(dest)
  3052  					So(err, ShouldBeNil)
  3053  				}()
  3054  
  3055  				// you can immediately read it back
  3056  				bytes, err := ioutil.ReadFile(dest)
  3057  				So(err, ShouldBeNil)
  3058  				b := []byte("1234567890abcdefghijklmnopqrstuvwxyz1234567890\n")
  3059  				So(bytes, ShouldResemble, b)
  3060  
  3061  				// and it's statable and listable
  3062  				_, err = os.Stat(dest)
  3063  				So(err, ShouldBeNil)
  3064  
  3065  				entries, err = ioutil.ReadDir(mountPoint)
  3066  				So(err, ShouldBeNil)
  3067  				details := dirDetails(entries)
  3068  				rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir", "write.test:file:47"}
  3069  				So(details, ShouldResemble, rootEntries)
  3070  
  3071  				err = fs.Unmount()
  3072  				So(err, ShouldBeNil)
  3073  
  3074  				_, err = os.Stat(dest)
  3075  				So(err, ShouldNotBeNil)
  3076  				So(os.IsNotExist(err), ShouldBeTrue)
  3077  
  3078  				// remounting lets us read the file again - it actually got
  3079  				// uploaded
  3080  				err = fs.Mount(remoteConfig2, remoteConfig)
  3081  				So(err, ShouldBeNil)
  3082  
  3083  				bytes, err = ioutil.ReadFile(dest)
  3084  				So(err, ShouldBeNil)
  3085  				So(bytes, ShouldResemble, b)
  3086  			})
  3087  		})
  3088  	})
  3089  
  3090  	if strings.HasPrefix(target, "https://cog.sanger.ac.uk") {
  3091  		Convey("You can mount a public bucket", t, func() {
  3092  			manualConfig.Target = "https://cog.sanger.ac.uk/npg-repository"
  3093  			accessor, errn = NewS3Accessor(manualConfig)
  3094  			So(errn, ShouldBeNil)
  3095  			remoteConfig := &RemoteConfig{
  3096  				Accessor:  accessor,
  3097  				CacheData: true,
  3098  				Write:     false,
  3099  			}
  3100  			fs, err := New(cfg)
  3101  			So(err, ShouldBeNil)
  3102  
  3103  			err = fs.Mount(remoteConfig)
  3104  			So(err, ShouldBeNil)
  3105  
  3106  			defer func() {
  3107  				err = fs.Unmount()
  3108  				So(err, ShouldBeNil)
  3109  			}()
  3110  
  3111  			Convey("Listing mount directory works", func() {
  3112  				entries, errr := ioutil.ReadDir(mountPoint)
  3113  				So(errr, ShouldBeNil)
  3114  
  3115  				details := dirDetails(entries)
  3116  				So(details, ShouldContain, "cram_cache:dir")
  3117  				So(details, ShouldContain, "references:dir")
  3118  			})
  3119  
  3120  			Convey("You can immediately stat deep files", func() {
  3121  				fasta := mountPoint + "/references/Homo_sapiens/GRCh38_full_analysis_set_plus_decoy_hla/all/fasta/Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla"
  3122  				// _, err := os.Stat(fasta + ".fa") //*** temp removed
  3123  				// So(err, ShouldBeNil)
  3124  				_, err = os.Stat(fasta + ".fa.alt")
  3125  				So(err, ShouldBeNil)
  3126  				_, err = os.Stat(fasta + ".fa.fai")
  3127  				So(err, ShouldBeNil)
  3128  				_, err = os.Stat(fasta + ".dict")
  3129  				So(err, ShouldBeNil)
  3130  			})
  3131  		})
  3132  
  3133  		Convey("You can mount a public bucket at a deep path", t, func() {
  3134  			manualConfig.Target = "https://cog.sanger.ac.uk/npg-repository/references/Homo_sapiens/GRCh38_full_analysis_set_plus_decoy_hla/all/fasta"
  3135  			accessor, errn = NewS3Accessor(manualConfig)
  3136  			So(errn, ShouldBeNil)
  3137  			remoteConfig := &RemoteConfig{
  3138  				Accessor:  accessor,
  3139  				CacheData: true,
  3140  				Write:     false,
  3141  			}
  3142  			fs, err := New(cfg)
  3143  			So(err, ShouldBeNil)
  3144  
  3145  			err = fs.Mount(remoteConfig)
  3146  			So(err, ShouldBeNil)
  3147  
  3148  			defer func() {
  3149  				err = fs.Unmount()
  3150  				So(err, ShouldBeNil)
  3151  			}()
  3152  
  3153  			Convey("Listing mount directory works", func() {
  3154  				entries, errr := ioutil.ReadDir(mountPoint)
  3155  				So(errr, ShouldBeNil)
  3156  
  3157  				details := dirDetails(entries)
  3158  				So(details, ShouldContain, "Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla.dict:file:756744")
  3159  			})
  3160  
  3161  			Convey("You can immediately stat files within", func() {
  3162  				fasta := mountPoint + "/Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla"
  3163  				// _, err := os.Stat(fasta + ".fa")
  3164  				// So(err, ShouldBeNil)
  3165  				_, err = os.Stat(fasta + ".fa.alt")
  3166  				So(err, ShouldBeNil)
  3167  				_, err = os.Stat(fasta + ".fa.fai")
  3168  				So(err, ShouldBeNil)
  3169  				_, err = os.Stat(fasta + ".dict")
  3170  				So(err, ShouldBeNil)
  3171  			})
  3172  		})
  3173  
  3174  		Convey("You can multiplex different buckets", t, func() {
  3175  			manualConfig2 := &S3Config{
  3176  				Target:    target + "/sub",
  3177  				AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"),
  3178  				SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
  3179  			}
  3180  			accessor2, err := NewS3Accessor(manualConfig2)
  3181  			So(err, ShouldBeNil)
  3182  			remoteConfig2 := &RemoteConfig{
  3183  				Accessor:  accessor2,
  3184  				CacheData: false,
  3185  			}
  3186  
  3187  			manualConfig.Target = "https://cog.sanger.ac.uk/npg-repository/references/Homo_sapiens/GRCh38_full_analysis_set_plus_decoy_hla/all/fasta"
  3188  			accessor, err = NewS3Accessor(manualConfig)
  3189  			So(err, ShouldBeNil)
  3190  			remoteConfig := &RemoteConfig{
  3191  				Accessor:  accessor,
  3192  				CacheData: true,
  3193  				Write:     false,
  3194  			}
  3195  
  3196  			fs, err := New(cfg)
  3197  			So(err, ShouldBeNil)
  3198  
  3199  			err = fs.Mount(remoteConfig, remoteConfig2)
  3200  			So(err, ShouldBeNil)
  3201  
  3202  			defer func() {
  3203  				err = fs.Unmount()
  3204  				So(err, ShouldBeNil)
  3205  			}()
  3206  
  3207  			Convey("Listing mount directory works", func() {
  3208  				entries, err := ioutil.ReadDir(mountPoint)
  3209  				So(err, ShouldBeNil)
  3210  
  3211  				details := dirDetails(entries)
  3212  				So(details, ShouldContain, "Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla.dict:file:756744")
  3213  				So(details, ShouldContain, "empty.file:file:0")
  3214  			})
  3215  
  3216  			Convey("You can immediately stat files within", func() {
  3217  				_, err := os.Stat(mountPoint + "/Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla.dict")
  3218  				So(err, ShouldBeNil)
  3219  				_, err = os.Stat(mountPoint + "/empty.file")
  3220  				So(err, ShouldBeNil)
  3221  			})
  3222  		})
  3223  
  3224  		Convey("You can mount a public bucket with blank credentials", t, func() {
  3225  			manualConfig.Target = "https://cog.sanger.ac.uk/npg-repository"
  3226  			manualConfig.AccessKey = ""
  3227  			manualConfig.SecretKey = ""
  3228  			accessor, errn = NewS3Accessor(manualConfig)
  3229  			So(errn, ShouldBeNil)
  3230  			remoteConfig := &RemoteConfig{
  3231  				Accessor:  accessor,
  3232  				CacheData: true,
  3233  				Write:     false,
  3234  			}
  3235  
  3236  			fs, err := New(cfg)
  3237  			So(err, ShouldBeNil)
  3238  
  3239  			err = fs.Mount(remoteConfig)
  3240  			So(err, ShouldBeNil)
  3241  
  3242  			defer func() {
  3243  				err = fs.Unmount()
  3244  				So(err, ShouldBeNil)
  3245  			}()
  3246  
  3247  			Convey("Listing mount directory works", func() {
  3248  				entries, err := ioutil.ReadDir(mountPoint)
  3249  				So(err, ShouldBeNil)
  3250  
  3251  				details := dirDetails(entries)
  3252  				So(details, ShouldContain, "cram_cache:dir")
  3253  				So(details, ShouldContain, "references:dir")
  3254  			})
  3255  		})
  3256  	}
  3257  }
  3258  
  3259  func dirDetails(entries []os.FileInfo) []string {
  3260  	details := make([]string, 0, len(entries))
  3261  	for _, entry := range entries {
  3262  		info := entry.Name()
  3263  		if entry.IsDir() {
  3264  			info += ":dir"
  3265  		} else {
  3266  			info += fmt.Sprintf(":file:%d", entry.Size())
  3267  		}
  3268  		details = append(details, info)
  3269  	}
  3270  	sort.Slice(details, func(i, j int) bool { return details[i] < details[j] })
  3271  	return details
  3272  }
  3273  
  3274  func streamFile(src string, seek int64) (read int64, err error) {
  3275  	r, err := os.Open(src)
  3276  	if err != nil {
  3277  		return read, err
  3278  	}
  3279  	if seek > 0 {
  3280  		r.Seek(seek, io.SeekStart)
  3281  	}
  3282  	read, err = stream(r)
  3283  	r.Close()
  3284  	return read, err
  3285  }
  3286  
  3287  func stream(r io.Reader) (read int64, err error) {
  3288  	br := bufio.NewReader(r)
  3289  	b := make([]byte, 1000)
  3290  	for {
  3291  		done, rerr := br.Read(b)
  3292  		if rerr != nil {
  3293  			if rerr != io.EOF {
  3294  				err = rerr
  3295  			}
  3296  			break
  3297  		}
  3298  		read += int64(done)
  3299  	}
  3300  	return read, err
  3301  }