Skip to content

Commit

Permalink
Add submitMustFail to DAML script (digital-asset#3510)
Browse files Browse the repository at this point in the history
This is useful for testing purposes and matches the function provided
in scenarios. We probably want to expose a variant of submitMustFail
that only succeeds if the SubmitFailure matches a specific condition
but I need to think a bit more about which API I want for that.
  • Loading branch information
cocreature authored Nov 18, 2019
1 parent 5458053 commit d776489
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 21 deletions.
14 changes: 12 additions & 2 deletions daml-script/daml/Daml/Script.daml
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,21 @@ data AllocateParty a = AllocateParty
allocateParty : Text -> Script Party
allocateParty displayName = Script $ Free (AllocParty $ AllocateParty displayName pure)

data SubmitCmd a = SubmitCmd { party : Party, commands : Commands a }
data SubmitFailure = SubmitFailure
{ status : Int
, description : Text
}

data SubmitCmd a = SubmitCmd { party : Party, commands : Commands a, handleFailure : SubmitFailure -> a }
deriving Functor

submit : Party -> Commands a -> Script a
submit p cmds = Script $ Free (fmap pure $ Submit $ SubmitCmd p cmds)
submit p cmds = Script $ Free (fmap pure $ Submit $ SubmitCmd p cmds fail)
where fail (SubmitFailure status msg) = error $ "Submit failed with code " <> show status <> ": " <> msg

submitMustFail : Party -> Commands a -> Script ()
submitMustFail p cmds = Script $ Free (fmap pure $ Submit $ SubmitCmd p (fail <$> cmds) (const ()))
where fail _ = error "Expected submit to fail but it succeeded"

newtype Script a = Script (Free ScriptF a)
deriving (Functor, Applicative, Action)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package com.digitalasset.daml.lf.engine.script

import io.grpc.StatusRuntimeException
import java.util
import scala.annotation.tailrec
import scala.collection.JavaConverters._
Expand Down Expand Up @@ -300,6 +301,18 @@ object Converter {
} yield record(anyTemplateTyCon, ("getAnyTemplate", SAny(TTyCon(tyCon), argSValue)))
}

def fromStatusException(
scriptPackageId: PackageId,
ex: StatusRuntimeException): Either[String, SValue] = {
val status = ex.getStatus
Right(
record(
Identifier(scriptPackageId, QualifiedName.assertFromString("Daml.Script:SubmitFailure")),
("status", SInt64(status.getCode.value.asInstanceOf[Long])),
("description", SText(status.getDescription))
))
}

// TODO This duplicates the type_ method from com.digitalasset.daml.lf.iface.reader.InterfaceReader
// since it is not exposed. We should find some place to expose that method or a way to
// avoid duplicating this logic.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package com.digitalasset.daml.lf.engine.script
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Sink
import com.typesafe.scalalogging.StrictLogging
import io.grpc.StatusRuntimeException
import java.util.UUID
import scala.concurrent.{ExecutionContext, Future}
import scalaz.{\/-}
Expand Down Expand Up @@ -181,6 +182,7 @@ class Runner(
machine.step() match {
case SResultContinue => ()
case SResultError(err) => {
logger.error(Pretty.prettyError(err, machine.ptx).render(80))
throw err
}
case res => {
Expand Down Expand Up @@ -216,7 +218,7 @@ class Runner(
v match {
case SVariant(_, "Submit", v) => {
v match {
case SRecord(_, _, vals) if vals.size == 2 => {
case SRecord(_, _, vals) if vals.size == 3 => {
val freeAp = vals.get(1) match {
// Unwrap Commands newtype
case SRecord(_, _, vals) if vals.size == 1 => vals.get(0)
Expand All @@ -228,23 +230,37 @@ class Runner(
commands <- Converter.toCommands(compiledPackages, freeAp)
} yield toSubmitRequest(client.ledgerId, party, commands)
val request = requestOrErr.fold(s => throw new ConverterException(s), identity)
val f = client.commandServiceClient.submitAndWaitForTransactionTree(request)
f.flatMap(transactionTree => {
val events =
transactionTree.getTransaction.rootEventIds.map(evId =>
transactionTree.getTransaction.eventsById(evId))
val filled =
Converter.fillCommandResults(
compiledPackages,
lookupChoiceTy,
valueTranslator,
freeAp,
events) match {
case Left(s) => throw new ConverterException(s)
case Right(r) => r
}
machine.ctrl = Speedy.CtrlExpr(filled)
go()
val f =
client.commandServiceClient
.submitAndWaitForTransactionTree(request)
.map(Right(_))
.recover({ case s: StatusRuntimeException => Left(s) })
f.flatMap({
case Right(transactionTree) => {
val events =
transactionTree.getTransaction.rootEventIds.map(evId =>
transactionTree.getTransaction.eventsById(evId))
val filled =
Converter.fillCommandResults(
compiledPackages,
lookupChoiceTy,
valueTranslator,
freeAp,
events) match {
case Left(s) => throw new ConverterException(s)
case Right(r) => r
}
machine.ctrl = Speedy.CtrlExpr(filled)
go()
}
case Left(statusEx) => {
val res = Converter
.fromStatusException(scriptPackageId, statusEx)
.fold(s => throw new ConverterException(s), identity)
machine.ctrl =
Speedy.CtrlExpr(SEApp(SEValue(vals.get(2)), Array(SEValue(res))))
go()
}
})
}
case _ => throw new RuntimeException(s"Expected record with 2 fields but got $v")
Expand Down
13 changes: 12 additions & 1 deletion daml-script/test/daml/ScriptTest.daml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
daml 1.2
module ScriptTest where

import Prelude hiding (getParty, submit)
import Prelude hiding (getParty, submit, submitMustFail)

import Daml.Script

Expand Down Expand Up @@ -35,6 +35,10 @@ template C
where
signatory p

choice ShouldFail : ()
controller p
do assert False

template NumericTpl
with
p : Party
Expand Down Expand Up @@ -77,3 +81,10 @@ test1 = do

test2 : C -> Script Int
test2 (C _ i) = pure i

test3 : Script ()
test3 = do
alice <- allocateParty "alice"
cid <- submit alice $ createCmd (C alice 42)
submitMustFail alice $ exerciseCmd cid ShouldFail
pure ()
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,23 @@ case class Test2(dar: Dar[(PackageId, Package)], runner: TestRunner) {
}
}

case class Test3(dar: Dar[(PackageId, Package)], runner: TestRunner) {
val scriptId = Identifier(dar.main._1, QualifiedName.assertFromString("ScriptTest:test3"))
def runTests() = {
runner.genericTest(
"test3",
dar,
scriptId,
None,
result =>
result match {
case SUnit => Right(())
case v => Left(s"Expected SUnit but got $v")
}
)
}
}

object TestMain {

private val configParser = new scopt.OptionParser[Config]("daml_script_test") {
Expand Down Expand Up @@ -262,6 +279,7 @@ object TestMain {
Test0(dar, runner).runTests()
Test1(dar, runner).runTests()
Test2(dar, runner).runTests()
Test3(dar, runner).runTests()
}
}
}

0 comments on commit d776489

Please sign in to comment.