Skip to content

Commit

Permalink
ledger-api-test-tool: Provide context when expecting a failing Future. (
Browse files Browse the repository at this point in the history
digital-asset#8690)

We use `Future#failed` a _lot_ in the conformance tests. Usually, this
works fine, but it has the unfortunate effect of yielding a useless
error message when the future was actually a success:

```
Future.failed not completed with a throwable.
```

Combined with the lack of useful stack traces, because futures, this
makes the test failure completely useless.

This changes all calls of `Future#failed` in our conformance tests to
use a new extension method, `#mustFail`, which takes a mandatory
"context" parameter to provide context, and includes both the context
and the value in the future in case of success.

CHANGELOG_BEGIN
CHANGELOG_END
  • Loading branch information
SamirTalwar authored Jan 29, 2021
1 parent 44c7b0b commit 0d1bc4b
Show file tree
Hide file tree
Showing 18 changed files with 294 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import ai.x.diff.DiffShow
import com.daml.grpc.{GrpcException, GrpcStatus}
import io.grpc.Status

import scala.language.higherKinds
import scala.concurrent.Future
import scala.language.{higherKinds, implicitConversions}
import scala.util.control.NonFatal

object Assertions extends DiffExtensions {
Expand Down Expand Up @@ -67,4 +68,8 @@ object Assertions extends DiffExtensions {
if (pattern.isEmpty) None else Some(Pattern.compile(Pattern.quote(pattern))),
)
}

/** Allows for assertions with more information in the error messages. */
implicit def futureAssertions[T](future: Future[T]): FutureAssertions[T] =
new FutureAssertions[T](future)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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.infrastructure

import com.daml.ledger.api.testtool.infrastructure.FutureAssertions.ExpectedFailureException

import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}

final class FutureAssertions[T](future: Future[T]) {

/** Checks that the future failed, and returns the throwable.
* We use this instead of `Future#failed` because the error message that delivers is unhelpful.
* It doesn't tell us what the value actually was.
*/
def mustFail(context: String)(implicit executionContext: ExecutionContext): Future[Throwable] =
future.transform {
case Failure(throwable) =>
Success(throwable)
case Success(value) =>
Failure(new ExpectedFailureException(context, value))
}
}

