Testing
odin run tools/run_tests runs every test package of this system, side by side, each its own process (-list names them; the first lines of
tools/run_tests/main.odin give the flags). On Windows it runs itself on a desktop of its own: what a test opens can take nobody's focus, mouse or keyboard.
odin test tests/<name> -vet runs one package; those that build an interface add -define:ODIN_TEST_THREADS=1. A new package is a line of the list in
tools/run_tests/main.odin, with its group for the WebGPU backend.
A test of an interface
A test is an @(test) procedure on Odin's own test runner, and it calls the toolkit directly (src/testkit/session, toolkit:testkit/session from an application):
Odin
App :: struct { clicks: int, name: ui.Text_Field }
@(test)
the_button_counts :: proc(t: ^testing.T) {
app: App
defer ui.field_destroy(&app.name)
s := session.start(t, {user = &app, build = proc(s: ^session.Session) {
app := (^App)(s.user)
if ui.button("Add###add").clicked { app.clicks += 1 }
ui.line_edit("###name", &app.name)
}})
session.click(s, "add") // by the name after ### in its id
testing.expect_value(t, app.clicks, 1)
session.click(s, "name")
session.type_text(s, "hello")
session.key(s, .A, {.Ctrl})
session.key(s, .C, {.Ctrl})
testing.expect_value(t, session.clipboard(s), "hello") // the process's clipboard, never the machine's
img := session.capture(s) // the last frame of the real renderer, offscreen
testing.expect(t, session.pixel(img, 10, 10).a == 255)
}The rules:
- No window of the system, no real input. The window is offscreen (
platform.open_window_offscreen), the renderer the offscreen one (WARP on Windows, a software Vulkan on Linux), input goes throughplatform.inject_eventandpoll_eventsas a device's does. Its machine is virtual:platform.virtual_screen_set(size, rate),session.set_dpi,session.activate,session.clipboard,session.cursor. Severalsession.startin a test are several windows. - Barriers, never sleeps.
session.wait_until(s, ready, "what"),wait_box,wait_text,wait_gone,wait_idlemake frames until what they wait for holds; a deadline only ends a failing run. Every input action returns once what it caused is built. - Time is the session's.
session.advance(s, ms)moves the clock (ui.set_clock) frame by frame: a tooltip's delay, a caret's blink, a scroll bar that hides happen with no wait. Elsewhere: a clock passed in (Build_Params.time_ms,platform.cadence_*take theirs), a semaphore or a barrier for another thread. - An action that fails ends the test there, with where it was called from; what it left is in
build/session/<test>/(failure.png,failure_tree.txt, itssave_pngs). A test checks no return; checks aretesting.expect*. - A whole application:
session.attach(t, {win = app.win, state = &app.ui_state, renderer = &app.r, frame = ..., user = app})drives one that makes its own window (offscreen) and frames (tests/examples). Something the application does not let a test see: add it to the application, not to the test. - The devtools protocol without a port:
devtools.start_localanddevtools.Local_Client(tests/examples/whatsnew_test.odin). - Pictures:
session.expect_like(s, path)against a reference image,TOOLKIT_TEST_UPDATE=1writes them: review them like code. Prefer a colour at a point (pixel,count_color): it says what is wrong. - The globals:
tests/globalsreads the packages' sources (next to it, not from the working directory) and checks every package-level variable againsttools/globals_allowed.txt(getting-started.md, "Where state lives"). - The case of names:
tests/unicode_casereads the sources the same way and fails on a call ofstrings.to_lower/to_upper/equal_foldorunicode.to_lower/to_upperthattools/unicode_case_allowed.txtdoes not list with why: they fold by Unicode (the Kelvin sign U+212A iskto them), names and protocols takeascii.lower,ascii.equal_fold... - What needs a real window stays in
tests/window,tests/cursors,tests/tray: hidden windows, fed the system's messages, on the runner's desktop. What needs a real server on Linux:tk desktop_tests. Each server and the test program run in a process group of their own, stopped whole when they end, on a failure, a time limit or a signal totk; their output goes to files, the test program's printed when it ends.
Numbers in a test are named at the top of its file.