github.com/VertebrateResequencing/muxfys@v3.0.5+incompatible/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/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, int(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, int(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  		if bigFileSize > 10000000 {
  2582  			// *** -race flag isn't passed through to us, so can't limit this skip to just -race
  2583  			SkipConvey("Writing a very large file breaks machines with race detector on", func() {})
  2584  		} else {
  2585  			Convey("Writing a large file works", func() {
  2586  				// because minio-go currently uses a ~600MB bytes.Buffer (2x
  2587  				// allocation growth) during streaming upload, and dd itself creates
  2588  				// an input buffer of the size bs, we have to give dd a small bs and
  2589  				// increase the count instead. This way we don't run out of memory
  2590  				// even when bigFileSize is greater than physical memory on the
  2591  				// machine
  2592  
  2593  				path := mountPoint + "/write.test"
  2594  				err := exec.Command("dd", "if=/dev/zero", "of="+path, fmt.Sprintf("bs=%d", bigFileSize/1000), "count=1000").Run()
  2595  				So(err, ShouldBeNil)
  2596  
  2597  				defer func() {
  2598  					err = os.Remove(path)
  2599  					So(err, ShouldBeNil)
  2600  				}()
  2601  
  2602  				info, err := os.Stat(path)
  2603  				So(err, ShouldBeNil)
  2604  				expectedSize := (bigFileSize / 1000) * 1000
  2605  				So(info.Size(), ShouldEqual, expectedSize)
  2606  
  2607  				err = fs.Unmount()
  2608  				So(err, ShouldBeNil)
  2609  
  2610  				_, err = os.Stat(path)
  2611  				So(err, ShouldNotBeNil)
  2612  				So(os.IsNotExist(err), ShouldBeTrue)
  2613  
  2614  				err = fs.Mount(remoteConfig)
  2615  				So(err, ShouldBeNil)
  2616  
  2617  				info, err = os.Stat(path)
  2618  				So(err, ShouldBeNil)
  2619  				So(info.Size(), ShouldEqual, expectedSize)
  2620  			})
  2621  		}
  2622  
  2623  		Convey("Given a local directory", func() {
  2624  			mvDir := filepath.Join(tmpdir, "mvtest2")
  2625  			mvSubDir := filepath.Join(mvDir, "mvsubdir")
  2626  			errf := os.MkdirAll(mvSubDir, os.FileMode(0700))
  2627  			So(errf, ShouldBeNil)
  2628  			mvFile := filepath.Join(mvSubDir, "file")
  2629  			mvBytes := []byte("mvfile\n")
  2630  			errf = ioutil.WriteFile(mvFile, mvBytes, 0644)
  2631  			So(errf, ShouldBeNil)
  2632  			errf = ioutil.WriteFile(filepath.Join(mvDir, "a.file"), mvBytes, 0644)
  2633  			So(errf, ShouldBeNil)
  2634  
  2635  			Convey("You can mv it to the mount point", func() {
  2636  				mountDir := filepath.Join(mountPoint, "mvtest2")
  2637  				dest := filepath.Join(mountDir, "mvsubdir", "file")
  2638  				dest2 := filepath.Join(mountDir, "a.file")
  2639  
  2640  				cmd := exec.Command("sh", "-c", fmt.Sprintf("mv %s %s/", mvDir, mountPoint))
  2641  				out, err := cmd.CombinedOutput()
  2642  				So(err, ShouldBeNil)
  2643  				So(len(out), ShouldEqual, 0)
  2644  
  2645  				bytes, err := ioutil.ReadFile(dest)
  2646  				So(err, ShouldBeNil)
  2647  				So(bytes, ShouldResemble, mvBytes)
  2648  
  2649  				bytes, err = ioutil.ReadFile(dest2)
  2650  				So(err, ShouldBeNil)
  2651  				So(bytes, ShouldResemble, mvBytes)
  2652  
  2653  				_, err = os.Stat(filepath.Join(mountPoint, "mvtest2", "mvsubdir"))
  2654  				So(err, ShouldBeNil)
  2655  				_, err = os.Stat(mountDir)
  2656  				So(err, ShouldBeNil)
  2657  
  2658  				err = fs.Unmount()
  2659  				So(err, ShouldBeNil)
  2660  				err = fs.Mount(remoteConfig)
  2661  				So(err, ShouldBeNil)
  2662  
  2663  				defer func() {
  2664  					err = os.Remove(dest)
  2665  					So(err, ShouldBeNil)
  2666  					err = os.Remove(dest2)
  2667  					So(err, ShouldBeNil)
  2668  				}()
  2669  
  2670  				bytes, err = ioutil.ReadFile(dest)
  2671  				So(err, ShouldBeNil)
  2672  				So(bytes, ShouldResemble, mvBytes)
  2673  			})
  2674  
  2675  			Convey("You can mv its contents to the mount point", func() {
  2676  				dest := filepath.Join(mountPoint, "mvsubdir", "file")
  2677  				dest2 := filepath.Join(mountPoint, "a.file")
  2678  
  2679  				cmd := exec.Command("sh", "-c", fmt.Sprintf("mv %s/* %s/", mvDir, mountPoint))
  2680  				err := cmd.Run()
  2681  				So(err, ShouldBeNil)
  2682  
  2683  				bytes, err := ioutil.ReadFile(dest)
  2684  				So(err, ShouldBeNil)
  2685  				So(bytes, ShouldResemble, mvBytes)
  2686  
  2687  				err = fs.Unmount()
  2688  				So(err, ShouldBeNil)
  2689  				err = fs.Mount(remoteConfig)
  2690  				So(err, ShouldBeNil)
  2691  
  2692  				defer func() {
  2693  					err = os.Remove(dest)
  2694  					So(err, ShouldBeNil)
  2695  					err = os.Remove(dest2)
  2696  					So(err, ShouldBeNil)
  2697  				}()
  2698  
  2699  				bytes, err = ioutil.ReadFile(dest)
  2700  				So(err, ShouldBeNil)
  2701  				So(bytes, ShouldResemble, mvBytes)
  2702  			})
  2703  		})
  2704  	})
  2705  
  2706  	Convey("You can mount multiple remotes on the same mount point", t, func() {
  2707  		remoteConfig.CacheData = true
  2708  		manualConfig2 := &S3Config{
  2709  			Target:    target + "/sub",
  2710  			AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"),
  2711  			SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
  2712  		}
  2713  		accessor, errn = NewS3Accessor(manualConfig2)
  2714  		So(errn, ShouldBeNil)
  2715  		remoteConfig2 := &RemoteConfig{
  2716  			Accessor:  accessor,
  2717  			CacheData: true,
  2718  		}
  2719  
  2720  		fs, err := New(cfg)
  2721  		So(err, ShouldBeNil)
  2722  
  2723  		err = fs.Mount(remoteConfig, remoteConfig2)
  2724  		So(err, ShouldBeNil)
  2725  
  2726  		defer func() {
  2727  			err = fs.Unmount()
  2728  			So(err, ShouldBeNil)
  2729  		}()
  2730  
  2731  		Convey("Listing mount directory and subdirs works", func() {
  2732  			s := time.Now()
  2733  			entries, err := ioutil.ReadDir(mountPoint)
  2734  			d := time.Since(s)
  2735  			So(err, ShouldBeNil)
  2736  
  2737  			details := dirDetails(entries)
  2738  			rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "deep:dir", "empty.file:file:0", "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir"}
  2739  			So(details, ShouldResemble, rootEntries)
  2740  
  2741  			// test it twice in a row to make sure caching is ok
  2742  			s = time.Now()
  2743  			entries, err = ioutil.ReadDir(mountPoint)
  2744  			dc := time.Since(s)
  2745  			So(err, ShouldBeNil)
  2746  			So(dc.Nanoseconds(), ShouldBeLessThan, d.Nanoseconds()/4)
  2747  
  2748  			details = dirDetails(entries)
  2749  			So(details, ShouldResemble, rootEntries)
  2750  
  2751  			// test the sub directories
  2752  			entries, err = ioutil.ReadDir(mountPoint + "/sub")
  2753  			So(err, ShouldBeNil)
  2754  
  2755  			details = dirDetails(entries)
  2756  			So(details, ShouldResemble, []string{"deep:dir", "empty.file:file:0"})
  2757  
  2758  			entries, err = ioutil.ReadDir(mountPoint + "/sub/deep")
  2759  			So(err, ShouldBeNil)
  2760  
  2761  			details = dirDetails(entries)
  2762  			So(details, ShouldResemble, []string{"bar:file:4"})
  2763  
  2764  			// and the sub dirs of the second mount
  2765  			entries, err = ioutil.ReadDir(mountPoint + "/deep")
  2766  			So(err, ShouldBeNil)
  2767  
  2768  			details = dirDetails(entries)
  2769  			So(details, ShouldResemble, []string{"bar:file:4"})
  2770  		})
  2771  
  2772  		Convey("You can immediately list a subdir", func() {
  2773  			entries, err := ioutil.ReadDir(mountPoint + "/sub")
  2774  			So(err, ShouldBeNil)
  2775  
  2776  			details := dirDetails(entries)
  2777  			So(details, ShouldResemble, []string{"deep:dir", "empty.file:file:0"})
  2778  		})
  2779  
  2780  		Convey("You can immediately list a subdir of the second remote", func() {
  2781  			entries, err := ioutil.ReadDir(mountPoint + "/deep")
  2782  			So(err, ShouldBeNil)
  2783  
  2784  			details := dirDetails(entries)
  2785  			So(details, ShouldResemble, []string{"bar:file:4"})
  2786  
  2787  			info, err := os.Stat(mountPoint + "/deep/bar")
  2788  			So(err, ShouldBeNil)
  2789  			So(info.Name(), ShouldEqual, "bar")
  2790  			So(info.Size(), ShouldEqual, 4)
  2791  		})
  2792  
  2793  		Convey("You can immediately list a deep subdir", func() {
  2794  			entries, err := ioutil.ReadDir(mountPoint + "/sub/deep")
  2795  			So(err, ShouldBeNil)
  2796  
  2797  			details := dirDetails(entries)
  2798  			So(details, ShouldResemble, []string{"bar:file:4"})
  2799  
  2800  			info, err := os.Stat(mountPoint + "/sub/deep/bar")
  2801  			So(err, ShouldBeNil)
  2802  			So(info.Name(), ShouldEqual, "bar")
  2803  			So(info.Size(), ShouldEqual, 4)
  2804  		})
  2805  
  2806  		Convey("You can read files from both remotes", func() {
  2807  			path := mountPoint + "/deep/bar"
  2808  			bytes, err := ioutil.ReadFile(path)
  2809  			So(err, ShouldBeNil)
  2810  			So(string(bytes), ShouldEqual, "foo\n")
  2811  
  2812  			path = mountPoint + "/sub/deep/bar"
  2813  			bytes, err = ioutil.ReadFile(path)
  2814  			So(err, ShouldBeNil)
  2815  			So(string(bytes), ShouldEqual, "foo\n")
  2816  		})
  2817  	})
  2818  
  2819  	Convey("You can mount the bucket directly", t, func() {
  2820  		u, errp := url.Parse(target)
  2821  		So(errp, ShouldBeNil)
  2822  		parts := strings.Split(u.Path[1:], "/")
  2823  		manualConfig.Target = u.Scheme + "://" + u.Host + "/" + parts[0]
  2824  		accessor, errn = NewS3Accessor(manualConfig)
  2825  		So(errn, ShouldBeNil)
  2826  		remoteConfig := &RemoteConfig{
  2827  			Accessor:  accessor,
  2828  			CacheData: true,
  2829  			Write:     false,
  2830  		}
  2831  		fs, errc := New(cfg)
  2832  		So(errc, ShouldBeNil)
  2833  
  2834  		errm := fs.Mount(remoteConfig)
  2835  		So(errm, ShouldBeNil)
  2836  
  2837  		defer func() {
  2838  			erru := fs.Unmount()
  2839  			So(erru, ShouldBeNil)
  2840  		}()
  2841  
  2842  		Convey("Listing bucket directory works", func() {
  2843  			entries, err := ioutil.ReadDir(mountPoint)
  2844  			So(err, ShouldBeNil)
  2845  
  2846  			details := dirDetails(entries)
  2847  			So(details, ShouldContain, path.Join(parts[1:]...)+":dir")
  2848  		})
  2849  
  2850  		Convey("You can't mount more than once at a time", func() {
  2851  			err := fs.Mount(remoteConfig)
  2852  			So(err, ShouldNotBeNil)
  2853  		})
  2854  	})
  2855  
  2856  	Convey("For non-existent paths...", t, func() {
  2857  		manualConfig.Target = target + "/nonexistent/subdir"
  2858  		accessor, errn = NewS3Accessor(manualConfig)
  2859  		So(errn, ShouldBeNil)
  2860  
  2861  		Convey("You can mount them read-only", func() {
  2862  			remoteConfig := &RemoteConfig{
  2863  				Accessor:  accessor,
  2864  				CacheData: true,
  2865  				Write:     false,
  2866  			}
  2867  			fs, err := New(cfg)
  2868  			So(err, ShouldBeNil)
  2869  
  2870  			err = fs.Mount(remoteConfig)
  2871  			So(err, ShouldBeNil)
  2872  			defer fs.Unmount()
  2873  
  2874  			Convey("Getting the contents of the dir works", func() {
  2875  				entries, err := ioutil.ReadDir(mountPoint)
  2876  				So(err, ShouldBeNil)
  2877  				So(len(entries), ShouldEqual, 0)
  2878  			})
  2879  		})
  2880  
  2881  		Convey("You can mount them writeable and writes work", func() {
  2882  			remoteConfig := &RemoteConfig{
  2883  				Accessor:  accessor,
  2884  				CacheData: false,
  2885  				Write:     true,
  2886  			}
  2887  			fs, err := New(cfg)
  2888  			So(err, ShouldBeNil)
  2889  
  2890  			err = fs.Mount(remoteConfig)
  2891  			defer func() {
  2892  				err = fs.Unmount()
  2893  				So(err, ShouldBeNil)
  2894  			}()
  2895  			So(err, ShouldBeNil)
  2896  
  2897  			entries, err := ioutil.ReadDir(mountPoint)
  2898  			So(err, ShouldBeNil)
  2899  			So(len(entries), ShouldEqual, 0)
  2900  
  2901  			path := mountPoint + "/write.test"
  2902  			b := []byte("write test\n")
  2903  			err = ioutil.WriteFile(path, b, 0644)
  2904  			So(err, ShouldBeNil)
  2905  
  2906  			defer func() {
  2907  				err = os.Remove(path)
  2908  				So(err, ShouldBeNil)
  2909  			}()
  2910  
  2911  			// you can immediately read it back
  2912  			bytes, err := ioutil.ReadFile(path)
  2913  			So(err, ShouldBeNil)
  2914  			So(bytes, ShouldResemble, b)
  2915  
  2916  			// and it's statable and listable
  2917  			_, err = os.Stat(path)
  2918  			So(err, ShouldBeNil)
  2919  
  2920  			entries, err = ioutil.ReadDir(mountPoint)
  2921  			So(err, ShouldBeNil)
  2922  			details := dirDetails(entries)
  2923  			rootEntries := []string{"write.test:file:11"}
  2924  			So(details, ShouldResemble, rootEntries)
  2925  
  2926  			err = fs.Unmount()
  2927  			So(err, ShouldBeNil)
  2928  
  2929  			_, err = os.Stat(path)
  2930  			So(err, ShouldNotBeNil)
  2931  			So(os.IsNotExist(err), ShouldBeTrue)
  2932  
  2933  			// remounting lets us read the file again - it actually got
  2934  			// uploaded
  2935  			err = fs.Mount(remoteConfig)
  2936  			So(err, ShouldBeNil)
  2937  
  2938  			bytes, err = ioutil.ReadFile(path)
  2939  			So(err, ShouldBeNil)
  2940  			So(bytes, ShouldResemble, b)
  2941  		})
  2942  
  2943  		Convey("You can mount a non-empty dir for reading and a non-existent dir for writing", func() {
  2944  			remoteConfig := &RemoteConfig{
  2945  				Accessor:  accessor,
  2946  				CacheData: false,
  2947  				Write:     true,
  2948  			}
  2949  
  2950  			manualConfig2 := &S3Config{
  2951  				Target:    target,
  2952  				AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"),
  2953  				SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
  2954  			}
  2955  			accessor, errn = NewS3Accessor(manualConfig2)
  2956  			So(errn, ShouldBeNil)
  2957  			remoteConfig2 := &RemoteConfig{
  2958  				Accessor:  accessor,
  2959  				CacheData: false,
  2960  			}
  2961  
  2962  			fs, err := New(cfg)
  2963  			So(err, ShouldBeNil)
  2964  
  2965  			err = fs.Mount(remoteConfig2, remoteConfig)
  2966  			defer func() {
  2967  				err = fs.Unmount()
  2968  				So(err, ShouldBeNil)
  2969  			}()
  2970  			So(err, ShouldBeNil)
  2971  
  2972  			entries, err := ioutil.ReadDir(mountPoint)
  2973  			So(err, ShouldBeNil)
  2974  			So(len(entries), ShouldEqual, 5)
  2975  
  2976  			details := dirDetails(entries)
  2977  			rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir"}
  2978  			So(details, ShouldResemble, rootEntries)
  2979  
  2980  			Convey("Reads and writes work", func() {
  2981  				source := mountPoint + "/numalphanum.txt"
  2982  				dest := mountPoint + "/write.test"
  2983  				err := exec.Command("cp", source, dest).Run()
  2984  				So(err, ShouldBeNil)
  2985  
  2986  				defer func() {
  2987  					err = os.Remove(dest)
  2988  					So(err, ShouldBeNil)
  2989  				}()
  2990  
  2991  				// you can immediately read it back
  2992  				bytes, err := ioutil.ReadFile(dest)
  2993  				So(err, ShouldBeNil)
  2994  				b := []byte("1234567890abcdefghijklmnopqrstuvwxyz1234567890\n")
  2995  				So(bytes, ShouldResemble, b)
  2996  
  2997  				// and it's statable and listable
  2998  				_, err = os.Stat(dest)
  2999  				So(err, ShouldBeNil)
  3000  
  3001  				entries, err = ioutil.ReadDir(mountPoint)
  3002  				So(err, ShouldBeNil)
  3003  				details := dirDetails(entries)
  3004  				rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir", "write.test:file:47"}
  3005  				So(details, ShouldResemble, rootEntries)
  3006  
  3007  				err = fs.Unmount()
  3008  				So(err, ShouldBeNil)
  3009  
  3010  				_, err = os.Stat(dest)
  3011  				So(err, ShouldNotBeNil)
  3012  				So(os.IsNotExist(err), ShouldBeTrue)
  3013  
  3014  				// remounting lets us read the file again - it actually got
  3015  				// uploaded
  3016  				err = fs.Mount(remoteConfig2, remoteConfig)
  3017  				So(err, ShouldBeNil)
  3018  
  3019  				bytes, err = ioutil.ReadFile(dest)
  3020  				So(err, ShouldBeNil)
  3021  				So(bytes, ShouldResemble, b)
  3022  			})
  3023  		})
  3024  	})
  3025  
  3026  	if strings.HasPrefix(target, "https://cog.sanger.ac.uk") {
  3027  		Convey("You can mount a public bucket", t, func() {
  3028  			manualConfig.Target = "https://cog.sanger.ac.uk/npg-repository"
  3029  			accessor, errn = NewS3Accessor(manualConfig)
  3030  			So(errn, ShouldBeNil)
  3031  			remoteConfig := &RemoteConfig{
  3032  				Accessor:  accessor,
  3033  				CacheData: true,
  3034  				Write:     false,
  3035  			}
  3036  			fs, err := New(cfg)
  3037  			So(err, ShouldBeNil)
  3038  
  3039  			err = fs.Mount(remoteConfig)
  3040  			So(err, ShouldBeNil)
  3041  
  3042  			defer func() {
  3043  				err = fs.Unmount()
  3044  				So(err, ShouldBeNil)
  3045  			}()
  3046  
  3047  			Convey("Listing mount directory works", func() {
  3048  				entries, errr := ioutil.ReadDir(mountPoint)
  3049  				So(errr, ShouldBeNil)
  3050  
  3051  				details := dirDetails(entries)
  3052  				So(details, ShouldContain, "cram_cache:dir")
  3053  				So(details, ShouldContain, "references:dir")
  3054  			})
  3055  
  3056  			Convey("You can immediately stat deep files", func() {
  3057  				fasta := mountPoint + "/references/Homo_sapiens/GRCh38_full_analysis_set_plus_decoy_hla/all/fasta/Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla"
  3058  				// _, err := os.Stat(fasta + ".fa") //*** temp removed
  3059  				// So(err, ShouldBeNil)
  3060  				_, err = os.Stat(fasta + ".fa.alt")
  3061  				So(err, ShouldBeNil)
  3062  				_, err = os.Stat(fasta + ".fa.fai")
  3063  				So(err, ShouldBeNil)
  3064  				_, err = os.Stat(fasta + ".dict")
  3065  				So(err, ShouldBeNil)
  3066  			})
  3067  		})
  3068  
  3069  		Convey("You can mount a public bucket at a deep path", t, func() {
  3070  			manualConfig.Target = "https://cog.sanger.ac.uk/npg-repository/references/Homo_sapiens/GRCh38_full_analysis_set_plus_decoy_hla/all/fasta"
  3071  			accessor, errn = NewS3Accessor(manualConfig)
  3072  			So(errn, ShouldBeNil)
  3073  			remoteConfig := &RemoteConfig{
  3074  				Accessor:  accessor,
  3075  				CacheData: true,
  3076  				Write:     false,
  3077  			}
  3078  			fs, err := New(cfg)
  3079  			So(err, ShouldBeNil)
  3080  
  3081  			err = fs.Mount(remoteConfig)
  3082  			So(err, ShouldBeNil)
  3083  
  3084  			defer func() {
  3085  				err = fs.Unmount()
  3086  				So(err, ShouldBeNil)
  3087  			}()
  3088  
  3089  			Convey("Listing mount directory works", func() {
  3090  				entries, errr := ioutil.ReadDir(mountPoint)
  3091  				So(errr, ShouldBeNil)
  3092  
  3093  				details := dirDetails(entries)
  3094  				So(details, ShouldContain, "Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla.dict:file:756744")
  3095  			})
  3096  
  3097  			Convey("You can immediately stat files within", func() {
  3098  				fasta := mountPoint + "/Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla"
  3099  				// _, err := os.Stat(fasta + ".fa")
  3100  				// So(err, ShouldBeNil)
  3101  				_, err = os.Stat(fasta + ".fa.alt")
  3102  				So(err, ShouldBeNil)
  3103  				_, err = os.Stat(fasta + ".fa.fai")
  3104  				So(err, ShouldBeNil)
  3105  				_, err = os.Stat(fasta + ".dict")
  3106  				So(err, ShouldBeNil)
  3107  			})
  3108  		})
  3109  
  3110  		Convey("You can multiplex different buckets", t, func() {
  3111  			manualConfig2 := &S3Config{
  3112  				Target:    target + "/sub",
  3113  				AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"),
  3114  				SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
  3115  			}
  3116  			accessor2, err := NewS3Accessor(manualConfig2)
  3117  			So(err, ShouldBeNil)
  3118  			remoteConfig2 := &RemoteConfig{
  3119  				Accessor:  accessor2,
  3120  				CacheData: false,
  3121  			}
  3122  
  3123  			manualConfig.Target = "https://cog.sanger.ac.uk/npg-repository/references/Homo_sapiens/GRCh38_full_analysis_set_plus_decoy_hla/all/fasta"
  3124  			accessor, err = NewS3Accessor(manualConfig)
  3125  			So(err, ShouldBeNil)
  3126  			remoteConfig := &RemoteConfig{
  3127  				Accessor:  accessor,
  3128  				CacheData: true,
  3129  				Write:     false,
  3130  			}
  3131  
  3132  			fs, err := New(cfg)
  3133  			So(err, ShouldBeNil)
  3134  
  3135  			err = fs.Mount(remoteConfig, remoteConfig2)
  3136  			So(err, ShouldBeNil)
  3137  
  3138  			defer func() {
  3139  				err = fs.Unmount()
  3140  				So(err, ShouldBeNil)
  3141  			}()
  3142  
  3143  			Convey("Listing mount directory works", func() {
  3144  				entries, err := ioutil.ReadDir(mountPoint)
  3145  				So(err, ShouldBeNil)
  3146  
  3147  				details := dirDetails(entries)
  3148  				So(details, ShouldContain, "Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla.dict:file:756744")
  3149  				So(details, ShouldContain, "empty.file:file:0")
  3150  			})
  3151  
  3152  			Convey("You can immediately stat files within", func() {
  3153  				_, err := os.Stat(mountPoint + "/Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla.dict")
  3154  				So(err, ShouldBeNil)
  3155  				_, err = os.Stat(mountPoint + "/empty.file")
  3156  				So(err, ShouldBeNil)
  3157  			})
  3158  		})
  3159  
  3160  		Convey("You can mount a public bucket with blank credentials", t, func() {
  3161  			manualConfig.Target = "https://cog.sanger.ac.uk/npg-repository"
  3162  			manualConfig.AccessKey = ""
  3163  			manualConfig.SecretKey = ""
  3164  			accessor, errn = NewS3Accessor(manualConfig)
  3165  			So(errn, ShouldBeNil)
  3166  			remoteConfig := &RemoteConfig{
  3167  				Accessor:  accessor,
  3168  				CacheData: true,
  3169  				Write:     false,
  3170  			}
  3171  
  3172  			fs, err := New(cfg)
  3173  			So(err, ShouldBeNil)
  3174  
  3175  			err = fs.Mount(remoteConfig)
  3176  			So(err, ShouldBeNil)
  3177  
  3178  			defer func() {
  3179  				err = fs.Unmount()
  3180  				So(err, ShouldBeNil)
  3181  			}()
  3182  
  3183  			Convey("Listing mount directory works", func() {
  3184  				entries, err := ioutil.ReadDir(mountPoint)
  3185  				So(err, ShouldBeNil)
  3186  
  3187  				details := dirDetails(entries)
  3188  				So(details, ShouldContain, "cram_cache:dir")
  3189  				So(details, ShouldContain, "references:dir")
  3190  			})
  3191  		})
  3192  	}
  3193  }
  3194  
  3195  func dirDetails(entries []os.FileInfo) []string {
  3196  	var details []string
  3197  	for _, entry := range entries {
  3198  		info := entry.Name()
  3199  		if entry.IsDir() {
  3200  			info += ":dir"
  3201  		} else {
  3202  			info += fmt.Sprintf(":file:%d", entry.Size())
  3203  		}
  3204  		details = append(details, info)
  3205  	}
  3206  	sort.Slice(details, func(i, j int) bool { return details[i] < details[j] })
  3207  	return details
  3208  }
  3209  
  3210  func streamFile(src string, seek int64) (read int64, err error) {
  3211  	r, err := os.Open(src)
  3212  	if err != nil {
  3213  		return read, err
  3214  	}
  3215  	if seek > 0 {
  3216  		r.Seek(seek, io.SeekStart)
  3217  	}
  3218  	read, err = stream(r)
  3219  	r.Close()
  3220  	return read, err
  3221  }
  3222  
  3223  func stream(r io.Reader) (read int64, err error) {
  3224  	br := bufio.NewReader(r)
  3225  	b := make([]byte, 1000)
  3226  	for {
  3227  		done, rerr := br.Read(b)
  3228  		if rerr != nil {
  3229  			if rerr != io.EOF {
  3230  				err = rerr
  3231  			}
  3232  			break
  3233  		}
  3234  		read += int64(done)
  3235  	}
  3236  	return read, err
  3237  }