Testing on the Host
On any architecture other than riscv64 the binding embeds libcmt's mock IO driver. The mock replaces the machine: inputs come from files you provide, and every output is written to a file next to them. This gives you fast, emulator-free feedback during development and works in any test runner.
Feeding inputs
The CMT_INPUTS environment variable lists the inputs for the session, as comma-separated reason:file pairs, consumed in order:
CMT_INPUTS="0:advance.bin,1:inspect.bin" node app.jsThe reason selects the request type:
| Reason | Request | File contents |
|---|---|---|
0 | advance | EVM-ABI encoded EvmAdvance input (see below) |
1 | inspect | raw query payload |
| anything else | gio reply | raw response data (the reason is the response code) |
Set CMT_DEBUG=yes to make the mock log every operation.
When the input list is exhausted, the next finish call fails — that is your test's natural end-of-session signal. run recognizes it and resolves, so a test can await the loop and then assert on the outputs. Driving finish yourself, you see the failure directly as a RollupError, whose errno depends on how the last request ended: -ENODATA if it was accepted, -ENOSYS if it was rejected (the mock's two ways of saying the same thing). Rejecting a request that is not the last one just moves on to the next input.
import { constants } from "node:os";
import { , } from "@cartesi/rollup";
const = (: unknown) =>
=== "mock" &&
instanceof &&
. === "cmt_rollup_finish" &&
(. === -constants.. ||
. === -constants..);driver tells you which half of libcmt the binding was built against — "mock" on your host, "ioctl" inside a Cartesi Machine. It is fixed at build time by the target architecture, so it is a reliable check, unlike looking at CMT_INPUTS.
Collecting outputs
Each output is written to a file named after the input that produced it:
advance.output-0.bin # first voucher/notice while handling advance.bin
advance.report-0.bin # first report
advance.finish.bin # accept/reject result
Vouchers and notices are EVM-ABI encoded (Voucher(address,uint256,bytes) / Notice(bytes)); reports are raw bytes. @cartesi/codec decodes both, so a test can assert on typed objects instead of byte offsets.
Generating an advance input
An advance input file is the ABI encoding of the EvmAdvance(uint256,address,address,uint256,uint256,uint256,uint256,bytes) call that the on-chain input box would produce. The simplest way to build one is encodeInput from @cartesi/codec:
// @filename: encode.ts
import { } from "@cartesi/codec";
export const = (
{
: 31337n,
: `0x${"02".(20)}`,
: `0x${"03".(20)}`,
: 1n,
: 1700000000n,
: 0n,
: 0n,
: "0x68656c6c6f", // "hello"
},
"bytes",
);You can also craft one with foundry's cast calldata, or by hand — the package's own test suite contains a dependency-free encoder in a dozen lines.
A complete test
Putting it together with node:test — encode an input, point CMT_INPUTS at it, run the application, and assert on the output files:
// @filename: app.test.ts
import from "node:assert/strict";
import from "node:fs";
import from "node:os";
import path from "node:path";
import from "node:test";
import { , } from "@cartesi/codec";
("echoes the payload as a notice", async () => {
// run in a temp dir: the mock writes output files next to the inputs
const = .(path.(.(), "my-app-"));
.();
const = path.(, "input.bin");
.(
,
(
{
: 31337n,
: `0x${"02".(20)}`,
: `0x${"03".(20)}`,
: 1n,
: 1700000000n,
: 0n,
: 0n,
: "0x68656c6c6f", // "hello"
},
"bytes",
),
);
.. = `0:${}`;
// the app under test: import it AFTER setting CMT_INPUTS, since the
// device opens on construction. run() resolves when inputs run out.
const { } = await import("@cartesi/rollup");
const = new ();
await .({
(, ) {
.(.);
return true;
},
}); // resolves once the inputs are exhausted — session over
const = (
.(path.(, "input.output-0.bin")),
);
.(., "Notice");
.(.(.).(), "hello");
});Where the real thing differs
The mock approximates the machine but does not emulate it: state is not rolled back on reject, and there is no determinism enforcement. Before shipping, run your application inside a real Cartesi Machine.