knob 0.1.1 → 0.2
raw patch · 4 files changed
+394/−390 lines, 4 filesdep ~basedep ~bytestringdep ~transformersnew-uploaderPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base, bytestring, transformers
API changes (from Hackage documentation)
- Data.Knob: instance BufferedIO Device
- Data.Knob: instance IODevice Device
- Data.Knob: instance Typeable Device
+ Data.Knob: instance GHC.IO.BufferedIO.BufferedIO Data.Knob.Device
+ Data.Knob: instance GHC.IO.Device.IODevice Data.Knob.Device
+ Data.Knob: instance GHC.IO.Device.RawIO Data.Knob.Device
Files
- knob.cabal +13/−16
- lib/Data/Knob.hs +102/−96
- tests/KnobTests.hs +273/−273
- tests/knob-tests.cabal +6/−5
knob.cabal view
@@ -1,15 +1,16 @@+cabal-version: 2.4 name: knob-version: 0.1.1+version: 0.2 license: MIT license-file: license.txt author: John Millikin <jmillikin@gmail.com>-maintainer: John Millikin <jmillikin@gmail.com>+maintainer: Naïm Favier <n@monade.li> build-type: Simple-cabal-version: >= 1.6+tested-with: GHC == 9.0.2, GHC == 9.2.1 category: System stability: experimental-homepage: https://john-millikin.com/software/knob/-bug-reports: mailto:jmillikin@gmail.com+homepage: https://github.com/ncfavier/knob+bug-reports: https://github.com/ncfavier/knob/issues synopsis: Memory-backed handles description:@@ -38,22 +39,18 @@ tests/KnobTests.hs source-repository head- type: bazaar- location: https://john-millikin.com/software/knob/--source-repository this- type: bazaar- location: https://john-millikin.com/branches/knob/0.1/- tag: knob_0.1.1+ type: git+ location: https://github.com/ncfavier/knob library+ default-language: Haskell2010 hs-source-dirs: lib- ghc-options: -Wall -O2+ ghc-options: -Wall build-depends:- base >= 4.2 && < 5.0- , bytestring >= 0.9- , transformers >= 0.2+ base >= 4.15 && < 5+ , bytestring >= 0.9 && < 0.12+ , transformers >= 0.2 && < 0.7 exposed-modules: Data.Knob
lib/Data/Knob.hs view
@@ -24,15 +24,15 @@ -- > bytes <- Data.Knob.getContents knob -- > putStrLn ("Wrote bytes: " ++ show bytes) module Data.Knob- ( Knob- , newKnob- , Data.Knob.getContents- , setContents- - , newFileHandle- , withFileHandle- ) where+ ( Knob+ , newKnob+ , Data.Knob.getContents+ , setContents + , newFileHandle+ , withFileHandle+ ) where+ import qualified Control.Concurrent.MVar as MVar import Control.Exception (bracket, throwIO) import Control.Monad (when)@@ -53,106 +53,112 @@ -- have multiple 'IO.Handle's open to it, each of which behaves like a standard -- file handle. ----- Use 'getContents' and 'setContents' to inspect and modify the knob’s+-- Use 'Data.Knob.getContents' and 'setContents' to inspect and modify the knob's -- byte buffer. newtype Knob = Knob (MVar.MVar ByteString) data Device = Device IO.IOMode (MVar.MVar ByteString) (MVar.MVar Int)- deriving (Typeable)+ deriving (Typeable) instance IO.IODevice Device where- ready _ _ _ = return True- close _ = return ()- isTerminal _ = return False- isSeekable _ = return True- - seek (Device _ _ var) IO.AbsoluteSeek off = do- checkOffset off- MVar.modifyMVar_ var (\_ -> return (fromInteger off))- - seek (Device _ _ var) IO.RelativeSeek off = do- MVar.modifyMVar_ var (\old_off -> do- let new_off = toInteger old_off + off- checkOffset new_off- return (fromInteger new_off))- - seek dev@(Device _ _ off_var) IO.SeekFromEnd off = do- MVar.modifyMVar_ off_var (\_ -> do- size <- IO.getSize dev- let new_off = size + off- checkOffset new_off- return (fromInteger new_off))- - tell (Device _ _ var) = fmap toInteger (MVar.readMVar var)- getSize (Device _ var _) = do- bytes <- MVar.readMVar var- return (toInteger (Data.ByteString.length bytes))- setSize dev size = setDeviceSize dev size- devType _ = return IO.RegularFile+ ready _ _ _ = return True+ close _ = return ()+ isTerminal _ = return False+ isSeekable _ = return True + seek (Device _ _ var) IO.AbsoluteSeek off = do+ checkOffset off+ MVar.modifyMVar var (\_ -> return (fromInteger off, off))++ seek (Device _ _ var) IO.RelativeSeek off = do+ MVar.modifyMVar var (\old_off -> do+ let new_off = toInteger old_off + off+ checkOffset new_off+ return (fromInteger new_off, new_off))++ seek dev@(Device _ _ off_var) IO.SeekFromEnd off = do+ MVar.modifyMVar off_var (\_ -> do+ size <- IO.getSize dev+ let new_off = size + off+ checkOffset new_off+ return (fromInteger new_off, new_off))++ tell (Device _ _ var) = fmap toInteger (MVar.readMVar var)+ getSize (Device _ var _) = do+ bytes <- MVar.readMVar var+ return (toInteger (Data.ByteString.length bytes))+ setSize dev size = setDeviceSize dev size+ devType _ = return IO.RegularFile+ checkOffset :: Integer -> IO () checkOffset off = when (toInteger (maxBound :: Int) < off) (throwIO err) where- err = IO.IOError Nothing IO.InvalidArgument "" "offset > (maxBound :: Int)" Nothing Nothing+ err = IO.IOError Nothing IO.InvalidArgument "" "offset > (maxBound :: Int)" Nothing Nothing setDeviceSize :: Device -> Integer -> IO () setDeviceSize (Device mode bytes_var _) size = checkSize >> setBytes where- intSize :: Int- intSize = fromInteger size- - checkSize = when (size > toInteger (maxBound :: Int)) $ do- throwIO (IO.IOError Nothing IO.InvalidArgument "" "size > (maxBound :: Int)" Nothing Nothing)- - setBytes = MVar.modifyMVar_ bytes_var $ \bytes -> case mode of- IO.ReadMode -> throwIO (IO.IOError Nothing IO.IllegalOperation "" "handle in ReadMode" Nothing Nothing)- IO.WriteMode -> return (Data.ByteString.replicate intSize 0)- IO.ReadWriteMode -> return (clip bytes)- IO.AppendMode -> return (clip bytes)- - clip bytes = case intSize - Data.ByteString.length bytes of- padLen | padLen > 0 -> Data.ByteString.append bytes (Data.ByteString.replicate padLen 0)- _ -> Data.ByteString.take intSize bytes+ intSize :: Int+ intSize = fromInteger size + checkSize = when (size > toInteger (maxBound :: Int)) $ do+ throwIO (IO.IOError Nothing IO.InvalidArgument "" "size > (maxBound :: Int)" Nothing Nothing)++ setBytes = MVar.modifyMVar_ bytes_var $ \bytes -> case mode of+ IO.ReadMode -> throwIO (IO.IOError Nothing IO.IllegalOperation "" "handle in ReadMode" Nothing Nothing)+ IO.WriteMode -> return (Data.ByteString.replicate intSize 0)+ IO.ReadWriteMode -> return (clip bytes)+ IO.AppendMode -> return (clip bytes)++ clip bytes = case intSize - Data.ByteString.length bytes of+ padLen | padLen > 0 -> Data.ByteString.append bytes (Data.ByteString.replicate padLen 0)+ _ -> Data.ByteString.take intSize bytes++instance IO.RawIO Device where+ read = error "Raw IO is not implemented for knobs"+ readNonBlocking = error "Raw IO is not implemented for knobs"+ write = error "Raw IO is not implemented for knobs"+ writeNonBlocking = error "Raw IO is not implemented for knobs"+ instance IO.BufferedIO Device where- newBuffer _ = IO.newByteBuffer 4096- - fillReadBuffer dev buf = do- (numRead, newBuf) <- IO.fillReadBuffer0 dev buf- return (maybe 0 id numRead, newBuf)- - fillReadBuffer0 (Device _ bytes_var pos_var) buf = do- MVar.withMVar bytes_var $ \bytes -> do- MVar.modifyMVar pos_var $ \pos -> do- if pos >= Data.ByteString.length bytes- then return (pos, (Nothing, buf))- else do- let chunk = Data.ByteString.take (IO.bufSize buf) (Data.ByteString.drop pos bytes)- unsafeUseAsCStringLen chunk $ \(chunkPtr, chunkLen) -> do- Foreign.withForeignPtr (IO.bufRaw buf) $ \ptr -> do- Foreign.copyArray ptr (Foreign.castPtr chunkPtr) chunkLen- return (pos + chunkLen, (Just chunkLen, (buf { IO.bufL = 0, IO.bufR = chunkLen })))- - flushWriteBuffer (Device _ bytes_var pos_var) buf = do- MVar.modifyMVar_ bytes_var $ \bytes -> do- MVar.modifyMVar pos_var $ \pos -> do- let (before, after) = Data.ByteString.splitAt pos bytes- let padding = Data.ByteString.replicate (pos - Data.ByteString.length before) 0- - let bufStart ptr = Foreign.castPtr (Foreign.plusPtr ptr (IO.bufL buf))- let bufLen = IO.bufR buf - IO.bufL buf- bufBytes <- Foreign.withForeignPtr (IO.bufRaw buf) (\ptr ->- Data.ByteString.packCStringLen (bufStart ptr, bufLen))- let newBytes = Data.ByteString.concat [before, padding, bufBytes, Data.ByteString.drop bufLen after]- return (pos + bufLen, newBytes)- return (buf { IO.bufL = 0, IO.bufR = 0 })- - flushWriteBuffer0 dev buf = do- newBuf <- IO.flushWriteBuffer dev buf- return (IO.bufR buf - IO.bufL buf, newBuf)+ newBuffer _ = IO.newByteBuffer 4096 + fillReadBuffer dev buf = do+ (numRead, newBuf) <- IO.fillReadBuffer0 dev buf+ return (maybe 0 id numRead, newBuf)++ fillReadBuffer0 (Device _ bytes_var pos_var) buf = do+ MVar.withMVar bytes_var $ \bytes -> do+ MVar.modifyMVar pos_var $ \pos -> do+ if pos >= Data.ByteString.length bytes+ then return (pos, (Nothing, buf))+ else do+ let chunk = Data.ByteString.take (IO.bufSize buf) (Data.ByteString.drop pos bytes)+ unsafeUseAsCStringLen chunk $ \(chunkPtr, chunkLen) -> do+ Foreign.withForeignPtr (IO.bufRaw buf) $ \ptr -> do+ Foreign.copyArray ptr (Foreign.castPtr chunkPtr) chunkLen+ return (pos + chunkLen, (Just chunkLen, (buf { IO.bufL = 0, IO.bufR = chunkLen })))++ flushWriteBuffer (Device _ bytes_var pos_var) buf = do+ MVar.modifyMVar_ bytes_var $ \bytes -> do+ MVar.modifyMVar pos_var $ \pos -> do+ let (before, after) = Data.ByteString.splitAt pos bytes+ let padding = Data.ByteString.replicate (pos - Data.ByteString.length before) 0++ let bufStart ptr = Foreign.castPtr (Foreign.plusPtr ptr (IO.bufL buf))+ let bufLen = IO.bufR buf - IO.bufL buf+ bufBytes <- Foreign.withForeignPtr (IO.bufRaw buf) (\ptr ->+ Data.ByteString.packCStringLen (bufStart ptr, bufLen))+ let newBytes = Data.ByteString.concat [before, padding, bufBytes, Data.ByteString.drop bufLen after]+ return (pos + bufLen, newBytes)+ return (buf { IO.bufL = 0, IO.bufR = 0 })++ flushWriteBuffer0 dev buf = do+ newBuf <- IO.flushWriteBuffer dev buf+ return (IO.bufR buf - IO.bufL buf, newBuf)+ newKnob :: MonadIO m => ByteString -> m Knob newKnob bytes = do- var <- liftIO (MVar.newMVar bytes)- return (Knob var)+ var <- liftIO (MVar.newMVar bytes)+ return (Knob var) getContents :: MonadIO m => Knob -> m ByteString getContents (Knob var) = liftIO (MVar.readMVar var)@@ -167,11 +173,11 @@ -> String -- ^ Filename shown in error messages -> IO.IOMode -> m IO.Handle newFileHandle (Knob var) name mode = liftIO $ do- startPosition <- MVar.withMVar var $ \bytes -> return $ case mode of- IO.AppendMode -> Data.ByteString.length bytes- _ -> 0- posVar <- MVar.newMVar startPosition- IO.mkFileHandle (Device mode var posVar) name mode Nothing IO.noNewlineTranslation+ startPosition <- MVar.withMVar var $ \bytes -> return $ case mode of+ IO.AppendMode -> Data.ByteString.length bytes+ _ -> 0+ posVar <- MVar.newMVar startPosition+ IO.mkFileHandle (Device mode var posVar) name mode Nothing IO.noNewlineTranslation -- | See 'newFileHandle'. withFileHandle :: MonadIO m
tests/KnobTests.hs view
@@ -5,9 +5,9 @@ -- -- See license.txt for details module Main- ( tests- , main- ) where+ ( tests+ , main+ ) where import Control.Monad.IO.Class (liftIO) import qualified Data.ByteString@@ -24,315 +24,315 @@ main = Test.Chell.defaultMain tests tests :: [Suite]-tests = [test_File, test_Duplex]+tests = [suite_File, suite_Duplex] -test_File :: Suite-test_File = suite "file"- [ suite "read"- [ test_ReadFromStart- , test_ReadFromOffset- , test_ReadToEOF- , test_ReadPastEOF- ]- , suite "write"- [ test_WriteFromStart- , test_WriteFromOffset- , test_WritePastEOF- , test_WriteAppended- ]- , suite "seek"- [ test_SeekAbsolute- , test_SeekRelative- , test_SeekFromEnd- , test_SeekBeyondMaxInt- ]- , suite "setSize"- [ test_SetSize_Read- , test_SetSize_Write- , test_SetSize_ReadWrite- , test_SetSize_Append- ]- - , test_Ready- , test_Close- , test_SetContents- , test_WithFileHandle- ]+suite_File :: Suite+suite_File = suite "file" $ concatMap suiteTests+ [ suite "read"+ [ test_ReadFromStart+ , test_ReadFromOffset+ , test_ReadToEOF+ , test_ReadPastEOF+ ]+ , suite "write"+ [ test_WriteFromStart+ , test_WriteFromOffset+ , test_WritePastEOF+ , test_WriteAppended+ ]+ , suite "seek"+ [ test_SeekAbsolute+ , test_SeekRelative+ , test_SeekFromEnd+ , test_SeekBeyondMaxInt+ ]+ , suite "setSize"+ [ test_SetSize_Read+ , test_SetSize_Write+ , test_SetSize_ReadWrite+ , test_SetSize_Append+ ]+ ]+ ++ [ test_Ready+ , test_Close+ , test_SetContents+ , test_WithFileHandle+ ] -test_ReadFromStart :: Suite+test_ReadFromStart :: Test test_ReadFromStart = assertions "from-start" $ do- k <- newKnob "abcde"- h <- newFileHandle k "foo.txt" ReadMode- - bytes <- liftIO $ Data.ByteString.hGet h 3- $expect (equal bytes "abc")- - off <- liftIO $ hTell h- $expect (equal off 3)+ k <- newKnob "abcde"+ h <- newFileHandle k "foo.txt" ReadMode -test_ReadFromOffset :: Suite+ bytes <- liftIO $ Data.ByteString.hGet h 3+ $expect (equal bytes "abc")++ off <- liftIO $ hTell h+ $expect (equal off 3)++test_ReadFromOffset :: Test test_ReadFromOffset = assertions "from-offset" $ do- k <- newKnob "abcde"- h <- newFileHandle k "foo.txt" ReadMode- - liftIO $ hSeek h AbsoluteSeek 1- bytes <- liftIO $ Data.ByteString.hGet h 3- $expect (equal bytes "bcd")- - off <- liftIO $ hTell h- $expect (equal off 4)+ k <- newKnob "abcde"+ h <- newFileHandle k "foo.txt" ReadMode -test_ReadToEOF :: Suite+ liftIO $ hSeek h AbsoluteSeek 1+ bytes <- liftIO $ Data.ByteString.hGet h 3+ $expect (equal bytes "bcd")++ off <- liftIO $ hTell h+ $expect (equal off 4)++test_ReadToEOF :: Test test_ReadToEOF = assertions "to-eof" $ do- k <- newKnob "abcde"- h <- newFileHandle k "foo.txt" ReadMode- - bytes <- liftIO $ Data.ByteString.hGet h 10- $expect (equal bytes "abcde")- - off <- liftIO $ hTell h- $expect (equal off 5)+ k <- newKnob "abcde"+ h <- newFileHandle k "foo.txt" ReadMode -test_ReadPastEOF :: Suite+ bytes <- liftIO $ Data.ByteString.hGet h 10+ $expect (equal bytes "abcde")++ off <- liftIO $ hTell h+ $expect (equal off 5)++test_ReadPastEOF :: Test test_ReadPastEOF = assertions "past-eof" $ do- k <- newKnob "abcde"- h <- newFileHandle k "foo.txt" ReadMode- - liftIO $ hSeek h AbsoluteSeek 10- bytes <- liftIO $ Data.ByteString.hGet h 10- $expect (equal bytes "")- - off <- liftIO $ hTell h- $expect (equal off 10)+ k <- newKnob "abcde"+ h <- newFileHandle k "foo.txt" ReadMode -test_WriteFromStart :: Suite+ liftIO $ hSeek h AbsoluteSeek 10+ bytes <- liftIO $ Data.ByteString.hGet h 10+ $expect (equal bytes "")++ off <- liftIO $ hTell h+ $expect (equal off 10)++test_WriteFromStart :: Test test_WriteFromStart = assertions "from-start" $ do- k <- newKnob ""- h <- newFileHandle k "foo.txt" WriteMode- liftIO $ hSetBuffering h NoBuffering- - liftIO $ Data.ByteString.hPut h "abcde"- bytes <- Data.Knob.getContents k- $expect (equal bytes "abcde")+ k <- newKnob ""+ h <- newFileHandle k "foo.txt" WriteMode+ liftIO $ hSetBuffering h NoBuffering -test_WriteFromOffset :: Suite+ liftIO $ Data.ByteString.hPut h "abcde"+ bytes <- Data.Knob.getContents k+ $expect (equal bytes "abcde")++test_WriteFromOffset :: Test test_WriteFromOffset = assertions "from-offset" $ do- k <- newKnob ""- h <- newFileHandle k "foo.txt" WriteMode- liftIO $ hSetBuffering h NoBuffering- - liftIO $ Data.ByteString.hPut h "abcde"- liftIO $ hSeek h AbsoluteSeek 2- liftIO $ Data.ByteString.hPut h "abcde"- - bytes <- Data.Knob.getContents k- $expect (equal bytes "ababcde")+ k <- newKnob ""+ h <- newFileHandle k "foo.txt" WriteMode+ liftIO $ hSetBuffering h NoBuffering -test_WritePastEOF :: Suite+ liftIO $ Data.ByteString.hPut h "abcde"+ liftIO $ hSeek h AbsoluteSeek 2+ liftIO $ Data.ByteString.hPut h "abcde"++ bytes <- Data.Knob.getContents k+ $expect (equal bytes "ababcde")++test_WritePastEOF :: Test test_WritePastEOF = assertions "past-eof" $ do- k <- newKnob ""- h <- newFileHandle k "foo.txt" WriteMode- liftIO $ hSetBuffering h NoBuffering- - liftIO $ hSeek h AbsoluteSeek 2- liftIO $ Data.ByteString.hPut h "abcde"- bytes <- Data.Knob.getContents k- $expect (equal bytes "\0\0abcde")+ k <- newKnob ""+ h <- newFileHandle k "foo.txt" WriteMode+ liftIO $ hSetBuffering h NoBuffering -test_WriteAppended :: Suite+ liftIO $ hSeek h AbsoluteSeek 2+ liftIO $ Data.ByteString.hPut h "abcde"+ bytes <- Data.Knob.getContents k+ $expect (equal bytes "\0\0abcde")++test_WriteAppended :: Test test_WriteAppended = assertions "appended" $ do- k <- newKnob "foo"- h <- newFileHandle k "foo.txt" AppendMode- liftIO $ hSetBuffering h NoBuffering- - liftIO $ Data.ByteString.hPut h "bar"- bytes <- Data.Knob.getContents k- $expect (equal bytes "foobar")+ k <- newKnob "foo"+ h <- newFileHandle k "foo.txt" AppendMode+ liftIO $ hSetBuffering h NoBuffering -test_SeekAbsolute :: Suite+ liftIO $ Data.ByteString.hPut h "bar"+ bytes <- Data.Knob.getContents k+ $expect (equal bytes "foobar")++test_SeekAbsolute :: Test test_SeekAbsolute = assertions "absolute" $ do- k <- newKnob ""- h <- newFileHandle k "foo.txt" ReadMode- - before <- liftIO $ hTell h- liftIO $ hSeek h AbsoluteSeek 2- after <- liftIO $ hTell h- - $expect (equal before 0)- $expect (equal after 2)+ k <- newKnob ""+ h <- newFileHandle k "foo.txt" ReadMode -test_SeekRelative :: Suite+ before <- liftIO $ hTell h+ liftIO $ hSeek h AbsoluteSeek 2+ after <- liftIO $ hTell h++ $expect (equal before 0)+ $expect (equal after 2)++test_SeekRelative :: Test test_SeekRelative = assertions "relative" $ do- k <- newKnob ""- h <- newFileHandle k "foo.txt" ReadMode- - before <- liftIO $ hTell h- liftIO $ hSeek h RelativeSeek 2- after1 <- liftIO $ hTell h- liftIO $ hSeek h RelativeSeek 2- after2 <- liftIO $ hTell h- - $expect (equal before 0)- $expect (equal after1 2)- $expect (equal after2 4)+ k <- newKnob ""+ h <- newFileHandle k "foo.txt" ReadMode -test_SeekFromEnd :: Suite+ before <- liftIO $ hTell h+ liftIO $ hSeek h RelativeSeek 2+ after1 <- liftIO $ hTell h+ liftIO $ hSeek h RelativeSeek 2+ after2 <- liftIO $ hTell h++ $expect (equal before 0)+ $expect (equal after1 2)+ $expect (equal after2 4)++test_SeekFromEnd :: Test test_SeekFromEnd = assertions "from-end" $ do- k <- newKnob "abcde"- h <- newFileHandle k "foo.txt" ReadMode- - before <- liftIO $ hTell h- liftIO $ hSeek h SeekFromEnd (- 2)- after <- liftIO $ hTell h- - $expect (equal before 0)- $expect (equal after 3)+ k <- newKnob "abcde"+ h <- newFileHandle k "foo.txt" ReadMode -test_SeekBeyondMaxInt :: Suite+ before <- liftIO $ hTell h+ liftIO $ hSeek h SeekFromEnd (- 2)+ after <- liftIO $ hTell h++ $expect (equal before 0)+ $expect (equal after 3)++test_SeekBeyondMaxInt :: Test test_SeekBeyondMaxInt = assertions "beyond-max-int" $ do- k <- newKnob "abcde"- h <- newFileHandle k "foo.txt" ReadMode- - let intPlusOne = toInteger (maxBound :: Int) + 1- - $expect $ throwsEq- (GHC.IOError (Just h) GHC.InvalidArgument "hSeek" "offset > (maxBound :: Int)" Nothing (Just "foo.txt"))- (hSeek h AbsoluteSeek intPlusOne)- $expect $ throwsEq- (GHC.IOError (Just h) GHC.InvalidArgument "hSeek" "offset > (maxBound :: Int)" Nothing (Just "foo.txt"))- (hSeek h RelativeSeek intPlusOne)- - -- testing this with real contents is difficult/impossible on a- -- 64-bit system, so use an unsafe function to corrupt the knob's- -- internal buffer first.- hugeBytes <- liftIO (unsafePackCStringLen (nullPtr, maxBound))- liftIO $ hSeek h AbsoluteSeek (intPlusOne - 1)- setContents k hugeBytes- $expect $ throwsEq- (GHC.IOError (Just h) GHC.InvalidArgument "hSeek" "offset > (maxBound :: Int)" Nothing (Just "foo.txt"))- (hSeek h SeekFromEnd 2)+ k <- newKnob "abcde"+ h <- newFileHandle k "foo.txt" ReadMode -test_Ready :: Suite+ let intPlusOne = toInteger (maxBound :: Int) + 1++ $expect $ throwsEq+ (GHC.IOError (Just h) GHC.InvalidArgument "hSeek" "offset > (maxBound :: Int)" Nothing (Just "foo.txt"))+ (hSeek h AbsoluteSeek intPlusOne)+ $expect $ throwsEq+ (GHC.IOError (Just h) GHC.InvalidArgument "hSeek" "offset > (maxBound :: Int)" Nothing (Just "foo.txt"))+ (hSeek h RelativeSeek intPlusOne)++ -- testing this with real contents is difficult/impossible on a+ -- 64-bit system, so use an unsafe function to corrupt the knob's+ -- internal buffer first.+ hugeBytes <- liftIO (unsafePackCStringLen (nullPtr, maxBound))+ liftIO $ hSeek h AbsoluteSeek (intPlusOne - 1)+ setContents k hugeBytes+ $expect $ throwsEq+ (GHC.IOError (Just h) GHC.InvalidArgument "hSeek" "offset > (maxBound :: Int)" Nothing (Just "foo.txt"))+ (hSeek h SeekFromEnd 2)++test_Ready :: Test test_Ready = assertions "ready" $ do- k <- newKnob "abcde"- h <- newFileHandle k "foo.txt" ReadMode- - ready <- liftIO $ hReady h- $expect ready- - _ <- liftIO $ Data.ByteString.hGet h 10- $expect $ throwsEq- (GHC.IOError (Just h) GHC.EOF "hWaitForInput" "" Nothing (Just "foo.txt"))- (hReady h)+ k <- newKnob "abcde"+ h <- newFileHandle k "foo.txt" ReadMode -test_Close :: Suite+ ready <- liftIO $ hReady h+ $expect ready++ _ <- liftIO $ Data.ByteString.hGet h 10+ $expect $ throwsEq+ (GHC.IOError (Just h) GHC.EOF "hWaitForInput" "" Nothing (Just "foo.txt"))+ (hReady h)++test_Close :: Test test_Close = assertions "close" $ do- k <- newKnob "abcde"- h <- newFileHandle k "foo.txt" ReadMode- - liftIO $ hClose h- $expect $ throwsEq- (GHC.IOError (Just h) GHC.IllegalOperation "hGetBuf" "handle is closed" Nothing (Just "foo.txt"))- (Data.ByteString.hGet h 1)- $expect $ throwsEq- (GHC.IOError (Just h) GHC.IllegalOperation "hWaitForInput" "handle is closed" Nothing (Just "foo.txt"))- (hReady h)+ k <- newKnob "abcde"+ h <- newFileHandle k "foo.txt" ReadMode -test_SetSize_Read :: Suite+ liftIO $ hClose h+ $expect $ throwsEq+ (GHC.IOError (Just h) GHC.IllegalOperation "hGetBuf" "handle is closed" Nothing (Just "foo.txt"))+ (Data.ByteString.hGet h 1)+ $expect $ throwsEq+ (GHC.IOError (Just h) GHC.IllegalOperation "hWaitForInput" "handle is closed" Nothing (Just "foo.txt"))+ (hReady h)++test_SetSize_Read :: Test test_SetSize_Read = assertions "ReadMode" $ do- k <- newKnob "abcde"- h <- newFileHandle k "foo.txt" ReadMode- - let intPlusOne = toInteger (maxBound :: Int) + 1- $expect $ throwsEq- (GHC.IOError (Just h) GHC.InvalidArgument "hSetFileSize" "size > (maxBound :: Int)" Nothing (Just "foo.txt"))- (hSetFileSize h intPlusOne)- - $expect $ throwsEq- (GHC.IOError (Just h) GHC.IllegalOperation "hSetFileSize" "handle in ReadMode" Nothing (Just "foo.txt"))- (hSetFileSize h 2)+ k <- newKnob "abcde"+ h <- newFileHandle k "foo.txt" ReadMode -test_SetSize_Write :: Suite+ let intPlusOne = toInteger (maxBound :: Int) + 1+ $expect $ throwsEq+ (GHC.IOError (Just h) GHC.InvalidArgument "hSetFileSize" "size > (maxBound :: Int)" Nothing (Just "foo.txt"))+ (hSetFileSize h intPlusOne)++ $expect $ throwsEq+ (GHC.IOError (Just h) GHC.IllegalOperation "hSetFileSize" "handle in ReadMode" Nothing (Just "foo.txt"))+ (hSetFileSize h 2)++test_SetSize_Write :: Test test_SetSize_Write = assertions "WriteMode" $ do- k <- newKnob "abcde"- h <- newFileHandle k "foo.txt" WriteMode- - let intPlusOne = toInteger (maxBound :: Int) + 1- $expect $ throwsEq- (GHC.IOError (Just h) GHC.InvalidArgument "hSetFileSize" "size > (maxBound :: Int)" Nothing (Just "foo.txt"))- (hSetFileSize h intPlusOne)- - -- Resets contents to all NULL, regardless of offset- liftIO $ hSeek h AbsoluteSeek 2- liftIO $ hSetFileSize h 4- - bytes <- Data.Knob.getContents k- $expect (equal bytes "\0\0\0\0")+ k <- newKnob "abcde"+ h <- newFileHandle k "foo.txt" WriteMode -test_SetSize_ReadWrite :: Suite+ let intPlusOne = toInteger (maxBound :: Int) + 1+ $expect $ throwsEq+ (GHC.IOError (Just h) GHC.InvalidArgument "hSetFileSize" "size > (maxBound :: Int)" Nothing (Just "foo.txt"))+ (hSetFileSize h intPlusOne)++ -- Resets contents to all NULL, regardless of offset+ liftIO $ hSeek h AbsoluteSeek 2+ liftIO $ hSetFileSize h 4++ bytes <- Data.Knob.getContents k+ $expect (equal bytes "\0\0\0\0")++test_SetSize_ReadWrite :: Test test_SetSize_ReadWrite = assertions "ReadWriteMode" $ do- k <- newKnob "abcde"- h <- newFileHandle k "foo.txt" ReadWriteMode- - let intPlusOne = toInteger (maxBound :: Int) + 1- $expect $ throwsEq- (GHC.IOError (Just h) GHC.InvalidArgument "hSetFileSize" "size > (maxBound :: Int)" Nothing (Just "foo.txt"))- (hSetFileSize h intPlusOne)- - -- Truncates contents, regardless of offset- do- liftIO $ hSeek h AbsoluteSeek 2- liftIO $ hSetFileSize h 4- bytes <- Data.Knob.getContents k- $expect (equal bytes "abcd")- - do- liftIO $ hSetFileSize h 6- bytes <- Data.Knob.getContents k- $expect (equal bytes "abcd\0\0")+ k <- newKnob "abcde"+ h <- newFileHandle k "foo.txt" ReadWriteMode -test_SetSize_Append :: Suite+ let intPlusOne = toInteger (maxBound :: Int) + 1+ $expect $ throwsEq+ (GHC.IOError (Just h) GHC.InvalidArgument "hSetFileSize" "size > (maxBound :: Int)" Nothing (Just "foo.txt"))+ (hSetFileSize h intPlusOne)++ -- Truncates contents, regardless of offset+ do+ liftIO $ hSeek h AbsoluteSeek 2+ liftIO $ hSetFileSize h 4+ bytes <- Data.Knob.getContents k+ $expect (equal bytes "abcd")++ do+ liftIO $ hSetFileSize h 6+ bytes <- Data.Knob.getContents k+ $expect (equal bytes "abcd\0\0")++test_SetSize_Append :: Test test_SetSize_Append = assertions "AppendMode" $ do- k <- newKnob "abcde"- h <- newFileHandle k "foo.txt" AppendMode- - let intPlusOne = toInteger (maxBound :: Int) + 1- $expect $ throwsEq- (GHC.IOError (Just h) GHC.InvalidArgument "hSetFileSize" "size > (maxBound :: Int)" Nothing (Just "foo.txt"))- (hSetFileSize h intPlusOne)- - do- liftIO $ hSetFileSize h 4- bytes <- Data.Knob.getContents k- $expect (equal bytes "abcd")- - do- liftIO $ hSetFileSize h 6- bytes <- Data.Knob.getContents k- $expect (equal bytes "abcd\0\0")+ k <- newKnob "abcde"+ h <- newFileHandle k "foo.txt" AppendMode -test_SetContents :: Suite+ let intPlusOne = toInteger (maxBound :: Int) + 1+ $expect $ throwsEq+ (GHC.IOError (Just h) GHC.InvalidArgument "hSetFileSize" "size > (maxBound :: Int)" Nothing (Just "foo.txt"))+ (hSetFileSize h intPlusOne)++ do+ liftIO $ hSetFileSize h 4+ bytes <- Data.Knob.getContents k+ $expect (equal bytes "abcd")++ do+ liftIO $ hSetFileSize h 6+ bytes <- Data.Knob.getContents k+ $expect (equal bytes "abcd\0\0")++test_SetContents :: Test test_SetContents = assertions "setContents" $ do- k <- newKnob "abcde"- before <- Data.Knob.getContents k- setContents k "foo"- after <- Data.Knob.getContents k- - $expect (equal before "abcde")- $expect (equal after "foo")+ k <- newKnob "abcde"+ before <- Data.Knob.getContents k+ setContents k "foo"+ after <- Data.Knob.getContents k -test_WithFileHandle :: Suite+ $expect (equal before "abcde")+ $expect (equal after "foo")++test_WithFileHandle :: Test test_WithFileHandle = assertions "withFileHandle" $ do- k <- newKnob ""- h <- withFileHandle k "test.txt" WriteMode $ \h -> do- Data.ByteString.hPut h "abcde"- return h- - bytes <- Data.Knob.getContents k- $expect (equal bytes "abcde")- - closed <- liftIO $ hIsClosed h- $expect closed+ k <- newKnob ""+ h <- withFileHandle k "test.txt" WriteMode $ \h -> do+ Data.ByteString.hPut h "abcde"+ return h -test_Duplex :: Suite-test_Duplex = suite "duplex" []+ bytes <- Data.Knob.getContents k+ $expect (equal bytes "abcde")++ closed <- liftIO $ hIsClosed h+ $expect closed++suite_Duplex :: Suite+suite_Duplex = suite "duplex" []
tests/knob-tests.cabal view
@@ -1,7 +1,7 @@ name: knob-tests version: 0 build-type: Simple-cabal-version: >= 1.6+cabal-version: >= 1.8 flag coverage default: False@@ -11,12 +11,13 @@ main-is: KnobTests.hs ghc-options: -Wall hs-source-dirs: ../lib,.+ other-modules: Data.Knob if flag(coverage) ghc-options: -fhpc build-depends:- base >= 4.2 && < 5.0- , bytestring >= 0.9 && < 0.10- , chell >= 0.2 && < 0.3- , transformers >= 0.2 && < 0.3+ base >= 4.15 && < 5+ , bytestring >= 0.9 && < 0.12+ , chell >= 0.2 && < 0.6+ , transformers >= 0.2 && < 0.7