libfuse3-0.1.1.1: test/integtest/Main.hs
-- Please note that @cabal v2-test@ handles the test output very badly for some reason;
-- the output is duplicated and interleaved.
--
-- Use @cabal v2-run -- integtest@ for more reliable output.
module Main where
import Control.Exception (SomeException, finally)
import Control.Monad (when)
import Data.Bits ((.|.))
import Data.Maybe (isNothing)
import Foreign.C (eIO, eNOENT)
import System.Directory (findExecutable, listDirectory)
import System.Environment (withArgs)
import System.Exit (exitFailure)
import System.FilePath ((</>))
import System.IO (hPrint, hPutStrLn, stderr)
import System.IO.Temp (withSystemTempDirectory)
import System.LibFuse3
import System.Posix.Files (directoryMode, regularFileMode)
import System.Posix.Process (getProcessStatus, forkProcess)
import System.Process (callProcess)
import Test.Hspec (Spec, SpecWith, around, describe, hspec, it, shouldBe)
import qualified Data.ByteString as B
-- | Runs a spec on a FUSE filesystem mounted on a temporary directory.
withFileSystem :: FuseOperations fh dh -> SpecWith FilePath -> Spec
withFileSystem ops = around $ \theSpec ->
withSystemTempDirectory "libfuse3test" $ \mountPoint -> do
let unmount = do
hPutStrLn stderr $ "unmounting : " <> mountPoint
callProcess "fusermount3" ["-u", mountPoint]
withArgs [mountPoint] $ do
hPutStrLn stderr $ "mounting on: " <> mountPoint
pid <- forkProcess $ fuseMain ops (\e -> hPrint stderr (e :: SomeException) >> pure eIO)
flip finally unmount $ do
-- wait for fuseMain to daemonize
_ <- getProcessStatus True False pid
theSpec mountPoint
specs :: Spec
specs = do
-- A basic test of fuseGetattr; we get what we give
describe "fuseGetattr" $
let stat = defaultFileStat
{ fileMode = directoryMode .|. 0o755
, linkCount = 1
}
ops = defaultFuseOperations
{ fuseGetattr = Just $ \path _ -> case path of
"/" -> pure $ Right stat
_ -> pure $ Left eNOENT
}
in withFileSystem ops $ it "getFileStat reads the stat as is" $ \mountPoint -> do
-- assumes that `getFileStat` works correctly
stat' <- getFileStat mountPoint
-- ignore differences in fileID
-- TODO test use_ino?
stat' { fileID = fileID stat } `shouldBe` stat
-- If `fuseOpen` is not defined, make sure that `fuseRead` doesn't throw unless
-- the file handle is evaluated.
describe "fuseRead without fuseOpen" $
let content = B.singleton 0
ops = defaultFuseOperations
{ fuseGetattr = Just $ \path mfh ->
case mfh of
Just _ -> pure $ Left eIO -- this should never happen
Nothing
| path == "/file" -> pure $ Right $ defaultFileStat
{ fileMode = regularFileMode .|. 0o644
, fileSize = 1
}
| otherwise -> pure $ Left eNOENT
, fuseRead = Just $ \path _fh len off ->
-- check that evaluating the args other than the file handle is harmless
path `seq` len `seq` off `seq` (pure $ Right content)
}
in withFileSystem ops $ it "fileRead reads without a crash" $ \mountPoint -> do
readContent <- B.readFile $ mountPoint </> "file"
readContent `shouldBe` content
-- If `fuseOpendir` is not defined, make sure that `fuseReaddir` doesn't throw unless
-- the directory handle is evaluated.
describe "fuseReaddir without fuseOpendir" $
let ops = defaultFuseOperations
{ fuseGetattr = Just $ \path _ ->
case () of
_ | path == "/dir/file" -> pure $ Right $ defaultFileStat
{ fileMode = regularFileMode .|. 0o644
, fileSize = 1
}
| path == "/dir" -> pure $ Right $ defaultFileStat
{ fileMode = directoryMode .|. 0o755
}
| otherwise -> pure $ Left eNOENT
, fuseReaddir = Just $ \path _dh ->
path `seq` (pure $ Right $ [(".", Nothing), ("..", Nothing), ("file", Nothing)])
}
in withFileSystem ops $ it "fileReaddir reads without a crash" $ \mountPoint -> do
entries <- listDirectory $ mountPoint </> "dir"
entries `shouldBe` ["file"]
main :: IO ()
main = do
mfusermount3 <- findExecutable "fusermount3"
when (isNothing mfusermount3) $ do
hPutStrLn stderr "Command 'fusermount3' not found. Please install it first. It usually comes with a package named like 'fuse3'."
exitFailure
hspec specs