@@ -60,7 +60,7 @@ module Convex.TestingInterface (
6060) where
6161
6262import Control.Monad (foldM , forM , unless , when )
63- import Control.Monad.IO.Class (liftIO )
63+ import Control.Monad.IO.Class (MonadIO , liftIO )
6464import Test.HUnit (Assertion )
6565import Test.QuickCheck (Arbitrary (.. ), Gen , Property , counterexample , discard , elements , frequency , oneof , property )
6666import Test.QuickCheck.Monadic (monadicIO , monitor , run )
@@ -118,74 +118,84 @@ correctly.
118118Minimal complete definition: 'Action', 'initialState', 'arbitraryAction', 'nextState', 'perform'
119119-}
120120class (Show state , Eq state ) => TestingInterface state where
121- -- | Actions that can be performed on the contract.
122- -- This is typically a data type with one constructor per contract operation.
121+ {- | Actions that can be performed on the contract.
122+ This is typically a data type with one constructor per contract operation.
123+ -}
123124 data Action state
124125
125126 -- | The initial state of the model, before any actions are performed.
126127 initialState :: state
127128
128- -- | Generate a random action given the current state.
129- -- The generated action should be appropriate for the current state.
129+ {- | Generate a random action given the current state.
130+ The generated action should be appropriate for the current state.
131+ -}
130132 arbitraryAction :: state -> Gen (Action state )
131133
132- -- | Precondition that must hold before an action can be executed.
133- -- Return 'False' to indicate that an action is not valid in the current state.
134- -- Default: all actions are always valid.
134+ {- | Precondition that must hold before an action can be executed.
135+ Return 'False' to indicate that an action is not valid in the current state.
136+ Default: all actions are always valid.
137+ -}
135138 precondition :: state -> Action state -> Bool
136139 precondition _ _ = True
137140
138- -- | Update the model state after an action is performed.
139- -- This should reflect the expected effect of the action on the contract state.
141+ {- | Update the model state after an action is performed.
142+ This should reflect the expected effect of the action on the contract state.
143+ -}
140144 nextState :: state -> Action state -> state
141145
142- -- | Perform the action on the real blockchain (mockchain).
143- -- This should execute the actual transaction(s) that implement the action.
144- -- The current model state is provided to allow access to tracked blockchain state.
146+ {- | Perform the action on the real blockchain (mockchain).
147+ This should execute the actual transaction(s) that implement the action.
148+ The current model state is provided to allow access to tracked blockchain state.
149+ -}
145150 perform :: state -> Action state -> TestingMonadT IO ()
146151
147- -- | Validate that the blockchain state matches the model state.
148- -- Default: no validation (always succeeds).
152+ {- | Validate that the blockchain state matches the model state.
153+ Default: no validation (always succeeds).
154+ -}
149155 validate :: state -> TestingMonadT IO Bool
150156 validate _ = pure True
151157
152- -- | Called after each action to check custom properties.
153- -- Default: no additional checks.
158+ {- | Called after each action to check custom properties.
159+ Default: no additional checks.
160+ -}
154161 monitoring :: state -> Action state -> Property -> Property
155162 monitoring _ _ = id
156163
157- -- | Threat models to run against the last transaction.
158- -- Each threat model will be evaluated against the final transaction
159- -- with the UTxO state captured before that transaction executed.
160- -- Default: no threat models.
164+ {- | Threat models to run against the last transaction.
165+ Each threat model will be evaluated against the final transaction
166+ with the UTxO state captured before that transaction executed.
167+ Default: no threat models.
168+ -}
161169 threatModels :: [ThreatModel () ]
162170 threatModels = []
163171
164- -- | Threat models that are expected to find vulnerabilities.
165- -- These are run like 'threatModels' but with inverted pass/fail semantics:
166- --
167- -- * OK when a vulnerability IS detected
168- -- * FAIL when a vulnerability is NOT detected
169- --
170- -- Output is quiet — no verbose transaction dumps.
171- -- Default: empty, backward compatible.
172+ {- | Threat models that are expected to find vulnerabilities.
173+ These are run like 'threatModels' but with inverted pass/fail semantics:
174+
175+ * OK when a vulnerability IS detected
176+ * FAIL when a vulnerability is NOT detected
177+
178+ Output is quiet — no verbose transaction dumps.
179+ Default: empty, backward compatible.
180+ -}
172181 expectedVulnerabilities :: [ThreatModel () ]
173182 expectedVulnerabilities = []
174183
175- -- | Whether to discard (skip) test cases where the invalid action fails due to
176- -- a user-level error (e.g., off-chain balancing failure) rather than an
177- -- on-chain validator rejection during negative testing.
178- --
179- -- When 'True', negative tests that throw user exceptions are discarded
180- -- (via QuickCheck's 'discard'), so only on-chain rejections count as
181- -- successful negative tests.
182- --
183- -- When 'False' (the default), user exceptions also cause the test case
184- -- to be discarded — meaning both off-chain and on-chain failures are
185- -- treated the same way.
186- --
187- -- Override this in your 'TestingInterface' instance if you need finer
188- -- control over which failure modes are accepted in negative testing.
184+ {- | Whether to discard (skip) test cases where the invalid action fails due to
185+ a user-level error (e.g., off-chain balancing failure) rather than an
186+ on-chain validator rejection during negative testing.
187+
188+ When 'True', negative tests that throw user exceptions are discarded
189+ (via QuickCheck's 'discard'), so only on-chain rejections count as
190+ successful negative tests.
191+
192+ When 'False' (the default), user exceptions also cause the test case
193+ to be discarded — meaning both off-chain and on-chain failures are
194+ treated the same way.
195+
196+ Override this in your 'TestingInterface' instance if you need finer
197+ control over which failure modes are accepted in negative testing.
198+ -}
189199 discarNegativeTestForUserExceptions :: Bool
190200 discarNegativeTestForUserExceptions = False
191201
@@ -204,11 +214,14 @@ newtype TestingMonadT m a = TestingMonad
204214 , C. MonadError (BalanceTxError C. ConwayEra )
205215 , C. MonadIO
206216 , MonadLog
207- , MonadFail
208217 , MonadBlockchain C. ConwayEra
209218 , MonadMockchain C. ConwayEra
210219 )
211220
221+ -- Let the TestingMonad fail in IO
222+ instance (MonadIO m ) => MonadFail (TestingMonadT m ) where
223+ fail s = liftIO $ fail s
224+
212225-- | Opaque wrapper for model state
213226newtype ModelState state = ModelState { unModelState :: state }
214227 deriving (Eq , Show )
@@ -277,8 +290,9 @@ data RunOptions = RunOptions
277290 -- ^ Maximum number of actions to generate
278291 , mcOptions :: Options C. ConwayEra
279292 , disableNegativeTesting :: Maybe String
280- -- ^ If @Just reason@, negative tests are skipped (shown as IGNORED) with the given reason.
281- -- If @Nothing@, negative tests run normally. Default: @Nothing@.
293+ {- ^ If @Just reason@, negative tests are skipped (shown as IGNORED) with the given reason.
294+ If @Nothing@, negative tests run normally. Default: @Nothing@.
295+ -}
282296 }
283297
284298defaultRunOptions :: RunOptions
@@ -619,11 +633,11 @@ expectedVulnTestCase getTmResultsRef idx tm =
619633
620634-- | Execute a single action and update the model state
621635runAction
622- :: (TestingInterface state , Show (Action state ))
636+ :: (TestingInterface state , Show (Action state ), MonadIO m )
623637 => RunOptions
624638 -> state
625639 -> Action state
626- -> TestingMonadT IO state
640+ -> TestingMonadT m state
627641runAction opts modelState action = do
628642 when (verbose opts) $
629643 liftIO $
@@ -654,11 +668,13 @@ Use with 'withCoverage' to set up coverage tracking for your test suite.
654668-}
655669data CoverageConfig = CoverageConfig
656670 { coverageIndices :: [CoverageIndex ]
657- -- ^ Coverage indices from compiled scripts (obtained via @'PlutusTx.Code.getCovIdx'@).
658- -- Multiple indices are combined with @'<>'@.
671+ {- ^ Coverage indices from compiled scripts (obtained via @'PlutusTx.Code.getCovIdx'@).
672+ Multiple indices are combined with @'<>'@.
673+ -}
659674 , coverageReport :: CoverageReport -> IO ()
660- -- ^ Action to perform with the final coverage report.
661- -- Use 'printCoverageReport', 'writeCoverageReport', or 'silentCoverageReport'.
675+ {- ^ Action to perform with the final coverage report.
676+ Use 'printCoverageReport', 'writeCoverageReport', or 'silentCoverageReport'.
677+ -}
662678 }
663679
664680-- | Print a coverage report to stdout using prettyprinter.
0 commit comments