123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- // 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.")
- var fDebug = flag.Bool("debug", false, "If true, print fuse debug info.")
- // 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.
- func (t *SubprocessTest) SetUp(ti *ogletest.TestInfo) {
- err := t.initialize(ti.Ctx)
- 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(ctx context.Context) (err error) {
- // Initialize the context.
- t.Ctx = ctx
- // 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
- // Handle debug mode.
- if *fDebug {
- mountCmd.Stderr = os.Stderr
- mountCmd.Args = append(mountCmd.Args, "--debug")
- }
- // Start the command.
- 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
- }
|