diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -32,3 +32,6 @@
 
 to synchronize the folder /bar (of the container named bar) with the local folder foo/.
 
+Note that: The options -a -u --delete respectively, allow copying of files to the target directory,
+updating files that are already in the target directory - not matching the contents in the source
+directory and delete files that are in the destination directory but not in the source directory.
diff --git a/dist/build/mainStub/mainStub-tmp/mainStub.hs b/dist/build/mainStub/mainStub-tmp/mainStub.hs
new file mode 100644
--- /dev/null
+++ b/dist/build/mainStub/mainStub-tmp/mainStub.hs
@@ -0,0 +1,5 @@
+module Main ( main ) where
+import Distribution.Simple.Test ( stubMain )
+import Sync.MerkleTree.Test ( tests )
+main :: IO ()
+main = stubMain tests
diff --git a/src/main/hs/Main.hs b/src/main/hs/Main.hs
--- a/src/main/hs/Main.hs
+++ b/src/main/hs/Main.hs
@@ -1,165 +1,16 @@
--- main module
 {-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE OverloadedStrings #-}
 import System.Environment
-import System.Process
-import System.IO
 import System.IO.Error
 import System.Console.GetOpt
-import Paths_sync_mht (version)
-import Data.List
-import Data.Version (showVersion)
 
-import Sync.MerkleTree.CommTypes
-import Sync.MerkleTree.Sync
-
-data SyncOptions
-    = SyncOptions
-      { so_source :: Maybe FilePath
-      , so_destination :: Maybe FilePath
-      , so_remote :: Maybe String
-      , so_ignore :: [FilePath]
-      , so_add :: Bool
-      , so_update :: Bool
-      , so_delete :: Bool
-      , so_help :: Bool
-      , so_nonOptions :: [String]
-      }
-
-defaultSyncOptions :: SyncOptions
-defaultSyncOptions =
-    SyncOptions
-    { so_source = Nothing
-    , so_destination = Nothing
-    , so_remote = Nothing
-    , so_ignore = []
-    , so_add = False
-    , so_update = False
-    , so_delete = False
-    , so_help = False
-    , so_nonOptions = []
-    }
-
-
-toClientServerOptions :: SyncOptions -> ClientServerOptions
-toClientServerOptions so =
-     ClientServerOptions
-     { cs_add = so_add so
-     , cs_update = so_update so
-     , cs_delete = so_delete so
-     , cs_ignore = so_ignore so
-     }
-
-optDescriptions :: [OptDescr (SyncOptions -> SyncOptions)]
-optDescriptions =
-    [ Option ['s'] ["source"] (ReqArg (\fp so -> so { so_source = Just fp }) "DIR")
-        "directory to copy files from (source) (this option is required)"
-    , Option ['d'] ["destination"] (ReqArg (\fp so -> so { so_destination = Just fp }) "DIR")
-        "directory to copy files to (destination) (this option is required)"
-    , Option ['r'] ["remote-shell"] (ReqArg (\s so -> so { so_remote = Just s }) "CMD")
-        "synchroize with a remote-site using a remote command execution tool (like ssh or docker)"
-    , Option ['i'] ["ignore"] (ReqArg (\fp so -> so { so_ignore = fp:(so_ignore so) }) "PATH") ( concat
-        [ "files or directories (relative to the source and destination directories) that are to "
-        , "be ignored during synchroization (this option can be given multiple times)"
-        ])
-    , Option ['a'] ["add"] (NoArg (\so -> so { so_add = True })) ( concat
-        [ "copy files from the source directory if there is corresponding file inside "
-        , "the destination directory"
-        ])
-    , Option ['u'] ["update"] (NoArg (\so -> so { so_update = True })) ( concat
-        [ "overwrite existing files inside the destination directory if their content does not "
-        , "match the respective files inside the source directory"
-        ])
-    , Option [] ["delete"] (NoArg (\so -> so { so_delete = True })) ( concat
-        [ "delete files inside the destination directory if there is no corresponding file inside "
-        , "the source directory"
-        ])
-    , Option ['h'] ["help"] (NoArg (\so -> so { so_help = True })) "shows usage information"
-    ]
-
-parseNonOption :: String -> (SyncOptions -> SyncOptions)
-parseNonOption s so = so { so_nonOptions = s:(so_nonOptions so) }
-
-toSyncOptions :: [(SyncOptions -> SyncOptions)] -> SyncOptions
-toSyncOptions = foldl (flip id) defaultSyncOptions
-
-putError :: String -> IO ()
-putError = hPutStrLn stderr
-
-printUsageInfo :: [String] -> IO ()
-printUsageInfo prefix = mapM_ putError (prefix ++ [usageInfo header optDescriptions] ++ [details])
-    where
-      header = unlines
-          [ "sync-mht version " ++ showVersion version
-          , ""
-          , "Usage: sync-mht [OPTIONS..]"
-          ]
-      details = concat
-          [ "Note: If the --remote-shell option has been provided, exactly one of the directories "
-          , "must be prepended with 'remote:' - indicating a folder on the site, accessible with "
-          , "the provided remote shell command."
-          ]
-
-_HIDDENT_CLIENT_MODE_OPTION_ :: String
-_HIDDENT_CLIENT_MODE_OPTION_ = "--hidden-client-mode-option"
+import Sync.MerkleTree.Run
 
 main :: IO ()
 main = flip catchIOError (putError . show) $
     do args <- getArgs
        let parsedOpts = getOpt (ReturnInOrder parseNonOption) optDescriptions args
        case () of
-         () | [_HIDDENT_CLIENT_MODE_OPTION_] == args -> child
+         () | [_HIDDENT_CLIENT_MODE_OPTION_] == args -> runChild
             | (options,[],[]) <- parsedOpts -> run $ toSyncOptions options
             | (_,_,errs) <- parsedOpts -> printUsageInfo errs
-
-data Side
-    = Remote FilePath
-    | Local FilePath
-
-parseFilePath :: FilePath -> Side
-parseFilePath fp
-    | Just rest <- stripPrefix "remote:" fp = Remote rest
-    | otherwise = Local fp
-
-run :: SyncOptions -> IO ()
-run so
-    | so_help so =
-        printUsageInfo []
-    | not (null (so_nonOptions so)) =
-        printUsageInfo ["Could not understand the following options: " ++ show (so_nonOptions so)]
-    | Just source <- so_source so, Just destination <- so_destination so =
-        case (parseFilePath source, parseFilePath destination) of
-          (Remote _, Remote _) -> printUsageInfo [doubleRemote]
-          (Local source', Local destination')
-              | Just _ <- so_remote so -> printUsageInfo [missingRemote]
-              | otherwise -> local cs source' destination'
-          (Remote source', Local destination')
-              | Just remoteCmd <- so_remote so -> runParent cs remoteCmd source' destination' FromRemote
-              | otherwise -> printUsageInfo [missingRemoteCmd]
-          (Local source', Remote destination')
-              | Just remoteCmd <- so_remote so -> runParent cs remoteCmd source' destination' ToRemote
-              | otherwise -> printUsageInfo [missingRemoteCmd]
-    | otherwise =
-        do let missingOpts =
-                intercalate ", " $ map snd $ filter ((== Nothing) . ($ so) . fst)
-                [(so_source, "--source"),(so_destination, "--destination")]
-           printUsageInfo ["The follwing required options: " ++ missingOpts ++ " were missing."]
-    where
-      cs = toClientServerOptions so
-      doubleRemote = "Either the directory given in --source or --destination must be local."
-      missingRemote = concat
-          [ "The --remote-shell options requires that either the directory given at "
-          , "--source or --destination is at remote site. (Indicated by the prefix: 'remote:')"
-          ]
-      missingRemoteCmd = "The --remote-shell is required when the prefix 'remote:' is used."
-
-runParent :: ClientServerOptions -> String -> FilePath -> FilePath -> Direction -> IO ()
-runParent clientServerOpts remoteCmd source destination dir =
-    do let remoteCmd' = remoteCmd ++ " " ++ _HIDDENT_CLIENT_MODE_OPTION_
-       handles <- createProcess ((shell remoteCmd') { std_in = CreatePipe, std_out = CreatePipe })
-       case handles of
-         (Just hIn, Just hOut, Nothing, _ph) ->
-             do streams <- openStreams hOut hIn
-                parent streams source destination dir clientServerOpts
-         _ -> fail "createProcess did return the correct set of handles."
-
diff --git a/src/main/hs/Sync/MerkleTree/Analyse.hs b/src/main/hs/Sync/MerkleTree/Analyse.hs
--- a/src/main/hs/Sync/MerkleTree/Analyse.hs
+++ b/src/main/hs/Sync/MerkleTree/Analyse.hs
@@ -5,17 +5,19 @@
 {-# LANGUAGE OverloadedStrings #-}
 module Sync.MerkleTree.Analyse
     ( analyseDirectory
+    , isRealFile
     ) where
 
 import Control.Monad
+import Data.Maybe
 import System.FilePath
 import Foreign.C.Types
 import Prelude hiding (lookup)
 import Sync.MerkleTree.Types
 import System.Directory
-
 import System.Posix.Types
 import System.Posix.Files
+import Text.Regex (matchRegex, mkRegex, Regex)
 import qualified Data.Text as T
 
 isRealFile :: String -> Bool
@@ -23,16 +25,22 @@
     | x `elem` [".", ".."] = False
     | otherwise = True
 
-analyseDirectory :: FilePath -> [FilePath] -> Path -> IO [Entry]
-analyseDirectory fp ignore path
-    | fp `elem` ignore = return []
+shouldIgnore :: Path -> [Regex] -> Bool
+shouldIgnore p regexes = any (isJust . (`matchRegex` (toFilePath "" p))) regexes
+
+analyseDirectory :: FilePath -> [String] -> Path -> IO [Entry]
+analyseDirectory fp ignore path = analyseDir fp (map mkRegex ignore) path
+
+analyseDir :: FilePath -> [Regex] -> Path -> IO [Entry]
+analyseDir fp ignore path
+    | shouldIgnore path ignore = return []
     | otherwise =
         do files <- getDirectoryContents fp
            liftM concat $ mapM (analyse fp ignore path) $ filter isRealFile files
 
-analyse :: FilePath -> [FilePath] -> Path -> String -> IO [Entry]
+analyse :: FilePath -> [Regex] -> Path -> String -> IO [Entry]
 analyse fp ignore path name
-    | (fp </> name) `elem` ignore = return []
+    | shouldIgnore path' ignore = return []
     | otherwise =
         do status <- getFileStatus fp'
            analyse' status
@@ -50,6 +58,6 @@
                     , f_modtime = FileModTime modtime
                     } ]
           | isDirectory status =
-              liftM ((DirectoryEntry path'):) $ analyseDirectory fp' ignore path'
+              liftM ((DirectoryEntry path'):) $ analyseDir fp' ignore path'
           | otherwise = return [] -- No support for devices, sockets yet.
 
diff --git a/src/main/hs/Sync/MerkleTree/Client.hs b/src/main/hs/Sync/MerkleTree/Client.hs
--- a/src/main/hs/Sync/MerkleTree/Client.hs
+++ b/src/main/hs/Sync/MerkleTree/Client.hs
@@ -6,6 +6,7 @@
 import Control.Monad
 import Control.Monad.IO.Class
 import Data.Foldable(Foldable)
+import Data.Function
 import Data.Monoid(Monoid, mappend, mempty, Sum(..))
 import Data.Set(Set)
 import Data.List
@@ -49,6 +50,11 @@
     do True <- logReq $ SerText t
        return ()
 
+data SimpleEntry
+    = FileSimpleEntry Path
+    | DirectorySimpleEntry Path
+    deriving (Eq, Ord)
+
 analyseEntries :: Diff Entry -> ([Entry],[Entry],[Entry])
 analyseEntries (Diff obsoleteEntries newEntries) =
     (M.elems deleteMap, M.elems changeMap, M.elems newMap)
@@ -59,8 +65,8 @@
       obsMap = M.fromList $ S.toList $ S.map keyValue obsoleteEntries
       updMap = M.fromList $ S.toList $ S.map keyValue newEntries
       keyValue x = (name x, x)
-      name (FileEntry f) = f_name f
-      name (DirectoryEntry f) = f
+      name (FileEntry f) = FileSimpleEntry $ f_name f
+      name (DirectoryEntry f) = DirectorySimpleEntry f
 
 abstractClient :: (ClientMonad m) => ClientServerOptions -> FilePath -> Trie Entry -> m ()
 abstractClient cs fp trie =
@@ -78,20 +84,25 @@
                case e of
                  FileEntry f -> liftIO $ removeFile $ toFilePath fp $ f_name f
                  DirectoryEntry p -> liftIO $ removeDirectory $ toFilePath fp p
-       mapM_ (split . map (synchronizeNewOrChangedEntry fp))
-           $ splitEvery _CONCURRENT_FILETRANSFER_SIZE_ $ sort
-           $ [ e | cs_add cs, e <- newEntries ] ++ [ e | cs_update cs, e <- changedEntries ]
+       mapM_ (synchronizeNewOrChangedEntries fp)
+           $ groupBy ((==) `on` levelOf)
+           $ sort $ [ e | cs_add cs, e <- newEntries ] ++ [ e | cs_update cs, e <- changedEntries ]
        True <- terminateReq
        return ()
 
 _CONCURRENT_FILETRANSFER_SIZE_ :: Int
-_CONCURRENT_FILETRANSFER_SIZE_ = 96
+_CONCURRENT_FILETRANSFER_SIZE_ = 48
 
 splitEvery :: Int -> [a] -> [[a]]
 splitEvery n l
     | null l = []
     | (h,t) <- splitAt n l = h:(splitEvery n t)
 
+synchronizeNewOrChangedEntries :: (ClientMonad m) => FilePath -> [Entry] -> m ()
+synchronizeNewOrChangedEntries fp entries =
+    forM_ (splitEvery _CONCURRENT_FILETRANSFER_SIZE_ entries) $ \entryGroup ->
+        split $ map (synchronizeNewOrChangedEntry fp) entryGroup
+
 synchronizeNewOrChangedEntry :: (ClientMonad m) => FilePath -> Entry -> m ()
 synchronizeNewOrChangedEntry fp entry =
     case entry of
@@ -121,6 +132,3 @@
                 do s' <- querySetReq loc
                    let s = getAll trie
                    return $ Diff (s `S.difference` s') (s' `S.difference` s)
-
-
-
diff --git a/src/main/hs/Sync/MerkleTree/CommTypes.hs b/src/main/hs/Sync/MerkleTree/CommTypes.hs
--- a/src/main/hs/Sync/MerkleTree/CommTypes.hs
+++ b/src/main/hs/Sync/MerkleTree/CommTypes.hs
@@ -33,9 +33,7 @@
       , cs_delete :: Bool
       , cs_ignore :: [FilePath]
       }
-      deriving (Generic, Show, Typeable)
-
-instance Serialize ClientServerOptions
+      deriving (Read, Show)
 
 data Request
     = QuerySet TrieLocation
@@ -55,9 +53,21 @@
 
 instance Serialize QueryFileResponse
 
-data ChildSide
-    = Service FilePath ClientServerOptions
-    | Client FilePath ClientServerOptions
-    deriving (Show, Generic, Typeable)
+data ProtocolVersion
+    = Version1
+    deriving (Read, Show, Eq)
 
-instance Serialize ChildSide
+thisProtocolVersion :: ProtocolVersion
+thisProtocolVersion = Version1
+
+data LaunchMessage
+    = LaunchMessage
+    { lm_protocolVersion :: ProtocolVersion
+    , lm_dir :: FilePath
+    , lm_side :: Side
+    , lm_clientServerOptions :: ClientServerOptions
+    }
+    deriving (Read, Show)
+
+data Side = Server | Client
+    deriving (Read, Show)
diff --git a/src/main/hs/Sync/MerkleTree/Run.hs b/src/main/hs/Sync/MerkleTree/Run.hs
new file mode 100644
--- /dev/null
+++ b/src/main/hs/Sync/MerkleTree/Run.hs
@@ -0,0 +1,175 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Sync.MerkleTree.Run where
+
+import Control.Concurrent
+import System.Process
+import System.IO
+import System.Console.GetOpt
+import Paths_sync_mht (version)
+import Data.List
+import Data.Version (showVersion)
+
+import Sync.MerkleTree.CommTypes
+import Sync.MerkleTree.Sync
+
+data RemoteCmd
+    = RemoteCmd String
+    | Simulate
+
+data SyncOptions
+    = SyncOptions
+      { so_source :: Maybe FilePath
+      , so_destination :: Maybe FilePath
+      , so_remote :: Maybe RemoteCmd
+      , so_ignore :: [String]
+      , so_add :: Bool
+      , so_update :: Bool
+      , so_delete :: Bool
+      , so_help :: Bool
+      , so_nonOptions :: [String]
+      }
+
+defaultSyncOptions :: SyncOptions
+defaultSyncOptions =
+    SyncOptions
+    { so_source = Nothing
+    , so_destination = Nothing
+    , so_remote = Nothing
+    , so_ignore = []
+    , so_add = False
+    , so_update = False
+    , so_delete = False
+    , so_help = False
+    , so_nonOptions = []
+    }
+
+toClientServerOptions :: SyncOptions -> ClientServerOptions
+toClientServerOptions so =
+     ClientServerOptions
+     { cs_add = so_add so
+     , cs_update = so_update so
+     , cs_delete = so_delete so
+     , cs_ignore = so_ignore so
+     }
+
+optDescriptions :: [OptDescr (SyncOptions -> SyncOptions)]
+optDescriptions =
+    [ Option ['s'] ["source"] (ReqArg (\fp so -> so { so_source = Just fp }) "DIR")
+        "directory to copy files from (source) (this option is required)"
+    , Option ['d'] ["destination"] (ReqArg (\fp so -> so { so_destination = Just fp }) "DIR")
+        "directory to copy files to (destination) (this option is required)"
+    , Option ['r'] ["remote-shell"] (ReqArg (\s so -> so { so_remote = Just $ RemoteCmd s }) "CMD")
+        "synchroize with a remote-site using a remote command execution tool (like ssh or docker)"
+    , Option ['i'] ["ignore"] (ReqArg (\fp so -> so { so_ignore = fp:(so_ignore so) }) "PATH") ( concat
+        [ "files or directories (relative to the source and destination directories) that are to "
+        , "be ignored during synchroization (this option can be given multiple times)"
+        ])
+    , Option ['a'] ["add"] (NoArg (\so -> so { so_add = True })) ( concat
+        [ "copy files from the source directory if there is corresponding file inside "
+        , "the destination directory"
+        ])
+    , Option ['u'] ["update"] (NoArg (\so -> so { so_update = True })) ( concat
+        [ "overwrite existing files inside the destination directory if their content does not "
+        , "match the respective files inside the source directory"
+        ])
+    , Option [] ["delete"] (NoArg (\so -> so { so_delete = True })) ( concat
+        [ "delete files inside the destination directory if there is no corresponding file inside "
+        , "the source directory"
+        ])
+    , Option ['h'] ["help"] (NoArg (\so -> so { so_help = True })) "shows usage information"
+    ]
+
+parseNonOption :: String -> (SyncOptions -> SyncOptions)
+parseNonOption s so = so { so_nonOptions = s:(so_nonOptions so) }
+
+toSyncOptions :: [(SyncOptions -> SyncOptions)] -> SyncOptions
+toSyncOptions = foldl (flip id) defaultSyncOptions
+
+putError :: String -> IO ()
+putError = hPutStrLn stderr
+
+
+_HIDDENT_CLIENT_MODE_OPTION_ :: String
+_HIDDENT_CLIENT_MODE_OPTION_ = "--hidden-client-mode-option"
+
+
+printUsageInfo :: [String] -> IO ()
+printUsageInfo prefix = mapM_ putError (prefix ++ [usageInfo header optDescriptions] ++ [details])
+    where
+      header = unlines
+          [ "sync-mht version " ++ showVersion version
+          , ""
+          , "Usage: sync-mht [OPTIONS..]"
+          ]
+      details = concat
+          [ "Note: If the --remote-shell option has been provided, exactly one of the directories "
+          , "must be prepended with 'remote:' - indicating a folder on the site, accessible with "
+          , "the provided remote shell command."
+          ]
+
+
+data Location
+    = Remote FilePath
+    | Local FilePath
+
+parseFilePath :: FilePath -> Location
+parseFilePath fp
+    | Just rest <- stripPrefix "remote:" fp = Remote rest
+    | otherwise = Local fp
+
+run :: SyncOptions -> IO ()
+run so
+    | so_help so =
+        printUsageInfo []
+    | not (null (so_nonOptions so)) =
+        printUsageInfo ["Could not understand the following options: " ++ show (so_nonOptions so)]
+    | Just source <- so_source so, Just destination <- so_destination so =
+        case (parseFilePath source, parseFilePath destination) of
+          (Remote _, Remote _) -> printUsageInfo [doubleRemote]
+          (Local source', Local destination')
+              | Just _ <- so_remote so -> printUsageInfo [missingRemote]
+              | otherwise -> local cs source' destination'
+          (Remote source', Local destination')
+              | Just remoteCmd <- so_remote so -> runParent cs remoteCmd source' destination' FromRemote
+              | otherwise -> printUsageInfo [missingRemoteCmd]
+          (Local source', Remote destination')
+              | Just remoteCmd <- so_remote so -> runParent cs remoteCmd source' destination' ToRemote
+              | otherwise -> printUsageInfo [missingRemoteCmd]
+    | otherwise =
+        do let missingOpts =
+                intercalate ", " $ map snd $ filter ((== Nothing) . ($ so) . fst)
+                [(so_source, "--source"),(so_destination, "--destination")]
+           printUsageInfo ["The follwing required options: " ++ missingOpts ++ " were missing."]
+    where
+      cs = toClientServerOptions so
+      doubleRemote = "Either the directory given in --source or --destination must be local."
+      missingRemote = concat
+          [ "The --remote-shell options requires that either the directory given at "
+          , "--source or --destination is at remote site. (Indicated by the prefix: 'remote:')"
+          ]
+      missingRemoteCmd = "The --remote-shell is required when the prefix 'remote:' is used."
+
+runChild :: IO ()
+runChild =
+     do streams <- openStreams stdin stdout
+        child streams
+
+runParent :: ClientServerOptions -> RemoteCmd -> FilePath -> FilePath -> Direction -> IO ()
+runParent clientServerOpts mRemoteCmd source destination dir =
+    case mRemoteCmd of
+      RemoteCmd remoteCmd ->
+          do let remoteCmd' = remoteCmd ++ " " ++ _HIDDENT_CLIENT_MODE_OPTION_
+             handles <-
+                 createProcess ((shell remoteCmd') { std_in = CreatePipe, std_out = CreatePipe })
+             case handles of
+               (Just hIn, Just hOut, Nothing, _ph) ->
+                   do streams <- openStreams hOut hIn
+                      parent streams source destination dir clientServerOpts
+               _ -> fail "createProcess did not return the correct set of handles."
+      Simulate ->
+          do (parentInStream, childOutStream) <- mkChanStreams
+             (childInStream, parentOutStream) <- mkChanStreams
+             _ <- forkIO $ child $ StreamPair { sp_in = childInStream, sp_out = childOutStream }
+             let parentStreams = StreamPair { sp_in = parentInStream, sp_out = parentOutStream }
+             parent parentStreams source destination dir clientServerOpts
diff --git a/src/main/hs/Sync/MerkleTree/Sync.hs b/src/main/hs/Sync/MerkleTree/Sync.hs
--- a/src/main/hs/Sync/MerkleTree/Sync.hs
+++ b/src/main/hs/Sync/MerkleTree/Sync.hs
@@ -8,9 +8,12 @@
     , local
     , parent
     , openStreams
+    , mkChanStreams
+    , StreamPair(..)
     , Direction(..)
     ) where
 
+import Control.Concurrent(newChan)
 import Control.Monad
 import Control.Monad.State
 import Data.Monoid
@@ -24,6 +27,7 @@
 import qualified Data.Serialize as SE
 import qualified Data.ByteString as BS
 import qualified System.IO.Streams as ST
+import qualified System.IO.Streams.Concurrent as ST
 
 import Sync.MerkleTree.Analyse
 import Sync.MerkleTree.CommTypes
@@ -43,6 +47,11 @@
        outStream <- ST.handleToOutputStream hOut
        return $ StreamPair { sp_in = inStream, sp_out = outStream }
 
+mkChanStreams :: IO (InputStream ByteString, OutputStream ByteString)
+mkChanStreams =
+    do chan <- newChan
+       liftM2 (,) (ST.chanToInput chan) (ST.chanToOutput chan)
+
 instance Protocol RequestMonad where
     queryHashReq = request . QueryHash
     querySetReq = request . QuerySet
@@ -61,23 +70,28 @@
     = FromRemote
     | ToRemote
 
-child :: IO ()
-child =
-    do streams <- openStreams stdin stdout
-       side <- getFromInputStream (sp_in streams)
-       case side of
-         Service dir clientServerOpts -> server dir clientServerOpts streams
-         Client dir clientServerOpts -> client dir clientServerOpts streams
+child :: StreamPair -> IO ()
+child streams =
+    do launchMessage <- getFromInputStream (sp_in streams)
+       serverOrClient (read launchMessage) streams
 
 parent :: StreamPair -> FilePath -> FilePath -> Direction -> ClientServerOptions -> IO ()
 parent streams source destination direction clientServerOpts =
     case direction of
       FromRemote ->
-        do respond (sp_out streams) $ Service source clientServerOpts
-           client destination clientServerOpts streams
+        do respond (sp_out streams) $ show $ mkLaunchMessage Server source
+           serverOrClient (mkLaunchMessage Client destination) streams
       ToRemote ->
-        do respond (sp_out streams) $ Client destination clientServerOpts
-           server source clientServerOpts streams
+        do respond (sp_out streams) $ show $ mkLaunchMessage Client destination
+           serverOrClient (mkLaunchMessage Server source) streams
+    where
+      mkLaunchMessage side dir =
+          LaunchMessage
+          { lm_dir = dir
+          , lm_clientServerOptions = clientServerOpts
+          , lm_protocolVersion = thisProtocolVersion
+          , lm_side = side
+          }
 
 respond :: (Show a, SE.Serialize a) => OutputStream ByteString -> a -> IO ()
 respond os = mapM_ (flip ST.write os . Just) . (:[BS.empty]) . SE.encode
@@ -88,10 +102,19 @@
        destinationDir <- liftM (mkTrie 0) $ analyseDirectory destination (cs_ignore cs) Root
        evalStateT (abstractClient cs destination destinationDir) (startServerState source sourceDir)
 
-server :: FilePath -> ClientServerOptions -> StreamPair -> IO ()
-server fp cs streams =
-    do dir <- analyseDirectory fp (cs_ignore cs) Root
-       evalStateT loop (startServerState fp $ mkTrie 0 dir)
+serverOrClient :: LaunchMessage -> StreamPair -> IO ()
+serverOrClient lm streams
+    | lm_protocolVersion lm == thisProtocolVersion =
+        let side =
+                case lm_side lm of
+                  Server -> server
+                  Client -> client (lm_clientServerOptions lm)
+        in do entries <- analyseDirectory (lm_dir lm) (cs_ignore $ lm_clientServerOptions lm) Root
+              side entries (lm_dir lm) streams
+    | otherwise = fail "Incompatible sync-mht versions."
+
+server :: [Entry] -> FilePath -> StreamPair -> IO ()
+server entries fp streams = evalStateT loop (startServerState fp $ mkTrie 0 entries)
     where
        serverRespond = liftIO . respond (sp_out streams)
        loop =
@@ -104,8 +127,6 @@
                 Log t -> logReq t >>= serverRespond >> loop
                 Terminate -> terminateReq >>= serverRespond >> return ()
 
-client :: FilePath -> ClientServerOptions -> StreamPair -> IO ()
-client fp cs streams =
-    do dir <- analyseDirectory fp (cs_ignore cs) Root
-       runRequestMonad (sp_in streams) (sp_out streams) $ abstractClient cs fp $ mkTrie 0 dir
-
+client :: ClientServerOptions -> [Entry] -> FilePath -> StreamPair -> IO ()
+client cs entries fp streams =
+    runRequestMonad (sp_in streams) (sp_out streams) $ abstractClient cs fp $ mkTrie 0 entries
diff --git a/src/main/hs/Sync/MerkleTree/Test.hs b/src/main/hs/Sync/MerkleTree/Test.hs
new file mode 100644
--- /dev/null
+++ b/src/main/hs/Sync/MerkleTree/Test.hs
@@ -0,0 +1,183 @@
+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" $
+        [ TS.Test testOptions
+        ] ++ (map TS.Test testSync)
+    ]
+
+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_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..50] $ \_ ->
+               withSystemTempDirectory "sync-mht" $ \testDir ->
+                   do mkRandomDir 2 [testDir </> "src", testDir </> "src-backup"]
+                      mkRandomDir 2 [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_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
diff --git a/src/main/hs/Sync/MerkleTree/Types.hs b/src/main/hs/Sync/MerkleTree/Types.hs
--- a/src/main/hs/Sync/MerkleTree/Types.hs
+++ b/src/main/hs/Sync/MerkleTree/Types.hs
@@ -60,13 +60,21 @@
 instance Ord Entry where
     compare = comparing withLevel
         where
-          level Root = 0 :: Int
-          level (Path _ p) = 1 + level p
-          withLevel entry =
+          withLevel entry = (levelOf entry, toEither entry)
+          toEither entry =
               case entry of
-                DirectoryEntry p -> (level p, Right p)
-                FileEntry f -> (level $ f_name f, Left f)
+                DirectoryEntry p -> Right p
+                FileEntry f -> Left f
 
+levelOf :: Entry -> Int
+levelOf e =
+    case e of
+      DirectoryEntry p -> level p
+      FileEntry f -> level $ f_name f
+    where
+      level Root = 0
+      level (Path _ p) = 1 + level p
+
 instance HasDigest Entry where
     digest = hash . SE.encode
 
@@ -76,4 +84,3 @@
 instance SE.Serialize SerText where
     get = SE.get >>= either (fail . show) (return . SerText) . TE.decodeUtf8'
     put = SE.put . TE.encodeUtf8 . unSerText
-
diff --git a/src/main/hs/Sync/MerkleTree/Util/RequestMonad.hs b/src/main/hs/Sync/MerkleTree/Util/RequestMonad.hs
--- a/src/main/hs/Sync/MerkleTree/Util/RequestMonad.hs
+++ b/src/main/hs/Sync/MerkleTree/Util/RequestMonad.hs
@@ -39,7 +39,6 @@
 import Data.IORef(IORef,newIORef,modifyIORef,readIORef)
 import Data.Monoid(Monoid, mempty, mappend)
 import Data.Serialize(Serialize)
-import System.IO(hPutStrLn,stderr)
 import System.IO.Streams(InputStream, OutputStream)
 import qualified Data.ByteString as BS
 import qualified Data.Serialize as SE
@@ -162,9 +161,7 @@
           do x <- getFromInputStream input
              modifyIORef recvIdx (+1)
              expected <- readIORef recvIdx
-             unless (expected == i) $
-                 do hPutStrLn stderr $ "Expected " ++ (show i) ++ " but got " ++ show expected
-                    fail $ "Expected " ++ show i ++ " but got " ++ show expected
+             unless (expected == i) $ fail ("Expected " ++ (show i) ++ " but got " ++ show expected)
              queueRequests sq $ cont x
       Split (SplitState xs z cont) -> loop cont z xs []
       Return x -> return $ Return x
diff --git a/sync-mht.cabal b/sync-mht.cabal
--- a/sync-mht.cabal
+++ b/sync-mht.cabal
@@ -6,9 +6,9 @@
 maintainer: Emin Karayel <me@eminkarayel.de>
 category: Utility
 extra-doc-files: README.md
-cabal-version: >=1.10
+cabal-version: >= 1.18
 build-type: Simple
-version: 0.2.1.1
+version: 0.3.0.0
 homepage: https://github.com/ekarayel/sync-mht
 bug-reports: https://github.com/ekarayel/sync-mht/issues
 package-url: https://github.com/ekarayel/sync-mht
@@ -29,8 +29,46 @@
     location: https://github.com/ekarayel/sync-mht --recursive
 source-repository this
     type: git
-    tag: 0.2.1.1
+    tag: 0.3.0.0
     location: https://github.com/ekarayel/sync-mht --recursive
+test-suite main
+    type: detailed-0.9
+    test-module: Sync.MerkleTree.Test
+    other-modules:
+        Sync.MerkleTree.Analyse
+        Sync.MerkleTree.Client
+        Sync.MerkleTree.CommTypes
+        Sync.MerkleTree.Server
+        Sync.MerkleTree.Sync
+        Sync.MerkleTree.Run
+        Sync.MerkleTree.Trie
+        Sync.MerkleTree.Types
+        Sync.MerkleTree.Util.RequestMonad
+        Paths_sync_mht
+    hs-source-dirs: src/main/hs
+    default-language: Haskell2010
+    build-depends: 
+        base >=4.7 && <4.8
+        , unix >=2.7 && <2.8
+        , directory >=1.2.3 && <1.3
+        , filepath >=1.3 && <1.4
+        , process >=1.2 &&  <1.3
+        , cryptohash >=0.11 && <0.12
+        , byteable >=0.1 && <0.2
+        , array >=0.5 && <0.6
+        , containers >=0.5 && <0.6
+        , text >=1.2 && <1.3
+        , bytestring >=0.10 && <0.11
+        , base16-bytestring >=0.1 && <0.2
+        , cereal >= 0.4 && < 0.5
+        , io-streams >= 1.2 && <1.3
+        , transformers >= 0.4 && < 0.5
+        , regex-compat >= 0.95 && < 0.96
+        , mtl >= 2.2 && < 2.3
+        , Cabal >= 1.18 && <= 1.19
+        , time >= 1.4 && < 1.5
+        , random >= 1.0 && < 1.1
+        , temporary >= 1.2
 executable sync-mht
     main-is: Main.hs
     other-modules:
@@ -39,6 +77,7 @@
         Sync.MerkleTree.CommTypes
         Sync.MerkleTree.Server
         Sync.MerkleTree.Sync
+        Sync.MerkleTree.Run
         Sync.MerkleTree.Trie
         Sync.MerkleTree.Types
         Sync.MerkleTree.Util.RequestMonad
@@ -46,7 +85,7 @@
     build-depends: 
         base >=4.7 && <4.8
         , unix >=2.7 && <2.8
-        , directory >=1.2 && <1.3
+        , directory >=1.2.3 && <1.3
         , filepath >=1.3 && <1.4
         , process >=1.2 &&  <1.3
         , cryptohash >=0.11 && <0.12
@@ -59,6 +98,11 @@
         , cereal >= 0.4 && < 0.5
         , io-streams >= 1.2 && <1.3
         , transformers >= 0.4 && < 0.5
+        , regex-compat >= 0.95 && < 0.96
         , mtl >= 2.2 && < 2.3
+        , Cabal >= 1.18 && <= 1.19
+        , time >= 1.4 && < 1.5
+        , random >= 1.0 && < 1.1
+        , temporary >= 1.2
     hs-source-dirs: src/main/hs
     default-language: Haskell2010
