diff --git a/fsnotify.cabal b/fsnotify.cabal
--- a/fsnotify.cabal
+++ b/fsnotify.cabal
@@ -1,5 +1,5 @@
 Name:                   fsnotify
-Version:                0.0.3
+Version:                0.0.4
 Author:                 Mark Dittmer <mark.s.dittmer@gmail.com>
 Maintainer:             Mark Dittmer <mark.s.dittmer@gmail.com>, Greg Weber <greg@gregweber.info>
 License:                BSD3
@@ -13,6 +13,10 @@
 Category:               Filesystem
 Cabal-Version:          >= 1.8
 Build-Type:             Simple
+Extra-Source-Files:     test/FSNotify.hs
+                        test/Path.hs
+                        test/Util.hs
+                        test/watch-here.hs
 
 
 Library
@@ -59,38 +63,40 @@
 --                 , text >= 0.11.0
 --                 , time >= 1.4
 
-Test-Suite test
-  Type:                 exitcode-stdio-1.0
-  Main-Is:              main.hs
-  -- Type:                 detailed-0.9
-  -- Test-Module:          Tests
-  Hs-Source-Dirs:       test, src
-  GHC-Options:          -Wall -threaded
-  Build-depends:          base >= 4.3.1.0
-                        , bytestring >= 0.9.2.1
-                        , Cabal >= 1.14.0
-                        , containers >= 0.4.2.1
-                        , directory >= 1.1.0.2
-                        , Glob >= 0.7.1
-                        , hspec >= 1.3.0
-                        , random >= 1.0.1.1
-                        , system-filepath >= 0.4.6
-                        , system-fileio >= 0.3.7
-                        , text >= 0.10
-                        , time >= 1.4
-                        , QuickCheck >= 2.4.2
-                        , uniqueid >= 0.1.1
-  if os(linux)
-    CPP-Options:        -DOS_Linux
-    Build-Depends:      hinotify == 0.3.2
-  else
-    if os(windows)
-      CPP-Options:      -DOS_Win32
-      Build-Depends:    Win32-notify == 0.2, ghc >= 7.4.2
-    else
-      if os(darwin)
-        CPP-Options:    -DOS_Mac
-        Build-Depends:  hfsevents >= 0.1.3
+-- DISABLED: older Cabal lib versions have a bug which means they fall
+-- over when looking at test-suite sections that contain conditionals
+-- Test-Suite test
+--   Type:                 exitcode-stdio-1.0
+--   Main-Is:              main.hs
+--   -- Type:                 detailed-0.9
+--   -- Test-Module:          Tests
+--   Hs-Source-Dirs:       test, src
+--   GHC-Options:          -Wall -threaded
+--   Build-depends:          base >= 4.3.1.0
+--                         , bytestring >= 0.9.2.1
+--                         , Cabal >= 1.14.0
+--                         , containers >= 0.4.2.1
+--                         , directory >= 1.1.0.2
+--                         , Glob >= 0.7.1
+--                         , hspec >= 1.3.0
+--                         , random >= 1.0.1.1
+--                         , system-filepath >= 0.4.6
+--                         , system-fileio >= 0.3.7
+--                         , text >= 0.10
+--                         , time >= 1.4
+--                         , QuickCheck >= 2.4.2
+--                         , uniqueid >= 0.1.1
+--   if os(linux)
+--     CPP-Options:        -DOS_Linux
+--     Build-Depends:      hinotify == 0.3.2
+--   else
+--     if os(windows)
+--       CPP-Options:      -DOS_Win32
+--       Build-Depends:    Win32-notify == 0.2, ghc >= 7.4.2
+--     else
+--       if os(darwin)
+--         CPP-Options:    -DOS_Mac
+--         Build-Depends:  hfsevents >= 0.1.3
 
 
 Source-Repository head
