summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/cocoa/test_native.c58
-rw-r--r--tests/common/test_backends.c215
-rw-r--r--tests/common/test_selection.c34
-rw-r--r--tests/helpers/run_backends.py93
-rw-r--r--tests/meson.build45
5 files changed, 445 insertions, 0 deletions
diff --git a/tests/cocoa/test_native.c b/tests/cocoa/test_native.c
new file mode 100644
index 0000000..03fde88
--- /dev/null
+++ b/tests/cocoa/test_native.c
@@ -0,0 +1,58 @@
+#define _POSIX_C_SOURCE 200809L
+#include "yasl/yasl.h"
+
+#include <assert.h>
+#include <time.h>
+
+struct state {
+ struct yasl_display *display;
+ struct yasl_surface *surface;
+ unsigned draws;
+};
+
+static void draw(struct yasl_surface *surface, struct yasl_frame *frame, void *ud)
+{
+ struct state *state = ud;
+ assert(surface == state->surface && frame->surface == surface);
+ assert(yasl_surface_display(surface) == state->display);
+ assert(frame->target.pixels);
+ struct yasl_vector2i size = yasl_surface_size(surface);
+ assert(size.x == frame->target.size.x && size.y == frame->target.size.y);
+ for (int i = 0; i < size.x * size.y; i++) frame->target.pixels[i] = 0xff4080c0;
+ struct yasl_damage_list damage = {.n = -1};
+ assert(yasl_surface_present(surface, &frame->target, &damage));
+ assert(!yasl_surface_present(surface, &frame->target, &damage));
+ state->draws++;
+ yasl_surface_destroy(surface);
+ state->surface = NULL;
+}
+
+static void test_window(enum yasl_backend request, bool null_spec)
+{
+ struct yasl_display_spec spec = {.backend = request};
+ struct state state = {.display = yasl_display_open(NULL, null_spec ? NULL : &spec)};
+ assert(state.display);
+ assert(yasl_display_backend(state.display) == YASL_BACKEND_COCOA);
+ struct yasl_surface_spec surface = {.title = "Native backend test", .size = {160, 100}};
+ state.surface = yasl_surface_create_toplevel(state.display, &surface);
+ assert(state.surface);
+ static const struct yasl_surface_cbs callbacks = {.draw = draw};
+ yasl_surface_set_callbacks(state.surface, &callbacks, &state);
+ yasl_surface_schedule_redraw(state.surface);
+ uint64_t deadline = yasl_loop_now_ns() + 3000000000ull;
+ while (!state.draws && yasl_loop_now_ns() < deadline) {
+ yasl_display_dispatch(state.display);
+ yasl_display_flush(state.display);
+ nanosleep(&(struct timespec){.tv_nsec = 1000000}, NULL);
+ }
+ assert(state.draws == 1 && !state.surface);
+ yasl_display_close(state.display);
+}
+
+int main(void)
+{
+ test_window(YASL_BACKEND_AUTO, true);
+ test_window(YASL_BACKEND_AUTO, false);
+ test_window(YASL_BACKEND_COCOA, false);
+ return 0;
+}
diff --git a/tests/common/test_backends.c b/tests/common/test_backends.c
new file mode 100644
index 0000000..48a6b68
--- /dev/null
+++ b/tests/common/test_backends.c
@@ -0,0 +1,215 @@
+#define _POSIX_C_SOURCE 200809L
+#include "yasl/yasl.h"
+
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+#include <assert.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/timerfd.h>
+#include <time.h>
+#include <unistd.h>
+
+struct window {
+ struct yasl_surface *surface;
+ struct yasl_display *display;
+ int draws, configures, closes;
+ bool destroy_on_draw, destroy_on_configure;
+};
+
+struct pump {
+ struct yasl_loop *loop;
+ struct yasl_display *displays[2];
+ int ticks;
+};
+
+static int tick(int fd, short events, void *ud)
+{
+ (void)events;
+ uint64_t expirations;
+ assert(read(fd, &expirations, sizeof(expirations)) == sizeof(expirations));
+ struct pump *p = ud;
+ for (int i = 0; i < 2; i++) {
+ if (p->displays[i]) {
+ yasl_display_dispatch(p->displays[i]);
+ yasl_display_flush(p->displays[i]);
+ }
+ }
+ if (--p->ticks == 0) yasl_loop_quit(p->loop);
+ return 0;
+}
+
+static void pump(struct yasl_loop *loop, struct yasl_display *a, struct yasl_display *b)
+{
+ int timer = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
+ assert(timer >= 0);
+ struct pump p = {.loop = loop, .displays = {a, b}, .ticks = 20};
+ struct itimerspec spec = {.it_value.tv_nsec = 10000000, .it_interval.tv_nsec = 10000000};
+ assert(timerfd_settime(timer, 0, &spec, NULL) == 0);
+ int watch = yasl_loop_add_fd(loop, timer, POLLIN, tick, &p);
+ assert(watch > 0);
+ assert(yasl_loop_run(loop) == 0);
+ assert(yasl_loop_del_fd(loop, watch) == 0);
+ close(timer);
+}
+
+static void draw(struct yasl_surface *s, struct yasl_frame *frame, void *ud)
+{
+ struct window *w = ud;
+ assert(s == w->surface);
+ assert(frame->surface == s);
+ assert(yasl_surface_display(s) == w->display);
+ struct yasl_vector2i size = yasl_surface_size(s);
+ assert(size.x == frame->target.size.x && size.y == frame->target.size.y);
+ assert(frame->target.pixels);
+ for (int i = 0; i < size.x * size.y; i++) frame->target.pixels[i] = 0xff4080c0;
+ struct yasl_damage_list damage = {.n = -1};
+ assert(yasl_surface_present(s, &frame->target, &damage));
+ assert(!yasl_surface_present(s, &frame->target, &damage));
+ w->draws++;
+ if (w->destroy_on_draw) { yasl_surface_destroy(s); w->surface = NULL; }
+}
+
+static void configure(struct yasl_surface *s, struct yasl_geometry geometry, void *ud)
+{
+ struct window *w = ud;
+ assert(w->surface == s);
+ struct yasl_vector2i size = yasl_surface_size(s);
+ assert(geometry.size.x == size.x && geometry.size.y == size.y);
+ assert(geometry.scale_120 == yasl_surface_scale_120(s));
+ w->configures++;
+ if (w->destroy_on_configure) { yasl_surface_destroy(s); w->surface = NULL; }
+}
+
+static void close_window(struct yasl_surface *s, void *ud)
+{
+ struct window *w = ud;
+ assert(s == w->surface);
+ w->closes++;
+ yasl_surface_destroy(s);
+ w->surface = NULL;
+}
+
+static const struct yasl_surface_cbs callbacks = {
+ .draw = draw, .configure = configure, .close = close_window,
+};
+
+static struct window create(struct yasl_display *d)
+{
+ struct yasl_surface_spec spec = {.title = "Backend test", .size = {160, 100}};
+ struct yasl_surface *s = yasl_surface_create_toplevel(d, &spec);
+ assert(s);
+ assert(yasl_surface_display(s) == d);
+ return (struct window){.surface = s, .display = d};
+}
+
+static void x11_behavior(struct yasl_loop *loop, struct yasl_display *d, struct window *w)
+{
+ Display *native = yasl_display_get_native_handle(d);
+ Window window = (Window)(uintptr_t)yasl_surface_get_native_handle(w->surface);
+ assert(native && window);
+ XSync(native, False);
+ XImage *image = XGetImage(native, window, 0, 0, 1, 1, AllPlanes, ZPixmap);
+ assert(image && (XGetPixel(image, 0, 0) & 0xffffff) == 0x4080c0);
+ XDestroyImage(image);
+ yasl_surface_set_title(w->surface, "Updated title");
+ char *title = NULL;
+ assert(XFetchName(native, window, &title));
+ assert(strcmp(title, "Updated title") == 0);
+ XFree(title);
+ XResizeWindow(native, window, 200, 120);
+ XFlush(native);
+ pump(loop, d, NULL);
+ assert(w->configures > 0);
+ struct yasl_vector2i size = yasl_surface_size(w->surface);
+ assert(size.x == 200 && size.y == 120);
+ XEvent key = {0};
+ key.xkey = (XKeyEvent){.type = KeyPress, .display = native, .window = window,
+ .root = DefaultRootWindow(native), .same_screen = True,
+ .keycode = XKeysymToKeycode(native, XK_a)};
+ XSendEvent(native, window, False, KeyPressMask, &key);
+ XFlush(native);
+ yasl_display_copy(d, YASL_CLIPBOARD, "clipboard text");
+ yasl_display_paste(d, YASL_CLIPBOARD, w->surface);
+ pump(loop, d, NULL);
+ bool saw_key = false, saw_paste = false;
+ struct yasl_input_event event;
+ while (yasl_surface_input_poll(w->surface, &event)) {
+ assert(event.surface == w->surface);
+ if (event.kind == YASL_INPUT_KEY) {
+ assert(event.key.codepoint == 'a');
+ assert(event.key.shifted == 'A');
+ assert(event.key.base == 30);
+ saw_key = event.key.key == XK_a;
+ }
+ if (event.kind == YASL_INPUT_PASTE) {
+ assert(event.paste.len == strlen("clipboard text"));
+ assert(memcmp(event.paste.text, "clipboard text", event.paste.len) == 0);
+ saw_paste = true;
+ }
+ }
+ assert(saw_key && saw_paste);
+ key.xclient = (XClientMessageEvent){.type = ClientMessage, .display = native,
+ .window = window, .message_type = XInternAtom(native, "WM_PROTOCOLS", False),
+ .format = 32, .data.l = {(long)XInternAtom(native, "WM_DELETE_WINDOW", False)}};
+ XSendEvent(native, window, False, NoEventMask, &key);
+ XFlush(native);
+ pump(loop, d, NULL);
+ assert(w->closes == 1 && !w->surface);
+}
+
+int main(int argc, char **argv)
+{
+ assert(argc >= 2);
+ struct yasl_loop *loop = yasl_loop_create();
+ assert(loop);
+ if (strcmp(argv[1], "failure") == 0) {
+ struct yasl_display_spec spec = {.backend = (enum yasl_backend)atoi(argv[2])};
+ assert(!yasl_display_open(loop, &spec));
+ } else if (strcmp(argv[1], "mixed") == 0) {
+ struct yasl_display_spec spec = {.backend = YASL_BACKEND_WAYLAND};
+ struct yasl_display *a = yasl_display_open(loop, &spec);
+ spec.backend = YASL_BACKEND_X11;
+ struct yasl_display *b = yasl_display_open(loop, &spec);
+ assert(a && b);
+ struct window wa = create(a), wb = create(b);
+ yasl_surface_set_callbacks(wa.surface, &callbacks, &wa);
+ yasl_surface_set_callbacks(wb.surface, &callbacks, &wb);
+ pump(loop, a, b);
+ assert(wa.draws && wb.draws);
+ yasl_surface_destroy(wa.surface);
+ yasl_surface_destroy(wb.surface);
+ yasl_display_close(a);
+ yasl_display_close(b);
+ } else {
+ assert(argc == 4);
+ struct yasl_display_spec spec = {.backend = (enum yasl_backend)atoi(argv[2])};
+ struct yasl_display *d = strcmp(argv[1], "null") == 0 ?
+ yasl_display_open(loop, NULL) : yasl_display_open(loop, &spec);
+ assert(d);
+ assert(yasl_display_backend(d) == (enum yasl_backend)atoi(argv[3]));
+ struct window w = create(d);
+ pump(loop, d, NULL);
+ assert(w.draws == 0);
+ yasl_surface_set_callbacks(w.surface, &callbacks, &w);
+ yasl_surface_schedule_redraw(w.surface);
+ pump(loop, d, NULL);
+ assert(w.draws > 0);
+ if (yasl_display_backend(d) == YASL_BACKEND_X11)
+ x11_behavior(loop, d, &w);
+ if (w.surface) yasl_surface_destroy(w.surface);
+ w = create(d);
+ w.destroy_on_draw = true;
+ yasl_surface_set_callbacks(w.surface, &callbacks, &w);
+ pump(loop, d, NULL);
+ assert(w.draws && !w.surface);
+ yasl_display_close(d);
+ }
+ yasl_loop_destroy(loop);
+ puts("Backend test passed.");
+ return 0;
+}
diff --git a/tests/common/test_selection.c b/tests/common/test_selection.c
new file mode 100644
index 0000000..d1b3eb8
--- /dev/null
+++ b/tests/common/test_selection.c
@@ -0,0 +1,34 @@
+#define _POSIX_C_SOURCE 200809L
+#include "yasl/yasl.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+
+int main(void)
+{
+ struct yasl_display_spec spec = {.backend = (enum yasl_backend)999};
+ errno = 0;
+ assert(!yasl_display_open(NULL, &spec));
+ assert(errno == EINVAL);
+ assert(yasl_display_backend(NULL) == YASL_BACKEND_AUTO);
+#if !YASL_HAS_COCOA
+ spec.backend = YASL_BACKEND_COCOA;
+ errno = 0;
+ assert(!yasl_display_open(NULL, &spec));
+ assert(errno == ENOSYS);
+#endif
+#if !YASL_HAS_WAYLAND
+ spec.backend = YASL_BACKEND_WAYLAND;
+ errno = 0;
+ assert(!yasl_display_open(NULL, &spec));
+ assert(errno == ENOSYS);
+#endif
+#if !YASL_HAS_X11
+ spec.backend = YASL_BACKEND_X11;
+ errno = 0;
+ assert(!yasl_display_open(NULL, &spec));
+ assert(errno == ENOSYS);
+#endif
+ return 0;
+}
diff --git a/tests/helpers/run_backends.py b/tests/helpers/run_backends.py
new file mode 100644
index 0000000..9ad9b0d
--- /dev/null
+++ b/tests/helpers/run_backends.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+"""Test public window behavior on isolated display servers."""
+
+import os
+from pathlib import Path
+import subprocess
+import sys
+import tempfile
+import time
+
+
+def wait_for(process, ready):
+ deadline = time.monotonic() + 10
+ while time.monotonic() < deadline:
+ if process.poll() is not None:
+ raise RuntimeError("The display server stopped.")
+ if ready():
+ return
+ time.sleep(0.02)
+ raise TimeoutError("The display server did not start.")
+
+
+def main():
+ weston, xwayland, test = sys.argv[1:]
+ servers = []
+ with tempfile.TemporaryDirectory(prefix="yasl-backends-") as directory:
+ root = Path(directory)
+ env = os.environ.copy()
+ env.pop("WAYLAND_SOCKET", None)
+ env.update(XDG_RUNTIME_DIR=directory, WAYLAND_DISPLAY="yasl-test",
+ XDG_SESSION_TYPE="wayland")
+ with (root / "weston.log").open("w+") as wl_log, \
+ (root / "xwayland.log").open("w+") as x_log:
+ try:
+ wl = subprocess.Popen([
+ weston, "--backend=headless", "--renderer=pixman",
+ "--socket=yasl-test", "--idle-time=0", "--no-config",
+ ], env=env, stdout=wl_log, stderr=wl_log)
+ servers.append(wl)
+ wait_for(wl, lambda: (root / "yasl-test").is_socket())
+ display_file = root / "display"
+ with display_file.open("w") as display_fd:
+ x = subprocess.Popen([
+ xwayland, "-displayfd", str(display_fd.fileno()),
+ "-shm", "-noreset", "-nolisten", "tcp",
+ ], env=env, pass_fds=(display_fd.fileno(),), stdout=x_log, stderr=x_log)
+ servers.append(x)
+ wait_for(x, lambda: bool(display_file.read_text().strip()))
+ env["DISPLAY"] = ":" + display_file.read_text().strip()
+
+ def run(mode, request=None, expected=None, **changes):
+ case_env = env.copy()
+ for key, value in changes.items():
+ if value is None:
+ case_env.pop(key, None)
+ else:
+ case_env[key] = value
+ args = [test, mode]
+ if request is not None:
+ args.append(str(request))
+ if expected is not None:
+ args.append(str(expected))
+ print("Test:", mode, request, expected, changes, flush=True)
+ subprocess.run(args, env=case_env, check=True, timeout=8)
+
+ run("open", 1, 1)
+ run("open", 2, 2)
+ run("open", 0, 1)
+ run("null", 0, 1)
+ run("open", 0, 2, WAYLAND_DISPLAY=None, XDG_SESSION_TYPE="x11")
+ run("open", 0, 2, WAYLAND_DISPLAY="missing-socket")
+ run("failure", 1, WAYLAND_DISPLAY="missing-socket")
+ run("failure", 2, DISPLAY=":59999")
+ run("failure", 0, WAYLAND_DISPLAY="missing-socket", DISPLAY=":59999")
+ run("mixed")
+ except BaseException:
+ for log in (wl_log, x_log):
+ log.flush()
+ log.seek(0)
+ print(log.read(), file=sys.stderr)
+ raise
+ finally:
+ for server in reversed(servers):
+ server.terminate()
+ try:
+ server.wait(timeout=5)
+ except subprocess.TimeoutExpired:
+ server.kill()
+ server.wait()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/meson.build b/tests/meson.build
new file mode 100644
index 0000000..950f5f5
--- /dev/null
+++ b/tests/meson.build
@@ -0,0 +1,45 @@
+test_exe = executable(
+ 'yasl-test',
+ 'test_lifecycle.c',
+ include_directories: inc,
+ link_with: yasl_lib.get_static_lib(),
+ dependencies: deps,
+)
+
+add_test_setup('asan')
+add_test_setup('tsan')
+add_test_setup('msan')
+
+test('basic', test_exe)
+
+selection_test = executable(
+ 'yasl-selection-test',
+ 'common/test_selection.c',
+ dependencies: yasl_dep,
+)
+test('selection', selection_test)
+
+if get_option('integration_tests')
+ if has_cocoa
+ native_test = executable('yasl-native-test', 'cocoa/test_native.c',
+ dependencies: yasl_dep,
+ )
+ test('native', native_test, timeout: 20)
+ else
+ if not has_wayland or not has_x11
+ error('Linux integration tests require Wayland and X11.')
+ endif
+ backend_test = executable(
+ 'yasl-backend-test',
+ 'common/test_backends.c',
+ dependencies: yasl_dep,
+ )
+ python = find_program('python3')
+ weston = find_program('weston')
+ xwayland = find_program('Xwayland')
+ test('backends', python,
+ args: [files('helpers/run_backends.py'), weston, xwayland, backend_test],
+ timeout: 60,
+ )
+ endif
+endif