@@ -8,7 +8,9 @@ import Test.Hydra.Prelude
8
8
9
9
import Blaze.ByteString.Builder.Char8 (writeChar )
10
10
import CardanoNode (NodeLog , withCardanoNodeDevnet )
11
+ import Control.Concurrent.Class.MonadMVar (MonadMVar (.. ))
11
12
import Control.Concurrent.Class.MonadSTM (readTQueue , tryReadTQueue , writeTQueue )
13
+ import Control.Monad.Class.MonadAsync (cancel , waitCatch )
12
14
import Data.ByteString qualified as BS
13
15
import Graphics.Vty (
14
16
DisplayContext (.. ),
@@ -39,16 +41,23 @@ import Hydra.Cluster.Fixture (
39
41
aliceSk ,
40
42
)
41
43
import Hydra.Cluster.Util (chainConfigFor , createAndSaveSigningKey , keysFor )
42
- import Hydra.Logging (showLogsOnFailure )
44
+ import Hydra.Logging (Tracer , showLogsOnFailure )
43
45
import Hydra.Network (Host (.. ))
44
- import Hydra.Options (DirectOptions (.. ))
46
+ import Hydra.Options (DirectOptions (.. ), RunOptions , persistenceRotateAfter )
45
47
import Hydra.TUI (runWithVty )
46
48
import Hydra.TUI.Drawing (renderTime )
47
49
import Hydra.TUI.Options (Options (.. ))
48
50
import Hydra.Tx.ContestationPeriod (ContestationPeriod , toNominalDiffTime )
49
- import HydraNode (HydraClient (HydraClient , hydraNodeId ), HydraNodeLog , withHydraNode )
51
+ import HydraNode (
52
+ HydraClient (HydraClient , hydraNodeId ),
53
+ HydraNodeLog ,
54
+ prepareHydraNode ,
55
+ withHydraNode ,
56
+ withPreparedHydraNode ,
57
+ )
50
58
import System.FilePath ((</>) )
51
59
import System.Posix (OpenMode (WriteOnly ), closeFd , defaultFileFlags , openFd )
60
+ import Test.QuickCheck (Positive (.. ))
52
61
53
62
tuiContestationPeriod :: ContestationPeriod
54
63
tuiContestationPeriod = 10
@@ -63,6 +72,60 @@ spec = do
63
72
sendInputEvent $ EvKey (KChar ' q' ) []
64
73
threadDelay 1
65
74
shouldNotRender " Connecting"
75
+
76
+ around setupRotatedStateTUI $ do
77
+ fit " tui-rotated starts" $ do
78
+ \ TUIRotatedTest
79
+ { tuiTest = TUITest {sendInputEvent, shouldRender, shouldNotRender}
80
+ , nodeHandle = HydraNodeHandle {restartNode}
81
+ } -> do
82
+ threadDelay 1
83
+ shouldRender " Connected"
84
+ shouldRender " Idle"
85
+ sendInputEvent $ EvKey (KChar ' i' ) []
86
+ threadDelay 1
87
+ shouldRender " Initializing"
88
+ restartNode
89
+ sendInputEvent $ EvKey (KChar ' h' ) []
90
+ threadDelay 1
91
+ shouldNotRender " HeadIsInitializing"
92
+ shouldRender " Checkpoint triggered"
93
+ sendInputEvent $ EvKey (KChar ' s' ) []
94
+ threadDelay 1
95
+ shouldRender " Initializing"
96
+ shouldRender " Head id"
97
+ -- open the head
98
+ sendInputEvent $ EvKey (KChar ' c' ) []
99
+ threadDelay 1
100
+ shouldRender " 42000000 lovelace"
101
+ sendInputEvent $ EvKey (KChar ' >' ) []
102
+ sendInputEvent $ EvKey (KChar ' ' ) []
103
+ sendInputEvent $ EvKey KEnter []
104
+ threadDelay 1
105
+ shouldRender " Open"
106
+ restartNode
107
+ sendInputEvent $ EvKey (KChar ' h' ) []
108
+ threadDelay 1
109
+ shouldNotRender " HeadIsOpen"
110
+ shouldRender " Checkpoint triggered"
111
+ sendInputEvent $ EvKey (KChar ' s' ) []
112
+ threadDelay 1
113
+ shouldRender " Open"
114
+ -- close the head
115
+ sendInputEvent $ EvKey (KChar ' c' ) []
116
+ threadDelay 1
117
+ sendInputEvent $ EvKey KEnter []
118
+ threadDelay 1
119
+ shouldRender " Closed"
120
+ restartNode
121
+ sendInputEvent $ EvKey (KChar ' h' ) []
122
+ threadDelay 1
123
+ shouldNotRender " HeadIsClosed"
124
+ shouldRender " Checkpoint triggered"
125
+ sendInputEvent $ EvKey (KChar ' s' ) []
126
+ threadDelay 1
127
+ shouldRender " Closed"
128
+
66
129
around setupNodeAndTUI $ do
67
130
it " starts & renders" $
68
131
\ TUITest {sendInputEvent, shouldRender} -> do
@@ -153,6 +216,115 @@ spec = do
153
216
threadDelay 1
154
217
shouldRender " Not enough Fuel. Please provide more to the internal wallet and try again."
155
218
219
+ setupRotatedStateTUI :: (TUIRotatedTest -> IO () ) -> IO ()
220
+ setupRotatedStateTUI action = do
221
+ showLogsOnFailure " TUISpec" $ \ tracer ->
222
+ withTempDir " tui-end-to-end" $ \ tmpDir -> do
223
+ withCardanoNodeDevnet (contramap FromCardano tracer) tmpDir $ \ _ backend -> do
224
+ hydraScriptsTxId <- publishHydraScriptsAs backend Faucet
225
+ chainConfig <- chainConfigFor Alice tmpDir backend hydraScriptsTxId [] tuiContestationPeriod
226
+ let nodeId = 1
227
+ let externalKeyFilePath = tmpDir </> " external.sk"
228
+ externalSKey <- createAndSaveSigningKey externalKeyFilePath
229
+ let externalVKey = getVerificationKey externalSKey
230
+ seedFromFaucet_ backend externalVKey 42_000_000 (contramap FromFaucet tracer)
231
+ (aliceCardanoVk, _) <- keysFor Alice
232
+ seedFromFaucet_ backend aliceCardanoVk 100_000_000 (contramap FromFaucet tracer)
233
+ options <- prepareHydraNode chainConfig tmpDir nodeId aliceSk [] [nodeId] id
234
+ let options' = options{persistenceRotateAfter = Just (Positive 1 )}
235
+ withTUIRotatedTest (contramap FromHydra tracer) tmpDir nodeId backend externalKeyFilePath options' action
236
+
237
+ data TUIRotatedTest = TUIRotatedTest
238
+ { tuiTest :: TUITest
239
+ , nodeHandle :: HydraNodeHandle
240
+ }
241
+
242
+ data HydraNodeHandle = HydraNodeHandle
243
+ { startNode :: IO ()
244
+ , stopNode :: IO ()
245
+ , restartNode :: IO ()
246
+ , getClient :: IO HydraClient
247
+ }
248
+
249
+ withHydraNodeHandle ::
250
+ Tracer IO HydraNodeLog ->
251
+ FilePath ->
252
+ Int ->
253
+ RunOptions ->
254
+ (HydraNodeHandle -> IO a ) ->
255
+ IO a
256
+ withHydraNodeHandle tracer tmpDir nodeId options action = do
257
+ clientVar <- newEmptyMVar
258
+ runningAsyncVar <- newEmptyMVar
259
+ let
260
+ -- If startNode is called more than once without stopNode,
261
+ -- putMVar clientVar will block because it’s already full.
262
+ startNode = do
263
+ a <- asyncLabelled " hydra-node" $
264
+ withPreparedHydraNode tracer tmpDir nodeId options $ \ client -> do
265
+ putMVar clientVar client
266
+ -- keep async alive as long as node is running
267
+ forever (threadDelay 1_000_000 )
268
+ putMVar runningAsyncVar a
269
+
270
+ stopNode = do
271
+ cancelRunningAsync
272
+ void $ tryTakeMVar clientVar
273
+
274
+ cancelRunningAsync =
275
+ tryTakeMVar runningAsyncVar >>= mapM_ (\ a -> cancel a >> waitCatch a >> pure () )
276
+
277
+ restartNode = stopNode >> startNode
278
+
279
+ getClient = readMVar clientVar
280
+
281
+ bracket
282
+ (pure HydraNodeHandle {startNode, stopNode, restartNode, getClient})
283
+ (const stopNode)
284
+ action
285
+
286
+ withTUIRotatedTest ::
287
+ Tracer IO HydraNodeLog ->
288
+ FilePath ->
289
+ Int ->
290
+ DirectBackend ->
291
+ FilePath ->
292
+ RunOptions ->
293
+ (TUIRotatedTest -> Expectation ) ->
294
+ Expectation
295
+ withTUIRotatedTest tracer tmpDir nodeId backend externalKeyFilePath options' action = do
296
+ withHydraNodeHandle tracer tmpDir nodeId options' $ \ nodeHandle -> do
297
+ startNode nodeHandle
298
+ HydraClient {hydraNodeId} <- getClient nodeHandle
299
+ withTUITest (150 , 10 ) $ \ brickTest@ TUITest {buildVty} -> do
300
+ raceLabelled_
301
+ ( " run-vty"
302
+ , do
303
+ runWithVty
304
+ buildVty
305
+ Options
306
+ { hydraNodeHost =
307
+ Host
308
+ { hostname = " 127.0.0.1"
309
+ , port = fromIntegral $ 4000 + hydraNodeId
310
+ }
311
+ , cardanoNodeSocket =
312
+ nodeSocket
313
+ , cardanoNetworkId =
314
+ networkId
315
+ , cardanoSigningKey = externalKeyFilePath
316
+ }
317
+ )
318
+ ( " action-brick-test"
319
+ , action $
320
+ TUIRotatedTest
321
+ { tuiTest = brickTest
322
+ , nodeHandle
323
+ }
324
+ )
325
+ where
326
+ DirectBackend DirectOptions {nodeSocket, networkId} = backend
327
+
156
328
setupNodeAndTUI' :: Text -> Coin -> (TUITest -> IO () ) -> IO ()
157
329
setupNodeAndTUI' hostname lovelace action =
158
330
showLogsOnFailure " TUISpec" $ \ tracer ->
0 commit comments