forked from digital-asset/daml
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
daml test-script (digital-asset#3918)
* Start on daml test-scripts * Run all `Script a` as test cases * LedgerClient: Expose PackageManagementClient To enable DAR uploads * Upload the DAR to the ledger * Start sandbox if no ledger specified * Format daml test-script * Fix deprecation warning on ActorMaterializer * Add test-case //daml-script/tests:test_daml_script_test_runner * Add daml test-script command CHANGELOG_BEGIN - [DAML Script - Experimental] Allow running DAML scripts as test-cases. Executing ``daml test-script --dar mydar.dar`` will execute all definitions matching the type ``Script a`` as test-cases. See `digital-asset#3687 <https://github.com/digital-asset/daml/issues/3687>`__. CHANGELOG_END * daml-test-script enable logging * Remove outdated TODO comment * daml script-test More elaborate test-caseo Compare to expected output and add failing test-case * daml test-script Don't abort on test-failure Before the test runner would abort on the first failed test-case. This occasionally introduce additional test-failures if the sandbox was torn down half-way through execution. * ./fmt.sh Co-authored-by: Andreas Herrmann <andreash87@gmx.ch>
- Loading branch information
1 parent
f8c247c
commit 6e25d10
Showing
9 changed files
with
361 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/TestConfig.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// Copyright (c) 2020 The DAML Authors. All rights reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package com.digitalasset.daml.lf.engine.script | ||
|
||
import java.io.File | ||
import java.time.Duration | ||
|
||
import com.digitalasset.platform.services.time.TimeProviderType | ||
|
||
case class TestConfig( | ||
darPath: File, | ||
ledgerHost: Option[String], | ||
ledgerPort: Option[Int], | ||
participantConfig: Option[File], | ||
timeProviderType: TimeProviderType, | ||
commandTtl: Duration, | ||
) | ||
|
||
object TestConfig { | ||
private val parser = new scopt.OptionParser[TestConfig]("test-script") { | ||
head("test-script") | ||
|
||
opt[File]("dar") | ||
.required() | ||
.action((f, c) => c.copy(darPath = f)) | ||
.text("Path to the dar file containing the script") | ||
|
||
opt[String]("ledger-host") | ||
.optional() | ||
.action((t, c) => c.copy(ledgerHost = Some(t))) | ||
.text("Ledger hostname") | ||
|
||
opt[Int]("ledger-port") | ||
.optional() | ||
.action((t, c) => c.copy(ledgerPort = Some(t))) | ||
.text("Ledger port") | ||
|
||
opt[File]("participant-config") | ||
.optional() | ||
.action((t, c) => c.copy(participantConfig = Some(t))) | ||
.text("File containing the participant configuration in JSON format") | ||
|
||
opt[Unit]('w', "wall-clock-time") | ||
.action { (t, c) => | ||
c.copy(timeProviderType = TimeProviderType.WallClock) | ||
} | ||
.text("Use wall clock time (UTC). When not provided, static time is used.") | ||
|
||
opt[Long]("ttl") | ||
.action { (t, c) => | ||
c.copy(commandTtl = Duration.ofSeconds(t)) | ||
} | ||
.text("TTL in seconds used for commands emitted by the trigger. Defaults to 30s.") | ||
|
||
checkConfig(c => { | ||
if (c.ledgerHost.isDefined != c.ledgerPort.isDefined) { | ||
failure("Must specify both --ledger-host and --ledger-port") | ||
} else if (c.ledgerHost.isDefined && c.participantConfig.isDefined) { | ||
failure("Cannot specify both --ledger-host and --participant-config") | ||
} else { | ||
success | ||
} | ||
}) | ||
} | ||
def parse(args: Array[String]): Option[TestConfig] = | ||
parser.parse( | ||
args, | ||
TestConfig( | ||
darPath = null, | ||
ledgerHost = None, | ||
ledgerPort = None, | ||
participantConfig = None, | ||
timeProviderType = TimeProviderType.Static, | ||
commandTtl = Duration.ofSeconds(30L), | ||
) | ||
) | ||
} |
174 changes: 174 additions & 0 deletions
174
daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/TestMain.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
// Copyright (c) 2020 The DAML Authors. All rights reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package com.digitalasset.daml.lf.engine.script | ||
|
||
import java.io.FileInputStream | ||
|
||
import akka.actor.ActorSystem | ||
import akka.stream._ | ||
import com.typesafe.scalalogging.StrictLogging | ||
import java.time.Instant | ||
import java.util.concurrent.atomic.AtomicBoolean | ||
|
||
import scala.concurrent.{Await, ExecutionContext, Future} | ||
import scala.concurrent.duration.Duration | ||
import scala.io.Source | ||
import scalaz.syntax.traverse._ | ||
import spray.json._ | ||
import com.digitalasset.api.util.TimeProvider | ||
import com.digitalasset.daml.lf.archive.{Dar, DarReader} | ||
import com.digitalasset.daml.lf.archive.Decode | ||
import com.digitalasset.daml.lf.data.Ref.{Identifier, PackageId, QualifiedName} | ||
import com.digitalasset.daml.lf.language.Ast | ||
import com.digitalasset.daml.lf.language.Ast.Package | ||
import com.digitalasset.daml_lf_dev.DamlLf | ||
import com.digitalasset.grpc.adapter.{AkkaExecutionSequencerPool, ExecutionSequencerFactory} | ||
import com.digitalasset.ledger.api.refinements.ApiTypes.ApplicationId | ||
import com.digitalasset.ledger.client.configuration.{ | ||
CommandClientConfiguration, | ||
LedgerClientConfiguration, | ||
LedgerIdRequirement | ||
} | ||
import com.digitalasset.ledger.client.services.commands.CommandUpdater | ||
import com.digitalasset.platform.sandbox.SandboxServer | ||
import com.digitalasset.platform.sandbox.config.SandboxConfig | ||
import com.digitalasset.platform.services.time.TimeProviderType | ||
import com.google.protobuf.ByteString | ||
|
||
import scala.util.control.NonFatal | ||
import scala.util.{Failure, Success} | ||
|
||
object TestMain extends StrictLogging { | ||
|
||
def main(args: Array[String]): Unit = { | ||
|
||
TestConfig.parse(args) match { | ||
case None => sys.exit(1) | ||
case Some(config) => { | ||
val encodedDar: Dar[(PackageId, DamlLf.ArchivePayload)] = | ||
DarReader().readArchiveFromFile(config.darPath).get | ||
val dar: Dar[(PackageId, Package)] = encodedDar.map { | ||
case (pkgId, pkgArchive) => Decode.readArchivePayload(pkgId, pkgArchive) | ||
} | ||
|
||
val applicationId = ApplicationId("Script Test") | ||
val clientConfig = LedgerClientConfiguration( | ||
applicationId = ApplicationId.unwrap(applicationId), | ||
ledgerIdRequirement = LedgerIdRequirement("", enabled = false), | ||
commandClient = CommandClientConfiguration.default, | ||
sslContext = None | ||
) | ||
val timeProvider: TimeProvider = | ||
config.timeProviderType match { | ||
case TimeProviderType.Static => TimeProvider.Constant(Instant.EPOCH) | ||
case TimeProviderType.WallClock => TimeProvider.UTC | ||
case _ => | ||
throw new RuntimeException(s"Unexpected TimeProviderType: $config.timeProviderType") | ||
} | ||
val commandUpdater = new CommandUpdater( | ||
timeProviderO = Some(timeProvider), | ||
ttl = config.commandTtl, | ||
overrideTtl = true) | ||
|
||
val system: ActorSystem = ActorSystem("ScriptTest") | ||
implicit val sequencer: ExecutionSequencerFactory = | ||
new AkkaExecutionSequencerPool("ScriptTestPool")(system) | ||
implicit val materializer: Materializer = Materializer(system) | ||
implicit val ec: ExecutionContext = system.dispatcher | ||
|
||
val runner = new Runner(dar, applicationId, commandUpdater) | ||
val (participantParams, participantCleanup) = config.participantConfig match { | ||
case Some(file) => { | ||
val source = Source.fromFile(file) | ||
val fileContent = try { | ||
source.mkString | ||
} finally { | ||
source.close | ||
} | ||
val jsVal = fileContent.parseJson | ||
import ParticipantsJsonProtocol._ | ||
(jsVal.convertTo[Participants[ApiParameters]], () => ()) | ||
} | ||
case None => | ||
val (apiParameters, cleanup) = if (config.ledgerHost.isEmpty) { | ||
val sandboxConfig = SandboxConfig.default.copy( | ||
timeProviderType = config.timeProviderType | ||
) | ||
val sandbox = new SandboxServer(sandboxConfig) | ||
val sandboxClosed = new AtomicBoolean(false) | ||
|
||
def closeSandbox(): Unit = { | ||
if (sandboxClosed.compareAndSet(false, true)) sandbox.close() | ||
} | ||
|
||
try Runtime.getRuntime.addShutdownHook(new Thread(() => closeSandbox())) | ||
catch { | ||
case NonFatal(t) => | ||
logger.error( | ||
"Shutting down Sandbox application because of initialization error", | ||
t) | ||
closeSandbox() | ||
} | ||
(ApiParameters("localhost", sandbox.port), () => closeSandbox()) | ||
} else { | ||
(ApiParameters(config.ledgerHost.get, config.ledgerPort.get), () => ()) | ||
} | ||
( | ||
Participants( | ||
default_participant = Some(apiParameters), | ||
participants = Map.empty, | ||
party_participants = Map.empty), | ||
cleanup) | ||
} | ||
|
||
val flow: Future[Boolean] = for { | ||
clients <- Runner.connect(participantParams, clientConfig) | ||
_ <- clients.getParticipant(None) match { | ||
case Left(err) => throw new RuntimeException(err) | ||
case Right(client) => | ||
client.packageManagementClient.uploadDarFile( | ||
ByteString.readFrom(new FileInputStream(config.darPath))) | ||
} | ||
success = new AtomicBoolean(true) | ||
_ <- Future.sequence { | ||
dar.main._2.modules.flatMap { | ||
case (moduleName, module) => | ||
module.definitions.collect { | ||
case (name, Ast.DValue(Ast.TApp(Ast.TTyCon(tycon), _), _, _, _)) | ||
if tycon == runner.scriptTyCon => | ||
val testRun: Future[Unit] = for { | ||
_ <- runner.run( | ||
clients, | ||
Identifier(dar.main._1, QualifiedName(moduleName, name)), | ||
None) | ||
} yield () | ||
// Print test result and remember failure. | ||
testRun.onComplete { | ||
case Failure(exception) => | ||
success.set(false) | ||
println(s"$moduleName:$name FAILURE ($exception)") | ||
case Success(_) => | ||
println(s"$moduleName:$name SUCCESS") | ||
} | ||
// Do not abort in case of failure, but complete all test runs. | ||
testRun.recover { | ||
case _ => () | ||
} | ||
} | ||
} | ||
} | ||
} yield success.get() | ||
|
||
flow.onComplete { _ => | ||
participantCleanup() | ||
system.terminate() | ||
} | ||
|
||
if (!Await.result(flow, Duration.Inf)) { | ||
sys.exit(1) | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
#!/usr/bin/env bash | ||
# Copyright (c) 2020 The DAML Authors. All rights reserved. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
# Copy-pasted from the Bazel Bash runfiles library v2. | ||
set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash | ||
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ | ||
source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ | ||
source "$0.runfiles/$f" 2>/dev/null || \ | ||
source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ | ||
source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ | ||
{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e | ||
# --- end runfiles.bash initialization v2 --- | ||
|
||
set -euo pipefail | ||
|
||
TEST_RUNNER=$(rlocation $TEST_WORKSPACE/$1) | ||
DAR_FILE=$(rlocation $TEST_WORKSPACE/$2) | ||
DIFF=$3 | ||
GREP=$4 | ||
SORT=$5 | ||
|
||
set +e | ||
TEST_OUTPUT="$($TEST_RUNNER --dar=$DAR_FILE 2>&1)" | ||
TEST_RESULT=$? | ||
set -e | ||
|
||
echo "-- Runner Output -----------------------" >&2 | ||
echo "$TEST_OUTPUT" >&2 | ||
echo "----------------------------------------" >&2 | ||
|
||
FAIL= | ||
|
||
if [[ $TEST_RESULT = 0 ]]; then | ||
FAIL=1 | ||
echo "Expected non-zero exit-code." >&2 | ||
fi | ||
|
||
EXPECTED="$($SORT <<'EOF' | ||
MultiTest:multiTest SUCCESS | ||
ScriptExample:test SUCCESS | ||
ScriptTest:failingTest FAILURE (com.digitalasset.daml.lf.speedy.SError$DamlEUserError) | ||
ScriptTest:test0 SUCCESS | ||
ScriptTest:test1 SUCCESS | ||
ScriptTest:test3 SUCCESS | ||
ScriptTest:test4 SUCCESS | ||
ScriptTest:testCreateAndExercise SUCCESS | ||
ScriptTest:testKey SUCCESS | ||
EOF | ||
)" | ||
|
||
ACTUAL="$(echo -n "$TEST_OUTPUT" | $GREP "SUCCESS\|FAILURE" | $SORT)" | ||
|
||
if ! $DIFF -du0 --label expected <(echo -n "$EXPECTED") --label actual <(echo -n "$ACTUAL") >&2; then | ||
FAIL=1 | ||
fi | ||
|
||
if [[ $FAIL = 1 ]]; then | ||
exit 1 | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.