agora/agora-testlib/Test/Specification.hs
Seungheon Oh d2018afd4d Use liqwid-script-export
commit ec70bfd539fe2e27fd48f5f76395400287ac72d7
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Tue Oct 18 18:58:59 2022 -0500

    use LSE

commit 25fff9b3ad1f2dde4cd7cf36977530b06a87d23c
Merge: 01cd3aa 1821dd6
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Tue Oct 18 18:17:45 2022 -0500

    Merge branch 'staging' into seungheonoh/ply

commit 01cd3aa7a235e6fe6658246ca1026fa26dc71a83
Author: Hongrui Fang <chfanghr@gmail.com>
Date:   Tue Oct 11 12:02:03 2022 +0800

    update benchmark

commit a8513244892ce33cfdc9edf8cd501c4985ae8008
Author: Hongrui Fang <chfanghr@gmail.com>
Date:   Tue Oct 11 11:59:22 2022 +0800

    fix tests

commit 20ca40823485c2e2f78253643cf4453ac7b7ddd5
Author: Hongrui Fang <chfanghr@gmail.com>
Date:   Tue Oct 11 11:57:37 2022 +0800

    better import

commit a19fe49424210891bd03db71e4083fc1e0edfd98
Author: Hongrui Fang <chfanghr@gmail.com>
Date:   Tue Oct 11 11:08:20 2022 +0800

    update flake inputs

commit c93b21f1f9441e5c6f54525bf7c6a54757ec36cc
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Mon Oct 10 12:54:12 2022 -0500

    tried to make tests pass

commit 1046ae1237299a33c58b48661bdb6d325a22147e
Merge: 2bf4e36 363bd83
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Mon Oct 10 12:18:48 2022 -0500

    Merge branch 'staging' into seungheonoh/ply

commit 2bf4e3627c1b229f58078695082da85c80efd560
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Mon Oct 10 10:48:36 2022 -0500

    remove junkpile

commit a1dbc9ad9e531fe0d0a0480c4aef9cf9ffa90f1d
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Mon Oct 10 10:47:25 2022 -0500

    versions

commit 4542a06ac733858297d3a48c53368fad19dedc43
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Thu Oct 6 22:57:48 2022 -0500

    script exporting interface

commit 6bd8c1a1d57e4bf9dc25c3068a9c8eae6bf6a19d
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Thu Oct 6 22:58:41 2022 -0500

    fixed tests

commit d3ce2cf95633d336f3e621833677bd5bf10ee2c8
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Sun Oct 2 00:55:18 2022 -0500

    fixed tests

commit 1ae64c9f692652b77b0506013853b2ba44267c65
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Sat Oct 1 13:28:20 2022 -0500

    linker

commit db88cb75c7b74843141ad8ab4e6522b66d0dcfbc
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Sat Oct 1 01:03:50 2022 -0500

    exporting scripts

commit 6389fce28e885a8a7f8669629c266f59c0edb51f
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Sat Oct 1 00:51:49 2022 -0500

    made scripts parameterized on the script level

commit aea1e518a8890550bdebd0e5251da11d915c53a9
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Wed Sep 28 19:53:29 2022 -0500

    Use `TypedScriptEnvelope` for `Agora.Bootstrap`
2022-10-18 19:02:10 -05:00

320 lines
8.3 KiB
Haskell

