test(craftos): add suite runner

This commit is contained in:
Guillaume ARM 2026-06-08 05:10:31 +02:00
parent 582bbf639f
commit c7ba641e2d
14 changed files with 361 additions and 95 deletions

View File

@ -13,7 +13,7 @@ Use `docs/README.md` as the entrypoint for CC:Tweaked, CraftOS-PC, Advanced Peri
- Do not add a standalone Lua test harness unless asked. Local execution happens through the CraftOS-PC harness (see `docs/install-craftos-pc.md`, `docs/craftos_pc_glossary.md`, and ADR-0005); code otherwise executes in-game.
- Do not run `just repl` as an LLM agent; it is a human-only interactive CraftOS-PC wrapper. Use `just craftos --headless ...` for automated probes.
- When changing behavior, add as many useful CraftOS-PC tests as practical. It is acceptable to skip tests that require human-only validation, such as complex turtle motion, in-game UX feel, or visual approval, but still add unit-style non-regression tests for deterministic parts when possible.
- Use `/apis/libtest.lua` for CraftOS-PC test scripts under `tests/`; scripts must print `__TRAPOS_TEST_OK__` only after all assertions pass.
- Use `/apis/libtest.lua` for test scripts under `tests/`; `/programs/runtest.lua` prints `__TRAPOS_TEST_OK__` only after the suite passes.
- After editing Lua, run `just check` and fix all `luacheck` warnings.
- Use 2-space indent, semicolons, and `local function`.
- `require` paths are absolute ComputerCraft paths, for example `require('/apis/net')()`.
@ -22,7 +22,7 @@ Use `docs/README.md` as the entrypoint for CC:Tweaked, CraftOS-PC, Advanced Peri
## Architecture
- `apis/eventloop.lua` is the single-threaded event loop around `os.pullEventRaw`; consider using it everywhere async behavior is needed. A handler that returns `api.STOP` auto-unregisters.
- `apis/libtest.lua` is the lightweight CraftOS-PC test helper used by scripts under `tests/`; it provides assertions, verbose case output, failure reporting, and the `__TRAPOS_TEST_OK__` success marker.
- `apis/libtest.lua` is the lightweight test helper used by scripts under `tests/`; `/programs/runtest.lua` discovers tests, renders suite output, and owns the `__TRAPOS_TEST_OK__` success marker.
- `apis/net.lua` builds modem packet messaging, routing, and request/response RPC on the event loop. `sendRequest` returns `ok, result, packet` and defaults to a 0.5s timeout.
- A router (`/programs/router.lua`) must be running somewhere on the network; without it, packets lack `routerId`, `isPacketOk` rejects them, and cross-machine messaging silently fails.
- `servers/` listen for requests and start loops; `programs/` are clients that send requests and exit.

View File

@ -16,6 +16,6 @@ This creates `.env` from `.env.sample` when needed and installs the local Git ho
`just ci` is the local verification entry point. Today it verifies that `craftos --version` reports v2.8.3 or newer, runs `just check` for `luacheck`, and runs `just test` for CraftOS-PC headless tests. Use `just craftos` to launch CraftOS-PC with repo-local save data under `.craftos` and read-only mounts for `/trapos`, `/apis`, `/programs`, `/servers`, and `/startup`. `just repl` opens the same environment with `--cli` for human interactive use only; LLM agents must not run it. Use the CraftOS-PC glossary when adjusting `--headless`, `--exec`, `--script`, `--rom`, or `--mount-*` usage.
Tests live under `tests/` and run inside CraftOS-PC through `just test`. API-level tests should use `require('/apis/libtest')({ ... })`, register cases with `testlib.test(name, fn)`, and call `testlib.run()` at the end. `libtest` prints `__TRAPOS_TEST_OK__` only when every case passes, which is the contract consumed by the shell harness. Pass `--verbose` to `just test` to list each test script and each `libtest` case with colored `[OK]`/`[KO]` statuses.
Tests live under `tests/` and run inside CraftOS-PC through `just test`, which launches `/programs/runtest.lua`. API-level tests should use `require('/apis/libtest')({ ... })`, register cases with `testlib.test(name, fn)`, and call `testlib.run()` at the end. `libtest` tests remain normal ComputerCraft programs; the runner handles suite discovery, grouped output, and the `__TRAPOS_TEST_OK__` shell success contract. Pass `--pretty` to `just test` for grouped colored `[OK]`/`[KO]` statuses, or `--verbose` for additional runner/debug detail.
Each CraftOS-PC test process is killed if it does not finish within `TRAP_CCLIBS_TEST_TIMEOUT_SECONDS`, defaulting to `3`. Override this in `.env` for slower local probes.

