@@ -1,27 +1,30 @@
1−-- |
2−-- Module : Streamly.Test.FileSystem.Event
3−-- Copyright : (c) 2020 Composewell Technologies
4−--
5−-- License : BSD3
6−-- Maintainer : streamly@composewell.com
7−-- Stability : experimental
8−-- Portability : GHC
9−--
10−-- Just report all events under the paths provided as arguments
11−module Main (main) where
12−
1+import Control.Concurrent (MVar, newEmptyMVar, putMVar, takeMVar, threadDelay)
132import Control.Monad.IO.Class (MonadIO)
14−import Data.Function ((&))
153import Data.List.NonEmpty (NonEmpty)
4+import Data.Maybe (fromJust)
165import Data.Word (Word8)
17−import System.Environment (getArgs)
6+import System.Directory ( createDirectoryIfMissing
7+ , removeFile
8+ , removeDirectory
9+ , removePathForcibly
10+ , renameDirectory
11+ , renamePath
12+ )
13+import System.FilePath ((</>))
14+import System.IO (BufferMode(..), hSetBuffering, stdout)
15+import System.IO.Temp (withSystemTempDirectory)
1816import Streamly.Prelude (SerialT)
1917import Streamly.Internal.Data.Array.Storable.Foreign (Array)
18+import Test.Hspec
19+import Test.Hspec.QuickCheck
2020
21+import qualified Data.List.NonEmpty as NonEmpty
22+import qualified Data.Set as Set
2123import qualified Streamly.Unicode.Stream as Unicode
2224import qualified Streamly.Internal.Data.Array.Storable.Foreign as Array
23−import qualified Data.List.NonEmpty as NonEmpty
24−import qualified Streamly.Internal.Data.Stream.IsStream as Stream
25+import qualified Streamly.Internal.Data.Fold as FL
26+import qualified Streamly.Internal.Data.Parser as PR
27+import qualified Streamly.Internal.Data.Stream.IsStream as S
2528#if defined(CABAL_OS_DARWIN)
2629import qualified Streamly.Internal.FileSystem.Event.Darwin as Event
2730#elif defined(CABAL_OS_LINUX)
@@ -32,28 +35,574 @@ import qualified Streamly.Internal.FileSystem.Event.Windows as Event
3235#error "FS Events not supported on this platform
3336#endif
3437
38+#if !defined(CABAL_OS_WINDOWS)
39+import Data.Functor.Identity (runIdentity)
40+import qualified Streamly.Internal.Unicode.Stream as U
41+#endif
3542-------------------------------------------------------------------------------
3643-- Utilities
3744-------------------------------------------------------------------------------
38−
3945toUtf8 :: MonadIO m => String -> m (Array Word8)
40−toUtf8 = Array.fromStream . Unicode.encodeUtf8' . Stream.fromList
46+toUtf8 = Array.fromStream . Unicode.encodeUtf8' . S.fromList
47+
48+#if !defined(CABAL_OS_WINDOWS)
49+utf8ToString :: Array Word8 -> String
50+utf8ToString = runIdentity . S.toList . U.decodeUtf8' . Array.toStream
51+#endif
52+
53+watchPaths :: NonEmpty (Array Word8) -> SerialT IO Event.Event
54+watchPaths = Event.watchTrees
55+
56+timeout :: IO String
57+timeout = threadDelay 5000000 >> return "Timeout"
58+
59+fseventDir :: String
60+fseventDir = "fsevent_dir"
61+
62+eventPredicate :: Event.Event -> Bool
63+eventPredicate ev =
64+#if defined(CABAL_OS_WINDOWS)
65+ if (Event.getRelPath ev) == "EOTask"
66+#else
67+ if (utf8ToString $ Event.getRelPath ev) == "EOTask"
68+#endif
69+ then False
70+ else True
4171
4272-------------------------------------------------------------------------------
43−-- Main
73+-- Event lists to be matched with
4474-------------------------------------------------------------------------------
75+#if defined(CABAL_OS_WINDOWS)
76+
77+singleDirCreateEvents :: [String]
78+singleDirCreateEvents =
79+ [ "dir1Single_1" ]
80+
81+singleDirRemoveEvents :: [String]
82+singleDirRemoveEvents =
83+ [ "dir1Single_2" ]
84+
85+singleDirRenameEvents :: [String]
86+singleDirRenameEvents =
87+ [ "dir1Single_4"
88+ , "dir1SingleRenamed_5"
89+ ]
90+
91+nestedDirCreateEvents :: [String]
92+nestedDirCreateEvents =
93+ [ "dir1_1"
94+ , "dir1\\dir2_1"
95+ , "dir1\\dir2\\dir3_1"
96+ ]
97+
98+nestedDirRemoveEvents :: [String]
99+nestedDirRemoveEvents =
100+ [ "dir1_3"
101+ , "dir1\\dir2_3"
102+ , "dir1\\dir2\\dir3_2"
103+ , "dir1\\dir2_2","dir1_2"
104+ ]
105+
106+nestedDirRenameEvents :: [String]
107+nestedDirRenameEvents =
108+ [ "dir1\\dir2_3"
109+ , "dir1\\dir2\\dir3_4"
110+ , "dir1\\dir2\\dir3Renamed_5"
111+ , "dir1\\dir2_3"
112+ ]
113+
114+createFileRootDirEvents :: [String]
115+createFileRootDirEvents =
116+ [ "FileCreated.txt_1"
117+ , "FileCreated.txt_3"
118+ , "FileCreated.txt_3"
119+ ]
120+
121+removeFileRootDirEvents :: [String]
122+removeFileRootDirEvents =
123+ [ "FileCreated.txt_2" ]
124+
125+renameFileRootDirEvents :: [String]
126+renameFileRootDirEvents =
127+ [ "FileCreated.txt_4"
128+ , "FileRenamed.txt_5"
129+ ]
130+
131+createFileNestedDirEvents :: [String]
132+createFileNestedDirEvents =
133+ [ "dir1\\dir2\\dir3\\FileCreated.txt_1"
134+ , "dir1\\dir2\\dir3\\FileCreated.txt_3"
135+ ]
136+
137+removeFileNestedDirEvents :: [String]
138+removeFileNestedDirEvents =
139+ ["dir1\\dir2\\dir3\\FileCreated.txt_2"]
140+
141+renameFileNestedDirEvents :: [String]
142+renameFileNestedDirEvents =
143+ [ "dir1\\dir2\\dir3_3"
144+ , "dir1\\dir2\\dir3\\FileCreated.txt_4"
145+ , "dir1\\dir2\\dir3\\FileRenamed.txt_5"
146+ ]
45147
46−watchPaths :: NonEmpty (Array Word8) -> SerialT IO Event.Event
47−#if defined(CABAL_OS_LINUX)
48−watchPaths = Event.watchPaths
49148#else
50−watchPaths = Event.watchTrees
149+
150+singleDirCreateEvents :: [String]
151+singleDirCreateEvents =
152+ [ "dir1Single_1073742080_Dir"
153+ , "dir1Single_1073741856_Dir"
154+ , "dir1Single_1073741825_Dir"
155+ , "dir1Single_1073741840_Dir"
156+ ]
157+
158+singleDirRemoveEvents :: [String]
159+singleDirRemoveEvents =
160+ [ "dir1Single_1024"
161+ , "dir1Single_32768"
162+ ]
163+
164+singleDirRenameEvents :: [String]
165+singleDirRenameEvents =
166+ [ "dir1Single_1073741888_Dir"
167+ , "dir1SingleRenamed_1073741952_Dir"
168+ ]
169+
170+nestedDirCreateEvents :: [String]
171+nestedDirCreateEvents =
172+ [ "dir1_1073742080_Dir"
173+ , "dir1_1073741856_Dir"
174+ , "dir1_1073741825_Dir"
175+ , "dir1_1073741840_Dir"
176+ ]
177+
178+nestedDirRemoveEvents :: [String]
179+nestedDirRemoveEvents =
180+ [ "dir1/dir2/dir3_1073742336_Dir"
181+ , "dir1/dir2_1073742336_Dir"
182+ , "dir1_1073742336_Dir"
183+ ]
184+
185+nestedDirRenameEvents :: [String]
186+nestedDirRenameEvents =
187+ [ "dir1/dir2/dir3_1073741888_Dir"
188+ , "dir1/dir2/dir3Renamed_1073741952_Dir"
189+ ]
190+
191+createFileRootDirEvents :: [String]
192+createFileRootDirEvents =
193+ [ "FileCreated.txt_256"
194+ , "FileCreated.txt_32"
195+ , "FileCreated.txt_2"
196+ ]
197+
198+removeFileRootDirEvents :: [String]
199+removeFileRootDirEvents =
200+ ["FileCreated.txt_512"]
201+
202+renameFileRootDirEvents :: [String]
203+renameFileRootDirEvents =
204+ [ "FileCreated.txt_64"
205+ , "FileRenamed.txt_128"
206+ ]
207+
208+createFileNestedDirEvents :: [String]
209+createFileNestedDirEvents =
210+ [ "dir1/dir2/dir3/FileCreated.txt_256"
211+ , "dir1/dir2/dir3/FileCreated.txt_32"
212+ , "dir1/dir2/dir3/FileCreated.txt_2"
213+ , "dir1/dir2/dir3/FileCreated.txt_8"
214+ ]
215+
216+removeFileNestedDirEvents :: [String]
217+removeFileNestedDirEvents =
218+ ["dir1/dir2/dir3/FileCreated.txt_512"]
219+
220+renameFileNestedDirEvents :: [String]
221+renameFileNestedDirEvents =
222+ [ "dir1/dir2/dir3/FileCreated.txt_64"
223+ , "dir1/dir2/dir3/FileRenamed.txt_128"
224+ ]
51225#endif
52226
53−main :: IO ()
54−main = do
55− args <- getArgs
227+-------------------------------------------------------------------------------
228+-- Event Watcher
229+-------------------------------------------------------------------------------
230+checkEvents :: FilePath -> MVar () -> [String] -> IO String
231+checkEvents rootPath m matchList = do
232+ let args = [rootPath]
56233 paths <- mapM toUtf8 args
234+ putStrLn ("Watch started !!!! on Path " ++ rootPath)
235+ events <- S.parse (PR.takeWhile eventPredicate FL.toList)
236+ $ S.before (putMVar m ())
237+ $ watchPaths (NonEmpty.fromList paths)
238+ let eventStr = map Event.showEventShort events
239+ putStrLn $ show (eventStr)
240+ let baseSet = Set.fromList matchList
241+ resultSet = Set.fromList eventStr
242+ if (baseSet `Set.isSubsetOf` resultSet)
243+ then
244+ return "PASS"
245+ else
246+ return "Mismatch"
247+
248+-------------------------------------------------------------------------------
249+-- FS Event Generators
250+-------------------------------------------------------------------------------
251+fsOpsCreateSingleDir :: FilePath -> MVar () -> IO ()
252+fsOpsCreateSingleDir fp m = do
253+ takeMVar m
254+ putStrLn ("Create Single Directory !!!!!!! on " ++ fp)
255+ threadDelay 200000
256+ >> createDirectoryIfMissing True (fp </> "dir1Single")
257+ >> threadDelay 200000
258+ >> createDirectoryIfMissing True (fp </> "EOTask")
259+
260+fsOpsRemoveSingleDir :: FilePath -> MVar () -> IO ()
261+fsOpsRemoveSingleDir fp m = do
262+ takeMVar m
263+ putStrLn ("Remove Single Directory !!!!!!! on " ++ fp)
264+ threadDelay 200000
265+ >> removeDirectory (fp </> "dir1Single")
266+ >> threadDelay 200000
267+ >> createDirectoryIfMissing True (fp </> "EOTask")
268+
269+fsOpsRenameSingleDir :: FilePath -> MVar () -> IO ()
270+fsOpsRenameSingleDir fp m = do
271+ takeMVar m
272+ putStrLn ("Rename a Single Directory !!!!!!! on " ++ fp)
273+ threadDelay 200000
274+ >> renameDirectory (fp </> "dir1Single") (fp </> "dir1SingleRenamed")
275+ >> threadDelay 200000
276+ >> createDirectoryIfMissing True (fp </> "EOTask")
277+
278+fsOpsCreateNestedDir :: FilePath -> MVar () -> IO ()
279+fsOpsCreateNestedDir fp m = do
280+ takeMVar m
281+ putStrLn ("Create Nested Directory !!!!!!!!!!!!! on " ++ fp)
282+ threadDelay 200000
283+ >> createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
284+ >> threadDelay 200000
285+ >> createDirectoryIfMissing True (fp </> "EOTask")
286+
287+fsOpsRemoveNestedDir :: FilePath -> MVar () -> IO ()
288+fsOpsRemoveNestedDir fp m = do
289+ takeMVar m
290+ putStrLn ("Remove Nested Directory !!!!!!!!!!!!! on " ++ fp)
291+ threadDelay 200000
292+ >> removePathForcibly (fp </> "dir1")
293+ >> threadDelay 200000
294+ >> createDirectoryIfMissing True (fp </> "EOTask")
295+
296+fsOpsRenameNestedDir :: FilePath -> MVar () -> IO ()
297+fsOpsRenameNestedDir fp m = do
298+ takeMVar m
299+ putStrLn ("Rename Nested Directory !!!!!!!!!!!!! on " ++ fp)
300+ threadDelay 200000
301+ >> renameDirectory
302+ (fp </> "dir1" </> "dir2" </> "dir3")
303+ (fp </> "dir1" </> "dir2" </> "dir3Renamed")
304+ >> threadDelay 200000
305+ >> createDirectoryIfMissing True (fp </> "EOTask")
306+
307+fsOpsCreateFileInRootDir :: FilePath -> MVar () -> IO ()
308+fsOpsCreateFileInRootDir fp m = do
309+ takeMVar m
310+ let tpath = (fp </> "FileCreated.txt")
311+ putStrLn ("create a File on " ++ fp)
312+ threadDelay 200000
313+ >> writeFile tpath "Test Data"
314+ >> threadDelay 200000
315+ >> createDirectoryIfMissing True (fp </> "EOTask")
316+
317+fsOpsRemoveFileInRootDir :: FilePath -> MVar () -> IO ()
318+fsOpsRemoveFileInRootDir fp m = do
319+ takeMVar m
320+ let tpath = (fp </> "FileCreated.txt")
321+ putStrLn ("Remove a File on " ++ fp)
322+ threadDelay 200000
323+ >> removeFile tpath
324+ >> threadDelay 200000
325+ >> createDirectoryIfMissing True (fp </> "EOTask")
326+
327+fsOpsRenameFileInRootDir :: FilePath -> MVar () -> IO ()
328+fsOpsRenameFileInRootDir fp m = do
329+ takeMVar m
330+ let spath = (fp </> "FileCreated.txt")
331+ tpath = (fp </> "FileRenamed.txt")
332+ putStrLn ("Rename a File on " ++ fp)
333+ threadDelay 200000
334+ >> renamePath spath tpath
335+ >> threadDelay 200000
336+ >> createDirectoryIfMissing True (fp </> "EOTask")
337+
338+fsOpsCreateFileInNestedDir :: FilePath -> MVar () -> IO ()
339+fsOpsCreateFileInNestedDir fp m = do
340+ takeMVar m
341+ let tpath = (fp </> "dir1" </> "dir2" </> "dir3" </> "FileCreated.txt")
342+ putStrLn ("create a File on nested dir " ++ fp)
343+ threadDelay 200000
344+ >> writeFile tpath "Test Data"
345+ >> threadDelay 200000
346+ >> createDirectoryIfMissing True (fp </> "EOTask")
347+
348+fsOpsRemoveFileInNestedDir :: FilePath -> MVar () -> IO ()
349+fsOpsRemoveFileInNestedDir fp m = do
350+ takeMVar m
351+ let tpath = (fp </> "dir1" </> "dir2" </> "dir3" </> "FileCreated.txt")
352+ putStrLn ("Remove a File on nested dir " ++ fp)
353+ threadDelay 200000
354+ >> threadDelay 200000 >> removeFile tpath
355+ >> threadDelay 200000
356+ >> createDirectoryIfMissing True (fp </> "EOTask")
357+
358+fsOpsRenameFileInNestedDir :: FilePath -> MVar () -> IO ()
359+fsOpsRenameFileInNestedDir fp m = do
360+ takeMVar m
361+ let spath = (fp </> "dir1" </> "dir2" </> "dir3" </> "FileCreated.txt")
362+ tpath = (fp </> "dir1" </> "dir2" </> "dir3" </> "FileRenamed.txt")
363+ putStrLn ("Rename a File on nested dir " ++ fp)
364+ threadDelay 200000
365+ >> renamePath spath tpath
366+ >> threadDelay 200000
367+ >> createDirectoryIfMissing True (fp </> "EOTask")
368+
369+
370+checker :: S.IsStream t =>
371+ FilePath -> MVar () -> [String] -> t IO String
372+checker rootPath synch matchList =
373+ S.yieldM (checkEvents rootPath synch matchList)
374+ `S.parallelFst`
375+ S.yieldM timeout
376+
377+driverInit :: IO (MVar ())
378+driverInit = do
379+ hSetBuffering stdout NoBuffering
380+ pre <- newEmptyMVar
381+ return pre
382+
383+-------------------------------------------------------------------------------
384+-- Test Drivers
385+-------------------------------------------------------------------------------
386+driverCreateSingleDir :: IO String
387+driverCreateSingleDir = do
388+ sync <- driverInit
389+ withSystemTempDirectory fseventDir $ \fp -> do
390+ res <- S.head
391+ $ (checker fp sync singleDirCreateEvents)
392+ `S.parallelFst`
393+ S.yieldM -- ^ this message should follow checker
394+ (fsOpsCreateSingleDir fp sync
395+ >> threadDelay 10000000
396+ >> return "fOps Done")
397+ return $ fromJust res
398+
399+driverRemoveSingleDir :: IO String
400+driverRemoveSingleDir = do
401+ sync <- driverInit
402+ withSystemTempDirectory fseventDir $ \fp -> do
403+ createDirectoryIfMissing True (fp </> "dir1Single")
404+ res <- S.head
405+ $ (checker fp sync singleDirRemoveEvents)
406+ `S.parallelFst`
407+ S.yieldM -- ^ this message should follow checker
408+ (fsOpsRemoveSingleDir fp sync
409+ >> threadDelay 10000000
410+ >> return "fOps Done")
411+ return $ fromJust res
412+
413+driverRenameSingleDir :: IO String
414+driverRenameSingleDir = do
415+ sync <- driverInit
416+ withSystemTempDirectory fseventDir $ \fp -> do
417+ createDirectoryIfMissing True (fp </> "dir1Single")
418+ res <- S.head
419+ $ (checker fp sync singleDirRenameEvents)
420+ `S.parallelFst`
421+ S.yieldM -- ^ this message should follow checker
422+ (fsOpsRenameSingleDir fp sync
423+ >> threadDelay 10000000
424+ >> return "fOps Done")
425+ return $ fromJust res
426+
427+driverCreateNestedDir :: IO String
428+driverCreateNestedDir = do
429+ sync <- driverInit
430+ withSystemTempDirectory fseventDir $ \fp -> do
431+ res <- S.head
432+ $ (checker fp sync nestedDirCreateEvents)
433+ `S.parallelFst`
434+ S.yieldM
435+ (fsOpsCreateNestedDir fp sync
436+ >> threadDelay 10000000
437+ >> return "fOps Done")
438+ return $ fromJust res
439+
440+driverRemoveNestedDir :: IO String
441+driverRemoveNestedDir = do
442+ sync <- driverInit
443+ withSystemTempDirectory fseventDir $ \fp -> do
444+ createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
445+ res <- S.head
446+ $ (checker fp sync nestedDirRemoveEvents)
447+ `S.parallelFst`
448+ S.yieldM
449+ (fsOpsRemoveNestedDir fp sync
450+ >> threadDelay 10000000
451+ >> return "fOps Done")
452+ return $ fromJust res
453+
454+driverRenameNestedDir :: IO String
455+driverRenameNestedDir = do
456+ sync <- driverInit
457+ withSystemTempDirectory fseventDir $ \fp -> do
458+ createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
459+ res <- S.head
460+ $ (checker fp sync nestedDirRenameEvents)
461+ `S.parallelFst`
462+ S.yieldM
463+ (fsOpsRenameNestedDir fp sync
464+ >> threadDelay 10000000
465+ >> return "fOps Done")
466+ return $ fromJust res
467+
468+driverCreateFileInRootDir :: IO String
469+driverCreateFileInRootDir = do
470+ sync <- driverInit
471+ withSystemTempDirectory fseventDir $ \fp -> do
472+ res <- S.head
473+ $ (checker fp sync createFileRootDirEvents)
474+ `S.parallelFst`
475+ S.yieldM
476+ (fsOpsCreateFileInRootDir fp sync
477+ >> threadDelay 10000000
478+ >> return "fOps Done")
479+ return $ fromJust res
57480
58− watchPaths (NonEmpty.fromList paths)
59− & Stream.mapM_ (putStrLn . Event.showEvent)
481+driverRemoveFileInRootDir :: IO String
482+driverRemoveFileInRootDir = do
483+ sync <- driverInit
484+ withSystemTempDirectory fseventDir $ \fp -> do
485+ writeFile (fp </> "FileCreated.txt") "Test Data"
486+ res <- S.head
487+ $ (checker fp sync removeFileRootDirEvents)
488+ `S.parallelFst`
489+ S.yieldM
490+ (fsOpsRemoveFileInRootDir fp sync
491+ >> threadDelay 10000000
492+ >> return "fOps Done")
493+ return $ fromJust res
494+
495+driverRenameFileInRootDir :: IO String
496+driverRenameFileInRootDir = do
497+ sync <- driverInit
498+ withSystemTempDirectory fseventDir $ \fp -> do
499+ writeFile (fp </> "FileCreated.txt") "Test Data"
500+ res <- S.head
501+ $ (checker fp sync renameFileRootDirEvents)
502+ `S.parallelFst`
503+ S.yieldM
504+ (fsOpsRenameFileInRootDir fp sync
505+ >> threadDelay 10000000
506+ >> return "fOps Done")
507+ return $ fromJust res
508+
509+driverCreateFileInNestedDir :: IO String
510+driverCreateFileInNestedDir = do
511+ sync <- driverInit
512+ withSystemTempDirectory fseventDir $ \fp -> do
513+ createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
514+ res <- S.head
515+ $ (checker fp sync createFileNestedDirEvents)
516+ `S.parallelFst`
517+ S.yieldM
518+ (fsOpsCreateFileInNestedDir fp sync
519+ >> threadDelay 10000000
520+ >> return "fOps Done")
521+ return $ fromJust res
522+
523+driverRemoveFileInNestedDir :: IO String
524+driverRemoveFileInNestedDir = do
525+ sync <- driverInit
526+ withSystemTempDirectory fseventDir $ \fp -> do
527+ createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
528+ >> writeFile (fp </> "dir1" </> "dir2" </> "dir3" </> "FileCreated.txt") "Test Data"
529+ res <- S.head
530+ $ (checker fp sync removeFileNestedDirEvents)
531+ `S.parallelFst`
532+ S.yieldM
533+ (fsOpsRemoveFileInNestedDir fp sync
534+ >> threadDelay 10000000
535+ >> return "fOps Done")
536+ return $ fromJust res
537+
538+driverRenameFileInNestedDir :: IO String
539+driverRenameFileInNestedDir = do
540+ sync <- driverInit
541+ withSystemTempDirectory fseventDir $ \fp -> do
542+ createDirectoryIfMissing True (fp </> "dir1" </> "dir2" </> "dir3")
543+ >> writeFile (fp </> "dir1" </> "dir2" </> "dir3" </> "FileCreated.txt") "Test Data"
544+ res <- S.head
545+ $ (checker fp sync renameFileNestedDirEvents)
546+ `S.parallelFst`
547+ S.yieldM
548+ (fsOpsRenameFileInNestedDir fp sync
549+ >> threadDelay 10000000
550+ >> return "fOps Done")
551+ return $ fromJust res
552+
553+-------------------------------------------------------------------------------
554+-- Test Cases
555+-------------------------------------------------------------------------------
556+testCreateSingleDir :: Expectation
557+testCreateSingleDir = driverCreateSingleDir `shouldReturn` "PASS"
558+
559+testRemoveSingleDir :: Expectation
560+testRemoveSingleDir = driverRemoveSingleDir `shouldReturn` "PASS"
561+
562+testRenameSingleDir :: Expectation
563+testRenameSingleDir = driverRenameSingleDir `shouldReturn` "PASS"
564+
565+testCreateNestedDir :: Expectation
566+testCreateNestedDir = driverCreateNestedDir `shouldReturn` "PASS"
567+
568+testRemoveNestedDir :: Expectation
569+testRemoveNestedDir = driverRemoveNestedDir `shouldReturn` "PASS"
570+
571+testRenameNestedDir :: Expectation
572+testRenameNestedDir = driverRenameNestedDir `shouldReturn` "PASS"
573+
574+testCreateFileInRootDir :: Expectation
575+testCreateFileInRootDir = driverCreateFileInRootDir `shouldReturn` "PASS"
576+
577+testRemoveFileInRootDir :: Expectation
578+testRemoveFileInRootDir = driverRemoveFileInRootDir `shouldReturn` "PASS"
579+
580+testRenameFileInRootDir :: Expectation
581+testRenameFileInRootDir = driverRenameFileInRootDir `shouldReturn` "PASS"
582+
583+testCreateFileInNestedDir :: Expectation
584+testCreateFileInNestedDir = driverCreateFileInNestedDir `shouldReturn` "PASS"
585+
586+testRemoveFileInNestedDir :: Expectation
587+testRemoveFileInNestedDir = driverRemoveFileInNestedDir `shouldReturn` "PASS"
588+
589+testRenameFileInNestedDir :: Expectation
590+testRenameFileInNestedDir = driverRenameFileInNestedDir `shouldReturn` "PASS"
591+
592+-------------------------------------------------------------------------------
593+-- Main
594+-------------------------------------------------------------------------------
595+main :: IO ()
596+main = hspec $ do
597+ prop "Create a single directory" testCreateSingleDir
598+ prop "Remove a single directory" testRemoveSingleDir
599+ prop "Rename a single directory" testRenameSingleDir
600+ prop "Create a nested directory" testCreateNestedDir
601+ prop "Remove a nested directory" testRemoveNestedDir
602+ prop "Rename a nested directory" testRenameNestedDir
603+ prop "Create a file in root Dir" testCreateFileInRootDir
604+ prop "Remove a file in root Dir" testRemoveFileInRootDir
605+ prop "Rename a file in root Dir" testRenameFileInRootDir
606+ prop "Create a file in a nested Dir" testCreateFileInNestedDir
607+ prop "Remove a file in a nested Dir" testRemoveFileInNestedDir
608+ prop "Rename a file in a nested Dir" testRenameFileInNestedDir