tests: Initial test and benchmark setup

Add a unit test for wlr_box and benchmark for wlr_scene_node_at as our
first test examples, lowering the barrier for adding more tests as
suitable.
This commit is contained in:
Kenny Levinsen 2026-02-26 17:12:41 +01:00 committed by Simon Ser
parent ff7d093800
commit 648790f43a
5 changed files with 318 additions and 0 deletions

View file

@ -178,6 +178,10 @@ if get_option('examples')
subdir('tinywl') subdir('tinywl')
endif endif
if get_option('tests')
subdir('test')
endif
pkgconfig = import('pkgconfig') pkgconfig = import('pkgconfig')
pkgconfig.generate( pkgconfig.generate(
lib_wlr, lib_wlr,

View file

@ -7,5 +7,6 @@ option('backends', type: 'array', choices: ['auto', 'drm', 'libinput', 'x11'], v
option('allocators', type: 'array', choices: ['auto', 'gbm', 'udmabuf'], value: ['auto'], option('allocators', type: 'array', choices: ['auto', 'gbm', 'udmabuf'], value: ['auto'],
description: 'Select built-in allocators') description: 'Select built-in allocators')
option('session', type: 'feature', value: 'auto', description: 'Enable session support') option('session', type: 'feature', value: 'auto', description: 'Enable session support')
option('tests', type: 'boolean', value: true, description: 'Build tests and benchmarks')
option('color-management', type: 'feature', value: 'auto', description: 'Enable support for color management') option('color-management', type: 'feature', value: 'auto', description: 'Enable support for color management')
option('libliftoff', type: 'feature', value: 'auto', description: 'Enable support for libliftoff') option('libliftoff', type: 'feature', value: 'auto', description: 'Enable support for libliftoff')

154
test/bench_scene.c Normal file
View file

@ -0,0 +1,154 @@
#include <stdio.h>
#include <time.h>
#include <wlr/types/wlr_scene.h>
struct tree_spec {
// Parameters for the tree we'll construct
int depth;
int branching;
int rect_size;
int spread;
// Stats around the tree we built
int tree_count;
int rect_count;
int max_x;
int max_y;
};
static int max(int a, int b) {
return a > b ? a : b;
}
static double timespec_diff_msec(struct timespec *start, struct timespec *end) {
return (double)(end->tv_sec - start->tv_sec) * 1e3 +
(double)(end->tv_nsec - start->tv_nsec) / 1e6;
}
static bool build_tree(struct wlr_scene_tree *parent, struct tree_spec *spec,
int depth, int x, int y) {
if (depth == spec->depth) {
float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
struct wlr_scene_rect *rect =
wlr_scene_rect_create(parent, spec->rect_size, spec->rect_size, color);
if (rect == NULL) {
fprintf(stderr, "wlr_scene_rect_create failed\n");
return false;
}
wlr_scene_node_set_position(&rect->node, x, y);
spec->max_x = max(spec->max_x, x + spec->rect_size);
spec->max_y = max(spec->max_y, y + spec->rect_size);
spec->rect_count++;
return true;
}
for (int i = 0; i < spec->branching; i++) {
struct wlr_scene_tree *child = wlr_scene_tree_create(parent);
if (child == NULL) {
fprintf(stderr, "wlr_scene_tree_create failed\n");
return false;
}
spec->tree_count++;
int offset = i * spec->spread;
wlr_scene_node_set_position(&child->node, offset, offset);
if (!build_tree(child, spec, depth + 1, x + offset, y + offset)) {
return false;
}
}
return true;
}
static bool bench_create_tree(struct wlr_scene *scene, struct tree_spec *spec) {
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
if (!build_tree(&scene->tree, spec, 0, 0, 0)) {
fprintf(stderr, "build_tree failed\n");
return false;
}
clock_gettime(CLOCK_MONOTONIC, &end);
printf("Built tree with %d tree nodes, %d rect nodes\n\n",
spec->tree_count, spec->rect_count);
double elapsed = timespec_diff_msec(&start, &end);
int nodes = spec->tree_count + spec->rect_count;
printf("create test tree: %d nodes, %.3f ms, %.0f nodes/ms\n",
nodes, elapsed, nodes / elapsed);
return true;
}
static void bench_scene_node_at(struct wlr_scene *scene, struct tree_spec *spec) {
struct timespec start, end;
int iters = 10000;
int hits = 0;
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < iters; i++) {
// Spread lookups across the tree extent
double lx = (double)(i * 97 % spec->max_x);
double ly = (double)(i * 53 % spec->max_y);
double nx, ny;
struct wlr_scene_node *node =
wlr_scene_node_at(&scene->tree.node, lx, ly, &nx, &ny);
if (node != NULL) {
hits++;
}
}
clock_gettime(CLOCK_MONOTONIC, &end);
double elapsed = timespec_diff_msec(&start, &end);
int nodes = (spec->tree_count + spec->rect_count) * iters;
printf("wlr_scene_node_at: %d iters, %.3f ms, %.0f nodes/ms (hits: %d/%d)\n",
iters, elapsed, nodes / elapsed, hits, iters);
}
static void noop_iterator(struct wlr_scene_buffer *buffer,
int sx, int sy, void *user_data) {
(void)buffer;
(void)sx;
(void)sy;
int *cnt = user_data;
(*cnt)++;
}
static void bench_scene_node_for_each_buffer(struct wlr_scene *scene, struct tree_spec *spec) {
struct timespec start, end;
int iters = 10000;
int hits = 0;
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < iters; i++) {
wlr_scene_node_for_each_buffer(&scene->tree.node,
noop_iterator, &hits);
}
clock_gettime(CLOCK_MONOTONIC, &end);
double elapsed = timespec_diff_msec(&start, &end);
int nodes = (spec->tree_count + spec->rect_count) * iters;
printf("wlr_scene_node_for_each_buffer: %d iters, %.3f ms, %.0f nodes/ms (hits: %d/%d)\n",
iters, elapsed, nodes / elapsed, hits, iters);
}
int main(void) {
struct wlr_scene *scene = wlr_scene_create();
if (scene == NULL) {
fprintf(stderr, "wlr_scene_create failed\n");
return 99;
}
struct tree_spec spec = {
.depth = 5,
.branching = 5,
.rect_size = 10,
.spread = 100,
};
if (!bench_create_tree(scene, &spec)) {
return 99;
}
bench_scene_node_at(scene, &spec);
bench_scene_node_for_each_buffer(scene, &spec);
wlr_scene_node_destroy(&scene->tree.node);
return 0;
}

10
test/meson.build Normal file
View file

@ -0,0 +1,10 @@
test(
'box',
executable('test-box', 'test_box.c', dependencies: wlroots),
)
benchmark(
'scene',
executable('bench-scene', 'bench_scene.c', dependencies: wlroots),
timeout: 30,
)

149
test/test_box.c Normal file
View file

@ -0,0 +1,149 @@
#include <assert.h>
#include <stddef.h>
#include <wlr/util/box.h>
static void test_box_empty(void) {
// NULL is empty
assert(wlr_box_empty(NULL));
// Zero width/height
struct wlr_box box = { .x = 0, .y = 0, .width = 0, .height = 10 };
assert(wlr_box_empty(&box));
box = (struct wlr_box){ .x = 0, .y = 0, .width = 10, .height = 0 };
assert(wlr_box_empty(&box));
// Negative width/height
box = (struct wlr_box){ .x = 0, .y = 0, .width = -1, .height = 10 };
assert(wlr_box_empty(&box));
box = (struct wlr_box){ .x = 0, .y = 0, .width = 10, .height = -1 };
assert(wlr_box_empty(&box));
// Valid box
box = (struct wlr_box){ .x = 0, .y = 0, .width = 10, .height = 10 };
assert(!wlr_box_empty(&box));
}
static void test_box_intersection(void) {
struct wlr_box dest;
// Overlapping
struct wlr_box a = { .x = 0, .y = 0, .width = 100, .height = 100 };
struct wlr_box b = { .x = 50, .y = 50, .width = 100, .height = 100 };
assert(wlr_box_intersection(&dest, &a, &b));
assert(dest.x == 50 && dest.y == 50 &&
dest.width == 50 && dest.height == 50);
// Non-overlapping
b = (struct wlr_box){ .x = 200, .y = 200, .width = 50, .height = 50 };
assert(!wlr_box_intersection(&dest, &a, &b));
assert(dest.width == 0 && dest.height == 0);
// Touching edges
b = (struct wlr_box){ .x = 100, .y = 0, .width = 50, .height = 50 };
assert(!wlr_box_intersection(&dest, &a, &b));
// Self-intersection
assert(wlr_box_intersection(&dest, &a, &a));
assert(dest.x == a.x && dest.y == a.y &&
dest.width == a.width && dest.height == a.height);
// Empty input
struct wlr_box empty = { .x = 0, .y = 0, .width = 0, .height = 0 };
assert(!wlr_box_intersection(&dest, &a, &empty));
// NULL input
assert(!wlr_box_intersection(&dest, &a, NULL));
assert(!wlr_box_intersection(&dest, NULL, &a));
}
static void test_box_intersects_box(void) {
// Overlapping
struct wlr_box a = { .x = 0, .y = 0, .width = 100, .height = 100 };
struct wlr_box b = { .x = 50, .y = 50, .width = 100, .height = 100 };
assert(wlr_box_intersects(&a, &b));
// Non-overlapping
b = (struct wlr_box){ .x = 200, .y = 200, .width = 50, .height = 50 };
assert(!wlr_box_intersects(&a, &b));
// Touching edges
b = (struct wlr_box){ .x = 100, .y = 0, .width = 50, .height = 50 };
assert(!wlr_box_intersects(&a, &b));
// Self-intersection
assert(wlr_box_intersects(&a, &a));
// Empty input
struct wlr_box empty = { .x = 0, .y = 0, .width = 0, .height = 0 };
assert(!wlr_box_intersects(&a, &empty));
// NULL input
assert(!wlr_box_intersects(&a, NULL));
assert(!wlr_box_intersects(NULL, &a));
}
static void test_box_contains_point(void) {
struct wlr_box box = { .x = 10, .y = 20, .width = 100, .height = 50 };
// Interior point
assert(wlr_box_contains_point(&box, 50, 40));
// Inclusive lower bound
assert(wlr_box_contains_point(&box, 10, 20));
// Exclusive upper bound
assert(!wlr_box_contains_point(&box, 110, 70));
assert(!wlr_box_contains_point(&box, 110, 40));
assert(!wlr_box_contains_point(&box, 50, 70));
// Outside
assert(!wlr_box_contains_point(&box, 5, 40));
assert(!wlr_box_contains_point(&box, 50, 15));
// Empty box
struct wlr_box empty = { .x = 0, .y = 0, .width = 0, .height = 0 };
assert(!wlr_box_contains_point(&empty, 0, 0));
// NULL
assert(!wlr_box_contains_point(NULL, 0, 0));
}
static void test_box_contains_box(void) {
struct wlr_box outer = { .x = 0, .y = 0, .width = 100, .height = 100 };
// Fully contained
struct wlr_box inner = { .x = 10, .y = 10, .width = 50, .height = 50 };
assert(wlr_box_contains_box(&outer, &inner));
// Self-containment
assert(wlr_box_contains_box(&outer, &outer));
// Partial overlap — not contained
struct wlr_box partial = { .x = 50, .y = 50, .width = 100, .height = 100 };
assert(!wlr_box_contains_box(&outer, &partial));
// Empty inner
struct wlr_box empty = { .x = 0, .y = 0, .width = 0, .height = 0 };
assert(!wlr_box_contains_box(&outer, &empty));
// Empty outer
assert(!wlr_box_contains_box(&empty, &inner));
// NULL
assert(!wlr_box_contains_box(&outer, NULL));
assert(!wlr_box_contains_box(NULL, &outer));
}
int main(void) {
#ifdef NDEBUG
fprintf(stderr, "NDEBUG must be disabled for tests\n");
return 1;
#endif
test_box_empty();
test_box_intersection();
test_box_intersects_box();
test_box_contains_point();
test_box_contains_box();
return 0;
}