diff --git a/daml-lf/integration-test-lib/src/main/com/daml/lf/CantonFixture.scala b/daml-lf/integration-test-lib/src/main/com/daml/lf/CantonFixture.scala index 57be063202c1..25d6295b180f 100644 --- a/daml-lf/integration-test-lib/src/main/com/daml/lf/CantonFixture.scala +++ b/daml-lf/integration-test-lib/src/main/com/daml/lf/CantonFixture.scala @@ -101,10 +101,10 @@ trait CantonFixture extends SuiteResource[Vector[Port]] with AkkaBeforeAndAfterA |""".stripMargin ) - private val tmpDir = Files.createTempDirectory("CantonFixture") - private val cantonConfigFile = tmpDir.resolve("participant.config") - private val cantonLogFile = tmpDir.resolve("canton.log") - private val portFile = tmpDir.resolve("portfile") + protected val tmpDir = Files.createTempDirectory("CantonFixture") + protected val cantonConfigFile = tmpDir.resolve("participant.config") + protected val cantonLogFile = tmpDir.resolve("canton.log") + protected val portFile = tmpDir.resolve("portfile") private val files = List(cantonConfigFile, portFile, cantonLogFile) @@ -168,7 +168,10 @@ trait CantonFixture extends SuiteResource[Vector[Port]] with AkkaBeforeAndAfterA | ${tslConfig} | } | storage.type = memory - | parameters.dev-version-support = ${devMode} + | parameters = { + | enable-engine-stack-traces = true + | dev-version-support = ${devMode} + | } | ${timeType.fold("")(x => "testing-time.type = " + x)} | }""".stripMargin } diff --git a/daml-script/test/BUILD.bazel b/daml-script/test/BUILD.bazel index 565317d5be2d..2de8c48e594c 100644 --- a/daml-script/test/BUILD.bazel +++ b/daml-script/test/BUILD.bazel @@ -170,6 +170,7 @@ da_scala_test_suite( ":script-test.dar", ":script-test-1.dev.dar", ":script-test-no-ledger.dar", + "//daml-script/runner:daml-script-binary", "//test-common/test-certificates", "@canton//:lib", ], @@ -224,29 +225,10 @@ da_scala_test_suite( "//libs-scala/resources", "//libs-scala/resources-akka", "//libs-scala/resources-grpc", + "//libs-scala/scala-utils", "//observability/metrics", "//test-common", "@maven//:com_auth0_java_jwt", "@maven//:io_dropwizard_metrics_metrics_core", ], ) - -sh_test( - name = "test_daml_script_test_runner", - srcs = [":daml-script-test-runner.sh"], - args = [ - "$(rootpath //daml-script/runner:daml-script-binary)", - "$(rootpath :script-test.dar)", - "$(POSIX_DIFF)", - "$(POSIX_GREP)", - "$(POSIX_SED)", - "$(rootpath //ledger/sandbox-on-x:app)", - ], - data = [ - ":script-test.dar", - "//daml-script/runner:daml-script-binary", - "//ledger/sandbox-on-x:app", - ], - toolchains = ["@rules_sh//sh/posix:make_variables"], - deps = ["@bazel_tools//tools/bash/runfiles"], -) diff --git a/daml-script/test/daml-script-test-runner.sh b/daml-script/test/daml-script-test-runner.sh deleted file mode 100755 index 6c253eb9d221..000000000000 --- a/daml-script/test/daml-script-test-runner.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. 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 -SED=$5 -SANDBOX=$(rlocation $TEST_WORKSPACE/$6) - -PORTFILE_DIR=$(mktemp -d) -PORTFILE=$PORTFILE_DIR/port-file -$SANDBOX run-legacy-cli-config --participant=participant-id=example,port=0,port-file=$PORTFILE --stack-traces=true --static-time --enable-user-management=true & -SANDBOX_PID=$! - -cleanup() { - kill $SANDBOX_PID || true - rm -rf $PORTFILE_DIR -} - -trap cleanup EXIT - -while [ ! -f $PORTFILE ] -do - sleep 0.5 -done - -PORT=$(cat $PORTFILE) - -set +e -TEST_OUTPUT="$($TEST_RUNNER --all --static-time --dar=$DAR_FILE --max-inbound-message-size 41943040 --ledger-host localhost --ledger-port $PORT 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="$(cat <<'EOF' -MultiTest:listKnownPartiesTest SUCCESS -MultiTest:multiTest SUCCESS -MultiTest:partyIdHintTest SUCCESS -ScriptExample:allocateParties SUCCESS -ScriptExample:initializeFixed SUCCESS -ScriptExample:initializeUser SUCCESS -ScriptExample:test SUCCESS -ScriptTest:clearUsers SUCCESS -ScriptTest:failingTest FAILURE (com.daml.lf.engine.script.ScriptF$FailedCmd: Command submit failed: FAILED_PRECONDITION: DAML_INTERPRETATION_ERROR(9,XXXXXXXX): Interpretation error: Error: Unhandled Daml exception: DA.Exception.AssertionFailed:AssertionFailed@3f4deaf1{ message = "Assertion failed" }. Details: Last location: [DA.Internal.Exception:168], partial transaction: -ScriptTest:listKnownPartiesTest SUCCESS -ScriptTest:multiPartySubmission SUCCESS -ScriptTest:partyIdHintTest SUCCESS -ScriptTest:sleepTest SUCCESS -ScriptTest:stackTrace FAILURE (com.daml.lf.engine.script.ScriptF$FailedCmd: Command submit failed: FAILED_PRECONDITION: DAML_INTERPRETATION_ERROR(9,XXXXXXXX): Interpretation error: Error: Unhandled Daml exception: DA.Exception.AssertionFailed:AssertionFailed@3f4deaf1{ message = "Assertion failed" }. Details: Last location: [DA.Internal.Exception:168], partial transaction: -ScriptTest:test0 SUCCESS -ScriptTest:test1 SUCCESS -ScriptTest:test3 SUCCESS -ScriptTest:test4 SUCCESS -ScriptTest:testCreateAndExercise SUCCESS -ScriptTest:testGetTime SUCCESS -ScriptTest:testKey SUCCESS -ScriptTest:testMaxInboundMessageSize SUCCESS -ScriptTest:testMultiPartyQueries SUCCESS -ScriptTest:testQueryContractId SUCCESS -ScriptTest:testQueryContractKey SUCCESS -ScriptTest:testSetTime SUCCESS -ScriptTest:testStack SUCCESS -ScriptTest:testUserListPagination SUCCESS -ScriptTest:testUserManagement SUCCESS -ScriptTest:testUserRightManagement SUCCESS -ScriptTest:traceOrder SUCCESS -ScriptTest:tree SUCCESS -ScriptTest:tupleKey SUCCESS -EOF -)" - -# We strip away the actual partial transaction since contract ids are not deterministic. -ACTUAL="$(echo -n "$TEST_OUTPUT" | $GREP "SUCCESS\|FAILURE" | $SED 's/partial transaction: .*$/partial transaction:/g; s/INTERPRETATION_ERROR(\([[:digit:]]\),.\{8\})/INTERPRETATION_ERROR(\1,XXXXXXXX)/g')" - -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 diff --git a/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/DamlScriptTestRunner.scala b/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/DamlScriptTestRunner.scala new file mode 100644 index 000000000000..ba1fce2e6d9c --- /dev/null +++ b/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/DamlScriptTestRunner.scala @@ -0,0 +1,115 @@ +// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.daml.lf.engine.script + +import com.daml.bazeltools.BazelRunfiles +import com.daml.ledger.api.testing.utils.SuiteResourceManagementAroundAll +import com.daml.lf.integrationtest.CantonFixture +import com.daml.platform.services.time.TimeProviderType +import com.daml.scalautil.Statement.discard +import org.scalatest.Suite +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +import java.nio.file.Files + +class DamlScriptTestRunner + extends AnyWordSpec + with CantonFixture + with Matchers + with SuiteResourceManagementAroundAll { + self: Suite => + + override protected def authSecret = None + override protected def darFiles = List.empty + override protected def devMode = false + override protected def nParticipants = 1 + override protected def timeProviderType = TimeProviderType.Static + override protected def tlsEnable = false + + private val exe = if (sys.props("os.name").toLowerCase.contains("windows")) ".exe" else "" + val scriptPath = BazelRunfiles.rlocation("daml-script/runner/daml-script-binary" + exe) + val darPath = BazelRunfiles.rlocation("daml-script/test/script-test.dar") + + "daml-script command line" should { + "pick up all scripts and returns somewhat sensible outputs" in { + val expected = + """MultiTest:listKnownPartiesTest SUCCESS + |MultiTest:multiTest SUCCESS + |MultiTest:partyIdHintTest SUCCESS + |ScriptExample:allocateParties SUCCESS + |ScriptExample:initializeFixed SUCCESS + |ScriptExample:initializeUser SUCCESS + |ScriptExample:test SUCCESS + |ScriptTest:clearUsers SUCCESS + |ScriptTest:failingTest FAILURE (com.daml.lf.engine.script.ScriptF$FailedCmd: Command submit failed: FAILED_PRECONDITION: DAML_INTERPRETATION_ERROR(9,XXXXXXXX): Interpretation error: Error: Unhandled Daml exception: DA.Exception.AssertionFailed:AssertionFailed@3f4deaf1{ message = "Assertion failed" }. Details: Last location: [DA.Internal.Exception:168], partial transaction: ... + |ScriptTest:listKnownPartiesTest SUCCESS + |ScriptTest:multiPartySubmission SUCCESS + |ScriptTest:partyIdHintTest SUCCESS + |ScriptTest:sleepTest SUCCESS + |ScriptTest:stackTrace FAILURE (com.daml.lf.engine.script.ScriptF$FailedCmd: Command submit failed: FAILED_PRECONDITION: DAML_INTERPRETATION_ERROR(9,XXXXXXXX): Interpretation error: Error: Unhandled Daml exception: DA.Exception.AssertionFailed:AssertionFailed@3f4deaf1{ message = "Assertion failed" }. Details: Last location: [DA.Internal.Exception:168], partial transaction: ... + |ScriptTest:test0 SUCCESS + |ScriptTest:test1 SUCCESS + |ScriptTest:test3 SUCCESS + |ScriptTest:test4 SUCCESS + |ScriptTest:testCreateAndExercise SUCCESS + |ScriptTest:testGetTime SUCCESS + |ScriptTest:testKey SUCCESS + |ScriptTest:testMaxInboundMessageSize SUCCESS + |ScriptTest:testMultiPartyQueries SUCCESS + |ScriptTest:testQueryContractId SUCCESS + |ScriptTest:testQueryContractKey SUCCESS + |ScriptTest:testSetTime SUCCESS + |ScriptTest:testStack SUCCESS + |ScriptTest:testUserListPagination SUCCESS + |ScriptTest:testUserManagement SUCCESS + |ScriptTest:testUserRightManagement SUCCESS + |ScriptTest:traceOrder SUCCESS + |ScriptTest:tree SUCCESS + |ScriptTest:tupleKey SUCCESS + |""".stripMargin + + val port = suiteResource.value.head.value + + import scala.sys.process._ + val builder = new StringBuilder + def log(s: String) = discard(builder.append(s).append('\n')) + + val cmd = Seq( + scriptPath, + "--all", + "--static-time", + "--dar", + darPath, + "--max-inbound-message-size", + "41943040", + "--ledger-host", + "localhost", + "--ledger-port", + port.toString, + ) + + cmd ! ProcessLogger(log, log) + + val actual = builder + .toString() + .linesIterator + .filter(s => List("SUCCESS", "FAILURE").exists(s.contains)) + .mkString("", f"%n", f"%n") + // ignore partial transactions as parties, cids, and package Ids are pretty unpredictable + .replaceAll("partial transaction: .*", "partial transaction: ...") + .replaceAll( + """DAML_INTERPRETATION_ERROR\((\d+),\w{8}\)""", + "DAML_INTERPRETATION_ERROR($1,XXXXXXXX)", + ) + + if (cantonFixtureDebugMode) { + Files.writeString(tmpDir.resolve(getClass.getSimpleName + ".expected"), expected) + Files.writeString(tmpDir.resolve(getClass.getSimpleName + ".actual"), actual) + } + + actual shouldBe expected + } + } +}