Skip to content

Future idea: cases-as-exports producer format (via implements annotations)Β #18

Description

@lann

Idea

With the WIP implements component feature (🏷️ in the Component Model explainer: annotations declaring that a plain-named export implements a named interface), a suite could be authored as a component with many exports, each implementing a common test-case interface, instead of (or in addition to) exporting the tests aggregation interface:

interface test-case {
    use test-context.{context};
    features: func() -> list<string>;
    run: async func(ctx: borrow<context>) -> result<_, outcome>;
}

Each case is a plain-named instance export annotated implements lann:component-test/test-case. Consumption paths:

  1. Synthesized suite: a tool reads the component type and emits an adapter component implementing tests β€” the runner contract is unchanged; this is purely a producer format.
  2. Introspecting host runner: a host runner enumerates and calls the case exports directly, skipping composition.

Why it's attractive

  • Static inventory: case names are export names, so wasm-tools component wit suite.wasm is the inventory β€” no instantiation or all() call. Directly serves the lockfile/inventory workflow.
  • Clean layering: adopted incrementally underneath the frozen runner contract; failure costs nothing.
  • Graceful degradation on the WIP feature: works today via structural matching of plain-named exports; implements later upgrades detection from structural guessing to a declared, versioned claim. (Type checking ignores the annotation by design.)
  • Composition/aggregation model: cases from multiple components can be linked into one suite ("test object files β†’ test binary"), and multi-component suites shrink trap-poisoning blast radius to per-component.

Open questions (decision-forcing, in order)

  1. Plain-name grammar vs. hierarchical names. Plain names are constrained labels; stable group/source/case identifiers with / don't fit. Needs a mangling scheme, or hierarchy has to live elsewhere. Export names would become the stable identity the whole reporting stack keys on β€” this must be settled first.
  2. Can features be static? The headline win is execution-free inventory, but features: func() still requires instantiate-and-call. Options: encode in export names (collides with Rust guest SDK: suite authoring crateΒ #1), custom section (invisible to the type system), πŸͺ™ value exports (gated, further from shipping). If features stay dynamic the win shrinks to "names are static".
  3. Where does missing-features materialization live? all(missing-features) lets the suite decide how a case degrades; with static enumeration that logic moves to the synthesizer/runner, or run grows a parameter frozen into the per-case interface.

Caveats

  • Poisoning is unchanged for a single-component suite: one instance, one memory, one trap takes it all down. Isolation only materializes with multi-component suites (per-group components look like the sweet spot).
  • Scale spike needed: ~8000 exports = ~8000 instance entries in the component type and a very large synthesized aggregator; untested territory for wac/wasm-tools. Suggested spike: generate a 1000-export component and push it through composition today.
  • Tooling status of implements (WIT syntax, wasm-tools support) unverified as of filing.

Status

Future feature idea β€” not scheduled. Filed so other design work (naming, lockfile format, results schema, SDK shape) keeps it in mind, particularly anything that would make export-name-as-identity or static-features harder later.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions