func TestLinkForget(t *testing.T) {
	tc := NewTestCase(t)
	defer tc.Cleanup()
	c := "hello"
	tc.WriteFile(tc.orig+"/file1", []byte(c), 0644)
	err := os.Link(tc.orig+"/file1", tc.orig+"/file2")
	if err != nil {
		t.Fatalf("Link failed: %v", err)
	}
	for _, fn := range []string{"file1", "file2"} {
		var s syscall.Stat_t
		err = syscall.Lstat(tc.mnt+"/"+fn, &s)
		if err != nil {
			t.Fatalf("Lstat failed: %v", err)
		}
		tc.pathFs.ForgetClientInodes()
	}
	for _, fn := range []string{"file1", "file2"} {
		fn = tc.orig + "/" + fn
		if err := os.Remove(fn); err != nil {
			t.Fatalf("Remove", err)
		}
		tc.WriteFile(fn, []byte(c), 0644)
	}
	for i, fn := range []string{"file1", "file2"} {
		fn = tc.mnt + "/" + fn
		if err := os.Truncate(fn, int64(i)); err != nil {
			t.Fatalf("Truncate", err)
		}
	}
	for i, fn := range []string{"file1", "file2"} {
		var s syscall.Stat_t
		err = syscall.Lstat(tc.mnt+"/"+fn, &s)
		if err != nil {
			t.Fatalf("Lstat failed: %v", err)
		}
		if s.Size != int64(i) {
			t.Errorf("Lstat(%q): got size %d, want %d", fn, s.Size, i)
		}
	}
}
Deal correctly with hard links implied by matching client inode
numbers.
package test
import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"runtime"
	"syscall"
	"testing"
	"time"
	"github.com/hanwen/go-fuse/fuse"
	"github.com/hanwen/go-fuse/fuse/nodefs"
	"github.com/hanwen/go-fuse/fuse/pathfs"
	"github.com/hanwen/go-fuse/internal/testutil"
)
const mode uint32 = 0757
type testCase struct {
	tmpDir string
	orig   string
	mnt    string
	mountFile   string
	mountSubdir string
	origFile    string
	origSubdir  string
	tester      *testing.T
	state       *fuse.Server
	pathFs      *pathfs.PathNodeFs
	connector   *nodefs.FileSystemConnector
}
const testTtl = 100 * time.Millisecond
func (tc *testCase) Mkdir(name string, mode os.FileMode) {
	if err := os.Mkdir(name, mode); err != nil {
		tc.tester.Fatalf("Mkdir(%q,%v): %v", name, mode, err)
	}
}
func (tc *testCase) WriteFile(name string, content []byte, mode os.FileMode) {
	if err := ioutil.WriteFile(name, content, mode); err != nil {
		if len(content) > 50 {
			content = append(content[:50], '.', '.', '.')
		}
		tc.tester.Fatalf("WriteFile(%q, %q, %o): %v", name, content, mode, err)
	}
}
func NewTestCase(t *testing.T) *testCase {
	tc := &testCase{}
	tc.tester = t
	syscall.Umask(0)
	const name string = "hello.txt"
	const subdir string = "subdir"
	var err error
	tc.tmpDir = testutil.TempDir()
	tc.orig = tc.tmpDir + "/orig"
	tc.mnt = tc.tmpDir + "/mnt"
	tc.Mkdir(tc.orig, 0700)
	tc.Mkdir(tc.mnt, 0700)
	tc.mountFile = filepath.Join(tc.mnt, name)
	tc.mountSubdir = filepath.Join(tc.mnt, subdir)
	tc.origFile = filepath.Join(tc.orig, name)
	tc.origSubdir = filepath.Join(tc.orig, subdir)
	var pfs pathfs.FileSystem
	pfs = pathfs.NewLoopbackFileSystem(tc.orig)
	pfs = pathfs.NewLockingFileSystem(pfs)
	tc.pathFs = pathfs.NewPathNodeFs(pfs, &pathfs.PathNodeFsOptions{
		ClientInodes: true})
	tc.connector = nodefs.NewFileSystemConnector(tc.pathFs.Root(),
		&nodefs.Options{
			EntryTimeout:    testTtl,
			AttrTimeout:     testTtl,
			NegativeTimeout: 0.0,
			Debug:           testutil.VerboseTest(),
		})
	tc.state, err = fuse.NewServer(
		fuse.NewRawFileSystem(tc.connector.RawFS()), tc.mnt, &fuse.MountOptions{
			SingleThreaded: true,
			Debug:          testutil.VerboseTest(),
		})
	if err != nil {
		t.Fatal("NewServer:", err)
	}
	go tc.state.Serve()
	if err := tc.state.WaitMount(); err != nil {
		t.Fatal("WaitMount", err)
	}
	return tc
}
func (tc *testCase) Cleanup() {
	err := tc.state.Unmount()
	if err != nil {
		tc.tester.Fatalf("Unmount failed: %v", err)
	}
	os.RemoveAll(tc.tmpDir)
}
func (tc *testCase) rootNode() *nodefs.Inode {
	return tc.pathFs.Root().Inode()
}
func TestOpenUnreadable(t *testing.T) {
	tc := NewTestCase(t)
	defer tc.Cleanup()
	_, err := os.Open(tc.mnt + "/doesnotexist")
	if err == nil {
		t.Errorf("open non-existent should raise error")
	}
}
func TestReadThrough(t *testing.T) {
	tc := NewTestCase(t)
	defer tc.Cleanup()
	content := randomData(125)
	tc.WriteFile(tc.origFile, content, 0700)
	err := os.Chmod(tc.mountFile, os.FileMode(mode))
	if err != nil {
		t.Fatalf("Chmod failed: %v", err)
	}
	fi, err := os.Lstat(tc.mountFile)
	if err != nil {
		t.Fatalf("Lstat failed: %v", err)
	}
	if uint32(fi.Mode().Perm()) != mode {
		t.Errorf("Wrong mode %o != %o", int(fi.Mode().Perm()), mode)
	}
	f, err := os.Open(tc.mountFile)
	if err != nil {
		t.Fatalf("Open failed: %v", err)
	}
	defer f.Close()
	var buf [1024]byte
	slice := buf[:]
	n, err := f.Read(slice)
	CompareSlices(t, slice[:n], content)
}
func TestRemove(t *testing.T) {
	tc := NewTestCase(t)
	defer tc.Cleanup()
	contents := []byte{1, 2, 3}
	tc.WriteFile(tc.origFile, []byte(contents), 0700)
	err := os.Remove(tc.mountFile)
	if err != nil {
		t.Fatalf("Remove failed: %v", err)
	}
	_, err = os.Lstat(tc.origFile)
	if err == nil {
		t.Errorf("Lstat() after delete should have generated error.")
	}
}
func TestWriteThrough(t *testing.T) {
	tc := NewTestCase(t)
	defer tc.Cleanup()
	f, err := os.OpenFile(tc.mountFile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		t.Fatalf("OpenFile failed: %v", err)
	}
	defer f.Close()
	content := randomData(125)
	n, err := f.Write(content)
	if err != nil {
		t.Fatalf("Write failed: %v", err)
	}
	if n != len(content) {
		t.Errorf("Write mismatch: %v of %v", n, len(content))
	}
	fi, err := os.Lstat(tc.origFile)
	if err != nil {
		t.Fatalf("Lstat(%q): %v", tc.origFile, err)
	}
	if fi.Mode().Perm() != 0644 {
		t.Errorf("create mode error %o", fi.Mode()&0777)
	}
	f, err = os.Open(tc.origFile)
	if err != nil {
		t.Fatalf("Open failed: %v", err)
	}
	defer f.Close()
	var buf [1024]byte
	slice := buf[:]
	n, err = f.Read(slice)
	if err != nil {
		t.Fatalf("Read failed: %v", err)
	}
	CompareSlices(t, slice[:n], content)
}
func TestMkdirRmdir(t *testing.T) {
	tc := NewTestCase(t)
	defer tc.Cleanup()
	if err := os.Mkdir(tc.mountSubdir, 0777); err != nil {
		t.Fatalf("Mkdir failed: %v", err)
	}
	if fi, err := os.Lstat(tc.origSubdir); err != nil {
		t.Fatalf("Lstat(%q): %v", tc.origSubdir, err)
	} else if !fi.IsDir() {
		t.Errorf("Not a directory: %v", fi)
	}
	if err := os.Remove(tc.mountSubdir); err != nil {
		t.Fatalf("Remove failed: %v", err)
	}
}
func TestLinkCreate(t *testing.T) {
	tc := NewTestCase(t)
	defer tc.Cleanup()
	content := randomData(125)
	tc.WriteFile(tc.origFile, content, 0700)
	tc.Mkdir(tc.origSubdir, 0777)
	mountSubfile := filepath.Join(tc.mountSubdir, "subfile")
	err := os.Link(tc.mountFile, mountSubfile)
	if err != nil {
		t.Fatalf("Link failed: %v", err)
	}
	var subStat, stat syscall.Stat_t
	err = syscall.Lstat(mountSubfile, &subStat)
	if err != nil {
		t.Fatalf("Lstat failed: %v", err)
	}
	err = syscall.Lstat(tc.mountFile, &stat)
	if err != nil {
		t.Fatalf("Lstat failed: %v", err)
	}
	if stat.Nlink != 2 {
		t.Errorf("Expect 2 links: %v", stat)
	}
	if stat.Ino != subStat.Ino {
		t.Errorf("Link succeeded, but inode numbers different: %v %v", stat.Ino, subStat.Ino)
	}
	readback, err := ioutil.ReadFile(mountSubfile)
	if err != nil {
		t.Fatalf("ReadFile failed: %v", err)
	}
	CompareSlices(t, readback, content)
	err = os.Remove(tc.mountFile)
	if err != nil {
		t.Fatalf("Remove failed: %v", err)
	}
	_, err = ioutil.ReadFile(mountSubfile)
	if err != nil {
		t.Fatalf("ReadFile failed: %v", err)
	}
}
func randomData(size int) []byte {
	return bytes.Repeat([]byte{'x'}, size)
}
func TestLinkExisting(t *testing.T) {
	tc := NewTestCase(t)
	defer tc.Cleanup()
	c := randomData(5)
	tc.WriteFile(tc.orig+"/file1", c, 0644)
	err := os.Link(tc.orig+"/file1", tc.orig+"/file2")
	if err != nil {
		t.Fatalf("Link failed: %v", err)
	}
	var s1, s2 syscall.Stat_t
	err = syscall.Lstat(tc.mnt+"/file1", &s1)
	if err != nil {
		t.Fatalf("Lstat failed: %v", err)
	}
	err = syscall.Lstat(tc.mnt+"/file2", &s2)
	if err != nil {
		t.Fatalf("Lstat failed: %v", err)
	}
	if s1.Ino != s2.Ino {
		t.Errorf("linked files should have identical inodes %v %v", s1.Ino, s2.Ino)
	}
	back, err := ioutil.ReadFile(tc.mnt + "/file1")
	if err != nil {
		t.Fatalf("ReadFile failed: %v", err)
	}
	CompareSlices(t, back, c)
}