ledger-api-test-tool: Run tests on Canton, and fix a few issues. (#3446)

* ledger-api-test-tool-on-canton: Run Canton.

* ledger-api-test-tool-on-canton: Run the SemanticTests against Canton.

This was _so_ much work.

These will probably be flaky, so can't merge them in until we fix the
underlying issues with the tests around multi-participant allocation.

* ledger-api-test-tool: Wait for parties to arrive on all participants.

Thanks to Canton for exacerbating this bug.

Can't turn on all the tests on Canton yet as there are other spurious
failures. Will investigate next.

* ledger-api-test-tool: Move all contract key tests to ContractKeys.

If a ledger doesn't support ContractKeys, they need to be able to turn
these tests off.

* ledger-api-test-tool: If a test name is misspelled, fail immediately.

I keep getting names slightly wrong in the `--exclude` arguments and
wondering why it didn't work.

* client_server: Revert client_server_test.bzl.

Turns out I don't know what I'm doing. ¯\_(ツ)_/¯

* sandbox: Don't call `.toString` on an array. Doesn't do much.

* dev-env: Don't untar netcat automatically.

It needs to be installed with Pacman.
This commit is contained in:
Samir Talwar 2019-11-13 17:27:28 +01:00 committed by GitHub
parent 87de36a7fe
commit c5d9d1014e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 427 additions and 87 deletions

View File

@ -116,6 +116,26 @@ nixpkgs_package(
repositories = dev_env_nix_repos,
)
# netcat dependency
nixpkgs_package(
name = "netcat_nix",
attribute_path = "netcat-gnu",
nix_file = "//nix:bazel.nix",
nix_file_deps = common_nix_file_deps,
repositories = dev_env_nix_repos,
)
dev_env_tool(
name = "netcat_dev_env",
nix_include = ["bin/nc"],
nix_label = "@netcat_nix",
nix_paths = ["bin/nc"],
tools = ["nc"],
win_include = ["usr/bin/nc.exe"],
win_paths = ["usr/bin/nc.exe"],
win_tool = "msys2",
)
# Tar & gzip dependency
nixpkgs_package(
name = "tar_nix",
@ -181,6 +201,22 @@ nixpkgs_package(
repositories = dev_env_nix_repos,
)
nixpkgs_package(
name = "coreutils_nix",
attribute_path = "coreutils",
nix_file = "//nix:bazel.nix",
nix_file_deps = common_nix_file_deps,
repositories = dev_env_nix_repos,
)
nixpkgs_package(
name = "grpcurl_nix",
attribute_path = "grpcurl",
nix_file = "//nix:bazel.nix",
nix_file_deps = common_nix_file_deps,
repositories = dev_env_nix_repos,
)
nixpkgs_package(
name = "hlint_nix",
attribute_path = "hlint",
@ -947,3 +983,14 @@ dev_env_tool(
],
win_tool = "msys2",
)
http_archive(
name = "canton",
build_file_content = '''
package(default_visibility = ["//visibility:public"])
filegroup(name="jars", srcs=glob(["lib/**"]))
''',
sha256 = "b67215655bdcfaf0f4b271ebd3f9ce8f887c3c81a53351f47165a9bf5b93e435",
strip_prefix = "canton-0.3.0",
urls = ["https://github.com/digital-asset/canton/releases/download/v0.3.0/canton-0.3.0.tar.gz"],
)

View File

@ -4,6 +4,7 @@
"url": [
"http://repo.msys2.org/distrib/x86_64/msys2-base-x86_64-20180531.tar.xz",
"http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-jq-1.6-2-any.pkg.tar.xz#/jq.msys2",
"http://repo.msys2.org/msys/x86_64/gnu-netcat-0.7.1-1-x86_64.pkg.tar.xz#/netcat.msys2",
"http://repo.msys2.org/msys/x86_64/patch-2.7.6-1-x86_64.pkg.tar.xz#/patch.msys2",
"http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-openssl-1.1.1.c-1-any.pkg.tar.xz#/openssl.msys2",
"http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-postgresql-11.3-1-any.pkg.tar.xz#/pgsql.msys2"
@ -11,6 +12,7 @@
"hash": [
"4e799b5c3efcf9efcb84923656b7bcff16f75a666911abd6620ea8e5e1e9870c",
"da8a3b88d6ad1f5d28bc190405de9ca0f802ebcae19080a5b5b2b30a7614272b",
"32fa739d26fd49a3f8c22717ae338472d71d4798844cbc0db5e7780131fe69aa",
"5c18ce8979e9019d24abd2aee7ddcdf8824e31c4c7e162a204d4dc39b3b73776",
"28c8f3acbfa3b5435cd3759cd3f051e7cdeb0c6bb5cfccb7bacb70c8b91f66db",
"df53271e77f85f9f2b5fc959da1976af8482cc9ae24229ac8ffd6103f6428123"
@ -26,6 +28,7 @@
"iex \"$dir\\usr\\bin\\bash.exe -lc 'pacman -S --noconfirm unzip zip mingw-w64-x86_64-gcc'\"",
"iex \"$dir\\usr\\bin\\bash.exe -lc 'pacman -S --noconfirm tar diffutils'\"",
"iex \"$dir\\usr\\bin\\bash.exe -lc 'pacman -U --noconfirm /jq.msys2'\"",
"iex \"$dir\\usr\\bin\\bash.exe -lc 'pacman -U --noconfirm /netcat.msys2'\"",
"iex \"$dir\\usr\\bin\\bash.exe -lc 'pacman -U --noconfirm /patch.msys2'\"",
"iex \"$dir\\usr\\bin\\bash.exe -lc 'pacman -U --noconfirm /openssl.msys2'\"",
"iex \"$dir\\usr\\bin\\bash.exe -lc 'pacman -U --noconfirm /pgsql.msys2'\"",
@ -51,6 +54,7 @@
"usr\\bin\\expr.exe",
"usr\\bin\\ln.exe",
"usr\\bin\\ls.exe",
"usr\\bin\\nc.exe",
"usr\\bin\\rm.exe",
"usr\\bin\\sed.exe",
"usr\\bin\\sh.exe",

View File

@ -0,0 +1,72 @@
# Copyright (c) 2019 The DAML Authors. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
load("//ledger/ledger-api-test-tool:conformance.bzl", "conformance_test")
load("@os_info//:os_info.bzl", "is_windows")
java_import(
name = "canton-lib",
jars = ["@canton//:jars"],
)
java_binary(
name = "canton",
main_class = "com.digitalasset.canton.CantonApp",
runtime_deps = [":canton-lib"],
)
# Disabled on Windows because `coreutils` and `grpcurl` aren't easily available.
genrule(
name = "canton-test-runner-with-dependencies-script",
srcs = [
":canton-test-runner.sh",
],
outs = ["canton-test-runner-with-dependencies.sh"],
cmd = """
cat > $@ <<EOF
#!/usr/bin/env bash
set -euo 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
PATH="\$$(rlocation coreutils_nix/bin):\$$(rlocation jq_nix/bin):\$$(rlocation grpcurl_nix/bin):\$$(rlocation jq_dev_env/bin):\$$(rlocation netcat_dev_env/bin):\$$PATH"
export PATH
EOF
cat $< >> $@
""",
) if not is_windows else None
# Required because running `canton-test-runner-with-dependencies-script` directly fails.
sh_binary(
name = "canton-test-runner-with-dependencies",
srcs = [":canton-test-runner-with-dependencies-script"],
# Ideally these would be part of the script definition above, but that doesn't seem to work.
deps = ["@bazel_tools//tools/bash/runfiles"],
) if not is_windows else None
conformance_test(
name = "conformance-test",
# Ideally these would be part of the script definition above, but that doesn't seem to work.
extra_data = [
":bootstrap.canton",
":canton",
":canton.conf",
"@coreutils_nix//:bin/base64",
"@grpcurl_nix//:bin/grpcurl",
"@jq_dev_env//:jq",
"@netcat_dev_env//:nc",
],
ports = [
5011,
5021,
],
server = ":canton-test-runner-with-dependencies",
) if not is_windows else None

View File

@ -0,0 +1,7 @@
all start
all_participants foreach (connect(_, test_domain))
retryUntilTrue() {
all_participants forall (participant_is_active(_, test_domain.name))
}

View File

@ -0,0 +1,126 @@
#!/usr/bin/env bash
# Copyright (c) 2019 The DAML Authors. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
set -e
set -u
set -o pipefail
CANTON_COMMAND=(
"$(rlocation com_github_digital_asset_daml/ledger/ledger-api-test-tool-on-canton/canton)"
daemon
"--config=$(rlocation com_github_digital_asset_daml/ledger/ledger-api-test-tool-on-canton/canton.conf)"
"--bootstrap=$(rlocation com_github_digital_asset_daml/ledger/ledger-api-test-tool-on-canton/bootstrap.canton)"
)
PARTICIPANT_1_LEDGER_API_HOST=localhost
PARTICIPANT_1_LEDGER_API_PORT=5011
PARTICIPANT_2_LEDGER_API_HOST=localhost
PARTICIPANT_2_LEDGER_API_PORT=5021
PARTICIPANTS=(
"${PARTICIPANT_1_LEDGER_API_HOST}:${PARTICIPANT_1_LEDGER_API_PORT}"
"${PARTICIPANT_2_LEDGER_API_HOST}:${PARTICIPANT_2_LEDGER_API_PORT}"
)
TIMEOUT=30
function wait_for_ports {
local start success host port
start="$(date +%s)"
while true; do
if [[ "$(( "$(date +%s)" - start ))" -gt "$TIMEOUT" ]]; then
echo >&2 "Timed out after ${TIMEOUT} seconds."
return 1
fi
success=true
for (( i=1; i < $#; i++ )); do
host="${!i}"
i=$(( i + 1 ))
port="${!i}"
if ! nc -w 1 -z "$host" "$port"; then
success=false
break
fi
done
if "$success"; then
return 0
fi
sleep 1
done
}
command=("${CANTON_COMMAND[@]}")
dars=()
port_file=''
while (( $# )); do
# extract the port file
if [[ "$1" == '--port-file' ]]; then
port_file="$2"
shift
shift
# upload DARs with the DAML assistant
elif [[ "$1" =~ \.dar$ ]]; then
dars+=("$1")
shift
else
command+=("$1")
shift
fi
done
if [[ -z "$port_file" ]]; then
# shellcheck disable=SC2016
echo >&2 'You must specify a port file with the `--port-file` switch.'
exit 2
fi
# Redirect the Canton logs to a file for now, because they're really, really noisy.
log_file="$(mktemp -t 'canton.XXXXX.log')"
echo >&2 'Starting Canton...'
echo >&2 "(Logs will be written to \"${log_file}\".)"
"${command[@]}" >& "$log_file" &
pid="$!"
sleep 1
if ! kill -0 "$pid" 2> /dev/null; then
echo >&2 'Failed to start Canton.'
exit 1
fi
function stop {
local status
status=$?
kill "$pid" || :
rm -f "$port_file" || :
exit "$status"
}
trap stop EXIT INT TERM
wait_for_ports \
"$PARTICIPANT_1_LEDGER_API_HOST" "$PARTICIPANT_1_LEDGER_API_PORT" \
"$PARTICIPANT_2_LEDGER_API_HOST" "$PARTICIPANT_2_LEDGER_API_PORT"
for participant in "${PARTICIPANTS[@]}"; do
for dar in "${dars[@]}"; do
base64 "$dar" \
| jq -R --slurp '{"darFile": .}' \
| grpcurl \
-plaintext \
-d @ \
"$participant" \
com.digitalasset.ledger.api.v1.admin.PackageManagementService.UploadDarFile \
> /dev/null
done
done
# This should write two ports, but the runner doesn't support that.
echo "$PARTICIPANT_1_LEDGER_API_PORT" > "$port_file"
echo >&2 'Canton is up and running.'
wait "$pid"

View File

@ -0,0 +1,47 @@
# Copyright (c) 2019 The DAML Authors. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
canton {
domains {
test_domain {
storage {
type = memory
}
server {
public.port = 4011
admin.port = 4012
}
}
}
participants {
participant_1 {
storage {
type = memory
}
ledger-api {
port = 5011
}
admin-api {
port = 5012
}
}
participant_2 {
storage {
type = memory
}
ledger-api {
port = 5021
}
admin-api {
port = 5022
}
}
}
}

View File

@ -73,6 +73,15 @@ object LedgerApiTestTool {
sys.exit(0)
}
val missingTests = (config.included ++ config.excluded).filterNot(tests.all.contains)
if (missingTests.nonEmpty) {
println("The following tests could not be found:")
missingTests.foreach { testName =>
println(s" - $testName")
}
sys.exit(2)
}
val included =
if (config.allTests) tests.all.keySet
else if (config.included.isEmpty) tests.default.keySet

View File

@ -3,14 +3,15 @@
package com.daml.ledger.api.testtool.infrastructure
import scala.concurrent.duration.DurationInt
import scala.concurrent.duration.{Duration, DurationInt}
import scala.concurrent.{ExecutionContext, Future}
object Eventually {
private val withRetryStrategy = RetryStrategy.exponentialBackoff(10, 10.millis)
def eventually[A](runAssertion: => Future[A])(implicit ec: ExecutionContext): Future[A] =
withRetryStrategy { _ =>
def eventually[A](
runAssertion: => Future[A],
attempts: Int = 10,
firstWaitTime: Duration = 10.millis)(implicit ec: ExecutionContext): Future[A] =
RetryStrategy.exponentialBackoff(attempts, firstWaitTime) { _ =>
runAssertion
}
}

View File

@ -8,7 +8,9 @@ import com.daml.ledger.api.testtool.infrastructure.Allocation.{
ParticipantAllocation,
Participants
}
import com.daml.ledger.api.testtool.infrastructure.Eventually.eventually
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.digitalasset.ledger.client.binding.Primitive.Party
import scala.concurrent.{ExecutionContext, Future}
@ -39,12 +41,30 @@ private[testtool] final class LedgerTestContext private[infrastructure] (
Future
.sequence(allocation.partyCounts.map(partyCount => {
val participant = nextParticipant()
participant
.allocateParties(partyCount.count)
.map(parties => Participant(participant, parties: _*))
for {
parties <- participant.allocateParties(partyCount.count)
partiesSet = parties.toSet
_ <- eventually(waitForParties(participant, partiesSet))
} yield Participant(participant, parties: _*)
}))
.map(Participants(_: _*))
private[this] def waitForParties(
participant: ParticipantTestContext,
expectedParties: Set[Party]): Future[Unit] = {
Future
.sequence(participants.map(otherParticipant => {
otherParticipant
.listParties()
.map(actualParties => {
assert(
expectedParties.subsetOf(actualParties),
s"Parties from $participant never appeared on $otherParticipant.")
})
}))
.map(_ => ())
}
private[this] def nextParticipant(): ParticipantTestContext =
participantsRing.synchronized {
participantsRing.next()

View File

@ -19,7 +19,8 @@ import com.digitalasset.ledger.api.v1.admin.package_management_service.{
}
import com.digitalasset.ledger.api.v1.admin.party_management_service.{
AllocatePartyRequest,
GetParticipantIdRequest
GetParticipantIdRequest,
ListKnownPartiesRequest
}
import com.digitalasset.ledger.api.v1.command_completion_service.{
CompletionStreamRequest,
@ -188,6 +189,11 @@ private[testtool] final class ParticipantTestContext private[participant] (
def allocateParties(n: Int): Future[Vector[Party]] =
Future.sequence(Vector.fill(n)(allocateParty()))
def listParties(): Future[Set[Party]] =
services.partyManagement
.listKnownParties(new ListKnownPartiesRequest())
.map(_.partyDetails.map(partyDetails => Party(partyDetails.party)).toSet)
def activeContracts(
request: GetActiveContractsRequest): Future[(Option[LedgerOffset], Vector[CreatedEvent])] =
for {

View File

@ -3,13 +3,10 @@
package com.daml.ledger.api.testtool.tests
import java.util.UUID
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.TransactionHelpers._
import com.daml.ledger.api.testtool.infrastructure.{LedgerSession, LedgerTestSuite}
import com.digitalasset.ledger.api.v1.value.{Record, RecordField, Value}
import com.digitalasset.ledger.client.binding.Primitive
import com.digitalasset.ledger.client.binding.Value.encode
import com.digitalasset.ledger.test_stable.Test.CallablePayout._
@ -313,57 +310,6 @@ final class CommandService(session: LedgerSession) extends LedgerTestSuite(sessi
}
}
test(
"CSExerciseByKey",
"Exercising by key should be possible only when the corresponding contract is available",
allocate(SingleParty)) {
case Participants(Participant(ledger, party)) =>
val keyString = UUID.randomUUID.toString
val expectedKey = Value(
Value.Sum.Record(
Record(
fields = Seq(
RecordField("_1", Some(Value(Value.Sum.Party(party.unwrap)))),
RecordField("_2", Some(Value(Value.Sum.Text(keyString))))
))))
for {
failureBeforeCreation <- ledger
.exerciseByKey(
party,
TextKey.id,
expectedKey,
"TextKeyChoice",
Value(Value.Sum.Record(Record())))
.failed
_ <- ledger.create(party, TextKey(party, keyString, Primitive.List.empty))
_ <- ledger.exerciseByKey(
party,
TextKey.id,
expectedKey,
"TextKeyChoice",
Value(Value.Sum.Record(Record())))
failureAfterConsuming <- ledger
.exerciseByKey(
party,
TextKey.id,
expectedKey,
"TextKeyChoice",
Value(Value.Sum.Record(Record())))
.failed
} yield {
assertGrpcError(
failureBeforeCreation,
Status.Code.INVALID_ARGUMENT,
"dependency error: couldn't find key"
)
assertGrpcError(
failureAfterConsuming,
Status.Code.INVALID_ARGUMENT,
"dependency error: couldn't find key"
)
}
}
test(
"CSDiscloseCreateToObservers",
"Disclose create to observers",

View File

@ -9,7 +9,9 @@ import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.Eventually.eventually
import com.daml.ledger.api.testtool.infrastructure.Synchronize.synchronize
import com.daml.ledger.api.testtool.infrastructure.TransactionHelpers._
import com.daml.ledger.api.testtool.infrastructure.{LedgerSession, LedgerTestSuite}
import com.digitalasset.ledger.api.v1.value.{Record, RecordField, Value}
import com.digitalasset.ledger.test_stable.DA.Types.Tuple2
import com.digitalasset.ledger.test_stable.Test.Delegated._
import com.digitalasset.ledger.test_stable.Test.Delegation._
@ -220,4 +222,77 @@ final class ContractKeys(session: LedgerSession) extends LedgerTestSuite(session
assertGrpcError(failedFetch, Status.Code.INVALID_ARGUMENT, "couldn't find key")
}
}
test(
"CKExposedByTemplate",
"The contract key should be exposed if the template specifies one",
allocate(SingleParty)) {
case Participants(Participant(ledger, party)) =>
val expectedKey = "some-fancy-key"
for {
_ <- ledger.create(party, TextKey(party, expectedKey, List.empty))
transactions <- ledger.flatTransactions(party)
} yield {
val contract = assertSingleton("CKExposedByTemplate", transactions.flatMap(createdEvents))
assertEquals(
"CKExposedByTemplate",
contract.getContractKey.getRecord.fields,
Seq(
RecordField("_1", Some(Value(Value.Sum.Party(Tag.unwrap(party))))),
RecordField("_2", Some(Value(Value.Sum.Text(expectedKey))))
)
)
}
}
test(
"CKExerciseByKey",
"Exercising by key should be possible only when the corresponding contract is available",
allocate(SingleParty)) {
case Participants(Participant(ledger, party)) =>
val keyString = UUID.randomUUID.toString
val expectedKey = Value(
Value.Sum.Record(
Record(
fields = Seq(
RecordField("_1", Some(Value(Value.Sum.Party(Tag.unwrap(party))))),
RecordField("_2", Some(Value(Value.Sum.Text(keyString))))
))))
for {
failureBeforeCreation <- ledger
.exerciseByKey(
party,
TextKey.id,
expectedKey,
"TextKeyChoice",
Value(Value.Sum.Record(Record())))
.failed
_ <- ledger.create(party, TextKey(party, keyString, List.empty))
_ <- ledger.exerciseByKey(
party,
TextKey.id,
expectedKey,
"TextKeyChoice",
Value(Value.Sum.Record(Record())))
failureAfterConsuming <- ledger
.exerciseByKey(
party,
TextKey.id,
expectedKey,
"TextKeyChoice",
Value(Value.Sum.Record(Record())))
.failed
} yield {
assertGrpcError(
failureBeforeCreation,
Status.Code.INVALID_ARGUMENT,
"dependency error: couldn't find key"
)
assertGrpcError(
failureAfterConsuming,
Status.Code.INVALID_ARGUMENT,
"dependency error: couldn't find key"
)
}
}
}

View File

@ -10,7 +10,6 @@ import com.daml.ledger.api.testtool.infrastructure.Synchronize.synchronize
import com.daml.ledger.api.testtool.infrastructure.TransactionHelpers._
import com.daml.ledger.api.testtool.infrastructure.{LedgerSession, LedgerTestSuite}
import com.digitalasset.ledger.api.v1.transaction.Transaction
import com.digitalasset.ledger.api.v1.value.{RecordField, Value}
import com.digitalasset.ledger.client.binding.Primitive
import com.digitalasset.ledger.client.binding.Value.encode
import com.digitalasset.ledger.test_dev.Test.TextContainer
@ -665,28 +664,6 @@ class TransactionService(session: LedgerSession) extends LedgerTestSuite(session
}
}
test(
"TXContractKey",
"The contract key should be exposed if the template specifies one",
allocate(SingleParty)) {
case Participants(Participant(ledger, party)) =>
val expectedKey = "some-fancy-key"
for {
_ <- ledger.create(party, TextKey(party, expectedKey, Primitive.List.empty))
transactions <- ledger.flatTransactions(party)
} yield {
val contract = assertSingleton(s"ContractKey", transactions.flatMap(createdEvents))
assertEquals(
"ContractKey",
contract.getContractKey.getRecord.fields,
Seq(
RecordField("_1", Some(Value(Value.Sum.Party(Tag.unwrap(party))))),
RecordField("_2", Some(Value(Value.Sum.Text(expectedKey))))
)
)
}
}
test(
"TXMultiActorChoiceOk",
"Accept exercising a well-authorized multi-actor choice",

View File

@ -110,7 +110,7 @@ trait PostgresAround {
} catch {
case NonFatal(e) =>
throw new IllegalStateException(
s"Cannot start Postgres fixture. Failed command: ${command.toString}",
s"Cannot start Postgres fixture. Failed command: ${command.mkString(" ")}",
e)
}
}

View File

@ -6,15 +6,18 @@
}:
let shared = rec {
inherit (pkgs)
coreutils
curl
docker
gawk
gnutar
grpcurl
gzip
hlint
imagemagick
jdk8
jq
netcat-gnu
nodejs
patchelf
postgresql_9_6