{- |
Module : Test.Specification
Maintainer : seungheon.ooh@gmail.com
Description: Helpers to build Specification for testing and bench-marking
Constructors for building a specification for Plutarch scripts:
- 'policySucceedsWith': checks that a minting policy succeeds.
- 'policyFailsWith': checks that a minting policy fails.
- 'validatorSucceedsWith': checks that validator succeeds.
- 'validatorFailsWith': checks that validator fails.
- 'effectSucceedsWith': checks that effect succeeds.
- 'effectFailsWith': checks that effect fails.
- 'scriptSucceeds': checks that an arbitrary script does not
`perror`.
- 'scriptFails': checks that an arbitrary script `perror`s out.
-}
module Test.Specification (
-- * Structures
Specification (..),
SpecificationExpectation (..),
SpecificationTree (..),
-- * Spec helpers
group,
getSpecification,
getSpecificationTree,
-- * Spec builders
scriptSucceeds,
scriptFails,
policySucceedsWith,
policyFailsWith,
validatorSucceedsWith,
validatorFailsWith,
effectSucceedsWith,
effectFailsWith,
testValidator,
testPolicy,
-- * Converters
toTestTree,
) where
import Control.Composition ((.**), (.***))
import Data.Coerce (coerce)
import Data.Text qualified as Text
import Plutarch.Evaluate (evalScript)
import PlutusLedgerApi.V1.Scripts (
Context (Context),
applyMintingPolicyScript,
applyValidator,
)
import PlutusLedgerApi.V2 (
Datum (..),
MintingPolicy,
Redeemer (Redeemer),
Script,
ScriptContext,
ToData (toBuiltinData),
Validator,
)
import PlutusTx.IsData qualified as PlutusTx (ToData)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertFailure, testCase)
{- | Expectations upon execution of script
@Success@ indicates a successful execution.
@Failure@ inidcates a faulty execution.
@FailureWith@ indicates a faulty execution but with expected reason for failure.
-}
data SpecificationExpectation
= Success
| Failure
| FailureWith String
deriving stock (Show)
{- | Unit of specification. @Specification@ holds name, expectation, and
script to be tested or executed later on.
-}
data Specification = Specification
{ sName :: String
, sExpectation :: SpecificationExpectation
, sScript :: Script
}
deriving stock (Show)
-- | Tree-structure to group alike specifications--modeled after @TestTree@ from tasty.
data SpecificationTree
= Terminal Specification
| Group String [SpecificationTree]
deriving stock (Show)
{- | Checks if given name exists in @SpecificationTree@ as either
group name or specification name.
-}
exists :: String -> SpecificationTree -> Bool
exists s (Terminal (Specification name _ _)) = s == name
exists s (Group name st) = or (exists s <$> st) || s == name
-- | Groups alike @SpecificationTree@s into a bigger tree.
group :: String -> [SpecificationTree] -> SpecificationTree
group name st
| or $ exists name <$> st = error $ "Name already exists: " <> name
| otherwise = Group name st
-- | Query specific @Specification@ from a tree.
getSpecification :: String -> SpecificationTree -> [Specification]
getSpecification name (Terminal spec@(Specification sn _ _))
| name == sn = [spec]
| otherwise = []
getSpecification name (Group _ st) = mconcat $ getSpecification name <$> st
-- | Query specific @SpecificationTree@ from a tree.
getSpecificationTree :: String -> SpecificationTree -> [SpecificationTree]
getSpecificationTree name specTree@(Group gn st)
| gn == name = [specTree]
| otherwise = mconcat $ getSpecificationTree name <$> st
getSpecificationTree _ _ = []
-- | Convert @SpecificationTree@ into @TestTree@ to be used as a unit test.
toTestTree :: SpecificationTree -> TestTree
toTestTree (Group name st) = testGroup name $ toTestTree <$> st
toTestTree (Terminal (Specification name expectation script)) =
testCase name $ do
case expectation of
Success -> onSuccess
Failure -> onFailure
FailureWith s -> onFailureWith s
where
beautifyTraces =
Text.unpack
. Text.intercalate "\n"
. map (" " <>)
(res, _budget, traces) = evalScript script
ts = " Traces:\n" <> beautifyTraces traces
onSuccess = case res of
Left e ->
assertFailure $
show e <> ts
_ -> pure ()
onFailure = case res of
Right v ->
assertFailure $
"Expected failure, but succeeded. "
<> show v
<> ts
_ -> pure ()
onFailureWith _s = case res of -- TODO: check Trace for this
Right v ->
assertFailure $
"Expected failure, but succeeded. "
<> show v
<> ts
_ -> pure ()
-- | Check that an arbitrary script doesn't error when evaluated, given a name.
scriptSucceeds :: String -> Script -> SpecificationTree
scriptSucceeds name script = Terminal $ Specification name Success script
-- | Check that an arbitrary script **does** error when evaluated, given a name.
scriptFails :: String -> Script -> SpecificationTree
scriptFails name script = Terminal $ Specification name Failure script
mkContext :: ScriptContext -> Context
mkContext = Context . toBuiltinData
mkRedeemer ::
forall redeemer.
(PlutusTx.ToData redeemer) =>
redeemer ->
Redeemer
mkRedeemer = Redeemer . toBuiltinData
mkDatum ::
forall datum.
(PlutusTx.ToData datum) =>
datum ->
Datum
mkDatum = Datum . toBuiltinData
applyMintingPolicy' ::
(PlutusTx.ToData redeemer) =>
MintingPolicy ->
redeemer ->
ScriptContext ->
Script
applyMintingPolicy' policy redeemer scriptContext =
applyMintingPolicyScript
(mkContext scriptContext)
policy
(mkRedeemer redeemer)
applyValidator' ::
( PlutusTx.ToData datum
, PlutusTx.ToData redeemer
) =>
Validator ->
datum ->
redeemer ->
ScriptContext ->
Script
applyValidator' validator datum redeemer scriptContext =
applyValidator
(mkContext scriptContext)
validator
(mkDatum datum)
(mkRedeemer redeemer)
-- | Check that a policy script succeeds, given a name and arguments.
policySucceedsWith ::
(PlutusTx.ToData redeemer) =>
String ->
MintingPolicy ->
redeemer ->
ScriptContext ->
SpecificationTree
policySucceedsWith tag =
scriptSucceeds tag .** applyMintingPolicy'
-- | Check that a policy script fails, given a name and arguments.
policyFailsWith ::
(PlutusTx.ToData redeemer) =>
String ->
MintingPolicy ->
redeemer ->
ScriptContext ->
SpecificationTree
policyFailsWith tag =
scriptFails tag .** applyMintingPolicy'
-- | Check that a validator script succeeds, given a name and arguments.
validatorSucceedsWith ::
( PlutusTx.ToData datum
, PlutusTx.ToData redeemer
) =>
String ->
Validator ->
datum ->
redeemer ->
ScriptContext ->
SpecificationTree
validatorSucceedsWith tag =
scriptSucceeds tag .*** applyValidator'
-- | Check that a validator script fails, given a name and arguments.
validatorFailsWith ::
( PlutusTx.ToData datum
, PlutusTx.ToData redeemer
) =>
String ->
Validator ->
datum ->
redeemer ->
ScriptContext ->
SpecificationTree
validatorFailsWith tag =
scriptFails tag .*** applyValidator'
-- | Check that an effect succeeds, given a name and argument.
effectSucceedsWith ::
( PlutusTx.ToData datum
) =>
String ->
Validator ->
datum ->
ScriptContext ->
SpecificationTree
effectSucceedsWith tag eff datum = validatorSucceedsWith tag (coerce eff) datum ()
-- | Check that an effect fails, given a name and argument.
effectFailsWith ::
( PlutusTx.ToData datum
) =>
String ->
Validator ->
datum ->
ScriptContext ->
SpecificationTree
effectFailsWith tag eff datum = validatorFailsWith tag (coerce eff) datum ()
-- | Test a validator, given the expectation as a boolean value.
testValidator ::
forall datum redeemer.
(PlutusTx.ToData datum, PlutusTx.ToData redeemer) =>
-- | Is this test case expected to succeed?
Bool ->
String ->
Validator ->
datum ->
redeemer ->
ScriptContext ->
SpecificationTree
testValidator isValid =
if isValid
then validatorSucceedsWith
else validatorFailsWith
-- | Test a policy, given the expectation as a boolean value.
testPolicy ::
forall redeemer.
(PlutusTx.ToData redeemer) =>
-- | Is this test case expected to succeed?
Bool ->
String ->
MintingPolicy ->
redeemer ->
ScriptContext ->
SpecificationTree
testPolicy isValid =
if isValid
then policySucceedsWith
else policyFailsWith