# `Hyperlang.Test`
[🔗](https://github.com/hyperbob-labs/hyperlang/blob/v0.1.0/lib/hyperlang/test.ex#L1)

Helpers for testing Hyperlang guests and the hosts that run them.

The kit plays the role `vitest-pool-workers` plays for Cloudflare Workers:
it compiles a guest, supplies fake bindings, runs exports, and starts
callback actors. It uses only the public door (`Hyperlang.program/1`,
`Hyperlang.run/4`, `Hyperlang.module/1`), so a test exercises exactly the
path production code takes.

    import Hyperlang.Test

    test "prices an item" do
      prog =
        program!("-module(shop).\n-export([result/1]).\nresult(Sku) -> catalog:price(Sku).\n",
          bindings: %{"catalog" => %{price: fn _sku -> 42 end}})

      assert {:ok, 42, meta} =
               run(prog, :entry, ["A1"], bindings: %{"catalog" => %{price: fn _ -> 42 end}})

      assert_stdout meta, []
    end

## Fake bindings

Wherever a binding is expected, the kit accepts a plain map of functions,
`%{get: fn key -> ... end}`. The function arity is the export arity. A
value may also be `{fun, opts}` to pass `Hyperlang.Binding` export options
such as `nondeterministic: true`. The kit backs each distinct export shape
with a generated `Hyperlang.Binding` module, so the same fake map at
compile time and at run time names the same module. Real bindings are
passed as a module (compile time) or `{module, state}` (run time).

# `assert_stdout`
*macro* 

Assert on guest stdout. `subject` is a `run/4` result or its `meta` map.
`expected` is the list of chunks, a binary compared against the joined
chunks, or a regex matched against them.

# `program`

```elixir
@spec program(binary() | map(), map() | keyword()) ::
  {:ok, Hyperlang.Program.t()} | {:error, [map()]}
```

Build a program. `code` is a source binary, or a source map
`%{entry:, files:, manifest:}` passed to `Hyperlang.program/1` unchanged
except for fake bindings.

A binary with a `-module(name)` attribute becomes `name.erl`; any other
binary (a `result/1` form list or an expression) becomes `main.erl`.
`manifest` is a manifest map or a keyword list; its `bindings` may hold
fake binding maps.

# `program!`

```elixir
@spec program!(binary() | map(), map() | keyword()) :: Hyperlang.Program.t()
```

Like `program/2`, but returns the program or raises with the diagnostics.

# `run`

```elixir
@spec run(
  Hyperlang.Program.t() | binary() | map(),
  atom() | binary(),
  list(),
  keyword()
) ::
  {:ok, term(), map()} | {:error, term()}
```

Run `export` through `Hyperlang.run/4` and return the door result.

`target` is a program or source code; code is compiled with `program/2`
using `opts[:manifest]` plus the binding modules named by
`opts[:bindings]`. Options:

  * `:bindings` — `%{name => fake_map | {module, state}}`
  * `:policy` — the run policy (map or keyword list)
  * `:syscall` — the host syscall door
  * `:manifest` — manifest for code targets

# `run!`

```elixir
@spec run!(
  Hyperlang.Program.t() | binary() | map(),
  atom() | binary(),
  list(),
  keyword()
) :: term()
```

Like `run/4`, but returns the value or raises with the error.

# `start_actor!`

```elixir
@spec start_actor!(Hyperlang.Program.t(), term(), keyword()) :: pid()
```

Start the program's callback module as an unlinked `GenServer` and return
its pid. `arg` is the guest `init/1` argument. Options are `:bindings`,
`:policy`, and `:syscall`, as for `run/4`; they become the actor's spec.

---

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