Per ScalaCheck Commands pattern, we maintain two separate emulators:
'''State''' (CommandsState): The abstract model - wraps ImmutableEmulator + contract state S + RNG seed. Used for genCommand, nextState, and preCondition.
'''Sut''' (scalus.cardano.node.Emulator): The system under test - the existing mutable Emulator class. Used for run (actual execution) and postCondition verification.
This separation allows ScalaCheck to detect discrepancies between model and implementation.
==Slot Advancement==
Slot advancement is controlled by overriding ContractStepVariations.slotDelays in the step. This returns slot delays that are included as StepAction.Wait actions alongside transaction submissions.
==JVM-Only==
This class uses Await.result for synchronous execution and is JVM-only. For cross-platform testing, use ScenarioExplorer instead.
==Example Usage==
val auctionBidStep = new ContractStepVariations[AuctionState] {
def extractState(reader: BlockchainReader)(using EC) = ???
def makeBaseTx(reader: BlockchainReader, state: AuctionState)(using EC) = ???
def variations = TxVariations.standard.default(...)
override def slotDelays(state: AuctionState) = Seq(10L, 100L)
}
// Without invariant checking
val emulator = ImmutableEmulator.withAddresses(Seq(Alice.address, Bob.address))
val commands = ContractScalaCheckCommands(emulator, auctionBidStep)()
commands.property().check()
// With invariant checking
val commandsWithInvariants = ContractScalaCheckCommands(emulator, auctionBidStep) {
(reader, state) => Future {
Prop(state.balance >= Coin.zero) && Prop(state.highestBidder.isDefined)
}
}
Type parameters
S
the contract state type
Value parameters
checkInvariants
async function to check invariants after successful transactions
ec
execution context for async operations
initialEmulator
the starting emulator state with funded addresses
step
the contract step variations to test
timeout
timeout for async operations (default: 30 seconds)
The abstract state type. Must be immutable. The State type should model the state of the system under test (SUT). It should only contain details needed for specifying our pre- and post-conditions, and for creating Sut instances.
The abstract state type. Must be immutable. The State type should model the state of the system under test (SUT). It should only contain details needed for specifying our pre- and post-conditions, and for creating Sut instances.
A type representing one instance of the system under test (SUT). The Sut type should be a proxy to the actual system under test and is therefore, by definition, a mutable type. It is used by the Command.run method to execute commands in the system under test. It should be possible to have any number of co-existing instances of the Sut type, as long as canCreateNewSut isn't violated, and each Sut instance should be a proxy to a distinct SUT instance. There should be no dependencies between the Sut instances, as they might be used in parallel by ScalaCheck. Sut instances are created by newSut and destroyed by destroySut. newSut and destroySut might be called at any time by ScalaCheck, as long as canCreateNewSut isn't violated.
A type representing one instance of the system under test (SUT). The Sut type should be a proxy to the actual system under test and is therefore, by definition, a mutable type. It is used by the Command.run method to execute commands in the system under test. It should be possible to have any number of co-existing instances of the Sut type, as long as canCreateNewSut isn't violated, and each Sut instance should be a proxy to a distinct SUT instance. There should be no dependencies between the Sut instances, as they might be used in parallel by ScalaCheck. Sut instances are created by newSut and destroyed by destroySut. newSut and destroySut might be called at any time by ScalaCheck, as long as canCreateNewSut isn't violated.
A command that runs a sequence of other commands. All commands (and their post conditions) are executed even if some command fails. Note that you probably can't use this method if you're testing in parallel (threadCount larger than 1). This is because ScalaCheck regards each command as atomic, even if the command is a sequence of other commands.
A command that runs a sequence of other commands. All commands (and their post conditions) are executed even if some command fails. Note that you probably can't use this method if you're testing in parallel (threadCount larger than 1). This is because ScalaCheck regards each command as atomic, even if the command is a sequence of other commands.
Attributes
Inherited from:
Commands
final def property(threadCount: Int = ..., maxParComb: Int = ...): Prop
A property that can be used to test this Commands specification.
A property that can be used to test this Commands specification.
The parameter threadCount specifies the number of commands that might be executed in parallel. Defaults to one, which means the commands will only be run serially for the same Sut instance. Distinct Sut instances might still receive commands in parallel, if the Test.Parameters.workers parameter is larger than one. Setting threadCount higher than one enables ScalaCheck to reveal thread-related issues in your system under test.
When setting threadCount larger than one, ScalaCheck must evaluate all possible command interleavings (and the end State instances they produce), since parallel command execution is non-deterministic. ScalaCheck tries out all possible end states with the Command.postCondition function of the very last command executed (there is always exactly one command executed after all parallel command executions). If it fails to find an end state that satisfies the postcondition, the test fails. However, the number of possible end states grows rapidly with increasing values of threadCount. Therefore, the lengths of the parallel command sequences are limited so that the number of possible end states don't exceed maxParComb. The default value of maxParComb is 1000000.