108
Justfile
View File

@ -101,69 +101,53 @@ repl:
ci *args: check-craftos check
@just test {{args}}
# Run CraftOS-PC headless integration tests. Pass `--verbose` to list each case.
# Run CraftOS-PC headless integration tests. Pass `--pretty` for grouped output.
test *args:
@if [ -f .env ]; then set -a; . ./.env; set +a; fi; \
verbose=0; \
timeout_seconds="${TRAP_CCLIBS_TEST_TIMEOUT_SECONDS:-3}"; \
case "$timeout_seconds" in ''|*[!0-9]*) printf '%s\n' 'TRAP_CCLIBS_TEST_TIMEOUT_SECONDS must be a positive integer' >&2; exit 1 ;; esac; \
if [ "$timeout_seconds" -lt 1 ]; then printf '%s\n' 'TRAP_CCLIBS_TEST_TIMEOUT_SECONDS must be >= 1' >&2; exit 1; fi; \
for a in {{args}}; do case "$a" in --verbose|-v) verbose=1 ;; esac; done; \
rom_arg=""; \
if [ "$(uname -s)" = "Darwin" ]; then \
rom_arg="--rom /Applications/CraftOS-PC.app/Contents/Resources"; \
fi; \
repo='{{justfile_directory()}}'; \
mount_arg="--mount-ro /apis=$repo/apis --mount-ro /tests=$repo/tests"; \
green=$(printf '\033[32m'); reset=$(printf '\033[0m'); red=$(printf '\033[31m'); \
for script in tests/boot.lua tests/ready.lua tests/eventloop.lua; do \
tmp="$(mktemp)"; \
data_dir="$(mktemp -d)"; \
exec_code="shell.run('/$script')"; \
if [ "$verbose" -eq 1 ]; then exec_code="shell.run('/$script', '--verbose')"; fi; \
craftos --directory "$data_dir" --headless $rom_arg $mount_arg --exec "$exec_code" >"$tmp" 2>&1 & \
pid="$!"; \
( sleep "$timeout_seconds"; kill -TERM "$pid" >/dev/null 2>&1 ) & \
watchdog="$!"; \
wait "$pid" >/dev/null 2>&1; \
status="$?"; \
kill "$watchdog" >/dev/null 2>&1 || true; \
wait "$watchdog" >/dev/null 2>&1 || true; \
if grep -q __TRAPOS_TEST_OK__ "$tmp"; then \
if [ "$verbose" -eq 1 ]; then \
report="$data_dir/computer/0/trapos-test-report"; \
if [ -f "$report" ]; then while IFS= read -r line; do \
case "$line" in \
'OK '*) name="${line#OK }"; printf '%s\n' "${green}[OK]${reset} $name" ;; \
'KO '*) name="${line#KO }"; printf '%s\n' "${red}[KO]${reset} $name" ;; \
esac; \
done <"$report"; fi; \
fi; \
rm -f "$tmp"; \
rm -rf "$data_dir"; \
[ "$verbose" -eq 1 ] && printf '%s\n' "${green}PASS${reset} $script"; \
else \
if [ "$status" -eq 143 ]; then \
printf '%s\n' "${red}FAIL${reset} $script timed out after ${timeout_seconds}s" >&2; \
else \
printf '%s\n' "${red}FAIL${reset} $script did not print __TRAPOS_TEST_OK__" >&2; \
fi; \
if [ "$verbose" -eq 1 ]; then \
report="$data_dir/computer/0/trapos-test-report"; \
if [ -f "$report" ]; then while IFS= read -r line; do \
case "$line" in \
'OK '*) name="${line#OK }"; printf '%s\n' "${green}[OK]${reset} $name" >&2 ;; \
'KO '*) name="${line#KO }"; printf '%s\n' "${red}[KO]${reset} $name" >&2 ;; \
esac; \
done <"$report"; fi; \
fi; \
cat "$tmp" >&2; \
rm -f "$tmp"; \
rm -rf "$data_dir"; \
exit 1; \
fi; \
done; \
printf '%s\n' 'OK: CraftOS integration tests passed'
@if [ -f .env ]; then set -a; . ./.env; set +a; fi; \
pretty=0; \
verbose=0; \
timeout_seconds="${TRAP_CCLIBS_TEST_TIMEOUT_SECONDS:-3}"; \
case "$timeout_seconds" in ''|*[!0-9]*) printf '%s\n' 'TRAP_CCLIBS_TEST_TIMEOUT_SECONDS must be a positive integer' >&2; exit 1 ;; esac; \
if [ "$timeout_seconds" -lt 1 ]; then printf '%s\n' 'TRAP_CCLIBS_TEST_TIMEOUT_SECONDS must be >= 1' >&2; exit 1; fi; \
for a in {{args}}; do case "$a" in --pretty) pretty=1 ;; --verbose|-v) pretty=1; verbose=1 ;; esac; done; \
rom_arg=""; \
if [ "$(uname -s)" = "Darwin" ]; then \
rom_arg="--rom /Applications/CraftOS-PC.app/Contents/Resources"; \
fi; \
repo='{{justfile_directory()}}'; \
mount_arg="--mount-ro /apis=$repo/apis --mount-ro /programs=$repo/programs --mount-ro /tests=$repo/tests"; \
tmp="$(mktemp)"; \
data_dir="$(mktemp -d)"; \
output_path="$data_dir/computer/0/trapos-test-output"; \
exec_code="shell.run('/programs/runtest.lua', '--shutdown')"; \
if [ "$pretty" -eq 1 ]; then exec_code="shell.run('/programs/runtest.lua', '--pretty', '--output', '/trapos-test-output', '--shutdown')"; fi; \
if [ "$verbose" -eq 1 ]; then exec_code="shell.run('/programs/runtest.lua', '--verbose', '--output', '/trapos-test-output', '--shutdown')"; fi; \
craftos --directory "$data_dir" --headless $rom_arg $mount_arg --exec "$exec_code" >"$tmp" 2>&1 & \
pid="$!"; \
( sleep "$timeout_seconds"; kill -TERM "$pid" >/dev/null 2>&1 ) & \
watchdog="$!"; \
wait "$pid" >/dev/null 2>&1; \
status="$?"; \
kill "$watchdog" >/dev/null 2>&1 || true; \
wait "$watchdog" >/dev/null 2>&1 || true; \
if grep -q __TRAPOS_TEST_OK__ "$tmp"; then \
if [ "$pretty" -eq 1 ] && [ -f "$output_path" ]; then cat "$output_path"; fi; \
rm -f "$tmp"; \
rm -rf "$data_dir"; \
else \
red=$(printf '\033[31m'); reset=$(printf '\033[0m'); \
if [ "$status" -eq 143 ]; then \
printf '%s\n' "${red}FAIL${reset} CraftOS integration tests timed out after ${timeout_seconds}s" >&2; \
else \
printf '%s\n' "${red}FAIL${reset} CraftOS integration tests did not print __TRAPOS_TEST_OK__" >&2; \
fi; \
if [ -f "$output_path" ]; then cat "$output_path" >&2; fi; \
cat "$tmp" >&2; \
rm -f "$tmp"; \
rm -rf "$data_dir"; \
exit 1; \
fi; \
printf '%s\n' 'OK: CraftOS integration tests passed'
# Lint all Lua source with luacheck.
check: check-luacheck

