lsm-tree-1.1.0.0: test/Test/Util/FS/Error.hs
module Test.Util.FS.Error (
-- * Errors log
ErrorsLog
, emptyLog
, countNoisyErrors
-- * Logged HasFS and HasBlockIO
, simErrorHasBlockIOLogged
, simErrorHasFS
) where
import Control.Concurrent.Class.MonadMVar (MonadMVar)
import Control.Concurrent.Class.MonadSTM.Strict
import Control.Monad.Class.MonadThrow
import Control.Monad.Primitive
import Data.Functor.Barbie
import Data.Monoid
import GHC.Generics
import System.FS.API
import System.FS.BlockIO.API
import System.FS.BlockIO.Sim
import System.FS.Sim.Error
import System.FS.Sim.MockFS (HandleMock, MockFS)
import System.FS.Sim.Stream
{-------------------------------------------------------------------------------
Higher-kinded datatype
-------------------------------------------------------------------------------}
-- | A higher-kinded datatype (HKD) version of the 'Errors' type. 'Errors' is
-- equivalent to @'HKD' 'Stream'@.
--
-- 'HKD' has instances for @barbies@ classes, which lets us manipulate 'HKD'
-- with a small set of combinators like 'bpure' and 'bmap'. This makes writing
-- functions operating on 'HKD' less arduous than writing the functions out
-- fully.
--
-- TODO: admittedly, we are not getting the full benefis from higher-kinded
-- datatypes because 'Errors' and 'HasFS' are not HKDs, meaning we still have to
-- write some stuff out manually, like in 'simErrorHasFSLogged'. We could take
-- the HKD approach even further, but it is likely going to require quite a bit
-- of boilerplate to set up before we can start programming with HKDs. At that
-- point one might ask whether the costs of defining the HKD boilerplate
-- outweighs the benefits of programming with HKDs.
data HKD f = HKD {
dumpStateHKD :: f FsErrorType
, hOpenHKD :: f FsErrorType
, hCloseHKD :: f FsErrorType
, hSeekHKD :: f FsErrorType
, hGetSomeHKD :: f (Either FsErrorType Partial)
, hGetSomeAtHKD :: f (Either FsErrorType Partial)
, hPutSomeHKD :: f (Either (FsErrorType, Maybe PutCorruption) Partial)
, hTruncateHKD :: f FsErrorType
, hGetSizeHKD :: f FsErrorType
, createDirectoryHKD :: f FsErrorType
, createDirectoryIfMissingHKD:: f FsErrorType
, listDirectoryHKD :: f FsErrorType
, doesDirectoryExistHKD :: f FsErrorType
, doesFileExistHKD :: f FsErrorType
, removeDirectoryRecursiveHKD:: f FsErrorType
, removeFileHKD :: f FsErrorType
, renameFileHKD :: f FsErrorType
, hGetBufSomeHKD :: f (Either FsErrorType Partial)
, hGetBufSomeAtHKD :: f (Either FsErrorType Partial)
, hPutBufSomeHKD :: f (Either (FsErrorType, Maybe PutCorruption) Partial)
, hPutBufSomeAtHKD :: f (Either (FsErrorType, Maybe PutCorruption) Partial)
}
deriving stock Generic
deriving anyclass instance FunctorB HKD
deriving anyclass instance ApplicativeB HKD
deriving anyclass instance TraversableB HKD
deriving anyclass instance ConstraintsB HKD
{-------------------------------------------------------------------------------
Errors log
-------------------------------------------------------------------------------}
type ErrorsLog = HKD []
emptyLog :: ErrorsLog
emptyLog = bpure []
{-------------------------------------------------------------------------------
Count noisy errors in the log
-------------------------------------------------------------------------------}
class IsNoisy a where
isNoisy :: a -> Bool
instance IsNoisy FsErrorType where
isNoisy _ = True
instance IsNoisy (Either FsErrorType b) where
isNoisy (Left x) = isNoisy x
isNoisy (Right _) = False
instance IsNoisy (Either (FsErrorType, b) c) where
isNoisy (Left (x, _)) = isNoisy x
isNoisy (Right _) = False
countNoisyErrors :: ErrorsLog -> Int
countNoisyErrors = getSum . bfoldMapC @IsNoisy (Sum . length . Prelude.filter isNoisy)
{-------------------------------------------------------------------------------
Logged HasFS and HasBlockIO
-------------------------------------------------------------------------------}
-- | Like 'simErrorHasFSLogged', but also produces a simulated 'HasBlockIO'.
simErrorHasBlockIOLogged ::
forall m. (MonadMask m, MonadMVar m, PrimMonad m, MonadSTM m)
=> StrictTMVar m MockFS
-> StrictTVar m Errors
-> StrictTVar m (HKD [])
-> m (HasFS m HandleMock, HasBlockIO m HandleMock)
simErrorHasBlockIOLogged fsVar errorsVar logVar = do
let hfs = simErrorHasFSLogged fsVar errorsVar logVar
hbio <- unsafeFromHasFS hfs
pure (hfs, hbio)
-- | Produce a simulated file system with injected errors and a logger for those
-- errors.
--
-- Every time a 'HasFS' primitive is used and an error from 'Errors' is used, it
-- will be logged in 'ErrorsLog'.
simErrorHasFSLogged ::
forall m. (MonadSTM m, MonadMask m, PrimMonad m)
=> StrictTMVar m MockFS
-> StrictTVar m Errors
-> StrictTVar m ErrorsLog
-> HasFS m HandleMock
simErrorHasFSLogged fsVar errorsVar logVar =
HasFS {
dumpState = do
addToLog dumpStateE dumpStateHKD (\l x -> l { dumpStateHKD = x} )
dumpState hfs
, hOpen = \a b -> do
addToLog hOpenE hOpenHKD (\l x -> l { hOpenHKD = x} )
hOpen hfs a b
, hClose = \a -> do
addToLog hCloseE hCloseHKD (\l x -> l { hCloseHKD = x} )
hClose hfs a
, hIsOpen = \a ->
hIsOpen hfs a
, hSeek = \a b c -> do
addToLog hSeekE hSeekHKD (\l x -> l { hSeekHKD = x} )
hSeek hfs a b c
, hGetSome = \a b -> do
addToLog hGetSomeE hGetSomeHKD (\l x -> l { hGetSomeHKD = x} )
hGetSome hfs a b
, hGetSomeAt = \a b c -> do
addToLog hGetSomeAtE hGetSomeAtHKD (\l x -> l { hGetSomeAtHKD = x} )
hGetSomeAt hfs a b c
, hPutSome = \a b -> do
addToLog hPutSomeE hPutSomeHKD (\l x -> l { hPutSomeHKD = x} )
hPutSome hfs a b
, hTruncate = \a b -> do
addToLog hTruncateE hTruncateHKD (\l x -> l { hTruncateHKD = x} )
hTruncate hfs a b
, hGetSize = \a -> do
addToLog hGetSizeE hGetSizeHKD (\l x -> l { hGetSizeHKD = x} )
hGetSize hfs a
, createDirectory = \a -> do
addToLog createDirectoryE createDirectoryHKD (\l x -> l { createDirectoryHKD = x} )
createDirectory hfs a
, createDirectoryIfMissing = \a b -> do
addToLog createDirectoryIfMissingE createDirectoryIfMissingHKD (\l x -> l { createDirectoryIfMissingHKD = x} )
createDirectoryIfMissing hfs a b
, listDirectory = \a -> do
addToLog listDirectoryE listDirectoryHKD (\l x -> l { listDirectoryHKD = x} )
listDirectory hfs a
, doesDirectoryExist = \a -> do
addToLog doesDirectoryExistE doesDirectoryExistHKD (\l x -> l { doesDirectoryExistHKD = x} )
doesDirectoryExist hfs a
, doesFileExist = \a -> do
addToLog doesFileExistE doesFileExistHKD (\l x -> l { doesFileExistHKD = x} )
doesFileExist hfs a
, removeDirectoryRecursive = \a -> do
addToLog removeDirectoryRecursiveE removeDirectoryRecursiveHKD (\l x -> l { removeDirectoryRecursiveHKD = x} )
removeDirectoryRecursive hfs a
, removeFile = \a -> do
addToLog removeFileE removeFileHKD (\l x -> l { removeFileHKD = x} )
removeFile hfs a
, renameFile = \a b -> do
addToLog renameFileE renameFileHKD (\l x -> l { renameFileHKD = x} )
renameFile hfs a b
, mkFsErrorPath = mkFsErrorPath hfs
, unsafeToFilePath = unsafeToFilePath hfs
, hGetBufSome = \a b c d -> do
addToLog hGetBufSomeE hGetBufSomeHKD (\l x -> l { hGetBufSomeHKD = x} )
hGetBufSome hfs a b c d
, hGetBufSomeAt = \a b c d e -> do
addToLog hGetBufSomeAtE hGetBufSomeAtHKD (\l x -> l { hGetBufSomeAtHKD = x} )
hGetBufSomeAt hfs a b c d e
, hPutBufSome = \a b c d -> do
addToLog hPutBufSomeE hPutBufSomeHKD (\l x -> l { hPutBufSomeHKD = x} )
hPutBufSome hfs a b c d
, hPutBufSomeAt = \a b c d e -> do
addToLog hPutBufSomeAtE hPutBufSomeAtHKD (\l x -> l { hPutBufSomeAtHKD = x} )
hPutBufSomeAt hfs a b c d e
}
where
hfs = simErrorHasFS fsVar errorsVar
-- Peek at the first element from an error stream and add it to the errors
-- log if it is 'Just' an error.
addToLog ::
(Errors -> Stream e)
-> (ErrorsLog -> [e])
-> (ErrorsLog -> [e] -> ErrorsLog)
-> m ()
addToLog getE getL setL = do
errors <- readTVarIO errorsVar
logs <- readTVarIO logVar
let s = getE errors
let x = peek s
case x of
Nothing -> pure ()
Just y ->
atomically $ writeTVar logVar $
setL logs $ (++ [y]) $ getL logs
-- Peek at the first element from an error stream.
peek :: Stream a -> Maybe a
peek (UnsafeStream _ xs) = case xs of
[] -> Nothing
(x:_) -> x