# `Macaw.Data.Source.Conformance`
[🔗](https://github.com/dcoai/macaw_data/blob/v0.5.3/lib/macaw/data/source/conformance.ex#L1)

The source conformance kit: the contract of spec/01 §2–§4 as checks every source runs
(spec/01 §9).

`check/3` runs one of eight checks against a source and answers its **findings**,
`{check, reason}` terms, `[]` for a source that passes; `check/2` runs them all and
`format/1` words a finding. `use Macaw.Data.Source.Conformance, opts` inside a module
that uses `ExUnit.Case` writes one test per check that fails with the findings:

    defmodule MyApp.SourceTest do
      use ExUnit.Case, async: true

      use Macaw.Data.Source.Conformance,
        source: MyApp.Source.new(),
        requests: [[], [window: [10, 20]]],
        push: &MyApp.Source.emit/1
    end

The kit drives the source through `Macaw.Data.Source` alone and calls every callback in
a process of its own (`Macaw.Data.Bounded`), so a callback that raises is a finding
(`{:raised, call, reason}`), not a crash of the check. It references no ExUnit function
itself: `describe`, `test` and `flunk` appear only in the code `use` writes into the
caller's module, so the kit loads in production. A callback is judged by the reductions
its process spends, never by the clock, so a loaded host changes no verdict.

Ported from visualize's `Visualize.Feed.Conformance`, with the checks a source's
superset of a feed needs: a describe context, parameters, `as_of`, one answer per `now`
and viewer independence.

## Options

  * `source:` the source (to `use`: an expression, evaluated in each test).
  * `context:` the host's configuration for the source, passed to `describe/2` (`nil`).
  * `requests:` what is asked, each a keyword list of `Macaw.Data.Source.Request` fields
    among `params`, `window`, `box`, `region`, `latest` and `as_of`; `[[]]` by default.
    A source naming a capability needs a request that probes it.
  * `budgets:` the budgets each request is fetched with, `[nil, 5]`.
  * `now:` the request's `now`, a `DateTime`; the moment the check starts by default.
  * `viewers:` two opaque viewers, the first asking every request and the second held
    to the same answer when the source declares itself viewer independent.
  * `as_of:` the instant an `:as_of` source is asked as of; a day before `now` by default.
  * `push:` a function of the source that makes it push; a source naming `:push` needs
    one.
  * `down:` a source of the same kind that cannot answer, held to error values.
  * `timeout:` the liveness bound: milliseconds before a callback that has not answered
    is hung (blocked, not slow), and the wait for a first push (`30_000`).
  * `reductions:` the most reductions one callback may spend, the source author's cost
    budget; `nil`, the default, makes no cost claim.
  * `processes:` pids or registered names whose reductions during a callback count
    toward its budget, for a source that does its work in a server (`[]`).
  * `quiet:` milliseconds of silence that end a push (`100`).
  * `name:` to `use` only: the `describe` block's name, `"source conformance"`.

## Examples

    iex> Macaw.Data.Source.Conformance.check(%Macaw.Data.Test.Reference{},
    ...>   requests: [[], [window: [2, 6]]])
    []

# `check`

```elixir
@type check() ::
  :describe
  | :deliveries
  | :supports
  | :push
  | :errors
  | :unsubscribe
  | :determinism
  | :viewers
```

One of the eight checks.

# `finding`

```elixir
@type finding() :: {check(), term()}
```

A fault one check found: `{check, reason}` (spec/01 §9).

# `__using__`
*macro* 

One ExUnit test per check, in a `describe` block, written into a module that uses
`ExUnit.Case` (spec/01 §9). `source:` and `down:` are expressions evaluated in each
test, in the test process.

# `check`

```elixir
@spec check(Macaw.Data.Source.t(), keyword()) :: [finding()]
```

Every check's findings for a source, concatenated in the order of `checks/0`.

# `check`

```elixir
@spec check(check(), Macaw.Data.Source.t(), keyword()) :: [finding()]
```

One check's findings for a source, `[]` when it passes (spec/01 §9). Raises
`ArgumentError` for an unknown check, an unknown option or a request with a key that
is not one of `params`, `window`, `box`, `region`, `latest` and `as_of`.

# `checks`

```elixir
@spec checks() :: [check(), ...]
```

The eight checks, in the order `check/2` runs them.

# `format`

```elixir
@spec format(finding()) :: String.t()
```

One finding as a line a person reads.

    iex> Macaw.Data.Source.Conformance.format({:supports, {:over_budget, {[], 5}, 9}})
    "supports: asked for at most 5 rows (fetch [] with budget 5), the source names :budget and served 9"

---

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