fsnotify-0.3.0.0: test/Test.hs
{-# LANGUAGE CPP, OverloadedStrings, ImplicitParams, MultiWayIf #-}
import Control.Concurrent
import Control.Exception
import Control.Monad
import Data.Monoid
import Prelude hiding (FilePath)
import System.Directory
import System.FSNotify
import System.FilePath
import System.IO
import System.IO.Temp
import System.PosixCompat.Files
import System.Random as R
import Test.Tasty
import Test.Tasty.HUnit
import EventUtils
#ifdef mingw32_HOST_OS
import Data.Bits
import System.Win32.File (getFileAttributes, setFileAttributes, fILE_ATTRIBUTE_TEMPORARY)
-- Perturb the file's attributes, to check that a modification event is emitted
changeFileAttributes :: FilePath -> IO ()
changeFileAttributes file = do
attrs <- getFileAttributes file
setFileAttributes file (attrs `xor` fILE_ATTRIBUTE_TEMPORARY)
#else
changeFileAttributes :: FilePath -> IO ()
changeFileAttributes = touchFile
#endif
isMac :: Bool
#ifdef darwin_HOST_OS
isMac = True
#else
isMac = False
#endif
nativeMgrSupported :: IO Bool
nativeMgrSupported = do
mgr <- startManager
stopManager mgr
return $ not $ isPollingManager mgr
main :: IO ()
main = do
hasNative <- nativeMgrSupported
unless hasNative $ putStrLn "WARNING: native manager cannot be used or tested on this platform"
defaultMain $ withResource (createDirectoryIfMissing True testDirPath)
(const $ removeDirectoryRecursive testDirPath)
(const $ tests hasNative)
-- | There's some kind of race in OS X where the creation of the containing directory shows up as an event
-- I explored whether this was due to passing 0 as the sinceWhen argument to FSEventStreamCreate
-- in the hfsevents package, but changing that didn't seem to help
pauseBeforeStartingTest :: IO ()
pauseBeforeStartingTest = threadDelay 10000
tests :: Bool -> TestTree
tests hasNative = testGroup "Tests" $ do
poll <- if hasNative then [False, True] else [True]
let ?timeInterval = if poll then 2*10^(6 :: Int) else 5*10^(5 :: Int)
return $ testGroup (if poll then "Polling" else "Native") $ do
recursive <- [False, True]
return $ testGroup (if recursive then "Recursive" else "Non-recursive") $ do
nested <- [False, True]
return $ testGroup (if nested then "In a subdirectory" else "Right here") $ do
t <- [ mkTest "new file" (if | isMac && not poll -> [evAddedOrModified False]
| otherwise -> [evAdded False])
(const $ return ())
(\f -> openFile f AppendMode >>= hClose)
, mkTest "modify file" [evModified False]
(\f -> writeFile f "")
(\f -> appendFile f "foo")
-- This test is disabled when polling because the PollManager only keeps track of
-- modification time, so it won't catch an unrelated file attribute change
, mkTest "modify file attributes" (if poll then [] else [evModified False])
(\f -> writeFile f "")
(\f -> if poll then return () else changeFileAttributes f)
, mkTest "delete file" [evRemoved False]
(\f -> writeFile f "")
(\f -> removeFile f)
, mkTest "new directory" (if | isMac -> [evAddedOrModified True]
| otherwise -> [evAdded True])
(const $ return ())
createDirectory
, mkTest "delete directory" [evRemoved True]
(\f -> createDirectory f)
removeDirectory
]
return $ t nested recursive poll
mkTest :: (?timeInterval::Int) => TestName -> [FilePath -> EventPattern] -> (FilePath -> IO a) ->
(FilePath -> IO ()) -> Bool -> Bool -> Bool -> TestTree
mkTest title evs prepare action nested recursive poll = do
testCase title $ do
-- Use a random identifier so that every test happens in a different folder
-- This is unfortunately necessary because of the madness of OS X FSEvents; see the comments in OSX.hs
randomID <- replicateM 10 $ R.randomRIO ('a', 'z')
let pollDelay = when poll (threadDelay $ 10^(6 :: Int))
withTempDirectory testDirPath ("test." <> randomID) $ \watchedDir -> do
let fileName = "testfile"
let baseDir = if nested then watchedDir </> "subdir" else watchedDir
f = normalise $ baseDir </> fileName
watchFn = if recursive then watchTree else watchDir
expect = expectEvents poll watchFn watchedDir
createDirectoryIfMissing True baseDir
pauseBeforeStartingTest
flip finally (doesFileExist f >>= flip when (removeFile f)) $ do
_ <- prepare f
pauseBeforeStartingTest
flip expect (pollDelay >> action f) (if | nested && (not recursive) -> []
| otherwise -> [ev f | ev <- evs])