-
Notifications
You must be signed in to change notification settings - Fork 217
/
Main.hs
432 lines (407 loc) · 16.9 KB
/
Main.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Main where
import Prelude
import Cardano.BM.Data.Severity
( Severity (..) )
import Cardano.BM.Data.Tracer
( HasPrivacyAnnotation (..), HasSeverityAnnotation (..) )
import Cardano.BM.Plugin
( loadPlugin )
import Cardano.BM.Trace
( appendName )
import Cardano.CLI
( LogOutput (..)
, Port (..)
, ekgEnabled
, getEKGURL
, getPrometheusURL
, withLogging
)
import Cardano.Launcher
( ProcessHasExited (..) )
import Cardano.Startup
( installSignalHandlersNoLogging
, setDefaultFilePermissions
, withUtf8Encoding
)
import Cardano.Wallet.Api.Types
( EncodeAddress (..) )
import Cardano.Wallet.Logging
( BracketLog (..), bracketTracer, stdoutTextTracer, trMessageText )
import Cardano.Wallet.Network.Ports
( portFromURL )
import Cardano.Wallet.Primitive.AddressDerivation
( NetworkDiscriminant (..) )
import Cardano.Wallet.Primitive.SyncProgress
( SyncTolerance (..) )
import Cardano.Wallet.Primitive.Types.Coin
( Coin (..) )
import Cardano.Wallet.Shelley
( SomeNetworkDiscriminant (..)
, Tracers
, serveWallet
, setupTracers
, tracerSeverities
)
import Cardano.Wallet.Shelley.Faucet
( initFaucet )
import Cardano.Wallet.Shelley.Launch
( withSystemTempDir )
import Cardano.Wallet.Shelley.Launch.Cluster
( ClusterLog
, RunningNode (..)
, clusterEraFromEnv
, clusterEraToString
, clusterToApiEra
, localClusterConfigFromEnv
, moveInstantaneousRewardsTo
, oneMillionAda
, sendFaucetAssetsTo
, sendFaucetFundsTo
, testLogDirFromEnv
, testMinSeverityFromEnv
, walletListenFromEnv
, walletMinSeverityFromEnv
, withCluster
, withSMASH
)
import Cardano.Wallet.TokenMetadata.MockServer
( queryServerStatic, withMetadataServer )
import Control.Arrow
( first )
import Control.Monad
( when )
import Control.Monad.IO.Class
( liftIO )
import Control.Tracer
( Tracer (..), contramap, traceWith )
import Data.Either.Combinators
( whenLeft )
import Data.IORef
( IORef, atomicModifyIORef', newIORef )
import Data.Maybe
( fromMaybe )
import Data.Proxy
( Proxy (..) )
import Data.Text
( Text )
import Data.Text.Class
( ToText (..) )
import Network.HTTP.Client
( defaultManagerSettings
, managerResponseTimeout
, newManager
, responseTimeoutMicro
)
import Network.URI
( URI )
import System.Directory
( createDirectory )
import System.Environment
( setEnv )
import System.FilePath
( (</>) )
import Test.Hspec.Core.Runner
( defaultConfig, hspecWith )
import Test.Hspec.Core.Spec
( Spec, SpecWith, describe, parallel, sequential )
import Test.Hspec.Extra
( aroundAll, configWithExecutionTimes )
import Test.Integration.Faucet
( genRewardAccounts
, maryIntegrationTestAssets
, mirMnemonics
, seaHorseTestAssets
, shelleyIntegrationTestFunds
)
import Test.Integration.Framework.Context
( Context (..), PoolGarbageCollectionEvent (..) )
import Test.Utils.Paths
( getTestData, inNixBuild )
import Test.Utils.Startup
( withLineBuffering )
import UnliftIO.Async
( race )
import UnliftIO.Exception
( SomeException, isAsyncException, throwIO, withException )
import UnliftIO.MVar
( newEmptyMVar, newMVar, putMVar, takeMVar, withMVar )
import qualified Cardano.BM.Backend.EKGView as EKG
import qualified Cardano.Pool.DB as Pool
import qualified Cardano.Pool.DB.Sqlite as Pool
import qualified Data.Text as T
import qualified Test.Integration.Scenario.API.Byron.Addresses as ByronAddresses
import qualified Test.Integration.Scenario.API.Byron.CoinSelections as ByronCoinSelections
import qualified Test.Integration.Scenario.API.Byron.HWWallets as ByronHWWallets
import qualified Test.Integration.Scenario.API.Byron.Migrations as ByronMigrations
import qualified Test.Integration.Scenario.API.Byron.Transactions as ByronTransactions
import qualified Test.Integration.Scenario.API.Byron.TransactionsNew as ByronTransactionsNew
import qualified Test.Integration.Scenario.API.Byron.Wallets as ByronWallets
import qualified Test.Integration.Scenario.API.Network as Network
import qualified Test.Integration.Scenario.API.Shared.Addresses as SharedAddresses
import qualified Test.Integration.Scenario.API.Shared.Wallets as SharedWallets
import qualified Test.Integration.Scenario.API.Shelley.Addresses as Addresses
import qualified Test.Integration.Scenario.API.Shelley.CoinSelections as CoinSelections
import qualified Test.Integration.Scenario.API.Shelley.HWWallets as HWWallets
import qualified Test.Integration.Scenario.API.Shelley.Migrations as Migrations
import qualified Test.Integration.Scenario.API.Shelley.Network as Network_
import qualified Test.Integration.Scenario.API.Shelley.Settings as Settings
import qualified Test.Integration.Scenario.API.Shelley.StakePools as StakePools
import qualified Test.Integration.Scenario.API.Shelley.Transactions as Transactions
import qualified Test.Integration.Scenario.API.Shelley.TransactionsNew as TransactionsNew
import qualified Test.Integration.Scenario.API.Shelley.Wallets as Wallets
import qualified Test.Integration.Scenario.CLI.Miscellaneous as MiscellaneousCLI
import qualified Test.Integration.Scenario.CLI.Network as NetworkCLI
import qualified Test.Integration.Scenario.CLI.Port as PortCLI
import qualified Test.Integration.Scenario.CLI.Shelley.Addresses as AddressesCLI
import qualified Test.Integration.Scenario.CLI.Shelley.HWWallets as HWWalletsCLI
import qualified Test.Integration.Scenario.CLI.Shelley.Transactions as TransactionsCLI
import qualified Test.Integration.Scenario.CLI.Shelley.Wallets as WalletsCLI
main :: forall n. (n ~ 'Mainnet) => IO ()
main = withTestsSetup $ \testDir tracers -> do
nix <- inNixBuild
hspecWith (configWithExecutionTimes defaultConfig) $ do
describe "No backend required" $
parallelIf (not nix) $ describe "Miscellaneous CLI tests"
MiscellaneousCLI.spec
specWithServer testDir tracers $ do
describe "API Specifications" $ do
parallel $ do
Addresses.spec @n
CoinSelections.spec @n
ByronAddresses.spec @n
ByronCoinSelections.spec @n
Wallets.spec @n
SharedWallets.spec @n
SharedAddresses.spec @n
ByronWallets.spec @n
HWWallets.spec @n
Migrations.spec @n
ByronMigrations.spec @n
Transactions.spec @n
TransactionsNew.spec @n
Network.spec
Network_.spec
StakePools.spec @n
ByronTransactions.spec @n
ByronTransactionsNew.spec @n
ByronHWWallets.spec @n
-- Possible conflict with StakePools - mark as not parallizable
sequential $ Settings.spec @n
-- Hydra runs tests with code coverage enabled. CLI tests run
-- multiple processes. These processes can try to write to the
-- same .tix file simultaneously, causing errors.
--
-- Because of this, don't run the CLI tests in parallel in hydra.
parallelIf (not nix) $ describe "CLI Specifications" $ do
AddressesCLI.spec @n
TransactionsCLI.spec @n
WalletsCLI.spec @n
HWWalletsCLI.spec @n
PortCLI.spec
NetworkCLI.spec
where
parallelIf flag = if flag then parallel else sequential
-- | Do all the program setup required for integration tests, create a temporary
-- directory, and pass this info to the main hspec action.
withTestsSetup :: (FilePath -> (Tracer IO TestsLog, Tracers IO) -> IO a) -> IO a
withTestsSetup action = do
-- Handle SIGTERM properly
installSignalHandlersNoLogging
-- Stop cardano-cli complaining about file permissions
setDefaultFilePermissions
-- Enables small test-specific workarounds, like timing out faster if wallet
-- deletion fails.
setEnv "CARDANO_WALLET_TEST_INTEGRATION" "1"
-- Flush test output as soon as a line is printed.
-- Set UTF-8, regardless of user locale.
withLineBuffering $ withUtf8Encoding $
-- This temporary directory will contain logs, and all other data
-- produced by the integration tests.
withSystemTempDir stdoutTextTracer "test" $ \testDir ->
withTracers testDir $ action testDir
specWithServer
:: FilePath
-> (Tracer IO TestsLog, Tracers IO)
-> SpecWith Context
-> Spec
specWithServer testDir (tr, tracers) = aroundAll withContext
where
withContext :: (Context -> IO ()) -> IO ()
withContext action = bracketTracer' tr "withContext" $ do
ctx <- newEmptyMVar
poolGarbageCollectionEvents <- newIORef []
let dbEventRecorder =
recordPoolGarbageCollectionEvents poolGarbageCollectionEvents
let setupContext smashUrl faucetConn np baseUrl = bracketTracer' tr "setupContext" $ do
prometheusUrl <- (maybe "none" (\(h, p) -> T.pack h <> ":" <> toText @(Port "Prometheus") p)) <$> getPrometheusURL
ekgUrl <- (maybe "none" (\(h, p) -> T.pack h <> ":" <> toText @(Port "EKG") p)) <$> getEKGURL
traceWith tr $ MsgBaseUrl baseUrl ekgUrl prometheusUrl smashUrl
let fiveMinutes = 300 * 1000 * 1000 -- 5 minutes in microseconds
manager <- newManager $ defaultManagerSettings
{ managerResponseTimeout = responseTimeoutMicro fiveMinutes
}
faucet <- initFaucet
era <- clusterToApiEra <$> clusterEraFromEnv
mintSeaHorseAssetsLock <- newMVar ()
putMVar ctx $ Context
{ _cleanup = pure ()
, _manager = (baseUrl, manager)
, _walletPort = Port . fromIntegral $ portFromURL baseUrl
, _faucet = faucet
, _feeEstimator = error "feeEstimator: unused in shelley specs"
, _networkParameters = np
, _poolGarbageCollectionEvents = poolGarbageCollectionEvents
, _mainEra = era
, _smashUrl = smashUrl
, _mintSeaHorseAssets = \nPerAddr batchSize c addrs ->
withMVar mintSeaHorseAssetsLock $ \() ->
sendFaucetAssetsTo tr' faucetConn testDir batchSize
$ encodeAddresses
$ seaHorseTestAssets nPerAddr c addrs
}
let action' = bracketTracer' tr "spec" . action
res <- race
(withServer dbEventRecorder setupContext)
(takeMVar ctx >>= action')
whenLeft res (throwIO . ProcessHasExited "integration")
-- A decorator for the pool database that records all calls to the
-- 'removeRetiredPools' operation.
--
-- The parameters and return value of each call are recorded by appending
-- a 'PoolGarbageCollectionEvent' value to the start of the given log.
--
recordPoolGarbageCollectionEvents
:: IORef [PoolGarbageCollectionEvent]
-> Pool.DBDecorator IO
recordPoolGarbageCollectionEvents eventsRef = Pool.DBDecorator decorate
where
decorate Pool.DBLayer {..} =
Pool.DBLayer {removeRetiredPools = removeRetiredPoolsDecorated, ..}
where
removeRetiredPoolsDecorated epochNo = do
certificates <- removeRetiredPools epochNo
let event = PoolGarbageCollectionEvent epochNo certificates
liftIO $ do
traceWith tr $ MsgPoolGarbageCollectionEvent event
atomicModifyIORef' eventsRef ((,()) . (event :))
pure certificates
withServer dbDecorator onReady = bracketTracer' tr "withServer" $
withSMASH testDir $ \smashUrl -> do
clusterCfg <- localClusterConfigFromEnv
withCluster tr' testDir clusterCfg setupFaucet $
onClusterStart (onReady $ T.pack smashUrl) dbDecorator
tr' = contramap MsgCluster tr
encodeAddresses = map (first (T.unpack . encodeAddress @'Mainnet))
setupFaucet (RunningNode conn _ _) = do
traceWith tr MsgSettingUpFaucet
let rewards = (,Coin $ fromIntegral oneMillionAda) <$>
concatMap genRewardAccounts mirMnemonics
moveInstantaneousRewardsTo tr' conn testDir rewards
sendFaucetFundsTo tr' conn testDir $
encodeAddresses shelleyIntegrationTestFunds
sendFaucetAssetsTo tr' conn testDir 20 $
encodeAddresses (maryIntegrationTestAssets (Coin 10_000_000))
onClusterStart action dbDecorator (RunningNode conn block0 (gp, vData)) = do
let db = testDir </> "wallets"
createDirectory db
listen <- walletListenFromEnv
let testMetadata = $(getTestData) </> "token-metadata.json"
withMetadataServer (queryServerStatic testMetadata) $ \tokenMetaUrl ->
serveWallet
(SomeNetworkDiscriminant $ Proxy @'Mainnet)
tracers
(SyncTolerance 10)
(Just db)
(Just dbDecorator)
"127.0.0.1"
listen
Nothing
Nothing
(Just tokenMetaUrl)
conn
block0
(gp, vData)
(action conn gp)
`withException` (traceWith tr . MsgServerError)
{-------------------------------------------------------------------------------
Logging
-------------------------------------------------------------------------------}
data TestsLog
= MsgBracket Text BracketLog
| MsgBaseUrl URI Text Text Text
| MsgSettingUpFaucet
| MsgCluster ClusterLog
| MsgPoolGarbageCollectionEvent PoolGarbageCollectionEvent
| MsgServerError SomeException
deriving (Show)
instance ToText TestsLog where
toText = \case
MsgBracket name b -> name <> ": " <> toText b
MsgBaseUrl walletUrl ekgUrl prometheusUrl smashUrl -> T.unlines
[ "Wallet url: " <> T.pack (show walletUrl)
, "EKG url: " <> ekgUrl
, "Prometheus url: " <> prometheusUrl
, "SMASH url: " <> smashUrl
]
MsgSettingUpFaucet -> "Setting up faucet..."
MsgCluster msg -> toText msg
MsgPoolGarbageCollectionEvent e -> mconcat
[ "Intercepted pool garbage collection event for epoch "
, toText (poolGarbageCollectionEpochNo e)
, ". "
, case poolGarbageCollectionCertificates e of
[] -> "No pools were removed from the database."
ps -> mconcat
[ "The following pools were removed from the database: "
, T.unwords (T.pack . show <$> ps)
]
]
MsgServerError e
| isAsyncException e -> "Server thread cancelled"
| otherwise -> T.pack (show e)
instance HasPrivacyAnnotation TestsLog
instance HasSeverityAnnotation TestsLog where
getSeverityAnnotation = \case
MsgBracket _ _ -> Debug
MsgSettingUpFaucet -> Notice
MsgBaseUrl {} -> Notice
MsgCluster msg -> getSeverityAnnotation msg
MsgPoolGarbageCollectionEvent _ -> Info
MsgServerError e
| isAsyncException e -> Info
| otherwise -> Critical
withTracers
:: FilePath
-> ((Tracer IO TestsLog, Tracers IO) -> IO a)
-> IO a
withTracers testDir action = do
let getLogOutputs getMinSev name = do
minSev <- getMinSev
eraStr <- clusterEraToString <$> clusterEraFromEnv
logDir <- fromMaybe testDir <$> testLogDirFromEnv (Just eraStr)
pure
[ LogToFile (logDir </> name) (min minSev Info)
, LogToStdStreams minSev
]
walletLogOutputs <- getLogOutputs walletMinSeverityFromEnv "wallet.log"
testLogOutputs <- getLogOutputs testMinSeverityFromEnv "test.log"
withLogging walletLogOutputs $ \(sb, (cfg, walTr)) -> do
ekgEnabled >>= flip when (EKG.plugin cfg walTr sb >>= loadPlugin sb)
withLogging testLogOutputs $ \(_, (_, testTr)) -> do
let trTests = appendName "integration" testTr
let tracers = setupTracers (tracerSeverities (Just Debug)) walTr
action (trMessageText trTests, tracers)
bracketTracer' :: Tracer IO TestsLog -> Text -> IO a -> IO a
bracketTracer' tr name = bracketTracer (contramap (MsgBracket name) tr)