github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/fs/fs_test.go (about)

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