subprocess.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. // Copyright 2015 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package samples
  15. import (
  16. "bytes"
  17. "flag"
  18. "fmt"
  19. "io"
  20. "io/ioutil"
  21. "log"
  22. "os"
  23. "os/exec"
  24. "path"
  25. "sync"
  26. "github.com/jacobsa/ogletest"
  27. "golang.org/x/net/context"
  28. )
  29. var fToolPath = flag.String(
  30. "mount_sample",
  31. "",
  32. "Path to the mount_sample tool. If unset, we will compile it.")
  33. var fDebug = flag.Bool("debug", false, "If true, print fuse debug info.")
  34. // A struct that implements common behavior needed by tests in the samples/
  35. // directory where the file system is mounted by a subprocess. Use it as an
  36. // embedded field in your test fixture, calling its SetUp method from your
  37. // SetUp method after setting the MountType and MountFlags fields.
  38. type SubprocessTest struct {
  39. // The type of the file system to mount. Must be recognized by mount_sample.
  40. MountType string
  41. // Additional flags to be passed to the mount_sample tool.
  42. MountFlags []string
  43. // A list of files to pass to mount_sample. The given string flag will be
  44. // used to pass the file descriptor number.
  45. MountFiles map[string]*os.File
  46. // A context object that can be used for long-running operations.
  47. Ctx context.Context
  48. // The directory at which the file system is mounted.
  49. Dir string
  50. // Anothing non-nil in this slice will be closed by TearDown. The test will
  51. // fail if closing fails.
  52. ToClose []io.Closer
  53. mountSampleErr <-chan error
  54. }
  55. // Mount the file system and initialize the other exported fields of the
  56. // struct. Panics on error.
  57. func (t *SubprocessTest) SetUp(ti *ogletest.TestInfo) {
  58. err := t.initialize(ti.Ctx)
  59. if err != nil {
  60. panic(err)
  61. }
  62. }
  63. // Private state for getToolPath.
  64. var getToolContents_Contents []byte
  65. var getToolContents_Err error
  66. var getToolContents_Once sync.Once
  67. // Implementation detail of getToolPath.
  68. func getToolContentsImpl() (contents []byte, err error) {
  69. // Fast path: has the user set the flag?
  70. if *fToolPath != "" {
  71. contents, err = ioutil.ReadFile(*fToolPath)
  72. if err != nil {
  73. err = fmt.Errorf("Reading mount_sample contents: %v", err)
  74. return
  75. }
  76. return
  77. }
  78. // Create a temporary directory into which we will compile the tool.
  79. tempDir, err := ioutil.TempDir("", "sample_test")
  80. if err != nil {
  81. err = fmt.Errorf("TempDir: %v", err)
  82. return
  83. }
  84. toolPath := path.Join(tempDir, "mount_sample")
  85. // Ensure that we kill the temporary directory when we're finished here.
  86. defer os.RemoveAll(tempDir)
  87. // Run "go build".
  88. cmd := exec.Command(
  89. "go",
  90. "build",
  91. "-o",
  92. toolPath,
  93. "github.com/jacobsa/fuse/samples/mount_sample")
  94. output, err := cmd.CombinedOutput()
  95. if err != nil {
  96. err = fmt.Errorf(
  97. "mount_sample exited with %v, output:\n%s",
  98. err,
  99. string(output))
  100. return
  101. }
  102. // Slurp the tool contents.
  103. contents, err = ioutil.ReadFile(toolPath)
  104. if err != nil {
  105. err = fmt.Errorf("ReadFile: %v", err)
  106. return
  107. }
  108. return
  109. }
  110. // Build the mount_sample tool if it has not yet been built for this process.
  111. // Return its contents.
  112. func getToolContents() (contents []byte, err error) {
  113. // Get hold of the binary contents, if we haven't yet.
  114. getToolContents_Once.Do(func() {
  115. getToolContents_Contents, getToolContents_Err = getToolContentsImpl()
  116. })
  117. contents, err = getToolContents_Contents, getToolContents_Err
  118. return
  119. }
  120. func waitForMountSample(
  121. cmd *exec.Cmd,
  122. errChan chan<- error,
  123. stderr *bytes.Buffer) {
  124. // However we exit, write the error to the channel.
  125. var err error
  126. defer func() {
  127. errChan <- err
  128. }()
  129. // Wait for the command.
  130. err = cmd.Wait()
  131. if err == nil {
  132. return
  133. }
  134. // Make exit errors nicer.
  135. if exitErr, ok := err.(*exec.ExitError); ok {
  136. err = fmt.Errorf(
  137. "mount_sample exited with %v. Stderr:\n%s",
  138. exitErr,
  139. stderr.String())
  140. return
  141. }
  142. err = fmt.Errorf("Waiting for mount_sample: %v", err)
  143. }
  144. func waitForReady(readyReader *os.File, c chan<- struct{}) {
  145. _, err := readyReader.Read(make([]byte, 1))
  146. if err != nil {
  147. log.Printf("Readying from ready pipe: %v", err)
  148. return
  149. }
  150. c <- struct{}{}
  151. }
  152. // Like SetUp, but doens't panic.
  153. func (t *SubprocessTest) initialize(ctx context.Context) (err error) {
  154. // Initialize the context.
  155. t.Ctx = ctx
  156. // Set up a temporary directory.
  157. t.Dir, err = ioutil.TempDir("", "sample_test")
  158. if err != nil {
  159. err = fmt.Errorf("TempDir: %v", err)
  160. return
  161. }
  162. // Build/read the mount_sample tool.
  163. toolContents, err := getToolContents()
  164. if err != nil {
  165. err = fmt.Errorf("getTooltoolContents: %v", err)
  166. return
  167. }
  168. // Create a temporary file to hold the contents of the tool.
  169. toolFile, err := ioutil.TempFile("", "sample_test")
  170. if err != nil {
  171. err = fmt.Errorf("TempFile: %v", err)
  172. return
  173. }
  174. defer toolFile.Close()
  175. // Ensure that it is deleted when we leave.
  176. toolPath := toolFile.Name()
  177. defer os.Remove(toolPath)
  178. // Write out the tool contents and make them executable.
  179. if _, err = toolFile.Write(toolContents); err != nil {
  180. err = fmt.Errorf("toolFile.Write: %v", err)
  181. return
  182. }
  183. if err = toolFile.Chmod(0500); err != nil {
  184. err = fmt.Errorf("toolFile.Chmod: %v", err)
  185. return
  186. }
  187. // Close the tool file to prevent "text file busy" errors below.
  188. err = toolFile.Close()
  189. toolFile = nil
  190. if err != nil {
  191. err = fmt.Errorf("toolFile.Close: %v", err)
  192. return
  193. }
  194. // Set up basic args for the subprocess.
  195. args := []string{
  196. "--type",
  197. t.MountType,
  198. "--mount_point",
  199. t.Dir,
  200. }
  201. args = append(args, t.MountFlags...)
  202. // Set up a pipe for the "ready" status.
  203. readyReader, readyWriter, err := os.Pipe()
  204. if err != nil {
  205. err = fmt.Errorf("Pipe: %v", err)
  206. return
  207. }
  208. defer readyReader.Close()
  209. defer readyWriter.Close()
  210. t.MountFiles["ready_file"] = readyWriter
  211. // Set up inherited files and appropriate flags.
  212. var extraFiles []*os.File
  213. for flag, file := range t.MountFiles {
  214. // Cf. os/exec.Cmd.ExtraFiles
  215. fd := 3 + len(extraFiles)
  216. extraFiles = append(extraFiles, file)
  217. args = append(args, "--"+flag)
  218. args = append(args, fmt.Sprintf("%d", fd))
  219. }
  220. // Set up a command.
  221. var stderr bytes.Buffer
  222. mountCmd := exec.Command(toolPath, args...)
  223. mountCmd.Stderr = &stderr
  224. mountCmd.ExtraFiles = extraFiles
  225. // Handle debug mode.
  226. if *fDebug {
  227. mountCmd.Stderr = os.Stderr
  228. mountCmd.Args = append(mountCmd.Args, "--debug")
  229. }
  230. // Start the command.
  231. if err = mountCmd.Start(); err != nil {
  232. err = fmt.Errorf("mountCmd.Start: %v", err)
  233. return
  234. }
  235. // Launch a goroutine that waits for it and returns its status.
  236. mountSampleErr := make(chan error, 1)
  237. go waitForMountSample(mountCmd, mountSampleErr, &stderr)
  238. // Wait for the tool to say the file system is ready. In parallel, watch for
  239. // the tool to fail.
  240. readyChan := make(chan struct{}, 1)
  241. go waitForReady(readyReader, readyChan)
  242. select {
  243. case <-readyChan:
  244. case err = <-mountSampleErr:
  245. return
  246. }
  247. // TearDown is no responsible for joining.
  248. t.mountSampleErr = mountSampleErr
  249. return
  250. }
  251. // Unmount the file system and clean up. Panics on error.
  252. func (t *SubprocessTest) TearDown() {
  253. err := t.destroy()
  254. if err != nil {
  255. panic(err)
  256. }
  257. }
  258. // Like TearDown, but doesn't panic.
  259. func (t *SubprocessTest) destroy() (err error) {
  260. // Make sure we clean up after ourselves after everything else below.
  261. // Close what is necessary.
  262. for _, c := range t.ToClose {
  263. if c == nil {
  264. continue
  265. }
  266. ogletest.ExpectEq(nil, c.Close())
  267. }
  268. // If we didn't try to mount the file system, there's nothing further to do.
  269. if t.mountSampleErr == nil {
  270. return
  271. }
  272. // In the background, initiate an unmount.
  273. unmountErrChan := make(chan error)
  274. go func() {
  275. unmountErrChan <- unmount(t.Dir)
  276. }()
  277. // Make sure we wait for the unmount, even if we've already returned early in
  278. // error. Return its error if we haven't seen any other error.
  279. defer func() {
  280. // Wait.
  281. unmountErr := <-unmountErrChan
  282. if unmountErr != nil {
  283. if err != nil {
  284. log.Println("unmount:", unmountErr)
  285. return
  286. }
  287. err = fmt.Errorf("unmount: %v", unmountErr)
  288. return
  289. }
  290. // Clean up.
  291. ogletest.ExpectEq(nil, os.Remove(t.Dir))
  292. }()
  293. // Wait for the subprocess.
  294. if err = <-t.mountSampleErr; err != nil {
  295. return
  296. }
  297. return
  298. }