packages feed

lattest-lib 0.1.0.0 → 0.1.1

raw patch · 7 files changed

+121/−21 lines, 7 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Lattest.Model.BoundedMonad: instance Data.OrdMonad.OrdTraversable Lattest.Model.BoundedMonad.FreeLattice
+ System.IO.Streams.Synchronized.Attoparsec: instance GHC.Exception.Type.Exception System.IO.Streams.Synchronized.Attoparsec.ParseException
+ System.IO.Streams.Synchronized.Attoparsec: instance GHC.Show.Show System.IO.Streams.Synchronized.Attoparsec.ParseException
+ System.IO.Streams.Synchronized.Attoparsec: parseFromStream :: TVar (Bool, IResult ByteString (Maybe r)) -> Parser ByteString (Maybe r) -> TInputStream ByteString -> STM (Maybe r)
+ System.IO.Streams.Synchronized.Attoparsec: parserToInputStream :: Parser (Maybe r) -> TInputStream ByteString -> IO (TInputStream r)
- Lattest.Streams.Synchronized: fromInputStreamBuffered :: InputStream a -> IO (TInputStream a)
+ Lattest.Streams.Synchronized: fromInputStreamBuffered :: InputStream a -> IO (ThreadId, TInputStream a)

Files

CHANGELOG.md view
@@ -8,6 +8,9 @@  ## Unreleased +## 0.1.1 - 2026-07-15+Bugfix patch: adds instance OrdTraversable FreeLattice, and properly kills the reading thread before its socket is closed.+ ## 0.1.0.0 - 2026-07-14 First release 
lattest-lib.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           lattest-lib-version:        0.1.0.0+version:        0.1.1 synopsis:       Model-based testing framework description:    Welcome to Lattest, the latest attested lattice testing tech!                 .@@ -50,6 +50,7 @@       Lattest.Util.ReportUtils       Lattest.Util.STSJSONParser       Lattest.Util.Utils+      System.IO.Streams.Synchronized.Attoparsec   other-modules:       Lattest.Model.Internal.NonDeterministic       Lattest.Model.Symbolic.Internal.Boute
src/Lattest/Adapter/StandardAdapters.hs view
@@ -74,7 +74,7 @@  import Debug.Trace(trace) -- FIXME find a better alternative -import GHC.Conc(forkIO, newTVarIO, TVar, retry, atomically, writeTVar, readTVar, STM, orElse)+import GHC.Conc(forkIO, newTVarIO, TVar, retry, atomically, writeTVar, readTVar, STM, orElse, killThread)  import Network.Socket(HostName, PortNumber) import Network.Utils (niceSocketsDo, connectTCP)@@ -566,11 +566,12 @@ connectSocketAdapterWith settings = niceSocketsDo $ do     socket <- connectTCP (hostName settings) (portNumber settings)     (actionBytes, inputCommandBytes) <- socketToStreams socket-    forkedActionBytes <- fromInputStreamBuffered actionBytes+    (threadid, forkedActionBytes) <- fromInputStreamBuffered actionBytes     return $ Adapter {         inputCommandsToSut = inputCommandBytes,         actionsFromSut = forkedActionBytes,-        close = Socket.gracefulClose socket 1000+        -- when the adapter is closed, we first kill the thread, to prevent it from throwing errors when we kill its socket+        close = killThread threadid >> Socket.gracefulClose socket 1000     }  -- | Create an adapter by connecting to a server socket, with the default settings, and sending inputs and reading outputs in JSON format.
src/Lattest/Exec/ADG/SplitGraph.hs view

file too large to diff

