Test extraction scripts
Lab runs FQL test files, applies timeouts and retries, and reports whether each file passed or failed. Assertions come from Ferret’s t standard-library namespace.
The selected Ferret runtime still owns FQL execution and module availability. Lab’s builtin runtime provides Ferret core; Lab does not add runtime-specific modules such as web::html. To test a script that uses one, select a binary or HTTP runtime that includes it.
Install Lab
Download the documented Lab release for your platform. On Linux x86_64:
mkdir -p "$HOME/.ferret"curl -fsSL https://github.com/MontFerret/lab/releases/download/v2.0.0-alpha.35/lab_linux_x86_64.tar.gz -o lab.tar.gztar -xzf lab.tar.gzinstall -m 0755 lab "$HOME/.ferret/lab"export PATH="$HOME/.ferret:$PATH"
Verify both Lab and its selected Ferret runtime:
lab version
See Lab Installation for release archives, Docker, and source builds.
Write a FQL unit test
Any .fql file can be a Lab unit test. Assertions return true when they succeed and raise an assertion error when they fail.
Create tests/products.fql:
Run the directory:
lab run tests/
Lab traverses directories recursively and runs .fql, .yaml, and .yml files. Unsupported files are ignored.
Tests pass on execution, not return values
A .fql test passes when the selected runtime executes it without returning an error. Lab does not inspect the returned value, so this file passes:
Use a t assertion when a false condition should fail the test:
Choose an assertion
The t namespace groups assertions by the value or relationship they check:
| Check | Assertions |
|---|---|
| Equality and order | t::eq, t::gt, t::gte, t::lt, t::lte |
| Size and content | t::empty, t::len, t::include, t::match |
| Exact values | t::true, t::false, t::none |
| Value types | t::string, t::int, t::float, t::datetime, t::array, t::object, t::binary |
| Explicit failure | t::fail |
Except for t::fail, each assertion has a negated form under t::not. For example:
Assertions accept an optional message as their final argument. Use it to explain the expectation in the failure output. See Testing for every signature and accepted value type.
Test an expected runtime error
Use a YAML suite with expect.error when a query must fail. Save this as tests/rejected-input.yaml:
query:
text: |
return 1 / 0
expect:
error:
contains: "division by zero"
Lab passes the test only when the runtime returns an error containing the configured text. Matching a stable part of the message prevents an unrelated runtime failure from accidentally satisfying the test. Only contains is supported inside expect.error; unknown fields fail during suite construction instead of falling back to an unqualified error expectation. Use an empty object when any runtime error is sufficient:
expect:
error: {}
The test fails if the query completes successfully. Do not define assert together with expect.error; an expected query failure produces no result for an assertion script.
The older .fail.fql filename convention remains supported for backward compatibility. It passes on any runtime error and fails when execution succeeds, but Lab emits a deprecation warning. Prefer expect.error for new negative tests; Lab does not rewrite legacy files automatically.
Separate a query from its assertions
Use a YAML suite when the extraction and its assertions should be separate scripts. The query runs first; its JSON result is available to the assert script as @lab.data.query.result.
# tests/products.yaml
query:
text: |
let products = [
{ name: "Mechanical Keyboard", price: 129 },
{ name: "USB-C Dock", price: 89 }
]
return products[*].name
assert:
text: |
let names = @lab.data.query.result
t::len(names, 2, "expected two product names")
return t::include(names, "Mechanical Keyboard")
Each query and assert block must define exactly one of text or ref. Use ref to load FQL from another file, params to add user parameters for that script, and the suite’s top-level timeout field to override the command timeout in seconds. See Writing Tests for the full suite format.
Serve local fixtures
Stable fixtures avoid test failures caused by live APIs changing or becoming unavailable. Create this structure:
tests/
├── fixtures/
│ └── products.json
└── fixture-products.fql
Add a small JSON response to tests/fixtures/products.json:
[
{ "name": "Mechanical Keyboard", "price": 129 },
{ "name": "USB-C Dock", "price": 89 }
]
Keep the script production-oriented by reading its normal @baseUrl parameter:
Start the fixture server for the duration of the test run, then bind the generated endpoint to @baseUrl:
lab run tests/ \ --serve ./tests/fixtures@fixtures \ --param-bind baseUrl=@lab.static.fixtures \ --policy-http-allow-localhost
Lab assigns a free port automatically and resolves the binding after that URL exists. The localhost policy flag permits the builtin runtime’s HTTP client to fetch the local fixture. Scripts that deliberately depend on Lab can use @lab.static.fixtures directly. See Static File Server for fixed ports, multiple directories, and runtimes outside the Lab host.
Control the test run
Use runner options when a suite needs parallelism, repeated passes, or retries:
lab run tests/ \ --concurrency=4 \ --timeout=30 \ --attempts=2 \ --times=3 \ --reporter=simple
--concurrency=4runs up to four test files at once.--timeout=30sets the per-test timeout to 30 seconds.--attempts=2allows at most two attempts for a failed required run.--times=3requires three successful runs of each test.--reporter=simpleproduces line-oriented output suited to CI logs.
See Runners for retry and repetition behavior.
Run tests in CI
Install Lab and use the simple reporter in GitHub Actions:
# .github/workflows/test.yml
name: FQL tests
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Lab
run: |
mkdir -p "$HOME/.ferret"
curl -fsSL https://github.com/MontFerret/lab/releases/download/v2.0.0-alpha.35/lab_linux_x86_64.tar.gz -o lab.tar.gz
tar -xzf lab.tar.gz
install -m 0755 lab "$HOME/.ferret/lab"
echo "$HOME/.ferret" >> "$GITHUB_PATH"
- name: Run FQL tests
run: lab run tests/ --reporter=simple
The release URL follows the version selected for this site. For waits, fixture services, Docker, and external runtimes, see CI.