commit
53bae01f57
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Create a temporary file with the same semantics as ioutil.TempFile, but
|
||||
// ensure that it is unlinked before returning so that it does not persist
|
||||
// after the process exits.
|
||||
//
|
||||
// Warning: this is not production-quality code, and should only be used for
|
||||
// testing purposes. In particular, there is a race between creating and
|
||||
// unlinking by name.
|
||||
func AnonymousFile(dir string) (f *os.File, err error) {
|
||||
// Choose a prefix based on the binary name.
|
||||
prefix := path.Base(os.Args[0])
|
||||
|
||||
// Create the file.
|
||||
f, err = ioutil.TempFile(dir, prefix)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("TempFile: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Unlink it.
|
||||
err = os.Remove(f.Name())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Remove: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -15,18 +15,19 @@
|
|||
package flushfs_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/jacobsa/fuse"
|
||||
"github.com/jacobsa/bazilfuse"
|
||||
"github.com/jacobsa/fuse/fsutil"
|
||||
"github.com/jacobsa/fuse/samples"
|
||||
"github.com/jacobsa/fuse/samples/flushfs"
|
||||
. "github.com/jacobsa/oglematchers"
|
||||
. "github.com/jacobsa/ogletest"
|
||||
)
|
||||
|
@ -37,54 +38,59 @@ func TestFlushFS(t *testing.T) { RunTests(t) }
|
|||
// Boilerplate
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type FlushFSTest struct {
|
||||
samples.SampleTest
|
||||
type flushFSTest struct {
|
||||
samples.SubprocessTest
|
||||
|
||||
// Files to which mount_sample is writing reported flushes and fsyncs.
|
||||
flushes *os.File
|
||||
fsyncs *os.File
|
||||
|
||||
// File handles that are closed in TearDown if non-nil.
|
||||
f1 *os.File
|
||||
f2 *os.File
|
||||
|
||||
mu sync.Mutex
|
||||
|
||||
// GUARDED_BY(mu)
|
||||
flushes []string
|
||||
flushErr error
|
||||
|
||||
// GUARDED_BY(mu)
|
||||
fsyncs []string
|
||||
fsyncErr error
|
||||
}
|
||||
|
||||
func init() { RegisterTestSuite(&FlushFSTest{}) }
|
||||
|
||||
func (t *FlushFSTest) SetUp(ti *TestInfo) {
|
||||
func (t *flushFSTest) setUp(
|
||||
ti *TestInfo,
|
||||
flushErr bazilfuse.Errno,
|
||||
fsyncErr bazilfuse.Errno) {
|
||||
var err error
|
||||
|
||||
// Set up a file system.
|
||||
reportTo := func(slice *[]string, err *error) func(string) error {
|
||||
return func(s string) error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
// Set up files to receive flush and fsync reports.
|
||||
t.flushes, err = fsutil.AnonymousFile("")
|
||||
AssertEq(nil, err)
|
||||
|
||||
*slice = append(*slice, s)
|
||||
return *err
|
||||
}
|
||||
t.fsyncs, err = fsutil.AnonymousFile("")
|
||||
AssertEq(nil, err)
|
||||
|
||||
// Set up test config.
|
||||
t.MountType = "flushfs"
|
||||
t.MountFlags = []string{
|
||||
"--flushfs.flush_error",
|
||||
fmt.Sprintf("%d", int(flushErr)),
|
||||
|
||||
"--flushfs.fsync_error",
|
||||
fmt.Sprintf("%d", int(fsyncErr)),
|
||||
}
|
||||
|
||||
t.FileSystem, err = flushfs.NewFileSystem(
|
||||
reportTo(&t.flushes, &t.flushErr),
|
||||
reportTo(&t.fsyncs, &t.fsyncErr))
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
t.MountFiles = map[string]*os.File{
|
||||
"flushfs.flushes_file": t.flushes,
|
||||
"flushfs.fsyncs_file": t.fsyncs,
|
||||
}
|
||||
|
||||
// Mount it.
|
||||
t.SampleTest.SetUp(ti)
|
||||
t.SubprocessTest.SetUp(ti)
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) TearDown() {
|
||||
// Close files if non-nil.
|
||||
func (t *flushFSTest) TearDown() {
|
||||
// Unlink reporting files.
|
||||
os.Remove(t.flushes.Name())
|
||||
os.Remove(t.fsyncs.Name())
|
||||
|
||||
// Close reporting files.
|
||||
t.flushes.Close()
|
||||
t.fsyncs.Close()
|
||||
|
||||
// Close test files if non-nil.
|
||||
if t.f1 != nil {
|
||||
ExpectEq(nil, t.f1.Close())
|
||||
}
|
||||
|
@ -94,53 +100,66 @@ func (t *FlushFSTest) TearDown() {
|
|||
}
|
||||
|
||||
// Finish tearing down.
|
||||
t.SampleTest.TearDown()
|
||||
t.SubprocessTest.TearDown()
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Helpers
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Return a copy of the current contents of t.flushes.
|
||||
//
|
||||
// LOCKS_EXCLUDED(t.mu)
|
||||
func (t *FlushFSTest) getFlushes() (p []string) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
func readReports(f *os.File) (reports []string, err error) {
|
||||
// Seek the file to the start.
|
||||
_, err = f.Seek(0, 0)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Seek: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// We expect reports to end in a newline (including the final one).
|
||||
reader := bufio.NewReader(f)
|
||||
for {
|
||||
var record []byte
|
||||
record, err = reader.ReadBytes('\n')
|
||||
if err == io.EOF {
|
||||
if len(record) != 0 {
|
||||
err = fmt.Errorf("Unexpected record:\n%s", hex.Dump(record))
|
||||
return
|
||||
}
|
||||
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("ReadBytes: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Strip the newline.
|
||||
reports = append(reports, string(record[:len(record)-1]))
|
||||
}
|
||||
}
|
||||
|
||||
// Return a copy of the current contents of t.flushes.
|
||||
func (t *flushFSTest) getFlushes() (p []string) {
|
||||
var err error
|
||||
if p, err = readReports(t.flushes); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
p = make([]string, len(t.flushes))
|
||||
copy(p, t.flushes)
|
||||
return
|
||||
}
|
||||
|
||||
// Return a copy of the current contents of t.fsyncs.
|
||||
//
|
||||
// LOCKS_EXCLUDED(t.mu)
|
||||
func (t *FlushFSTest) getFsyncs() (p []string) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
func (t *flushFSTest) getFsyncs() (p []string) {
|
||||
var err error
|
||||
if p, err = readReports(t.fsyncs); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
p = make([]string, len(t.fsyncs))
|
||||
copy(p, t.fsyncs)
|
||||
return
|
||||
}
|
||||
|
||||
// LOCKS_EXCLUDED(t.mu)
|
||||
func (t *FlushFSTest) setFlushError(err error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
t.flushErr = err
|
||||
}
|
||||
|
||||
// LOCKS_EXCLUDED(t.mu)
|
||||
func (t *FlushFSTest) setFsyncError(err error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
t.fsyncErr = err
|
||||
}
|
||||
|
||||
// Like syscall.Dup2, but correctly annotates the syscall as blocking. See here
|
||||
// for more info: https://github.com/golang/go/issues/10202
|
||||
func dup2(oldfd int, newfd int) (err error) {
|
||||
|
@ -155,10 +174,21 @@ func dup2(oldfd int, newfd int) (err error) {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Tests
|
||||
// No errors
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (t *FlushFSTest) CloseReports_ReadWrite() {
|
||||
type NoErrorsTest struct {
|
||||
flushFSTest
|
||||
}
|
||||
|
||||
func init() { RegisterTestSuite(&NoErrorsTest{}) }
|
||||
|
||||
func (t *NoErrorsTest) SetUp(ti *TestInfo) {
|
||||
const noErr = 0
|
||||
t.flushFSTest.setUp(ti, noErr, noErr)
|
||||
}
|
||||
|
||||
func (t *NoErrorsTest) Close_ReadWrite() {
|
||||
var n int
|
||||
var off int64
|
||||
var err error
|
||||
|
@ -196,7 +226,7 @@ func (t *FlushFSTest) CloseReports_ReadWrite() {
|
|||
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) CloseReports_ReadOnly() {
|
||||
func (t *NoErrorsTest) Close_ReadOnly() {
|
||||
var err error
|
||||
|
||||
// Open the file.
|
||||
|
@ -217,7 +247,7 @@ func (t *FlushFSTest) CloseReports_ReadOnly() {
|
|||
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) CloseReports_WriteOnly() {
|
||||
func (t *NoErrorsTest) Close_WriteOnly() {
|
||||
var n int
|
||||
var err error
|
||||
|
||||
|
@ -244,7 +274,7 @@ func (t *FlushFSTest) CloseReports_WriteOnly() {
|
|||
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) CloseReports_MultipleTimes_NonOverlappingFileHandles() {
|
||||
func (t *NoErrorsTest) Close_MultipleTimes_NonOverlappingFileHandles() {
|
||||
var n int
|
||||
var err error
|
||||
|
||||
|
@ -291,7 +321,7 @@ func (t *FlushFSTest) CloseReports_MultipleTimes_NonOverlappingFileHandles() {
|
|||
AssertThat(t.getFsyncs(), ElementsAre())
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) CloseReports_MultipleTimes_OverlappingFileHandles() {
|
||||
func (t *NoErrorsTest) Close_MultipleTimes_OverlappingFileHandles() {
|
||||
var n int
|
||||
var err error
|
||||
|
||||
|
@ -340,25 +370,7 @@ func (t *FlushFSTest) CloseReports_MultipleTimes_OverlappingFileHandles() {
|
|||
AssertThat(t.getFsyncs(), ElementsAre())
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) CloseError() {
|
||||
var err error
|
||||
|
||||
// Open the file.
|
||||
t.f1, err = os.OpenFile(path.Join(t.Dir, "foo"), os.O_RDWR, 0)
|
||||
AssertEq(nil, err)
|
||||
|
||||
// Configure a flush error.
|
||||
t.setFlushError(fuse.ENOENT)
|
||||
|
||||
// Close the file.
|
||||
err = t.f1.Close()
|
||||
t.f1 = nil
|
||||
|
||||
AssertNe(nil, err)
|
||||
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) FsyncReports() {
|
||||
func (t *NoErrorsTest) Fsync() {
|
||||
var n int
|
||||
var err error
|
||||
|
||||
|
@ -397,24 +409,7 @@ func (t *FlushFSTest) FsyncReports() {
|
|||
AssertThat(t.getFsyncs(), ElementsAre("taco", "tacos"))
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) FsyncError() {
|
||||
var err error
|
||||
|
||||
// Open the file.
|
||||
t.f1, err = os.OpenFile(path.Join(t.Dir, "foo"), os.O_RDWR, 0)
|
||||
AssertEq(nil, err)
|
||||
|
||||
// Configure an fsync error.
|
||||
t.setFsyncError(fuse.ENOENT)
|
||||
|
||||
// Fsync.
|
||||
err = t.f1.Sync()
|
||||
|
||||
AssertNe(nil, err)
|
||||
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) Dup() {
|
||||
func (t *NoErrorsTest) Dup() {
|
||||
var n int
|
||||
var err error
|
||||
|
||||
|
@ -478,46 +473,7 @@ func (t *FlushFSTest) Dup() {
|
|||
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) Dup_FlushError() {
|
||||
var err error
|
||||
|
||||
// Open the file.
|
||||
t.f1, err = os.OpenFile(path.Join(t.Dir, "foo"), os.O_WRONLY, 0)
|
||||
AssertEq(nil, err)
|
||||
|
||||
fd1 := t.f1.Fd()
|
||||
|
||||
// Use dup(2) to get another copy.
|
||||
fd2, err := syscall.Dup(int(fd1))
|
||||
AssertEq(nil, err)
|
||||
|
||||
t.f2 = os.NewFile(uintptr(fd2), t.f1.Name())
|
||||
|
||||
// Configure a flush error.
|
||||
t.setFlushError(fuse.ENOENT)
|
||||
|
||||
// Close by the first handle. On OS X, where the semantics of file handles
|
||||
// are different (cf. https://github.com/osxfuse/osxfuse/issues/199), this
|
||||
// does not result in an error.
|
||||
err = t.f1.Close()
|
||||
t.f1 = nil
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
AssertEq(nil, err)
|
||||
} else {
|
||||
AssertNe(nil, err)
|
||||
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||
}
|
||||
|
||||
// Close by the second handle.
|
||||
err = t.f2.Close()
|
||||
t.f2 = nil
|
||||
|
||||
AssertNe(nil, err)
|
||||
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) Dup2() {
|
||||
func (t *NoErrorsTest) Dup2() {
|
||||
var n int
|
||||
var err error
|
||||
|
||||
|
@ -530,11 +486,8 @@ func (t *FlushFSTest) Dup2() {
|
|||
AssertEq(nil, err)
|
||||
AssertEq(4, n)
|
||||
|
||||
// Open and unlink some temporary file.
|
||||
t.f2, err = ioutil.TempFile("", "")
|
||||
AssertEq(nil, err)
|
||||
|
||||
err = os.Remove(t.f2.Name())
|
||||
// Create some anonymous temporary file.
|
||||
t.f2, err = fsutil.AnonymousFile("")
|
||||
AssertEq(nil, err)
|
||||
|
||||
// Duplicate the temporary file descriptor on top of the file from our file
|
||||
|
@ -546,30 +499,7 @@ func (t *FlushFSTest) Dup2() {
|
|||
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) Dup2_FlushError() {
|
||||
var err error
|
||||
|
||||
// Open the file.
|
||||
t.f1, err = os.OpenFile(path.Join(t.Dir, "foo"), os.O_WRONLY, 0)
|
||||
AssertEq(nil, err)
|
||||
|
||||
// Open and unlink some temporary file.
|
||||
t.f2, err = ioutil.TempFile("", "")
|
||||
AssertEq(nil, err)
|
||||
|
||||
err = os.Remove(t.f2.Name())
|
||||
AssertEq(nil, err)
|
||||
|
||||
// Configure a flush error.
|
||||
t.setFlushError(fuse.ENOENT)
|
||||
|
||||
// Duplicate the temporary file descriptor on top of the file from our file
|
||||
// system. We shouldn't see the flush error.
|
||||
err = dup2(int(t.f2.Fd()), int(t.f1.Fd()))
|
||||
ExpectEq(nil, err)
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) Mmap_MunmapBeforeClose() {
|
||||
func (t *NoErrorsTest) Mmap_MunmapBeforeClose() {
|
||||
var n int
|
||||
var err error
|
||||
|
||||
|
@ -629,7 +559,7 @@ func (t *FlushFSTest) Mmap_MunmapBeforeClose() {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) Mmap_CloseBeforeMunmap() {
|
||||
func (t *NoErrorsTest) Mmap_CloseBeforeMunmap() {
|
||||
var n int
|
||||
var err error
|
||||
|
||||
|
@ -683,6 +613,118 @@ func (t *FlushFSTest) Mmap_CloseBeforeMunmap() {
|
|||
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||
}
|
||||
|
||||
func (t *FlushFSTest) Directory() {
|
||||
func (t *NoErrorsTest) Directory() {
|
||||
AssertTrue(false, "TODO")
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Flush error
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type FlushErrorTest struct {
|
||||
flushFSTest
|
||||
}
|
||||
|
||||
func init() { RegisterTestSuite(&FlushErrorTest{}) }
|
||||
|
||||
func (t *FlushErrorTest) SetUp(ti *TestInfo) {
|
||||
const noErr = 0
|
||||
t.flushFSTest.setUp(ti, bazilfuse.ENOENT, noErr)
|
||||
}
|
||||
|
||||
func (t *FlushErrorTest) Close() {
|
||||
var err error
|
||||
|
||||
// Open the file.
|
||||
t.f1, err = os.OpenFile(path.Join(t.Dir, "foo"), os.O_RDWR, 0)
|
||||
AssertEq(nil, err)
|
||||
|
||||
// Close the file.
|
||||
err = t.f1.Close()
|
||||
t.f1 = nil
|
||||
|
||||
AssertNe(nil, err)
|
||||
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||
}
|
||||
|
||||
func (t *FlushErrorTest) Dup() {
|
||||
var err error
|
||||
|
||||
// Open the file.
|
||||
t.f1, err = os.OpenFile(path.Join(t.Dir, "foo"), os.O_WRONLY, 0)
|
||||
AssertEq(nil, err)
|
||||
|
||||
fd1 := t.f1.Fd()
|
||||
|
||||
// Use dup(2) to get another copy.
|
||||
fd2, err := syscall.Dup(int(fd1))
|
||||
AssertEq(nil, err)
|
||||
|
||||
t.f2 = os.NewFile(uintptr(fd2), t.f1.Name())
|
||||
|
||||
// Close by the first handle. On OS X, where the semantics of file handles
|
||||
// are different (cf. https://github.com/osxfuse/osxfuse/issues/199), this
|
||||
// does not result in an error.
|
||||
err = t.f1.Close()
|
||||
t.f1 = nil
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
AssertEq(nil, err)
|
||||
} else {
|
||||
AssertNe(nil, err)
|
||||
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||
}
|
||||
|
||||
// Close by the second handle.
|
||||
err = t.f2.Close()
|
||||
t.f2 = nil
|
||||
|
||||
AssertNe(nil, err)
|
||||
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||
}
|
||||
|
||||
func (t *FlushErrorTest) Dup2() {
|
||||
var err error
|
||||
|
||||
// Open the file.
|
||||
t.f1, err = os.OpenFile(path.Join(t.Dir, "foo"), os.O_WRONLY, 0)
|
||||
AssertEq(nil, err)
|
||||
|
||||
// Create some anonymous temporary file.
|
||||
t.f2, err = fsutil.AnonymousFile("")
|
||||
AssertEq(nil, err)
|
||||
|
||||
// Duplicate the temporary file descriptor on top of the file from our file
|
||||
// system. We shouldn't see the flush error.
|
||||
err = dup2(int(t.f2.Fd()), int(t.f1.Fd()))
|
||||
ExpectEq(nil, err)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Fsync error
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type FsyncErrorTest struct {
|
||||
flushFSTest
|
||||
}
|
||||
|
||||
func init() { RegisterTestSuite(&FsyncErrorTest{}) }
|
||||
|
||||
func (t *FsyncErrorTest) SetUp(ti *TestInfo) {
|
||||
const noErr = 0
|
||||
t.flushFSTest.setUp(ti, noErr, bazilfuse.ENOENT)
|
||||
}
|
||||
|
||||
func (t *FsyncErrorTest) Fsync() {
|
||||
var err error
|
||||
|
||||
// Open the file.
|
||||
t.f1, err = os.OpenFile(path.Join(t.Dir, "foo"), os.O_RDWR, 0)
|
||||
AssertEq(nil, err)
|
||||
|
||||
// Fsync.
|
||||
err = t.f1.Sync()
|
||||
|
||||
AssertNe(nil, err)
|
||||
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||
}
|
||||
|
|
|
@ -18,8 +18,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/googlecloudplatform/gcsfuse/timeutil"
|
||||
|
@ -55,8 +54,8 @@ type SampleTest struct {
|
|||
mfs *fuse.MountedFileSystem
|
||||
}
|
||||
|
||||
// Mount the supplied file system and initialize the other exported fields of
|
||||
// the struct. Panics on error.
|
||||
// Mount t.FileSystem and initialize the other exported fields of the struct.
|
||||
// Panics on error.
|
||||
//
|
||||
// REQUIRES: t.FileSystem has been set.
|
||||
func (t *SampleTest) SetUp(ti *ogletest.TestInfo) {
|
||||
|
@ -66,7 +65,7 @@ func (t *SampleTest) SetUp(ti *ogletest.TestInfo) {
|
|||
}
|
||||
}
|
||||
|
||||
// Like Initialize, but doens't panic.
|
||||
// Like SetUp, but doens't panic.
|
||||
func (t *SampleTest) initialize(
|
||||
fs fuse.FileSystem,
|
||||
config *fuse.MountConfig) (err error) {
|
||||
|
@ -90,7 +89,7 @@ func (t *SampleTest) initialize(
|
|||
return
|
||||
}
|
||||
|
||||
// Wait for it to be read.
|
||||
// Wait for it to be ready.
|
||||
err = t.mfs.WaitForReady(t.Ctx)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("WaitForReady: %v", err)
|
||||
|
@ -124,27 +123,16 @@ func (t *SampleTest) destroy() (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// Unmount the file system. Try again on "resource busy" errors.
|
||||
delay := 10 * time.Millisecond
|
||||
for {
|
||||
err = t.mfs.Unmount()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "resource busy") {
|
||||
log.Println("Resource busy error while unmounting; trying again")
|
||||
time.Sleep(delay)
|
||||
delay = time.Duration(1.3 * float64(delay))
|
||||
continue
|
||||
}
|
||||
|
||||
err = fmt.Errorf("MountedFileSystem.Unmount: %v", err)
|
||||
// Unmount the file system.
|
||||
err = unmount(t.Dir)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unmount: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = t.mfs.Join(t.Ctx); err != nil {
|
||||
err = fmt.Errorf("MountedFileSystem.Join: %v", err)
|
||||
// Unlink the mount point.
|
||||
if err = os.Remove(t.Dir); err != nil {
|
||||
err = fmt.Errorf("Unlinking mount point: %v", err)
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// A simple tool for mounting sample file systems, used by the tests in
|
||||
// samples/.
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/jacobsa/bazilfuse"
|
||||
"github.com/jacobsa/fuse"
|
||||
"github.com/jacobsa/fuse/samples/flushfs"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var fType = flag.String("type", "", "The name of the samples/ sub-dir.")
|
||||
var fMountPoint = flag.String("mount_point", "", "Path to mount point.")
|
||||
var fReadyFile = flag.Uint64("ready_file", 0, "FD to signal when ready.")
|
||||
|
||||
var fFlushesFile = flag.Uint64("flushfs.flushes_file", 0, "")
|
||||
var fFsyncsFile = flag.Uint64("flushfs.fsyncs_file", 0, "")
|
||||
var fFlushError = flag.Int("flushfs.flush_error", 0, "")
|
||||
var fFsyncError = flag.Int("flushfs.fsync_error", 0, "")
|
||||
|
||||
func makeFlushFS() (fs fuse.FileSystem, err error) {
|
||||
// Check the flags.
|
||||
if *fFlushesFile == 0 || *fFsyncsFile == 0 {
|
||||
err = fmt.Errorf("You must set the flushfs flags.")
|
||||
return
|
||||
}
|
||||
|
||||
// Set up the files.
|
||||
flushes := os.NewFile(uintptr(*fFlushesFile), "(flushes file)")
|
||||
fsyncs := os.NewFile(uintptr(*fFsyncsFile), "(fsyncs file)")
|
||||
|
||||
// Set up errors.
|
||||
var flushErr error
|
||||
var fsyncErr error
|
||||
|
||||
if *fFlushError != 0 {
|
||||
flushErr = bazilfuse.Errno(*fFlushError)
|
||||
}
|
||||
|
||||
if *fFsyncError != 0 {
|
||||
fsyncErr = bazilfuse.Errno(*fFsyncError)
|
||||
}
|
||||
|
||||
// Report flushes and fsyncs by writing the contents followed by a newline.
|
||||
report := func(f *os.File, outErr error) func(string) error {
|
||||
return func(s string) (err error) {
|
||||
buf := []byte(s)
|
||||
buf = append(buf, '\n')
|
||||
|
||||
_, err = f.Write(buf)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Write: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = outErr
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
reportFlush := report(flushes, flushErr)
|
||||
reportFsync := report(fsyncs, fsyncErr)
|
||||
|
||||
// Create the file system.
|
||||
fs, err = flushfs.NewFileSystem(reportFlush, reportFsync)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func makeFS() (fs fuse.FileSystem, err error) {
|
||||
switch *fType {
|
||||
default:
|
||||
err = fmt.Errorf("Unknown FS type: %v", *fType)
|
||||
|
||||
case "flushfs":
|
||||
fs, err = makeFlushFS()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getReadyFile() (f *os.File, err error) {
|
||||
if *fReadyFile == 0 {
|
||||
err = errors.New("You must set --ready_file.")
|
||||
return
|
||||
}
|
||||
|
||||
f = os.NewFile(uintptr(*fReadyFile), "(ready file)")
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Grab the file to signal when ready.
|
||||
readyFile, err := getReadyFile()
|
||||
if err != nil {
|
||||
log.Fatalf("getReadyFile:", err)
|
||||
}
|
||||
|
||||
// Create an appropriate file system.
|
||||
fs, err := makeFS()
|
||||
if err != nil {
|
||||
log.Fatalf("makeFS: %v", err)
|
||||
}
|
||||
|
||||
// Mount the file system.
|
||||
if *fMountPoint == "" {
|
||||
log.Fatalf("You must set --mount_point.")
|
||||
}
|
||||
|
||||
mfs, err := fuse.Mount(*fMountPoint, fs, &fuse.MountConfig{})
|
||||
if err != nil {
|
||||
log.Fatalf("Mount: %v", err)
|
||||
}
|
||||
|
||||
// Wait for it to be ready.
|
||||
if err = mfs.WaitForReady(context.Background()); err != nil {
|
||||
log.Fatalf("WaitForReady: %v", err)
|
||||
}
|
||||
|
||||
// Signal that it is ready.
|
||||
_, err = readyFile.Write([]byte("x"))
|
||||
if err != nil {
|
||||
log.Fatalf("readyFile.Write: %v", err)
|
||||
}
|
||||
|
||||
// Wait for it to be unmounted.
|
||||
if err = mfs.Join(context.Background()); err != nil {
|
||||
log.Fatalf("Join: %v", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,360 @@
|
|||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package samples
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/jacobsa/ogletest"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var fToolPath = flag.String(
|
||||
"mount_sample",
|
||||
"",
|
||||
"Path to the mount_sample tool. If unset, we will compile it.")
|
||||
|
||||
// A struct that implements common behavior needed by tests in the samples/
|
||||
// directory where the file system is mounted by a subprocess. Use it as an
|
||||
// embedded field in your test fixture, calling its SetUp method from your
|
||||
// SetUp method after setting the MountType and MountFlags fields.
|
||||
type SubprocessTest struct {
|
||||
// The type of the file system to mount. Must be recognized by mount_sample.
|
||||
MountType string
|
||||
|
||||
// Additional flags to be passed to the mount_sample tool.
|
||||
MountFlags []string
|
||||
|
||||
// A list of files to pass to mount_sample. The given string flag will be
|
||||
// used to pass the file descriptor number.
|
||||
MountFiles map[string]*os.File
|
||||
|
||||
// A context object that can be used for long-running operations.
|
||||
Ctx context.Context
|
||||
|
||||
// The directory at which the file system is mounted.
|
||||
Dir string
|
||||
|
||||
// Anothing non-nil in this slice will be closed by TearDown. The test will
|
||||
// fail if closing fails.
|
||||
ToClose []io.Closer
|
||||
|
||||
mountSampleErr <-chan error
|
||||
}
|
||||
|
||||
// Mount the file system and initialize the other exported fields of the
|
||||
// struct. Panics on error.
|
||||
//
|
||||
// REQUIRES: t.FileSystem has been set.
|
||||
func (t *SubprocessTest) SetUp(ti *ogletest.TestInfo) {
|
||||
err := t.initialize()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Private state for getToolPath.
|
||||
var getToolContents_Contents []byte
|
||||
var getToolContents_Err error
|
||||
var getToolContents_Once sync.Once
|
||||
|
||||
// Implementation detail of getToolPath.
|
||||
func getToolContentsImpl() (contents []byte, err error) {
|
||||
// Fast path: has the user set the flag?
|
||||
if *fToolPath != "" {
|
||||
contents, err = ioutil.ReadFile(*fToolPath)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Reading mount_sample contents: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Create a temporary directory into which we will compile the tool.
|
||||
tempDir, err := ioutil.TempDir("", "sample_test")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("TempDir: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
toolPath := path.Join(tempDir, "mount_sample")
|
||||
|
||||
// Ensure that we kill the temporary directory when we're finished here.
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Run "go build".
|
||||
cmd := exec.Command(
|
||||
"go",
|
||||
"build",
|
||||
"-o",
|
||||
toolPath,
|
||||
"github.com/jacobsa/fuse/samples/mount_sample")
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
err = fmt.Errorf(
|
||||
"mount_sample exited with %v, output:\n%s",
|
||||
err,
|
||||
string(output))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Slurp the tool contents.
|
||||
contents, err = ioutil.ReadFile(toolPath)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("ReadFile: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Build the mount_sample tool if it has not yet been built for this process.
|
||||
// Return its contents.
|
||||
func getToolContents() (contents []byte, err error) {
|
||||
// Get hold of the binary contents, if we haven't yet.
|
||||
getToolContents_Once.Do(func() {
|
||||
getToolContents_Contents, getToolContents_Err = getToolContentsImpl()
|
||||
})
|
||||
|
||||
contents, err = getToolContents_Contents, getToolContents_Err
|
||||
return
|
||||
}
|
||||
|
||||
func waitForMountSample(
|
||||
cmd *exec.Cmd,
|
||||
errChan chan<- error,
|
||||
stderr *bytes.Buffer) {
|
||||
// However we exit, write the error to the channel.
|
||||
var err error
|
||||
defer func() {
|
||||
errChan <- err
|
||||
}()
|
||||
|
||||
// Wait for the command.
|
||||
err = cmd.Wait()
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Make exit errors nicer.
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
err = fmt.Errorf(
|
||||
"mount_sample exited with %v. Stderr:\n%s",
|
||||
exitErr,
|
||||
stderr.String())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = fmt.Errorf("Waiting for mount_sample: %v", err)
|
||||
}
|
||||
|
||||
func waitForReady(readyReader *os.File, c chan<- struct{}) {
|
||||
_, err := readyReader.Read(make([]byte, 1))
|
||||
if err != nil {
|
||||
log.Printf("Readying from ready pipe: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
c <- struct{}{}
|
||||
}
|
||||
|
||||
// Like SetUp, but doens't panic.
|
||||
func (t *SubprocessTest) initialize() (err error) {
|
||||
// Initialize the context.
|
||||
t.Ctx = context.Background()
|
||||
|
||||
// Set up a temporary directory.
|
||||
t.Dir, err = ioutil.TempDir("", "sample_test")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("TempDir: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Build/read the mount_sample tool.
|
||||
toolContents, err := getToolContents()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("getTooltoolContents: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a temporary file to hold the contents of the tool.
|
||||
toolFile, err := ioutil.TempFile("", "sample_test")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("TempFile: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer toolFile.Close()
|
||||
|
||||
// Ensure that it is deleted when we leave.
|
||||
toolPath := toolFile.Name()
|
||||
defer os.Remove(toolPath)
|
||||
|
||||
// Write out the tool contents and make them executable.
|
||||
if _, err = toolFile.Write(toolContents); err != nil {
|
||||
err = fmt.Errorf("toolFile.Write: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = toolFile.Chmod(0500); err != nil {
|
||||
err = fmt.Errorf("toolFile.Chmod: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Close the tool file to prevent "text file busy" errors below.
|
||||
err = toolFile.Close()
|
||||
toolFile = nil
|
||||
if err != nil {
|
||||
err = fmt.Errorf("toolFile.Close: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Set up basic args for the subprocess.
|
||||
args := []string{
|
||||
"--type",
|
||||
t.MountType,
|
||||
"--mount_point",
|
||||
t.Dir,
|
||||
}
|
||||
|
||||
args = append(args, t.MountFlags...)
|
||||
|
||||
// Set up a pipe for the "ready" status.
|
||||
readyReader, readyWriter, err := os.Pipe()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Pipe: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer readyReader.Close()
|
||||
defer readyWriter.Close()
|
||||
|
||||
t.MountFiles["ready_file"] = readyWriter
|
||||
|
||||
// Set up inherited files and appropriate flags.
|
||||
var extraFiles []*os.File
|
||||
for flag, file := range t.MountFiles {
|
||||
// Cf. os/exec.Cmd.ExtraFiles
|
||||
fd := 3 + len(extraFiles)
|
||||
|
||||
extraFiles = append(extraFiles, file)
|
||||
args = append(args, "--"+flag)
|
||||
args = append(args, fmt.Sprintf("%d", fd))
|
||||
}
|
||||
|
||||
// Set up a command.
|
||||
var stderr bytes.Buffer
|
||||
mountCmd := exec.Command(toolPath, args...)
|
||||
mountCmd.Stderr = &stderr
|
||||
mountCmd.ExtraFiles = extraFiles
|
||||
|
||||
// Start it.
|
||||
if err = mountCmd.Start(); err != nil {
|
||||
err = fmt.Errorf("mountCmd.Start: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Launch a goroutine that waits for it and returns its status.
|
||||
mountSampleErr := make(chan error, 1)
|
||||
go waitForMountSample(mountCmd, mountSampleErr, &stderr)
|
||||
|
||||
// Wait for the tool to say the file system is ready. In parallel, watch for
|
||||
// the tool to fail.
|
||||
readyChan := make(chan struct{}, 1)
|
||||
go waitForReady(readyReader, readyChan)
|
||||
|
||||
select {
|
||||
case <-readyChan:
|
||||
case err = <-mountSampleErr:
|
||||
return
|
||||
}
|
||||
|
||||
// TearDown is no responsible for joining.
|
||||
t.mountSampleErr = mountSampleErr
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Unmount the file system and clean up. Panics on error.
|
||||
func (t *SubprocessTest) TearDown() {
|
||||
err := t.destroy()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Like TearDown, but doesn't panic.
|
||||
func (t *SubprocessTest) destroy() (err error) {
|
||||
// Make sure we clean up after ourselves after everything else below.
|
||||
|
||||
// Close what is necessary.
|
||||
for _, c := range t.ToClose {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ogletest.ExpectEq(nil, c.Close())
|
||||
}
|
||||
|
||||
// If we didn't try to mount the file system, there's nothing further to do.
|
||||
if t.mountSampleErr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// In the background, initiate an unmount.
|
||||
unmountErrChan := make(chan error)
|
||||
go func() {
|
||||
unmountErrChan <- unmount(t.Dir)
|
||||
}()
|
||||
|
||||
// Make sure we wait for the unmount, even if we've already returned early in
|
||||
// error. Return its error if we haven't seen any other error.
|
||||
defer func() {
|
||||
// Wait.
|
||||
unmountErr := <-unmountErrChan
|
||||
if unmountErr != nil {
|
||||
if err != nil {
|
||||
log.Println("unmount:", unmountErr)
|
||||
return
|
||||
}
|
||||
|
||||
err = fmt.Errorf("unmount: %v", unmountErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
ogletest.ExpectEq(nil, os.Remove(t.Dir))
|
||||
}()
|
||||
|
||||
// Wait for the subprocess.
|
||||
if err = <-t.mountSampleErr; err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package samples
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jacobsa/bazilfuse"
|
||||
)
|
||||
|
||||
// Unmount the file system mounted at the supplied directory. Try again on
|
||||
// "resource busy" errors, which happen from time to time on OS X (due to weird
|
||||
// requests from the Finder) and when tests don't or can't synchronize all
|
||||
// events.
|
||||
func unmount(dir string) (err error) {
|
||||
delay := 10 * time.Millisecond
|
||||
for {
|
||||
err = bazilfuse.Unmount(dir)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "resource busy") {
|
||||
log.Println("Resource busy error while unmounting; trying again")
|
||||
time.Sleep(delay)
|
||||
delay = time.Duration(1.3 * float64(delay))
|
||||
continue
|
||||
}
|
||||
|
||||
err = fmt.Errorf("Unmount: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue