# `Mix.Statifier.Corpus.HostCase`
[🔗](https://github.com/riddler/statifier-ex/blob/v2.10.0/lib/mix/statifier/corpus/host_case.ex#L1)

Runs a corpus case that carries a `host` object (ADR-0070 decision 5):
the host registers the case's `send_types` (ADR-0069), and the sends
handed to it must be the case's `expect_sends`.

`Statifier.Testing.Case.test_scxml/4` starts its session with no
registration, so a host case runs here instead, through the same public
session API: `Statifier.start_session/2` with `:send_types` naming
`Mix.Statifier.Corpus.HostCase.Processor` for every registered type,
`Statifier.Session.send_event/2` for each step, with the step's event
`data`, when it gives one, as the injected event's payload, and
`Statifier.Session.status/1` and `Statifier.Session.snapshot/1` to wait
for the chart to settle and read its active leaf states. The waiting
follows `test_scxml/4`'s: a pending library timer is given a short window
to fire before the next event, and a configuration is read once the
session has had nothing queued and no timer pending on two consecutive
polls, or the chart has left the running status, or a deadline passed.

A case agrees when the active leaf states after initialization and after
every step are the ones it expects, and the sends handed to the processor
over the whole run, in order, are exactly its `expect_sends`. Each handed
send is written in the case's language-neutral item shape: `type`,
`target`, `event` with its `name` and, when the send carries a payload,
its `data`; `delay_ms` for a delayed send; and `send_id` only when the
author named the send, which is when the delivered event carries one.
A registered delayed send is the host's timer, and this host never fires
one.

An `expect_sends` item may carry an `outcome` the runner performs or
observes for that send (ADR-0070's 2026-09-23 Amendment). With
`"fail"`, the runner reports the send handed at that item's position
through `Statifier.Session.failed_send/3` as soon as it reads the
processor's message, before it reads the next configuration, so the
step that led to the send is the one whose configuration shows what
the sender made of the `error.communication` it got. With
`"cancelled"`, a `<cancel>` naming the send must reach the processor
after it was handed the send: when a cancel reaches the processor, the
runner writes `"outcome": "cancelled"` on each delayed send handed
before it under the cancel's send id, generated or not, whose item
asks for it, so the comparison above refuses a marked item no cancel
reached. An item with no `outcome` claims nothing about a cancel: a
cancel naming its send is not compared.

A host object may also carry `declared_events` and `expect_accepts`,
present together or not at all (ADR-0071 decision 7). Before it starts the
session, the runner calls `Statifier.Chart.check_accepts/2` on the compiled
chart with `declared_events`, and the case agrees only when both lists it
answers are exactly `expect_accepts`' `unreachable` and `undeclared`, order
included. Either key without the other is a disagreement.

A host object may carry a diff pair as well - `to_source` and
`expect_diff`, with an optional `mapping` and `expect_compatible_at`
(ADR-0072, `conformance/schema/case.json`) - and this runner compares
none of them: it runs such a case like any other host case. Nothing in
`lib/` calls the chart diff or the position predicate (ADR-0072
decision 6), so the test suite compares those four keys
(`test/corpus/diff_cases_test.exs`), through `run/2`'s `:after_steps`
option for the position the steps leave.

A host object may carry `event_io_processors`, the one host key a `w3c`
case may carry (ADR-0075 decision 7): the Event I/O Processor URIs the
host runs with a location that reaches the running session. For each one
the runner starts a loopback front (`Mix.Statifier.BasicHTTPFront`) and
registers the processor under its URI and its short form with the
front's base URL, beside the case's `send_types`, so every send to it is
delivered through the front and none is handed to the recording
processor (`with_event_io_processors/2`). The front is stopped when the
case ends. The item set is closed; its one member is the Basic HTTP
Event I/O Processor, `Statifier.Send.BasicHTTP`.

# `event_io_processors`

```elixir
@spec event_io_processors(source :: String.t()) :: [String.t()]
```

The Event I/O Processor URIs of `host.event_io_processors`' closed item
set that `source` names as a `<send>`'s `type`, sorted: what a corpus
case transformed from `source` declares in `host.event_io_processors`.

## Examples

    iex> Mix.Statifier.Corpus.HostCase.event_io_processors(
    ...>   ~s|<send type="http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor" event="e"/>|
    ...> )
    ["http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor"]

    iex> Mix.Statifier.Corpus.HostCase.event_io_processors(~s|<send event="e"/>|)
    []

# `run`

```elixir
@spec run(
  corpus_case :: map(),
  opts :: [
    {:after_steps,
     (Statifier.MachineState.t() -&gt; :ok | {:disagree, String.t()})}
  ]
) :: :agree | {:disagree, String.t()}
```

Runs one host case, returning `:agree` or `{:disagree, message}`.

`opts` takes `:after_steps`, a function the runner calls once, after the
last step's configuration agrees and before it compares the handed sends,
with the session's settled `Statifier.MachineState`; it answers `:ok` or
`{:disagree, message}`, and a disagreement is the case's. A case that
disagrees before the sends are compared leaves them in the calling
process's mailbox, so each run belongs in a process of its own, as
`Mix.Statifier.Corpus.Runner.run/1` gives it.

# `with_event_io_processors`

```elixir
@spec with_event_io_processors(
  uris :: [String.t()],
  fun :: (%{required(String.t()) =&gt; {module(), keyword()}} -&gt; result)
) :: result
when result: term()
```

Runs `fun` with the `:send_types` entries that register every processor
`uris` names, delivering through a loopback front started for the call
and stopped when `fun` returns or raises; answers what `fun` answers.

Each processor is registered under its URI and its short form, with the
front's base URL as its `:base_url` (ADR-0075 decisions 2 and 3). With no
URI, no front is started and `fun` is handed an empty map. A URI outside
the closed set raises.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
