packages feed

system-fileio-0.3.3: tests/FilesystemTests/Posix.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

-- Copyright (C) 2011 John Millikin <jmillikin@gmail.com>
--
-- See license.txt for details
module FilesystemTests.Posix
	( test_Posix
	) where

import           Prelude hiding (FilePath)
import           Control.Monad
import           Control.Monad.IO.Class (liftIO)
import qualified Data.ByteString
import           Data.Text (Text)
import           Data.Text.Encoding (encodeUtf8)
import           Foreign
import           Foreign.C
import           Test.Chell

import           Filesystem
import           Filesystem.Path.CurrentOS

import           FilesystemTests.Util (assertionsWithTemp, todo)

test_Posix :: Suite
test_Posix = suite "posix"
	[ suite "isFile"
		[ test_IsFile "ascii"
			(decode "test.txt")
		, test_IsFile "utf8"
			(fromText "\xA1\xA2.txt")
		, test_IsFile "iso8859"
			(decode "\xA1\xA2\xA3.txt")
		]
	, suite "isDirectory"
		[ test_IsDirectory "ascii"
			(decode "test.d")
		, test_IsDirectory "utf8"
			(fromText "\xA1\xA2.d")
		, test_IsDirectory "iso8859"
			(decode "\xA1\xA2\xA3.d")
		]
	, suite "rename"
		[ test_Rename "ascii"
			(decode "old_test.txt")
			(decode "new_test.txt")
		, test_Rename "utf8"
			(fromText "old_\xA1\xA2.txt")
			(fromText "new_\xA1\xA2.txt")
		, test_Rename "iso8859"
			(decode "old_\xA1\xA2\xA3.txt")
			(decode "new_\xA1\xA2\xA3.txt")
		]
	, suite "canonicalizePath"
		[ test_CanonicalizePath "ascii"
			(decode "test-a.txt")
			(decode "test-b.txt")
		, test_CanonicalizePath "utf8"
			(fromText "\xA1\xA2-a.txt")
			(fromText "\xA1\xA2-b.txt")
		, test_CanonicalizePath "iso8859"
			(decode "\xA1\xA2\xA3-a.txt")
			(decode "\xA1\xA2\xA3-b.txt")
		]
	, todo "createDirectory"
	, todo "createTree"
	, test_ListDirectory
	, todo "removeFile"
	, todo "removeDirectory"
	, todo "removeTree"
	, todo "getWorkingDirectory"
	, todo "setWorkingDirectory"
	, todo "getHomeDirectory"
	, todo "getDesktopDirectory"
	, todo "getDocumentsDirectory"
	, todo "getAppDataDirectory"
	, todo "getAppCacheDirectory"
	, todo "getAppConfigDirectory"
	, todo "copyFile"
	, todo "getModified"
	, todo "getSize"
	, todo "openFile"
	, todo "withFile"
	, todo "readFile"
	, todo "writeFile"
	, todo "appendFile"
	, todo "openTextFile"
	, todo "withTextFile"
	, todo "readTextFile"
	, todo "writeTextFile"
	, todo "appendTextFile"
	]

test_IsFile :: Text -> FilePath -> Suite
test_IsFile test_name file_name = assertionsWithTemp test_name $ \dir -> do
	let path = dir </> file_name
	
	before <- liftIO $ Filesystem.isFile path
	$expect (not before)
	
	touch_ffi path "contents\n"
	
	after <- liftIO $ Filesystem.isFile path
	$expect after

test_IsDirectory :: Text -> FilePath -> Suite
test_IsDirectory test_name dir_name = assertionsWithTemp test_name $ \dir -> do
	let path = dir </> dir_name
	
	before <- liftIO $ Filesystem.isDirectory path
	$expect (not before)
	
	mkdir_ffi path
	
	after <- liftIO $ Filesystem.isDirectory path
	$expect after

