Skip to main content

Module testkit

Module testkit 

Source
Expand description

A conformance kit for Resolver implementations (feature testkit).

A resolver is read-only, so the kit never writes through it. Instead it hands out Fixtures (key documents, descriptors, receipts, and the versions of stored cells), the caller loads them into the resolver under test however that resolver is filled (maps, a database, files), and the kit then verifies receipts through it and looks things up in it. Each check returns a Report of what failed; Kit::check runs all of them.

use datumesh_core::testkit::{Fixtures, Kit};
use datumesh_core::MapResolver;

fn load(fx: &Fixtures) -> MapResolver {
    let mut m = MapResolver::new();
    for (fn_hash, bytes) in &fx.descriptors {
        m.insert_descriptor(fn_hash.as_str(), bytes.as_slice());
    }
    for (hash, bytes) in &fx.receipts {
        m.insert_receipt(hash.as_str(), bytes.as_slice());
    }
    // MapResolver keeps one value per cell: the versions come in tx order, so the
    // latest one stays. A time-aware resolver loads them all.
    for v in &fx.versions {
        m.insert_value(v.cell_id.as_str(), v.value.clone());
    }
    m
}

if let Err(report) = Kit::new().run(load) {
    panic!("{report}");
}

The checks:

  • check_lazy_calls: through a Recording wrapper, verify asks the resolver only for what step 7 reaches: the descriptor once; the inputs in order up to the first that fails; evaluate once, after 7.1-7.5, with the receipt’s cell and asof; and nothing at all when the receipt fails before step 7.
  • check_unavailable: an unknown fn_hash is E_FN_UNAVAILABLE, an input receipt it does not have is E_INPUT_UNAVAILABLE (first or second input), a function it cannot evaluate is E_EVAL_UNAVAILABLE (ok, level signed), and looking up unknown or malformed hashes (empty, uppercase, too long, non-hex, NUL, SQL or path syntax, non-ASCII) gives None without panicking.
  • check_bytes: descriptor and receipt return exactly the bytes loaded (all fixture bytes are canonical JSON, so a resolver that canonicalises what it is given still passes).
  • check_asof: evaluate receives the receipt’s asof, and stored cells recompute. With Kit::time_aware, a cell with versions at several times gives the value of its latest version that answers as of the requested time under the kit’s AnswerRule (None before the first), both through verify and through direct evaluate calls.

The answer rule (Kit::answer_rule) says when a version of a stored cell answers as of a time A:

  • AnswerRule::TxAndAsof, the default: its tx <= A and its receipt’s asof <= A. This is what SPEC 5.5 step 5 (D31 revised) requires of an ok response to a request with asof A: the receipt’s asof, and so the version answering, at or before A. A version is not an answer between its tx and the time it became known.
  • AnswerRule::TxOnly: its tx <= A, whenever its receipt was made (the kit’s only rule before 0.3.3). A time-aware resolver with this rule must say so.

In the fixtures, the EUR/CHF cell’s first version has tx 2024-03-15T16:00 and receipt asof 2024-03-16T09:00: under TxAndAsof it answers from 2024-03-16T09:00 on (and nothing does before); under TxOnly, from 2024-03-15T16:00 on. The rule matters only to a resolver declared time-aware. Receipts recompute through verify the same under both, since each receipt is verified as of its own asof.

Slices (0.3.4, spec-0.4.0). Fixtures::slices holds receipts whose lineage cites slices: their descriptors, the citing receipts and every member, the manifests by entry, and the key documents of their signers. A resolver that supplies manifests (Resolver::manifest) loads them too and is held to check_slices, which Kit::check and Kit::run include once Kit::slices says so:

  • each citing receipt passes step 7.5 through the resolver: it recomputes, or ends at E_EVAL_UNAVAILABLE; never another code;
  • verify asks, per slice entry, for the manifest once and then for each member in the manifest’s order; a probe whose manifest is not loaded stops at it, and one whose manifest names a member that is not loaded stops at that member, both E_INPUT_UNAVAILABLE;
  • manifest returns exactly the bytes loaded, and None for entries it was not given (unknown, malformed, a receipt hash);
  • a lineage the resolver reports with an evaluation (Resolver::evaluation) is made of inputs entries, and those the receipt does not list are ones the resolver can back with a receipt or a manifest.

The descriptors of the slice fixtures are formula descriptors with signature.order: read them with crate::FunctionDescriptor (a Descriptor refuses order). A resolver loaded as for 0.3.3, without the slice fixtures, passes the kit as before as long as slices is not set.

The kit expects a resolver to evaluate only what the fixtures give it: the versions of stored cells. Computed cells (convert, language expr) are not evaluable, so receipts of them end at E_EVAL_UNAVAILABLE. A panic in the resolver propagates to the caller.

The fixtures come from the specification’s vectors (revision spec-0.4.0; the files of the 0.3.3 fixtures are unchanged since spec-0.2.1, the slice fixtures are the slice-lineage receipt vectors of spec-0.4.0), in canonical form, plus receipts signed for the kit with the vectors’ seeds (feature sign): a second fx_rate cell (EUR/CHF) with versions at two times, a convert receipt whose first input is not loaded, and two receipts citing a slice whose manifest, or one of whose members, is not loaded. They are embedded in the crate (src/testkit/data.rs), generated and checked against spec/ by the crate’s test tests/testkit_data.rs.

Structs§

Fixtures
What the resolver under test is loaded with. All bytes are canonical JSON (JCS).
Kit
The resolver test kit: the fixtures and the checks. See the module documentation.
Recording
A Resolver that forwards to another and records each call, in order.
Report
The failures of a check, one line each: <check>: <case>: <what>, where <check> is lazy_calls, unavailable, bytes, asof, slices (or setup, if the embedded fixtures could not be read, which the crate’s tests rule out).
SliceFixtures
The slice fixtures (0.3.4, spec-0.4.0): receipts whose lineage cites slices, from the specification’s slice-lineage receipt vectors. All bytes are canonical JSON (JCS). A resolver that supplies manifests loads the descriptors under their fn_hash, the receipts (citing receipts and members) under their hashes and the manifests under their entries.
Version
A version of a stored cell: from tx on, the cell’s value is that of the receipt. When it answers a request as of some time is the kit’s AnswerRule; the receipt’s asof is that of receipt receipt_hash.

Enums§

AnswerRule
When a version of a stored cell answers as of a time A, for a resolver declared time-aware (Kit::time_aware). Set with Kit::answer_rule; the default is TxAndAsof. Among the versions that answer, the one with the latest tx gives the value; when none does, evaluate gives None.
Call
A call made to a Resolver, as Recording records it.