Testing
Rendering frames and feeding events to a sketch in tests.
testing.render_frames
testing.render_frames(
app,
frames=1,
size=(256, 256),
*,
pixel_ratio=1.0,
on_frame=None,
events=(),
with_gui=False,
fps=None,
background=None,
auto_clear=True,
exit_on_escape=None,
)Runs app for frames frames in a hidden window and returns the last frame.
The app’s full lifecycle runs: setup, then update and draw per frame, then exit. Input can be scripted with events, so tests and docs can drive mouse and keyboard handlers. To keep every frame, for an animation, pass on_frame:
frames = []
render_frames(MyApp(), frames=60, on_frame=frames.append)
The frames are the sketch’s art: kiln.Gui panels are a tool, not part of it, so they are left out, as save_screen leaves them out. Pass with_gui=True to get the frames as shown on screen, panels included (for pictures of a panel, as in the GUI tutorial).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| app | App | The App to run. | required |
| frames | int | Number of frames to render, at least 1. | 1 |
| size | tuple[int, int] | Window size in points, (width, height). | (256, 256) |
| pixel_ratio | float | Pixels per point of the render target; 2 emulates Retina. | 1.0 |
| on_frame | Callable[[np.ndarray], None] | None | Called after each frame with that frame’s pixels, in the same format as the return value. | None |
| events | Sequence[tuple[int, object]] | Scripted input, a sequence of (frame, event) pairs where event is a typed event (MouseMove(x, y), MousePress(button, x=None, y=None), MouseRelease, MouseScroll(scroll_x, scroll_y), MouseEnter(), MouseExit(), KeyPress(key), KeyRelease(key), Drop(files)). Each fires through the same dispatch as real input, right before that frame’s update (frames count from 0). The events are checked before the window opens. |
() |
| with_gui | bool | Include the Gui panels in the returned frame and in the frames given to on_frame, as they were shown. |
False |
| fps | float | None | The starting kiln.clock.target_fps, as kiln.run takes it. |
None |
| background | ColorLike | None | The starting kiln.window.background, as kiln.run takes it. |
None |
| auto_clear | bool | The starting kiln.window.auto_clear, as kiln.run takes it. |
True |
| exit_on_escape | bool | None | The starting kiln.window.exit_on_escape, as kiln.run takes it. |
None |
Returns
| Name | Type | Description |
|---|---|---|
| np.ndarray | The final frame as an (h, w, 4) uint8 RGBA array, top row first, where | |
| np.ndarray | h and w are size scaled by pixel_ratio. |
Raises
| Name | Type | Description |
|---|---|---|
| ValueError | frames is less than 1, or an event names an unknown event, has the wrong arguments, or a frame outside range(frames). |
|
| RuntimeError | No OpenGL 4.1 context is available (no GUI session). |
testing.drain_calls
testing.drain_calls()Runs the calls queued by kiln.call_soon, in order, on the calling thread.
A running App runs them at the start of every frame. A simulation stepped without a window (a test, a headless run) calls this instead, between its steps, so that the writes other threads made with Population.write land::
pop.write(ids, goal=targets) # from any thread
kiln.testing.drain_calls() # on the simulation's thread: the goals land
for _ in range(60):
step(pop, 1 / 60)
Only the calls queued when it starts run; a call queued by one of them waits for the next drain. An exception propagates and leaves the later calls queued.
testing.pinned_clock
testing.pinned_clock(fps)Runs Kiln’s clock off the frame number: frame n starts at n / fps seconds.
For deterministic tests and renders: a run’s timing depends only on how many frames have passed, never on the machine’s speed or load, so the same sketch gives the same pixels and the same state every time::
with kiln.testing.pinned_clock(60):
first = render_frames(MyApp(), frames=30)
with kiln.testing.pinned_clock(60):
second = render_frames(MyApp(), frames=30)
assert (first == second).all()
Inside the block, for an App that starts running there, every frame lasts exactly 1 / fps seconds: kiln.clock.time (and so Material.time, a shader’s kiln_time) reads n / fps during frame n, clock.dt reads 1 / fps from the first frame on, clock.fps reads fps, and clock.fixed_steps(rate) gives exactly rate steps per second of pinned time (fps / rate frames). Frames are not paced: clock.target_fps waits for nothing, so a long pinned run takes only as long as its drawing.
Only an App started inside the block is pinned, and the clock moves only when a frame ends. On exit, including on an exception, Kiln’s clock and its frame counters are as they were before the block. Blocks nest: the innermost fps applies, and leaving it brings back the outer one. The block pins the process, not one thread or one App; it is not safe to enter from two threads at once.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| fps | float | Pinned frames per second, a finite number above 0. | required |
Raises
| Name | Type | Description |
|---|---|---|
| ValueError | fps is not a finite number above 0. |
testing.MouseMove
testing.MouseMove(x, y)Scripted mouse move for render_frames(events=...).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| x | float | Mouse x in window points. | required |
| y | float | Mouse y in window points. | required |
testing.MousePress
testing.MousePress(button=MouseButton.LEFT, x=None, y=None)Scripted mouse press for render_frames(events=...).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| button | int | A MouseButton. | MouseButton.LEFT |
| x | float | None | Move the mouse here first. None presses at the current position. | None |
| y | float | None | Move the mouse here first. None presses at the current position. | None |
testing.MouseRelease
testing.MouseRelease(button=MouseButton.LEFT, x=None, y=None)Scripted mouse release for render_frames(events=...).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| button | int | A MouseButton. | MouseButton.LEFT |
| x | float | None | Move the mouse here first. None releases at the current position. | None |
| y | float | None | Move the mouse here first. None releases at the current position. | None |
testing.MouseScroll
testing.MouseScroll(scroll_x, scroll_y)Scripted scroll for render_frames(events=...).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| scroll_x | float | Horizontal scroll amount. | required |
| scroll_y | float | Vertical scroll amount. | required |
testing.MouseEnter
testing.MouseEnter()Scripted cursor entering the window.
testing.MouseExit
testing.MouseExit()Scripted cursor leaving the window.
testing.KeyPress
testing.KeyPress(key)Scripted key press for render_frames(events=...).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| key | int | A Key member or a character code such as ord("a"). |
required |
testing.KeyRelease
testing.KeyRelease(key)Scripted key release for render_frames(events=...).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| key | int | A Key member or a character code such as ord("a"). |
required |
testing.Drop
testing.Drop(files=list())Scripted file drop for render_frames(events=...).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| files | list[str] | Paths of the dropped files. | list() |