scenario-tester: Allow to mangle names used in scenarios before their executions. (#795)

* Allow to mangle names used in scenarios before their executions.

This allows to run a scenario against a long-running server repeatedly and avoid
clashes between runs, since each party is unique (up to the randomness used).

* ScenarioRunner: add test for partyNameMangler
This commit is contained in:
gleber 2019-05-03 15:04:23 +02:00 committed by mergify[bot]
parent debef0ec89
commit b3e2e10897
5 changed files with 74 additions and 12 deletions

View File

@ -6,6 +6,7 @@ load(
"da_scala_binary", "da_scala_binary",
"da_scala_library", "da_scala_library",
"da_scala_test_suite", "da_scala_test_suite",
"lf_scalacopts",
) )
da_scala_library( da_scala_library(
@ -23,3 +24,16 @@ da_scala_library(
"//daml-lf/transaction", "//daml-lf/transaction",
], ],
) )
da_scala_test_suite(
name = "scenario-interpreter_tests",
size = "small",
srcs = glob(["src/test/**/*.scala"]),
scalacopts = lf_scalacopts,
deps = [
":scenario-interpreter",
"//daml-lf/data",
"//daml-lf/interpreter",
"//daml-lf/lfpackage",
],
)

View File

@ -13,14 +13,22 @@ import com.digitalasset.daml.lf.speedy.SError._
import com.digitalasset.daml.lf.speedy.SResult._ import com.digitalasset.daml.lf.speedy.SResult._
import com.digitalasset.daml.lf.transaction.Node.GlobalKey import com.digitalasset.daml.lf.transaction.Node.GlobalKey
//
// Speedy scenario runner that uses the reference ledger.
//
private case class SRunnerException(err: SError) extends RuntimeException(err.toString) private case class SRunnerException(err: SError) extends RuntimeException(err.toString)
final case class ScenarioRunner(machine: Speedy.Machine) { /** Speedy scenario runner that uses the reference ledger.
*
* @constructor Creates a runner using an instance of [[Speedy.Machine]].
* @param partyNameMangler allows to amend party names defined in scenarios,
* before they are executed against a ledger. The function should be idempotent
* in the context of a single {@code ScenarioRunner} life-time, i.e. return the
* same result each time given the same argument. Should return values compatible
* with [[com.digitalasset.daml.lf.data.Ref.SimpleString]].
*/
final case class ScenarioRunner(
machine: Speedy.Machine,
partyNameMangler: (String => String) = identity) {
var ledger: Ledger = Ledger.initialLedger(Time.Timestamp.Epoch) var ledger: Ledger = Ledger.initialLedger(Time.Timestamp.Epoch)
import scala.util.{Try, Success, Failure} import scala.util.{Try, Success, Failure}
def run(): Either[(SError, Ledger), (Double, Int, Ledger)] = def run(): Either[(SError, Ledger), (Double, Int, Ledger)] =
@ -83,11 +91,13 @@ final case class ScenarioRunner(machine: Speedy.Machine) {
private def crash(reason: String) = private def crash(reason: String) =
throw SRunnerException(SErrorCrash(reason)) throw SRunnerException(SErrorCrash(reason))
private def getParty(partyText: String, callback: Party => Unit) = private def getParty(partyText: String, callback: Party => Unit) = {
SimpleString.fromString(partyText) match { val mangledPartyText = partyNameMangler(partyText)
SimpleString.fromString(mangledPartyText) match {
case Right(s) => callback(s) case Right(s) => callback(s)
case _ => throw SRunnerException(ScenarioErrorInvalidPartyName(partyText)) case _ => throw SRunnerException(ScenarioErrorInvalidPartyName(partyText))
} }
}
private def mustFail(tx: Transaction, committer: Party) = { private def mustFail(tx: Transaction, committer: Party) = {
// Update expression evaluated successfully, // Update expression evaluated successfully,

View File

@ -0,0 +1,25 @@
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.daml.lf.speedy
import com.digitalasset.daml.lf.PureCompiledPackages
import com.digitalasset.daml.lf.data.Ref
import com.digitalasset.daml.lf.lfpackage.Ast
import com.digitalasset.daml.lf.lfpackage.Ast.ScenarioGetParty
import org.scalatest._
import org.scalatest.concurrent.ScalaFutures
class ScenarioRunnerTest extends AsyncWordSpec with Matchers with ScalaFutures {
"ScenarioRunner" can {
"mangle party names correctly" in {
val e = Ast.EScenario(ScenarioGetParty(Ast.EPrimLit(Ast.PLText("foo-bar"))))
val m = Speedy.Machine.fromExpr(e, PureCompiledPackages(Map.empty).right.get, true)
val sr = ScenarioRunner(m, (s) => s + "-XXX")
sr.run()
m.ctrl shouldBe Speedy.CtrlValue(SValue.SParty(Ref.Party.assertFromString("foo-bar-XXX")))
}
}
}

View File

@ -27,10 +27,18 @@ import scala.annotation.tailrec
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
import scala.reflect.{ClassTag, classTag} import scala.reflect.{ClassTag, classTag}
/** Scenario tester.
*
* @constructor Creates new tester.
* @param partyNameMangler allows to amend party names defined in scenarios,
* before they are executed against ledger created with {@code createLedger}.
* See {@code ScenarioRunner.partyNameMangler} for details.
*/
class SemanticTester( class SemanticTester(
createLedger: Set[SimpleString] => SemanticTester.GenericLedger, createLedger: Set[SimpleString] => SemanticTester.GenericLedger,
packageToTest: PackageId, packageToTest: PackageId,
packages: Map[PackageId, Package])(implicit ec: ExecutionContext) { packages: Map[PackageId, Package],
partyNameMangler: (String => String) = identity)(implicit ec: ExecutionContext) {
import SemanticTester._ import SemanticTester._
// result ledgers from all scenarios found in packages // result ledgers from all scenarios found in packages
@ -48,7 +56,7 @@ class SemanticTester(
case (name, DValue(_, _, body, isTest)) if isTest => case (name, DValue(_, _, body, isTest)) if isTest =>
val qualifiedName = QualifiedName(module.name, name) val qualifiedName = QualifiedName(module.name, name)
val machine = buildMachine(body) val machine = buildMachine(body)
ScenarioRunner(machine).run() match { ScenarioRunner(machine, partyNameMangler = partyNameMangler).run() match {
case Left((err, _ledger @ _)) => case Left((err, _ledger @ _)) =>
sys.error(s"error running scenario $err in scenario: $qualifiedName") sys.error(s"error running scenario $err in scenario: $qualifiedName")
case Right((_time @ _, _steps @ _, ledger)) => case Right((_time @ _, _steps @ _, ledger)) =>

View File

@ -3,8 +3,8 @@
package com.daml.ledger.api.testtool package com.daml.ledger.api.testtool
import java.io.{File, StringWriter, PrintWriter} import java.io.{File, PrintWriter, StringWriter}
import java.nio.file.{Files, StandardCopyOption, Paths, Path} import java.nio.file.{Files, Path, Paths, StandardCopyOption}
import akka.actor.ActorSystem import akka.actor.ActorSystem
import akka.stream.ActorMaterializer import akka.stream.ActorMaterializer
@ -19,6 +19,7 @@ import com.digitalasset.platform.semantictest.SemanticTestAdapter
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.{Await, ExecutionContext} import scala.concurrent.{Await, ExecutionContext}
import scala.collection.breakOut import scala.collection.breakOut
import scala.util.Random
object LedgerApiTestTool { object LedgerApiTestTool {
@ -61,13 +62,17 @@ object LedgerApiTestTool {
} }
var failed = false var failed = false
val runSuffix = Random.alphanumeric.take(10).mkString
var partyNameMangler = (partyText: String) => s"$partyText-$runSuffix"
try { try {
scenarios.foreach { scenarios.foreach {
case (pkgId, names) => case (pkgId, names) =>
val tester = new SemanticTester( val tester = new SemanticTester(
parties => new SemanticTestAdapter(ledger, packages, parties.map(_.underlyingString)), parties => new SemanticTestAdapter(ledger, packages, parties.map(_.underlyingString)),
pkgId, pkgId,
packages) packages,
partyNameMangler)
names names
.foreach { name => .foreach { name =>
println(s"Testing scenario: $name") println(s"Testing scenario: $name")