2-- Module : Streamly.Test.FileSystem.Event
3-- Copyright : (c) 2020 Composewell Technologies
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
9module Main (main) where
11import Control.Concurrent (MVar, newEmptyMVar, putMVar, takeMVar, threadDelay)
12import Control.Monad.IO.Class (MonadIO)
13#if !defined(CABAL_OS_WINDOWS)
16import Data.Maybe (fromJust)
17import Data.Word (Word8)
18import System.Directory
19 ( createDirectoryIfMissing
26import System.FilePath ((</>))
27import System.IO (BufferMode(..), hSetBuffering, stdout)
28import System.IO.Temp (withSystemTempDirectory)
29#if !defined(CABAL_OS_WINDOWS)
30import System.IO.Unsafe (unsafePerformIO)
32import Streamly.Internal.Data.Array.Foreign (Array)
34import qualified Data.List.NonEmpty as NonEmpty
35import qualified Data.Set as Set
36import qualified Streamly.Unicode.Stream as Unicode
37import qualified Streamly.Internal.Data.Array.Foreign as Array
38import qualified Streamly.Internal.Data.Fold as FL
39import qualified Streamly.Internal.Data.Parser as PR
40import qualified Streamly.Internal.Data.Stream.IsStream as S
41#if defined(CABAL_OS_DARWIN)
42import qualified Streamly.Internal.FileSystem.Event.Darwin as Event
43#elif defined(CABAL_OS_LINUX)
44import qualified Streamly.Internal.FileSystem.Event.Linux as Event
45#elif defined(CABAL_OS_WINDOWS)
46import qualified Streamly.Internal.FileSystem.Event.Windows as Event
48#error "FS Events not supported on this platform
51#if !defined(CABAL_OS_WINDOWS)
52import Data.Functor.Identity (runIdentity)
53import qualified Streamly.Internal.Unicode.Stream as U
58-------------------------------------------------------------------------------
60-------------------------------------------------------------------------------
62toUtf8 :: MonadIO m => String -> m (Array Word8)
63toUtf8 = Array.fromStream . Unicode.encodeUtf8' . S.fromList
65#if !defined(CABAL_OS_WINDOWS)
66utf8ToString :: Array Word8 -> String
67utf8ToString = runIdentity . S.toList . U.decodeUtf8' . Array.toStream
71timeout = threadDelay 5000000 >> return "Timeout"
74fseventDir = "fsevent_dir"
76-- XXX Make the getRelPath type same on windows and other platforms
77eventPredicate :: Event.Event -> Bool
79#if defined(CABAL_OS_WINDOWS)
80 if (Event.getRelPath ev) == "EOTask"
82 if (utf8ToString $ Event.getRelPath ev) == "EOTask"
87-------------------------------------------------------------------------------
88-- Event matching utilities
89-------------------------------------------------------------------------------
90#if defined(CABAL_OS_LINUX)
91removeTrailingSlash :: Array Word8 -> Array Word8
92removeTrailingSlash path =
93 if Array.length path == 0
96 let mx = Array.readIndex path (Array.length path - 1)
98 Nothing -> error "removeTrailingSlash: Bug: Invalid index"
100 if x == fromIntegral (ord '/')
101 -- XXX need array slicing
103 $ Array.fromStreamN (Array.length path - 1)
104 $ Array.toStream path
108-- XXX Return a tuple (path, flags) instead of appending flags to path. And
109-- then check the flags using an event mask.
111showEventShort :: Event.Event -> String
112#if defined(CABAL_OS_WINDOWS)
113-- | Convert an 'Event' record to a short representation for unit test.
114showEventShort ev@Event.Event{..} =
115 Event.getRelPath ev ++ "_" ++ show eventFlags
116#elif defined(CABAL_OS_LINUX)
117showEventShort ev@Event.Event{..} =
118 (utf8ToString $ removeTrailingSlash $ Event.getRelPath ev)
119 ++ "_" ++ show eventFlags
120 ++ showev Event.isDir "Dir"
122 where showev f str = if f ev then "_" ++ str else ""
124#error "Unsupported OS
127-------------------------------------------------------------------------------
129-------------------------------------------------------------------------------
131checkEvents :: FilePath -> MVar () -> [String] -> IO String
132checkEvents rootPath m matchList = do
133 let args = [rootPath]
134 paths <- mapM toUtf8 args
135 putStrLn ("Watch started !!!! on Path " ++ rootPath)
136 events <- S.parse (PR.takeWhile eventPredicate FL.toList)
137 $ S.before (putMVar m ())
138 $ Event.watchTrees (NonEmpty.fromList paths)
139 let eventStr = map showEventShort events
140 let baseSet = Set.fromList matchList
141 resultSet = Set.fromList eventStr
142 if (baseSet `Set.isSubsetOf` resultSet)
146 putStrLn $ "baseSet " ++ show matchList
147 putStrLn $ "resultSet " ++ show eventStr
150-------------------------------------------------------------------------------
151-- FS Event Generators
152-------------------------------------------------------------------------------
154checker :: S.IsStream t =>
155 FilePath -> MVar () -> [String] -> t IO String
156checker rootPath synch matchList =
157 S.yieldM (checkEvents rootPath synch matchList)
161-------------------------------------------------------------------------------
163-------------------------------------------------------------------------------
172driver (desc, pre, ops, events) = it desc $ runTest `shouldReturn` "PASS"
178 withSystemTempDirectory fseventDir $ \fp -> do
180 let eventStream = checker fp sync events
181 fsOps = S.yieldM $ runFSOps fp sync
182 fmap fromJust $ S.head $ eventStream `S.parallelFst` fsOps
184 runFSOps fp sync = do
188 threadDelay 200000 -- Why this delay?
189 createDirectoryIfMissing True (fp </> "EOTask")
191 error "fs ops timed out"
193-------------------------------------------------------------------------------
195-------------------------------------------------------------------------------
198 [ ( String -- test description
199 , FilePath -> IO () -- pre test operation
200 , FilePath -> IO () -- file system actions
201 , [String]) -- expected events
205 ( "Create a single directory"
207 , \fp -> createDirectoryIfMissing True (fp </> "dir1Single")
208#if defined(CABAL_OS_WINDOWS)
210#elif defined(CABAL_OS_LINUX)
211 , [ "dir1Single_1073742080_Dir"
212 , "dir1Single_1073741856_Dir"
213 , "dir1Single_1073741825_Dir"
214 , "dir1Single_1073741840_Dir"
218 , ( "Remove a single directory"
219 , \fp -> createDirectoryIfMissing True (fp </> "dir1Single")
220 , \fp -> removeDirectory (fp </> "dir1Single")
221#if defined(CABAL_OS_WINDOWS)
223#elif defined(CABAL_OS_LINUX)
224 , [ "dir1Single_1024"
229 , ( "Rename a single directory"
230 , \fp -> createDirectoryIfMissing True (fp </> "dir1Single")
232 let spath = fp </> "dir1Single"
233 tpath = fp </> "dir1SingleRenamed"
234 in renameDirectory spath tpath
235#if defined(CABAL_OS_WINDOWS)
237 , "dir1SingleRenamed_5"
239#elif defined(CABAL_OS_LINUX)
240 , [ "dir1Single_1073741888_Dir"
241 , "dir1SingleRenamed_1073741952_Dir"
245 , ( "Create a nested directory"
248 createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
249#if defined(CABAL_OS_WINDOWS)
252 , "dir1\\dir2\\dir3_1"
254#elif defined(CABAL_OS_LINUX)
255 , [ "dir1_1073742080_Dir"
256 , "dir1_1073741856_Dir"
257 , "dir1_1073741825_Dir"
258 , "dir1_1073741840_Dir"
262 , ( "Remove a nested directory"
264 createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
265 , \fp -> removePathForcibly (fp </> "dir1")
266#if defined(CABAL_OS_WINDOWS)
269 , "dir1\\dir2\\dir3_2"
270 , "dir1\\dir2_2","dir1_2"
272#elif defined(CABAL_OS_LINUX)
273 , [ "dir1/dir2/dir3_1073742336_Dir"
274 , "dir1/dir2_1073742336_Dir"
275 , "dir1_1073742336_Dir"
279 , ( "Rename a nested directory"
280 , \fp -> createDirectoryIfMissing True
281 (fp </> "dir1" </> "dir2" </> "dir3")
283 let spath = fp </> "dir1" </> "dir2" </> "dir3"
284 tpath = fp </> "dir1" </> "dir2" </> "dir3Renamed"
285 in renameDirectory spath tpath
286#if defined(CABAL_OS_WINDOWS)
288 , "dir1\\dir2\\dir3_4"
289 , "dir1\\dir2\\dir3Renamed_5"
292#elif defined(CABAL_OS_LINUX)
293 , [ "dir1/dir2/dir3_1073741888_Dir"
294 , "dir1/dir2/dir3Renamed_1073741952_Dir"
298 , ( "Create a file in root Dir"
300 , \fp -> writeFile (fp </> "FileCreated.txt") "Test Data"
301#if defined(CABAL_OS_WINDOWS)
302 , [ "FileCreated.txt_1"
303 , "FileCreated.txt_3"
304 , "FileCreated.txt_3"
306#elif defined(CABAL_OS_LINUX)
307 , [ "FileCreated.txt_256"
308 , "FileCreated.txt_32"
309 , "FileCreated.txt_2"
313 , ( "Remove a file in root Dir"
314 , \fp -> writeFile (fp </> "FileCreated.txt") "Test Data"
315 , \fp -> removeFile (fp </> "FileCreated.txt")
316#if defined(CABAL_OS_WINDOWS)
317 , [ "FileCreated.txt_2" ]
318#elif defined(CABAL_OS_LINUX)
319 , [ "FileCreated.txt_512" ]
322 , ( "Rename a file in root Dir"
323 , \fp -> writeFile (fp </> "FileCreated.txt") "Test Data"
325 let spath = (fp </> "FileCreated.txt")
326 tpath = (fp </> "FileRenamed.txt")
327 in renamePath spath tpath
328#if defined(CABAL_OS_WINDOWS)
329 , [ "FileCreated.txt_4"
330 , "FileRenamed.txt_5"
332#elif defined(CABAL_OS_LINUX)
333 , [ "FileCreated.txt_64"
334 , "FileRenamed.txt_128"
338 , ( "Create a file in a nested Dir"
340 createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
342 let p = fp </> "dir1" </> "dir2" </> "dir3" </> "FileCreated.txt"
343 in writeFile p "Test Data"
344#if defined(CABAL_OS_WINDOWS)
345 , [ "dir1\\dir2\\dir3\\FileCreated.txt_1"
346 , "dir1\\dir2\\dir3\\FileCreated.txt_3"
348#elif defined(CABAL_OS_LINUX)
349 , [ "dir1/dir2/dir3/FileCreated.txt_256"
350 , "dir1/dir2/dir3/FileCreated.txt_32"
351 , "dir1/dir2/dir3/FileCreated.txt_2"
352 , "dir1/dir2/dir3/FileCreated.txt_8"
356 , ( "Remove a file in a nested Dir"
358 let nestedDir = fp </> "dir1" </> "dir2" </> "dir3"
359 fpath = nestedDir </> "FileCreated.txt"
361 createDirectoryIfMissing True nestedDir
362 writeFile fpath "Test Data"
364 let p = fp </> "dir1" </> "dir2" </> "dir3" </> "FileCreated.txt"
366#if defined(CABAL_OS_WINDOWS)
367 , ["dir1\\dir2\\dir3\\FileCreated.txt_2"]
368#elif defined(CABAL_OS_LINUX)
369 , ["dir1/dir2/dir3/FileCreated.txt_512"]
372 , ( "Rename a file in a nested Dir"
374 let nestedDir = fp </> "dir1" </> "dir2" </> "dir3"
375 fpath = nestedDir </> "FileCreated.txt"
377 createDirectoryIfMissing True nestedDir
378 writeFile fpath "Test Data"
380 let s = (fp </> "dir1" </> "dir2" </> "dir3" </> "FileCreated.txt")
381 t = (fp </> "dir1" </> "dir2" </> "dir3" </> "FileRenamed.txt")
383#if defined(CABAL_OS_WINDOWS)
384 , [ "dir1\\dir2\\dir3_3"
385 , "dir1\\dir2\\dir3\\FileCreated.txt_4"
386 , "dir1\\dir2\\dir3\\FileRenamed.txt_5"
388#elif defined(CABAL_OS_LINUX)
389 , [ "dir1/dir2/dir3/FileCreated.txt_64"
390 , "dir1/dir2/dir3/FileRenamed.txt_128"
398 hSetBuffering stdout NoBuffering
399 hspec $ sequence_ $ map driver testDesc
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list