Skip to content

Commit

Permalink
Add conformance test for deeply nested values (#10319)
Browse files Browse the repository at this point in the history
CHANGELOG_BEGIN
CHANGELOG_END
  • Loading branch information
remyhaemmerle-da authored Jul 21, 2021
1 parent faf479e commit 63739fa
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml.ledger.api.testtool.suites

import com.daml.ledger.api.refinements.ApiTypes.Party
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.test.semantic.DeeplyNestedValue._
import com.daml.ledger.client.binding.Primitive
import io.grpc.Status

import scala.annotation.tailrec
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Success

final class DeeplyNestedValueIT extends LedgerTestSuite {

@tailrec
private[this] def toNat(i: Long, acc: Nat = Nat.Z(())): Nat =
if (i == 0) acc else toNat(i - 1, Nat.S(acc))

private[this] def waitForTransactionId(
alpha: ParticipantTestContext,
party: Party,
command: Primitive.Update[_],
)(implicit
ec: ExecutionContext
): Future[Either[Throwable, String]] =
alpha
.submitAndWaitForTransactionId(
alpha.submitAndWaitRequest(party, command.command)
)
.transform(x => Success(x.toEither))

private[this] def camlCase(s: String) =
s.split(" ").iterator.map(_.capitalize).mkString("")

List[Long](46, 100, 101, 110, 200).foreach { nesting =>
val accepted = nesting <= 100
val result = if (accepted) "Accept" else "Reject"

// Once converted to Nat, `n` will have a nesting `nesting`.
// Note that Nat.Z(()) has nesting 1.
val n = nesting - 1

// Choice argument are always wrapped in a record
val nChoiceArgument = n - 1

// The nesting of the payload of a `Contract` is one more than the nat it contains
val nContract = n - 1

// The nesting of the key of a `ContractWithKey` is one more than the nat it contains
val nKey = n - 1

def test[T](description: String)(
update: ExecutionContext => (
ParticipantTestContext,
Party,
) => Future[Either[Throwable, T]]
) =
super.test(
result + camlCase(description) + nesting.toString,
s"${result.toLowerCase}s $description with a nesting of $nesting",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(alpha, party)) =>
update(ec)(alpha, party).map {
case Right(_) if accepted => ()
case Left(err: Throwable) if !accepted =>
assertGrpcError(err, Status.Code.INVALID_ARGUMENT, None)
case otherwise =>
fail("Unexpected " + otherwise.fold(err => s"failure: $err", _ => "success"))
}
})

test("create command") { implicit ec => (alpha, party) =>
waitForTransactionId(alpha, party, Contract(party, nContract, toNat(nContract)).create)
}

test("exercise command") { implicit ec => (alpha, party) =>
for {
handler <- alpha.create(party, Handler(party))
result <- waitForTransactionId(
alpha,
party,
handler.exerciseDestruct(party, toNat(nChoiceArgument)),
)
} yield result
}

test("create argument in CreateAndExercise command") { implicit ec => (alpha, party) =>
waitForTransactionId(
alpha,
party,
Contract(party, nContract, toNat(nContract)).createAnd
.exerciseArchive(party),
)
}

test("choice argument in CreateAndExercise command") { implicit ec => (alpha, party) =>
waitForTransactionId(
alpha,
party,
Handler(party).createAnd.exerciseDestruct(party, toNat(nChoiceArgument)),
)
}

test("exercise argument") { implicit ec => (alpha, party) =>
for {
handler <- alpha.create(party, Handler(party))
result <-
waitForTransactionId(
alpha,
party,
handler.exerciseConstructThenDestruct(party, nChoiceArgument),
)
} yield result
}

test("exercise output") { implicit ec => (alpha, party) =>
for {
handler <- alpha.create(party, Handler(party))
result <-
waitForTransactionId(alpha, party, handler.exerciseConstruct(party, n))
} yield result
}

test("create argument") { implicit ec => (alpha, party) =>
for {
handler <- alpha.create(party, Handler(party))
result <- waitForTransactionId(alpha, party, handler.exerciseCreate(party, nContract))
} yield result
}

test("contract key") { implicit ec => (alpha, party) =>
for {
handler <- alpha.create(party, Handler(party))
result <- waitForTransactionId(alpha, party, handler.exerciseCreateKey(party, nKey))
} yield result
}

if (accepted) {
// Because we cannot create contracts with nesting > 100,
// it does not make sense to test fetch of those kinds of contracts.
test("fetch by key") { implicit ec => (alpha, party) =>
for {
handler <- alpha.create(party, Handler(party))
_ <- alpha.exercise(party, handler.exerciseCreateKey(_, nKey))
result <- waitForTransactionId(alpha, party, handler.exerciseFetchByKey(party, nKey))
} yield result
}
}

test("failing lookup by key") { implicit ec => (alpha, party) =>
for {
handler <- alpha.create(party, Handler(party))
result <- waitForTransactionId(alpha, party, handler.exerciseLookupByKey(party, nKey))
} yield result
}

if (accepted) {
// Because we cannot create contracts with key nesting > 100,
// it does not make sens to test successful lookup for those keys.
test("successful lookup by key") { implicit ec => (alpha, party) =>
for {
handler <- alpha.create(party, Handler(party))
_ <- alpha.exercise(party, handler.exerciseCreateKey(_, nKey))
result <-
waitForTransactionId(alpha, party, handler.exerciseLookupByKey(party, nKey))
} yield result
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ object Tests {
Vector(
new ParticipantPruningIT,
new MultiPartySubmissionIT,
new DeeplyNestedValueIT,
)

val retired: Vector[LedgerTestSuite] =
Expand Down
16 changes: 16 additions & 0 deletions ledger/sandbox-classic/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,19 @@ server_conformance_test(
"--exclude=ClosedWorldIT",
],
)

server_conformance_test(
name = "conformance-test-nesting",
lf_versions = ["stable"],
server_args = ["--contract-id-seeding=testing-weak"],
servers = SERVERS,
test_tool_args = [
"--include=" + ",".join([
"DeeplyNestedValueIT:Accept",
"DeeplyNestedValueIT:RejectCreateCommand",
"DeeplyNestedValueIT:RejectExerciseCommand",
"DeeplyNestedValueIT:RejectCreateArgumentInCreateAndExerciseCommand",
"DeeplyNestedValueIT:RejectChoiceArgumentInCreateAndExerciseCommand",
]),
],
)
25 changes: 25 additions & 0 deletions ledger/sandbox/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,28 @@ server_conformance_test(
"--include=ClosedWorldIT",
],
)

server_conformance_test(
name = "conformance-test-nesting",
lf_versions = ["stable"],
servers = NEXT_SERVERS,
test_tool_args = [
"--include=" + ",".join([
"DeeplyNestedValueIT:AcceptCreateCommand46",
"DeeplyNestedValueIT:AcceptExerciseCommand46",
"DeeplyNestedValueIT:AcceptCreateArgumentInCreateAndExerciseCommand46",
"DeeplyNestedValueIT:AcceptChoiceArgumentInCreateAndExerciseCommand46",
"DeeplyNestedValueIT:AcceptExerciseArgument46",
"DeeplyNestedValueIT:AcceptExerciseOutput46",
"DeeplyNestedValueIT:AcceptCreateArgument46",
"DeeplyNestedValueIT:AcceptContractKey46",
"DeeplyNestedValueIT:AcceptFetchByKey46",
"DeeplyNestedValueIT:AcceptFailingLookupByKey46",
"DeeplyNestedValueIT:AcceptSuccessfulLookupByKey46",
"DeeplyNestedValueIT:RejectCreateCommand",
"DeeplyNestedValueIT:RejectExerciseCommand",
"DeeplyNestedValueIT:RejectCreateArgumentInCreateAndExerciseCommand",
"DeeplyNestedValueIT:RejectChoiceArgumentInCreateAndExerciseCommand",
]),
],
)
88 changes: 88 additions & 0 deletions ledger/test-common/src/main/daml/semantic/DeeplyNestedValue.daml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
-- Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0

module DeeplyNestedValue where

data Nat = Z | S Nat
deriving (Eq, Show)

construct: Int -> Nat -> Nat
construct x acc | x <= 0 = acc
construct x acc = construct (x-1) (S acc)

toNat : Int -> Nat
toNat x = construct x Z

destruct: Nat -> Int -> Int
destruct Z acc = acc
destruct (S n) acc = destruct n (acc + 1)

toInt: Nat -> Int
toInt x = destruct x 0

template Contract
with
party: Party
i: Int
n: Nat
where
signatory party

template ContractWithKey
with
party: Party
i: Int
where
signatory party
key (party, toNat i): (Party, Nat)
maintainer key._1

template Handler
with
party : Party
where
signatory party
controller party can
nonconsuming Construct : Nat
with i : Int
do
pure $ toNat i
nonconsuming Destruct : Int
with n: Nat
do pure $ toInt n
nonconsuming ConstructThenDestruct : Int
with
i: Int
do
exercise self Destruct with n = toNat i
nonconsuming Create: ContractId Contract
with
i : Int
do
create Contract with
party = party
i = i
n = toNat i
nonconsuming CreateKey: ContractId ContractWithKey
with
i : Int
do
create ContractWithKey with
party = party
i = i
nonconsuming Fetch: Contract
with cid: ContractId Contract
do
fetch cid
nonconsuming FetchByKey: ContractId ContractWithKey
with
i: Int
do
(cid, _) <- fetchByKey @ContractWithKey (party, toNat i)
pure cid
nonconsuming LookupByKey: Optional (ContractId ContractWithKey)
with
i: Int
do
lookupByKey @ContractWithKey (party, toNat i)

0 comments on commit 63739fa

Please sign in to comment.