seihou-core-0.4.0.0: test/Seihou/Effect/FilesystemSpec.hs
module Seihou.Effect.FilesystemSpec (tests) where
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Effectful
import Seihou.Effect.Filesystem
import Seihou.Effect.FilesystemInterp (runFilesystem)
import Seihou.Effect.FilesystemPure (PureFS (..), emptyFS, runFilesystemPure)
import System.Directory (removeDirectoryRecursive)
import System.FilePath ((</>))
import System.IO.Temp (withSystemTempDirectory)
import Test.Hspec
import Test.Tasty
import Test.Tasty.Hspec (testSpec)
tests :: IO TestTree
tests = testSpec "Seihou.Effect.Filesystem" spec
-- | Run an action with the pure filesystem interpreter.
runPure :: PureFS -> Eff '[Filesystem] a -> (a, PureFS)
runPure fs action = runPureEff $ runFilesystemPure fs action
-- | Run an action with the real filesystem interpreter in a temp directory.
runReal :: (FilePath -> Eff '[Filesystem, IOE] a) -> IO a
runReal action =
withSystemTempDirectory "seihou-fs-test" $ \tmpDir ->
runEff $ runFilesystem (action tmpDir)
spec :: Spec
spec = do
describe "pure interpreter" $ do
it "writes and reads a file" $ do
let (content, _) = runPure emptyFS $ do
writeFileText "hello.txt" "hello world"
readFileText "hello.txt"
content `shouldBe` "hello world"
it "doesFileExist returns False before write" $ do
let (exists, _) = runPure emptyFS $ doesFileExist "missing.txt"
exists `shouldBe` False
it "doesFileExist returns True after write" $ do
let (exists, _) = runPure emptyFS $ do
writeFileText "test.txt" "data"
doesFileExist "test.txt"
exists `shouldBe` True
it "copyFile copies content" $ do
let initial = PureFS (Map.singleton "src.txt" "source content") Set.empty
(content, _) = runPure initial $ do
copyFile "src.txt" "dest.txt"
readFileText "dest.txt"
content `shouldBe` "source content"
it "createDirectoryIfMissing creates a directory" $ do
let (exists, _) = runPure emptyFS $ do
createDirectoryIfMissing True "my/dir"
doesDirectoryExist "my/dir"
exists `shouldBe` True
it "createDirectoryIfMissing is idempotent" $ do
let (exists, _) = runPure emptyFS $ do
createDirectoryIfMissing True "my/dir"
createDirectoryIfMissing True "my/dir"
doesDirectoryExist "my/dir"
exists `shouldBe` True
it "overwrite replaces content" $ do
let (content, _) = runPure emptyFS $ do
writeFileText "file.txt" "first"
writeFileText "file.txt" "second"
readFileText "file.txt"
content `shouldBe` "second"
it "returns final filesystem state" $ do
let (_, fs) = runPure emptyFS $ do
writeFileText "a.txt" "aaa"
writeFileText "b.txt" "bbb"
Map.size fs.files `shouldBe` 2
it "getCurrentDirectory returns /pure-fs" $ do
let (cwd, _) = runPure emptyFS getCurrentDirectory
cwd `shouldBe` "/pure-fs"
describe "real interpreter" $ do
it "writes and reads a file" $ do
content <- runReal $ \tmpDir -> do
let path = tmpDir </> "hello.txt"
writeFileText path "hello world"
readFileText path
content `shouldBe` "hello world"
it "doesFileExist returns True after write" $ do
exists <- runReal $ \tmpDir -> do
let path = tmpDir </> "test.txt"
writeFileText path "data"
doesFileExist path
exists `shouldBe` True
it "doesFileExist returns False for nonexistent file" $ do
exists <- runReal $ \tmpDir -> do
doesFileExist (tmpDir </> "nope.txt")
exists `shouldBe` False
it "createDirectoryIfMissing creates nested directories" $ do
exists <- runReal $ \tmpDir -> do
let dirPath = tmpDir </> "a" </> "b" </> "c"
createDirectoryIfMissing True dirPath
doesDirectoryExist dirPath
exists `shouldBe` True
it "copyFile copies content" $ do
content <- runReal $ \tmpDir -> do
let src = tmpDir </> "src.txt"
dest = tmpDir </> "dest.txt"
writeFileText src "copy me"
copyFile src dest
readFileText dest
content `shouldBe` "copy me"
it "removeDirectoryIfEmpty removes an empty directory" $ do
exists <- runReal $ \tmpDir -> do
let dir = tmpDir </> "empty"
createDirectoryIfMissing True dir
removeDirectoryIfEmpty dir
doesDirectoryExist dir
exists `shouldBe` False
it "removeDirectoryIfEmpty leaves a non-empty directory alone" $ do
exists <- runReal $ \tmpDir -> do
let dir = tmpDir </> "non-empty"
createDirectoryIfMissing True dir
writeFileText (dir </> "keep.txt") "."
removeDirectoryIfEmpty dir
doesDirectoryExist dir
exists `shouldBe` True
it "removeDirectoryIfEmpty is a no-op when the directory is missing" $ do
-- Regression test: a migration chain that mixes MoveFile with a
-- RunCommand 'rm -rf <src-dir>' previously crashed here because
-- cleanupEmptyDirs revisits the source's parents after the
-- shell step has already removed them.
exists <- runReal $ \tmpDir -> do
let dir = tmpDir </> "never-existed"
removeDirectoryIfEmpty dir
doesDirectoryExist dir
exists `shouldBe` False