Skip to main content

CLI reference

lest [OPTIONS] [SUITE]...
lest run [OPTIONS] [SUITE]...
lest init [-y|--yes] [--no-color]
lest self install | uninstall

A bare lest is exactly lest run — every flag below works either way.

lest run

Runs test suites. With no SUITE arguments, every suite whose default isn't false runs.

$ lest # every default suite
$ lest run unit # one suite
$ lest run unit scripts # several
$ lest run engine # a default = false suite, by name

Naming a suite is the only way to run one configured with default = false outside CI. An unknown suite name is an error listing the ones that exist, and naming a suite twice (lest run unit unit) runs it once — repeats are deduped, preserving order.

Selection

-t, --filter <TEXT>

Run only tests whose full name contains TEXT — a plain substring, not a pattern. The full name is the describe path joined with the test's own name, separated by single spaces.

$ lest -t 'cart'
$ lest -t 'parser errors'

The pretty reporter shows describe levels as indented nested headers, and the that appears in its "Slowest Tests:" block (and in the JUnit classname) is display decoration only — neither indentation nor the glyph is part of the filterable name, so filtering on matches nothing.

A filter that selects no tests is a tool error (exit 2), not a green run — a typo'd -t in CI must not look like a passing suite.

--changed <REF>

Run only the specs affected by files that changed since a git ref. Lest diffs against the ref, then walks the inverted require graph to find every spec whose transitive requires touched a changed file.

$ lest --changed origin/main
$ lest --changed HEAD~1

Needs a git repository and a valid ref; otherwise it's a tool error. When nothing is affected, that's an answer, not a mistake: the run exits 0 with a note (and any coverage minimum is explicitly skipped).

--changed conflicts with --watch — watch mode already selects by change, from the file system rather than from git, so combining them is rejected rather than accepted and ignored.

--backend <BACKEND>

Force every selected suite onto one backend for this run: native, lune, lute, cloud, studio, or gargantuan. A debugging override — see Backends.

Watch mode

--watch

Re-run affected suites when files they depend on change.

$ lest --watch
$ lest run unit --watch

Watch mode is graph-driven, not glob-driven. Saving a file invalidates its content hash; the inverted require graph then yields exactly the specs whose transitive requires touched it, and only those re-run. Watching is debounced, hidden files are ignored (except .luaurc and .config.luau, which affect resolution), and cloud, studio, and gargantuan suites are always excluded — the fast loop never waits on a network round-trip or an engine boot.

At startup and after every pass, a dim banner marks the loop as alive:

Watching /path/to/project — save a file to re-run (ctrl+c to quit)

Saving lest.toml reloads the configuration. If the fresh config fails to load or select, the reload is rejected with a Warning: and the previous configuration stays live — the loop never dies to a half-typed config. If the file watcher itself stops, that's a tool error (exit 2) rather than a silent exit that looks like a clean quit.

Pairing --watch with run <suite> and -t is the tight inner loop:

$ lest run unit -t 'parser' --watch

Output

--reporter <REPORTER>

ReporterOutput
prettyDefault. Nested suites, inline diffs, slowest tests, summary
jsonThe event log, one JSON object per line
junitJUnit XML, for CI annotations

All reporters consume the same merged stream regardless of which backend produced it, tagging each suite's section with the environment it ran in.

Alongside the report itself, the CLI speaks in exactly two voices on stderr: diagnostics — a colored bold label followed by a capitalized sentence with no trailing period (Error: and Failure: in bold red, Warning: in bold yellow, e.g. Error: This is the error) — and notes, dim capitalized sentences with no trailing period. There is no lest: prefix anywhere.

--no-color

Disable ANSI color. Color is already disabled automatically when the output stream isn't a terminal, or when the NO_COLOR environment variable is set.

Snapshots

-u, --update

Overwrite snapshots that differ instead of failing on the difference. See Snapshots.

Coverage

--coverage

Measure line coverage (native suites only) and print a coverage report.

--coverage-format <FORMAT>

table (default) prints a box-drawn terminal table (pretty reporter only — it's omitted under --reporter json|junit); lcov writes an lcov tracefile to stdout for Codecov and editor gutters, moving the human test report to stderr so > lcov.info captures a clean tracefile.

--min <PCT>

Fail with exit code 1 when overall coverage is below PCT. Implies --coverage, and overrides [coverage] min in config. If nothing reached the coverage table — no native suite in the selection, or [coverage] globs that matched nothing — the gate is a tool error (exit 2), except when --changed selected no specs, which exits 0 and skips the gate with a note. See Coverage.

Config

--config <PATH>

Path to the config file. Defaults to ./lest.toml when present; the config file's directory becomes the project root.

lest init

Creates lest.toml by asking a few questions, after detecting whatever it can infer — a rojo project file, lune/lute on PATH, existing spec files.

$ lest init
$ lest init --yes # accept every default, no prompts (also -y)
$ lest init --no-color # plain prompts

Re-running is safe: an existing lest.toml prompts A lest.toml already exists. Overwrite it? (yes replaces it, no leaves it untouched; with --yes init refuses and exits 2), and an alias already bound to lest in .luaurc is left alone. .luaurc is only rewritten when it parses as plain JSON with no comments; otherwise init prints the snippet for you to paste. Key order is preserved. A project using .config.luau never gains a .luaurc (the Luau config RFC makes the pair an error) — init prints the alias line to paste into it instead.

See Getting started.

lest self

$ lest self install # copy into ~/.lest/bin and add it to PATH
$ lest self uninstall # remove it from PATH and delete ~/.lest/bin

On Windows the user PATH is edited in the registry with its value kind preserved, so %VAR%-style (REG_EXPAND_SZ) entries keep expanding instead of being baked into whatever they expanded to that day.

Exit codes

CodeMeaning
0Everything passed
1Test failures — including timeouts, spec load errors, and a --min shortfall
2Tool error — bad config, a backend that couldn't start, an undecodable event stream, a filter or coverage gate over nothing

These are never conflated, which is the whole point. A test that times out or a spec that fails to load is a test failure (1). A backend that can't start, a protocol line that won't decode, or a suite that produces no outcomes at all is a tool error (2) — the run didn't happen, so calling it a test failure would be a lie.

Environment variables

VariableEffect
ROBLOX_API_KEYOpen Cloud API key for cloud suites
LEST_API_KEYAlternative name for the same key
CIWhen set (and not empty, 0, or false), suites with default = false run automatically
NO_COLORWhen set, disables ANSI color everywhere, same as --no-color

A .env file at the project root is loaded automatically.