Skip to content

Commit 74df97b

Browse files
committed
Use io-sim-classes to replace IO with IOSim later
Also, 'expectationFailure' got replaced by 'error' (for now). Maybe use 'MonadThrow' instead?
1 parent 6abe356 commit 74df97b

5 files changed

Lines changed: 70 additions & 53 deletions

File tree

hydra-node/hydra-node.cabal

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ library
118118
, optparse-applicative
119119
, ouroboros-network-framework
120120
, prometheus
121-
, safe-exceptions
122121
, serialise
123122
, shelley-spec-ledger
124123
, shelley-spec-ledger-test

hydra-node/src/Hydra/Logging.hs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module Hydra.Logging (
1010
nullTracer,
1111
contramap,
1212
traceWith,
13-
traceInTVarIO,
13+
traceInTVar,
1414
LoggerName,
1515

1616
-- * Using it
@@ -38,8 +38,7 @@ import Cardano.BM.Setup (
3838
setupTrace_,
3939
shutdown,
4040
)
41-
import Cardano.BM.Trace (traceInTVarIO)
42-
import Cardano.Prelude
41+
import Cardano.Prelude hiding (atomically)
4342
import Control.Tracer (
4443
Tracer (..),
4544
contramap,
@@ -50,6 +49,7 @@ import Control.Tracer (
5049

5150
import qualified Cardano.BM.Configuration.Model as CM
5251
import qualified Cardano.BM.Data.BackendKind as CM
52+
import Control.Monad.Class.MonadSTM (MonadSTM (atomically), TVar, modifyTVar)
5353

5454
data Verbosity = Quiet | Verbose LoggerName
5555
deriving (Eq, Show)
@@ -89,3 +89,7 @@ transformLogObject transform tr = Tracer $ \a -> do
8989
traceWith tr . (mempty,) =<< LogObject mempty
9090
<$> mkLOMeta Debug Public
9191
<*> pure (LogMessage (transform a))
92+
93+
traceInTVar :: MonadSTM m => TVar m [a] -> Tracer m a
94+
traceInTVar tvar = Tracer $ \a ->
95+
atomically $ modifyTVar tvar (a :)

hydra-node/src/Hydra/Node.hs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,9 @@
66
module Hydra.Node where
77

88
import Cardano.Prelude hiding (STM, async, atomically, cancel, check, poll, threadDelay)
9-
import Control.Concurrent.STM (
10-
newTQueueIO,
11-
readTQueue,
12-
writeTQueue,
13-
)
14-
import Control.Exception.Safe (MonadThrow)
159
import Control.Monad.Class.MonadAsync (MonadAsync, async)
16-
import Control.Monad.Class.MonadSTM (MonadSTM (STM), atomically, newTVar, stateTVar)
10+
import Control.Monad.Class.MonadSTM (MonadSTM (STM), atomically, newTQueue, newTVar, readTQueue, stateTVar, writeTQueue)
11+
import Control.Monad.Class.MonadThrow (MonadThrow)
1712
import Control.Monad.Class.MonadTimer (MonadTimer, threadDelay)
1813
import Hydra.HeadLogic (
1914
ClientRequest (..),
@@ -126,9 +121,9 @@ data EventQueue m e = EventQueue
126121
, nextEvent :: m e
127122
}
128123

129-
createEventQueue :: IO (EventQueue IO e)
124+
createEventQueue :: MonadSTM m => m (EventQueue m e)
130125
createEventQueue = do
131-
q <- newTQueueIO
126+
q <- atomically newTQueue
132127
pure
133128
EventQueue
134129
{ putEvent = atomically . writeTQueue q
@@ -156,7 +151,7 @@ putState :: HydraHead tx m -> HeadState tx -> STM m ()
156151
putState HydraHead{modifyHeadState} new =
157152
modifyHeadState $ const ((), new)
158153

159-
createHydraHead :: (MonadSTM m) => HeadState tx -> Ledger tx -> m (HydraHead tx m)
154+
createHydraHead :: MonadSTM m => HeadState tx -> Ledger tx -> m (HydraHead tx m)
160155
createHydraHead initialState ledger = do
161156
tv <- atomically $ newTVar initialState
162157
pure HydraHead{modifyHeadState = stateTVar tv, ledger}

hydra-node/test/Hydra/BehaviorSpec.hs

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
{-# LANGUAGE TypeApplications #-}
2+
{-# OPTIONS_GHC -Wno-unused-matches #-}
23

34
module Hydra.BehaviorSpec where
45

5-
import Cardano.Prelude hiding (atomically, check, threadDelay)
6-
import Control.Monad.Class.MonadSTM (TVar, atomically, check, modifyTVar, newTVarIO, readTVar)
7-
import Control.Monad.Class.MonadTime (DiffTime)
8-
import Control.Monad.Class.MonadTimer (threadDelay)
9-
import Data.IORef (modifyIORef', newIORef, readIORef)
6+
import Cardano.Prelude hiding (Async, STM, async, atomically, cancel, check, link, poll, threadDelay)
7+
import Control.Monad.Class.MonadAsync (MonadAsync, async, cancel, link, poll)
8+
import Control.Monad.Class.MonadFork (MonadFork)
9+
import Control.Monad.Class.MonadSTM (
10+
MonadSTM,
11+
TVar,
12+
atomically,
13+
check,
14+
modifyTVar,
15+
modifyTVar',
16+
newEmptyTMVar,
17+
newTVarIO,
18+
putTMVar,
19+
readTVar,
20+
takeTMVar,
21+
)
22+
import Control.Monad.Class.MonadThrow (MonadMask)
23+
import Control.Monad.Class.MonadTimer (DiffTime, MonadTimer, threadDelay, timeout)
1024
import Hydra.HeadLogic (
1125
ClientRequest (..),
1226
ClientResponse (..),
@@ -19,8 +33,7 @@ import Hydra.HeadLogic (
1933
)
2034
import Hydra.Ledger (LedgerState)
2135
import Hydra.Ledger.Mock (MockTx (..), mockLedger)
22-
23-
import Hydra.Logging (traceInTVarIO)
36+
import Hydra.Logging (traceInTVar)
2437
import Hydra.Network (HydraNetwork (..))
2538
import Hydra.Node (
2639
HydraNode (..),
@@ -34,17 +47,16 @@ import Hydra.Node (
3447
queryLedgerState,
3548
runHydraNode,
3649
)
37-
import System.Timeout (timeout)
3850
import Test.Hspec (
3951
Spec,
4052
describe,
41-
expectationFailure,
4253
it,
4354
shouldContain,
4455
shouldNotBe,
4556
shouldReturn,
4657
)
4758
import Test.Util (failAfter)
59+
import Prelude (error)
4860

4961
spec :: Spec
5062
spec = describe "Behavior of one ore more hydra-nodes" $ do
@@ -64,7 +76,7 @@ spec = describe "Behavior of one ore more hydra-nodes" $ do
6476
sendRequest n (Init [1]) `shouldReturn` ()
6577

6678
it "accepts Commit after successful Init" $ do
67-
n <- simulatedChainAndNetwork >>= startHydraNode 1
79+
n :: HydraProcess IO MockTx <- simulatedChainAndNetwork >>= startHydraNode 1
6880
sendRequest n (Init [1])
6981
sendRequest n (Commit 1)
7082

@@ -217,7 +229,7 @@ data HydraProcess m tx = HydraProcess
217229
, capturedLogs :: TVar m [HydraNodeLog tx]
218230
}
219231

220-
data Connections = Connections {chain :: OnChain IO, network :: HydraNetwork MockTx IO}
232+
data Connections m = Connections {chain :: OnChain m, network :: HydraNetwork MockTx m}
221233

222234
-- | Creates a simulated chain by returning a function to create the chain
223235
-- client interface for a node. This is necessary, to get to know all nodes
@@ -226,19 +238,25 @@ data Connections = Connections {chain :: OnChain IO, network :: HydraNetwork Moc
226238
-- NOTE: This implementation currently ensures that no two equal 'OnChainTx' can
227239
-- be posted on chain assuming the construction of the real transaction is
228240
-- referentially transparent.
229-
simulatedChainAndNetwork :: IO (HydraNode MockTx IO -> IO Connections)
241+
simulatedChainAndNetwork :: MonadSTM m => m (HydraNode MockTx m -> m (Connections m))
230242
simulatedChainAndNetwork = do
231-
refHistory <- newIORef []
243+
refHistory <- newTVarIO []
232244
nodes <- newTVarIO []
233245
pure $ \n -> do
234246
atomically $ modifyTVar nodes (n :)
235247
pure $ Connections OnChain{postTx = postTx nodes refHistory} HydraNetwork{broadcast = broadcast nodes}
236248
where
237249
postTx nodes refHistory tx = do
238-
h <- readIORef refHistory
239-
unless (tx `elem` h) $ do
240-
modifyIORef' refHistory (tx :)
241-
atomically (readTVar nodes) >>= mapM_ (`handleChainTx` tx)
250+
res <- atomically $ do
251+
h <- readTVar refHistory
252+
if tx `elem` h
253+
then pure Nothing
254+
else do
255+
modifyTVar' refHistory (tx :)
256+
Just <$> readTVar nodes
257+
case res of
258+
Nothing -> pure ()
259+
Just ns -> mapM_ (`handleChainTx` tx) ns
242260

243261
broadcast nodes msg = atomically (readTVar nodes) >>= mapM_ (`handleMessage` msg)
244262

@@ -247,21 +265,24 @@ testContestationPeriod :: DiffTime
247265
testContestationPeriod = 10
248266

249267
startHydraNode ::
268+
(MonadAsync m, MonadTimer m, MonadFork m, MonadMask m) =>
250269
Natural ->
251-
(HydraNode MockTx IO -> IO Connections) ->
252-
IO (HydraProcess IO MockTx)
270+
(HydraNode MockTx m -> m (Connections m)) ->
271+
m (HydraProcess m MockTx)
253272
startHydraNode = startHydraNode' NoSnapshots
254273

255274
startHydraNode' ::
275+
(MonadAsync m, MonadTimer m, MonadFork m, MonadMask m) =>
256276
SnapshotStrategy ->
257277
Natural ->
258-
(HydraNode MockTx IO -> IO Connections) ->
259-
IO (HydraProcess IO MockTx)
278+
(HydraNode MockTx m -> m (Connections m)) ->
279+
m (HydraProcess m MockTx)
260280
startHydraNode' snapshotStrategy nodeId connectToChain = do
261281
capturedLogs <- newTVarIO []
262-
response <- newEmptyMVar
282+
response <- atomically newEmptyTMVar
263283
node <- createHydraNode response
264-
nodeThread <- async $ runHydraNode (traceInTVarIO capturedLogs) node
284+
-- TODO(SN): trace directly into io-sim's 'Trace'
285+
nodeThread <- async $ runHydraNode (traceInTVar capturedLogs) node
265286
link nodeThread
266287
pure $
267288
HydraProcess
@@ -271,27 +292,25 @@ startHydraNode' snapshotStrategy nodeId connectToChain = do
271292
Nothing -> pure Ready
272293
Just _ -> pure NotReady
273294
, sendRequest = handleClientRequest node
274-
, waitForResponse = takeMVar response
295+
, waitForResponse = atomically $ takeTMVar response
275296
, waitForLedgerState =
276297
\st -> do
277-
result <-
278-
timeout
279-
1_000_000
280-
( atomically $ do
281-
st' <- queryLedgerState node
282-
check (st == st')
283-
)
284-
when (isNothing result) $ expectationFailure ("Expected ledger state of node " <> show nodeId <> " to be " <> show st)
298+
result <- timeout 1 $
299+
atomically $ do
300+
st' <- queryLedgerState node
301+
check (st == st')
302+
-- TODO(SN): use MonadThrow instead?
303+
when (isNothing result) $ error ("Expected ledger state of node " <> show nodeId <> " to be " <> show st)
285304
, nodeId
286305
, capturedLogs
287306
}
288307
where
289308
createHydraNode response = do
290309
let env = Environment nodeId
291310
eq <- createEventQueue
292-
let headState = createHeadState [] (HeadParameters testContestationPeriod mempty) SnapshotStrategy
311+
let headState = createHeadState [] (HeadParameters testContestationPeriod mempty) snapshotStrategy
293312
hh <- createHydraHead headState mockLedger
294313
let hn' = HydraNetwork{broadcast = const $ pure ()}
295-
let node = HydraNode{eq, hn = hn', hh, oc = OnChain (const $ pure ()), sendResponse = putMVar response, env}
314+
let node = HydraNode{eq, hn = hn', hh, oc = OnChain (const $ pure ()), sendResponse = atomically . putTMVar response, env}
296315
Connections oc hn <- connectToChain node
297316
pure node{oc, hn}

hydra-node/test/Test/Util.hs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
module Test.Util where
22

33
import Cardano.Prelude
4-
import Control.Monad.Class.MonadTime (DiffTime)
5-
import Control.Monad.Class.MonadTimer (timeout)
6-
import Test.Hspec (expectationFailure)
4+
import Control.Monad.Class.MonadTimer (DiffTime, MonadTimer, timeout)
5+
import Prelude (error)
76

8-
failAfter :: HasCallStack => DiffTime -> IO () -> IO ()
7+
failAfter :: (HasCallStack, MonadTimer m) => DiffTime -> m () -> m ()
98
failAfter seconds action =
109
timeout seconds action >>= \case
11-
Nothing -> expectationFailure $ "Test timed out after " <> show seconds <> " seconds"
10+
-- TODO(SN): use MonadThrow instead?
11+
Nothing -> error $ "Test timed out after " <> show seconds <> " seconds"
1212
Just _ -> pure ()

0 commit comments

Comments
 (0)