sync-mht-0.3.8.0: src/Sync/MerkleTree/Test.hs
module Sync.MerkleTree.Test where
import Control.Concurrent
import Control.Monad
import Data.Ix
import Data.List
import Data.Time.Calendar
import Data.Time.Clock
import Foreign.C.Types
import Sync.MerkleTree.Analyse
import Sync.MerkleTree.Run
import Sync.MerkleTree.Client
import System.Directory
import System.FilePath
import System.Posix.Files
import System.IO
import System.IO.Error
import System.IO.Temp
import System.Posix.Files
import System.Random
import qualified Test.HUnit as H
tests :: H.Test
tests = H.TestList $
[ testClockDriftFail
, testOptions
, testBigFile
, testHelp
, testCmdLine
, testCmdLineFail
, testExit
, testEntry
, testIgnoreBoring
, testSync
, testDoesThrowIOError
, testDirsEqual
]
testIgnoreBoring :: H.Test
testIgnoreBoring =
let testCase ign bor = withSystemTempDirectory "sync-mht" $ \fp ->
do let srcDir = fp </> "src"
destDir = fp </> "dest"
createDirectory srcDir
createDirectory $ srcDir </> "a"
createDirectory $ srcDir </> "with-foo"
createDirectory destDir
writeFile (fp </> ".boring") $ unlines $ [ "#baz", "", "foo", "^bar" ]
writeFile (srcDir </> "added.txt") "testA"
writeFile (srcDir </> "added-bar.txt") "testB"
writeFile (srcDir </> "baz") "testC"
writeFile (srcDir </> "bar-ignore.txt") "testD"
writeFile (srcDir </> "a" </> "bar") "testE"
writeFile (srcDir </> "some-foo.txt") "testF"
writeFile (srcDir </> "a" </> "foo") "testG"
runSync $
defaultSyncOptions
{ so_source = Just $ srcDir
, so_destination = Just $ destDir
, so_ignore = ign
, so_boring = map (const $ fp </> ".boring") bor
, so_add = True
, so_update = True
, so_delete = True
}
readFile (destDir </> "added.txt") >>= ("testA" H.@=?)
readFile (destDir </> "added-bar.txt") >>= ("testB" H.@=?)
readFile (destDir </> "baz") >>= ("testC" H.@=?)
doesFileExist (destDir </> "bar-ignore.txt") >>= (False H.@=?)
readFile (destDir </> "a" </> "bar") >>= ("testE" H.@=?)
doesFileExist (destDir </> "some-foo.txt") >>= (False H.@=?)
doesFileExist (destDir </> "a" </> "foo") >>= (False H.@=?)
(doesDirectoryExist $ destDir </> "with-foo") >>= (False H.@=?)
in H.TestList
[ H.TestLabel "testIgnore" $ H.TestCase $ testCase ["foo","^bar"] []
, H.TestLabel "testBoring" $ H.TestCase $ testCase [] [()]
]
testBigFile :: H.Test
testBigFile = H.TestLabel "testBigFile" $ H.TestCase $
withSystemTempDirectory "sync-mht" $ \testDir ->
do let srcDir = testDir </> "src"
destDir = testDir </> "dest"
data_ = show [1..(2^17)]
createDirectory srcDir
forM_ [1..20] $ \i ->
do createDirectory $ srcDir </> (show i)
forM_ [1..20] $ \j ->
writeFile (srcDir </> (show i) </> ("new.txt"++show j)) data_
createDirectory destDir
runSync $
defaultSyncOptions
{ so_source = Just $ srcDir
, so_destination = Just $ destDir
, so_add = True
}
dataNew1 <- readFile $ destDir </> "1" </> "new.txt19"
dataNew2 <- readFile $ destDir </> "17" </> "new.txt13"
dataNew3 <- readFile $ destDir </> "12" </> "new.txt1"
data_ H.@=? dataNew1
data_ H.@=? dataNew2
data_ H.@=? dataNew3
testCmdLine :: H.Test
testCmdLine = H.TestLabel "testCmdLine" $ H.TestCase $
withSystemTempDirectory "sync-mht" $ \testDir ->
do let srcDir = testDir </> "src"
destDir = testDir </> "dest"
boringFile = testDir </> ".boring"
expected = show [1..(2^17)]
createDirectory srcDir
createDirectory destDir
writeFile boringFile "$bar^"
writeFile (srcDir </> "new.txt") expected
runMain ["-s",srcDir,"-d",destDir,"-a","-u","--delete","-i","$foo^","-b",boringFile]
got <- readFile $ destDir </> "new.txt"
expected H.@=? got
testCmdLineFail :: H.Test
testCmdLineFail = H.TestLabel "testCmdLineFail" $ H.TestCase $
withSystemTempDirectory "sync-mht" $ \testDir ->
do let srcDir = testDir </> "src"
destDir = testDir </> "dest"
expected = "test"
createDirectory srcDir
createDirectory destDir
writeFile (srcDir </> "new.txt") expected
runMain ["-s",srcDir,"-d",destDir,"foo","bar"]
runMain ["-s",srcDir,"-d","remote:"++destDir,"-r","exit 0"]
runMain ["-foobar"]
runMain ["--help"]
runMain ["-s",srcDir,"-d",destDir]
(doesFileExist $ destDir </> "new.txt") >>= (False H.@=?)
testClockDriftFail :: H.Test
testClockDriftFail = H.TestLabel "testClockDriftFail" $ H.TestCase $
withSystemTempDirectory "sync-mht" $ \testDir ->
do let srcDir = testDir </> "src"
destDir = testDir </> "dest"
expected = "expected"
syncOpts =
defaultSyncOptions
{ so_add = True
, so_source = Just $ srcDir
, so_destination = Just $ "remote:" ++ destDir
, so_remote = Just Simulate
}
createDirectory srcDir
createDirectory destDir
writeFile (srcDir </> "new.txt") expected
runSync $ syncOpts { so_compareClocks = Just (10.0, -100.0) }
runSync $ syncOpts { so_compareClocks = Just (10.0, 100.0) }
(doesFileExist $ destDir </> "new.txt") >>= (False H.@=?)
runSync $ syncOpts { so_compareClocks = Just (10.0, 0.0) }
got <- readFile $ destDir </> "new.txt"
expected H.@=? got
runMain :: [String] -> IO ()
runMain = main ""
runSync :: SyncOptions -> IO ()
runSync = run ""
testHelp :: H.Test
testHelp = H.TestLabel "testHelp" $ H.TestCase $
withSystemTempDirectory "sync-mht" $ \testDir ->
do let srcDir = testDir </> "src"
destDir = testDir </> "dest"
data_ = "d"
syncOpts =
defaultSyncOptions
{ so_add = True
, so_source = Just $ srcDir
, so_destination = Just $ destDir
}
createDirectory srcDir
createDirectory destDir
writeFile (srcDir </> "new.txt") data_
mapM (runSync) $
[ syncOpts { so_nonOptions = ["foo", "bar"] }
, syncOpts { so_source = Just $ "remote:" ++ srcDir }
, syncOpts { so_source = Nothing }
, syncOpts { so_destination = Nothing }
, syncOpts { so_source = Nothing, so_destination = Nothing }
, defaultSyncOptions
, syncOpts { so_remote = Just $ RemoteCmd "/bin/true" }
, syncOpts { so_destination = Just $ "remote:" ++ destDir }
, syncOpts { so_help = True }
, syncOpts
{ so_source = Just $ "remote:" ++ srcDir
, so_destination = Just $ "remote:" ++ destDir
, so_remote = Just $ RemoteCmd "/bin/true"
} ]
(doesFileExist $ destDir </> "new.txt") >>= (False H.@=?)
testExit :: H.Test
testExit = H.TestLabel "testExit" $ H.TestCase $
withSystemTempDirectory "sync-mht" $ \testDir ->
do let srcDir = testDir </> "src"
destDir = testDir </> "dest"
data_ = "d"
createDirectory srcDir
createDirectory destDir
writeFile (srcDir </> "new.txt") data_
shouldFail $
do runSync $ defaultSyncOptions
{ so_source = Just $ "remote:" ++ srcDir
, so_destination = Just $ destDir
, so_remote = Just $ RemoteCmd "exit"
, so_add = True
}
testOptions :: H.Test
testOptions = H.TestLabel "testOptions" $ H.TestCase $
let prepare add update delete go =
withSystemTempDirectory "sync-mht" $ \testDir ->
do let srcDir = testDir </> "src"
destDir = testDir </> "dest"
createDirectory srcDir
createDirectory destDir
writeFile (srcDir </> "same.txt") "test"
copyFile (srcDir </> "same.txt") (destDir </> "same.txt")
writeFile (srcDir </> "changed.txt") "test"
writeFile (destDir </> "changed.txt") "testB"
writeFile (srcDir </> "added.txt") "test"
writeFile (destDir </> "deleted.txt") "testB"
runSync $
defaultSyncOptions
{ so_source = Just $ srcDir
, so_destination = Just $ destDir
, so_add = add
, so_update = update
, so_delete = delete
}
True <- liftM (=="test") $ readFile (srcDir </> "same.txt")
True <- liftM (=="test") $ readFile (srcDir </> "changed.txt")
True <- liftM (=="test") $ readFile (srcDir </> "added.txt")
go destDir
in do prepare True False False $ \destDir ->
do readFile (destDir </> "same.txt") >>= ("test" H.@=?)
readFile (destDir </> "changed.txt") >>= ("testB" H.@=?)
readFile (destDir </> "added.txt") >>= ("test" H.@=?)
readFile (destDir </> "deleted.txt") >>= ("testB" H.@=?)
prepare False True False $ \destDir ->
do readFile (destDir </> "same.txt") >>= ("test" H.@=?)
readFile (destDir </> "changed.txt") >>= ("test" H.@=?)
readFile (destDir </> "deleted.txt") >>= ("testB" H.@=?)
prepare False False True $ \destDir ->
do readFile (destDir </> "same.txt") >>= ("test" H.@=?)
readFile (destDir </> "changed.txt") >>= ("testB" H.@=?)
doesFileExist (destDir </> "deleted.txt") >>= (False H.@=?)
testSync :: H.Test
testSync =
H.TestList
$ flip map [0,1,2]
$ \simulate -> H.TestLabel ("testSync"++ show simulate)
$ H.TestCase
$ forM_ [1..20] $ \_ ->
withSystemTempDirectory "sync-mht" $ \testDir ->
do mkRandomDir 4 [testDir </> "src", testDir </> "src-backup"]
mkRandomDir 4 [testDir </> "target"]
let sourcePrefix
| simulate == 1 = "remote:"
| otherwise = ""
targetPrefix
| simulate == 2 = "remote:"
| otherwise = ""
let cmd = runSync $
defaultSyncOptions
{ so_source = Just $ (sourcePrefix ++) testDir </> "src"
, so_destination = Just $ (targetPrefix ++) testDir </> "target"
, so_remote =
case simulate of
0 -> Nothing
_ -> Just Simulate
, so_add = True
, so_update = True
, so_delete = True
}
cmd
areDirsEqual (testDir </> "src") (testDir </> "target")
areDirsEqual (testDir </> "src") (testDir </> "src-backup")
cmd
areDirsEqual (testDir </> "target") (testDir </> "src-backup")
utcTimeFrom :: Integer -> CTime
utcTimeFrom x = CTime (1000+ fromIntegral x)
mkRandomDir :: Integer -> [FilePath] -> IO ()
mkRandomDir md fps =
do forM fps createDirectory
names <- distinctNames 6
forM_ names $ \n ->
do genDir <- randomIO
case () of
() | genDir, md > 0 -> mkRandomDir (md - 1) $ map (</> n) fps
| genDir -> return ()
| otherwise ->
do d <- randomRIO (1,4)
forM_ fps $ \fp ->
do writeFile (fp </> n) (show d)
setFileTimes (fp </> n) (utcTimeFrom d) (utcTimeFrom d)
doesThrowIOError :: IO () -> IO Bool
doesThrowIOError a = catchIOError (a >>= (return . (`seq` False))) (return . (`seq` True))
shouldFail :: IO () -> IO ()
shouldFail action = doesThrowIOError action >>= (True H.@=?)
testDoesThrowIOError :: H.Test
testDoesThrowIOError = H.TestLabel "testDoesThrowIOError" $ H.TestCase $
do r1 <- doesThrowIOError $ return ()
False H.@=? r1
testDirsEqual :: H.Test
testDirsEqual = H.TestLabel "testDirsEqual" $ H.TestCase $
shouldFail $ withSystemTempDirectory "sync-mht" $ \testDir ->
do createDirectory $ testDir </> "a"
createDirectory $ testDir </> "b"
writeFile (testDir </> "a" </> "x") "x"
writeFile (testDir </> "b" </> "x") "y"
areDirsEqual (testDir </> "a") (testDir </> "b")
areDirsEqual :: FilePath -> FilePath -> IO ()
areDirsEqual fp1 fp2 =
do files1 <- liftM (sort . filter isRealFile) $ getDirectoryContents fp1
files2 <- liftM (sort . filter isRealFile) $ getDirectoryContents fp2
case () of
() | files1 == files2 -> forM_ files1 $ \f -> areEntriesEqual (fp1 </> f) (fp2 </> f)
| otherwise -> fail $ "Unequal: " ++ show (fp1, fp2, files1, files2)
areEntriesEqual :: FilePath -> FilePath -> IO ()
areEntriesEqual f1 f2 =
do s1 <- getFileStatus f1
s2 <- getFileStatus f2
case () of
() | isDirectory s1, isDirectory s2 -> areDirsEqual f1 f2
| isRegularFile s1, isRegularFile s2 ->
do c1 <- readFile f1
c2 <- readFile f2
unless (c1 == c2) $ fail $ "Unequal files: " ++ show (f1, f2, c1, c2)
| otherwise ->
fail $ show
(f1, f2, isDirectory s1, isDirectory s2, isRegularFile s1, isRegularFile s2)
-- | @distinctNames k@ creates k distinct file names
distinctNames :: Integer -> IO [String]
distinctNames k = retry
where
isDistinct names = null $ filter hasDuplicate $ group $ sort names
hasDuplicate = (/= 1) . length
mkName _ = liftM (:[]) $ randomRIO ('a','m')
retry =
do result <- forM [1..k] mkName
if isDistinct result
then return result
else retry