sync-mht-0.3.4.0: src/main/hs/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 Sync.MerkleTree.Analyse
import Sync.MerkleTree.Run
import System.Directory
import System.FilePath
import System.IO
import System.IO.Error
import System.IO.Temp
import System.Posix.Files
import System.Random
import qualified Distribution.TestSuite as TS
mkTestInstance :: String -> IO TS.Result -> TS.TestInstance
mkTestInstance name run = ti
where
ti =
TS.TestInstance
{ TS.run = liftM TS.Finished (catchIOError run $ return . TS.Fail . show)
, TS.name = name
, TS.tags = []
, TS.options = []
, TS.setOption = setOpt
}
setOpt _ _ = Right ti
tests :: IO [TS.Test]
tests = return $
[ TS.testGroup "all" $ map TS.Test $
[ testOptions ]
++ testIgnoreBoring
++ testSync
]
testIgnoreBoring :: [TS.TestInstance]
testIgnoreBoring =
let testCase ign bor = withSystemTempDirectory "sync-mht" $ \fp ->
do let srcDir = fp </> "src"
destDir = fp </> "dest"
createDirectory srcDir
createDirectory $ srcDir </> "a"
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"
run $
SyncOptions
{ so_source = Just $ srcDir
, so_destination = Just $ destDir
, so_remote = Nothing
, so_ignore = ign
, so_boring = map (const $ fp </> ".boring") bor
, so_add = True
, so_update = True
, so_delete = True
, so_help = False
, so_nonOptions = []
}
True <- liftM (=="testA") $ readFile (destDir </> "added.txt")
True <- liftM (=="testB") $ readFile (destDir </> "added-bar.txt")
True <- liftM (=="testC") $ readFile (destDir </> "baz")
False <- doesFileExist (destDir </> "bar-ignore.txt")
True <- liftM (=="testE") $ readFile (destDir </> "a" </> "bar")
False <- doesFileExist (destDir </> "some-foo.txt")
False <- doesFileExist (destDir </> "a" </> "foo")
return TS.Pass
in [ mkTestInstance "testIgnore" $ testCase ["foo","^bar"] []
, mkTestInstance "testBoring" $ testCase [] [()]
]
testOptions :: TS.TestInstance
testOptions = mkTestInstance "testOptions" $
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"
run $
SyncOptions
{ so_source = Just $ srcDir
, so_destination = Just $ destDir
, so_remote = Nothing
, so_ignore = []
, so_boring = []
, so_add = add
, so_update = update
, so_delete = delete
, so_help = False
, so_nonOptions = []
}
True <- liftM (=="test") $ readFile (srcDir </> "same.txt")
True <- liftM (=="test") $ readFile (srcDir </> "changed.txt")
True <- liftM (=="test") $ readFile (srcDir </> "added.txt")
go srcDir destDir
in do prepare True False False $ \srcDir destDir ->
do True <- liftM (=="test") $ readFile (destDir </> "same.txt")
True <- liftM (=="testB") $ readFile (destDir </> "changed.txt")
True <- liftM (=="test") $ readFile (destDir </> "added.txt")
True <- liftM (=="testB") $ readFile (destDir </> "deleted.txt")
return ()
prepare False True False $ \srcDir destDir ->
do True <- liftM (=="test") $ readFile (destDir </> "same.txt")
True <- liftM (=="test") $ readFile (destDir </> "changed.txt")
True <- liftM (=="testB") $ readFile (destDir </> "deleted.txt")
return ()
prepare False False True $ \srcDir destDir ->
do True <- liftM (=="test") $ readFile (destDir </> "same.txt")
True <- liftM (=="testB") $ readFile (destDir </> "changed.txt")
False <- doesFileExist (destDir </> "deleted.txt")
return ()
return TS.Pass
testSync :: [TS.TestInstance]
testSync =
flip map [0,1,2] $ \simulate -> mkTestInstance ("testSync"++ show simulate) $
do forM [1..10] $ \_ ->
withSystemTempDirectory "sync-mht" $ \testDir ->
do mkRandomDir 3 [testDir </> "src", testDir </> "src-backup"]
mkRandomDir 3 [testDir </> "target"]
let sourcePrefix
| simulate == 1 = "remote:"
| otherwise = ""
targetPrefix
| simulate == 2 = "remote:"
| otherwise = ""
run $
SyncOptions
{ so_source = Just $ (sourcePrefix ++) testDir </> "src"
, so_destination = Just $ (targetPrefix ++) testDir </> "target"
, so_remote =
case simulate of
0 -> Nothing
_ -> Just Simulate
, so_ignore = []
, so_boring = []
, so_add = True
, so_update = True
, so_delete = True
, so_help = False
, so_nonOptions = []
}
areDirsEqual (testDir </> "src") (testDir </> "target")
areDirsEqual (testDir </> "src") (testDir </> "src-backup")
return TS.Pass
data FileOrDir
= File
| Dir
deriving (Eq, Ix, Enum, Bounded, Ord, Show)
instance Random FileOrDir where
random = randomR (minBound, maxBound)
randomR (l, u) g =
let (i, g) = randomR (fromEnum l, fromEnum u) g
in (toEnum i, g)
utcTimeFrom :: Integer -> UTCTime
utcTimeFrom x = UTCTime (fromGregorian 2015 07 29) (fromInteger x)
mkRandomDir :: Integer -> [FilePath] -> IO ()
mkRandomDir md fps =
do forM fps createDirectory
names <- distinctNames 6
forM_ names $ \n ->
do m <- randomRIO (0,1)
case toEnum m of
Dir | md > 0 -> mkRandomDir (md - 1) $ map (</> n) fps
| otherwise -> return ()
File ->
do d <- randomRIO (1,4)
forM_ fps $ \fp ->
do writeFile (fp </> n) (show d)
setModificationTime (fp </> n) (utcTimeFrom d)
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