Skip to content

Multiple Runtimes

A mode is a named execution context that overrides part of your config. Modes are how one suite runs against several runtimes — WASI, bindings, the browser, standalone engines — from a single command.

bash
ast test --mode node:wasi,node:bindings,chromium:headless

Each named mode runs the selected specs with its own target and runtime, and the summary reports per mode.

Defining modes

Add a modes map to as-test.config.json. Each entry overrides the base config — commonly the build target and the runtime command:

json
{
  "input": ["assembly/__tests__/*.spec.ts"],
  "modes": {
    "node:wasi": {
      "buildOptions": { "target": "wasi" },
      "runOptions": { "runtime": { "cmd": "node ./.as-test/runners/default.wasi.js" } }
    },
    "node:bindings": {
      "buildOptions": { "target": "bindings" },
      "runOptions": { "runtime": { "cmd": "node ./.as-test/runners/default.bindings.js" } }
    },
    "chromium:headless": {
      "default": false,
      "buildOptions": { "target": "web" },
      "runOptions": {
        "runtime": {
          "cmd": "node ./.as-test/runners/default.web.js --headless",
          "browser": "chromium"
        }
      }
    }
  }
}

A mode can override any top-level field: input, output, coverage, features, fuzz, buildOptions, runOptions, and env. A mode value can also be a string path to an external config file instead of an inline object.

Default vs. manual modes

"default": false makes a mode manual-only. On a bare ast test (no --mode), default-false modes are skipped; they still run when named explicitly:

bash
ast test                      # default modes only
ast test --mode chromium:headless   # the manual mode, on demand

When a config declares any modes, an implicit ast test runs only those modes — the unnamed base config is not added on top. With no modes declared, the base config runs by itself.

Standalone WASI engines

Because WASI artifacts don't need host injection, the same wasi build runs on external engines. Point a mode's runtime at the engine binary with the <file> placeholder:

json
{
  "modes": {
    "wasmtime": {
      "default": false,
      "buildOptions": { "target": "wasi" },
      "runOptions": { "runtime": { "cmd": "wasmtime run <file>" } }
    },
    "wasmer": {
      "default": false,
      "buildOptions": { "target": "wasi" },
      "runOptions": { "runtime": { "cmd": "wasmer run <file>" } }
    },
    "wazero": {
      "default": false,
      "buildOptions": { "target": "wasi" },
      "runOptions": { "runtime": { "cmd": "wazero run <file>" } }
    }
  }
}

Standalone engines can't be handed host modules, so specs that retain a real @external import (via unmockImport) can't run on them — exclude those specs from the mode's input.

Per-mode specs and exclusions

A mode can narrow or change which specs it runs. This is how you keep a suite green across runtimes with different capabilities:

json
{
  "modes": {
    "node:bindings:esm": {
      "default": false,
      "buildOptions": { "target": "bindings", "args": ["--bindings esm"] },
      "input": ["assembly/__tests__/**/*.spec.ts", "!**/unmock.spec.ts"]
    }
  }
}

Output namespacing

When several modes are active, output paths are namespaced by mode (e.g. .as-test/build/<mode>/…) so artifacts don't collide. ast clean cleans every configured mode when --mode is omitted, regardless of default: false.