test_Rename :: Text -> FilePath -> FilePath -> Suite
test_Rename test_name old_name new_name = assertionsWithTemp test_name $ \dir -> do
	let old_path = dir </> old_name
	let new_path = dir </> new_name
	
	touch_ffi old_path ""
	
	old_before <- liftIO $ Filesystem.isFile old_path
	new_before <- liftIO $ Filesystem.isFile new_path
	$expect old_before
	$expect (not new_before)
	
	liftIO $ Filesystem.rename old_path new_path
	
	old_after <- liftIO $ Filesystem.isFile old_path
	new_after <- liftIO $ Filesystem.isFile new_path
	$expect (not old_after)
	$expect new_after

test_ListDirectory :: Suite
test_ListDirectory = assertionsWithTemp "listDirectory" $ \dir -> do
	let paths =
		[ dir </> decode "test.txt"
		, dir </> fromText "\xA1\xA2.txt"
		, dir </> decode "\xA1\xA2\xA3.txt"
		]
	forM_ paths (\path -> touch_ffi path "")
	
	names <- liftIO $ Filesystem.listDirectory dir
	$expect $ sameItems paths names

test_CanonicalizePath :: Text -> FilePath -> FilePath -> Suite
test_CanonicalizePath test_name src_name dst_name = assertionsWithTemp test_name $ \dir -> do
	let src_path = dir </> src_name
	let subdir = dir </> "subdir"
	
	-- canonicalize the directory first, to avoid false negatives if
	-- it gets placed in a symlinked location.
	mkdir_ffi subdir
	canon_subdir <- liftIO (Filesystem.canonicalizePath subdir)
	
	let dst_path = canon_subdir </> dst_name
	
	touch_ffi dst_path ""
	symlink_ffi dst_path src_path
	
	canonicalized <- liftIO $ Filesystem.canonicalizePath src_path
	$expect $ equal canonicalized dst_path

withPathCString :: FilePath -> (CString -> IO a) -> IO a
withPathCString p = Data.ByteString.useAsCString bytes where
#ifdef CABAL_OS_DARWIN
	bytes = encodeUtf8 (encode p)
#else
	bytes = encode p
#endif

-- | Create a file using the raw POSIX API, via FFI
touch_ffi :: FilePath -> Data.ByteString.ByteString -> Assertions ()
touch_ffi path contents = do
	fp <- liftIO $ withPathCString path $ \path_cstr ->
		Foreign.C.withCString "wb" $ \mode_cstr ->
		c_fopen path_cstr mode_cstr
	
	$assert (fp /= nullPtr)
	
	_ <- liftIO $ Data.ByteString.useAsCStringLen contents $ \(buf, len) ->
		c_fwrite buf 1 (fromIntegral len) fp
	
	_ <- liftIO $ c_fclose fp
	return ()

-- | Create a directory using the raw POSIX API, via FFI
mkdir_ffi :: FilePath -> Assertions ()
mkdir_ffi path = do
	ret <- liftIO $ withPathCString path $ \path_cstr ->
		c_mkdir path_cstr 0o700
	
	$assert (ret == 0)

-- | Create a symlink using the raw POSIX API, via FFI
symlink_ffi :: FilePath -> FilePath -> Assertions ()
symlink_ffi dst src  = do
	ret <- liftIO $
		withPathCString dst $ \dst_p ->
		withPathCString src $ \src_p ->
		c_symlink dst_p src_p
	
	$assert (ret == 0)

foreign import ccall unsafe "fopen"
	c_fopen :: CString -> CString -> IO (Ptr ())

foreign import ccall unsafe "fclose"
	c_fclose :: Ptr () -> IO CInt

foreign import ccall unsafe "fwrite"
	c_fwrite :: CString -> CSize -> CSize -> Ptr () -> IO CSize

foreign import ccall unsafe "mkdir"
	c_mkdir :: CString -> CInt -> IO CInt

foreign import ccall unsafe "symlink"
	c_symlink :: CString -> CString -> IO CInt