View File

@ -1,15 +1,28 @@
local _VERSION = "1.3.0"
local _VERSION = "1.4.0"
local function createLibTest(args)
local api = {}
local tests = {}
local pretty = false
local verbose = false
local reportPath = "/trapos-test-report"
local reportPath = nil
local printMarker = true
for _, arg in ipairs(args or {}) do
local i = 1
while i <= #(args or {}) do
local arg = args[i]
if arg == "--verbose" then
pretty = true
verbose = true
elseif arg == "--pretty" then
pretty = true
elseif arg == "--report" then
reportPath = args[i + 1]
i = i + 1
elseif arg == "--no-marker" then
printMarker = false
end
i = i + 1
end
local function fail(message)
@ -17,7 +30,7 @@ local function createLibTest(args)
end
local function writeReport(line)
if not verbose then
if not reportPath then
return
end
@ -28,6 +41,12 @@ local function createLibTest(args)
end
end
function api.log(message)
if verbose then
writeReport("LOG " .. tostring(message))
end
end
function api.test(name, fn)
assert(type(name) == "string", "bad argument #1 (string expected)")
assert(type(fn) == "function", "bad argument #2 (function expected)")
@ -67,19 +86,22 @@ local function createLibTest(args)
function api.run()
for _, t in ipairs(tests) do
api.log("RUN " .. t.name)
local ok, err = pcall(t.fn)
if not ok then
writeReport("KO " .. t.name .. ": " .. tostring(err))
if not verbose then
if not pretty then
print("FAIL " .. t.name .. ": " .. tostring(err))
end
os.shutdown()
error(err, 0)
end
writeReport("OK " .. t.name)
end
print("__TRAPOS_TEST_OK__")
os.shutdown()
if printMarker then
print("__TRAPOS_TEST_OK__")
end
return true
end
function api.version()