object FutureAssertions {

final class ExpectedFailureException[T](context: String, value: T)
extends NoSuchElementException(
s"Expected a failure when $context, but got a successful result of: $value"
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class ActiveContractsServiceIT extends LedgerTestSuite {
.activeContractsRequest(parties)
.update(_.ledgerId := invalidLedgerId)
for {
failure <- ledger.activeContracts(invalidRequest).failed
failure <- ledger.activeContracts(invalidRequest).mustFail("retrieving active contracts")
} yield {
assertGrpcError(failure, Status.Code.NOT_FOUND, "not found. Actual Ledger ID")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@

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

import com.daml.ledger.api.testtool.infrastructure.Allocation.{
Participant,
Participants,
SingleParty,
allocate,
}
import com.daml.ledger.api.testtool.infrastructure.Assertions.assertGrpcError
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.client.binding
import com.daml.ledger.test.semantic.SemanticTests.{Amount, Iou}
Expand All @@ -31,7 +26,7 @@ class ClosedWorldIT extends LedgerTestSuite {
for {
failure <- alpha
.create(payer, Iou(payer, binding.Primitive.Party("unallocated"), onePound))
.failed
.mustFail("referencing an unallocated party")
} yield {
assertGrpcError(failure, Status.Code.INVALID_ARGUMENT, "Party not known on ledger")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
package com.daml.ledger.api.testtool.suites

import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions.{assertGrpcError, assertSingleton}
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.ProtobufConverters._
import com.daml.ledger.test.model.DA.Types.Tuple2
Expand Down Expand Up @@ -44,7 +44,9 @@ final class CommandDeduplicationIT(ledgerTimeInterval: Duration) extends LedgerT
// only one submission is therefore sent to the ledger.
ledgerEnd1 <- ledger.currentEnd()
_ <- ledger.submit(requestA1)
failure1 <- ledger.submit(requestA1).failed
failure1 <- ledger
.submit(requestA1)
.mustFail("submitting the first request for the second time")
completions1 <- ledger.firstCompletions(ledger.completionStreamRequest(ledgerEnd1)(party))

// Wait until the end of first deduplication window
Expand All @@ -57,7 +59,9 @@ final class CommandDeduplicationIT(ledgerTimeInterval: Duration) extends LedgerT
// The first submit() in this block should therefore lead to an accepted transaction.
ledgerEnd2 <- ledger.currentEnd()
_ <- ledger.submit(requestA2)
failure2 <- ledger.submit(requestA2).failed
failure2 <- ledger
.submit(requestA2)
.mustFail("submitting the second request for the second time")
completions2 <- ledger.firstCompletions(ledger.completionStreamRequest(ledgerEnd2)(party))

// Inspect created contracts
Expand Down Expand Up @@ -100,10 +104,12 @@ final class CommandDeduplicationIT(ledgerTimeInterval: Duration) extends LedgerT

for {
// Submit an invalid command (should fail with INVALID_ARGUMENT)
failure1 <- ledger.submit(requestA).failed
failure1 <- ledger.submit(requestA).mustFail("submitting an invalid argument")

// Re-submit the invalid command (should again fail with INVALID_ARGUMENT and not with ALREADY_EXISTS)
failure2 <- ledger.submit(requestA).failed
failure2 <- ledger
.submit(requestA)
.mustFail("submitting an invalid argument for the second time")
} yield {
assertGrpcError(failure1, Status.Code.INVALID_ARGUMENT, "")
assertGrpcError(failure2, Status.Code.INVALID_ARGUMENT, "")
Expand Down Expand Up @@ -165,14 +171,18 @@ final class CommandDeduplicationIT(ledgerTimeInterval: Duration) extends LedgerT
for {
// Submit command A (first deduplication window)
_ <- ledger.submitAndWait(requestA)
failure1 <- ledger.submitAndWait(requestA).failed
failure1 <- ledger
.submitAndWait(requestA)
.mustFail("submitting a request for the second time, in the first deduplication window")

// Wait until the end of first deduplication window
_ <- Delayed.by(deduplicationWindowWait)(())

// Submit command A (second deduplication window)
_ <- ledger.submitAndWait(requestA)
failure2 <- ledger.submitAndWait(requestA).failed
failure2 <- ledger
.submitAndWait(requestA)
.mustFail("submitting a request for the second time, in the second deduplication window")

// Inspect created contracts
activeContracts <- ledger.activeContracts(party)
Expand Down Expand Up @@ -200,11 +210,15 @@ final class CommandDeduplicationIT(ledgerTimeInterval: Duration) extends LedgerT
for {
// Submit a command as alice
_ <- ledger.submit(aliceRequest)
failure1 <- ledger.submit(aliceRequest).failed
failure1 <- ledger
.submit(aliceRequest)
.mustFail("submitting a request as Alice for the second time")

// Submit another command that uses same commandId, but is submitted by Bob
_ <- ledger.submit(bobRequest)
failure2 <- ledger.submit(bobRequest).failed
failure2 <- ledger
.submit(bobRequest)
.mustFail("submitting the same request as Bob, for the second time")

// Wait for command completions and inspect the ledger state
_ <- ledger.firstCompletions(alice)
Expand Down Expand Up @@ -240,11 +254,15 @@ final class CommandDeduplicationIT(ledgerTimeInterval: Duration) extends LedgerT
for {
// Submit a command as alice
_ <- ledger.submitAndWait(aliceRequest)
failure1 <- ledger.submitAndWait(aliceRequest).failed
failure1 <- ledger
.submitAndWait(aliceRequest)
.mustFail("submitting a request as Alice for the second time")

// Submit another command that uses same commandId, but is submitted by Bob
_ <- ledger.submitAndWait(bobRequest)
failure2 <- ledger.submitAndWait(bobRequest).failed
failure2 <- ledger
.submitAndWait(bobRequest)
.mustFail("submitting the same request as Bob, for the second time")

// Inspect the ledger state
aliceContracts <- ledger.activeContracts(alice)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ final class CommandServiceIT extends LedgerTestSuite {
val request = ledger.submitAndWaitRequest(party, Dummy(party).create.command)
for {
_ <- ledger.submitAndWait(request)
failure <- ledger.submitAndWait(request).failed
failure <- ledger.submitAndWait(request).mustFail("submitting a duplicate request")
} yield {
assertGrpcError(failure, Status.Code.ALREADY_EXISTS, "")
}
Expand All @@ -167,7 +167,9 @@ final class CommandServiceIT extends LedgerTestSuite {
val request = ledger.submitAndWaitRequest(party, Dummy(party).create.command)
for {
_ <- ledger.submitAndWaitForTransactionId(request)
failure <- ledger.submitAndWaitForTransactionId(request).failed
failure <- ledger
.submitAndWaitForTransactionId(request)
.mustFail("submitting a duplicate request")
} yield {
assertGrpcError(failure, Status.Code.ALREADY_EXISTS, "")
}
Expand All @@ -181,7 +183,9 @@ final class CommandServiceIT extends LedgerTestSuite {
val request = ledger.submitAndWaitRequest(party, Dummy(party).create.command)
for {
_ <- ledger.submitAndWaitForTransaction(request)
failure <- ledger.submitAndWaitForTransaction(request).failed
failure <- ledger
.submitAndWaitForTransaction(request)
.mustFail("submitting a duplicate request")
} yield {
assertGrpcError(failure, Status.Code.ALREADY_EXISTS, "")
}
Expand All @@ -195,23 +199,27 @@ final class CommandServiceIT extends LedgerTestSuite {
val request = ledger.submitAndWaitRequest(party, Dummy(party).create.command)
for {
_ <- ledger.submitAndWaitForTransactionTree(request)
failure <- ledger.submitAndWaitForTransactionTree(request).failed
failure <- ledger
.submitAndWaitForTransactionTree(request)
.mustFail("submitting a duplicate request")
} yield {
assertGrpcError(failure, Status.Code.ALREADY_EXISTS, "")
}
})

test(
"CSsubmitAndWaitForTransactionIdInvalidLedgerId",
"SubmitAndWaitForTransactionId should fail for invalid ledger ids",
"SubmitAndWaitForTransactionId should fail for invalid ledger IDs",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val invalidLedgerId = "CSsubmitAndWaitForTransactionIdInvalidLedgerId"
val request = ledger
.submitAndWaitRequest(party, Dummy(party).create.command)
.update(_.commands.ledgerId := invalidLedgerId)
for {
failure <- ledger.submitAndWaitForTransactionId(request).failed
failure <- ledger
.submitAndWaitForTransactionId(request)
.mustFail("submitting a request with an invalid ledger ID")
} yield assertGrpcError(
failure,
Status.Code.NOT_FOUND,
Expand All @@ -221,15 +229,17 @@ final class CommandServiceIT extends LedgerTestSuite {

test(
"CSsubmitAndWaitForTransactionInvalidLedgerId",
"SubmitAndWaitForTransaction should fail for invalid ledger ids",
"SubmitAndWaitForTransaction should fail for invalid ledger IDs",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val invalidLedgerId = "CSsubmitAndWaitForTransactionInvalidLedgerId"
val request = ledger
.submitAndWaitRequest(party, Dummy(party).create.command)
.update(_.commands.ledgerId := invalidLedgerId)
for {
failure <- ledger.submitAndWaitForTransaction(request).failed
failure <- ledger
.submitAndWaitForTransaction(request)
.mustFail("submitting a request with an invalid ledger ID")
} yield assertGrpcError(
failure,
Status.Code.NOT_FOUND,
Expand All @@ -247,7 +257,9 @@ final class CommandServiceIT extends LedgerTestSuite {
.submitAndWaitRequest(party, Dummy(party).create.command)
.update(_.commands.ledgerId := invalidLedgerId)
for {
failure <- ledger.submitAndWaitForTransactionTree(request).failed
failure <- ledger
.submitAndWaitForTransactionTree(request)
.mustFail("submitting a request with an invalid ledger ID")
} yield assertGrpcError(
failure,
Status.Code.NOT_FOUND,
Expand All @@ -264,7 +276,9 @@ final class CommandServiceIT extends LedgerTestSuite {
.update(_.create.createArguments.fields.foreach(_.label := "INVALID_PARAM"))
val badRequest = ledger.submitAndWaitRequest(party, createWithBadArgument)
for {
failure <- ledger.submitAndWait(badRequest).failed
failure <- ledger
.submitAndWait(badRequest)
.mustFail("submitting a request with a bad parameter label")
} yield {
assertGrpcError(failure, Status.Code.INVALID_ARGUMENT, s"Missing record label")
}
Expand All @@ -277,7 +291,9 @@ final class CommandServiceIT extends LedgerTestSuite {
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
dummy <- ledger.create(party, Dummy(party))
failure <- ledger.exercise(party, dummy.exerciseFailingClone).failed
failure <- ledger
.exercise(party, dummy.exerciseFailingClone)
.mustFail("submitting a request with an interpretation error")
} yield {
assertGrpcError(
failure,
Expand Down Expand Up @@ -427,13 +443,13 @@ final class CommandServiceIT extends LedgerTestSuite {
for {
e1 <- ledger
.submitAndWait(ledger.submitAndWaitRequest(party, rounding(wouldLosePrecision)))
.failed
.mustFail("submitting a request which would lose precision")
e2 <- ledger
.submitAndWait(ledger.submitAndWaitRequest(party, rounding(positiveOutOfBounds)))
.failed
.mustFail("submitting a request with a positive number out of bounds")
e3 <- ledger
.submitAndWait(ledger.submitAndWaitRequest(party, rounding(negativeOutOfBounds)))
.failed
.mustFail("submitting a request with a negative number out of bounds")
} yield {
assertGrpcError(e1, Status.Code.INVALID_ARGUMENT, "Cannot represent")
assertGrpcError(e2, Status.Code.INVALID_ARGUMENT, "Out-of-bounds (Numeric 10)")
Expand Down Expand Up @@ -481,7 +497,9 @@ final class CommandServiceIT extends LedgerTestSuite {
.update(_.createAndExercise.createArguments := Record())
val request = ledger.submitAndWaitRequest(party, createAndExercise)
for {
failure <- ledger.submitAndWait(request).failed
failure <- ledger
.submitAndWait(request)
.mustFail("submitting a request with bad create arguments")
} yield {
assertGrpcError(failure, Status.Code.INVALID_ARGUMENT, "Expecting 1 field for record")
}
Expand All @@ -498,7 +516,9 @@ final class CommandServiceIT extends LedgerTestSuite {
.update(_.createAndExercise.choiceArgument := Value(Value.Sum.Bool(false)))
val request = ledger.submitAndWaitRequest(party, createAndExercise)
for {
failure <- ledger.submitAndWait(request).failed
failure <- ledger
.submitAndWait(request)
.mustFail("submitting a request with bad choice arguments")
} yield {
assertGrpcError(failure, Status.Code.INVALID_ARGUMENT, "mismatching type")
}
Expand All @@ -516,7 +536,9 @@ final class CommandServiceIT extends LedgerTestSuite {
.update(_.createAndExercise.choice := missingChoice)
val request = ledger.submitAndWaitRequest(party, createAndExercise)
for {
failure <- ledger.submitAndWait(request).failed
failure <- ledger
.submitAndWait(request)
.mustFail("submitting a request with an invalid choice")
} yield {
assertGrpcError(
failure,
Expand Down
Loading

0 comments on commit 0d1bc4b

Please sign in to comment.