Skip to content

Commit d992b35

Browse files
committed
feat(testing): collect coverage-data when script fails before submission
1 parent 2f072dc commit d992b35

File tree

5 files changed

+118
-53
lines changed

5 files changed

+118
-53
lines changed

src/coin-selection/test/Spec.hs

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Cardano.Ledger.Conway.PParams qualified as Ledger
1414
import Cardano.Ledger.Conway.Rules qualified as Rules
1515
import Cardano.Ledger.Shelley.API (ApplyTxError (..))
1616
import Cardano.Ledger.Shelley.TxCert qualified as TxCert
17+
import Control.Exception (catch, throwIO)
1718
import Control.Lens (view, (&), (.~), (^.), _3, _4)
1819
import Control.Monad (replicateM, void, when)
1920
import Control.Monad.Except (MonadError, runExceptT)
@@ -71,8 +72,13 @@ import Convex.MockChain.Defaults qualified as Defaults
7172
import Convex.MockChain.Gen qualified as Gen
7273
import Convex.MockChain.Staking (registerPool)
7374
import Convex.MockChain.Utils (
75+
Options (coverageRef),
76+
defaultOptions,
7477
mockchainFails,
78+
mockchainFailsWithOptions,
7579
mockchainSucceeds,
80+
mockchainSucceedsWithOptions,
81+
modifyTransactionLimits,
7682
runMockchainProp,
7783
runTestableErr,
7884
)
@@ -82,8 +88,10 @@ import Convex.NodeParams (
8288
)
8389
import Convex.Query (balancePaymentCredentials)
8490
import Convex.TestingInterface (
91+
RunOptions (mcOptions),
8592
TestingInterface (..),
86-
propRunActions,
93+
defaultRunOptions,
94+
propRunActionsWithOptions,
8795
)
8896
import Convex.Utils (failOnError, inBabbage)
8997
import Convex.Utils.String (unsafeAssetName, unsafeTxId)
@@ -99,15 +107,18 @@ import Convex.Wallet.Operator (
99107
verificationKey,
100108
)
101109
import Data.Foldable (traverse_)
110+
import Data.IORef (IORef, newIORef, readIORef)
102111
import Data.List qualified as List
103112
import Data.List.NonEmpty (NonEmpty (..))
104113
import Data.Map qualified as Map
105114
import Data.Set qualified as Set
106115
import PlutusLedgerApi.V2 qualified as PV2
107-
import PlutusTx.Coverage (CoverageReport (CoverageReport))
116+
import PlutusTx.Coverage (CoverageData, CoverageReport (CoverageReport))
108117
import Prettyprinter qualified as Pretty
118+
import Scripts (pingPongCovIdx)
109119
import Scripts qualified
110120
import Scripts.PingPong qualified as PingPong
121+
import System.Exit (ExitCode)
111122
import Test.QuickCheck.Gen qualified as Gen
112123
import Test.Tasty (
113124
TestTree,
@@ -121,7 +132,6 @@ import Test.Tasty.QuickCheck (
121132
testProperty,
122133
)
123134
import Test.Tasty.QuickCheck qualified as QC
124-
import Text.Show.Pretty (ppShow)
125135

126136
-- | Model state for the PingPong contract testing interface
127137
data PingPongModel = PingPongModel
@@ -282,10 +292,18 @@ instance TestingInterface PingPongModel where
282292
monitoring _state _action prop = prop
283293

284294
main :: IO ()
285-
main = defaultMain tests
295+
main = do
296+
ref <- newIORef mempty
297+
defaultMain (tests ref)
298+
`catch` ( \(e :: ExitCode) -> do
299+
covData <- readIORef ref
300+
let report = CoverageReport pingPongCovIdx covData
301+
print $ Pretty.pretty report
302+
throwIO e
303+
)
286304

287-
tests :: TestTree
288-
tests =
305+
tests :: IORef CoverageData -> TestTree
306+
tests ref =
289307
testGroup
290308
"unit tests"
291309
[ testGroup
@@ -323,63 +341,74 @@ tests =
323341
"ping-pong"
324342
[ testCase
325343
"Ping and Pong should succeed"
326-
( mockchainSucceeds $
344+
( mockchainSucceedsWithOptions opts $
327345
failOnError
328346
(pingPongMultipleRounds Scripts.Pinged [Scripts.Pong])
329347
)
330348
, testCase
331349
"Pong and Ping should succeed"
332-
( mockchainSucceeds $
350+
( mockchainSucceedsWithOptions opts $
333351
failOnError (pingPongMultipleRounds Scripts.Ponged [Scripts.Ping])
334352
)
335-
, testCase
353+
, --
354+
-- , testCase
355+
-- "Ping and Ping should fail"
356+
-- ( mockchainSucceedsWithOptions opts $
357+
-- failOnError (pingPongMultipleRounds Scripts.Pinged [Scripts.Ping])
358+
-- )
359+
testCase
336360
"Ping and Ping should fail"
337-
( mockchainFails
361+
( mockchainFailsWithOptions
362+
opts
338363
(failOnError (pingPongMultipleRounds Scripts.Pinged [Scripts.Ping]))
339364
-- Test tree fails
340365
(\_ -> pure ())
341366
)
342367
, testCase
343368
"Pong and Pong should fail"
344-
( mockchainFails
369+
( mockchainFailsWithOptions
370+
opts
345371
(failOnError (pingPongMultipleRounds Scripts.Ponged [Scripts.Pong]))
346372
-- Test tree fails
347373
(\_ -> pure ())
348374
)
349375
, testCase
350376
"Stop after Ping should succeed"
351-
( mockchainSucceeds $
377+
( mockchainSucceedsWithOptions opts $
352378
failOnError (pingPongMultipleRounds Scripts.Ponged [Scripts.Ping, Scripts.Stop])
353379
)
354380
, testCase
355381
"Stop after Pong should succeed"
356-
( mockchainSucceeds $
382+
( mockchainSucceedsWithOptions opts $
357383
failOnError (pingPongMultipleRounds Scripts.Pinged [Scripts.Pong, Scripts.Stop])
358384
)
359385
, testCase
360386
"Stop after Stop should fail"
361-
( mockchainFails
387+
( mockchainFailsWithOptions
388+
opts
362389
(failOnError (pingPongMultipleRounds Scripts.Stopped [Scripts.Stop]))
363390
-- Test tree fails
364391
(\_ -> pure ())
365392
)
366393
, testCase
367394
"Ping after Stop should fail"
368-
( mockchainFails
395+
( mockchainFailsWithOptions
396+
opts
369397
(failOnError (pingPongMultipleRounds Scripts.Stopped [Scripts.Ping]))
370398
-- Test tree fails
371399
(\_ -> pure ())
372400
)
373401
, testCase
374402
"Pong after Stop should fail"
375-
( mockchainFails
403+
( mockchainFailsWithOptions
404+
opts
376405
(failOnError (pingPongMultipleRounds Scripts.Stopped [Scripts.Pong]))
377406
-- Test tree fails
378407
(\_ -> pure ())
379408
)
380409
, testProperty
381410
"Property-based test with TestingInterface"
382-
(propRunActions @PingPongModel)
411+
(propRunActionsWithOptions @PingPongModel runOpts)
383412
]
384413
]
385414
, testGroup
@@ -401,6 +430,9 @@ tests =
401430
, testCase "script withdrawl with custom redeemer" (mockchainSucceeds $ failOnError addScriptWithdrawalWithCustomRedeemerTest)
402431
]
403432
]
433+
where
434+
opts = modifyTransactionLimits (defaultOptions{coverageRef = Just ref}) 17000
435+
runOpts = defaultRunOptions{mcOptions = opts}
404436

405437
spendPublicKeyOutput :: Assertion
406438
spendPublicKeyOutput = mockchainSucceeds $ failOnError (Wallet.w2 `paymentTo` Wallet.w1)

src/mockchain/lib/Convex/MockChain.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,6 @@ constructValidated globals (UtxoEnv _ pp _) st tx =
424424
scriptResults = map (evaluatePlutusWithContext Verbose) sLst
425425
-- Extract logs and check if validation passed
426426
allLogs = concatMap fst scriptResults
427-
_ = coverageDataFromLogMsg
428427
validationPassed = all (isRight . snd) scriptResults
429428

430429
-- Format logs for display

src/mockchain/lib/Convex/MockChain/Defaults.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ protocolParameters =
115115
L.emptyPParamsIdentity @ConwayEra
116116
& L.hkdMaxBHSizeL .~ 1_100
117117
& L.hkdMaxBBSizeL .~ 90_112
118-
& L.hkdMaxTxSizeL .~ 17_384
118+
& L.hkdMaxTxSizeL .~ 16_384
119119
& L.hkdMinFeeAL .~ 44
120120
& L.hkdMinFeeBL .~ 155_381
121121
& L.hkdKeyDepositL .~ 2_000_000

src/mockchain/lib/Convex/MockChain/Utils.hs

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{-# LANGUAGE LambdaCase #-}
22
{-# LANGUAGE NamedFieldPuns #-}
33
{-# LANGUAGE TypeApplications #-}
4+
{-# LANGUAGE ViewPatterns #-}
45

56
-- | Utility functions for using the mockchain types in @hunit@ or @QuickCheck@ tests
67
module Convex.MockChain.Utils (
@@ -22,12 +23,13 @@ module Convex.MockChain.Utils (
2223
-- * Options for running mockchain testCase
2324
Options (..),
2425
defaultOptions,
26+
modifyTransactionLimits,
2527
) where
2628

2729
import Cardano.Api (ConwayEra)
2830
import Cardano.Api qualified as C
2931
import Control.Exception (SomeException, try)
30-
import Control.Lens ((^.))
32+
import Control.Lens ((&), (.~), (^.))
3133
import Control.Monad.Except (ExceptT, runExceptT)
3234
import Control.Monad.IO.Class (liftIO)
3335
import Convex.Class (coverageData)
@@ -39,19 +41,25 @@ import Convex.MockChain (
3941
runMockchain,
4042
runMockchain0IOWith,
4143
)
44+
45+
import Cardano.Ledger.Core qualified as L
4246
import Convex.MockChain.Defaults qualified as Defaults
43-
import Convex.NodeParams (NodeParams)
47+
import Convex.NodeParams (NodeParams (..))
4448
import Convex.Wallet.MockWallet qualified as Wallet
4549
import Data.Functor.Identity (Identity)
4650
import Data.IORef (IORef, modifyIORef)
47-
import PlutusTx.Coverage (CoverageData)
51+
import Data.Maybe (fromMaybe)
52+
import Data.Word (Word32)
53+
import PlutusTx.Coverage (CoverageData, coverageDataFromLogMsg)
4854
import Test.HUnit (Assertion)
4955
import Test.QuickCheck (
5056
Property,
5157
Testable (..),
5258
counterexample,
5359
)
5460
import Test.QuickCheck.Monadic (PropertyM (..), monadic, monadicIO)
61+
import Text.Read (readMaybe)
62+
import Text.Show.Pretty (ppShow)
5563

5664
data Options era = Options
5765
{ params :: NodeParams era
@@ -65,6 +73,14 @@ defaultOptions =
6573
, coverageRef = Nothing
6674
}
6775

76+
-- | Modify the maximum transaction size in the protocol parameters of the given options
77+
modifyTransactionLimits :: Options ConwayEra -> Word32 -> Options ConwayEra
78+
modifyTransactionLimits opts@Options{params = Defaults.pParams -> pp} newVal =
79+
-- TODO: use lenses to make this cleaner
80+
opts
81+
{ params = (params opts){npProtocolParameters = C.LedgerProtocolParameters $ pp & L.ppMaxTxSizeL .~ newVal}
82+
}
83+
6884
-- | Run the 'Mockchain' action and fail if there is an error
6985
mockchainSucceeds :: MockchainIO C.ConwayEra a -> Assertion
7086
mockchainSucceeds = mockchainSucceedsWith Defaults.nodeParams
@@ -78,13 +94,11 @@ mockchainSucceedsWithOptions :: (C.IsShelleyBasedEra era) => Options era -> Mock
7894
mockchainSucceedsWithOptions Options{params, coverageRef} action =
7995
try @SomeException (runMockchain0IOWith Wallet.initialUTxOs params action) >>= \case
8096
Right (_, st) -> do
81-
case coverageRef of
82-
Nothing -> pure ()
83-
Just ref -> do
84-
let covData = st ^. coverageData
85-
modifyIORef ref (<> covData)
97+
appendCovData coverageRef $ st ^. coverageData
8698
pure ()
87-
Left err -> fail (show err)
99+
Left err -> do
100+
appendCovData coverageRef $ tryExtractCovverageData err
101+
fail (show err)
88102

89103
{- | Run the 'Mockchain' action, fail if it succeeds, and handle the error
90104
appropriately.
@@ -106,13 +120,13 @@ mockchainFailsWithOptions :: (C.IsShelleyBasedEra era) => Options era -> Mockcha
106120
mockchainFailsWithOptions Options{params, coverageRef} action handleError =
107121
try @SomeException (runMockchain0IOWith Wallet.initialUTxOs params action) >>= \case
108122
Right (_, st) -> do
109-
case coverageRef of
110-
Nothing -> pure ()
111-
Just ref -> do
112-
let covData = st ^. coverageData
113-
modifyIORef ref (<> covData)
123+
let covData = st ^. coverageData
124+
appendCovData coverageRef covData
114125
fail "mockchainFailsWithOptions: Did not fail"
115-
Left err -> handleError err
126+
Left err -> do
127+
let covData = tryExtractCovverageData err
128+
appendCovData coverageRef covData
129+
handleError err
116130

117131
{- | Run the 'Mockchain' action as a QuickCheck property, considering all 'MockchainError'
118132
as test failures.
@@ -145,21 +159,16 @@ runMockchainPropWithOptions
145159
-- ^ The mockchain action to run
146160
-> Property
147161
runMockchainPropWithOptions Options{params, coverageRef} utxos =
148-
monadic runFinalPredicate
149-
where
150-
runFinalPredicate
151-
:: MockchainT era Identity Property
152-
-> Property
153-
runFinalPredicate m =
154-
let (prop', state) = runMockchain m params iState
155-
in case coverageRef of
156-
Nothing -> prop'
157-
Just ref -> monadicIO $ do
158-
liftIO $ do
159-
let covData = state ^. coverageData
160-
modifyIORef ref (<> covData)
161-
pure prop'
162-
iState = initialStateFor params utxos
162+
let iState = initialStateFor params utxos
163+
in monadic $ \m ->
164+
let (prop', state) = runMockchain m params iState
165+
in case coverageRef of
166+
Nothing -> prop'
167+
Just ref -> monadicIO $ do
168+
liftIO $ do
169+
let covData = state ^. coverageData
170+
modifyIORef ref (<> covData)
171+
pure prop'
163172

164173
{- | Run the 'Mockchain' action as a QuickCheck property, using the default node params
165174
and initial distribution, and considering all 'MockchainError's as test failures.
@@ -180,3 +189,20 @@ instance (Show e, Testable a) => Testable (TestableErr e a) where
180189
-}
181190
runTestableErr :: forall e m a. (Functor m) => ExceptT e m a -> m (TestableErr e a)
182191
runTestableErr = fmap TestableErr . runExceptT
192+
193+
appendCovData :: Maybe (IORef CoverageData) -> CoverageData -> IO ()
194+
appendCovData Nothing _ = pure ()
195+
appendCovData (Just ref) cd = modifyIORef ref (<> cd)
196+
197+
-- TODO: ugly hack, we have to somehow extract the coverage data from the Exception
198+
-- the problem is that BalanceError is exposed on the upper level, so we cannot pattern match on it directly
199+
tryExtractCovverageData :: SomeException -> CoverageData
200+
tryExtractCovverageData e = mconcat $ map coverageDataFromLogMsg xs
201+
where
202+
xs = fromErrToStringList e
203+
dropO = dropWhile (/= '[')
204+
dropC = dropWhile (/= ']')
205+
tail' [] = []
206+
tail' (_ : xs') = xs'
207+
parse = reverse . dropC . tail' . dropC . reverse . dropO . tail' . dropO
208+
fromErrToStringList err = fromMaybe [] (readMaybe @[String] $ parse $ show err)

0 commit comments

Comments
 (0)