Log inGet started

Test Suite (asset type)

Updated 5 September 2026

Test suites are the engine's regression net. Every Luau-facing system — FFI bindings, components, the renderer, the VFS, multiplayer, the input stack — ships its coverage as one or more .testSuite assets. Because a suite is an ordinary registrable asset, a new system or package adds its own tests by dropping a .testSuite folder next to its code; nothing central (the runner, the shell script, CI) has to be edited.

When to use one

  • You added or changed a Luau-facing capability and want a permanent regression guard for it.
  • You fixed a bug — add a test that reproduces it so it can't silently come back (the bug_regression suite is the canonical home for one-test-per-issue guards).
  • A package or system ships behavior that should be validated on every CI run.

If you just want to manually exercise something once, use execute directly — don't author a throwaway suite.

Where it lives

  • Built-in engine suites: src/lua/lib/tests/suites/<name>.testSuite/ (VFS: /zero/source/libs/@builtin/tests/suites/<name>.testSuite/).
  • Package / system suites: anywhere under the package, e.g. …/voxelEngine.package/tests/voxel_grid.testSuite/.
  • Identity: the folder stem (strip_suffix: true), e.g. vfs. Suite names must be unique across the whole tree — the runner and CI key on the stem.

Folder shape

<name>.testSuite/
├── init.luau      # registers the suite via Test.describe / Test.it
├── README.md      # what this suite validates + how to run it
└── .metadata      # agent-editable tags + free-form fields

Suites may also ship fixture files (helper .module folders, sample data) alongside init.luauallow_unlisted: true permits them.

How to construct a new suite

  1. Create <name>.testSuite/init.luau. Reach the framework the type ships, then declare the suite with Test.describe(name, fn) and each test with Test.it(name, fn). Documentation is authored as doc-comments the engine's doc parser extracts statically: a top-of-file --!desc for the suite, and a --!desc (what the test checks) plus --!pass (the condition that must hold) directly above each Test.it.

    --!desc Spawn / despawn / exists / find over the entity API.
    
    local Test = asset.containing(__FILE__).modules.shared
    
    Test.describe("Entity Lifecycle", function()
        --!desc entity.spawn(name) creates an entity and returns its proxy.
        --!pass The returned id is a non-empty string and entity.exists(id) is true.
        Test.it("spawn returns a stable string id", function()
            local id = entity.spawn("probe").id
            Test.expect(type(id)).toBe("string")
            Test.expect(entity.exists(id)).toBe(true)
        end)
    end)
    
    return true
    
  2. Create <name>.testSuite/README.md describing the suite (what system it covers, notable edge cases, how to run just this suite).

  3. That's it. No registration step, no runner edit. The next test run discovers the suite via asset.list("testSuite").

See template/ for a copy-ready skeleton (the templater fills in the [name] markers).

How to run tests

  • All suites (writes /source/tmp/test_results.md + .json; refused in play mode). A bare run() covers the world-authored suites; "*" adds the baked @builtin library:
    return tools.use("tests", "run", "*")
    
  • One suite directly (instance-direct — runs only this suite):
    return asset.resolve("vfs", "testSuite"):run()
    
  • From the shell / CI: ./scripts/run_luau_tests.sh (all) or ./scripts/run_luau_tests.sh <name> (one). --list prints every discovered suite.
  • From inside the engine (agent loop): the tests toolbox — tools.use("tests", "list"), tools.use("tests", "run", { suite = "vfs" }), tools.use("tests", "compare", before, after) to diff two runs and surface NEW failures. See the toolbox README.

Discovery commands

asset.list("testSuite")            -- every registered suite, as AssetRef handles
asset.inspect("vfs")               -- summary for one suite
asset.resolve("vfs", "testSuite")  -- handle { guid, identity, path, type }
tools.use("tests", "list")         -- { count, suites = { { name, identity, path, origin, testCount? } } }

Authoring conventions

  • One suite per asset. Keep a suite focused on one system; large systems split into multiple .testSuite assets (e.g. zinput_state, zinput_events, zinput_actions).
  • No await() inside Test.it(). The framework runs synchronously; for multi-frame behavior use the per-test context t.waitFrames(n) (see the framework README).
  • Clean up. Entities the runner can't attribute are despawned automatically, but prefer Test.afterEach for deterministic cleanup.
  • Write fixtures via Test.tmpPath. Any file a test writes to the VFS must go under its per-test sandbox: Test.tmpPath("Foo.component/init.luau")/zero/source/tmp/tests/<suite>/<test>/.... That root is under /source/tmp/ (excluded from world saves + multiplayer sync, so it never clogs the synced VFS) and is torn down per-test and again at end-of-run, regardless of pass/fail/crash. Call it INSIDE the test body. For a fixture shared across tests in one block, write under Test.TMP_ROOT directly (survives per-test cleanup, still swept at end-of-run). Never hand-roll a bare /source/<name> path — it persists and syncs.
  • Idempotent. A suite must produce the same result run twice and must not depend on ordering between suites.
  • Never silence the LSP. No --!nocheck / ---@diagnostic disable in a suite — fix the underlying type/global instead.

Common pitfalls

  • Duplicate stem. Two .testSuite folders with the same stem collide; the runner warns and skips the duplicate, and --list fails. Pick a unique name.
  • Forgetting return true. Harmless but conventional — every suite ends with return true.
  • Deferred mutations. despawn / setParent / scene clears apply next frame; verify their effects with t.waitFrames or a follow-up query, not in the same frame.
  • .module — reusable Luau library (a suite often requires the system module under test).
  • .tool — agent-callable function; the tests toolbox tools are .tool assets.
  • .component — script component; component behavior is frequently what a suite exercises.
  • asset-type
  • reference