diff --git a/test/FSNotify.hs b/test/FSNotify.hs
new file mode 100644
--- /dev/null
+++ b/test/FSNotify.hs
@@ -0,0 +1,198 @@
+--
+-- Copyright (c) 2012 Mark Dittmer - http://www.markdittmer.org
+-- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org
+--
+
+module FSNotify (spec) where
+
+import Prelude hiding (appendFile, FilePath, writeFile)
+
+import Control.Concurrent.Chan (newChan, writeChan)
+import Data.ByteString (empty)
+import Data.Text (pack)
+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
+import Filesystem (removeFile, rename, writeFile, writeTextFile)
+import Filesystem.Path.CurrentOS ((</>), FilePath)
+import System.FilePath.Glob (compile, match, Pattern)
+import System.FSNotify.Path (fp)
+import System.FSNotify.Types
+import Test.Hspec (describe, it, Spec)
+import Util
+type Assertion = IO ()
+
+spec :: Spec
+spec = do
+  describe "watchDir" $ do
+    it "Create file" $ testFileName "txt" >>= createFileSpec ActionEnv
+    it "Modify file" $ testFileName "txt" >>= modifyFileSpec ActionEnv
+    it "Remove file" $ testFileName "txt" >>= removeFileSpec ActionEnv
+    it "Rename file" $ renameInput        >>= renameFileSpec ActionEnv
+    it "Debounce"    $ testFileName "txt" >>= dbFileSpec     ActionEnv
+  describe "watchDirChan" $ do
+    it "Create file" $ testFileName "txt" >>= createFileSpec ChanEnv
+    it "Modify file" $ testFileName "txt" >>= modifyFileSpec ChanEnv
+    it "Remove file" $ testFileName "txt" >>= removeFileSpec ChanEnv
+    it "Rename file" $ renameInput        >>= renameFileSpec ChanEnv
+  describe "watchTree" $ do
+    it "Create file (pre-existing directory)" $ testFileName "txt" >>= createFileSpecR1 ActionEnv
+    it "Create file (create directory)"       $ testFileName "txt" >>= createFileSpecR2 ActionEnv
+    it "Modify file" $ testFileName "txt" >>= modifyFileSpecR ActionEnv
+    it "Remove file" $ testFileName "txt" >>= removeFileSpecR ActionEnv
+    it "Rename file" $ renameInput        >>= renameFileSpecR ActionEnv
+  describe "watchTreeChan" $ do
+    it "Create file (pre-existing directory)" $ testFileName "txt" >>= createFileSpecR1 ChanEnv
+    it "Create file (create directory)"       $ testFileName "txt" >>= createFileSpecR2 ChanEnv
+    it "Modify file" $ testFileName "txt" >>= modifyFileSpecR ChanEnv
+    it "Remove file" $ testFileName "txt" >>= removeFileSpecR ChanEnv
+    it "Rename file" $ renameInput        >>= renameFileSpecR ChanEnv
+
+createFileSpec :: ChanActionEnv -> FilePath -> Assertion
+createFileSpec envType fileName = do
+  inEnv envType DirEnv act action $ matchEvents matchers
+  where
+    action :: FilePath -> IO ()
+    action envDir = writeFile (envDir </> fileName) empty
+    matchers :: [EventPredicate]
+    matchers = [EventPredicate "File creation" (matchCreate fileName)]
+
+modifyFileSpec :: ChanActionEnv -> FilePath -> Assertion
+modifyFileSpec envType fileName = do
+  withTempDir $ \envDir -> do
+    writeFile (envDir </> fileName) empty
+    inTempDirEnv envType DirEnv act action (matchEvents matchers) envDir
+  where
+    action :: FilePath -> IO ()
+    action envDir = do
+      writeTextFile  (envDir </> fileName) $ pack "Hello world"
+    matchers :: [EventPredicate]
+    matchers = [EventPredicate "File modification" (matchModify fileName)]
+
+removeFileSpec :: ChanActionEnv -> FilePath -> Assertion
+removeFileSpec envType fileName = do
+  withTempDir $ \envDir -> do
+    writeFile (envDir </> fileName) empty
+    inTempDirEnv envType DirEnv act action (matchEvents matchers) envDir
+  where
+    action :: FilePath -> IO ()
+    action envDir = removeFile (envDir </> fileName)
+    matchers :: [EventPredicate]
+    matchers = [EventPredicate "File deletion" (matchRemove fileName)]
+
+renameFileSpec :: ChanActionEnv -> (FilePath, FilePath) -> Assertion
+renameFileSpec envType (oldFileName, newFileName) = do
+  withTempDir $ \envDir -> do
+    writeFile (envDir </> oldFileName) empty
+    inTempDirEnv envType DirEnv act action (matchEvents matchers) envDir
+  where
+    action :: FilePath -> IO ()
+    action envDir = rename (envDir </> oldFileName) (envDir </> newFileName)
+    matchers :: [EventPredicate]
+    matchers = [ EventPredicate "Rename: File deletion" (matchRemove oldFileName)
+               , EventPredicate "Rename: File creation" (matchCreate newFileName) ]
+
+-- TODO: This is a weak test. What we actually need is an interface for
+-- "anti-matchers" to ensure that certain events do NOT get reported.
+dbFileSpec :: ChanActionEnv -> FilePath -> Assertion
+dbFileSpec envType _ = do
+  chan <- newChan
+  inChanEnv envType DirEnv act (action chan) (matchEvents matchers) chan
+  where
+    action :: EventChannel -> FilePath -> IO ()
+    action chan _ = do
+      writeChan chan e1
+      writeChan chan e2
+    matchers :: [EventPredicate]
+    matchers = [EventPredicate "First debounced event" (\e -> e == e1)]
+    e1 :: Event
+    e1 = Added (fp "") (posixSecondsToUTCTime 0)
+    e2 :: Event
+    e2 = Modified (fp "") (posixSecondsToUTCTime 0)
+
+createFileSpecR1 :: ChanActionEnv -> FilePath -> Assertion
+createFileSpecR1 envType fileName = do
+  withTempDir $ \envDir -> do
+    withNestedTempDir envDir $ \envPath -> do
+      inTempDirEnv envType TreeEnv act (action envPath) (matchEvents matchers) envDir
+  where
+    action :: FilePath -> FilePath -> IO ()
+    action envPath _ = do
+      writeFile (envPath </> fileName) empty
+    matchers :: [EventPredicate]
+    matchers = [EventPredicate "File creation" (matchCreate fileName)]
+
+createFileSpecR2 :: ChanActionEnv -> FilePath -> Assertion
+createFileSpecR2 envType fileName = do
+  withTempDir $ \envDir -> do
+    inTempDirEnv envType TreeEnv act (action envDir) (matchEvents matchers) envDir
+  where
+    action :: FilePath -> FilePath -> IO ()
+    action envDir _ = do
+      withNestedTempDir envDir $ \envPath -> writeFile (envPath </> fileName) empty
+    matchers :: [EventPredicate]
+    matchers = [EventPredicate "File creation" (matchCreate fileName)]
+
+modifyFileSpecR :: ChanActionEnv -> FilePath -> Assertion
+modifyFileSpecR envType fileName = do
+  withTempDir $ \envDir -> do
+    withNestedTempDir envDir $ \envPath -> do
+      writeFile (envPath </> fileName) empty
+      inTempDirEnv envType TreeEnv act (action envPath) (matchEvents matchers) envDir
+  where
+    action :: FilePath -> FilePath -> IO ()
+    action envPath _ = do
+      writeTextFile  (envPath </> fileName) $ pack "Hello world"
+    matchers :: [EventPredicate]
+    matchers = [EventPredicate "File deletion" (matchModify fileName)]
+
+removeFileSpecR :: ChanActionEnv -> FilePath -> Assertion
+removeFileSpecR envType fileName = do
+  withTempDir $ \envDir -> do
+    withNestedTempDir envDir $ \envPath -> do
+      writeFile (envPath </> fileName) empty
+      inTempDirEnv envType TreeEnv act (action envPath) (matchEvents matchers) envDir
+  where
+    action :: FilePath -> FilePath -> IO ()
+    action envPath _ = do
+      removeFile (envPath </> fileName)
+    matchers :: [EventPredicate]
+    matchers = [EventPredicate "File deletion" (matchRemove fileName)]
+
+renameFileSpecR :: ChanActionEnv -> (FilePath, FilePath) -> Assertion
+renameFileSpecR envType (oldFileName, newFileName) = do
+  withTempDir $ \envDir -> do
+    withNestedTempDir envDir $ \envPath -> do
+      writeFile (envPath </> oldFileName) empty
+      inTempDirEnv envType TreeEnv act (action envPath) (matchEvents matchers) envDir
+  where
+    action :: FilePath -> FilePath -> IO ()
+    action envPath _ = rename (envPath </> oldFileName) (envPath </> newFileName)
+    matchers :: [EventPredicate]
+    matchers = [ EventPredicate "Rename: File deletion" (matchRemove oldFileName)
+               , EventPredicate "Rename: File creation" (matchCreate newFileName) ]
+
+renameInput :: IO (FilePath, FilePath)
+renameInput = do
+  oldName <- testFileName "txt"
+  newName <- testFileName "txt"
+  return (oldName, newName)
+
+matchCreate :: FilePath -> Event -> Bool
+matchCreate fileName (Added path _) = matchFP pattern path
+  where
+    pattern = compile $  "**/*" ++ fp fileName
+matchCreate _ _ = False
+
+matchModify :: FilePath -> Event -> Bool
+matchModify fileName (Modified path _) = matchFP pattern path
+  where
+    pattern = compile $  "**/*" ++ fp fileName
+matchModify _ _ = False
+
+matchRemove :: FilePath -> Event -> Bool
+matchRemove fileName (Removed path _) = matchFP pattern path
+  where
+    pattern = compile $  "**/*" ++ fp fileName
+matchRemove _ _ = False
+
+matchFP :: Pattern -> FilePath -> Bool
+matchFP pattern path = match pattern $ fp path
diff --git a/test/Path.hs b/test/Path.hs
new file mode 100644
--- /dev/null
+++ b/test/Path.hs
@@ -0,0 +1,139 @@
+--
+-- Copyright (c) 2012 Mark Dittmer - http://www.markdittmer.org
+-- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org
+--
+{-# LANGUAGE CPP #-}
+
+module Path (spec) where
+
+import Prelude hiding (FilePath, writeFile)
+
+import Control.Applicative ((<*>))
+import Filesystem (writeFile)
+import Filesystem.Path.CurrentOS (FilePath)
+import Filesystem.Path ((</>), empty)
+import System.FilePath.Glob (compile, match)
+import System.FSNotify.Path (canonicalizeDirPath, canonicalizePath, findDirs, findFiles, fp)
+import Test.Hspec (describe, it, Spec, shouldBe)
+import Util
+import qualified Data.ByteString as BS
+type Assertion = IO ()
+
+-- Boolean XOR
+(.^.) :: Bool -> Bool -> Bool
+(.^.) True  True  = False
+(.^.) True  False = True
+(.^.) False True  = True
+(.^.) False False = False
+
+hasTrailingSlash :: FilePath -> (FilePath -> IO FilePath) -> Assertion
+hasTrailingSlash path canonicalizeFn = do
+  let expectedTail = last $ fp (fp "dir" </> empty) -- Get OS/filesystem's idea of a separator
+  actualPath <- canonicalizeFn path
+  let actualTail = last (fp actualPath) :: Char
+  actualTail `shouldBe` expectedTail
+
+relPath      :: FilePath
+relPathSlash :: FilePath
+absPath      :: FilePath
+absPathSlash :: FilePath
+
+relPath      = fp "."
+#ifdef OS_Linux
+absPath      = fp "/home"
+absPathSlash = fp "/home/"
+relPathSlash = fp "./" </> empty
+#else
+#  ifdef OS_Win32
+absPath      = fp "C:" </> fp "Windows"
+absPathSlash = fp "C:" </> fp "Windows" </> empty
+relPathSlash = fp ".\\" </> empty
+#  else
+#    ifdef OS_Mac
+absPath      = fp "/Users"
+absPathSlash = fp "/Users/"
+relPathSlash = fp "./"
+#    else
+-- Assume UNIX-like for anything non-Linux/Windows/Mac
+absPath      = fp "/home"
+absPathSlash = fp "/home/"
+relPathSlash = fp "./" </> empty
+#    endif
+#  endif
+#endif
+
+
+spec :: Spec
+spec = do
+  describe "canonicalizeDirPath" $ do
+    it "Absolute path keeps trailing slash" $ do
+      hasTrailingSlash absPathSlash canonicalizeDirPath
+    it "Absolute path gains trailing slash" $ do
+      hasTrailingSlash absPath canonicalizeDirPath
+    it "Relative path keeps trailing slash" $ do
+      hasTrailingSlash relPathSlash canonicalizeDirPath
+    it "Relative path gains trailing slash" $ do
+      hasTrailingSlash relPath canonicalizeDirPath
+  describe "canonicalizePath" $ do
+    it "Absolute path keeps trailing slash" $ do
+      hasTrailingSlash absPathSlash canonicalizePath
+    it "Relative path keeps trailing slash" $ do
+      hasTrailingSlash relPathSlash canonicalizePath
+  describe "findFiles" $ do
+    it "Non-recursive" $ do
+      withTempDir $ \tmpDir -> do
+        fileName <- testFileName "txt"
+        writeFile (tmpDir </> fileName) BS.empty
+        files <- findFiles False tmpDir
+        1 `shouldBe` length files
+        let (resultFP:_) = files
+            pattern = "**/*" ++ fp fileName
+            result = fp resultFP
+        if match (compile pattern) result then
+          True `shouldBe` True
+          else
+          result `shouldBe` pattern
+    it "Recursive" $ do
+      withTempDir $ \tmpDir -> do
+        withNestedTempDir tmpDir $ \tmpPath -> do
+          fileName <- testFileName "txt"
+          writeFile (tmpPath </> fileName) BS.empty
+          files <- findFiles True tmpDir
+          1 `shouldBe` length files
+          let (resultFP:_) = files
+              pattern = "**/*" ++ fp fileName
+              result = fp resultFP
+          if match (compile pattern) result then
+            True `shouldBe` True
+            else
+            result `shouldBe` pattern
+  describe "findDirs" $ do
+    it "Non-recursive" $
+      withTempDir $ \tmpDir -> do
+        withNestedTempDir tmpDir $ \dirName -> do
+          dirs <- findDirs False tmpDir
+          1 `shouldBe` length dirs
+          let (resultFP:_) = dirs
+              pattern = "**/*" ++ fp dirName
+              result = fp resultFP
+          if match (compile pattern) result then
+            True `shouldBe` True
+            else
+            result `shouldBe` pattern
+    it "Recursive" $
+      withTempDir $ \tmpDir -> do
+        withNestedTempDir tmpDir $ \dirName1 -> do
+          withNestedTempDir tmpDir $ \dirName2 -> do
+            dirs <- findDirs False tmpDir
+            2 `shouldBe` length dirs
+            let pats = ["**/*" ++ fp dirName1, "**/*" ++ fp dirName2]
+                patFns = map (match . compile) pats
+                dirStrings = map fp dirs
+                (r1:r2:r3:r4:_) = patFns <*> dirStrings
+            -- The two patterns should succeed once and fail once on
+            -- opposite tests.
+            if (r1 .^. r2) && (r3 .^. r4) && (r1 .^. r3) && (r2 .^. r4) then
+              True `shouldBe` True
+              else
+              dirStrings `shouldBe` pats
+            return ()
diff --git a/test/Util.hs b/test/Util.hs
new file mode 100644
--- /dev/null
+++ b/test/Util.hs
@@ -0,0 +1,189 @@
+--
+-- Copyright (c) 2012 Mark Dittmer - http://www.markdittmer.org
+-- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org
+--
+
+module Util where
+
+import Prelude hiding (FilePath, catch, pred)
+
+import Control.Concurrent (threadDelay)
+import Control.Concurrent.Chan
+import Control.Concurrent.MVar (MVar, newMVar, readMVar, swapMVar)
+import Control.Exception
+import Control.Monad (when)
+import Data.Unique.Id
+import Filesystem.Path.CurrentOS hiding (concat)
+import System.Directory
+import System.IO.Error (isPermissionError)
+import System.FSNotify
+import System.FSNotify.Path
+import System.FSNotify.Types
+import System.Random
+import System.Timeout (timeout)
+
+data ChanActionEnv =
+    ChanEnv
+  | ActionEnv
+data DirTreeEnv =
+    DirEnv
+  | TreeEnv
+data TestContext = TestContext ChanActionEnv DirTreeEnv ActionPredicate
+
+data TestReport = TestReport FilePath [Event] deriving (Show)
+data TestResult = TestResult Bool String TestReport deriving (Show)
+type TestAction = FilePath -> IO ()
+type MTestResult = MVar TestResult
+type TestCase = MTestResult -> IO ()
+type CurriedEventProcessor = TestReport -> IO (TestResult)
+type EventProcessor = MTestResult -> CurriedEventProcessor
+data EventPredicate = EventPredicate String (Event -> Bool)
+
+void :: IO ()
+void = return ()
+
+predicateName :: EventPredicate -> String
+predicateName (EventPredicate name _) = name
+
+matchEvents :: [EventPredicate] -> EventProcessor
+matchEvents preds mVar report@(TestReport _ events) =
+  swapMVar mVar result >> return result
+  where
+    matchMatrix :: [[Bool]]
+    matchMatrix = map (\(EventPredicate _ pred) -> map (\event -> pred event) events) preds
+    matchList :: [Bool]
+    matchList = map (\lst -> any id lst) matchMatrix
+    errorList :: [(Bool, String)]
+    errorList = zip matchList (map (\(EventPredicate errStr _) -> errStr) preds)
+    errorString :: String
+    errorString = foldl foldError "" errorList
+    foldError :: String -> (Bool, String) -> String
+    foldError accStr (success, errStr) = if not success then accStr ++ " " ++ errStr else accStr
+    status :: Bool
+    status = all id matchList
+    result =   if status then
+                 TestResult status "" report
+               else
+                 TestResult status ("Failed to match events: " ++ errorString) report
+
+newId :: IO String
+newId = randomIO >>= initIdSupply >>= return . show . hashedId . idFromSupply
+
+testFileName :: String -> IO FilePath
+testFileName ext = do
+  uId <- newId
+  return $ fp ("test-" ++ uId ++ "." ++ ext)
+
+testName :: IO FilePath
+testName = do
+  uId <- newId
+  return $ fp ("sandbox-" ++ uId) </> empty
+
+dirPreAction :: Int
+dirPreAction = 500000
+
+-- Delay to keep temporary directories around long enough for events to be
+-- picked up by OS (in microseconds)
+dirPostAction :: Int
+dirPostAction = 500000
+
+withTempDir :: (FilePath -> IO ()) -> IO ()
+withTempDir fn = withNestedTempDir empty fn
+
+withNestedTempDir :: FilePath -> (FilePath -> IO ()) -> IO ()
+withNestedTempDir firstPath fn = do
+  secondPath <- testName
+  let path = if firstPath /= empty then
+               fp $ firstPath </> secondPath
+             else
+               fp secondPath
+  bracket (createDirectory path >> threadDelay dirPreAction >> return path) (attemptDirectoryRemoval . fp) (fn . fp)
+
+attemptDirectoryRemoval :: FilePath -> IO ()
+attemptDirectoryRemoval path = do
+  threadDelay dirPostAction
+  catch
+    (removeDirectoryRecursive pathString)
+    (\e -> when
+           (not $ isPermissionError e)
+           (throw e))
+  where
+    pathString = fp path
+
+performAction :: TestAction -> FilePath -> IO ()
+performAction action path = action path
+
+reportOnAction :: FilePath -> EventChannel -> CurriedEventProcessor -> IO TestResult
+reportOnAction = reportOnAction' []
+
+reportOnAction' :: [Event] -> FilePath -> EventChannel -> CurriedEventProcessor -> IO TestResult
+reportOnAction' events path chan processor = do
+  result@(TestResult status _ _) <- processor (TestReport path events)
+  if not status then do
+    event <- readChan chan
+    reportOnAction' (event:events) path chan processor
+    else
+    return result
+
+actAndReport :: TestAction -> FilePath -> EventChannel -> CurriedEventProcessor -> IO TestResult
+actAndReport action path chan processor = do
+  performAction action path
+  reportOnAction path chan processor
+
+testTimeout :: Int
+testTimeout = 3000000
+
+timeoutTest :: MTestResult -> Maybe () -> IO ()
+timeoutTest mResult Nothing = do
+  result <- readMVar mResult
+  error $ "TIMEOUT: Last test result: " ++ show result
+timeoutTest mResult (Just _) = do
+  result <- readMVar mResult
+  case result of
+    (TestResult False _ _) -> error $ show result
+    (TestResult True  _ _) -> void
+
+runTest :: TestCase -> IO ()
+runTest test = do
+  mVar <- newMVar $ TestResult False "Timeout with no test result" (TestReport empty [])
+  timeout testTimeout (test mVar) >>= timeoutTest mVar
+
+inEnv :: ChanActionEnv -> DirTreeEnv -> ActionPredicate -> TestAction -> EventProcessor -> IO ()
+inEnv caEnv dtEnv reportPred action eventProcessor =
+  withTempDir $ inTempDirEnv caEnv dtEnv reportPred action eventProcessor
+
+inTempDirEnv :: ChanActionEnv -> DirTreeEnv -> ActionPredicate -> TestAction -> EventProcessor-> FilePath -> IO ()
+inTempDirEnv caEnv dtEnv reportPred action eventProcessor path =
+  withManagerConf NoDebounce $ \manager -> do
+    chan <- newChan
+    inTempDirChanEnv caEnv dtEnv reportPred action eventProcessor path manager chan
+
+inChanEnv :: ChanActionEnv -> DirTreeEnv -> ActionPredicate -> TestAction -> EventProcessor -> EventChannel -> IO ()
+inChanEnv caEnv dtEnv reportPred action eventProcessor chan =
+  withTempDir $ \path -> do
+    withManagerConf NoDebounce $ \manager -> do
+      inTempDirChanEnv caEnv dtEnv reportPred action eventProcessor path manager chan
+
+inTempDirChanEnv :: ChanActionEnv -> DirTreeEnv -> ActionPredicate -> TestAction -> EventProcessor-> FilePath -> WatchManager -> EventChannel -> IO ()
+inTempDirChanEnv caEnv dtEnv reportPred action eventProcessor path manager chan = do
+    watchInEnv caEnv dtEnv manager path reportPred chan
+    runTest $ \mVar -> do
+      _ <- actAndReport action path chan $ eventProcessor mVar
+      void
+    void
+
+actionAsChan :: (WatchManager -> FilePath -> ActionPredicate -> Action       -> IO ()) ->
+                 WatchManager -> FilePath -> ActionPredicate -> EventChannel -> IO ()
+actionAsChan actionFunction wm path ap ec = actionFunction wm path ap (writeChan ec)
+
+watchInEnv :: ChanActionEnv
+           -> DirTreeEnv
+           -> WatchManager
+           -> FilePath
+           -> ActionPredicate
+           -> EventChannel
+           -> IO ()
+watchInEnv ChanEnv   DirEnv  = watchDirChan
+watchInEnv ChanEnv   TreeEnv = watchTreeChan
+watchInEnv ActionEnv DirEnv  = actionAsChan watchDir
+watchInEnv ActionEnv TreeEnv = actionAsChan watchTree
diff --git a/test/main.hs b/test/main.hs
deleted file mode 100644
--- a/test/main.hs
+++ /dev/null
@@ -1,11 +0,0 @@
-module Main where
-
-import Test.Hspec (hspec, Spec)
-import qualified Path as P
-import qualified FSNotify as FSN
-
-main :: IO ()
-main = hspec spec
-
-spec :: Spec
-spec = P.spec >> FSN.spec
diff --git a/test/watch-here.hs b/test/watch-here.hs
new file mode 100644
--- /dev/null
+++ b/test/watch-here.hs
@@ -0,0 +1,20 @@
+{-
+{-# LANGUAGE OverloadedStrings #-}
+import Filesystem.Path.CurrentOS
+-}
+import System.FSNotify
+import Filesystem
+
+main :: IO ()
+main = do
+  -- let wd = "."
+  wd <- getWorkingDirectory
+  print wd
+  man <- startManager
+  watchTree man wd (const True) print
+  print "press retrun to stop"
+  getLine
+  print "watching stopped, press retrun to exit"
+  stopManager man
+  getLine
+  return ()
