{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} module Dojang.MonadFileSystemSpec (spec) where import Control.Monad.IO.Class (MonadIO (liftIO)) import Data.List (sort, sortOn) import GHC.IO.Exception (IOErrorType (InappropriateType)) import System.IO.Error ( alreadyExistsErrorType , doesNotExistErrorType , ioeGetErrorType , ioeGetFileName , ioeGetLocation , isDoesNotExistError ) import Prelude hiding (readFile, writeFile) import Prelude qualified (readFile, writeFile) import Control.Monad.Except (MonadError (catchError), tryError) import Data.ByteString qualified (readFile, writeFile) import Hedgehog.Gen qualified as Gen import Hedgehog.Range (constantFrom) import System.Directory.OsPath ( createDirectoryLink , createFileLink , doesDirectoryExist , doesFileExist , doesPathExist ) import System.Directory.OsPath qualified (createDirectory) import System.FilePath (combine) import System.OsPath (dropFileName, encodeFS, ()) import Test.Hspec (Spec, describe, it, runIO, specify, xit, xspecify) import Test.Hspec.Expectations.Pretty ( shouldBe , shouldContain , shouldReturn , shouldSatisfy , shouldStartWith , shouldThrow ) import Test.Hspec.Hedgehog (forAll, hedgehog, (===)) import Dojang.MonadFileSystem ( FileType (..) , MonadFileSystem (..) , dryRunIO , tryDryRunIO ) import Dojang.TestUtils (withTempDir) packageYamlFP :: FilePath packageYamlFP = "package.yaml" testFP :: FilePath testFP = "test" nonExistentFP :: FilePath nonExistentFP = "---non-existent---" nonExistentFP' :: FilePath nonExistentFP' = "---non-existent-2---" spec :: Spec spec = do packageYamlP <- runIO $ encodeFS packageYamlFP testP <- runIO $ encodeFS testFP nonExistentP <- runIO $ encodeFS nonExistentFP nonExistentP' <- runIO $ encodeFS nonExistentFP' -- cSpell:ignore quux corge foo <- runIO $ encodeFS "foo" bar <- runIO $ encodeFS "bar" baz <- runIO $ encodeFS "baz" qux <- runIO $ encodeFS "qux" quux <- runIO $ encodeFS "quux" corge <- runIO $ encodeFS "corge" symlinkAvailable <- runIO $ withTempDir $ \tmpDir tmpDir' -> ( do Prelude.writeFile (tmpDir' `combine` "foo") "" createFileLink foo (tmpDir bar) return True ) `catchError` const (return False) let symIt = if symlinkAvailable then it else xit let symSpecify = if symlinkAvailable then specify else xspecify let withFixture action = withTempDir $ \tmpDir tmpDir' -> do () <- Prelude.writeFile (tmpDir' `combine` "foo") "" () <- System.Directory.OsPath.createDirectory $ tmpDir bar () <- System.Directory.OsPath.createDirectory $ tmpDir baz () <- System.Directory.OsPath.createDirectory (tmpDir baz qux) action tmpDir tmpDir' describe "MonadFileSystem IO" $ do specify "encodePath" $ hedgehog $ do filePath <- forAll $ Gen.string (constantFrom 0 0 256) Gen.unicode filePath' <- liftIO $ encodePath filePath >>= decodePath filePath' === filePath specify "exists" $ do exists packageYamlP `shouldReturn` True exists testP `shouldReturn` True exists nonExistentP `shouldReturn` False specify "isFile" $ do isFile packageYamlP `shouldReturn` True isFile testP `shouldReturn` False isFile nonExistentP `shouldReturn` False specify "isDirectory" $ do isDirectory packageYamlP `shouldReturn` False isDirectory testP `shouldReturn` True isDirectory nonExistentP `shouldReturn` False symSpecify "isSymlink" $ do isSymlink packageYamlP `shouldReturn` False isSymlink testP `shouldReturn` False isSymlink nonExistentP `shouldReturn` False withTempDir $ \tmpDir tmpDir' -> do Prelude.writeFile (tmpDir' `combine` "foo") "" createFileLink foo (tmpDir bar) isSymlink (tmpDir bar) `shouldReturn` True specify "readFile" $ withTempDir $ \tmpDir tmpDir' -> do () <- Prelude.writeFile (tmpDir' `combine` "foo") "Foo contents" contents <- readFile $ tmpDir foo contents `shouldBe` "Foo contents" Left error' <- tryError $ readFile nonExistentP ioeGetErrorType error' `shouldBe` doesNotExistErrorType ioeGetFileName error' `shouldBe` Just nonExistentFP specify "writeFile" $ withTempDir $ \tmpDirP tmpDirFP -> do () <- writeFile (tmpDirP nonExistentP) "foo" Data.ByteString.readFile (tmpDirFP `combine` nonExistentFP) `shouldReturn` "foo" describe "readSymlinkTarget" $ do symIt "tells the target path of a symbolic link" $ withTempDir $ \tmpDir tmpDir' -> do () <- Prelude.writeFile (tmpDir' `combine` "foo") "" createFileLink foo (tmpDir bar) readSymlinkTarget (tmpDir bar) `shouldReturn` foo it "fails with IOError if the path does not exist" $ do readSymlinkTarget nonExistentP `shouldThrow` \e -> do isDoesNotExistError e && ioeGetFileName e == Just nonExistentFP specify "copyFile" $ withTempDir $ \tmpDirP tmpDirFP -> do () <- copyFile packageYamlP (tmpDirP nonExistentP) contents <- Data.ByteString.readFile (tmpDirFP `combine` nonExistentFP) original <- Data.ByteString.readFile packageYamlFP contents `shouldBe` original specify "createDirectory" $ withTempDir $ \tmpDirP _ -> do () <- createDirectory (tmpDirP nonExistentP) doesDirectoryExist (tmpDirP nonExistentP) `shouldReturn` True specify "removeFile" $ withTempDir $ \tmpDirP tmpDirFP -> do Data.ByteString.writeFile (tmpDirFP `combine` nonExistentFP) "" doesFileExist (tmpDirP nonExistentP) `shouldReturn` True () <- removeFile (tmpDirP nonExistentP) doesFileExist (tmpDirP nonExistentP) `shouldReturn` False specify "removeDirectory" $ withTempDir $ \tmpDirP tmpDirFP -> do () <- createDirectory (tmpDirP nonExistentP) () <- removeDirectory (tmpDirP nonExistentP) doesDirectoryExist (tmpDirP nonExistentP) `shouldReturn` False removeDirectory (tmpDirP nonExistentP) `shouldThrow` \e -> isDoesNotExistError e && (ioeGetFileName e == Just (tmpDirFP `combine` nonExistentFP)) specify "removeDirectoryRecursively" $ withFixture $ \tmpDirP tmpDirFP -> do removeDirectoryRecursively tmpDirP doesPathExist tmpDirP `shouldReturn` False removeDirectoryRecursively tmpDirP `shouldThrow` \e -> isDoesNotExistError e && ioeGetFileName e == Just tmpDirFP specify "listDirectory" $ withFixture $ \tmpDir _ -> do result <- listDirectory tmpDir sort result `shouldBe` [bar, baz, foo] describe "listDirectoryRecursively" $ do specify "basic behavior" $ withFixture $ \tmpDir tmpDir' -> do () <- Prelude.writeFile (tmpDir' `combine` "bar" `combine` "quux") "" () <- Prelude.writeFile (tmpDir' `combine` "baz" `combine` "corge") "" result <- listDirectoryRecursively tmpDir [] sortOn snd result `shouldBe` [ (Directory, bar) , (File, bar quux) , (Directory, baz) , (File, baz corge) , (Directory, baz qux) , (File, foo) ] it "filters out files by ignorePatterns" $ withFixture $ \tmpDir tmpDir' -> do () <- Prelude.writeFile (tmpDir' `combine` "bar" `combine` "quux") "" () <- Prelude.writeFile (tmpDir' `combine` "baz" `combine` "corge") "" result <- listDirectoryRecursively tmpDir ["bar/q*", "baz"] sortOn snd result `shouldBe` [(Directory, bar), (File, foo)] symIt "distinguishes symlinks from regular files and directories" $ withFixture $ \tmpDir tmpDir' -> do () <- Prelude.writeFile (tmpDir' `combine` "bar" `combine` "quux") "" () <- createFileLink quux (tmpDir baz corge) () <- createDirectoryLink baz (tmpDir corge) result <- listDirectoryRecursively tmpDir [] sortOn snd result `shouldBe` [ (Directory, bar) , (File, bar quux) , (Directory, baz) , (Symlink, baz corge) , (Directory, baz qux) , (Symlink, corge) , (File, foo) ] specify "getFileSize" $ withFixture $ \tmpDir tmpDirFP -> do Data.ByteString.writeFile (tmpDirFP `combine` "foo") "asdf" getFileSize (tmpDir foo) `shouldReturn` 4 Left e <- tryError $ getFileSize (tmpDir nonExistentP) ioeGetErrorType e `shouldBe` doesNotExistErrorType ioeGetFileName e `shouldBe` Just (tmpDirFP `combine` nonExistentFP) Left e' <- tryError $ getFileSize tmpDir ioeGetErrorType e' `shouldBe` InappropriateType ioeGetLocation e' `shouldBe` "getFileSize" ioeGetFileName e' `shouldBe` Just tmpDirFP show e' `shouldContain` "it is a directory" describe "DryRunIO" $ do specify "encodePath" $ hedgehog $ do filePath <- forAll $ Gen.string (constantFrom 0 0 256) Gen.unicode filePath' <- liftIO $ dryRunIO (encodePath filePath >>= decodePath) filePath' === filePath describe "isFile" $ do it "checks an actual file that exists on the real file system" $ do packageYamlIsFile <- dryRunIO $ isFile packageYamlP packageYamlIsFile `shouldBe` True it "checks an actual directory that exists on the real file system" $ do testIsFile <- dryRunIO $ isFile testP testIsFile `shouldBe` False it "checks a non-existent file" $ do nonExistentIsFile <- dryRunIO $ isFile nonExistentP nonExistentIsFile `shouldBe` False it "checks a virtual file (Contents) that exists in memory" $ do nonExistentIsFile <- dryRunIO $ do () <- writeFile nonExistentP "" isFile nonExistentP nonExistentIsFile `shouldBe` True it "checks a virtual file (Copied) that exists in memory" $ do nonExistentIsFile <- dryRunIO $ do () <- copyFile packageYamlP nonExistentP isFile nonExistentP nonExistentIsFile `shouldBe` True it "checks a virtual directory that exists in memory" $ do nonExistentIsFile <- dryRunIO $ do () <- createDirectory nonExistentP isFile nonExistentP nonExistentIsFile `shouldBe` False it "checks a virtual non-existent file" $ do packageYamlIsFile <- dryRunIO $ do () <- removeFile packageYamlP isFile packageYamlP packageYamlIsFile `shouldBe` False describe "isDirectory" $ do it "check an actual file that exists on the real file system" $ do packageYamlIsDir <- dryRunIO $ isDirectory packageYamlP packageYamlIsDir `shouldBe` False it "checks an actual directory that exists on the real file system" $ do testIsDir <- dryRunIO $ isDirectory testP testIsDir `shouldBe` True it "checks a non-existent directory" $ do nonExistentIsDir <- dryRunIO $ isDirectory nonExistentP nonExistentIsDir `shouldBe` False it "checks a virtual directory that exists in memory" $ do nonExistentIsDir' <- dryRunIO $ do () <- createDirectory nonExistentP isDirectory nonExistentP nonExistentIsDir' `shouldBe` True it "checks a virtual file (Copied) that exists in memory" $ do nonExistentIsDir'' <- dryRunIO $ do () <- copyFile packageYamlP nonExistentP isDirectory nonExistentP nonExistentIsDir'' `shouldBe` False it "checks a virtual file (Contents) that exists in memory" $ do nonExistentIsDir''' <- dryRunIO $ do () <- writeFile nonExistentP "" isDirectory nonExistentP nonExistentIsDir''' `shouldBe` False it "checks a virtual non-existent directory" $ do packageYamlIsFile <- dryRunIO $ do () <- removeFile packageYamlP isDirectory packageYamlP packageYamlIsFile `shouldBe` False describe "isSymlink" $ do symIt "checks an actual symlink that exists on the real file system" $ withTempDir $ \tmpDir tmpDir' -> do () <- Prelude.writeFile (tmpDir' `combine` "foo") "" createFileLink foo (tmpDir bar) dryRunIO (isSymlink $ tmpDir bar) `shouldReturn` True it "return False if a path does not exist" $ do dryRunIO (isSymlink nonExistentP) `shouldReturn` False it "checks a virtual file that exists in memory" $ do nonExistentExists' <- dryRunIO $ do () <- writeFile nonExistentP "" isSymlink nonExistentP nonExistentExists' `shouldBe` False describe "exists" $ do it "checks an actual file that exists on the real file system" $ do packageYamlExists <- dryRunIO $ exists packageYamlP packageYamlExists `shouldBe` True it "checks an actual directory that exists on the real file system" $ do testExists <- dryRunIO $ exists testP testExists `shouldBe` True it "checks a non-existent file" $ do nonExistentExists <- dryRunIO $ exists nonExistentP nonExistentExists `shouldBe` False it "checks a virtual file (Contents) that exists in memory" $ do nonExistentExists' <- dryRunIO $ do () <- writeFile nonExistentP "" exists nonExistentP nonExistentExists' `shouldBe` True it "checks a virtual file (Copied) that exists in memory" $ do nonExistentExists'' <- dryRunIO $ do () <- copyFile packageYamlP nonExistentP exists nonExistentP nonExistentExists'' `shouldBe` True it "checks a virtual directory that exists in memory" $ do nonExistentExists''' <- dryRunIO $ do () <- createDirectory nonExistentP exists nonExistentP nonExistentExists''' `shouldBe` True it "checks a virtual non-existent file" $ do packageYamlIsFile <- dryRunIO $ do () <- removeFile packageYamlP exists packageYamlP packageYamlIsFile `shouldBe` False describe "readFile" $ do it "can read an actual file that exists on the real file system" $ withTempDir $ \tmpDir tmpDir' -> do () <- Prelude.writeFile (tmpDir' `combine` "foo") "Foo contents" packageYamlData <- dryRunIO $ readFile $ tmpDir foo packageYamlData `shouldBe` "Foo contents" it "can't read a directory that exists on the real file system" $ do Left failToReadTest <- tryDryRunIO $ readFile testP ioeGetErrorType failToReadTest `shouldBe` InappropriateType ioeGetFileName failToReadTest `shouldBe` Just testFP it "can't read a non-existent file" $ do Left failToReadNonExistent <- tryDryRunIO $ readFile nonExistentP ioeGetErrorType failToReadNonExistent `shouldBe` doesNotExistErrorType ioeGetFileName failToReadNonExistent `shouldBe` Just nonExistentFP it "can read a virtual file (Contents) that exists in memory" $ do nonExistentData <- dryRunIO $ do () <- writeFile nonExistentP "foo" readFile nonExistentP nonExistentData `shouldBe` "foo" it "can read a virtual file (Copied) that exists in memory" $ withTempDir $ \tmpDir tmpDir' -> do Prelude.writeFile (tmpDir' `combine` "foo") "Foo contents" nonExistentData <- dryRunIO $ do () <- copyFile (tmpDir foo) nonExistentP () <- writeFile packageYamlP "foo" -- must not affect the result () <- copyFile nonExistentP nonExistentP' () <- writeFile nonExistentP "bar" -- must not affect the result readFile nonExistentP' nonExistentData `shouldBe` "Foo contents" it "can't read a virtual directory that exists in memory" $ do Left failToReadNonExistent <- tryDryRunIO $ do () <- createDirectory nonExistentP readFile nonExistentP ioeGetErrorType failToReadNonExistent `shouldBe` InappropriateType ioeGetFileName failToReadNonExistent `shouldBe` Just nonExistentFP ioeGetLocation failToReadNonExistent `shouldBe` "readFile" show failToReadNonExistent `shouldContain` "is a directory" it "can't read a virtual non-existent file" $ do Left failToReadNonExistent <- tryDryRunIO $ do () <- removeFile packageYamlP readFile packageYamlP ioeGetErrorType failToReadNonExistent `shouldBe` doesNotExistErrorType ioeGetFileName failToReadNonExistent `shouldBe` Just packageYamlFP ioeGetLocation failToReadNonExistent `shouldBe` "readFile" show failToReadNonExistent `shouldContain` "no such file" describe "writeFile" $ do it "can write data to an non-existent file" $ do data' <- dryRunIO $ do () <- writeFile nonExistentP "foo" readFile nonExistentP data' `shouldBe` "foo" withTempDir $ \tmpDir tmpDir' -> do () <- Prelude.writeFile (tmpDir' `combine` "foo") "Foo contents" data'' <- dryRunIO $ do () <- removeFile $ tmpDir foo () <- writeFile (tmpDir foo) "bar" readFile $ tmpDir foo data'' `shouldBe` "bar" Prelude.readFile (tmpDir' `combine` "foo") `shouldReturn` "Foo contents" it "can write data to an existing file" $ withTempDir $ \tmpDir tmpDir' -> do () <- Prelude.writeFile (tmpDir' `combine` "foo") "Foo contents" data' <- dryRunIO $ do () <- writeFile (tmpDir foo) "modified" readFile (tmpDir foo) data' `shouldBe` "modified" Prelude.readFile (tmpDir' `combine` "foo") `shouldReturn` "Foo contents" data'' <- dryRunIO $ do () <- writeFile nonExistentP "bar" () <- writeFile nonExistentP "baz" readFile nonExistentP data'' `shouldBe` "baz" doesFileExist nonExistentP `shouldReturn` False it "can't write data to a directory" $ do Left failToWriteToTest <- tryDryRunIO $ writeFile testP "foo" ioeGetErrorType failToWriteToTest `shouldBe` InappropriateType ioeGetFileName failToWriteToTest `shouldBe` Just testFP ioeGetLocation failToWriteToTest `shouldBe` "writeFile" show failToWriteToTest `shouldContain` "is a directory" Left failToWriteToTest' <- tryDryRunIO $ do () <- createDirectory nonExistentP writeFile nonExistentP "foo" ioeGetErrorType failToWriteToTest' `shouldBe` InappropriateType ioeGetFileName failToWriteToTest' `shouldBe` Just nonExistentFP ioeGetLocation failToWriteToTest' `shouldBe` "writeFile" show failToWriteToTest' `shouldContain` "is a directory" it "can't write data to a file path in a non-existent directory" $ do Left failToWrite <- tryDryRunIO $ do writeFile (nonExistentP nonExistentP) "foo" ioeGetErrorType failToWrite `shouldBe` doesNotExistErrorType ioeGetFileName failToWrite `shouldBe` Just (nonExistentFP `combine` nonExistentFP) ioeGetLocation failToWrite `shouldBe` "writeFile" show failToWrite `shouldContain` "no parent directory" Left failToWrite' <- tryDryRunIO $ do () <- removeFile packageYamlP writeFile (packageYamlP nonExistentP) "foo" ioeGetErrorType failToWrite' `shouldBe` doesNotExistErrorType ioeGetFileName failToWrite' `shouldBe` Just (packageYamlFP `combine` nonExistentFP) ioeGetLocation failToWrite' `shouldBe` "writeFile" show failToWrite' `shouldContain` "no parent directory" it "can't write data to a file path inside a non-directory file" $ do Left failToWrite <- tryDryRunIO $ do writeFile (packageYamlP nonExistentP) "bar" ioeGetErrorType failToWrite `shouldBe` InappropriateType ioeGetFileName failToWrite `shouldBe` Just (packageYamlFP `combine` nonExistentFP) ioeGetLocation failToWrite `shouldBe` "writeFile" show failToWrite `shouldContain` "not inside a directory" Left failToWrite' <- tryDryRunIO $ do () <- writeFile nonExistentP "" writeFile (nonExistentP nonExistentP) "bar" ioeGetErrorType failToWrite' `shouldBe` InappropriateType ioeGetFileName failToWrite' `shouldBe` Just (nonExistentFP `combine` nonExistentFP) ioeGetLocation failToWrite' `shouldBe` "writeFile" show failToWrite' `shouldContain` "not inside a directory" Left failToWrite'' <- tryDryRunIO $ do () <- copyFile packageYamlP nonExistentP writeFile (nonExistentP nonExistentP) "bar" ioeGetErrorType failToWrite'' `shouldBe` InappropriateType ioeGetFileName failToWrite'' `shouldBe` Just (nonExistentFP `combine` nonExistentFP) ioeGetLocation failToWrite'' `shouldBe` "writeFile" show failToWrite'' `shouldContain` "not inside a directory" describe "readSymlinkTarget" $ do symIt "returns the target path of a symbolic link" $ withTempDir $ \tmpDir tmpDir' -> do () <- Prelude.writeFile (tmpDir' `combine` "foo") "" createFileLink foo (tmpDir bar) dryRunIO (readSymlinkTarget $ tmpDir bar) `shouldReturn` foo it "fails with non-existent file" $ do dryRunIO (readSymlinkTarget nonExistentP) `shouldThrow` \e -> isDoesNotExistError e && ioeGetFileName e == Just nonExistentFP it "just fails with any virtual files as they can't be a symlink" $ do Left failToReadSymlink <- tryDryRunIO $ do () <- writeFile nonExistentP "" readSymlinkTarget nonExistentP ioeGetErrorType failToReadSymlink `shouldBe` InappropriateType ioeGetFileName failToReadSymlink `shouldBe` Just nonExistentFP ioeGetLocation failToReadSymlink `shouldBe` "readSymlinkTarget" show failToReadSymlink `shouldContain` "not a symbolic link" it "fails with an overlaid removal tag" $ do Left failToReadSymlink <- tryDryRunIO $ do () <- writeFile nonExistentP "" () <- removeFile nonExistentP readSymlinkTarget nonExistentP ioeGetErrorType failToReadSymlink `shouldBe` doesNotExistErrorType ioeGetFileName failToReadSymlink `shouldBe` Just nonExistentFP ioeGetLocation failToReadSymlink `shouldBe` "readSymlinkTarget" show failToReadSymlink `shouldContain` "no such file" describe "copyFile" $ do it "copies a source file to a destination path" $ do data' <- dryRunIO $ do () <- copyFile packageYamlP nonExistentP readFile nonExistentP packageYamlData <- Data.ByteString.readFile packageYamlFP data' `shouldBe` packageYamlData doesFileExist nonExistentP `shouldReturn` False data'' <- dryRunIO $ do () <- writeFile nonExistentP "foo" () <- copyFile nonExistentP nonExistentP' readFile nonExistentP' data'' `shouldBe` "foo" doesFileExist nonExistentP' `shouldReturn` False data''' <- dryRunIO $ do () <- copyFile packageYamlP nonExistentP () <- copyFile nonExistentP nonExistentP' readFile nonExistentP' data''' `shouldBe` packageYamlData doesFileExist nonExistentP' `shouldReturn` False it "can copy a source file to an existing file by overwriting it" $ do data' <- dryRunIO $ do () <- writeFile nonExistentP "foo" () <- copyFile packageYamlP nonExistentP readFile nonExistentP packageYamlData <- Data.ByteString.readFile packageYamlFP data' `shouldBe` packageYamlData doesPathExist nonExistentP `shouldReturn` False it "can't copy a non-existent file" $ do Left failToCopy <- tryDryRunIO $ copyFile nonExistentP nonExistentP' ioeGetErrorType failToCopy `shouldBe` doesNotExistErrorType ioeGetFileName failToCopy `shouldBe` Just nonExistentFP ioeGetLocation failToCopy `shouldBe` "copyFile" show failToCopy `shouldContain` "source does not exist" Left failToCopy' <- tryDryRunIO $ do () <- removeFile packageYamlP copyFile packageYamlP nonExistentP ioeGetErrorType failToCopy' `shouldBe` doesNotExistErrorType ioeGetFileName failToCopy' `shouldBe` Just packageYamlFP ioeGetLocation failToCopy' `shouldBe` "copyFile" show failToCopy' `shouldContain` "source does not exist" it "can't copy a directory" $ do Left failToCopy <- tryDryRunIO $ copyFile testP nonExistentP ioeGetErrorType failToCopy `shouldBe` InappropriateType ioeGetFileName failToCopy `shouldBe` Just testFP ioeGetLocation failToCopy `shouldBe` "copyFile" show failToCopy `shouldContain` "source is a directory" doesPathExist nonExistentP `shouldReturn` False Left failToCopy' <- tryDryRunIO $ do () <- createDirectory nonExistentP copyFile nonExistentP nonExistentP' ioeGetErrorType failToCopy' `shouldBe` InappropriateType ioeGetFileName failToCopy' `shouldBe` Just nonExistentFP ioeGetLocation failToCopy' `shouldBe` "copyFile" show failToCopy' `shouldContain` "source is a directory" doesPathExist nonExistentP' `shouldReturn` False it "can't copy a file to a file path that has no parent directory" $ do Left failToCopy <- tryDryRunIO $ do copyFile packageYamlP (nonExistentP nonExistentP) failToCopy `shouldSatisfy` isDoesNotExistError ioeGetFileName failToCopy `shouldBe` Just (nonExistentFP `combine` nonExistentFP) ioeGetLocation failToCopy `shouldBe` "copyFile" show failToCopy `shouldContain` "no parent directory" Left failToCopy' <- tryDryRunIO $ do () <- writeFile nonExistentP "" () <- removeFile nonExistentP copyFile packageYamlP (nonExistentP nonExistentP) failToCopy' `shouldSatisfy` isDoesNotExistError ioeGetFileName failToCopy' `shouldBe` Just (nonExistentFP `combine` nonExistentFP) ioeGetLocation failToCopy' `shouldBe` "copyFile" show failToCopy' `shouldContain` "no parent directory" it "can't copy a file to a file path inside a non-directory path" $ do Left failToCopy <- tryDryRunIO $ do copyFile packageYamlP (packageYamlP nonExistentP) ioeGetErrorType failToCopy `shouldBe` InappropriateType ioeGetFileName failToCopy `shouldBe` Just (packageYamlFP `combine` nonExistentFP) ioeGetLocation failToCopy `shouldBe` "copyFile" show failToCopy `shouldContain` "not inside a directory" Left failToCopy' <- tryDryRunIO $ do () <- writeFile nonExistentP "" copyFile packageYamlP (nonExistentP nonExistentP) ioeGetErrorType failToCopy `shouldBe` InappropriateType ioeGetFileName failToCopy' `shouldBe` Just (nonExistentFP `combine` nonExistentFP) ioeGetLocation failToCopy' `shouldBe` "copyFile" show failToCopy' `shouldContain` "not inside a directory" Left failToCopy'' <- tryDryRunIO $ do () <- copyFile packageYamlP nonExistentP copyFile nonExistentP (nonExistentP nonExistentP) ioeGetErrorType failToCopy `shouldBe` InappropriateType ioeGetFileName failToCopy'' `shouldBe` Just (nonExistentFP `combine` nonExistentFP) ioeGetLocation failToCopy'' `shouldBe` "copyFile" show failToCopy'' `shouldContain` "not inside a directory" it "can't copy a file to a directory path" $ do Left failToCopy <- tryDryRunIO $ copyFile packageYamlP testP ioeGetErrorType failToCopy `shouldBe` InappropriateType ioeGetFileName failToCopy `shouldBe` Just testFP ioeGetLocation failToCopy `shouldBe` "copyFile" show failToCopy `shouldContain` "destination is a directory" doesDirectoryExist testP `shouldReturn` True Left failToCopy' <- tryDryRunIO $ do () <- createDirectory nonExistentP copyFile packageYamlP nonExistentP ioeGetErrorType failToCopy' `shouldBe` InappropriateType ioeGetFileName failToCopy' `shouldBe` Just nonExistentFP ioeGetLocation failToCopy' `shouldBe` "copyFile" show failToCopy' `shouldContain` "destination is a directory" describe "createDirectory" $ do it "creates an empty directory" $ do dryRunIO (createDirectory nonExistentP >> isDirectory nonExistentP) `shouldReturn` True it "can't create a directory inside a non-existent path" $ do Left failToCreate <- tryDryRunIO $ do createDirectory (nonExistentP nonExistentP) ioeGetErrorType failToCreate `shouldBe` doesNotExistErrorType ioeGetFileName failToCreate `shouldBe` Just (nonExistentFP `combine` nonExistentFP) ioeGetLocation failToCreate `shouldBe` "createDirectory" show failToCreate `shouldContain` "no parent directory" Left failToCreate' <- tryDryRunIO $ do () <- writeFile nonExistentP "" () <- removeFile nonExistentP createDirectory (nonExistentP nonExistentP) ioeGetErrorType failToCreate' `shouldBe` doesNotExistErrorType ioeGetFileName failToCreate' `shouldBe` Just (nonExistentFP `combine` nonExistentFP) ioeGetLocation failToCreate' `shouldBe` "createDirectory" show failToCreate' `shouldContain` "no parent directory" it "can't create a directory inside a non-directory path" $ do Left failToCreate <- tryDryRunIO $ do createDirectory (packageYamlP nonExistentP) ioeGetErrorType failToCreate `shouldBe` InappropriateType ioeGetFileName failToCreate `shouldBe` Just (packageYamlFP `combine` nonExistentFP) ioeGetLocation failToCreate `shouldStartWith` "createDirectory" doesFileExist packageYamlP `shouldReturn` True Left failToCreate' <- tryDryRunIO $ do () <- writeFile nonExistentP "" createDirectory (nonExistentP nonExistentP) ioeGetErrorType failToCreate' `shouldBe` InappropriateType ioeGetFileName failToCreate' `shouldBe` Just (nonExistentFP `combine` nonExistentFP) ioeGetLocation failToCreate' `shouldStartWith` "createDirectory" show failToCreate' `shouldContain` "not inside a directory" Left failToCreate'' <- tryDryRunIO $ do () <- copyFile packageYamlP nonExistentP createDirectory (nonExistentP nonExistentP) ioeGetErrorType failToCreate'' `shouldBe` InappropriateType ioeGetFileName failToCreate'' `shouldBe` Just (nonExistentFP `combine` nonExistentFP) ioeGetLocation failToCreate'' `shouldStartWith` "createDirectory" show failToCreate'' `shouldContain` "not inside a directory" it "can't create a directory by overwriting an existing file" $ do Left failToCreate <- tryDryRunIO $ do createDirectory packageYamlP ioeGetErrorType failToCreate `shouldBe` alreadyExistsErrorType ioeGetFileName failToCreate `shouldBe` Just packageYamlFP ioeGetLocation failToCreate `shouldBe` "createDirectory" show failToCreate `shouldContain` "destination is already a file" doesFileExist packageYamlP `shouldReturn` True Left failToCreate' <- tryDryRunIO $ do () <- writeFile nonExistentP "" createDirectory nonExistentP ioeGetErrorType failToCreate' `shouldBe` alreadyExistsErrorType ioeGetFileName failToCreate' `shouldBe` Just nonExistentFP ioeGetLocation failToCreate' `shouldBe` "createDirectory" show failToCreate' `shouldContain` "destination is already a file" Left failToCreate'' <- tryDryRunIO $ do () <- copyFile packageYamlP nonExistentP createDirectory nonExistentP ioeGetErrorType failToCreate'' `shouldBe` alreadyExistsErrorType ioeGetFileName failToCreate'' `shouldBe` Just nonExistentFP ioeGetLocation failToCreate'' `shouldBe` "createDirectory" show failToCreate'' `shouldContain` "destination is already a file" it "can't create a directory if it's already a directory" $ do Left failToCreate <- tryDryRunIO $ do createDirectory testP ioeGetErrorType failToCreate `shouldBe` alreadyExistsErrorType ioeGetFileName failToCreate `shouldBe` Just testFP ioeGetLocation failToCreate `shouldBe` "createDirectory" show failToCreate `shouldContain` "destination is already a directory" doesDirectoryExist testP `shouldReturn` True Left failToCreate' <- tryDryRunIO $ do () <- createDirectory nonExistentP createDirectory nonExistentP ioeGetErrorType failToCreate' `shouldBe` alreadyExistsErrorType ioeGetFileName failToCreate' `shouldBe` Just nonExistentFP ioeGetLocation failToCreate' `shouldBe` "createDirectory" show failToCreate' `shouldContain` "destination is already a directory" describe "removeFile" $ do it "removes an existing file" $ do dryRunIO (removeFile packageYamlP >> exists packageYamlP) `shouldReturn` False doesFileExist packageYamlP `shouldReturn` True fileExists <- dryRunIO $ do () <- writeFile nonExistentP "" () <- removeFile nonExistentP exists nonExistentP fileExists `shouldBe` False fileExists' <- dryRunIO $ do () <- copyFile packageYamlP nonExistentP () <- removeFile nonExistentP exists nonExistentP fileExists' `shouldBe` False it "can't remove a non-existent file" $ do Left failToRemove <- tryDryRunIO $ removeFile nonExistentP ioeGetErrorType failToRemove `shouldBe` doesNotExistErrorType ioeGetFileName failToRemove `shouldBe` Just nonExistentFP ioeGetLocation failToRemove `shouldBe` "removeFile" show failToRemove `shouldContain` "no such file" Left failToRemove' <- tryDryRunIO $ do () <- removeFile packageYamlP removeFile packageYamlP ioeGetErrorType failToRemove' `shouldBe` doesNotExistErrorType ioeGetFileName failToRemove' `shouldBe` Just packageYamlFP ioeGetLocation failToRemove' `shouldBe` "removeFile" show failToRemove' `shouldContain` "no such file" it "can't remove a directory" $ do Left failToRemove <- tryDryRunIO $ removeFile testP ioeGetErrorType failToRemove `shouldBe` InappropriateType ioeGetFileName failToRemove `shouldBe` Just testFP ioeGetLocation failToRemove `shouldBe` "removeFile" show failToRemove `shouldContain` "is a directory" doesDirectoryExist testP `shouldReturn` True Left failToRemove' <- tryDryRunIO $ do () <- createDirectory nonExistentP removeFile nonExistentP ioeGetErrorType failToRemove' `shouldBe` InappropriateType ioeGetFileName failToRemove' `shouldBe` Just nonExistentFP ioeGetLocation failToRemove' `shouldBe` "removeFile" show failToRemove' `shouldContain` "is a directory" describe "removeDirectory" $ do it "removes an existing directory" $ do result <- dryRunIO $ do createDirectory nonExistentP removeDirectory nonExistentP exists nonExistentP result `shouldBe` False it "can't remove a non-existent directory" $ do Left failToRemove <- tryDryRunIO $ removeDirectory nonExistentP failToRemove `shouldSatisfy` isDoesNotExistError ioeGetFileName failToRemove `shouldBe` Just nonExistentFP Left failToRemove' <- tryDryRunIO $ do () <- removeFile packageYamlP removeDirectory packageYamlP failToRemove' `shouldSatisfy` isDoesNotExistError ioeGetFileName failToRemove' `shouldBe` Just packageYamlFP it "can't remove a non-directory" $ do Left failToRemove <- tryDryRunIO $ removeDirectory packageYamlP ioeGetErrorType failToRemove `shouldBe` InappropriateType ioeGetFileName failToRemove `shouldBe` Just packageYamlFP doesFileExist packageYamlP `shouldReturn` True Left failToRemove' <- tryDryRunIO $ do () <- writeFile nonExistentP "" removeDirectory nonExistentP ioeGetErrorType failToRemove' `shouldBe` InappropriateType ioeGetFileName failToRemove' `shouldBe` Just nonExistentFP Left failToRemove'' <- tryDryRunIO $ do () <- copyFile packageYamlP nonExistentP removeDirectory nonExistentP ioeGetErrorType failToRemove'' `shouldBe` InappropriateType ioeGetFileName failToRemove'' `shouldBe` Just nonExistentFP describe "removeDirectoryRecursively" $ do it "removes an existing directory recursively" $ do result <- dryRunIO $ do createDirectory nonExistentP writeFile (nonExistentP foo) "" createDirectory (nonExistentP bar) writeFile (nonExistentP bar baz) "" removeDirectoryRecursively nonExistentP exists nonExistentP result `shouldBe` False it "can't remove a non-existent directory" $ do Left failToRemove <- tryDryRunIO $ do removeDirectoryRecursively nonExistentP failToRemove `shouldSatisfy` isDoesNotExistError ioeGetFileName failToRemove `shouldBe` Just nonExistentFP Left failToRemove' <- tryDryRunIO $ do () <- removeFile packageYamlP removeDirectoryRecursively packageYamlP failToRemove' `shouldSatisfy` isDoesNotExistError ioeGetFileName failToRemove' `shouldBe` Just packageYamlFP it "can't remove a non-directory" $ do Left failToRemove <- tryDryRunIO $ removeDirectoryRecursively packageYamlP ioeGetErrorType failToRemove `shouldBe` InappropriateType ioeGetLocation failToRemove `shouldStartWith` "removeDirectoryRecursively" ioeGetFileName failToRemove `shouldBe` Just packageYamlFP doesFileExist packageYamlP `shouldReturn` True Left failToRemove' <- tryDryRunIO $ do () <- writeFile nonExistentP "" removeDirectoryRecursively nonExistentP ioeGetErrorType failToRemove' `shouldBe` InappropriateType ioeGetLocation failToRemove' `shouldStartWith` "removeDirectoryRecursively" ioeGetFileName failToRemove' `shouldBe` Just nonExistentFP Left failToRemove'' <- tryDryRunIO $ do () <- copyFile packageYamlP nonExistentP removeDirectoryRecursively nonExistentP ioeGetErrorType failToRemove'' `shouldBe` InappropriateType ioeGetLocation failToRemove'' `shouldStartWith` "removeDirectoryRecursively" ioeGetFileName failToRemove'' `shouldBe` Just nonExistentFP describe "createDirectories" $ do it "creates a directory and its ancestors if needed" $ do result <- dryRunIO $ do () <- createDirectories (nonExistentP nonExistentP) isDirectory (nonExistentP nonExistentP) result `shouldBe` True doesDirectoryExist nonExistentP `shouldReturn` False it "can't create a directory if any of its ancestors is a file" $ do Left failToCreate <- tryDryRunIO $ do createDirectories (packageYamlP nonExistentP nonExistentP) ioeGetErrorType failToCreate `shouldBe` InappropriateType ioeGetFileName failToCreate `shouldBe` Just packageYamlFP ioeGetLocation failToCreate `shouldStartWith` "createDirectories" show failToCreate `shouldContain` "one of its ancestors is a non-directory file" describe "listDirectory" $ do it "lists direct children in a directory" $ do withFixture $ \tmpDir tmpDir' -> do result <- dryRunIO $ listDirectory tmpDir result `shouldBe` [bar, baz, foo] () <- Prelude.writeFile (tmpDir' `combine` "corge") "" result' <- dryRunIO $ do () <- writeFile (tmpDir qux) "" () <- createDirectory (tmpDir quux) () <- writeFile (tmpDir corge) "" () <- writeFile (dropFileName tmpDir foo) "" listDirectory tmpDir result' `shouldBe` [bar, baz, corge, foo, quux, qux] it "lists direct children in a virtual directory" $ do result <- dryRunIO $ do () <- createDirectory nonExistentP () <- writeFile (nonExistentP foo) "" () <- createDirectory (nonExistentP bar) () <- createDirectory (nonExistentP baz) () <- createDirectory (nonExistentP baz qux) listDirectory nonExistentP result `shouldBe` [bar, baz, foo] it "hides removed files in the sandbox" $ withFixture $ \tmpDir _ -> do result <- dryRunIO $ do () <- removeFile (tmpDir foo) listDirectory tmpDir result `shouldBe` [bar, baz] it "fails if a specified path does not exist" $ do Left e <- tryDryRunIO $ listDirectory nonExistentP e `shouldSatisfy` isDoesNotExistError ioeGetFileName e `shouldBe` Just nonExistentFP ioeGetErrorType e `shouldBe` doesNotExistErrorType ioeGetLocation e `shouldStartWith` "listDirectory" Left e' <- tryDryRunIO $ do () <- removeFile packageYamlP listDirectory packageYamlP e' `shouldSatisfy` isDoesNotExistError ioeGetFileName e' `shouldBe` Just packageYamlFP ioeGetLocation e' `shouldStartWith` "listDirectory" show e' `shouldContain` "no such directory" it "fails if a specified path is a regular file" $ do Left e <- tryDryRunIO $ listDirectory packageYamlP ioeGetErrorType e `shouldBe` InappropriateType ioeGetFileName e `shouldBe` Just packageYamlFP ioeGetLocation e `shouldBe` "listDirectory" Left e' <- tryDryRunIO $ do () <- writeFile nonExistentP "" listDirectory nonExistentP ioeGetErrorType e' `shouldBe` InappropriateType ioeGetFileName e' `shouldBe` Just nonExistentFP ioeGetLocation e' `shouldBe` "listDirectory" show e' `shouldContain` "not a directory" Left e'' <- tryDryRunIO $ do () <- copyFile packageYamlP nonExistentP listDirectory nonExistentP ioeGetErrorType e'' `shouldBe` InappropriateType ioeGetFileName e'' `shouldBe` Just nonExistentFP ioeGetLocation e'' `shouldBe` "listDirectory" show e'' `shouldContain` "not a directory" describe "getFileSize" $ do it "returns the size of a file" $ withTempDir $ \tmpDir tmpDirFP -> do Data.ByteString.writeFile (tmpDirFP `combine` "foo") "asdf" dryRunIO (getFileSize $ tmpDir foo) `shouldReturn` 4 size <- dryRunIO $ do () <- writeFile (tmpDir bar) "asdf asdf" getFileSize $ tmpDir bar size `shouldBe` 9 size' <- dryRunIO $ do () <- writeFile (tmpDir bar) "asdf" () <- copyFile (tmpDir bar) (tmpDir baz) getFileSize $ tmpDir baz size' `shouldBe` 4 it "fails if path doesn't exist" $ withTempDir $ \tmpDir tmpDirFP -> do Left e <- tryDryRunIO $ getFileSize $ tmpDir nonExistentP ioeGetErrorType e `shouldBe` doesNotExistErrorType ioeGetFileName e `shouldBe` Just (tmpDirFP `combine` nonExistentFP) ioeGetLocation e `shouldBe` "getFileSize" Left e' <- tryDryRunIO $ do () <- removeFile packageYamlP getFileSize packageYamlP ioeGetErrorType e' `shouldBe` doesNotExistErrorType ioeGetFileName e' `shouldBe` Just packageYamlFP ioeGetLocation e' `shouldBe` "getFileSize" show e' `shouldContain` "does not exist" it "fails if path is a directory" $ withTempDir $ \tmpDir tmpDirFP -> do Left e <- tryDryRunIO $ getFileSize tmpDir ioeGetErrorType e `shouldBe` InappropriateType ioeGetFileName e `shouldBe` Just tmpDirFP ioeGetLocation e `shouldBe` "getFileSize" show e `shouldContain` "not a regular file, but a directory" Left e' <- tryDryRunIO $ do () <- createDirectory nonExistentP getFileSize nonExistentP ioeGetErrorType e' `shouldBe` InappropriateType ioeGetFileName e' `shouldBe` Just nonExistentFP ioeGetLocation e' `shouldBe` "getFileSize" show e' `shouldContain` "not a regular file, but a directory"