View File

@ -15,3 +15,4 @@ Future ADRs can reuse the shape of the existing files when it is useful.
- [`adr-0005-craftos-pc-harness.md`](adr-0005-craftos-pc-harness.md) - CraftOS-PC as the local harness.
- [`adr-0006-simplify-periphemu-bootstrap.md`](adr-0006-simplify-periphemu-bootstrap.md) - Simplify periphemu bootstrap.
- [`adr-0007-use-libtest-for-craftos-tests.md`](adr-0007-use-libtest-for-craftos-tests.md) - Use libtest for CraftOS tests.
- [`adr-0008-keep-tests-runnable-in-craftos-and-in-game.md`](adr-0008-keep-tests-runnable-in-craftos-and-in-game.md) - Keep tests runnable in CraftOS and in-game.

View File

@ -36,7 +36,7 @@ The existing `CLAUDE.md` constraint ("Do not run Lua locally or add a test harne
- Contributors must install CraftOS-PC before `just install` succeeds. The install guide makes this a 4-step copy/paste on macOS.
- Contributors should use the local CraftOS-PC glossary first when researching emulator behavior, then follow the linked upstream pages for details.
- Headless tests live under `tests/` and are driven by `just test`. Basic boot tests still prove CraftOS-PC boots and the event queue works, and API behavior tests use `/apis/libtest.lua` for named cases and assertions. Tests are mounted into CraftOS-PC and invoked with `shell.run`, and stdout is grepped for `__TRAPOS_TEST_OK__`. Adding another test means dropping a Lua file in `tests/` and adding it to the loop in the `test:` recipe.
- Headless tests live under `tests/` and are driven by `just test`. Basic boot tests still prove CraftOS-PC boots and the event queue works, and API behavior tests use `/apis/libtest.lua` for named cases and assertions. The `Justfile` launches `/programs/runtest.lua`, which discovers tests, invokes them with `shell.run`, and prints `__TRAPOS_TEST_OK__` only after the suite passes.
- Each test process is guarded by `TRAP_CCLIBS_TEST_TIMEOUT_SECONDS`, defaulting to `3`, so a blocked ComputerCraft event loop fails quickly and prints captured output.
- The macOS install symlinks the binary into `/usr/local/bin`, which makes CraftOS-PC unable to auto-discover the ROM that ships inside the `.app` bundle (`Could not mount ROM`). The `test:` recipe works around this by passing `--rom /Applications/CraftOS-PC.app/Contents/Resources` on Darwin. Linux (AppImage) and Windows (installer) auto-discover correctly, so no flag is passed there.
- `just craftos` uses repository-local save data under `.craftos/config/` and `.craftos/computer/`. This keeps emulator state out of `~/Library/Application Support/CraftOS-PC` during repository work and keeps repo files visible through read-only mounts instead of copying them into the VM save.

View File

@ -15,14 +15,14 @@ ADR 0005 made CraftOS-PC the local harness. The first real behavior test, `tests
- Collect named cases.
- Print per-case progress only in verbose mode.
- Fail fast with a useful message.
- Print `__TRAPOS_TEST_OK__` only after every assertion passes.
- Report success only after every assertion passes, allowing the suite runner to print `__TRAPOS_TEST_OK__` once.
- Shut the ComputerCraft process down cleanly.
Those details are easy to copy incorrectly. A blocked eventloop test also showed that the shell harness needs a timeout and captured output so agentic debugging can proceed without manual interruption.
## Decision
Add `/apis/libtest.lua` as the repository's lightweight CraftOS-PC test helper.
Add `/apis/libtest.lua` as the repository's lightweight ComputerCraft test helper.
Tests under `tests/` should require it with an absolute ComputerCraft path:
@ -37,20 +37,20 @@ end);
testlib.run();
```
`libtest` intentionally stays small. It provides named cases, `assertEquals`, `assertTrue`, `assertErrors`, verbose case-status report output consumed by the shell harness, failure reporting, the `__TRAPOS_TEST_OK__` success marker, and `os.shutdown()` at process end.
`libtest` intentionally stays small. It provides named cases, `assertEquals`, `assertTrue`, `assertErrors`, optional case-status report output consumed by the suite runner, and failure reporting.
The shell harness keeps ownership of process-level concerns: CraftOS-PC launch flags, read-only mounts, stdout capture, and timeout enforcement through `TRAP_CCLIBS_TEST_TIMEOUT_SECONDS`.
`/programs/runtest.lua` owns suite-level concerns: test discovery, invoking test scripts, grouped pretty output, verbose runner diagnostics, the `__TRAPOS_TEST_OK__` success marker, and optional shutdown. The shell harness keeps only host-level concerns: CraftOS-PC launch flags, read-only mounts, stdout capture, and timeout enforcement through `TRAP_CCLIBS_TEST_TIMEOUT_SECONDS`.
## Consequences
- New deterministic behavior should get as many useful CraftOS-PC tests as practical.
- Tests that require human validation, such as complex turtle motion, in-game UX feel, or visual approval, may be skipped, but deterministic pieces should still get unit-style non-regression coverage.
- Test scripts remain normal ComputerCraft programs, not standalone Lua tests. They run through `just test`, not through a separate Lua test framework.
- Test scripts remain normal ComputerCraft programs, not standalone Lua tests. They can run through `just test`, `/programs/runtest.lua`, or direct in-game execution when copied with their dependencies.
- `libtest` lives under `/apis` and is listed in `manifest.json`, so it can be required consistently in the mounted CraftOS-PC environment.
- The `__TRAPOS_TEST_OK__` marker remains the single shell-level success contract.
- The `__TRAPOS_TEST_OK__` marker remains the single shell-level success contract and is owned by `/programs/runtest.lua`.
## Future Work
- Add more assertions only when tests need them; avoid growing a large framework.
- Consider making `just test` discover `tests/*.lua` automatically once the test set grows enough that the explicit list becomes noisy.
- Add more runner filters only when useful; keep the common no-argument path as automatic `tests/*.lua` discovery.
- Explore GitHub Actions with the Linux CraftOS-PC AppImage after local coverage is broader.

View File

@ -0,0 +1,37 @@
# ADR 0008: Keep Tests Runnable In CraftOS And In-Game
## Status
Accepted
## Date
2026-06-08
## Context
The initial CraftOS-PC harness proved that repository code can be tested locally, but the first shell recipe also owned too much suite behavior: explicit test lists, per-case rendering, and success formatting. That made the host shell script more complex than necessary and tied test orchestration to CraftOS-PC launch details.
At the same time, tests in this repository are still ComputerCraft programs. They should be useful in CraftOS-PC and in-game, not only inside a host-side shell loop.
## Decision
Keep `/apis/libtest.lua` focused on test cases and assertions. It must stay usable from normal ComputerCraft programs in CraftOS-PC or in-game.
Move suite orchestration into `/programs/runtest.lua`. The runner discovers tests under `/tests`, invokes each script with `shell.run`, renders grouped `--pretty` output, emits additional `--verbose` diagnostics, prints `__TRAPOS_TEST_OK__` only after the full suite passes, and can shut down when the host harness asks with `--shutdown`.
Keep the `Justfile` minimal. It launches CraftOS-PC, mounts repository directories, enforces the process timeout, checks for the success marker, and prints runner output files. It should not know about individual test files or cases.
## Consequences
- Tests using `libtest` remain plain ComputerCraft programs.
- `/programs/runtest.lua` can be run inside CraftOS-PC or in-game when `/tests` and dependencies are present.
- Pretty colors and grouped suite output are runner concerns, not `libtest` concerns.
- Verbose mode is reserved for debugging and agent work loops, while `--pretty` is the normal human-readable mode.
- Host-specific concerns remain outside production Lua code.
## Future Work
- Add test selection filters when the suite grows.
- Add runner-level timing if slow tests become hard to diagnose.
- Add more `libtest` assertions only when real tests need them.

View File

@ -82,7 +82,7 @@ On macOS, use the `--rom` form shown above if the command fails with `Could not
## Running tests
`just test` runs the headless CraftOS integration tests in `tests/` through CraftOS-PC. On macOS the recipe passes `--rom /Applications/CraftOS-PC.app/Contents/Resources` because the `/usr/local/bin/craftos` symlink loses ROM auto-discovery; on Linux and Windows no flag is needed. `just ci` runs the same tests after `luacheck`.
`just test` runs `/programs/runtest.lua` headlessly through CraftOS-PC. The runner discovers tests under `/tests`, while the `Justfile` only owns host launch flags, timeout, and success-marker checks. On macOS the recipe passes `--rom /Applications/CraftOS-PC.app/Contents/Resources` because the `/usr/local/bin/craftos` symlink loses ROM auto-discovery; on Linux and Windows no flag is needed. `just ci` runs the same tests after `luacheck`.
## Repository-local launches

View File

@ -9,6 +9,7 @@
"programs/router.lua",
"programs/events.lua",
"programs/ping.lua",
"programs/runtest.lua",
"programs/tuidemo.lua",
"programs/upgrade.lua",
"apis/net.lua",

209
programs/runtest.lua Normal file
View File

@ -0,0 +1,209 @@
local _VERSION = "1.0.0"
local SUCCESS_MARKER = "__TRAPOS_TEST_OK__"
local DEFAULT_REPORT_PATH = "/trapos-test-report"
local function printUsage()
print("runtest usage:")
print()
print("\t\truntest [--pretty] [--verbose] [--output <path>] [--shutdown] [test ...]")
print("\t\truntest --version")
print("\t\truntest --help")
end
local function parseArgs(args)
local opts = {
pretty = false,
verbose = false,
shutdown = false,
outputPath = nil,
tests = {},
}
local i = 1
while i <= #args do
local arg = args[i]
if arg == "--version" or arg == "-version" then
print("runtest v" .. _VERSION)
return nil
elseif arg == "--help" or arg == "-help" then
printUsage()
return nil
elseif arg == "--pretty" then
opts.pretty = true
elseif arg == "--verbose" or arg == "-v" then
opts.pretty = true
opts.verbose = true
elseif arg == "--output" then
opts.outputPath = args[i + 1]
i = i + 1
elseif arg == "--shutdown" then
opts.shutdown = true
elseif string.sub(arg, 1, 1) == "-" then
print("Unknown option: " .. arg)
printUsage()
return nil
else
opts.tests[#opts.tests + 1] = arg
end
i = i + 1
end
return opts
end
local function normalizeTestPath(path)
if string.sub(path, 1, 1) == "/" then
return path
end
return "/" .. path
end
local function discoverTests()
local tests = {}
if not fs.exists("/tests") then
return tests
end
for _, name in ipairs(fs.list("/tests")) do
local path = "/tests/" .. name
if not fs.isDir(path) and string.sub(name, -4) == ".lua" then
tests[#tests + 1] = path
end
end
table.sort(tests)
return tests
end
local function readLines(path)
local lines = {}
local file = fs.open(path, "r")
if not file then
return lines
end
while true do
local line = file.readLine()
if line == nil then
break
end
lines[#lines + 1] = line
end
file.close()
return lines
end
local function createEmitter(outputPath)
local file = nil
if outputPath then
file = fs.open(outputPath, "w")
end
local function emit(line)
if file then
file.writeLine(line)
else
print(line)
end
end
local function close()
if file then
file.close()
end
end
return emit, close
end
local function renderReport(emit, script, reportLines, ok, verbose, color)
local green = color and string.char(27) .. "[32m" or ""
local red = color and string.char(27) .. "[31m" or ""
local dim = color and string.char(27) .. "[2m" or ""
local reset = color and string.char(27) .. "[0m" or ""
local displayScript = string.sub(script, 1, 1) == "/" and string.sub(script, 2) or script
emit(displayScript)
if verbose then
emit(" " .. dim .. "report: " .. DEFAULT_REPORT_PATH .. reset)
end
if #reportLines == 0 then
if ok then
emit(" " .. green .. "[OK]" .. reset .. " script completed")
else
emit(" " .. red .. "[KO]" .. reset .. " script failed before reporting a case")
end
return
end
for _, line in ipairs(reportLines) do
if string.sub(line, 1, 3) == "OK " then
emit(" " .. green .. "[OK]" .. reset .. " " .. string.sub(line, 4))
elseif string.sub(line, 1, 3) == "KO " then
emit(" " .. red .. "[KO]" .. reset .. " " .. string.sub(line, 4))
elseif verbose and string.sub(line, 1, 4) == "LOG " then
emit(" " .. dim .. "[log] " .. string.sub(line, 5) .. reset)
end
end
end
local opts = parseArgs({ ... })
if not opts then
return
end
local tests = opts.tests
if #tests == 0 then
tests = discoverTests()
else
for i, path in ipairs(tests) do
tests[i] = normalizeTestPath(path)
end
end
if #tests == 0 then
print("FAIL: no tests found")
if opts.shutdown then
os.shutdown()
end
return
end
local emit, closeOutput = createEmitter(opts.outputPath)
local suiteOk = true
for _, script in ipairs(tests) do
fs.delete(DEFAULT_REPORT_PATH)
local ok
if opts.verbose then
ok = shell.run(script, "--no-marker", "--report", DEFAULT_REPORT_PATH, "--verbose")
elseif opts.pretty then
ok = shell.run(script, "--no-marker", "--report", DEFAULT_REPORT_PATH, "--pretty")
else
ok = shell.run(script, "--no-marker", "--report", DEFAULT_REPORT_PATH)
end
local reportLines = readLines(DEFAULT_REPORT_PATH)
if opts.pretty then
renderReport(emit, script, reportLines, ok, opts.verbose, opts.outputPath ~= nil)
end
if not ok then
suiteOk = false
break
end
end
closeOutput()
if suiteOk then
print(SUCCESS_MARKER)
else
print("FAIL: CraftOS integration tests failed")
end
if opts.shutdown then
os.shutdown()
end

View File

@ -1,4 +1,10 @@
-- Smoke test: prove CraftOS-PC boots and stdout flushes.
-- Invoked via `craftos --headless --script tests/boot.lua` from `just test`.
print('__TRAPOS_TEST_OK__');
os.shutdown();
-- Basic integration test: prove CraftOS-PC boots and can run a test script.
local createLibTest = require('/apis/libtest');
local testlib = createLibTest({ ... });
testlib.test('CraftOS-PC boots and runs Lua', function()
testlib.assertTrue(true);
end);
testlib.run();

View File

@ -1,5 +1,4 @@
-- Eventloop behavior tests for the CraftOS-PC harness.
-- Invoked via `craftos --headless --script tests/eventloop.lua` from `just test`.
local createEventLoop = require('/apis/eventloop');
local createLibTest = require('/apis/libtest');

View File

@ -1,8 +1,15 @@
-- Smoke test: prove the CC event queue round-trips a custom event.
-- Invoked via `craftos --headless --script tests/ready.lua` from `just test`.
os.queueEvent('craftos-ready');
local ev = os.pullEventRaw();
if ev == 'craftos-ready' then
print('__TRAPOS_TEST_OK__');
end
os.shutdown();
-- Basic integration test: prove the CC event queue round-trips a custom event.
local createLibTest = require('/apis/libtest');
local testlib = createLibTest({ ... });
testlib.test('event queue round-trips a custom event', function()
os.queueEvent('craftos-ready');
local ev;
repeat
ev = os.pullEventRaw();
until ev == 'craftos-ready';
testlib.assertEquals(ev, 'craftos-ready');
end);
testlib.run();