mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 01:07:18 +03:00
Run Transaction Service IT as part of Ledger API Test Tool (#1434)
Add tTansactionServiceIT into Ledger API Test Tool as an optional test. Available tests can be listed with the --list option. Tests can be included with --include, and excluded with --exclude. Fixes #1372, #1472
This commit is contained in:
parent
217c56d072
commit
b61203d1cd
@ -397,17 +397,17 @@ implementation of the :doc:`Ledger API
|
||||
</app-dev/index>`. For example, it will show you if
|
||||
there are consistency or conformance problem with your implementation.
|
||||
|
||||
Assuming that your Ledger API endpoint is accessible at ``localhost:6864``, you can use the tool in the following manner:
|
||||
Assuming that your Ledger API endpoint is accessible at ``localhost:6865``, you can use the tool in the following manner:
|
||||
|
||||
#. Obtain the tool:
|
||||
|
||||
``curl -L 'https://bintray.com/api/v1/content/digitalassetsdk/DigitalAssetSDK/com/daml/ledger/testtool/ledger-api-test-tool_2.12/$latest/ledger-api-test-tool_2.12-$latest.jar?bt_package=sdk-components' -o ledger-api-test-tool.jar``
|
||||
|
||||
#. Obtain the DAML archive required to run the tests:
|
||||
#. Obtain the DAML archives required to run the tests:
|
||||
|
||||
``java -jar ledger-api-test-tool.jar --extract``
|
||||
|
||||
#. Load ``SemanticTests.dar`` which was created in the current directory into your Ledger.
|
||||
#. Load all ``.dar`` files extracted in the current directory into your Ledger.
|
||||
|
||||
#. Run the tool against your ledger:
|
||||
|
||||
|
@ -58,6 +58,17 @@ Sandbox
|
||||
- Added recovery around failing ledger entry persistence queries using Postgres. See `#1505 <https://github.com/digital-asset/daml/pull/1505>`__.
|
||||
|
||||
|
||||
DAML Integration Kit
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- The :doc:`Ledger API Test Tool </tools/ledger-api-test-tool/index>` can now optionally run ``TransactionServiceIT`` as part of the conformance tests.
|
||||
This means you need to load additional ``.dar`` files into the ledger under test. Please refer to the updated instructions in the :doc:`documentation </tools/ledger-api-test-tool/index>`.
|
||||
- Added new CLI options to the :doc:`Ledger API Test Tool </tools/ledger-api-test-tool/index>`:
|
||||
|
||||
- ``--list`` prints all available tests to the console
|
||||
- ``--include`` takes a comma-separated list of test names that should be run
|
||||
- ``--exclude`` takes a comma-separated list of test names that should not be run
|
||||
|
||||
0.12.22 - 2019-05-29
|
||||
--------------------
|
||||
|
||||
|
@ -35,21 +35,21 @@ Run the following command to fetch the tool:
|
||||
|
||||
This will create a file ``ledger-api-test-tool.jar`` in your current directory.
|
||||
|
||||
Extracting ``.dar`` file required to run the tests
|
||||
Extracting ``.dar`` files required to run the tests
|
||||
======================================================
|
||||
|
||||
Before you can run the Ledger API test tool on your ledger, you need to load a
|
||||
specific set of DAML templates onto your ledger.
|
||||
|
||||
#. To obtain the corresponding ``.dar`` file, run:
|
||||
#. To obtain the corresponding ``.dar`` files, run:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ java -jar ledger-api-test-tool.jar --extract
|
||||
|
||||
This creates a file ``SemanticTests.dar`` in the current directory.
|
||||
This writes all ``.dar`` files required for the tests into the current directory.
|
||||
|
||||
#. Load ``SemanticTests.dar`` into your Ledger.
|
||||
#. Load all ``.dar`` files into your Ledger.
|
||||
|
||||
Running the tool against a custom Ledger API endpoint
|
||||
=====================================================
|
||||
@ -79,7 +79,22 @@ Run the tool with ``--help`` flag to obtain the list of options the tool provide
|
||||
|
||||
$ java -jar ledger-api-test-tool.jar --help
|
||||
|
||||
|
|
||||
Filtering tests
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
You can list the available tests with the ``--list`` flag. Some tests are not run by default. You can run them with the ``--include`` flag. To exclude tests, use the ``--exclude`` flag.
|
||||
|
||||
This command only runs the test ``TestA``:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ java -jar ledger-api-test-tool.jar --include TestA
|
||||
|
||||
This command runs all tests except the test ``TestB``:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ java -jar ledger-api-test-tool.jar --exclude TestB
|
||||
|
||||
Try out the Ledger API Test Tool against DAML Sandbox
|
||||
=====================================================
|
||||
@ -90,7 +105,7 @@ If you wanted to test out the tool, you can run it against :doc:`DAML Sandbox
|
||||
.. code-block:: console
|
||||
|
||||
$ java -jar ledger-api-test-tool.jar --extract
|
||||
$ da sandbox -- SemanticTests.dar
|
||||
$ da sandbox -- *.dar
|
||||
$ java -jar ledger-api-test-tool.jar
|
||||
|
||||
This should always succeed, as the Sandbox is tested to correctly implement the
|
||||
|
@ -19,7 +19,7 @@ trait AkkaBeforeAndAfterAll extends BeforeAndAfterAll {
|
||||
protected def actorSystemName = this.getClass.getSimpleName
|
||||
private val logger = LoggerFactory.getLogger(getClass)
|
||||
|
||||
private val executorContext = ExecutionContext.fromExecutorService(
|
||||
private lazy val executorContext = ExecutionContext.fromExecutorService(
|
||||
Executors.newSingleThreadExecutor(
|
||||
new ThreadFactoryBuilder()
|
||||
.setDaemon(true)
|
||||
@ -28,10 +28,10 @@ trait AkkaBeforeAndAfterAll extends BeforeAndAfterAll {
|
||||
logger.error(s"got an uncaught exception on thread: ${thread.getName}"))
|
||||
.build()))
|
||||
|
||||
protected implicit val system: ActorSystem =
|
||||
protected implicit lazy val system: ActorSystem =
|
||||
ActorSystem(actorSystemName, defaultExecutionContext = Some(executorContext))
|
||||
|
||||
protected implicit val materializer: ActorMaterializer = ActorMaterializer()
|
||||
protected implicit lazy val materializer: ActorMaterializer = ActorMaterializer()
|
||||
|
||||
override protected def afterAll(): Unit = {
|
||||
materializer.shutdown()
|
||||
|
@ -27,7 +27,6 @@ import org.scalatest.concurrent.{AsyncTimeLimitedTests, ScalaFutures}
|
||||
import org.scalatest.time.SpanSugar._
|
||||
import org.scalatest.time.{Millis, Span}
|
||||
import org.scalatest.{Assertion, AsyncWordSpec, Matchers, OptionValues}
|
||||
|
||||
import scalaz.syntax.tag._
|
||||
|
||||
/**
|
||||
@ -46,11 +45,13 @@ class ActiveContractsServiceIT
|
||||
with ScalaFutures
|
||||
with AsyncTimeLimitedTests
|
||||
with Matchers
|
||||
with OptionValues
|
||||
with TestCommands {
|
||||
with OptionValues {
|
||||
|
||||
override def timeLimit: Span = 60.seconds
|
||||
|
||||
protected val testCommands = new TestCommands(config)
|
||||
protected val templateIds = testCommands.templateIds
|
||||
|
||||
override implicit def patienceConfig: PatienceConfig =
|
||||
PatienceConfig(scaled(Span(30000, Millis)), scaled(Span(500, Millis)))
|
||||
|
||||
@ -97,7 +98,7 @@ class ActiveContractsServiceIT
|
||||
}.size should equal(occurrence)
|
||||
|
||||
def threeCommands(ledgerId: domain.LedgerId, commandId: String): SubmitAndWaitRequest =
|
||||
super.dummyCommands(ledgerId, commandId, "Alice").toWait
|
||||
testCommands.toWait(testCommands.dummyCommands(ledgerId, commandId, "Alice"))
|
||||
|
||||
private def filter = TransactionFilter(Map(config.parties.head -> Filters()))
|
||||
|
||||
@ -190,11 +191,13 @@ class ActiveContractsServiceIT
|
||||
contractId = extractContractId(responses1)
|
||||
_ <- submitRequest(
|
||||
ctx,
|
||||
buildRequest(
|
||||
ctx.ledgerId,
|
||||
"exercise-test-exercised",
|
||||
Seq(exerciseWithUnit(templateIds.dummy, contractId, "DummyChoice1")),
|
||||
"Alice").toWait)
|
||||
testCommands.toWait(
|
||||
testCommands.buildRequest(
|
||||
ctx.ledgerId,
|
||||
"exercise-test-exercised",
|
||||
Seq(testCommands.exerciseWithUnit(templateIds.dummy, contractId, "DummyChoice1")),
|
||||
"Alice"))
|
||||
)
|
||||
responses2 <- waitForActiveContracts(
|
||||
ctx.acsService,
|
||||
ctx.ledgerId,
|
||||
@ -223,11 +226,12 @@ class ActiveContractsServiceIT
|
||||
val resultsF = for {
|
||||
_ <- submitRequest(
|
||||
ctx,
|
||||
buildRequest(
|
||||
ctx.ledgerId,
|
||||
"commandId1",
|
||||
Seq(createWithOperator(templateIds.dummy, "Alice")),
|
||||
"Alice").toWait)
|
||||
testCommands.toWait(
|
||||
testCommands.buildRequest(
|
||||
ctx.ledgerId,
|
||||
"commandId1",
|
||||
Seq(testCommands.createWithOperator(templateIds.dummy, "Alice")),
|
||||
"Alice")))
|
||||
responses1 <- waitForActiveContracts(
|
||||
ctx.acsService,
|
||||
ctx.ledgerId,
|
||||
@ -236,11 +240,12 @@ class ActiveContractsServiceIT
|
||||
offset = extractOffset(responses1)
|
||||
_ <- submitRequest(
|
||||
ctx,
|
||||
buildRequest(
|
||||
ctx.ledgerId,
|
||||
"commandId2",
|
||||
Seq(createWithOperator(templateIds.dummyWithParam, "Alice")),
|
||||
"Alice").toWait
|
||||
testCommands.toWait(
|
||||
testCommands.buildRequest(
|
||||
ctx.ledgerId,
|
||||
"commandId2",
|
||||
Seq(testCommands.createWithOperator(templateIds.dummyWithParam, "Alice")),
|
||||
"Alice"))
|
||||
)
|
||||
responses2 <- transactionClient(ctx)
|
||||
.getTransactions(
|
||||
@ -283,8 +288,12 @@ class ActiveContractsServiceIT
|
||||
"multi-party request comes" should {
|
||||
"return the correct set of related contracts" in allFixtures { ctx =>
|
||||
val resultsF = for {
|
||||
_ <- submitRequest(ctx, dummyCommands(ctx.ledgerId, "acsCommand-1", "Alice").toWait)
|
||||
_ <- submitRequest(ctx, dummyCommands(ctx.ledgerId, "acsCommand-2", "Bob").toWait)
|
||||
_ <- submitRequest(
|
||||
ctx,
|
||||
testCommands.toWait(testCommands.dummyCommands(ctx.ledgerId, "acsCommand-1", "Alice")))
|
||||
_ <- submitRequest(
|
||||
ctx,
|
||||
testCommands.toWait(testCommands.dummyCommands(ctx.ledgerId, "acsCommand-2", "Bob")))
|
||||
allContractsForAlice <- waitForActiveContracts(
|
||||
ctx.acsService,
|
||||
ctx.ledgerId,
|
||||
|
@ -45,10 +45,12 @@ class DivulgenceIT
|
||||
with ScalaFutures
|
||||
with AsyncTimeLimitedTests
|
||||
with Matchers
|
||||
with OptionValues
|
||||
with TestTemplateIds {
|
||||
with OptionValues {
|
||||
override protected def config: Config = Config.default
|
||||
|
||||
protected val testTemplateIds = new TestTemplateIds(config)
|
||||
protected val templateIds = testTemplateIds.templateIds
|
||||
|
||||
private implicit def party(s: String): Ref.Party = Ref.Party.assertFromString(s)
|
||||
private implicit def pkgId(s: String): Ref.PackageId = Ref.PackageId.assertFromString(s)
|
||||
private implicit def id(s: String): Ref.Name = Ref.Name.assertFromString(s)
|
||||
|
@ -27,8 +27,10 @@ class LotsOfPartiesIT
|
||||
with MultiLedgerFixture
|
||||
with SuiteResourceManagementAroundAll
|
||||
with AsyncTimeLimitedTests
|
||||
with Matchers
|
||||
with TestTemplateIds {
|
||||
with Matchers {
|
||||
|
||||
protected lazy val testTemplateIds = new TestTemplateIds(config)
|
||||
protected lazy val templateIds = testTemplateIds.templateIds
|
||||
|
||||
private val numParties = 1024
|
||||
private val allParties: List[String] =
|
||||
|
@ -8,8 +8,10 @@ import java.time.{Duration, Instant}
|
||||
import akka.Done
|
||||
import akka.stream.scaladsl.{Flow, Sink}
|
||||
import com.digitalasset.daml.lf.data.Ref
|
||||
import com.digitalasset.daml.lf.data.Ref.QualifiedName
|
||||
import com.digitalasset.daml.lf.types.Ledger
|
||||
import com.digitalasset.grpc.adapter.utils.DirectExecutionContext
|
||||
import com.digitalasset.ledger.api.domain.EventId
|
||||
import com.digitalasset.ledger.api.domain.{EventId, LedgerId}
|
||||
import com.digitalasset.ledger.api.testing.utils.MockMessages.{party, _}
|
||||
import com.digitalasset.ledger.api.testing.utils.{
|
||||
AkkaBeforeAndAfterAll,
|
||||
@ -17,6 +19,7 @@ import com.digitalasset.ledger.api.testing.utils.{
|
||||
MockMessages,
|
||||
SuiteResourceManagementAroundAll
|
||||
}
|
||||
import com.digitalasset.ledger.api.v1.command_service.SubmitAndWaitRequest
|
||||
import com.digitalasset.ledger.api.v1.commands.Command.Command.Create
|
||||
import com.digitalasset.ledger.api.v1.commands.{Command, CreateCommand, ExerciseCommand}
|
||||
import com.digitalasset.ledger.api.v1.event.Event.Event.{Archived, Created}
|
||||
@ -35,6 +38,7 @@ import com.digitalasset.ledger.api.v1.value.{
|
||||
Value,
|
||||
Variant
|
||||
}
|
||||
import com.digitalasset.ledger.client.services.commands.CommandUpdater
|
||||
import com.digitalasset.ledger.client.services.transactions.TransactionClient
|
||||
import com.digitalasset.platform.api.v1.event.EventOps._
|
||||
import com.digitalasset.platform.apitesting.LedgerContextExtensions._
|
||||
@ -53,8 +57,7 @@ import scalaz.{ICons, NonEmptyList, Tag}
|
||||
|
||||
import scala.collection.{breakOut, immutable}
|
||||
import scala.concurrent.Future
|
||||
|
||||
import com.digitalasset.ledger.api.domain.LedgerId
|
||||
import scala.util.Random
|
||||
|
||||
@SuppressWarnings(Array("org.wartremover.warts.Any"))
|
||||
class TransactionServiceIT
|
||||
@ -65,15 +68,25 @@ class TransactionServiceIT
|
||||
with Inside
|
||||
with AsyncTimeLimitedTests
|
||||
with TestExecutionSequencerFactory
|
||||
with TransactionServiceHelpers
|
||||
with ParameterShowcaseTesting
|
||||
with OptionValues
|
||||
with Matchers
|
||||
with TestTemplateIds {
|
||||
with Matchers {
|
||||
|
||||
override protected val config: Config =
|
||||
override protected def config: Config =
|
||||
Config.default.withTimeProvider(TimeProviderType.WallClock)
|
||||
|
||||
protected val helpers = new TransactionServiceHelpers(config)
|
||||
protected val testTemplateIds = new TestTemplateIds(config)
|
||||
protected val templateIds = testTemplateIds.templateIds
|
||||
|
||||
val runSuffix = "-" + Random.alphanumeric.take(10).mkString
|
||||
val partyNameMangler =
|
||||
(partyText: String) => partyText + runSuffix + Random.alphanumeric.take(10).mkString
|
||||
val commandIdMangler: ((QualifiedName, Int, Ledger.ScenarioNodeId) => String) =
|
||||
(testName, stepId, nodeId) => {
|
||||
s"ledger-api-test-tool-$testName-$stepId-$nodeId-$runSuffix"
|
||||
}
|
||||
|
||||
override val timeLimit: Span = 300.seconds
|
||||
|
||||
private def newClient(stub: TransactionService, ledgerId: LedgerId): TransactionClient =
|
||||
@ -118,7 +131,11 @@ class TransactionServiceIT
|
||||
val elemsToTake = 10L
|
||||
|
||||
for {
|
||||
_ <- insertCommands(getTrackerFlow(context), "cancellation-test", 14, context.ledgerId)
|
||||
_ <- insertCommandsUnique(
|
||||
"cancellation-test",
|
||||
14,
|
||||
context
|
||||
)
|
||||
transactions <- context.transactionClient
|
||||
.getTransactions(ledgerBegin, None, getAllContracts)
|
||||
.take(elemsToTake)
|
||||
@ -133,8 +150,8 @@ class TransactionServiceIT
|
||||
val client = context.transactionClient
|
||||
for {
|
||||
le <- client.getLedgerEnd
|
||||
_ <- insertCommands("deduplicated", 1, context)
|
||||
_ = insertCommands("deduplicated", 1, context) // we don't wait for this since the result won't be seen
|
||||
_ <- insertCommandsUnique("deduplicated", 1, context)
|
||||
_ = insertCommandsUnique("deduplicated", 1, context) // we don't wait for this since the result won't be seen
|
||||
txs <- client
|
||||
.getTransactions(le.getOffset, None, getAllContracts)
|
||||
.takeWithin(2.seconds)
|
||||
@ -161,11 +178,7 @@ class TransactionServiceIT
|
||||
.runWith(Sink.seq)
|
||||
|
||||
for {
|
||||
_ <- insertCommands(
|
||||
getTrackerFlow(context),
|
||||
"stream-completion-test",
|
||||
14,
|
||||
context.ledgerId)
|
||||
_ <- insertCommandsUnique("stream-completion-test", 14, context)
|
||||
_ <- resultsF
|
||||
} yield {
|
||||
succeed // resultF would not complete unless the server terminates the connection
|
||||
@ -182,7 +195,7 @@ class TransactionServiceIT
|
||||
|
||||
for {
|
||||
ledgerEndResponse <- client.getLedgerEnd
|
||||
_ <- insertCommands(firstSectionPrefix, commandsPerSection, context)
|
||||
_ <- insertCommandsUnique(firstSectionPrefix, commandsPerSection, context)
|
||||
firstSection <- client
|
||||
.getTransactions(ledgerEndResponse.getOffset, None, getAllContracts)
|
||||
.filter(_.commandId.startsWith(sharedPrefix))
|
||||
@ -191,7 +204,7 @@ class TransactionServiceIT
|
||||
_ = firstSection should have size commandsPerSection.toLong
|
||||
ledgerEndAfterFirstSection = lastOffsetIn(firstSection).value
|
||||
|
||||
_ <- insertCommands(sharedPrefix + "-2", commandsPerSection, context)
|
||||
_ <- insertCommandsUnique(sharedPrefix + "-2", commandsPerSection, context)
|
||||
|
||||
secondSection <- client
|
||||
.getTransactions(ledgerEndAfterFirstSection, None, getAllContracts)
|
||||
@ -223,7 +236,7 @@ class TransactionServiceIT
|
||||
|
||||
for {
|
||||
ledgerEndOnStart <- client.getLedgerEnd
|
||||
_ <- insertCommands(commandPrefix, smallCommandCount, context)
|
||||
_ <- insertCommandsUnique(commandPrefix, smallCommandCount, context)
|
||||
readTransactions = () =>
|
||||
client
|
||||
.getTransactions(ledgerEndOnStart.getOffset, None, getAllContracts)
|
||||
@ -251,7 +264,7 @@ class TransactionServiceIT
|
||||
val anotherParty = "Alice"
|
||||
for {
|
||||
ledgerEndResponse <- client.getLedgerEnd
|
||||
_ <- insertCommands(commandPrefix, 1, context)
|
||||
_ <- insertCommandsUnique(commandPrefix, 1, context)
|
||||
// At this point we verified that the value has been written to the submitter's LSM.
|
||||
// This test code assumes that the value would be written to other parties' LSMs within 100 ms.
|
||||
transactions <- client
|
||||
@ -299,7 +312,7 @@ class TransactionServiceIT
|
||||
|
||||
for {
|
||||
savedLedgerEnd <- client.getLedgerEnd
|
||||
_ <- insertCommands("end-before-start-test", 1, context)
|
||||
_ <- insertCommandsUnique(s"end-before-start-test", 1, context)
|
||||
tx <- client
|
||||
.getTransactions(savedLedgerEnd.getOffset, None, getAllContracts)
|
||||
.runWith(Sink.head)
|
||||
@ -319,7 +332,7 @@ class TransactionServiceIT
|
||||
"expose transactions to non-submitting stakeholders without the commandId" in allFixtures {
|
||||
c =>
|
||||
c.submitCreateAndReturnTransaction(
|
||||
"Checking_commandId_visibility_for_non-submitter_party",
|
||||
s"Checking_commandId_visibility_for_non-submitter_party-${runSuffix}",
|
||||
templateIds.agreementFactory,
|
||||
List("receiver" -> party1.asParty, "giver" -> party2.asParty).asRecordFields,
|
||||
party2,
|
||||
@ -330,7 +343,7 @@ class TransactionServiceIT
|
||||
}
|
||||
|
||||
"expose only the requested templates to the client" in allFixtures { context =>
|
||||
val commandId = "Client_should_see_only_the_Dummy_create."
|
||||
val commandId = s"Client_should_see_only_the_Dummy_create-${runSuffix}"
|
||||
val templateInSubscription = templateIds.dummy
|
||||
val otherTemplateCreated = templateIds.dummyFactory
|
||||
for {
|
||||
@ -353,8 +366,8 @@ class TransactionServiceIT
|
||||
|
||||
"expose contract Ids that are ready to be used for exercising choices" in allFixtures {
|
||||
context =>
|
||||
val factoryCreation = "Creating_factory"
|
||||
val exercisingChoice = "Exercising_choice_on_factory"
|
||||
val factoryCreation = s"Creating_factory-${runSuffix}"
|
||||
val exercisingChoice = s"Exercising_choice_on_factory-${runSuffix}"
|
||||
val exercisedTemplate = templateIds.dummyFactory
|
||||
for {
|
||||
createdEvent <- context.submitCreate(
|
||||
@ -379,8 +392,8 @@ class TransactionServiceIT
|
||||
|
||||
"expose contract Ids that are results of exercising choices when filtering by template" in allFixtures {
|
||||
context =>
|
||||
val factoryCreation = "Creating_second_factory"
|
||||
val exercisingChoice = "Exercising_choice_on_second_factory"
|
||||
val factoryCreation = s"Creating_second_factory-${runSuffix}"
|
||||
val exercisingChoice = s"Exercising_choice_on_second_factory-${runSuffix}"
|
||||
val exercisedTemplate = templateIds.dummyFactory
|
||||
for {
|
||||
creation <- context.submitCreate(
|
||||
@ -423,7 +436,7 @@ class TransactionServiceIT
|
||||
"reject exercising a choice where an assertion fails" in allFixtures { c =>
|
||||
for {
|
||||
dummy <- c.submitCreate(
|
||||
"Create_for_assertion_failing_test",
|
||||
s"Create_for_assertion_failing_test-${runSuffix}",
|
||||
templateIds.dummy,
|
||||
List("operator" -> party.asParty).asRecordFields,
|
||||
party)
|
||||
@ -452,7 +465,7 @@ class TransactionServiceIT
|
||||
val expectedArg = paramShowcaseArgsWithoutLabels
|
||||
for {
|
||||
create <- c.submitCreate(
|
||||
"Creating_contract_with_a_multitude_of_param_types",
|
||||
s"Creating_contract_with_a_multitude_of_param_types-${runSuffix}",
|
||||
template,
|
||||
paramShowcaseArgs(templateIds.testPackageId),
|
||||
"party",
|
||||
@ -470,7 +483,7 @@ class TransactionServiceIT
|
||||
val variant = Value(Value.Sum.Variant(Variant(None, "SomeInteger", 1.asInt64)))
|
||||
for {
|
||||
create <- c.submitCreate(
|
||||
"Creating_contract_with_a_multitude_of_verbose_param_types",
|
||||
s"Creating_contract_with_a_multitude_of_verbose_param_types-${runSuffix}",
|
||||
template,
|
||||
arg,
|
||||
"party",
|
||||
@ -523,7 +536,7 @@ class TransactionServiceIT
|
||||
val template = templateIds.parameterShowcase
|
||||
for {
|
||||
create <- c.submitCreate(
|
||||
"Huge_command_with_a_long_list",
|
||||
s"Huge_command_with_a_long_list-${runSuffix}",
|
||||
template,
|
||||
arg,
|
||||
"party"
|
||||
@ -538,15 +551,16 @@ class TransactionServiceIT
|
||||
val giver = "Alice"
|
||||
for {
|
||||
created <- c.submitCreateWithListenerAndReturnEvent(
|
||||
"Creating_Agreement_Factory",
|
||||
s"Creating_Agreement_Factory-${runSuffix}",
|
||||
templateIds.agreementFactory,
|
||||
List("receiver" -> receiver.asParty, "giver" -> giver.asParty).asRecordFields,
|
||||
giver,
|
||||
giver)
|
||||
giver
|
||||
)
|
||||
|
||||
choiceResult <- c.testingHelpers.submitAndListenForSingleResultOfCommand(
|
||||
c.command(
|
||||
"Calling_non-consuming_choice",
|
||||
s"Calling_non-consuming_choice-${runSuffix}",
|
||||
List(
|
||||
ExerciseCommand(
|
||||
Some(templateIds.agreementFactory),
|
||||
@ -572,7 +586,7 @@ class TransactionServiceIT
|
||||
|
||||
for {
|
||||
branchingSignatories <- c.submitCreateWithListenerAndReturnEvent(
|
||||
"BranchingSignatoriesTrue",
|
||||
s"BranchingSignatoriesTrue-${runSuffix}",
|
||||
templateIds.branchingSignatories,
|
||||
branchingSignatoriesArg,
|
||||
party1,
|
||||
@ -586,7 +600,7 @@ class TransactionServiceIT
|
||||
val branchingSignatoriesArg =
|
||||
getBranchingSignatoriesArg(false, party1, party2)
|
||||
c.submitCreateWithListenerAndAssertNotVisible(
|
||||
"BranchingSignatoriesFalse",
|
||||
s"BranchingSignatoriesFalse-${runSuffix}",
|
||||
templateIds.branchingSignatories,
|
||||
branchingSignatoriesArg,
|
||||
party2,
|
||||
@ -599,7 +613,7 @@ class TransactionServiceIT
|
||||
val expectedArg = branchingControllersArgs.map(_.copy(label = ""))
|
||||
for {
|
||||
branchingControllers <- c.submitCreateWithListenerAndReturnEvent(
|
||||
"BranchingControllersTrue",
|
||||
s"BranchingControllersTrue-${runSuffix}",
|
||||
templateId,
|
||||
branchingControllersArgs,
|
||||
party1,
|
||||
@ -614,7 +628,7 @@ class TransactionServiceIT
|
||||
val branchingControllersArgs =
|
||||
getBranchingControllerArgs(party1, party2, party3, false)
|
||||
c.submitCreateWithListenerAndAssertNotVisible(
|
||||
"BranchingControllersFalse",
|
||||
s"BranchingControllersFalse-${runSuffix}",
|
||||
templateId,
|
||||
branchingControllersArgs,
|
||||
party1,
|
||||
@ -634,7 +648,7 @@ class TransactionServiceIT
|
||||
.sequence(observers.map(observer =>
|
||||
for {
|
||||
withObservers <- c.submitCreateWithListenerAndReturnEvent(
|
||||
"Obs1create:" + observer,
|
||||
s"Obs1create:${observer}-${runSuffix}",
|
||||
templateIds.withObservers,
|
||||
withObserversArg,
|
||||
giver,
|
||||
@ -655,7 +669,7 @@ class TransactionServiceIT
|
||||
val expectedArgs = createArguments.map(_.copy(label = ""))
|
||||
|
||||
c.submitCreate(
|
||||
"Creating_contract_with_a_Nothing_argument",
|
||||
s"Creating_contract_with_a_Nothing_argument-${runSuffix}",
|
||||
templateIds.nothingArgument,
|
||||
createArguments,
|
||||
"party")
|
||||
@ -673,7 +687,7 @@ class TransactionServiceIT
|
||||
"expose the default agreement text in CreatedEvents for templates with no explicit agreement text" in allFixtures {
|
||||
c =>
|
||||
val resultF = c.submitCreate(
|
||||
"Creating_dummy_contract_for_default_agreement_text_test",
|
||||
s"Creating_dummy_contract_for_default_agreement_text_test-${runSuffix}",
|
||||
templateIds.dummy,
|
||||
List(RecordField("operator", party1.asParty)),
|
||||
party1)
|
||||
@ -690,12 +704,12 @@ class TransactionServiceIT
|
||||
for {
|
||||
agreement <- createAgreement(c, "MA1", receiver, giver)
|
||||
triProposal <- c.submitCreate(
|
||||
"MA1proposal",
|
||||
s"MA1proposal-${runSuffix}",
|
||||
templateIds.triProposal,
|
||||
triProposalArg,
|
||||
operator)
|
||||
tx <- c.submitExercise(
|
||||
"MA1acceptance",
|
||||
s"MA1acceptance-${runSuffix}",
|
||||
templateIds.agreement,
|
||||
List("cid" -> Value(ContractId(triProposal.contractId))).asRecordValue,
|
||||
"AcceptTriProposal",
|
||||
@ -716,12 +730,12 @@ class TransactionServiceIT
|
||||
val expectedArg = triProposalArg.map(_.copy(label = ""))
|
||||
for {
|
||||
triProposal <- c.submitCreate(
|
||||
"MA2proposal",
|
||||
s"MA2proposal-${runSuffix}",
|
||||
templateIds.triProposal,
|
||||
triProposalArg,
|
||||
operator)
|
||||
tx <- c.submitExercise(
|
||||
"MA2acceptance",
|
||||
s"MA2acceptance-${runSuffix}",
|
||||
templateIds.triProposal,
|
||||
unitArg,
|
||||
"TriProposalAccept",
|
||||
@ -738,7 +752,7 @@ class TransactionServiceIT
|
||||
val triProposalArg = mkTriProposalArg(operator, receiver, giver)
|
||||
for {
|
||||
triProposal <- c.submitCreate(
|
||||
"MA3proposal",
|
||||
s"MA3proposal-${runSuffix}",
|
||||
templateIds.triProposal,
|
||||
triProposalArg,
|
||||
operator)
|
||||
@ -769,7 +783,7 @@ class TransactionServiceIT
|
||||
for {
|
||||
agreement <- createAgreement(c, "MA4", receiver, giver)
|
||||
triProposal <- c.submitCreate(
|
||||
"MA4proposal",
|
||||
s"MA4proposal-${runSuffix}",
|
||||
templateIds.triProposal,
|
||||
triProposalArg,
|
||||
operator)
|
||||
@ -794,12 +808,12 @@ class TransactionServiceIT
|
||||
val arguments = List("street", "city", "state", "zip")
|
||||
for {
|
||||
dummy <- c.submitCreate(
|
||||
"Create_dummy_for_creating_AddressWrapper",
|
||||
s"Create_dummy_for_creating_AddressWrapper-${runSuffix}",
|
||||
templateIds.dummy,
|
||||
List("operator" -> party.asParty).asRecordFields,
|
||||
party)
|
||||
exercise <- c.submitExercise(
|
||||
"Creating_AddressWrapper",
|
||||
s"Creating_AddressWrapper-${runSuffix}",
|
||||
templateIds.dummy,
|
||||
List("address" -> arguments.map(e => e -> e.asText).asRecordValue).asRecordValue,
|
||||
"WrapWithAddress",
|
||||
@ -860,13 +874,13 @@ class TransactionServiceIT
|
||||
val createAndFetchTid = templateIds.createAndFetch
|
||||
for {
|
||||
createdEvent <- context.submitCreate(
|
||||
"CreateAndFetch_Create",
|
||||
s"CreateAndFetch_Create-$runSuffix",
|
||||
createAndFetchTid,
|
||||
List("p" -> party.asParty).asRecordFields,
|
||||
party)
|
||||
cid = createdEvent.contractId
|
||||
exerciseTx <- context.submitExercise(
|
||||
"CreateAndFetch_Run",
|
||||
s"CreateAndFetch_Run-$runSuffix",
|
||||
createAndFetchTid,
|
||||
Value(Value.Sum.Record(Record())),
|
||||
"CreateAndFetch_Run",
|
||||
@ -916,11 +930,7 @@ class TransactionServiceIT
|
||||
val beginOffset =
|
||||
LedgerOffset(LedgerOffset.Value.Boundary(LedgerOffset.LedgerBoundary.LEDGER_BEGIN))
|
||||
for {
|
||||
_ <- insertCommands(
|
||||
getTrackerFlow(context),
|
||||
"tree-provenance-by-id",
|
||||
1,
|
||||
context.ledgerId)
|
||||
_ <- insertCommandsUnique("tree-provenance-by-id", 1, context)
|
||||
firstTransaction <- context.transactionClient
|
||||
.getTransactions(beginOffset, None, transactionFilter)
|
||||
.runWith(Sink.head)
|
||||
@ -1015,11 +1025,7 @@ class TransactionServiceIT
|
||||
val beginOffset =
|
||||
LedgerOffset(LedgerOffset.Value.Boundary(LedgerOffset.LedgerBoundary.LEDGER_BEGIN))
|
||||
for {
|
||||
_ <- insertCommands(
|
||||
getTrackerFlow(context),
|
||||
"flat-provenance-by-id",
|
||||
1,
|
||||
context.ledgerId)
|
||||
_ <- insertCommandsUnique("flat-provenance-by-id", 1, context)
|
||||
firstTransaction <- context.transactionClient
|
||||
.getTransactions(beginOffset, None, transactionFilter)
|
||||
.runWith(Sink.head)
|
||||
@ -1085,11 +1091,7 @@ class TransactionServiceIT
|
||||
val beginOffset =
|
||||
LedgerOffset(LedgerOffset.Value.Boundary(LedgerOffset.LedgerBoundary.LEDGER_BEGIN))
|
||||
for {
|
||||
_ <- insertCommands(
|
||||
getTrackerFlow(context),
|
||||
"tree-provenance-by-event-id",
|
||||
1,
|
||||
context.ledgerId)
|
||||
_ <- insertCommandsUnique("tree-provenance-by-event-id", 1, context)
|
||||
tx <- context.transactionClient
|
||||
.getTransactions(beginOffset, None, transactionFilter)
|
||||
.runWith(Sink.head)
|
||||
@ -1154,11 +1156,7 @@ class TransactionServiceIT
|
||||
val beginOffset =
|
||||
LedgerOffset(LedgerOffset.Value.Boundary(LedgerOffset.LedgerBoundary.LEDGER_BEGIN))
|
||||
for {
|
||||
_ <- insertCommands(
|
||||
getTrackerFlow(context),
|
||||
"flat-provenance-by-event-id",
|
||||
1,
|
||||
context.ledgerId)
|
||||
_ <- insertCommandsUnique("flat-provenance-by-event-id", 1, context)
|
||||
tx <- context.transactionClient
|
||||
.getTransactions(beginOffset, None, transactionFilter)
|
||||
.runWith(Sink.head)
|
||||
@ -1305,11 +1303,7 @@ class TransactionServiceIT
|
||||
.runWith(Sink.seq)
|
||||
|
||||
for {
|
||||
_ <- insertCommands(
|
||||
getTrackerFlow(context),
|
||||
"cancellation-test-tree",
|
||||
commandsToSend,
|
||||
context.ledgerId)
|
||||
_ <- insertCommandsUnique("cancellation-test-tree", commandsToSend, context)
|
||||
elems <- resultsF
|
||||
} yield (elems should have length elemsToTake)
|
||||
}
|
||||
@ -1368,11 +1362,7 @@ class TransactionServiceIT
|
||||
r1 <- context.transactionClient
|
||||
.getTransactionTrees(ledgerBegin, Some(ledgerEnd), transactionFilter)
|
||||
.runWith(Sink.seq)
|
||||
_ <- insertCommands(
|
||||
getTrackerFlow(context),
|
||||
"complete_test",
|
||||
noOfCommands,
|
||||
context.ledgerId)
|
||||
_ <- insertCommandsUnique("complete_test", noOfCommands, context)
|
||||
r2 <- context.transactionClient
|
||||
.getTransactionTrees(ledgerBegin, Some(ledgerEnd), transactionFilter)
|
||||
.runWith(Sink.seq)
|
||||
@ -1509,7 +1499,27 @@ class TransactionServiceIT
|
||||
prefix: String,
|
||||
commandsPerSection: Int,
|
||||
context: LedgerContext): Future[Done] = {
|
||||
insertCommands(getTrackerFlow(context), prefix, commandsPerSection, context.ledgerId)
|
||||
helpers.insertCommands(
|
||||
request => applyTimeAndSubmit(request, context),
|
||||
prefix,
|
||||
commandsPerSection,
|
||||
context.ledgerId)
|
||||
}
|
||||
|
||||
private def applyTimeAndSubmit(req: SubmitAndWaitRequest, context: LedgerContext) = {
|
||||
context.commandClient().flatMap { client =>
|
||||
val ttl = Duration.ofMillis(config.commandConfiguration.commandTtl.toMillis)
|
||||
val updater = new CommandUpdater(client.timeProviderO, ttl, true)
|
||||
val reqToSend = req.copy(commands = req.commands.map(updater.applyOverrides))
|
||||
context.commandService.submitAndWaitForTransactionId(reqToSend)
|
||||
}
|
||||
}
|
||||
|
||||
private def insertCommandsUnique(
|
||||
prefix: String,
|
||||
commandsPerSection: Int,
|
||||
context: LedgerContext): Future[Done] = {
|
||||
insertCommands(s"${prefix}-${runSuffix}", commandsPerSection, context)
|
||||
}
|
||||
|
||||
private def lastOffsetIn(secondSection: immutable.Seq[Transaction]): Option[LedgerOffset] = {
|
||||
@ -1600,15 +1610,16 @@ class TransactionServiceIT
|
||||
): Future[CreatedEvent] = {
|
||||
for {
|
||||
agreementFactory <- c.submitCreate(
|
||||
commandId + "factory_creation",
|
||||
commandId + s"factory_creation-${runSuffix}",
|
||||
templateIds.agreementFactory,
|
||||
List(
|
||||
"receiver" -> receiver.asParty,
|
||||
"giver" -> giver.asParty
|
||||
).asRecordFields,
|
||||
giver)
|
||||
giver
|
||||
)
|
||||
tx <- c.submitExercise(
|
||||
commandId + "_acceptance",
|
||||
commandId + s"_acceptance-${runSuffix}",
|
||||
templateIds.agreementFactory,
|
||||
unitArg,
|
||||
"AgreementFactoryAccept",
|
||||
@ -1632,7 +1643,7 @@ class TransactionServiceIT
|
||||
): Future[Assertion] =
|
||||
c.testingHelpers.assertCommandFailsWithCode(
|
||||
c.command(
|
||||
commandId,
|
||||
s"${commandId}-${runSuffix}",
|
||||
List(ExerciseCommand(Some(template), contractId, choice, Some(arg)).wrap))
|
||||
.update(
|
||||
_.commands.party := submitter
|
||||
@ -1650,7 +1661,7 @@ class TransactionServiceIT
|
||||
|
||||
for {
|
||||
creation <- context.submitCreate(
|
||||
s"Creating_contract_with_a_multitude_of_param_types_for_exercising_$choice#$lbl",
|
||||
s"Creating_contract_with_a_multitude_of_param_types_for_exercising-${runSuffix}_$choice#$lbl",
|
||||
templateIds.parameterShowcase,
|
||||
paramShowcaseArgs(templateIds.testPackageId),
|
||||
MockMessages.party
|
||||
@ -1664,7 +1675,9 @@ class TransactionServiceIT
|
||||
exerciseArg).wrap
|
||||
exerciseTx <- context.testingHelpers.submitAndListenForSingleResultOfCommand(
|
||||
context
|
||||
.command(s"Exercising_with_a_multitiude_of_params_$choice#$lbl", List(exerciseCommand)),
|
||||
.command(
|
||||
s"Exercising_with_a_multitiude_of_params-${runSuffix}_$choice#$lbl",
|
||||
List(exerciseCommand)),
|
||||
getAllContracts
|
||||
)
|
||||
} yield {
|
||||
|
@ -33,15 +33,17 @@ class TransactionServiceLargeCommandIT
|
||||
with Inside
|
||||
with AsyncTimeLimitedTests
|
||||
with TestExecutionSequencerFactory
|
||||
with TransactionServiceHelpers
|
||||
with ParameterShowcaseTesting
|
||||
with OptionValues
|
||||
with Matchers
|
||||
with TestTemplateIds {
|
||||
with Matchers {
|
||||
|
||||
override protected val config: Config =
|
||||
Config.default.withTimeProvider(TimeProviderType.Static)
|
||||
|
||||
protected val helpers = new TransactionServiceHelpers(config)
|
||||
protected val testTemplateIds = new TestTemplateIds(config)
|
||||
protected val templateIds = testTemplateIds.templateIds
|
||||
|
||||
override val timeLimit: Span = 300.seconds
|
||||
|
||||
private val getAllContracts = transactionFilter
|
||||
|
@ -29,10 +29,12 @@ class WitnessesIT
|
||||
with SuiteResourceManagementAroundEach
|
||||
with ScalaFutures
|
||||
with AsyncTimeLimitedTests
|
||||
with Matchers
|
||||
with TestTemplateIds {
|
||||
with Matchers {
|
||||
override protected def config: Config = Config.default
|
||||
|
||||
protected val testTemplateIds = new TestTemplateIds(config)
|
||||
protected val templateIds = testTemplateIds.templateIds
|
||||
|
||||
private def commandClient(ctx: LedgerContext): SynchronousCommandClient =
|
||||
new SynchronousCommandClient(ctx.commandService)
|
||||
|
||||
|
@ -22,10 +22,12 @@ class WronglyTypedContractIdIT
|
||||
with SuiteResourceManagementAroundEach
|
||||
with ScalaFutures
|
||||
with AsyncTimeLimitedTests
|
||||
with Matchers
|
||||
with TestTemplateIds {
|
||||
with Matchers {
|
||||
override protected def config: Config = Config.default
|
||||
|
||||
protected val testTemplateIds = new TestTemplateIds(config)
|
||||
protected val templateIds = testTemplateIds.templateIds
|
||||
|
||||
def createDummy(ctx: LedgerContext) = ctx.testingHelpers.simpleCreate(
|
||||
"create-dummy",
|
||||
"alice",
|
||||
|
@ -46,8 +46,10 @@ class CommandClientIT
|
||||
with SuiteResourceManagementAroundAll
|
||||
with ParameterShowcaseTesting
|
||||
with TryValues
|
||||
with MultiLedgerCommandUtils
|
||||
with TestTemplateIds {
|
||||
with MultiLedgerCommandUtils {
|
||||
|
||||
protected val testTemplateIds = new TestTemplateIds(config)
|
||||
protected val templateIds = testTemplateIds.templateIds
|
||||
|
||||
private val submittingParty: String = submitRequest.getCommands.party
|
||||
private val submittingPartyList = List(submittingParty)
|
||||
|
@ -46,9 +46,11 @@ class CommandStaticTimeIT
|
||||
with ScalaFutures
|
||||
with SuiteResourceManagementAroundAll
|
||||
with MultiLedgerCommandUtils
|
||||
with TestTemplateIds
|
||||
with OptionValues {
|
||||
|
||||
protected val testTemplateIds = new TestTemplateIds(config)
|
||||
protected val templateIds = testTemplateIds.templateIds
|
||||
|
||||
override def timeLimit: Span = 15.seconds
|
||||
|
||||
private val tenDays: time.Duration = java.time.Duration.ofDays(10L)
|
||||
|
@ -78,7 +78,7 @@ class FailingCommandsIT
|
||||
.runWith(Sink.head)
|
||||
} yield {
|
||||
result.value should matchPattern {
|
||||
case Completion(`failingCommandId`, Some(status), _, _) if status.code == 3 =>
|
||||
case Completion(helpers.failingCommandId, Some(status), _, _) if status.code == 3 =>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,8 +35,9 @@ class TransactionBackpressureIT
|
||||
with ScalaFutures
|
||||
with AkkaBeforeAndAfterAll
|
||||
with SuiteResourceManagementAroundAll
|
||||
with MultiLedgerFixture
|
||||
with TestCommands {
|
||||
with MultiLedgerFixture {
|
||||
|
||||
protected val testCommands = new TestCommands(config)
|
||||
|
||||
override def timeLimit: Span = 300.seconds
|
||||
|
||||
@ -63,7 +64,8 @@ class TransactionBackpressureIT
|
||||
Source(1 to noOfCommands)
|
||||
.throttle(10, 1.second)
|
||||
.mapAsync(10)(i =>
|
||||
commandClient.submitSingleCommand(oneKbCommandRequest(ctx.ledgerId, s"command-$i")))
|
||||
commandClient.submitSingleCommand(
|
||||
testCommands.oneKbCommandRequest(ctx.ledgerId, s"command-$i")))
|
||||
.runWith(Sink.ignore)
|
||||
|
||||
def subscribe(rate: Int) =
|
||||
|
@ -60,11 +60,13 @@ abstract class CommandTransactionChecks
|
||||
with ScalaFutures
|
||||
with AsyncTimeLimitedTests
|
||||
with Matchers
|
||||
with OptionValues
|
||||
with TestTemplateIds {
|
||||
with OptionValues {
|
||||
protected def submitCommand(ctx: LedgerContext, req: SubmitRequest): Future[Completion]
|
||||
|
||||
override protected val config: Config = Config.default
|
||||
protected val testTemplateIds = new TestTemplateIds(config)
|
||||
protected val templateIds = testTemplateIds.templateIds
|
||||
|
||||
override protected def config: Config = Config.default
|
||||
|
||||
private lazy val dummyTemplates =
|
||||
List(templateIds.dummy, templateIds.dummyFactory, templateIds.dummyWithParam)
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
package com.digitalasset.platform.apitesting
|
||||
|
||||
import java.io.File
|
||||
import java.util
|
||||
|
||||
import com.digitalasset.daml.lf.UniversalArchiveReader
|
||||
import com.digitalasset.ledger.api.domain
|
||||
import com.digitalasset.ledger.api.testing.utils.MockMessages.{
|
||||
applicationId,
|
||||
@ -19,19 +21,22 @@ import com.digitalasset.ledger.api.v1.commands.{Command, CreateCommand, Exercise
|
||||
import com.digitalasset.ledger.api.v1.value.Value.Sum
|
||||
import com.digitalasset.ledger.api.v1.value.Value.Sum.{Party, Text}
|
||||
import com.digitalasset.ledger.api.v1.value.{Identifier, Record, RecordField, Value}
|
||||
import com.digitalasset.platform.tests.integration.ledger.api.TransactionServiceHelpers
|
||||
import com.digitalasset.platform.PlatformApplications
|
||||
import com.google.protobuf.timestamp.Timestamp
|
||||
|
||||
import scalaz.syntax.tag._
|
||||
|
||||
trait TestTemplateIds extends TransactionServiceHelpers {
|
||||
protected lazy val templateIds: TestTemplateIdentifiers = {
|
||||
new TestTemplateIdentifiers(parsedPackageId)
|
||||
}
|
||||
class TestTemplateIds(config: PlatformApplications.Config) {
|
||||
lazy val defaultDar: File = config.darFiles.head.toFile
|
||||
lazy val parsedPackageId: String =
|
||||
UniversalArchiveReader().readFile(defaultDar).get.main._1
|
||||
lazy val templateIds: TestTemplateIdentifiers = new TestTemplateIdentifiers(parsedPackageId)
|
||||
}
|
||||
|
||||
trait TestCommands extends TestTemplateIds {
|
||||
protected def buildRequest(
|
||||
class TestCommands(config: PlatformApplications.Config) {
|
||||
protected val testIds = new TestTemplateIds(config)
|
||||
val templateIds = testIds.templateIds
|
||||
|
||||
def buildRequest(
|
||||
ledgerId: domain.LedgerId,
|
||||
commandId: String,
|
||||
commands: Seq[Command],
|
||||
@ -49,10 +54,7 @@ trait TestCommands extends TestTemplateIds {
|
||||
_.commands.maximumRecordTime := maxRecordTime
|
||||
)
|
||||
|
||||
protected def dummyCommands(
|
||||
ledgerId: domain.LedgerId,
|
||||
commandId: String,
|
||||
party: String = "party") =
|
||||
def dummyCommands(ledgerId: domain.LedgerId, commandId: String, party: String = "party") =
|
||||
buildRequest(
|
||||
ledgerId,
|
||||
commandId,
|
||||
@ -64,7 +66,7 @@ trait TestCommands extends TestTemplateIds {
|
||||
party
|
||||
)
|
||||
|
||||
protected def createWithOperator(templateId: Identifier, party: String = "party") =
|
||||
def createWithOperator(templateId: Identifier, party: String = "party") =
|
||||
Command(
|
||||
Create(CreateCommand(
|
||||
Some(templateId),
|
||||
@ -77,7 +79,7 @@ trait TestCommands extends TestTemplateIds {
|
||||
new String(array)
|
||||
}
|
||||
|
||||
protected def oneKbCommand(templateId: Identifier) =
|
||||
def oneKbCommand(templateId: Identifier) =
|
||||
Command(
|
||||
Create(
|
||||
CreateCommand(
|
||||
@ -91,21 +93,19 @@ trait TestCommands extends TestTemplateIds {
|
||||
)))
|
||||
)))
|
||||
|
||||
protected def oneKbCommandRequest(
|
||||
def oneKbCommandRequest(
|
||||
ledgerId: domain.LedgerId,
|
||||
commandId: String,
|
||||
party: String = "party"): SubmitRequest =
|
||||
buildRequest(ledgerId, commandId, List(oneKbCommand(templateIds.textContainer)), party)
|
||||
|
||||
protected def exerciseWithUnit(
|
||||
def exerciseWithUnit(
|
||||
templateId: Identifier,
|
||||
contractId: String,
|
||||
choice: String,
|
||||
args: Option[Value] = Some(Value(Sum.Record(Record.defaultInstance)))) =
|
||||
Command(Exercise(ExerciseCommand(Some(templateId), contractId, choice, args)))
|
||||
|
||||
implicit class SubmitRequestEnhancer(request: SubmitRequest) {
|
||||
def toWait: SubmitAndWaitRequest = SubmitAndWaitRequest(request.commands)
|
||||
}
|
||||
def toWait(request: SubmitRequest): SubmitAndWaitRequest = SubmitAndWaitRequest(request.commands)
|
||||
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ import com.google.rpc.status.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import org.scalatest.concurrent.Waiters
|
||||
import org.scalatest.{Assertion, Inside, Matchers, OptionValues}
|
||||
|
||||
import scalaz.syntax.tag._
|
||||
|
||||
import scala.collection.{breakOut, immutable}
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future}
|
||||
|
@ -7,25 +7,26 @@ import java.io.File
|
||||
|
||||
import akka.Done
|
||||
import akka.stream.Materializer
|
||||
import akka.stream.scaladsl.{Flow, Sink, Source}
|
||||
import akka.stream.scaladsl.{Sink, Source}
|
||||
import com.digitalasset.daml.lf.UniversalArchiveReader
|
||||
import com.digitalasset.ledger.api.domain
|
||||
import com.digitalasset.ledger.api.testing.utils.MockMessages.submitRequest
|
||||
import com.digitalasset.ledger.api.v1.command_submission_service.SubmitRequest
|
||||
import com.digitalasset.ledger.api.testing.utils.MockMessages.submitAndWaitRequest
|
||||
import com.digitalasset.ledger.api.v1.command_service.{
|
||||
SubmitAndWaitForTransactionIdResponse,
|
||||
SubmitAndWaitRequest
|
||||
}
|
||||
import com.digitalasset.ledger.api.v1.commands.Command.Command.Create
|
||||
import com.digitalasset.ledger.api.v1.commands.{Command, CreateCommand}
|
||||
import com.digitalasset.ledger.api.v1.completion.Completion
|
||||
import com.digitalasset.ledger.api.v1.value.Value.Sum
|
||||
import com.digitalasset.ledger.api.v1.value.{Identifier, Record, RecordField, Value}
|
||||
import com.digitalasset.platform.PlatformApplications
|
||||
import com.digitalasset.util.Ctx
|
||||
import org.scalatest.Matchers
|
||||
|
||||
import scalaz.syntax.tag._
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
trait TransactionServiceHelpers extends Matchers {
|
||||
lazy val defaultDar: File = PlatformApplications.Config.defaultDarFile
|
||||
class TransactionServiceHelpers(config: PlatformApplications.Config) extends Matchers {
|
||||
lazy val defaultDar: File = config.darFiles.head.toFile
|
||||
|
||||
lazy val parsedPackageId: String =
|
||||
UniversalArchiveReader().readFile(defaultDar).get.main._1
|
||||
@ -47,15 +48,15 @@ trait TransactionServiceHelpers extends Matchers {
|
||||
.withTemplateId(dummyTemplate)
|
||||
.withCreateArguments(wrongArgs))
|
||||
|
||||
def submitRequestWithId(id: String, command: Command, ledgerId: domain.LedgerId) =
|
||||
submitRequest
|
||||
def submitAndWaitRequestWithId(id: String, command: Command, ledgerId: domain.LedgerId) =
|
||||
submitAndWaitRequest
|
||||
.update(_.commands.commandId := id)
|
||||
.update(_.commands.commands := Seq(command))
|
||||
.update(_.commands.ledgerId := ledgerId.unwrap)
|
||||
|
||||
// TODO command tracking should be used here
|
||||
def insertCommands(
|
||||
trackingFlow: Flow[Ctx[Int, SubmitRequest], Ctx[Int, Completion], _],
|
||||
submit: SubmitAndWaitRequest => Future[SubmitAndWaitForTransactionIdResponse],
|
||||
prefix: String,
|
||||
i: Int,
|
||||
ledgerId: domain.LedgerId,
|
||||
@ -63,15 +64,11 @@ trait TransactionServiceHelpers extends Matchers {
|
||||
val arg =
|
||||
Record(Some(dummyTemplate), Vector(RecordField("operator", Some(Value(Sum.Party(party))))))
|
||||
val command = Create(CreateCommand(Some(dummyTemplate), Some(arg)))
|
||||
Source(for {
|
||||
x <- Range(0, i)
|
||||
req = submitRequestWithId(s"$prefix-$x", Command(command), ledgerId)
|
||||
c = Ctx(x, req)
|
||||
} yield c)
|
||||
.via(trackingFlow)
|
||||
Source(Range(0, i))
|
||||
.map(x => submitAndWaitRequestWithId(s"$prefix-$x", Command(command), ledgerId))
|
||||
.mapAsync(1)(submit)
|
||||
.runWith(Sink.foreach {
|
||||
case Ctx(i, Completion(_, Some(status), transactionId, _)) =>
|
||||
status should have('code (0))
|
||||
case SubmitAndWaitForTransactionIdResponse(transactionId) =>
|
||||
transactionId should not be empty
|
||||
()
|
||||
})
|
||||
|
@ -21,12 +21,14 @@ import scalaz.syntax.tag._
|
||||
Array(
|
||||
"org.wartremover.warts.Any"
|
||||
))
|
||||
trait MultiLedgerCommandUtils extends TransactionServiceHelpers with MultiLedgerFixture {
|
||||
trait MultiLedgerCommandUtils extends MultiLedgerFixture {
|
||||
self: AsyncTestSuite =>
|
||||
|
||||
protected final def newSynchronousCommandClient(ctx: LedgerContext): SynchronousCommandClient =
|
||||
new SynchronousCommandClient(ctx.commandService)
|
||||
|
||||
protected val helpers = new TransactionServiceHelpers(config)
|
||||
|
||||
protected val testLedgerId = domain.LedgerId("ledgerId")
|
||||
protected val testNotLedgerId = domain.LedgerId("hotdog")
|
||||
protected val submitRequest: SubmitRequest =
|
||||
@ -38,10 +40,10 @@ trait MultiLedgerCommandUtils extends TransactionServiceHelpers with MultiLedger
|
||||
Commands()
|
||||
.withParty("Alice")
|
||||
.withLedgerId(testLedgerId.unwrap)
|
||||
.withCommandId(failingCommandId)
|
||||
.withCommandId(helpers.failingCommandId)
|
||||
.withWorkflowId(workflowId)
|
||||
.withApplicationId(applicationId)
|
||||
.withCommands(Seq(wrongCreate))))
|
||||
.withCommands(Seq(helpers.wrongCreate))))
|
||||
|
||||
protected val submitAndWaitRequest: SubmitAndWaitRequest =
|
||||
MockMessages.submitAndWaitRequest
|
||||
|
@ -64,6 +64,7 @@ da_scala_binary(
|
||||
resources = [
|
||||
"src/main/resources/logback.xml",
|
||||
"//ledger/ledger-api-integration-tests:SemanticTests.dar",
|
||||
"//ledger/sandbox:Test.dar",
|
||||
],
|
||||
tags = [
|
||||
"maven_coordinates=com.daml.ledger.testtool:ledger-api-test-tool:__VERSION__",
|
||||
|
@ -64,6 +64,10 @@ object Cli {
|
||||
|Defaults to 1.0. Use numbers higher than 1.0 to make test timeouts more lax,
|
||||
|use numbers lower than 1.0 to make test timeouts more strict.""".stripMargin)
|
||||
|
||||
opt[Unit]("verbose")
|
||||
.action((_, c) => c.copy(verbose = true))
|
||||
.text("Prints full stacktraces on failures.")
|
||||
|
||||
opt[Unit]("must-fail")
|
||||
.action((_, c) => c.copy(mustFail = true))
|
||||
.text("""Reverse success status logic of the tool. Use this flag if you expect one or
|
||||
@ -76,6 +80,19 @@ object Cli {
|
||||
.text("""Extract a DAR necessary to test a DAML ledger and exit without running tests.
|
||||
|The DAR needs to be manually loaded into a DAML ledger for the tool to work.""".stripMargin)
|
||||
|
||||
opt[Seq[String]]("exclude")
|
||||
.action((ex, c) => c.copy(excluded = ex.toSet))
|
||||
.text("""A comma-separated list of tests that should NOT be run. By default, no tests are excluded.""")
|
||||
|
||||
opt[Seq[String]]("include")
|
||||
.action((inc, c) => c.copy(included = inc.toSet))
|
||||
.text(
|
||||
"""A comma-separated list of tests that should be run. By default, all tests are run.""")
|
||||
|
||||
opt[Unit]("list")
|
||||
.action((_, c) => c.copy(listTests = true))
|
||||
.text("""Lists all available tests that can be used in the include and exclude options.""")
|
||||
|
||||
}
|
||||
|
||||
def parse(args: Array[String]): Option[Config] =
|
||||
|
@ -11,9 +11,13 @@ final case class Config(
|
||||
port: Int,
|
||||
packageContainer: DamlPackageContainer,
|
||||
mustFail: Boolean,
|
||||
verbose: Boolean,
|
||||
timeoutScaleFactor: Double,
|
||||
extract: Boolean,
|
||||
tlsConfig: Option[TlsConfiguration]
|
||||
tlsConfig: Option[TlsConfiguration],
|
||||
excluded: Set[String],
|
||||
included: Set[String],
|
||||
listTests: Boolean
|
||||
)
|
||||
|
||||
object Config {
|
||||
@ -22,8 +26,12 @@ object Config {
|
||||
port = 6865,
|
||||
packageContainer = DamlPackageContainer(),
|
||||
mustFail = false,
|
||||
verbose = false,
|
||||
timeoutScaleFactor = 1.0,
|
||||
extract = false,
|
||||
tlsConfig = None
|
||||
tlsConfig = None,
|
||||
excluded = Set.empty,
|
||||
included = Set.empty,
|
||||
listTests = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -6,18 +6,26 @@ package com.daml.ledger.api.testtool
|
||||
import java.io.File
|
||||
import java.nio.file.{Files, Path, Paths, StandardCopyOption}
|
||||
|
||||
import com.digitalasset.platform.PlatformApplications
|
||||
import com.digitalasset.platform.PlatformApplications.RemoteApiEndpoint
|
||||
import com.digitalasset.platform.common.LedgerIdMode
|
||||
import com.digitalasset.platform.semantictest.SandboxSemanticTestsLfRunner
|
||||
import com.digitalasset.platform.services.time.TimeProviderType
|
||||
import com.digitalasset.platform.testing.LedgerBackend
|
||||
import org.scalatest.Args
|
||||
import com.digitalasset.platform.tests.integration.ledger.api.TransactionServiceIT
|
||||
import org.scalatest.time.{Seconds, Span}
|
||||
import org.scalatest.{Args, Suite}
|
||||
|
||||
import scala.util.control.NonFatal
|
||||
|
||||
object LedgerApiTestTool {
|
||||
|
||||
val semanticTestsResource = "/ledger/ledger-api-integration-tests/SemanticTests.dar"
|
||||
val integrationTestResource = "/ledger/sandbox/Test.dar"
|
||||
val testResources = List(
|
||||
integrationTestResource,
|
||||
semanticTestsResource,
|
||||
)
|
||||
def main(args: Array[String]): Unit = {
|
||||
val testResources = List("/ledger/ledger-api-integration-tests/SemanticTests.dar")
|
||||
|
||||
val toolConfig = Cli
|
||||
.parse(args)
|
||||
@ -25,53 +33,68 @@ object LedgerApiTestTool {
|
||||
|
||||
if (toolConfig.extract) {
|
||||
extractTestFiles(testResources)
|
||||
System.exit(0)
|
||||
sys.exit(0)
|
||||
}
|
||||
|
||||
val commonConfig = PlatformApplications.Config.default
|
||||
.withTimeProvider(TimeProviderType.WallClock)
|
||||
.withLedgerIdMode(LedgerIdMode.Dynamic())
|
||||
.withRemoteApiEndpoint(
|
||||
RemoteApiEndpoint.default
|
||||
.withHost(toolConfig.host)
|
||||
.withPort(toolConfig.port)
|
||||
.withTlsConfig(toolConfig.tlsConfig))
|
||||
|
||||
val default = defaultTests(commonConfig)
|
||||
val optional = optionalTests(commonConfig)
|
||||
|
||||
val allTests = default ++ optional
|
||||
|
||||
if (toolConfig.listTests) {
|
||||
println("Tests marked with * are run by default.\n")
|
||||
println(default.keySet.toSeq.sorted.map(_ + " *").mkString("\n"))
|
||||
println(optional.keySet.toSeq.sorted.mkString("\n"))
|
||||
sys.exit(0)
|
||||
}
|
||||
|
||||
var failed = false
|
||||
|
||||
try {
|
||||
|
||||
val integrationTestResourceStream =
|
||||
testResources.headOption.map(getClass.getResourceAsStream(_)).flatMap(Option(_))
|
||||
require(
|
||||
integrationTestResourceStream.isDefined,
|
||||
"Unable to load the required test DAR from resources.")
|
||||
val targetPath: Path = Files.createTempFile("ledger-api-test-tool-", "-test.dar")
|
||||
Files.copy(
|
||||
integrationTestResourceStream.get,
|
||||
targetPath,
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
val testsToRun = (if (toolConfig.included.isEmpty) default.keySet else toolConfig.included)
|
||||
.filterNot(toolConfig.excluded)
|
||||
|
||||
val reporter = new ToolReporter
|
||||
if (testsToRun.isEmpty) {
|
||||
println("No tests to run.")
|
||||
sys.exit(0)
|
||||
}
|
||||
|
||||
val reporter = new ToolReporter(toolConfig.verbose)
|
||||
val sorter = new ToolSorter
|
||||
|
||||
var semanticTestsRunner = new SandboxSemanticTestsLfRunner {
|
||||
override def suiteName: String = "Semantic Tests"
|
||||
override def actorSystemName = "SandboxSemanticTestsLfRunnerTestToolActorSystem"
|
||||
testsToRun
|
||||
.find(n => !allTests.contains(n))
|
||||
.foreach { unknownSuite =>
|
||||
println(s"Unknown Test: $unknownSuite")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
override def fixtureIdsEnabled: Set[LedgerBackend] =
|
||||
Set(LedgerBackend.RemoteApiProxy)
|
||||
|
||||
override implicit lazy val patienceConfig: PatienceConfig = PatienceConfig(
|
||||
Span(60L, Seconds))
|
||||
|
||||
override protected def config: Config =
|
||||
Config.default
|
||||
.withTimeProvider(TimeProviderType.WallClock)
|
||||
.withLedgerIdMode(LedgerIdMode.Dynamic())
|
||||
.withRemoteApiEndpoint(
|
||||
RemoteApiEndpoint.default
|
||||
.withHost(toolConfig.host)
|
||||
.withPort(toolConfig.port)
|
||||
.withTlsConfig(toolConfig.tlsConfig))
|
||||
.withDarFile(targetPath)
|
||||
testsToRun.foreach { suiteName =>
|
||||
try {
|
||||
allTests(suiteName)()
|
||||
.run(None, Args(reporter = reporter, distributedTestSorter = Some(sorter)))
|
||||
} catch {
|
||||
case NonFatal(t) =>
|
||||
failed = true
|
||||
}
|
||||
()
|
||||
}
|
||||
semanticTestsRunner.run(None, Args(reporter = reporter, distributedTestSorter = Some(sorter)))
|
||||
|
||||
failed |= reporter.statistics.testsStarted != reporter.statistics.testsSucceeded
|
||||
reporter.printStatistics
|
||||
} catch {
|
||||
case (t: Throwable) =>
|
||||
case NonFatal(t) if toolConfig.mustFail =>
|
||||
failed = true
|
||||
if (!toolConfig.mustFail) throw t
|
||||
}
|
||||
|
||||
if (toolConfig.mustFail) {
|
||||
@ -79,9 +102,66 @@ object LedgerApiTestTool {
|
||||
else
|
||||
throw new RuntimeException(
|
||||
"None of the scenarios failed, yet the --must-fail flag was specified!")
|
||||
} else {
|
||||
if (failed) {
|
||||
sys.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def defaultTests(commonConfig: PlatformApplications.Config): Map[String, () => Suite] = {
|
||||
val semanticTestsRunner = lazyInit(
|
||||
"SemanticTests",
|
||||
name =>
|
||||
new SandboxSemanticTestsLfRunner {
|
||||
override def suiteName: String = name
|
||||
|
||||
override def actorSystemName = s"${name}TestToolActorSystem"
|
||||
|
||||
override def fixtureIdsEnabled: Set[LedgerBackend] = Set(LedgerBackend.RemoteApiProxy)
|
||||
|
||||
override implicit lazy val patienceConfig: PatienceConfig =
|
||||
PatienceConfig(Span(60L, Seconds))
|
||||
|
||||
override protected def config: Config =
|
||||
commonConfig.withDarFile(resourceAsFile(semanticTestsResource))
|
||||
}
|
||||
)
|
||||
Map(semanticTestsRunner)
|
||||
}
|
||||
private def optionalTests(commonConfig: PlatformApplications.Config): Map[String, () => Suite] = {
|
||||
|
||||
val transactionServiceIT = lazyInit(
|
||||
"TransactionServiceTests",
|
||||
name =>
|
||||
new TransactionServiceIT {
|
||||
override def suiteName: String = name
|
||||
override def actorSystemName = s"${name}ToolActorSystem"
|
||||
override def fixtureIdsEnabled: Set[LedgerBackend] = Set(LedgerBackend.RemoteApiProxy)
|
||||
|
||||
override protected def config: Config =
|
||||
commonConfig.withDarFile(resourceAsFile(integrationTestResource))
|
||||
}
|
||||
)
|
||||
|
||||
Map(transactionServiceIT)
|
||||
}
|
||||
|
||||
def lazyInit[A](name: String, factory: String => A): (String, () => A) = {
|
||||
(name, () => factory(name))
|
||||
}
|
||||
|
||||
private def resourceAsFile(testResource: String): Path = {
|
||||
val integrationTestResourceStream =
|
||||
Option(getClass.getResourceAsStream(testResource))
|
||||
require(
|
||||
integrationTestResourceStream.isDefined,
|
||||
"Unable to load the required test DAR from resources.")
|
||||
val targetPath: Path = Files.createTempFile("ledger-api-test-tool-", "-test.dar")
|
||||
Files.copy(integrationTestResourceStream.get, targetPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
targetPath
|
||||
}
|
||||
|
||||
private def extractTestFiles(testResources: List[String]): Unit = {
|
||||
val pwd = Paths.get(".").toAbsolutePath
|
||||
println(s"Extracting all DAML resources necessary to run the tests into $pwd.")
|
||||
|
@ -12,7 +12,12 @@ import org.scalatest.Reporter
|
||||
* Ledger API Test Tool CLI reporter. Implements scalatest's Reporter interface and prints out colorized reports to the
|
||||
* stdout. Supports very limited set of scalatest events.
|
||||
*/
|
||||
class ToolReporter extends Reporter {
|
||||
class ToolReporter(verbose: Boolean) extends Reporter {
|
||||
case class Statistics(
|
||||
testsStarted: Int,
|
||||
testsSucceeded: Int,
|
||||
testsCancelled: Int,
|
||||
testsFailed: Int)
|
||||
|
||||
final val ansiReset = "\u001b[0m"
|
||||
final val ansiBlue = "\u001b[34m"
|
||||
@ -24,6 +29,10 @@ class ToolReporter extends Reporter {
|
||||
private def repeatChar(char: Char, n: Int) = char.toString * n
|
||||
|
||||
private var depth = 0
|
||||
private var testsStarted = 0
|
||||
private var testsSucceeded = 0
|
||||
private var testsCancelled = 0
|
||||
private var testsFailed = 0
|
||||
|
||||
private def indented(s: String, extra: Integer = 0, prefix: Char = '-') = {
|
||||
s.split("\n").map(indentedSingle(_, extra, prefix)).mkString("\n")
|
||||
@ -54,6 +63,7 @@ class ToolReporter extends Reporter {
|
||||
payload,
|
||||
threadName,
|
||||
timeStamp) =>
|
||||
testsStarted += 1
|
||||
print(indented(ansiBlue + testText + "... "))
|
||||
|
||||
case e.TestSucceeded(
|
||||
@ -71,6 +81,7 @@ class ToolReporter extends Reporter {
|
||||
payload,
|
||||
threadName,
|
||||
timeStamp) =>
|
||||
testsSucceeded += 1
|
||||
println(ansiGreen + "✓")
|
||||
|
||||
case e.TestCanceled(
|
||||
@ -90,6 +101,7 @@ class ToolReporter extends Reporter {
|
||||
payload,
|
||||
threadName,
|
||||
timeStamp) =>
|
||||
testsCancelled += 1
|
||||
println(ansiRed + "cancelled.")
|
||||
|
||||
case e.TestFailed(
|
||||
@ -109,13 +121,18 @@ class ToolReporter extends Reporter {
|
||||
payload,
|
||||
threadName,
|
||||
timeStamp) =>
|
||||
testsFailed += 1
|
||||
println(ansiRed + "✗" + ansiReset)
|
||||
throwable match {
|
||||
case None =>
|
||||
println(indented(ansiRed + s"Exception details missing!", 1, ' '))
|
||||
case Some(e) =>
|
||||
println(indented(s"Failure details:", 1, ' '))
|
||||
val st = ExceptionUtils.getStackTrace(e)
|
||||
val st = if (verbose) {
|
||||
ExceptionUtils.getStackTrace(e)
|
||||
} else {
|
||||
e.getMessage
|
||||
}
|
||||
println(indented(st, 2, '|'))
|
||||
}
|
||||
|
||||
@ -151,4 +168,22 @@ class ToolReporter extends Reporter {
|
||||
print(ansiReset)
|
||||
}
|
||||
|
||||
def statistics = Statistics(testsStarted, testsSucceeded, testsCancelled, testsFailed)
|
||||
|
||||
def printStatistics = {
|
||||
statistics match {
|
||||
case Statistics(0, _, _, _) =>
|
||||
println(ansiYellow + "No tests were run" + ansiReset)
|
||||
case Statistics(a, s, 0, 0) =>
|
||||
println(ansiGreen + s"All ${s}/${a} tests were successful!" + ansiReset)
|
||||
case Statistics(a, s, c, 0) =>
|
||||
println(ansiYellow + s"${s}/${a} tests were successful, but ${c} were skipped." + ansiReset)
|
||||
case Statistics(a, s, 0, f) =>
|
||||
println(ansiRed + s"${s} were successful and ${f} failed out of ${a} tests." + ansiReset)
|
||||
case _ =>
|
||||
println("BUG")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user