protocols/
test/
.gitignore
21 B
clients.py
36.0 KiB
run.py
30.7 KiB
unit.c
35.5 KiB
.clang-format
571 B
.clangd
365 B
.gitignore
82 B
.gitsigners
91 B
LICENSE
32.3 KiB
LICENSE.dwl
34.9 KiB
LICENSE.dwm
2.0 KiB
LICENSE.spectrwm
1.3 KiB
LICENSE.sway
1.0 KiB
LICENSE.tinywl
7.0 KiB
Makefile
7.8 KiB
PKGBUILD
952 B
README
5.6 KiB
config.def.h
15.7 KiB
config.mk
269 B
swm.1.adoc
3.3 KiB
swm.c
247.1 KiB
swm.desktop
81 B
swm.h
3.5 KiB
swmctl.1.adoc
2.5 KiB
swmctl.c
19.6 KiB
util.c
2.2 KiB
util.h
399 B
test/unit.c
raw
| 1 | #include <assert.h> |
| 2 | #include <fcntl.h> |
| 3 | #include <limits.h> |
| 4 | #include <stdlib.h> |
| 5 | #include <string.h> |
| 6 | #include <sys/wait.h> |
| 7 | |
| 8 | #define main swm_program_main |
| 9 | #include "swm.c" |
| 10 | #undef main |
| 11 | |
| 12 | /* Verify that releasing a pool item clears its storage. */ |
| 13 | static void test_pool(pool_t *pool) { |
| 14 | unsigned char *item = pool_take(pool); |
| 15 | size_t i; |
| 16 | |
| 17 | assert(item); |
| 18 | memset(item, 0xa5, pool->item_size); |
| 19 | pool_release(pool, item); |
| 20 | |
| 21 | for (i = 0; i < pool->item_size; i++) |
| 22 | assert(item[i] == 0); |
| 23 | } |
| 24 | |
| 25 | /* Verify pool allocation limits and clearing for every compositor object type. */ |
| 26 | static void test_pools(void) { |
| 27 | client_t *allocated[MAX_CLIENTS]; |
| 28 | int saved, nullfd; |
| 29 | size_t i; |
| 30 | |
| 31 | for (i = 0; i < MAX_CLIENTS; i++) { |
| 32 | allocated[i] = pool_take(&client_pool); |
| 33 | assert(allocated[i] == &client_items[i]); |
| 34 | } |
| 35 | saved = dup(STDERR_FILENO); |
| 36 | nullfd = open("/dev/null", O_WRONLY); |
| 37 | assert(saved >= 0 && nullfd >= 0); |
| 38 | assert(dup2(nullfd, STDERR_FILENO) >= 0); |
| 39 | assert(pool_take(&client_pool) == nullptr); |
| 40 | assert(dup2(saved, STDERR_FILENO) >= 0); |
| 41 | close(nullfd); |
| 42 | close(saved); |
| 43 | |
| 44 | for (i = 0; i < MAX_CLIENTS; i++) |
| 45 | pool_release(&client_pool, allocated[i]); |
| 46 | |
| 47 | test_pool(&monitor_pool); |
| 48 | test_pool(&layer_surface_pool); |
| 49 | test_pool(&keyboard_group_pool); |
| 50 | test_pool(&pointer_constraint_pool); |
| 51 | test_pool(&text_input_pool); |
| 52 | test_pool(&input_popup_pool); |
| 53 | test_pool(&session_lock_pool); |
| 54 | test_pool(&pending_spawn_pool); |
| 55 | test_pool(&window_state_pool); |
| 56 | test_pool(&static_listener_pool); |
| 57 | test_pool(&workspace_manager_pool); |
| 58 | test_pool(&workspace_handle_pool); |
| 59 | test_pool(&metadata_manager_pool); |
| 60 | } |
| 61 | |
| 62 | /* Require a geometry helper to return the expected rectangle. */ |
| 63 | static void assert_box(struct swm_box box, int x, int y, int width, int height) { |
| 64 | assert(box.x == x); |
| 65 | assert(box.y == y); |
| 66 | assert(box.width == width); |
| 67 | assert(box.height == height); |
| 68 | } |
| 69 | |
| 70 | /* Verify pure geometry helpers. */ |
| 71 | static void test_geometry(void) { |
| 72 | struct swm_box bounds = { 10, 20, 100, 80 }; |
| 73 | struct swm_box box = { 200, 200, 0, -2 }; |
| 74 | struct swm_box client = { 100, 100, 300, 200 }; |
| 75 | struct swm_box cursor = { 20, 30, 4, 10 }; |
| 76 | struct swm_box output = { 0, 0, 500, 400 }; |
| 77 | |
| 78 | swm_box_apply_bounds(&box, &bounds, 2); |
| 79 | assert_box(box, 105, 95, 5, 5); |
| 80 | box = (struct swm_box){ 10, 20, 100, 80 }; |
| 81 | assert_box(swm_box_resize(&box, 20, 10, 10, 1), 10, 20, 120, 90); |
| 82 | assert_box(swm_box_resize(&box, 500, 500, 5, 2), 105, 95, 5, 5); |
| 83 | swm_box_reconcile_commit(&box, 90, 70, 2, 5); |
| 84 | assert_box(box, 16, 26, 94, 74); |
| 85 | assert_box(swm_popup_position(&client, 2, &cursor, 400, 300, &output), 100, 0, 400, 300); |
| 86 | } |
| 87 | |
| 88 | /* Verify pure workspace and stack-state helpers. */ |
| 89 | static void test_state(void) { |
| 90 | bool visible[] = { false, false, false, false, false }; |
| 91 | bool occupied[] = { false, false, true, false, true }; |
| 92 | struct swm_stack_state state = { 16, 1, 1 }; |
| 93 | int side; |
| 94 | |
| 95 | assert(swm_workspace_state(false, false, false) == SWM_WORKSPACE_HIDDEN); |
| 96 | assert(swm_workspace_state(true, true, true) == (SWM_WORKSPACE_ACTIVE | SWM_WORKSPACE_URGENT)); |
| 97 | assert(swm_border_width(3, false, false, false, false, false) == 3); |
| 98 | assert(swm_border_width(3, true, false, false, false, false) == 0); |
| 99 | assert(swm_workspace_next(0, 5, 1, false, visible, occupied) == 2); |
| 100 | assert(swm_workspace_next(0, 5, -1, false, visible, occupied) == 4); |
| 101 | visible[2] = true; |
| 102 | assert(swm_workspace_next(0, 5, 1, false, visible, occupied) == 4); |
| 103 | assert(swm_workspace_next(0, 5, 1, true, visible, occupied) == 1); |
| 104 | assert(swm_stack_configure(&state, SWM_MASTER_SHRINK, SWM_MASTER_LEFT, &side)); |
| 105 | assert(state.msize == 15 && side == SWM_MASTER_LEFT); |
| 106 | assert(tiled_resize_value(500, 1000, false) == 16); |
| 107 | assert(tiled_resize_value(250, 1000, false) == 8); |
| 108 | assert(tiled_resize_value(250, 1000, true) == 24); |
| 109 | assert(tiled_resize_value(-100, 1000, false) == 1); |
| 110 | assert(tiled_resize_value(1100, 1000, false) == 31); |
| 111 | assert(tiled_resize_value(10, 16, false) == -1); |
| 112 | |
| 113 | for (int command = SWM_MASTER_GROW; command <= SWM_FLIP_LAYOUT; command++) |
| 114 | assert(swm_stack_configure(&state, command, SWM_MASTER_LEFT, &side)); |
| 115 | assert(!swm_stack_configure(&state, 99, SWM_MASTER_LEFT, &side)); |
| 116 | } |
| 117 | |
| 118 | /* Verify pure rule and persistent-state parsing helpers. */ |
| 119 | static void test_parsing(void) { |
| 120 | char small[5], appid[MAX_WINDOW_STATE_FIELD], title[MAX_WINDOW_STATE_FIELD]; |
| 121 | struct swm_box geometry; |
| 122 | |
| 123 | assert(swm_rule_matches("*", nullptr, "anything", "title")); |
| 124 | assert(swm_rule_matches("term", "shell", "my-terminal", "a shell")); |
| 125 | assert(!swm_rule_matches("browser", nullptr, "terminal", "browser")); |
| 126 | swm_sanitize_field(small, sizeof(small), "a\tb\nc", nullptr); |
| 127 | assert(!strcmp(small, "a b ")); |
| 128 | assert(swm_parse_window_state( |
| 129 | "org.app\tWindow\t1\t-2\t300\t200\n", appid, sizeof(appid), title, sizeof(title), &geometry |
| 130 | )); |
| 131 | assert(!strcmp(appid, "org.app")); |
| 132 | assert(!strcmp(title, "Window")); |
| 133 | assert_box(geometry, 1, -2, 300, 200); |
| 134 | assert( |
| 135 | !swm_parse_window_state("broken", appid, sizeof(appid), title, sizeof(title), &geometry) |
| 136 | ); |
| 137 | } |
| 138 | |
| 139 | /* Verify layout cycling and complete output-area partitioning. */ |
| 140 | static void test_layouts(void) { |
| 141 | bool cycle[] = { true, true, false, true }; |
| 142 | struct swm_box area = { -3, 7, 64, 61 }; |
| 143 | struct swm_box boxes[64]; |
| 144 | struct swm_stack_state state; |
| 145 | size_t count, produced; |
| 146 | int rotate, flip, masters, stacks; |
| 147 | |
| 148 | assert(swm_next_layout(0, 4, cycle) == 1); |
| 149 | assert(swm_next_layout(1, 4, cycle) == 3); |
| 150 | assert(swm_next_layout(3, 4, cycle) == 0); |
| 151 | |
| 152 | for (rotate = 0; rotate <= 1; rotate++) |
| 153 | for (flip = 0; flip <= 1; flip++) |
| 154 | for (masters = 0; masters < 5; masters++) |
| 155 | for (stacks = 1; stacks < 5; stacks++) |
| 156 | for (count = 1; count < 13; count++) { |
| 157 | int covered = 0; |
| 158 | |
| 159 | state = (struct swm_stack_state){ 13, masters, stacks }; |
| 160 | produced = swm_stack_layout(&area, &state, rotate, flip, count, boxes); |
| 161 | assert(produced == count); |
| 162 | |
| 163 | for (size_t i = 0; i < count; i++) |
| 164 | covered += boxes[i].width * boxes[i].height; |
| 165 | assert(covered == area.width * area.height); |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | /* Verify environment expansion and output bounds. */ |
| 170 | static void test_utilities(void) { |
| 171 | char output[64]; |
| 172 | |
| 173 | assert(setenv("SWM_TEST_ENV", "hello world", 1) == 0); |
| 174 | assert(env_expand(output, sizeof(output), "$SWM_TEST_ENV/tail") == 17); |
| 175 | assert(!strcmp(output, "hello world/tail")); |
| 176 | assert(env_expand(output, 4, "$SWM_TEST_ENV") == 0); |
| 177 | assert(env_expand(output, 0, "value") == 0); |
| 178 | assert(env_expand(output, sizeof(output), "$1") == 3); |
| 179 | assert(!strcmp(output, "$1")); |
| 180 | } |
| 181 | |
| 182 | /* Verify free-workspace selection and published workspace states. */ |
| 183 | static void test_workspace_queries(void) { |
| 184 | client_t *client; |
| 185 | workspace_t *workspace; |
| 186 | int i; |
| 187 | |
| 188 | for (i = 0; i < WSCOUNT; i++) { |
| 189 | workspaces[i].mon = (monitor_t *)1; |
| 190 | wl_list_init(&workspaces[i].handles); |
| 191 | } |
| 192 | workspaces[4].mon = nullptr; |
| 193 | assert(free_workspace() == &workspaces[4]); |
| 194 | workspaces[4].mon = (monitor_t *)1; |
| 195 | assert(free_workspace() == nullptr); |
| 196 | |
| 197 | wl_list_init(&clients); |
| 198 | workspace = &workspaces[2]; |
| 199 | workspace->mon = nullptr; |
| 200 | assert(workspace_state(workspace) == EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN); |
| 201 | client = pool_take(&client_pool); |
| 202 | assert(client); |
| 203 | client->ws = workspace; |
| 204 | client->is_urgent = 1; |
| 205 | wl_list_insert(&clients, &client->link); |
| 206 | assert(workspace_state(workspace) == EXT_WORKSPACE_HANDLE_V1_STATE_URGENT); |
| 207 | workspace->mon = (monitor_t *)1; |
| 208 | assert( |
| 209 | workspace_state(workspace) == |
| 210 | (EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE | EXT_WORKSPACE_HANDLE_V1_STATE_URGENT) |
| 211 | ); |
| 212 | wl_list_remove(&client->link); |
| 213 | pool_release(&client_pool, client); |
| 214 | } |
| 215 | |
| 216 | /* Verify state-file paths and client border-width policy. */ |
| 217 | static void test_paths_and_border_policy(void) { |
| 218 | const char *directory = getenv("SWM_TEST_DIR"); |
| 219 | client_t client = {}; |
| 220 | struct wlr_scene_tree scene = {}; |
| 221 | struct wlr_xwayland_surface xsurface = { .override_redirect = true }; |
| 222 | char path[PATH_MAX], expected[PATH_MAX]; |
| 223 | |
| 224 | assert(directory); |
| 225 | assert(snprintf(path, sizeof(path), "%s/state", directory) < (int)sizeof(path)); |
| 226 | assert(setenv("XDG_STATE_HOME", path, 1) == 0); |
| 227 | assert(snprintf(expected, sizeof(expected), "%s/swm/windows", path) < (int)sizeof(expected)); |
| 228 | assert(!strcmp(window_state_path(), expected)); |
| 229 | unsetenv("XDG_STATE_HOME"); |
| 230 | assert(snprintf(path, sizeof(path), "%s/home", directory) < (int)sizeof(path)); |
| 231 | assert(setenv("HOME", path, 1) == 0); |
| 232 | assert( |
| 233 | snprintf(expected, sizeof(expected), "%s/.local/state/swm/windows", path) < |
| 234 | (int)sizeof(expected) |
| 235 | ); |
| 236 | assert(!strcmp(window_state_path(), expected)); |
| 237 | client_set_border_color(&client, bordercolor); |
| 238 | client.type = X11; |
| 239 | client.surface.xwayland = &xsurface; |
| 240 | client.scene = &scene; |
| 241 | client_set_border_color(&client, bordercolor); |
| 242 | client = (client_t){}; |
| 243 | client.bw = 9; |
| 244 | assert(client_border_width(&client) == borderwidth); |
| 245 | client.is_fullscreen = 1; |
| 246 | assert(client_border_width(&client) == 0); |
| 247 | } |
| 248 | |
| 249 | /* Initialize a minimal XDG client object for internal tests. */ |
| 250 | static void init_xdg_client( |
| 251 | client_t *client, |
| 252 | struct wlr_xdg_surface *surface, |
| 253 | struct wlr_xdg_toplevel *toplevel, |
| 254 | const char *appid, |
| 255 | const char *title |
| 256 | ) { |
| 257 | memset(client, 0, sizeof(*client)); |
| 258 | memset(surface, 0, sizeof(*surface)); |
| 259 | memset(toplevel, 0, sizeof(*toplevel)); |
| 260 | client->type = XDG_SHELL; |
| 261 | client->surface.xdg = surface; |
| 262 | surface->toplevel = toplevel; |
| 263 | toplevel->base = surface; |
| 264 | toplevel->app_id = (char *)appid; |
| 265 | toplevel->title = (char *)title; |
| 266 | } |
| 267 | |
| 268 | /* Verify matching rules update client state and workspace assignment. */ |
| 269 | static void test_rule_application(void) { |
| 270 | client_t client; |
| 271 | struct wlr_xdg_surface surface; |
| 272 | struct wlr_xdg_toplevel toplevel; |
| 273 | |
| 274 | init_xdg_client(&client, &surface, &toplevel, "org.telegram.desktop", "Telegram"); |
| 275 | wl_list_init(&mons); |
| 276 | selmon = nullptr; |
| 277 | apply_rules(&client, nullptr); |
| 278 | assert(client.is_floating == 1); |
| 279 | assert(client.is_borderless == 1); |
| 280 | assert(client.mon == nullptr); |
| 281 | } |
| 282 | |
| 283 | /* Verify focus queries across mapped, hidden, and unmanaged clients. */ |
| 284 | static void test_focus_queries(void) { |
| 285 | client_t first, second, hidden; |
| 286 | struct wlr_xdg_surface surfaces[3]; |
| 287 | struct wlr_xdg_toplevel toplevels[3]; |
| 288 | monitor_t monitor = {}; |
| 289 | workspace_t shown = {}, other = {}; |
| 290 | |
| 291 | shown.mon = &monitor; |
| 292 | monitor.ws = &shown; |
| 293 | other.mon = nullptr; |
| 294 | init_xdg_client(&first, &surfaces[0], &toplevels[0], "first", "First"); |
| 295 | init_xdg_client(&second, &surfaces[1], &toplevels[1], "second", "Second"); |
| 296 | init_xdg_client(&hidden, &surfaces[2], &toplevels[2], "hidden", "Hidden"); |
| 297 | first.ws = second.ws = &shown; |
| 298 | hidden.ws = &other; |
| 299 | first.mon = second.mon = &monitor; |
| 300 | |
| 301 | wl_list_init(&clients); |
| 302 | wl_list_init(&fstack); |
| 303 | wl_list_insert(clients.prev, &first.link); |
| 304 | wl_list_insert(clients.prev, &second.link); |
| 305 | wl_list_insert(clients.prev, &hidden.link); |
| 306 | wl_list_insert(&fstack, &hidden.flink); |
| 307 | wl_list_insert(&fstack, &first.flink); |
| 308 | wl_list_insert(&fstack, &second.flink); |
| 309 | assert(focus_top(&monitor) == &second); |
| 310 | assert(focus_top(nullptr) == nullptr); |
| 311 | assert(focus_close(&second) == &first); |
| 312 | assert(focus_close(nullptr) == nullptr); |
| 313 | assert(focus_close(&hidden) == nullptr); |
| 314 | assert(client_main(nullptr) == nullptr); |
| 315 | assert(!clients_related(nullptr, &first)); |
| 316 | assert(clients_related(&first, &first)); |
| 317 | } |
| 318 | |
| 319 | /* Release every saved window-state entry. */ |
| 320 | static void clear_window_states(void) { |
| 321 | window_state_t *state, *tmp; |
| 322 | |
| 323 | wl_list_for_each_safe(state, tmp, &window_states, link) { |
| 324 | wl_list_remove(&state->link); |
| 325 | pool_release(&window_state_pool, state); |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | /* Verify saving, loading, finding, and forgetting persistent window state. */ |
| 330 | static void test_window_state_storage(void) { |
| 331 | char statehome[PATH_MAX]; |
| 332 | char filepath[MAX_STATE_PATH]; |
| 333 | client_t client; |
| 334 | struct wlr_xdg_surface surface; |
| 335 | struct wlr_xdg_toplevel toplevel; |
| 336 | window_state_t *state; |
| 337 | FILE *file; |
| 338 | |
| 339 | assert( |
| 340 | snprintf(statehome, sizeof(statehome), "%s/window-state-XXXXXX", getenv("SWM_TEST_DIR")) < |
| 341 | (int)sizeof(statehome) |
| 342 | ); |
| 343 | assert(mkdtemp(statehome)); |
| 344 | setenv("XDG_STATE_HOME", statehome, 1); |
| 345 | wl_list_init(&window_states); |
| 346 | init_xdg_client(&client, &surface, &toplevel, "org.test\tapp", "Window\nTitle"); |
| 347 | client.persist_float = client.is_floating = 1; |
| 348 | client.geom = (struct wlr_box){ 11, -12, 640, 480 }; |
| 349 | remember_client(&client); |
| 350 | state = find_window_state(&client); |
| 351 | assert(state); |
| 352 | assert(!strcmp(state->appid, "org.test app")); |
| 353 | assert(!strcmp(state->title, "Window Title")); |
| 354 | assert(state->geom.x == 11 && state->geom.height == 480); |
| 355 | |
| 356 | snprintf(filepath, sizeof(filepath), "%s/swm/windows", statehome); |
| 357 | file = fopen(filepath, "r"); |
| 358 | assert(file); |
| 359 | assert(fclose(file) == 0); |
| 360 | clear_window_states(); |
| 361 | load_window_states(); |
| 362 | assert(find_window_state(&client)); |
| 363 | forget_client(&client); |
| 364 | assert(find_window_state(&client) == nullptr); |
| 365 | |
| 366 | file = fopen(filepath, "w"); |
| 367 | assert(file); |
| 368 | assert(fputs("bad line\nlegacy\t1\t2\t3\t4\n", file) >= 0); |
| 369 | assert(fclose(file) == 0); |
| 370 | load_window_states(); |
| 371 | assert(!wl_list_empty(&window_states)); |
| 372 | clear_window_states(); |
| 373 | unlink(filepath); |
| 374 | snprintf(filepath, sizeof(filepath), "%s/swm", statehome); |
| 375 | rmdir(filepath); |
| 376 | rmdir(statehome); |
| 377 | } |
| 378 | |
| 379 | /* Verify SIGCHLD handling releases tracked child processes. */ |
| 380 | static void test_child_reaping(void) { |
| 381 | pending_spawn_t *spawned; |
| 382 | struct timespec delay = { 0, 1000000 }; |
| 383 | pid_t pid; |
| 384 | int i; |
| 385 | |
| 386 | wl_list_init(&pending_spawns); |
| 387 | pid = fork(); |
| 388 | assert(pid >= 0); |
| 389 | |
| 390 | if (pid == 0) |
| 391 | _exit(0); |
| 392 | spawned = pool_take(&pending_spawn_pool); |
| 393 | assert(spawned); |
| 394 | spawned->pid = pid; |
| 395 | wl_list_insert(&pending_spawns, &spawned->link); |
| 396 | child_pid = pid; |
| 397 | |
| 398 | for (i = 0; i < 100 && !wl_list_empty(&pending_spawns); i++) { |
| 399 | handle_signal(SIGCHLD, nullptr); |
| 400 | nanosleep(&delay, nullptr); |
| 401 | } |
| 402 | assert(wl_list_empty(&pending_spawns)); |
| 403 | assert(child_pid == -1); |
| 404 | assert(handle_signal(0, nullptr) == 0); |
| 405 | } |
| 406 | |
| 407 | /* Verify spawned commands are tracked and reaped. */ |
| 408 | static void test_spawn_tracking(void) { |
| 409 | const char *command[] = { "/bin/true", nullptr }; |
| 410 | monitor_t monitor = {}; |
| 411 | workspace_t workspace = {}; |
| 412 | struct timespec delay = { 0, 1000000 }; |
| 413 | int i; |
| 414 | |
| 415 | assert(sigprocmask(SIG_BLOCK, nullptr, &original_signal_mask) == 0); |
| 416 | wl_list_init(&pending_spawns); |
| 417 | monitor.ws = &workspace; |
| 418 | selmon = &monitor; |
| 419 | spawn(&(arg_t){ .v = command }); |
| 420 | assert(!wl_list_empty(&pending_spawns)); |
| 421 | |
| 422 | for (i = 0; i < 100 && !wl_list_empty(&pending_spawns); i++) { |
| 423 | handle_signal(SIGCHLD, nullptr); |
| 424 | nanosleep(&delay, nullptr); |
| 425 | } |
| 426 | assert(wl_list_empty(&pending_spawns)); |
| 427 | selmon = nullptr; |
| 428 | } |
| 429 | |
| 430 | /* Verify a failed keybinding command produces a desktop notification. */ |
| 431 | static void test_command_failure_notification(void) { |
| 432 | const char *stub_path = getenv("SWM_TEST_STUB_PATH"); |
| 433 | const char *test_dir = getenv("SWM_TEST_DIR"); |
| 434 | const char *old_path = getenv("PATH"); |
| 435 | char *saved_path; |
| 436 | char notification[PATH_MAX]; |
| 437 | char contents[1024]; |
| 438 | char *command[] = { "swm-command-that-does-not-exist", nullptr }; |
| 439 | FILE *file; |
| 440 | pid_t pid; |
| 441 | size_t length; |
| 442 | int status; |
| 443 | |
| 444 | assert(stub_path && test_dir && old_path); |
| 445 | saved_path = strdup(old_path); |
| 446 | assert(saved_path); |
| 447 | assert( |
| 448 | snprintf(notification, sizeof(notification), "%s/notification", test_dir) < |
| 449 | (int)sizeof(notification) |
| 450 | ); |
| 451 | unlink(notification); |
| 452 | assert(setenv("PATH", stub_path, 1) == 0); |
| 453 | pid = fork(); |
| 454 | assert(pid >= 0); |
| 455 | |
| 456 | if (pid == 0) |
| 457 | execute_command(command, "key binding"); |
| 458 | assert(waitpid(pid, &status, 0) == pid); |
| 459 | assert(WIFEXITED(status) && WEXITSTATUS(status) == 0); |
| 460 | file = fopen(notification, "r"); |
| 461 | assert(file); |
| 462 | length = fread(contents, 1, sizeof(contents) - 1, file); |
| 463 | assert(length > 0); |
| 464 | contents[length] = '\0'; |
| 465 | assert(fclose(file) == 0); |
| 466 | assert(strstr(contents, "--app-name=swm")); |
| 467 | assert(strstr(contents, "--urgency=critical")); |
| 468 | assert(strstr(contents, "Command failed")); |
| 469 | assert(strstr(contents, "swm-command-that-does-not-exist")); |
| 470 | assert(setenv("PATH", saved_path, 1) == 0); |
| 471 | free(saved_path); |
| 472 | unlink(notification); |
| 473 | } |
| 474 | |
| 475 | /* Verify client commands remain safe when no client can receive them. */ |
| 476 | static void test_commands_without_clients(void) { |
| 477 | struct wlr_output output = {}; |
| 478 | monitor_t monitor = {}; |
| 479 | arg_t argument; |
| 480 | int i; |
| 481 | |
| 482 | monitor.wlr_output = &output; |
| 483 | monitor.ws = &workspaces[0]; |
| 484 | workspaces[0].mon = &monitor; |
| 485 | workspaces[0].lt = &layouts[0]; |
| 486 | workspaces[0].prevlt = nullptr; |
| 487 | workspaces[0].v = workspaces[0].h = (stack_state_t){ 16, 1, 1 }; |
| 488 | |
| 489 | for (i = 1; i < WSCOUNT; i++) |
| 490 | workspaces[i].mon = (monitor_t *)1; |
| 491 | wl_list_init(&clients); |
| 492 | wl_list_init(&fstack); |
| 493 | wl_list_init(&mons); |
| 494 | wl_list_init(&ws_managers); |
| 495 | wl_list_init(&metadata_managers); |
| 496 | selmon = &monitor; |
| 497 | unsetenv("XDG_RUNTIME_DIR"); |
| 498 | |
| 499 | cycle_layout(nullptr); |
| 500 | assert(workspaces[0].lt == &layouts[1]); |
| 501 | assert(workspaces[0].prevlt == &layouts[0]); |
| 502 | toggle_max_stack(nullptr); |
| 503 | assert(workspaces[0].lt == &layouts[4]); |
| 504 | toggle_max_stack(nullptr); |
| 505 | assert(workspaces[0].lt == &layouts[1]); |
| 506 | argument.i = MASTER_GROW; |
| 507 | stack_config(&argument); |
| 508 | assert(workspaces[0].h.msize == 17); |
| 509 | argument.i = LAYOUT_FLIP; |
| 510 | stack_config(&argument); |
| 511 | assert(workspaces[0].lt == &layouts[3]); |
| 512 | |
| 513 | assert(focus_top(&monitor) == nullptr); |
| 514 | focus_stack(&(arg_t){ .i = 1 }); |
| 515 | focus_main(nullptr); |
| 516 | focus_urgent(nullptr); |
| 517 | tag(&(arg_t){ .i = 0 }); |
| 518 | tag(&(arg_t){ .i = WSCOUNT }); |
| 519 | tag_monitor(&(arg_t){ .i = WLR_DIRECTION_LEFT }); |
| 520 | toggle_floating(nullptr); |
| 521 | toggle_fullscreen(nullptr); |
| 522 | kill_client(nullptr); |
| 523 | raise_client(nullptr); |
| 524 | zoom(nullptr); |
| 525 | argument.i = WORKSPACE_NEXT; |
| 526 | cycle_workspace(&argument); |
| 527 | assert(monitor.ws == &workspaces[0]); |
| 528 | view(&(arg_t){ .i = WSCOUNT }); |
| 529 | |
| 530 | selmon = nullptr; |
| 531 | cycle_layout(nullptr); |
| 532 | toggle_max_stack(nullptr); |
| 533 | stack_config(&argument); |
| 534 | cycle_workspace(&argument); |
| 535 | view(&(arg_t){ .i = 0 }); |
| 536 | } |
| 537 | |
| 538 | /* Verify small geometry, focus, inhibition, and listener helpers. */ |
| 539 | static void test_small_helpers(void) { |
| 540 | client_t client = { .geom = { -20, 100, 2, 2 }, .bw = 2 }; |
| 541 | struct wlr_box bounds = { 0, 0, 80, 60 }; |
| 542 | static_listener_t *slot; |
| 543 | struct wlr_seat fake_seat = {}; |
| 544 | |
| 545 | apply_bounds(&client, &bounds); |
| 546 | assert(client.geom.x == 0 && client.geom.y == 55); |
| 547 | assert(client.geom.width == 5 && client.geom.height == 5); |
| 548 | seat = &fake_seat; |
| 549 | assert(shortcuts_inhibited() == 0); |
| 550 | assert(focus_close(nullptr) == nullptr); |
| 551 | slot = pool_take(&static_listener_pool); |
| 552 | assert(slot); |
| 553 | listener_release(&slot->listener); |
| 554 | } |
| 555 | |
| 556 | /* Verify XDG and XWayland client accessors without a running compositor. */ |
| 557 | static void test_client_accessors(void) { |
| 558 | client_t client = {}; |
| 559 | struct wlr_xdg_surface xdg = { .geometry = { 3, 4, 50, 60 } }; |
| 560 | struct wlr_xdg_toplevel toplevel = {}; |
| 561 | struct wlr_xwayland_surface xwayland = { |
| 562 | .class = "x11-app", |
| 563 | .title = "X11 title", |
| 564 | .x = 7, |
| 565 | .y = 8, |
| 566 | .width = 90, |
| 567 | .height = 100, |
| 568 | .modal = true, |
| 569 | .override_redirect = true, |
| 570 | .fullscreen = true, |
| 571 | }; |
| 572 | xcb_size_hints_t size_hints = { |
| 573 | .min_width = 20, |
| 574 | .min_height = 20, |
| 575 | .max_width = 20, |
| 576 | .max_height = 40, |
| 577 | }; |
| 578 | struct wlr_box box; |
| 579 | char storage[MAX_COMMAND_SIZE], *expanded[MAX_COMMAND_ARGS]; |
| 580 | const char *const command[] = { "before-$SWM_TEST_EXPAND", "$SWM_TEST_MISSING", nullptr }; |
| 581 | |
| 582 | init_xdg_client(&client, &xdg, &toplevel, "xdg-app", "XDG title"); |
| 583 | xdg.geometry = (struct wlr_box){ 3, 4, 50, 60 }; |
| 584 | client.geom = (struct wlr_box){ 1, 2, 70, 80 }; |
| 585 | client.bw = 2; |
| 586 | client.is_fullscreen = true; |
| 587 | assert(!client_allows_move_resize(&client, CURSOR_MOVE)); |
| 588 | assert(client_allows_move_resize(&client, CURSOR_RESIZE)); |
| 589 | client.is_fullscreen = false; |
| 590 | assert(client_allows_move_resize(&client, CURSOR_MOVE)); |
| 591 | assert(!strcmp(client_get_appid(&client), "xdg-app")); |
| 592 | assert(!strcmp(client_get_title(&client), "XDG title")); |
| 593 | client_get_clip(&client, &box); |
| 594 | assert(box.x == 3 && box.y == 4 && box.width == 68 && box.height == 78); |
| 595 | client_get_geometry(&client, &box); |
| 596 | assert(box.x == 3 && box.y == 4 && box.width == 50 && box.height == 60); |
| 597 | assert(client_get_parent(&client) == nullptr); |
| 598 | assert(!client_is_float_type(&client)); |
| 599 | toplevel.current = (struct wlr_xdg_toplevel_state){ |
| 600 | .min_width = 10, |
| 601 | .min_height = 10, |
| 602 | .max_width = 10, |
| 603 | .max_height = 20, |
| 604 | }; |
| 605 | assert(client_is_float_type(&client)); |
| 606 | assert(!client_is_unmanaged(&client)); |
| 607 | toplevel.requested.fullscreen = true; |
| 608 | assert(client_wants_fullscreen(&client)); |
| 609 | |
| 610 | client.type = X11; |
| 611 | client.surface.xwayland = &xwayland; |
| 612 | assert(client_set_bounds(&client, 10, 20) == 0); |
| 613 | assert(!strcmp(client_get_appid(&client), "x11-app")); |
| 614 | assert(!strcmp(client_get_title(&client), "X11 title")); |
| 615 | client_get_clip(&client, &box); |
| 616 | assert(box.x == 0 && box.y == 0); |
| 617 | client_get_geometry(&client, &box); |
| 618 | assert(box.x == 7 && box.y == 8 && box.width == 90 && box.height == 100); |
| 619 | assert(client_get_parent(&client) == nullptr); |
| 620 | assert(client_is_float_type(&client)); |
| 621 | assert(client_is_unmanaged(&client)); |
| 622 | assert(client_wants_fullscreen(&client)); |
| 623 | xwayland.modal = false; |
| 624 | xwayland.size_hints = &size_hints; |
| 625 | assert(client_is_float_type(&client)); |
| 626 | xwayland.class = xwayland.title = nullptr; |
| 627 | assert(!strcmp(client_get_appid(&client), "broken")); |
| 628 | assert(!strcmp(client_get_title(&client), "broken")); |
| 629 | client_set_resizing(&client, true); |
| 630 | client_set_suspended(&client, true); |
| 631 | |
| 632 | setenv("SWM_TEST_EXPAND", "expanded value", 1); |
| 633 | unsetenv("SWM_TEST_MISSING"); |
| 634 | expand_argv(command, expanded, storage); |
| 635 | assert(!strcmp(expanded[0], "before-expanded value")); |
| 636 | assert(!strcmp(expanded[1], "")); |
| 637 | assert(expanded[2] == nullptr); |
| 638 | publish_windows(nullptr); |
| 639 | } |
| 640 | |
| 641 | /* Verify configured autostart commands and child tracking with harmless stubs. */ |
| 642 | static void test_autostart_execution(void) { |
| 643 | const char *stub_path = getenv("SWM_TEST_STUB_PATH"); |
| 644 | const char *old_path = getenv("PATH"); |
| 645 | char *saved_path; |
| 646 | struct timespec delay = { 0, 1000000 }; |
| 647 | size_t i; |
| 648 | int pending; |
| 649 | |
| 650 | assert(stub_path && old_path); |
| 651 | saved_path = strdup(old_path); |
| 652 | assert(saved_path); |
| 653 | assert(setenv("PATH", stub_path, 1) == 0); |
| 654 | assert(sigprocmask(SIG_BLOCK, nullptr, &original_signal_mask) == 0); |
| 655 | wl_list_init(&pending_spawns); |
| 656 | autostart_len = 0; |
| 657 | autostart_exec(); |
| 658 | assert(autostart_len > 0); |
| 659 | |
| 660 | for (i = 0; i < 100; i++) { |
| 661 | pending = 0; |
| 662 | handle_signal(SIGCHLD, nullptr); |
| 663 | |
| 664 | for (size_t j = 0; j < autostart_len; j++) |
| 665 | pending |= autostart_pids[j] > 0; |
| 666 | |
| 667 | if (!pending) |
| 668 | break; |
| 669 | nanosleep(&delay, nullptr); |
| 670 | } |
| 671 | assert(!pending); |
| 672 | assert(setenv("PATH", saved_path, 1) == 0); |
| 673 | free(saved_path); |
| 674 | } |
| 675 | |
| 676 | /* Verify forwarding of every pointer-gesture event family. */ |
| 677 | static void test_pointer_gestures(void) { |
| 678 | struct wlr_pointer_swipe_begin_event swipe_begin = { .time_msec = 1, .fingers = 3 }; |
| 679 | struct wlr_pointer_swipe_update_event swipe_update = { .time_msec = 2, .dx = 1, .dy = -1 }; |
| 680 | struct wlr_pointer_swipe_end_event swipe_end = { .time_msec = 3, .cancelled = false }; |
| 681 | struct wlr_pointer_pinch_begin_event pinch_begin = { .time_msec = 4, .fingers = 2 }; |
| 682 | struct wlr_pointer_pinch_update_event pinch_update = { |
| 683 | .time_msec = 5, |
| 684 | .dx = 1, |
| 685 | .dy = 2, |
| 686 | .scale = 1.1, |
| 687 | .rotation = 3, |
| 688 | }; |
| 689 | struct wlr_pointer_pinch_end_event pinch_end = { .time_msec = 6, .cancelled = true }; |
| 690 | struct wlr_pointer_hold_begin_event hold_begin = { .time_msec = 7, .fingers = 2 }; |
| 691 | struct wlr_pointer_hold_end_event hold_end = { .time_msec = 8, .cancelled = false }; |
| 692 | |
| 693 | dpy = wl_display_create(); |
| 694 | assert(dpy); |
| 695 | seat = wlr_seat_create(dpy, "test-seat"); |
| 696 | idle_notifier = wlr_idle_notifier_v1_create(dpy); |
| 697 | pointer_gestures = wlr_pointer_gestures_v1_create(dpy); |
| 698 | assert(seat && idle_notifier && pointer_gestures); |
| 699 | gesture_swipe_begin(nullptr, &swipe_begin); |
| 700 | gesture_swipe_update(nullptr, &swipe_update); |
| 701 | gesture_swipe_end(nullptr, &swipe_end); |
| 702 | gesture_pinch_begin(nullptr, &pinch_begin); |
| 703 | gesture_pinch_update(nullptr, &pinch_update); |
| 704 | gesture_pinch_end(nullptr, &pinch_end); |
| 705 | gesture_hold_begin(nullptr, &hold_begin); |
| 706 | gesture_hold_end(nullptr, &hold_end); |
| 707 | wl_display_destroy(dpy); |
| 708 | dpy = nullptr; |
| 709 | } |
| 710 | |
| 711 | /* Verify text-input and input-method wrappers are created and released. */ |
| 712 | static void test_input_method_lifecycle(void) { |
| 713 | struct wlr_seat fake_seat = {}; |
| 714 | struct wlr_text_input_manager_v3 fake_ti_manager = {}; |
| 715 | struct wlr_text_input_v3 text_input = {}; |
| 716 | struct wlr_input_method_v2 method = {}; |
| 717 | text_input_t *wrapper = nullptr; |
| 718 | size_t i; |
| 719 | |
| 720 | seat = &fake_seat; |
| 721 | ti_mgr = &fake_ti_manager; |
| 722 | wl_list_init(&fake_ti_manager.text_inputs); |
| 723 | assert(input_method_focused_text_input() == nullptr); |
| 724 | input_method = nullptr; |
| 725 | input_method_send_state(&text_input); |
| 726 | input_method_commit_notify(nullptr, &method); |
| 727 | |
| 728 | wl_signal_init(&text_input.events.enable); |
| 729 | wl_signal_init(&text_input.events.commit); |
| 730 | wl_signal_init(&text_input.events.disable); |
| 731 | wl_signal_init(&text_input.events.destroy); |
| 732 | text_input_create_notify(nullptr, &text_input); |
| 733 | |
| 734 | for (i = 0; i < LENGTH(text_input_used); i++) |
| 735 | |
| 736 | if (text_input_used[i]) { |
| 737 | wrapper = &text_input_items[i]; |
| 738 | break; |
| 739 | } |
| 740 | assert(wrapper && wrapper->ti == &text_input); |
| 741 | text_input_enable_notify(&wrapper->enable, nullptr); |
| 742 | text_input_commit_notify(&wrapper->commit, nullptr); |
| 743 | text_input_disable_notify(&wrapper->disable, nullptr); |
| 744 | text_input_destroy_notify(&wrapper->destroy, nullptr); |
| 745 | assert(!text_input_used[wrapper - text_input_items]); |
| 746 | |
| 747 | wl_signal_init(&method.events.commit); |
| 748 | wl_signal_init(&method.events.destroy); |
| 749 | wl_signal_init(&method.events.grab_keyboard); |
| 750 | wl_signal_init(&method.events.new_popup_surface); |
| 751 | input_method_create_notify(nullptr, &method); |
| 752 | assert(input_method == &method); |
| 753 | input_method_destroy_notify(nullptr, nullptr); |
| 754 | assert(input_method == nullptr); |
| 755 | } |
| 756 | |
| 757 | /* Verify input handlers safely reject incomplete or inapplicable state. */ |
| 758 | static void test_input_early_paths(void) { |
| 759 | struct wlr_seat fake_seat = {}; |
| 760 | struct wlr_seat_pointer_request_set_cursor_event cursor_event = {}; |
| 761 | struct wlr_cursor_shape_manager_v1_request_set_shape_event shape_event = {}; |
| 762 | struct wlr_drag drag = {}; |
| 763 | keyboard_group_t group = {}; |
| 764 | monitor_t monitor = {}; |
| 765 | workspace_t workspace = {}; |
| 766 | struct wlr_output output = {}; |
| 767 | |
| 768 | seat = &fake_seat; |
| 769 | cursor_mode = CURSOR_MOVE; |
| 770 | set_cursor(nullptr, &cursor_event); |
| 771 | set_cursor_shape(nullptr, &shape_event); |
| 772 | start_drag(nullptr, &drag); |
| 773 | assert(key_repeat(&group) == 0); |
| 774 | assert(key_binding(0, XKB_KEY_NoSymbol, 0) == 0); |
| 775 | |
| 776 | wl_list_init(&clients); |
| 777 | wl_list_init(&fstack); |
| 778 | monitor.wlr_output = &output; |
| 779 | monitor.ws = &workspace; |
| 780 | workspace.mon = &monitor; |
| 781 | workspace.lt = &layouts[0]; |
| 782 | workspace.v = workspace.h = (stack_state_t){ 16, 1, 1 }; |
| 783 | master_left(&monitor); |
| 784 | master_right(&monitor); |
| 785 | master_top(&monitor); |
| 786 | master_bottom(&monitor); |
| 787 | max_stack(&monitor); |
| 788 | move_resize(&(arg_t){ .u = CURSOR_MOVE }); |
| 789 | } |
| 790 | |
| 791 | /* Assert that the program exits with the requested status. */ |
| 792 | static void assert_program_exits(int argc, char **argv, int clear_runtime, int code) { |
| 793 | int status; |
| 794 | pid_t pid = fork(); |
| 795 | |
| 796 | assert(pid >= 0); |
| 797 | |
| 798 | if (pid == 0) { |
| 799 | close(STDERR_FILENO); |
| 800 | |
| 801 | if (clear_runtime) |
| 802 | unsetenv("XDG_RUNTIME_DIR"); |
| 803 | optind = 1; |
| 804 | _exit(swm_program_main(argc, argv)); |
| 805 | } |
| 806 | assert(waitpid(pid, &status, 0) == pid); |
| 807 | assert(WIFEXITED(status) && WEXITSTATUS(status) == code); |
| 808 | } |
| 809 | |
| 810 | /* Assert that the program exits with failure status. */ |
| 811 | static void assert_program_fails(int argc, char **argv, int clear_runtime) { |
| 812 | assert_program_exits(argc, argv, clear_runtime, 1); |
| 813 | } |
| 814 | |
| 815 | /* Verify version output and invalid command-line invocations. */ |
| 816 | static void test_command_line_errors(void) { |
| 817 | char *version[] = { "swm", "-v", nullptr }; |
| 818 | char *help[] = { "swm", "-h", nullptr }; |
| 819 | char *extra[] = { "swm", "extra", nullptr }; |
| 820 | char *missing_runtime[] = { "swm", nullptr }; |
| 821 | |
| 822 | assert_program_exits(2, version, 0, 0); /* -v prints the version and succeeds. */ |
| 823 | assert_program_fails(2, help, 0); |
| 824 | assert_program_fails(2, extra, 0); |
| 825 | assert_program_fails(1, missing_runtime, 1); |
| 826 | } |
| 827 | |
| 828 | /* Verify pointer-constraint and decoration lifecycle handlers. */ |
| 829 | static void test_pointer_and_decoration_lifecycle(void) { |
| 830 | struct wlr_pointer_constraint_v1 constraint = {}; |
| 831 | struct wlr_surface surface = {}; |
| 832 | pointer_constraint_t *wrapper = nullptr; |
| 833 | struct wlr_xdg_surface xdg = {}; |
| 834 | struct wlr_xdg_toplevel toplevel = {}; |
| 835 | struct wlr_xdg_toplevel_decoration_v1 decoration = {}; |
| 836 | client_t client = {}; |
| 837 | struct wlr_seat fake_seat = {}; |
| 838 | size_t i; |
| 839 | |
| 840 | seat = &fake_seat; |
| 841 | constraint.surface = &surface; |
| 842 | wl_signal_init(&constraint.events.destroy); |
| 843 | create_pointer_constraint(nullptr, &constraint); |
| 844 | |
| 845 | for (i = 0; i < LENGTH(pointer_constraint_used); i++) |
| 846 | |
| 847 | if (pointer_constraint_used[i]) { |
| 848 | wrapper = &pointer_constraint_items[i]; |
| 849 | break; |
| 850 | } |
| 851 | assert(wrapper && wrapper->constraint == &constraint); |
| 852 | destroy_pointer_constraint(&wrapper->destroy, nullptr); |
| 853 | assert(!pointer_constraint_used[wrapper - pointer_constraint_items]); |
| 854 | cursor_constrain(nullptr); |
| 855 | cursor_constrain(nullptr); |
| 856 | |
| 857 | client.type = XDG_SHELL; |
| 858 | client.surface.xdg = &xdg; |
| 859 | client.decoration = &decoration; |
| 860 | xdg.toplevel = &toplevel; |
| 861 | toplevel.base = &xdg; |
| 862 | decoration.toplevel = &toplevel; |
| 863 | decoration.requested_mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; |
| 864 | request_decoration_mode(&client.set_decoration_mode, nullptr); |
| 865 | assert(client.bw == 0); |
| 866 | maximize_notify(&client.maximize, nullptr); |
| 867 | create_decoration(nullptr, &decoration); |
| 868 | urgent(nullptr, &(struct wlr_xdg_activation_v1_request_activate_event){}); |
| 869 | } |
| 870 | |
| 871 | /* Verify modifier handling, repeatable bindings, and key-repeat dispatch. */ |
| 872 | static void test_keybindings_and_repeat(void) { |
| 873 | struct wlr_seat fake_seat = {}; |
| 874 | struct wlr_keyboard_group keyboard_group = {}; |
| 875 | keyboard_group_t group = {}; |
| 876 | struct wl_event_loop *loop; |
| 877 | xkb_keysym_t symbol = XKB_KEY_j; |
| 878 | |
| 879 | seat = &fake_seat; |
| 880 | selmon = nullptr; |
| 881 | wl_list_init(&fstack); |
| 882 | assert(key_binding(MOD, XKB_KEY_1, false) == 1); |
| 883 | assert(key_binding(MOD | WLR_MODIFIER_CAPS, XKB_KEY_1, false) == 1); |
| 884 | assert(key_binding(MOD, XKB_KEY_1, true) == 0); |
| 885 | assert(key_binding(MOD, XKB_KEY_j, true) == 2); |
| 886 | assert(key_binding(0, XKB_KEY_NoSymbol, 0) == 0); |
| 887 | |
| 888 | loop = wl_event_loop_create(); |
| 889 | assert(loop); |
| 890 | group.wlr_group = &keyboard_group; |
| 891 | group.nsyms = 1; |
| 892 | group.keysyms = &symbol; |
| 893 | group.mods = MOD; |
| 894 | keyboard_group.keyboard.repeat_info.rate = 25; |
| 895 | group.key_repeat_source = wl_event_loop_add_timer(loop, key_repeat, &group); |
| 896 | assert(group.key_repeat_source); |
| 897 | assert(key_repeat(&group) == 0); |
| 898 | wl_event_source_remove(group.key_repeat_source); |
| 899 | wl_event_loop_destroy(loop); |
| 900 | } |
| 901 | |
| 902 | /* Name one compositor-internal unit test callable by the Python runner. */ |
| 903 | typedef struct { |
| 904 | const char *name; |
| 905 | void (*run)(void); |
| 906 | } internal_test_t; |
| 907 | |
| 908 | /* Run the requested compositor-internal unit test. */ |
| 909 | int main(int argc, char **argv) { |
| 910 | static const internal_test_t tests[] = { |
| 911 | { "geometry", test_geometry }, |
| 912 | { "state", test_state }, |
| 913 | { "parsing", test_parsing }, |
| 914 | { "layouts", test_layouts }, |
| 915 | { "utilities", test_utilities }, |
| 916 | { "pools", test_pools }, |
| 917 | { "workspaces", test_workspace_queries }, |
| 918 | { "paths-and-borders", test_paths_and_border_policy }, |
| 919 | { "rules", test_rule_application }, |
| 920 | { "focus", test_focus_queries }, |
| 921 | { "window-state", test_window_state_storage }, |
| 922 | { "child-reaping", test_child_reaping }, |
| 923 | { "spawn-tracking", test_spawn_tracking }, |
| 924 | { "command-failure-notification", test_command_failure_notification }, |
| 925 | { "commands-without-clients", test_commands_without_clients }, |
| 926 | { "small-helpers", test_small_helpers }, |
| 927 | { "client-accessors", test_client_accessors }, |
| 928 | { "autostart", test_autostart_execution }, |
| 929 | { "pointer-gestures", test_pointer_gestures }, |
| 930 | { "input-method", test_input_method_lifecycle }, |
| 931 | { "input-early-paths", test_input_early_paths }, |
| 932 | { "command-line", test_command_line_errors }, |
| 933 | { "pointer-and-decoration", test_pointer_and_decoration_lifecycle }, |
| 934 | { "keybindings-and-repeat", test_keybindings_and_repeat }, |
| 935 | }; |
| 936 | |
| 937 | if (argc == 2 && !strcmp(argv[1], "--list")) { |
| 938 | for (size_t i = 0; i < LENGTH(tests); i++) |
| 939 | puts(tests[i].name); |
| 940 | return 0; |
| 941 | } |
| 942 | if (argc != 2) { |
| 943 | fprintf(stderr, "usage: unit --list|TEST\n"); |
| 944 | return 2; |
| 945 | } |
| 946 | for (size_t i = 0; i < LENGTH(tests); i++) { |
| 947 | if (!strcmp(argv[1], tests[i].name)) { |
| 948 | tests[i].run(); |
| 949 | return 0; |
| 950 | } |
| 951 | } |
| 952 | fprintf(stderr, "unknown internal test: %s\n", argv[1]); |
| 953 | return 2; |
| 954 | } |