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
|
package flushfs_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jacobsa/fuse"
|
"github.com/jacobsa/bazilfuse"
|
||||||
|
"github.com/jacobsa/fuse/fsutil"
|
||||||
"github.com/jacobsa/fuse/samples"
|
"github.com/jacobsa/fuse/samples"
|
||||||
"github.com/jacobsa/fuse/samples/flushfs"
|
|
||||||
. "github.com/jacobsa/oglematchers"
|
. "github.com/jacobsa/oglematchers"
|
||||||
. "github.com/jacobsa/ogletest"
|
. "github.com/jacobsa/ogletest"
|
||||||
)
|
)
|
||||||
|
@ -37,54 +38,59 @@ func TestFlushFS(t *testing.T) { RunTests(t) }
|
||||||
// Boilerplate
|
// Boilerplate
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
type FlushFSTest struct {
|
type flushFSTest struct {
|
||||||
samples.SampleTest
|
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.
|
// File handles that are closed in TearDown if non-nil.
|
||||||
f1 *os.File
|
f1 *os.File
|
||||||
f2 *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
|
var err error
|
||||||
|
|
||||||
// Set up a file system.
|
// Set up files to receive flush and fsync reports.
|
||||||
reportTo := func(slice *[]string, err *error) func(string) error {
|
t.flushes, err = fsutil.AnonymousFile("")
|
||||||
return func(s string) error {
|
AssertEq(nil, err)
|
||||||
t.mu.Lock()
|
|
||||||
defer t.mu.Unlock()
|
|
||||||
|
|
||||||
*slice = append(*slice, s)
|
t.fsyncs, err = fsutil.AnonymousFile("")
|
||||||
return *err
|
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(
|
t.MountFiles = map[string]*os.File{
|
||||||
reportTo(&t.flushes, &t.flushErr),
|
"flushfs.flushes_file": t.flushes,
|
||||||
reportTo(&t.fsyncs, &t.fsyncErr))
|
"flushfs.fsyncs_file": t.fsyncs,
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mount it.
|
t.SubprocessTest.SetUp(ti)
|
||||||
t.SampleTest.SetUp(ti)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FlushFSTest) TearDown() {
|
func (t *flushFSTest) TearDown() {
|
||||||
// Close files if non-nil.
|
// 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 {
|
if t.f1 != nil {
|
||||||
ExpectEq(nil, t.f1.Close())
|
ExpectEq(nil, t.f1.Close())
|
||||||
}
|
}
|
||||||
|
@ -94,53 +100,66 @@ func (t *FlushFSTest) TearDown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finish tearing down.
|
// Finish tearing down.
|
||||||
t.SampleTest.TearDown()
|
t.SubprocessTest.TearDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
// Helpers
|
// Helpers
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Return a copy of the current contents of t.flushes.
|
func readReports(f *os.File) (reports []string, err error) {
|
||||||
//
|
// Seek the file to the start.
|
||||||
// LOCKS_EXCLUDED(t.mu)
|
_, err = f.Seek(0, 0)
|
||||||
func (t *FlushFSTest) getFlushes() (p []string) {
|
if err != nil {
|
||||||
t.mu.Lock()
|
err = fmt.Errorf("Seek: %v", err)
|
||||||
defer t.mu.Unlock()
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a copy of the current contents of t.fsyncs.
|
// Return a copy of the current contents of t.fsyncs.
|
||||||
//
|
func (t *flushFSTest) getFsyncs() (p []string) {
|
||||||
// LOCKS_EXCLUDED(t.mu)
|
var err error
|
||||||
func (t *FlushFSTest) getFsyncs() (p []string) {
|
if p, err = readReports(t.fsyncs); err != nil {
|
||||||
t.mu.Lock()
|
panic(err)
|
||||||
defer t.mu.Unlock()
|
}
|
||||||
|
|
||||||
p = make([]string, len(t.fsyncs))
|
|
||||||
copy(p, t.fsyncs)
|
|
||||||
return
|
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
|
// Like syscall.Dup2, but correctly annotates the syscall as blocking. See here
|
||||||
// for more info: https://github.com/golang/go/issues/10202
|
// for more info: https://github.com/golang/go/issues/10202
|
||||||
func dup2(oldfd int, newfd int) (err error) {
|
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 n int
|
||||||
var off int64
|
var off int64
|
||||||
var err error
|
var err error
|
||||||
|
@ -196,7 +226,7 @@ func (t *FlushFSTest) CloseReports_ReadWrite() {
|
||||||
ExpectThat(t.getFsyncs(), ElementsAre())
|
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FlushFSTest) CloseReports_ReadOnly() {
|
func (t *NoErrorsTest) Close_ReadOnly() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Open the file.
|
// Open the file.
|
||||||
|
@ -217,7 +247,7 @@ func (t *FlushFSTest) CloseReports_ReadOnly() {
|
||||||
ExpectThat(t.getFsyncs(), ElementsAre())
|
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FlushFSTest) CloseReports_WriteOnly() {
|
func (t *NoErrorsTest) Close_WriteOnly() {
|
||||||
var n int
|
var n int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -244,7 +274,7 @@ func (t *FlushFSTest) CloseReports_WriteOnly() {
|
||||||
ExpectThat(t.getFsyncs(), ElementsAre())
|
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FlushFSTest) CloseReports_MultipleTimes_NonOverlappingFileHandles() {
|
func (t *NoErrorsTest) Close_MultipleTimes_NonOverlappingFileHandles() {
|
||||||
var n int
|
var n int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -291,7 +321,7 @@ func (t *FlushFSTest) CloseReports_MultipleTimes_NonOverlappingFileHandles() {
|
||||||
AssertThat(t.getFsyncs(), ElementsAre())
|
AssertThat(t.getFsyncs(), ElementsAre())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FlushFSTest) CloseReports_MultipleTimes_OverlappingFileHandles() {
|
func (t *NoErrorsTest) Close_MultipleTimes_OverlappingFileHandles() {
|
||||||
var n int
|
var n int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -340,25 +370,7 @@ func (t *FlushFSTest) CloseReports_MultipleTimes_OverlappingFileHandles() {
|
||||||
AssertThat(t.getFsyncs(), ElementsAre())
|
AssertThat(t.getFsyncs(), ElementsAre())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FlushFSTest) CloseError() {
|
func (t *NoErrorsTest) Fsync() {
|
||||||
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() {
|
|
||||||
var n int
|
var n int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -397,24 +409,7 @@ func (t *FlushFSTest) FsyncReports() {
|
||||||
AssertThat(t.getFsyncs(), ElementsAre("taco", "tacos"))
|
AssertThat(t.getFsyncs(), ElementsAre("taco", "tacos"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FlushFSTest) FsyncError() {
|
func (t *NoErrorsTest) Dup() {
|
||||||
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() {
|
|
||||||
var n int
|
var n int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -478,46 +473,7 @@ func (t *FlushFSTest) Dup() {
|
||||||
ExpectThat(t.getFsyncs(), ElementsAre())
|
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FlushFSTest) Dup_FlushError() {
|
func (t *NoErrorsTest) Dup2() {
|
||||||
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() {
|
|
||||||
var n int
|
var n int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -530,11 +486,8 @@ func (t *FlushFSTest) Dup2() {
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
AssertEq(4, n)
|
AssertEq(4, n)
|
||||||
|
|
||||||
// Open and unlink some temporary file.
|
// Create some anonymous temporary file.
|
||||||
t.f2, err = ioutil.TempFile("", "")
|
t.f2, err = fsutil.AnonymousFile("")
|
||||||
AssertEq(nil, err)
|
|
||||||
|
|
||||||
err = os.Remove(t.f2.Name())
|
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
// Duplicate the temporary file descriptor on top of the file from our file
|
// 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())
|
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FlushFSTest) Dup2_FlushError() {
|
func (t *NoErrorsTest) Mmap_MunmapBeforeClose() {
|
||||||
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() {
|
|
||||||
var n int
|
var n int
|
||||||
var err error
|
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 n int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -683,6 +613,118 @@ func (t *FlushFSTest) Mmap_CloseBeforeMunmap() {
|
||||||
ExpectThat(t.getFsyncs(), ElementsAre())
|
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FlushFSTest) Directory() {
|
func (t *NoErrorsTest) Directory() {
|
||||||
AssertTrue(false, "TODO")
|
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"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/googlecloudplatform/gcsfuse/timeutil"
|
"github.com/googlecloudplatform/gcsfuse/timeutil"
|
||||||
|
@ -55,8 +54,8 @@ type SampleTest struct {
|
||||||
mfs *fuse.MountedFileSystem
|
mfs *fuse.MountedFileSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mount the supplied file system and initialize the other exported fields of
|
// Mount t.FileSystem and initialize the other exported fields of the struct.
|
||||||
// the struct. Panics on error.
|
// Panics on error.
|
||||||
//
|
//
|
||||||
// REQUIRES: t.FileSystem has been set.
|
// REQUIRES: t.FileSystem has been set.
|
||||||
func (t *SampleTest) SetUp(ti *ogletest.TestInfo) {
|
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(
|
func (t *SampleTest) initialize(
|
||||||
fs fuse.FileSystem,
|
fs fuse.FileSystem,
|
||||||
config *fuse.MountConfig) (err error) {
|
config *fuse.MountConfig) (err error) {
|
||||||
|
@ -90,7 +89,7 @@ func (t *SampleTest) initialize(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for it to be read.
|
// Wait for it to be ready.
|
||||||
err = t.mfs.WaitForReady(t.Ctx)
|
err = t.mfs.WaitForReady(t.Ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("WaitForReady: %v", err)
|
err = fmt.Errorf("WaitForReady: %v", err)
|
||||||
|
@ -124,27 +123,16 @@ func (t *SampleTest) destroy() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmount the file system. Try again on "resource busy" errors.
|
// Unmount the file system.
|
||||||
delay := 10 * time.Millisecond
|
err = unmount(t.Dir)
|
||||||
for {
|
if err != nil {
|
||||||
err = t.mfs.Unmount()
|
err = fmt.Errorf("unmount: %v", err)
|
||||||
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)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = t.mfs.Join(t.Ctx); err != nil {
|
// Unlink the mount point.
|
||||||
err = fmt.Errorf("MountedFileSystem.Join: %v", err)
|
if err = os.Remove(t.Dir); err != nil {
|
||||||
|
err = fmt.Errorf("Unlinking mount point: %v", err)
|
||||||
return
|
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