Daml.Ledger.Abstractions.Testing.Conformance
0.5.0-preview.2
Prefix Reserved
dotnet add package Daml.Ledger.Abstractions.Testing.Conformance --version 0.5.0-preview.2
NuGet\Install-Package Daml.Ledger.Abstractions.Testing.Conformance -Version 0.5.0-preview.2
<PackageReference Include="Daml.Ledger.Abstractions.Testing.Conformance" Version="0.5.0-preview.2" />
<PackageVersion Include="Daml.Ledger.Abstractions.Testing.Conformance" Version="0.5.0-preview.2" />
<PackageReference Include="Daml.Ledger.Abstractions.Testing.Conformance" />
paket add Daml.Ledger.Abstractions.Testing.Conformance --version 0.5.0-preview.2
#r "nuget: Daml.Ledger.Abstractions.Testing.Conformance, 0.5.0-preview.2"
#:package Daml.Ledger.Abstractions.Testing.Conformance@0.5.0-preview.2
#addin nuget:?package=Daml.Ledger.Abstractions.Testing.Conformance&version=0.5.0-preview.2&prerelease
#tool nuget:?package=Daml.Ledger.Abstractions.Testing.Conformance&version=0.5.0-preview.2&prerelease
Daml.Ledger.Abstractions.Testing.Conformance
Behavioral conformance kit for ILedgerClient implementations: an abstract
xUnit base class, LedgerClientConformanceTests<TProbe>, that verifies the
behavioral contract Daml.Ledger.Abstractions documents but cannot enforce
by itself.
This kit is for implementers of ILedgerClient. To unit-test
application code that consumes an ILedgerClient, don't hand-roll a fake
— use Canton.Ledger.Testing (published from
canton-ledger-api-csharp):
its FakeLedgerClient is a stageable in-memory implementation, with
builders for the fiddly event/result types and no mocking framework
required.
A transport package subclasses it, supplying a client factory and the
submitter whose visibility scopes the reads. TProbe is a Daml template in the
template family (ITemplate, IDamlRecord<TProbe>) — a generated template already
carries both facets:
public class MyClientConformanceTests : LedgerClientConformanceTests<MyProbeTemplate>
{
protected override ILedgerClient CreateClient() => MyClientFactory.CreateSeeded();
protected override SubmitterInfo Reader { get; } = new Party("alice");
}
Scope: the template family only
Every check below drives the template-family read surface — SubscribeAsync<T>,
SubscribeActiveAsync<T>, SubscribeLedgerEffectsAsync<T>. The interface-family
overloads that take a marker's View witness are not exercised: the kit would need
an interface marker and view record from the adopter's own corpus to drive them, which is
a fixture this kit does not yet ask for. An implementation whose
SubscribeAsync<TInterface, TView> gets its offset bounds, cancellation or view-absent
downgrade wrong will still pass this suite. Until that fixture exists, hold those three
members to the contract ILedgerStreamer documents by your own tests.
Covered contracts
- Cancellation — a cancelled live subscription surfaces
OperationCanceledException, not an in-band error. - Unclassified surfacing — a snapshot row the projector cannot classify is
yielded as
Unclassified, never silently dropped. - Terminal snapshot checkpoint — the snapshot always ends with a single
terminal
Checkpoint, and the seeded active rows precede it. - Empty-snapshot checkpoint — a snapshot with no active contracts (taken at
EmptySnapshotOffset,LedgerOffset.Beginby default) still ends with that single terminalCheckpoint. - Fault surfacing (opt-in) — a mid-snapshot transport fault surfaces in-band as a
terminal
AcsSnapshotEntry<T>.StreamErrorin place of theCheckpoint, never thrown, so a caller draining the snapshot handles faults as values. Skipped unless the adopter overridesCreateFaultingSnapshotClient()to return a client whose snapshot faults mid-stream; the default returnsnullbecause inducing a deterministic mid-snapshot fault is transport-specific. - Offset boundaries
(fromOffset, toOffset]—fromOffsetis exclusive, so resuming from a returned offset does not re-deliver the event at it;toOffsetis inclusive and terminal, so a bounded subscription delivers the event attoOffsetand then completes. - Stream shapes — the ACS-delta subscription conveys archival as a
first-class
Archivedevent and never anExercised; the ledger-effects subscription conveys it as a consumingExercisedand never anArchived. Each shape is checked in both directions: emitting the wrong variant fails, and so does dropping archival altogether, because the signal a shape exists to carry cannot be missing from a stream that claims to carry it. - Non-termination failure mode — every stream the contract requires to
terminate is enumerated under a time budget (
StreamTimeout, default 30s; override to widen). A stream that never terminates fails loudly with a contract-naming message instead of hanging the run. - Submitter authority (opt-in) —
SubmitAndWaitAsyncandTrySubmitAndWaitForTransactionAsyncapply thesubmitterparameter authoritatively viaCommandsSubmission.WithSubmitter, overwriting anyActAsalready set on the submission, rather than dispatching whatever the caller pre-set. Skipped unless the adopter overridesCreateWriteFixture()to return a client that accepts a submission from one party and rejects it from another. - Command-id deduplication (opt-in) —
TryExerciseAsyncandTryCreateAsyncdispatch a caller-suppliedcommandIdto the participant verbatim, and mint a fresh one only when the caller omits it, never leaving the participant'scommand_idunset. Both directions are checked: a client that mints over the supplied id breaks the first, a client that never mints breaks the second, and each is a distinct fault. Skipped unless the adopter overridesCreateCommandIdFixture().
Nine of the eighteen checks belong to the three opt-in families above: they skip, rather than
fail, while the corresponding factory stays at its null default. A green run therefore
reports your configuration as well as your correctness; the skips in the run output name which
opt-in families your configuration omits.
Seeding requirement
CreateClient() must return a client seeded with the canonical conformance
scenario:
- at least one active
TProbecontract and one row the transport cannot fully classify (e.g. a missing synchronizer id); - at least one event on the
SubscribeAsyncstream at a known offset, with the(fromOffset, toOffset]bounds honored; - one archived
TProbeat an offset no later than the seeded ledger end, reaching theSubscribeAsyncstream as anArchivedevent and theSubscribeLedgerEffectsAsyncstream as a consumingExercisedevent — the two shape checks read the archival signal itself, not only the absence of the wrong variant, so a scenario that archives nothing fails both; GetLedgerEndAsyncreturning the seeded ledger end;- an empty active-contract-set snapshot at
EmptySnapshotOffset(defaults toLedgerOffset.Begin; override it if your transport rejects an active-contract-set query at offset 0 withINVALID_ARGUMENT, pointing it at a known-empty offset); - a live subscription that honors cancellation.
The inherited [Fact] methods then exercise that seeded client against the
documented contract.
To also cover the fault path, override CreateFaultingSnapshotClient() to return a
separate client whose snapshot faults mid-stream (yielding a terminal
AcsSnapshotEntry<T>.StreamError and no Checkpoint). Leaving it at its null default
skips only the fault-surfacing check.
To also cover submitter authority, override CreateWriteFixture() to return a
WriteConformanceFixture: a fresh client plus a submission it accepts from an
Authorized party and rejects from an Unauthorized one. Leaving it at its null
default skips only the submitter-authority checks.
To also cover command-id deduplication, override CreateCommandIdFixture() to return a
CommandIdConformanceFixture: a fresh client, one exercise and one create it accepts
(each forwarding the fixture's CommandId? argument to the call's commandId parameter
verbatim, null included), and a read-back of the command_id the participant recorded
for the submission just dispatched — as a raw string, so an unset id reads back as null
rather than being smuggled past the check by a default(CommandId). Leaving it at its
null default skips only the command-id checks.
Writing those fixtures
Each of those overrides wants a client that proves one behavior and nothing else, and
ILedgerClient has twelve members. Derive from NotSupportedLedgerClient, the abstract
base this package publishes: every member is virtual and throws
NotSupportedException, so a fake overrides the ones its check drives and stubs none of
the rest.
private sealed class LedgerEndOnlyClient : NotSupportedLedgerClient
{
public override Task<LedgerOffset> GetLedgerEndAsync(
TimeSpan? timeout = null, CancellationToken cancellationToken = default) =>
Task.FromResult(LedgerOffset.At(42));
}
The kit's own command-id, submitter-authority and cancellation fakes are built that way; it is published rather than kept test-project-private so a transport author writing fixtures of their own pays the twelve-member tax once.
Not for production use.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- AwesomeAssertions (>= 9.6.0)
- Daml.Ledger.Abstractions (>= 0.5.0-preview.2)
- xunit.v3.assert (>= 4.0.0)
- xunit.v3.extensibility.core (>= 4.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.5.0-preview.2 | 75 | 9/9/2026 |
| 0.5.0-preview.1 | 366 | 9/2/2026 |
| 0.4.1-preview.1 | 396 | 7/25/2026 |
| 0.4.0-preview.3 | 71 | 7/19/2026 |
| 0.4.0-preview.2 | 66 | 7/18/2026 |
| 0.4.0-preview.1 | 79 | 7/17/2026 |