fs-api-0.3.0.0: test/Test/System/FS/API/FsPath.hs
{-# OPTIONS_GHC -Wno-orphans #-}
module Test.System.FS.API.FsPath (tests) where
import Data.Text (Text)
import qualified Data.Text as Text
import Prelude hiding (read)
import qualified System.FilePath as FilePath
import qualified System.FS.API as FS
import Test.Tasty
import qualified Test.Tasty.QuickCheck as QC
import Test.Tasty.QuickCheck
tests :: TestTree
tests = testGroup "Test.System.FS.API.FsPath" [
testProperty "prop_combineCommutes" prop_combineCommutes
, testProperty "prop_addExtensionCommutes" prop_addExtensionCommutes
]
-- | Orphan instance that generates a __non-empty__ text!
instance Arbitrary Text where
arbitrary = Text.pack <$> QC.listOf (QC.elements validChars) `suchThat` (not . null)
shrink x = [ x''' | let x' = Text.unpack x
, x'' <- shrink x'
, not (null x'')
, let x''' = Text.pack x'' ]
-- | We pick a small subset of characters to use in directory/file names, so
-- that we don't break the invariant of 'FsPath'.
validChars :: [Char]
validChars = concat [['a'..'z'], ['A'..'Z'], ['0'..'9']]
-- | Commutativity property for 'FS.</>' and 'FilePath.</>'.
--
-- TODO: commutativity might not be the right name for this type of property.
--
-- @
-- \x y -> toFilePath (x </> y) == toFilePath x </> toFilePath y
-- @
--
-- The first argument is used to create a mount point, which makes the property
-- more useful because we are testing more cases. Also, for 'FS.fsToFilePath' to
-- work, we need at least the empty mountpoint.
prop_combineCommutes :: [Text] -> [Text] -> [Text] -> Property
prop_combineCommutes mnt path1 path2 =
QC.classify (FilePath.isValid rhs) "Valid file path"
$ lhs === rhs
.&&. FilePath.makeValid lhs === FilePath.makeValid rhs
where
mnt' = filePathFromList mnt
mnt'' = FS.MountPoint mnt'
fsp = FS.fsPathFromList path1 FS.</> FS.fsPathFromList path2
lhs = FS.fsToFilePath mnt'' fsp
rhs = mnt' FilePath.</> filePathFromList path1 FilePath.</> filePathFromList path2
-- | Commutativity property for 'FS.<.>' and 'FilePath.<.>'.
--
-- TODO: commutativity might not be the right name for this type of property.
--
-- @
-- \path ext -> toFilePath (path <.> ext) == toFilePath path <.> ext
-- @
--
-- The first argument is used to create a mount point, which makes the property
-- more useful because we are testing more cases. Also, for 'FS.fsToFilePath' to
-- work, we need at least the empty mountpoint.
prop_addExtensionCommutes :: [Text] -> [Text] -> String -> Property
prop_addExtensionCommutes mnt path ext =
QC.classify (FilePath.isValid rhs) "Valid file path"
$ QC.classify (case ext of '.':_ -> True; _ -> False)
"Extension to add starts with an extension separator (.)"
$ lhs === rhs
.&&. FilePath.makeValid lhs === FilePath.makeValid rhs
where
mnt' = filePathFromList mnt
mnt'' = FS.MountPoint mnt'
fsp = FS.fsPathFromList path FS.<.> ext
lhs = FS.fsToFilePath mnt'' fsp
rhs = mnt' FilePath.</> filePathFromList path FilePath.<.> ext
-- | Build a 'FilePath' by 'FilePath.combine'ing the directory/file names.
filePathFromList :: [Text] -> FilePath
filePathFromList [] = []
filePathFromList xs = foldr (\y ys -> Text.unpack y FilePath.</> ys) (Text.unpack (last xs)) (init xs)