src/Lattest/Model/BoundedMonad.hs view
@@ -190,6 +190,10 @@ instance OM.OrdFunctor FreeLattice where     ordMap f (FreeLattice x) = FreeLattice $ Set.map (Set.map f) x +instance OM.OrdTraversable FreeLattice where+    ordTraverse f (FreeLattice x) = FreeLattice <$> ordTraverse (ordTraverse f) x+    ordSequenceA (FreeLattice x) = FreeLattice <$> ordTraverse ordSequenceA x+ instance Ord a => JoinSemiLattice (FreeLattice a) where     (FreeLattice x) \/ (FreeLattice y) = FreeLattice $ Set.map Set.unions $ nAryCartesianProduct $ Set.fromList [x,y] 
src/Lattest/Streams/Synchronized.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE TupleSections #-} module Lattest.Streams.Synchronized ( TInputStream, read,@@ -26,16 +27,14 @@ import Control.Concurrent.STM.TQueue(TQueue, newTQueueIO, writeTQueue, readTQueue, isEmptyTQueue, unGetTQueue) import Control.Concurrent.STM.TMVar(TMVar, takeTMVar, isEmptyTMVar) import Control.Concurrent.STM.TVar(newTVarIO, readTVar, writeTVar)-import GHC.IO.Exception (ioe_location, ioe_errno)-import Control.Exception(throwIO, Exception, catchJust)+import Control.Exception(throwIO, Exception) import Control.Monad (void, when) import Data.List(singleton) -import GHC.Conc (atomically, forkIO)+import GHC.Conc (atomically, forkIO, ThreadId) import System.IO.Streams (InputStream, OutputStream, makeOutputStream, connect) import qualified System.IO.Streams as Streams (write) import Control.Monad.Extra((||^))-import Foreign.C.Error (eBADF, Errno (Errno))  data TInputStream a = TInputStream {     tRead :: STM (Maybe a),@@ -141,22 +140,12 @@  -- a synchronized input stream that reads from the given input stream. A monitoring thread reads the given input stream and buffers the inputs on the background -- TODO support a bound for the buffer-fromInputStreamBuffered :: InputStream a -> IO (TInputStream a)+fromInputStreamBuffered :: InputStream a -> IO (ThreadId, TInputStream a) fromInputStreamBuffered is = do     buffer <- newTQueueIO     bufferOS <- makeOutputStream $ atomically . writeTQueue buffer -- an intermediate output stream to write to the queue-    void $ forkIO $ catchSocketClosed $ connect is bufferOS -- move items from the original adapter into the buffer in a separate thread-    fromBuffer buffer- where-   -- catches "threadWait: invalid argument (Bad file descriptor)"-   -- which is thrown when the socket is closed while this thread is running, e.g. on Adaptor.close()-   -- The alternative solution is to keep track of the threadID given by forkIO,-   -- and explicitly stop the thread before closing the socket.-   catchSocketClosed forkedThread = catchJust threadwaitinvalid forkedThread pure-   threadwaitinvalid e-    | ioe_location e == "threadWait"-    , fmap Errno (ioe_errno e) == Just eBADF = Just ()-    | otherwise = Nothing+    threadid <- forkIO $ connect is bufferOS -- move items from the original adapter into the buffer in a separate thread+    (threadid,) <$> fromBuffer buffer  mapUnbuffered :: (a -> b) -> (b -> a) -> TInputStream a -> TInputStream b -- we need an inverse to push a's back to the original TInputStream of b's mapUnbuffered f f' tis = TInputStream { -- FIXME either remove or wrap in withClosedState
+ src/System/IO/Streams/Synchronized/Attoparsec.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE FlexibleContexts   #-}+{-# LANGUAGE OverloadedStrings  #-}++module System.IO.Streams.Synchronized.Attoparsec (+parseFromStream,+parserToInputStream+)+where++import           Control.Exception(Exception)+import           Control.Monad(unless)+import           Data.String(IsString)+import           Control.Monad.Extra((||^), (&&^))+import qualified Data.Attoparsec.ByteString as C8(parse,feed, Parser)+import           Data.Attoparsec.Types(Parser,IResult(..))+import           Data.ByteString(ByteString)+import qualified Data.ByteString as C8(null)+import           Data.List(intercalate)++import           Lattest.Streams.Synchronized (TInputStream,makeTInputStream,hasInput)+import qualified Lattest.Streams.Synchronized as Streams (read, unRead)+import Control.Concurrent.STM.TVar(TVar, newTVarIO, writeTVar, readTVar)+import Control.Concurrent.STM(STM, throwSTM)++parseFromStream :: TVar (Bool, IResult ByteString (Maybe r)) -> Parser ByteString (Maybe r) -> TInputStream ByteString -> STM (Maybe r)+parseFromStream stateVar parser is = do+    parseFromStream' stateVar True is+    (closed, state) <- readTVar stateVar+    writeTVar stateVar (closed, C8.parse parser "")+    if closed+        then return Nothing+        else case state of+            (Done "" r) -> return r+            other ->+                let (residual,ctx,message) = errorContext other+                in throwSTM $ ParseException (toMsg ctx ++ message ++ ", residual [" ++ show residual ++ "]")+    where+    toMsg [] = ""+    toMsg xs = "[parsing " ++ intercalate "/" xs ++ "] "++parseFromStream' :: TVar (Bool, IResult ByteString (Maybe r)) -> Bool -> TInputStream ByteString -> STM ()+parseFromStream' stateVar blockUntilFinished is = do+    (closed, r) <- readTVar stateVar+    if closed+        then return ()+        else doParse r >>= writeTVar stateVar+    where+    doParse :: IResult ByteString (Maybe r) -> STM (Bool, IResult ByteString (Maybe r))+    doParse r = do+        stop <- return (isFinished r) ||^ (return (not blockUntilFinished) &&^ (not <$> hasInput is))+        if stop+            then do+                r' <- unread' r+                return (False, r')+            else  do+                m <- Streams.read is+                case m of+                    Nothing -> do+                        r' <- unread' r+                        return (True, r')+                    Just s -> if C8.null s+                        then doParse r+                        else doParse $! C8.feed r s+    unread rest = unless (C8.null rest) $ Streams.unRead rest is+    +    unread' (Fail rest ctx e) = unread rest >> return (Fail "" ctx e)+    unread' (Done rest result) = unread rest >> return (Done "" result)+    unread' partial = return partial++errorContext :: IsString a => IResult a r -> (a, [String], String)+errorContext (Fail residual ctx msg) = (residual, ctx, msg)+errorContext (Partial _) = ("", [], "")+errorContext (Done residual _) = (residual, [],"")++isFinished :: IResult a b -> Bool+isFinished (Partial _) = False+isFinished _ = True++newtype ParseException = ParseException String++instance Exception ParseException+instance Show ParseException where+    show (ParseException message) = "Parse error: " ++ message++-- Convert a stream of bytes to a stream of actions parsed from those bytes, using the provided parser. The stream ends if either the parser yields+-- a Nothing or if the underlying stream yields a Nothing+-- If the parser yields a Nothing, then all bytes up to that Nothing are consumed from the underlying stream, but if the underyling stream yields a+-- Nothing, then all bytes since the last Just result are unread.+parserToInputStream :: C8.Parser (Maybe r)+                    -> TInputStream ByteString+                    -> IO (TInputStream r)+parserToInputStream parser is = do+    stateVar <- newTVarIO (False, C8.parse parser "")+    makeTInputStream (parseFromStream stateVar parser is) (parserHasInput stateVar)+    where+    parserHasInput stateVar = do+        parseFromStream' stateVar False is+        (closed, state) <- readTVar stateVar+        return $ closed || isFinished state+++