LF: exhaustive test for CommandPreprocessor (#10914)

While implementing the tests I found a bug.

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
Remy 2021-09-17 13:41:29 +02:00 committed by GitHub
parent 61d214e451
commit 906368d7e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 142 additions and 14 deletions

View File

@ -119,6 +119,7 @@ private[lf] final class CommandPreprocessor(
choiceArgument, choiceArgument,
) )
case command.FetchCommand(templateId, coid) => case command.FetchCommand(templateId, coid) =>
handleLookup(interface.lookupTemplate(templateId))
val cid = valueTranslator.unsafeTranslateCid(coid) val cid = valueTranslator.unsafeTranslateCid(coid)
speedy.Command.Fetch(templateId, cid) speedy.Command.Fetch(templateId, cid)
case command.FetchByKeyCommand(templateId, key) => case command.FetchByKeyCommand(templateId, key) =>

View File

@ -7,14 +7,21 @@ package preprocessing
import com.daml.lf.command._ import com.daml.lf.command._
import com.daml.lf.data._ import com.daml.lf.data._
import com.daml.lf.transaction.test.TransactionBuilder.newCid
import com.daml.lf.value.Value._ import com.daml.lf.value.Value._
import org.scalatest.matchers.dsl.ResultOfATypeInvocation
import org.scalatest.Inside
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
import org.scalatest.prop.TableDrivenPropertyChecks import org.scalatest.prop.TableDrivenPropertyChecks
import org.scalatest.wordspec.AnyWordSpec import org.scalatest.wordspec.AnyWordSpec
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
class CommandPreprocessorSpec extends AnyWordSpec with Matchers with TableDrivenPropertyChecks { class CommandPreprocessorSpec
extends AnyWordSpec
with Matchers
with TableDrivenPropertyChecks
with Inside {
import com.daml.lf.testing.parser.Implicits._ import com.daml.lf.testing.parser.Implicits._
import com.daml.lf.transaction.test.TransactionBuilder.Implicits.{defaultPackageId => _, _} import com.daml.lf.transaction.test.TransactionBuilder.Implicits.{defaultPackageId => _, _}
@ -24,25 +31,38 @@ class CommandPreprocessorSpec extends AnyWordSpec with Matchers with TableDriven
p""" p"""
module Mod { module Mod {
record @serializable Record = { field : Int64 }; record @serializable Box a = { content: a };
record @serializable RecordRef = { owner: Party, cid: (ContractId Mod:Record) }; record @serializable Record = { owners: List Party, data : Int64 };
val @noPartyLiterals toParties: Mod:RecordRef -> List Party = template (this : Record) = {
\ (ref: Mod:RecordRef) -> Cons @Party [Mod:RecordRef {owner} ref] (Nil @Party); precondition True,
signatories Mod:Record {owners} this,
observers Mod:Record {owners} this,
agreement "Agreement",
choices {
choice Transfer (self) (box: Mod:Box (List Party)) : ContractId Mod:Record,
controllers Mod:Record {owners} this,
observers Nil @Party
to create @Mod:Record Mod:Record { owners = Mod:Box @(List Party) {content} box, data = Mod:Record {data} this }
},
key @(List Party) (Mod:Record {owners} this) (\ (parties: List Party) -> parties)
};
record @serializable RecordRef = { owners: List Party, cid: (ContractId Mod:Record) };
template (this : RecordRef) = { template (this : RecordRef) = {
precondition True, precondition True,
signatories Mod:toParties this, signatories Mod:RecordRef {owners} this,
observers Mod:toParties this, observers Mod:RecordRef {owners} this,
agreement "Agreement", agreement "Agreement",
choices { choices {
choice Change (self) (newCid: ContractId Mod:Record) : ContractId Mod:RecordRef, choice Change (self) (newCid: ContractId Mod:Record) : ContractId Mod:RecordRef,
controllers Mod:toParties this, controllers Mod:RecordRef {owners} this,
observers Nil @Party observers Nil @Party
to create @Mod:RecordRef Mod:RecordRef { owner = Mod:RecordRef {owner} this, cid = newCid } to create @Mod:RecordRef Mod:RecordRef { owners = Mod:RecordRef {owners} this, cid = newCid }
}, },
key @Party (Mod:RecordRef {owner} this) (\ (p: Party) -> Cons @Party [p] (Nil @Party)) key @(List Party) (Mod:RecordRef {owners} this) (\ (parties: List Party) -> parties)
}; };
} }
@ -51,13 +71,120 @@ class CommandPreprocessorSpec extends AnyWordSpec with Matchers with TableDriven
private[this] val compiledPackage = ConcurrentCompiledPackages() private[this] val compiledPackage = ConcurrentCompiledPackages()
assert(compiledPackage.addPackage(defaultPackageId, pkg) == ResultDone.Unit) assert(compiledPackage.addPackage(defaultPackageId, pkg) == ResultDone.Unit)
val valueParties = ValueList(FrontStack(ValueParty("Alice")))
"preprocessCommand" should { "preprocessCommand" should {
val defaultPreprocessor = new CommandPreprocessor(compiledPackage.interface, true, false)
"reject improperly typed commands" in {
val validCreate = CreateCommand(
"Mod:Record",
ValueRecord("", ImmArray("owners" -> valueParties, "data" -> ValueInt64(42))),
)
val validExe = ExerciseCommand(
"Mod:Record",
newCid,
"Transfer",
ValueRecord("", ImmArray("content" -> ValueList(FrontStack(ValueParty("Clara"))))),
)
val validExeByKey = ExerciseByKeyCommand(
"Mod:Record",
valueParties,
"Transfer",
ValueRecord("", ImmArray("content" -> ValueList(FrontStack(ValueParty("Clara"))))),
)
val validCreateAndExe = CreateAndExerciseCommand(
"Mod:Record",
ValueRecord("", ImmArray("owners" -> valueParties, "data" -> ValueInt64(42))),
"Transfer",
ValueRecord("", ImmArray("content" -> ValueList(FrontStack(ValueParty("Clara"))))),
)
val validFetch = FetchCommand(
"Mod:Record",
newCid,
)
val validFetchByKey = FetchByKeyCommand(
"Mod:Record",
valueParties,
)
val validLookup = LookupByKeyCommand(
"Mod:Record",
valueParties,
)
val noErrorTestCases = Table[Command](
"command",
validCreate,
validExe,
validExeByKey,
validCreateAndExe,
validFetch,
validFetchByKey,
validLookup,
)
val errorTestCases = Table[Command, ResultOfATypeInvocation[_]](
("command", "error"),
validCreate.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
validCreate.copy(argument = ValueRecord("", ImmArray("content" -> ValueInt64(42)))) ->
a[Error.Preprocessing.TypeMismatch],
validExe.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
validExe.copy(choiceId = "Undefined") ->
a[Error.Preprocessing.Lookup],
validExe.copy(argument = ValueRecord("", ImmArray("content" -> ValueInt64(42)))) ->
a[Error.Preprocessing.TypeMismatch],
validExeByKey.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
validExeByKey.copy(contractKey = ValueList(FrontStack(ValueInt64(42)))) ->
a[Error.Preprocessing.TypeMismatch],
validExeByKey.copy(choiceId = "Undefined") ->
a[Error.Preprocessing.Lookup],
validExeByKey.copy(argument = ValueRecord("", ImmArray("content" -> ValueInt64(42)))) ->
a[Error.Preprocessing.TypeMismatch],
validCreateAndExe.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
validCreateAndExe.copy(createArgument =
ValueRecord("", ImmArray("content" -> ValueInt64(42)))
) ->
a[Error.Preprocessing.TypeMismatch],
validCreateAndExe.copy(choiceId = "Undefined") ->
a[Error.Preprocessing.Lookup],
validCreateAndExe.copy(choiceArgument =
ValueRecord("", ImmArray("content" -> ValueInt64(42)))
) ->
a[Error.Preprocessing.TypeMismatch],
validFetch.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
validFetchByKey.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
validFetchByKey.copy(key = ValueList(FrontStack(ValueInt64(42)))) ->
a[Error.Preprocessing.TypeMismatch],
validLookup.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
validLookup.copy(contractKey = ValueList(FrontStack(ValueInt64(42)))) ->
a[Error.Preprocessing.TypeMismatch],
)
forEvery(noErrorTestCases) { command =>
Try(defaultPreprocessor.unsafePreprocessCommand(command)) shouldBe a[Success[_]]
}
forEvery(errorTestCases) { (command, typ) =>
inside(Try(defaultPreprocessor.unsafePreprocessCommand(command))) {
case Failure(error: Error.Preprocessing.Error) =>
error shouldBe typ
}
}
}
def contractIdTestCases(culpritCid: ContractId, innocentCid: ContractId) = Table[ApiCommand]( def contractIdTestCases(culpritCid: ContractId, innocentCid: ContractId) = Table[ApiCommand](
"command", "command",
CreateCommand( CreateCommand(
"Mod:RecordRef", "Mod:RecordRef",
ValueRecord("", ImmArray("" -> ValueParty("Alice"), "" -> ValueContractId(culpritCid))), ValueRecord("", ImmArray("" -> valueParties, "" -> ValueContractId(culpritCid))),
), ),
ExerciseCommand( ExerciseCommand(
"Mod:RecordRef", "Mod:RecordRef",
@ -73,19 +200,19 @@ class CommandPreprocessorSpec extends AnyWordSpec with Matchers with TableDriven
), ),
CreateAndExerciseCommand( CreateAndExerciseCommand(
"Mod:RecordRef", "Mod:RecordRef",
ValueRecord("", ImmArray("" -> ValueParty("Alice"), "" -> ValueContractId(culpritCid))), ValueRecord("", ImmArray("" -> valueParties, "" -> ValueContractId(culpritCid))),
"Change", "Change",
ValueContractId(innocentCid), ValueContractId(innocentCid),
), ),
CreateAndExerciseCommand( CreateAndExerciseCommand(
"Mod:RecordRef", "Mod:RecordRef",
ValueRecord("", ImmArray("" -> ValueParty("Alice"), "" -> ValueContractId(innocentCid))), ValueRecord("", ImmArray("" -> valueParties, "" -> ValueContractId(innocentCid))),
"Change", "Change",
ValueContractId(culpritCid), ValueContractId(culpritCid),
), ),
ExerciseByKeyCommand( ExerciseByKeyCommand(
"Mod:RecordRef", "Mod:RecordRef",
ValueParty("Alice"), valueParties,
"Change", "Change",
ValueContractId(culpritCid), ValueContractId(culpritCid),
), ),