From b3e2e108974d5a70ae87df5809f7ee9e5244ec3e Mon Sep 17 00:00:00 2001 From: gleber <34243031+gleber-da@users.noreply.github.com> Date: Fri, 3 May 2019 15:04:23 +0200 Subject: [PATCH] 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 --- daml-lf/scenario-interpreter/BUILD.bazel | 14 +++++++++++ .../daml/lf/speedy/ScenarioRunner.scala | 24 ++++++++++++------ .../daml/lf/speedy/ScenarioRunnerTest.scala | 25 +++++++++++++++++++ .../lf/engine/testing/SemanticTester.scala | 12 +++++++-- .../api/testtool/LedgerApiTestTool.scala | 11 +++++--- 5 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 daml-lf/scenario-interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/ScenarioRunnerTest.scala diff --git a/daml-lf/scenario-interpreter/BUILD.bazel b/daml-lf/scenario-interpreter/BUILD.bazel index 001963c584f..8f8abe370e2 100644 --- a/daml-lf/scenario-interpreter/BUILD.bazel +++ b/daml-lf/scenario-interpreter/BUILD.bazel @@ -6,6 +6,7 @@ load( "da_scala_binary", "da_scala_library", "da_scala_test_suite", + "lf_scalacopts", ) da_scala_library( @@ -23,3 +24,16 @@ da_scala_library( "//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", + ], +) diff --git a/daml-lf/scenario-interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/ScenarioRunner.scala b/daml-lf/scenario-interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/ScenarioRunner.scala index 8e57a1ae98d..672b191c4e6 100644 --- a/daml-lf/scenario-interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/ScenarioRunner.scala +++ b/daml-lf/scenario-interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/ScenarioRunner.scala @@ -13,14 +13,22 @@ import com.digitalasset.daml.lf.speedy.SError._ import com.digitalasset.daml.lf.speedy.SResult._ 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) -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) + import scala.util.{Try, Success, Failure} def run(): Either[(SError, Ledger), (Double, Int, Ledger)] = @@ -83,11 +91,13 @@ final case class ScenarioRunner(machine: Speedy.Machine) { private def crash(reason: String) = throw SRunnerException(SErrorCrash(reason)) - private def getParty(partyText: String, callback: Party => Unit) = - SimpleString.fromString(partyText) match { + private def getParty(partyText: String, callback: Party => Unit) = { + val mangledPartyText = partyNameMangler(partyText) + SimpleString.fromString(mangledPartyText) match { case Right(s) => callback(s) case _ => throw SRunnerException(ScenarioErrorInvalidPartyName(partyText)) } + } private def mustFail(tx: Transaction, committer: Party) = { // Update expression evaluated successfully, diff --git a/daml-lf/scenario-interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/ScenarioRunnerTest.scala b/daml-lf/scenario-interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/ScenarioRunnerTest.scala new file mode 100644 index 00000000000..e6952d41c8c --- /dev/null +++ b/daml-lf/scenario-interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/ScenarioRunnerTest.scala @@ -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"))) + } + } + +} diff --git a/daml-lf/testing-tools/src/main/scala/com/digitalasset/daml/lf/engine/testing/SemanticTester.scala b/daml-lf/testing-tools/src/main/scala/com/digitalasset/daml/lf/engine/testing/SemanticTester.scala index 423a203bbe3..f224fa8c16b 100644 --- a/daml-lf/testing-tools/src/main/scala/com/digitalasset/daml/lf/engine/testing/SemanticTester.scala +++ b/daml-lf/testing-tools/src/main/scala/com/digitalasset/daml/lf/engine/testing/SemanticTester.scala @@ -27,10 +27,18 @@ import scala.annotation.tailrec import scala.concurrent.{ExecutionContext, Future} 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( createLedger: Set[SimpleString] => SemanticTester.GenericLedger, packageToTest: PackageId, - packages: Map[PackageId, Package])(implicit ec: ExecutionContext) { + packages: Map[PackageId, Package], + partyNameMangler: (String => String) = identity)(implicit ec: ExecutionContext) { import SemanticTester._ // result ledgers from all scenarios found in packages @@ -48,7 +56,7 @@ class SemanticTester( case (name, DValue(_, _, body, isTest)) if isTest => val qualifiedName = QualifiedName(module.name, name) val machine = buildMachine(body) - ScenarioRunner(machine).run() match { + ScenarioRunner(machine, partyNameMangler = partyNameMangler).run() match { case Left((err, _ledger @ _)) => sys.error(s"error running scenario $err in scenario: $qualifiedName") case Right((_time @ _, _steps @ _, ledger)) => diff --git a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/LedgerApiTestTool.scala b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/LedgerApiTestTool.scala index 97226fa8b58..e4bd35c1164 100644 --- a/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/LedgerApiTestTool.scala +++ b/ledger/ledger-api-test-tool/src/main/scala/com/daml/ledger/api/testtool/LedgerApiTestTool.scala @@ -3,8 +3,8 @@ package com.daml.ledger.api.testtool -import java.io.{File, StringWriter, PrintWriter} -import java.nio.file.{Files, StandardCopyOption, Paths, Path} +import java.io.{File, PrintWriter, StringWriter} +import java.nio.file.{Files, Path, Paths, StandardCopyOption} import akka.actor.ActorSystem import akka.stream.ActorMaterializer @@ -19,6 +19,7 @@ import com.digitalasset.platform.semantictest.SemanticTestAdapter import scala.concurrent.duration._ import scala.concurrent.{Await, ExecutionContext} import scala.collection.breakOut +import scala.util.Random object LedgerApiTestTool { @@ -61,13 +62,17 @@ object LedgerApiTestTool { } var failed = false + val runSuffix = Random.alphanumeric.take(10).mkString + var partyNameMangler = (partyText: String) => s"$partyText-$runSuffix" + try { scenarios.foreach { case (pkgId, names) => val tester = new SemanticTester( parties => new SemanticTestAdapter(ledger, packages, parties.map(_.underlyingString)), pkgId, - packages) + packages, + partyNameMangler) names .foreach { name => println(s"Testing scenario: $name")