Skip to content

Commit

Permalink
Merge pull request #1504 from aske/aske/plutip-docs-update
Browse files Browse the repository at this point in the history
Update and expand docs and code comments related to Plutip testing
  • Loading branch information
klntsky committed May 31, 2023
2 parents 047f919 + 6627106 commit 6d35376
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 66 deletions.
217 changes: 159 additions & 58 deletions doc/plutip-testing.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions doc/test-utils.md
Expand Up @@ -87,4 +87,6 @@ type ContractCheck a =

Particular values can be constructed with utility functions, as demonstrated in the [ContractTestUtils example](../examples/ContractTestUtils.purs) (see `mkAssertions`).

An example for using checks in the tests is [here](../test/Plutip/Contract/Assert.purs).

All the functions require `Labeled` arguments, that can be constructed with `label` function; or `noLabel`, if descriptive names in error messages are not needed.
10 changes: 10 additions & 0 deletions plutip-server/README.md
@@ -1,3 +1,13 @@
# plutip-server

Plutip-server is a simple HTTP interface for [Plutip](https://github.com/mlabs-haskell/plutip) to start and stop local plutip clusters on demand.

CTL handles communication with Plutip via this server and there's usually no need to use it directly, though `plutip-server` can be useful if you need to control Plutip from a different service (although [`plutip-local-cluster`](https://github.com/mlabs-haskell/plutip/tree/master/local-cluster), which is a CLI program, is probably more convenient for that).

Server exposes two POST endpoints for starting and stopping clusters (only up to one active cluster is allowed at a time).

You can configure cluster parameters like slot length, max tx size, etc. and specify how many wallets to create, including Ada UTxO distribution in each wallet (see [here](../doc/plutip-testing.md#cluster-configuration-options) for how to configure this via CTL).

On a successful cluster startup `plutip-server` responds with a list of keys of the newly created wallets, node configuration, directory with public and private keys for the wallets and a path to the socket of one of the nodes from the cluster (usually it's the one that finished startup the first).

`plutip-server` uses Plutip as a [Haskell library](https://github.com/mlabs-haskell/plutip/tree/master#as-a-library) in Servant API handlers via `startFundedCluster` and `stopCluster` functions.
2 changes: 1 addition & 1 deletion plutip-server/src/Types.hs
Expand Up @@ -95,7 +95,7 @@ data StartClusterRequest = StartClusterRequest
, maxTxSize :: Maybe Natural
-- ^ Set The maxTxSize. If set to Nothing use the default
, raiseExUnitsToMax :: Maybe Bool
-- ^ Raise the execustion units to the maxbound when true.
-- ^ Raise the execution units to the maximum when true.
-- If set to Nothing use the default
}
deriving stock (Show, Eq, Generic)
Expand Down
2 changes: 1 addition & 1 deletion src/Contract/Test/Utils.purs
Expand Up @@ -17,7 +17,7 @@ import Node.Process as Process

foreign import exitCode :: Int -> Effect Unit

-- | Attaches a custom handler on SIGINt to kill the fiber.
-- | Attaches a custom handler on SIGINT to kill the fiber.
-- | see https://github.com/Plutonomicon/cardano-transaction-lib/blob/develop/doc/plutip-testing.md#note-on-sigint
interruptOnSignal :: forall a. Signal -> Fiber a -> Effect Unit
interruptOnSignal signal fiber = Process.onSignal signal do
Expand Down
34 changes: 29 additions & 5 deletions src/Internal/Plutip/Server.purs
Expand Up @@ -143,20 +143,24 @@ withPlutipContractEnv plutipCfg distr cont = do
$ liftEither >=> \{ env, wallets, printLogs } ->
whenError printLogs (cont env wallets)

-- | Run `Contract`s in tests in a single Plutip instance.
-- | NOTE: This uses `MoteT`s bracketting, and thus has the same caveats.
-- | Namely, brackets are run for each of the following groups and tests.
-- | If you wish to only set up Plutip once, ensure all tests are wrapped
-- | in a single group.
-- | Run several `Contract`s in tests in a (single) Plutip environment (plutip-server and cluster, kupo, etc.).
-- | NOTE: This uses `MoteT`s bracketing, and thus has the same caveats.
-- | Namely, brackets are run for each of the top-level groups and tests
-- | inside the bracket.
-- | If you wish to only set up Plutip once, ensure all tests that are passed
-- | to `testPlutipContracts` are wrapped in a single group.
-- | https://github.com/Plutonomicon/cardano-transaction-lib/blob/develop/doc/plutip-testing.md#testing-with-mote
testPlutipContracts
:: PlutipConfig
-> TestPlanM ContractTest Unit
-> TestPlanM (Aff Unit) Unit
testPlutipContracts plutipCfg tp = do
-- Modify tests to pluck out parts of a single combined distribution
ContractTestPlan runContractTestPlan <- lift $ execDistribution tp
runContractTestPlan \distr tests -> do
cleanupRef <- liftEffect $ Ref.new mempty
-- Sets a single Mote bracket at the top level, it will be run for all
-- immediate tests and groups
bracket (startPlutipContractEnv plutipCfg distr cleanupRef)
(runCleanup cleanupRef)
$ flip mapTest tests \test { env, wallets, printLogs, clearLogs } -> do
Expand Down Expand Up @@ -199,9 +203,15 @@ whenError whenErrorAction action = do
-- | distribution. Adapts the tests to pick their distribution out of the
-- | combined distribution.
-- | NOTE: Skipped tests still have their distribution generated.
-- | This is the current method of constructing all the wallets with required distributions
-- | in one go during Plutip startup.
execDistribution :: TestPlanM ContractTest Unit -> Aff ContractTestPlan
execDistribution (MoteT mote) = execWriterT mote <#> go
where
-- Recursively go over the tree of test `Description`s and construct a `ContractTestPlan` callback.
-- When run the `ContractTestPlan` will reconstruct the whole `MoteT` value passed to `execDistribution`
-- via similar writer effects (plus combining distributions) which append test descriptions
-- or wrap them in a group.
go :: Array (Description Aff ContractTest) -> ContractTestPlan
go = flip execState emptyContractTestPlan <<< traverse_ case _ of
Test rm { bracket, label, value: ContractTest runTest } ->
Expand All @@ -215,6 +225,16 @@ execDistribution (MoteT mote) = execWriterT mote <#> go
(censor (pure <<< Group rm <<< { bracket, label, value: _ }))
tests

-- This function is used by `go` for iteratively adding Mote tests (internally Writer monad actions)
-- to the `ContractTestPlan` in the State monad _and_ for combining UTxO distributions used by tests.
-- Given a distribution and tests (a MoteT value) this runs a `ContractTestPlan`, i.e. passes its
-- stored distribution and tests to our handler, and then makes a new `ContractTestPlan`, but this time
-- storing a tuple of stored and passed distributions and also storing a pair of Mote tests, modifying
-- the previously stored tests to use the first distribution, and the passed tests the second distribution
--
-- `go` starts at the top of the test tree and step-by-step constructs a big `ContractTestPlan` which
-- stores distributions of all inner tests tupled together and tests from the original test tree, which
-- know how to get their distribution out of the big tuple.
addTests
:: forall (distr :: Type) (wallets :: Type)
. ContractTestPlanHandler distr wallets (State ContractTestPlan Unit)
Expand All @@ -224,6 +244,9 @@ execDistribution (MoteT mote) = execWriterT mote <#> go
mapTest (_ <<< fst) tests'
mapTest (_ <<< snd) tests

-- Start with an empty plan, which passes an empty distribution
-- and an empty array of test `Description`s to the function that
-- will run tests.
emptyContractTestPlan :: ContractTestPlan
emptyContractTestPlan = ContractTestPlan \h -> h unit (pure unit)

Expand Down Expand Up @@ -514,6 +537,7 @@ startKupo cfg params = do
workdirExists <- FSSync.exists workdir
unless workdirExists (FSSync.mkdir workdir)
childProcess <- spawnKupoProcess workdir
-- here we also set the SIGINT handler for the whole process
sig <- liftEffect $ cleanupOnSigint workdir testClusterDir
pure (childProcess /\ workdir /\ sig)
where
Expand Down
14 changes: 13 additions & 1 deletion src/Internal/Test/ContractTest.purs
Expand Up @@ -15,6 +15,13 @@ import Ctl.Internal.Test.UtxoDistribution (class UtxoDistribution)

-- | Represents a `Contract` test suite that depend on *some* wallet
-- | `UtxoDistribution`.
-- Internally this function takes a two-argument callback from
-- some distribution and a single test to some value and returns that value.
-- Another way of looking at it: pattern-match `ContractTest runTest`,
-- then you can pass a function to `runTest`:
-- `runTest \distr test -> ...` which gets you a result.
-- In practice `runTest` is a closure that stores distribution and a test and
-- passes them to the (\distr test -> ...) function.
newtype ContractTest = ContractTest
( forall (r :: Type)
. ( forall (distr :: Type) (wallets :: Type)
Expand Down Expand Up @@ -42,6 +49,9 @@ type ContractTestHandler distr wallets r =
UtxoDistribution distr wallets => distr -> (wallets -> Contract Unit) -> r

-- | Represents `Contract`s in `TestPlanM` that depend on *some* wallet `UtxoDistribution`
-- Internally this is similar to `ContractTest`, except that
-- now a `runGroupPlan` (a function wrapped in the `ContractTestPlan`) closure
-- stores distribution and effects to construct a test tree.
newtype ContractTestPlan = ContractTestPlan
( forall (r :: Type)
. ( forall (distr :: Type) (wallets :: Type)
Expand All @@ -50,7 +60,9 @@ newtype ContractTestPlan = ContractTestPlan
-> r
)

-- | Same as `ContractTestHandler`, but wrapped in a `TestPaln`.
-- | Same as `ContractTestHandler`, but wrapped in a `TestPlanM`.
-- | It is used for the reconstruction of the `MoteT` value.
-- | See the `Ctl.Internal.Plutip.execDistribution` function for more info.
type ContractTestPlanHandler :: Type -> Type -> Type -> Type
type ContractTestPlanHandler distr wallets r =
UtxoDistribution distr wallets
Expand Down

0 comments on commit 6d35376

Please sign in to comment.